@routecraft/cli 0.7.0 → 0.7.1-canary-20260924104103
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 +2 -2
- package/dist/{acp-JCKR7JQK.js → acp-RSGIIDTK.js} +3 -3
- package/dist/{acp-JCKR7JQK.js.map → acp-RSGIIDTK.js.map} +1 -1
- package/dist/chunk-FFSMJDUF.js +2 -0
- package/dist/chunk-FFSMJDUF.js.map +1 -0
- package/dist/{chunk-5YILB3H6.js → chunk-HNPTPH33.js} +2 -2
- package/dist/{chunk-5YILB3H6.js.map → chunk-HNPTPH33.js.map} +1 -1
- package/dist/chunk-OKX5YME7.js +3 -0
- package/dist/chunk-OKX5YME7.js.map +1 -0
- package/dist/chunk-TSLKIWOL.js +2 -0
- package/dist/chunk-TSLKIWOL.js.map +1 -0
- package/dist/{chunk-PE5G4H72.js → chunk-V56CL7XK.js} +3 -3
- package/dist/{chunk-PE5G4H72.js.map → chunk-V56CL7XK.js.map} +1 -1
- package/dist/{chunk-KHUGU7ED.js → chunk-ZCDYYJUE.js} +3 -3
- package/dist/{chunk-KHUGU7ED.js.map → chunk-ZCDYYJUE.js.map} +1 -1
- package/dist/exec-4SOBSVRY.js +2 -0
- package/dist/{exec-XCYDOTN2.js.map → exec-4SOBSVRY.js.map} +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/{ops-MUE3PMNS.js → ops-7ZXRLK6M.js} +2 -2
- package/dist/{ops-MUE3PMNS.js.map → ops-7ZXRLK6M.js.map} +1 -1
- package/dist/run-BYR62OGM.js +2 -0
- package/dist/run-BYR62OGM.js.map +1 -0
- package/dist/runtime-gate-NEYB2HPB.js +2 -0
- package/dist/runtime-gate-NEYB2HPB.js.map +1 -0
- package/dist/settings-TJKUAC35.js +2 -0
- package/dist/{settings-W2CHBN4F.js.map → settings-TJKUAC35.js.map} +1 -1
- package/dist/{start-YASUN2DJ.js → start-OFODWBWZ.js} +3 -3
- package/dist/{start-YASUN2DJ.js.map → start-OFODWBWZ.js.map} +1 -1
- package/dist/tui/app.js +1 -1
- package/dist/util-CSBMQ2PQ.js +2 -0
- package/dist/{util-XVJ6ODCT.js.map → util-CSBMQ2PQ.js.map} +1 -1
- package/package.json +2 -2
- package/dist/chunk-ILBYIHLE.js +0 -2
- package/dist/chunk-ILBYIHLE.js.map +0 -1
- package/dist/chunk-REGB7OFD.js +0 -2
- package/dist/chunk-REGB7OFD.js.map +0 -1
- package/dist/exec-XCYDOTN2.js +0 -2
- package/dist/run-N7QNCQ6G.js +0 -2
- package/dist/run-N7QNCQ6G.js.map +0 -1
- package/dist/settings-W2CHBN4F.js +0 -2
- package/dist/util-XVJ6ODCT.js +0 -2
package/README.md
CHANGED
|
@@ -35,7 +35,7 @@ A personal settings file, `.routecraft/settings.yaml`, holds named profiles with
|
|
|
35
35
|
|
|
36
36
|
### Environment
|
|
37
37
|
|
|
38
|
-
`run` and `start` load `.env`, then `.env.<profile>` when a profile is selected, then `.env.local` from the project directory, each overriding the one before. `--env <path>` replaces the cascade with one file.
|
|
38
|
+
`run` and `start` load `.env`, then `.env.<profile>` when a profile is selected, then `.env.local` from the project directory, each overriding the one before. `--env <path>` replaces the cascade with one file. A named file that cannot be read stops the command, as does a cascade file that exists but cannot be read; a missing cascade file is skipped.
|
|
39
39
|
|
|
40
40
|
### Logging
|
|
41
41
|
|
|
@@ -44,7 +44,7 @@ A personal settings file, `.routecraft/settings.yaml`, holds named profiles with
|
|
|
44
44
|
--log-file <path> Write logs to a file instead of stdout
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
Both
|
|
47
|
+
Both go before the command, or after `start` and `run` as their own options (on `run`, before the file): `craft run --log-level info --log-file craft.log <file>`. `LOG_LEVEL` and `LOG_FILE` in any env file `run` or `start` loads work too, and a flag beats them. A log file that cannot be opened for appending stops the command before it runs, whether the flag, `LOG_FILE` or a `craft.log.js` / `craft.log.cjs` named it.
|
|
48
48
|
|
|
49
49
|
The CLI handles graceful shutdown on `SIGINT` / `SIGTERM` (a repeated one is ignored) and forces an immediate exit on `SIGQUIT` / `SIGBREAK`. TypeScript files are supported directly; Bun strips types natively, so there is no build step.
|
|
50
50
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {a as a$1,k as k$1}from'./chunk-
|
|
2
|
-
`))});return l.kind==="editor-closed"?{code:0}:l.kind==="editor-error"?{code:k$1.failed,error:`The editor's side of the pipe failed: ${b$2(l.error)}`}:{code:k$1.unreachable,error:`Could not reach ${h} (from the ${e(e$1.url)}): ${b$2(l.error)}`}}export{D as ACP_AGENT_HEADER,Q as acpCommand};//# sourceMappingURL=acp-
|
|
3
|
-
//# sourceMappingURL=acp-
|
|
1
|
+
import {a as a$1,k as k$1}from'./chunk-ZCDYYJUE.js';import {d as d$1,b as b$1,e}from'./chunk-V56CL7XK.js';import {b as b$2}from'./chunk-FFSMJDUF.js';import {Readable}from'stream';var T=[250,500,1e3,2e3,4e3,5e3],q=1e4,$=12,b=-32e3,A=-32002,P="craft-acp:",I=200,a={initialize:"initialize",newSession:"session/new",loadSession:"session/load",resumeSession:"session/resume",closeSession:"session/close",prompt:"session/prompt"};function E(r){return new g(r).run()}var g=class{constructor(e){this.options=e;this.editorWriter=e.editor.writable.getWriter(),this.delays=e.delays??T,this.handshakeTimeoutMs=e.handshakeTimeoutMs??q;}options;state="connected";everConnected=false;writer;editorWriter;editorChain=Promise.resolve();initialize;sessions=new Map;editorRequests=new Map;instanceRequests=new Set;ownRequests=new Map;queue=[];ownSequence=0;reconnecting=false;flushing=false;generation=0;lastFailureReason;delays;handshakeTimeoutMs;stopped=new AbortController;settle;settled=new Promise(e=>{this.settle=e;});async run(){try{this.attach(this.options.connect());}catch(e){return this.lost(e),this.settled}return this.readEditor(),this.settled}async readEditor(){try{await k(this.options.editor.readable,e=>this.fromEditor(e));}catch(e){this.editorClosed(true,e);return}this.editorClosed(false);}async fromEditor(e){if(this.state!=="done"){if(m(e)){let t=d(e.id);if(!this.instanceRequests.has(t))return;this.instanceRequests.delete(t),await this.toInstance(e);return}if(e.method===a.initialize&&u(e)&&(this.initialize=e),u(e)){let t=O(e);if(this.editorRequests.set(d(e.id),{id:e.id,method:e.method,...typeof t.sessionId=="string"?{sessionId:t.sessionId}:{},...typeof t.cwd=="string"?{cwd:t.cwd}:{}}),e.method===a.closeSession){let n=t.sessionId;typeof n=="string"&&this.sessions.delete(n);}}if(this.state!=="connected"||this.flushing){if(this.queue.length>=I){u(e)&&(this.editorRequests.delete(d(e.id)),this.toEditor({jsonrpc:"2.0",id:e.id,error:{code:b,message:`Lost the connection to the instance; too many messages queued during the outage, "${e.method}" was dropped rather than queued.`}}));return}this.queue.push(e);return}await this.toInstance(e);}}async toInstance(e){let t=this.writer;if(t===void 0){this.queue.unshift(e);return}try{await t.write(e);}catch(n){let i=u(e)?d(e.id):void 0;i!==void 0&&!this.editorRequests.has(i)||this.queue.unshift(e),this.lost(n);}}toEditor(e){this.editorChain=this.editorChain.then(()=>this.editorWriter.write(e)).catch(t=>this.editorClosed(true,t));}editorClosed(e,t){if(this.state!=="done"){this.state="done",this.stopped.abort();for(let n of this.ownRequests.values())n.reject(new Error("the editor closed"));this.ownRequests.clear(),this.writer?.close().catch(()=>{}),this.editorWriter.close().catch(()=>{}),this.settle(e?{kind:"editor-error",error:t}:{kind:"editor-closed"});}}attach(e){this.generation+=1;let t=this.generation;this.writer=e.writable.getWriter(),this.readInstance(e,t);}async readInstance(e,t){let n=new Error("the instance closed the connection");try{await k(e.readable,i=>{this.fromInstance(i,t);});}catch(i){n=i;}this.state==="done"||t!==this.generation||this.lost(n);}fromInstance(e,t){if(!(this.state==="done"||t!==this.generation)){if(this.everConnected=true,m(e)){let n=d(e.id),i=this.ownRequests.get(n);if(i!==void 0){this.ownRequests.delete(n),i.resolve(e);return}let s=this.editorRequests.get(n);this.editorRequests.delete(n),s!==void 0&&e.error===void 0&&this.noteAttached(s,e),this.toEditor(e);return}u(e)&&this.instanceRequests.add(d(e.id)),this.toEditor(e);}}noteAttached(e,t){if(e.cwd!==void 0){if(e.method===a.newSession){let n=t.result;typeof n?.sessionId=="string"&&this.sessions.set(n.sessionId,e.cwd);return}(e.method===a.loadSession||e.method===a.resumeSession)&&e.sessionId!==void 0&&this.sessions.set(e.sessionId,e.cwd);}}lost(e){if(this.state==="done"||this.state==="lost")return;if(!this.everConnected){this.state="done",this.stopped.abort(),this.editorWriter.close().catch(()=>{}),this.settle({kind:"unreachable",error:e});return}let t=this.state==="connecting";this.generation+=1,this.state="lost",this.writer?.abort().catch(()=>{}),this.writer=void 0;for(let n of this.ownRequests.values())n.reject(e);if(this.ownRequests.clear(),t){this.lastFailureReason=b$2(e);return}this.lastFailureReason=void 0,this.options.log(`Lost the connection to ${this.options.target}: ${b$2(e)}. Waiting for it to come back.`),this.failInFlight(),this.instanceRequests.clear(),this.reconnect();}failInFlight(){let e=new Set(this.queue.filter(u).map(t=>d(t.id)));for(let[t,n]of this.editorRequests)e.has(t)||(this.toEditor(n.method===a.prompt?{jsonrpc:"2.0",id:n.id,result:{stopReason:"cancelled"}}:{jsonrpc:"2.0",id:n.id,error:{code:b,message:`Lost the connection to the instance while "${n.method}" was in flight; the request was not answered.`}}),this.editorRequests.delete(t));}async reconnect(){if(this.reconnecting)return;this.reconnecting=true;let e=Date.now(),t=0;try{for(;this.state==="lost";){if(await C(this.delays[Math.min(t,this.delays.length-1)]??0,this.stopped.signal),this.state!=="lost")return;t+=1,t>1&&t%$===1&&this.options.log(`Still waiting for ${this.options.target} (attempt ${t}, ${S(e)})${this.lastFailureReason!==void 0?`: ${this.lastFailureReason}`:""}.`),this.state="connecting";try{let n=this.options.connect();this.attach(n),await _(this.handshake(),this.handshakeTimeoutMs);}catch(n){this.lost(n);continue}this.state==="connecting"&&(this.state="connected",this.options.log(`Reconnected to ${this.options.target} after ${t} attempt${t===1?"":"s"} (${S(e)}).`),await this.flush());}}finally{this.reconnecting=false;}}async handshake(){if(this.initialize===void 0)throw new Error("the editor never initialized");let e=await this.own(a.initialize,this.initialize.params);if(e.error!==void 0)throw new Error(`initialize was refused: ${e.error.message}`);for(let[t,n]of [...this.sessions]){let i=await this.own(a.resumeSession,{sessionId:t,cwd:n,mcpServers:[]});if(i.error!==void 0){if(i.error.code===A){this.sessions.delete(t),this.options.log(`Conversation ${t} is not on ${this.options.target} any more (${i.error.message}). Start a new one from the editor.`);continue}this.options.log(`Conversation ${t} could not be resumed on ${this.options.target} (${i.error.message}). It is kept, and the next reconnection tries again.`);}}}own(e,t){let n=this.writer;if(n===void 0)return Promise.reject(new Error("no transport"));this.ownSequence+=1;let i=`${P}${this.ownSequence}`,s=new Promise((h,p)=>{this.ownRequests.set(d(i),{resolve:h,reject:p});});return s.catch(()=>{}),n.write({jsonrpc:"2.0",id:i,method:e,params:t}).then(()=>s)}async flush(){this.flushing=true;try{for(;this.queue.length>0&&this.state==="connected";){let e=this.queue.shift();m(e)||await this.toInstance(e);}}finally{this.flushing=false;}}};async function k(r,e){let t=r.getReader();try{for(;;){let{done:n,value:i}=await t.read();if(n)return;await e(i);}}finally{t.releaseLock();}}function u(r){return typeof r.method=="string"&&r.id!==void 0&&r.id!==null}function m(r){return r.method===void 0&&r.id!==void 0&&r.id!==null&&("result"in r||"error"in r)}function d(r){return `${typeof r}:${String(r)}`}function O(r){return r.params!==null&&typeof r.params=="object"?r.params:{}}function S(r){return `${Math.round((Date.now()-r)/1e3)}s`}function C(r,e){return new Promise(t=>{if(e.aborted){t();return}let n=()=>{clearTimeout(i),e.removeEventListener("abort",n),t();},i=setTimeout(n,r);e.addEventListener("abort",n,{once:true});})}function _(r,e){return new Promise((t,n)=>{let i=setTimeout(()=>n(new Error(`the handshake took longer than ${e}ms`)),e);r.then(s=>{clearTimeout(i),t(s);},s=>{clearTimeout(i),n(s);});})}var W="/acp",D="Routecraft-Agent";function U(){return new WritableStream({write(r){return new Promise((e,t)=>{process.stdout.write(r,n=>n?t(n):e());})}})}function x(){return Readable.toWeb(process.stdin)}async function Q(r={}){let e$1;try{e$1=d$1(r);}catch(o){if(o instanceof b$1)return {code:2,error:o.message};throw o}let{ndJsonStream:t}=await import('@agentclientprotocol/sdk'),{createHttpStream:n,MemoryAcpCookieStore:i}=await import('@agentclientprotocol/sdk/experimental/http-client'),s={};if(e$1.token!==void 0){try{a$1(e$1);}catch(o){if(o instanceof b$1)return {code:2,error:o.message};throw o}s.Authorization=`Bearer ${e$1.token.value}`;}e$1.agent!==void 0&&(s[D]=e$1.agent.value);let h=`${e$1.url.value.replace(/\/+$/,"")}${W}`,p=new i,M=t(r.stdout??U(),r.stdin??x()),l=await E({editor:M,connect:()=>n(h,{headers:s,cookieStore:p}),target:h,log:r.stderr??(o=>process.stderr.write(`${o}
|
|
2
|
+
`))});return l.kind==="editor-closed"?{code:0}:l.kind==="editor-error"?{code:k$1.failed,error:`The editor's side of the pipe failed: ${b$2(l.error)}`}:{code:k$1.unreachable,error:`Could not reach ${h} (from the ${e(e$1.url)}): ${b$2(l.error)}`}}export{D as ACP_AGENT_HEADER,Q as acpCommand};//# sourceMappingURL=acp-RSGIIDTK.js.map
|
|
3
|
+
//# sourceMappingURL=acp-RSGIIDTK.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/acp-bridge.ts","../src/acp.ts"],"names":["DEFAULT_DELAYS_MS","DEFAULT_HANDSHAKE_TIMEOUT_MS","ATTEMPTS_PER_LOG","CONNECTION_LOST","RESOURCE_NOT_FOUND","OWN_ID_PREFIX","MAX_QUEUED","METHOD","runBridge","options","Bridge","resolve","error","readEach","message","isResponse","key","keyOf","isRequest","params","paramsOf","sessionId","writer","hadError","own","transport","generation","failure","request","response","result","wasConnecting","messageOf","stillQueued","startedAt","attempt","sleep","elapsed","withTimeout","initialized","cwd","resumed","method","id","reject","readable","each","reader","done","value","since","ms","signal","timer","promise","ACP_PATH","ACP_AGENT_HEADER","stdoutStream","chunk","stdinStream","Readable","acpCommand","settings","resolveSettings","SettingsError","ndJsonStream","createHttpStream","MemoryAcpCookieStore","headers","refuseClearTextBearer","url","cookieStore","editor","outcome","line","EXEC_EXIT","describeSource"],"mappings":"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","file":"acp-JCKR7JQK.js","sourcesContent":["/**\n * The relay behind `craft acp`: a pipe that re-establishes itself.\n *\n * Every message crosses it verbatim while the instance is up. When the\n * instance goes away (a restart under an open editor window is the usual\n * case), the editor's side stays open and the relay reconnects: a fresh\n * transport, the editor's own `initialize` replayed, and `session/resume`\n * for every conversation the editor had attached, so the editor keeps\n * talking to the same conversation without knowing anything happened.\n *\n * The protocol does not replay what was in flight when the transport\n * died, so the relay answers for it: a `session/prompt` that was waiting\n * gets `cancelled` (the protocol's own word for a turn that ended with no\n * reply), any other request gets an error naming the outage, and an\n * answer the editor later gives to a request the dead connection made is\n * dropped rather than posted to a connection that never asked. Everything\n * the editor sends during the outage is queued and delivered once the\n * handshake on the new transport has completed.\n *\n * The first connection is different: an address nothing answers on is a\n * configuration mistake, and the relay reports it rather than waiting for\n * it to come right. Reconnection is for a connection that once worked.\n */\n\nimport { messageOf } from \"./util.js\";\n\n/** A JSON-RPC 2.0 message as it crosses the relay. */\nexport interface RpcMessage {\n readonly jsonrpc: \"2.0\";\n readonly id?: string | number | null;\n readonly method?: string;\n readonly params?: unknown;\n readonly result?: unknown;\n readonly error?: { code: number; message: string; data?: unknown };\n}\n\n/** One side of the relay: what it reads from and writes to. */\nexport interface BridgeTransport {\n readonly readable: ReadableStream<RpcMessage>;\n readonly writable: WritableStream<RpcMessage>;\n}\n\nexport interface BridgeOptions {\n /** The editor's side, open for the life of the relay. */\n readonly editor: BridgeTransport;\n /**\n * A fresh transport to the instance. Called once at the start and once\n * per reconnection attempt; a transport is never reused after it fails.\n */\n readonly connect: () => BridgeTransport;\n /** Where the instance is, for the diagnostics. */\n readonly target: string;\n /** One diagnostic line, to standard error in the command. */\n readonly log: (line: string) => void;\n /**\n * Delays between reconnection attempts, in milliseconds. The last one\n * repeats until the editor closes.\n */\n readonly delays?: readonly number[];\n /** How long one handshake may take before the attempt is abandoned. */\n readonly handshakeTimeoutMs?: number;\n}\n\n/** How the relay ended. */\nexport type BridgeOutcome =\n | { readonly kind: \"editor-closed\" }\n | { readonly kind: \"editor-error\"; readonly error: unknown }\n | { readonly kind: \"unreachable\"; readonly error: unknown };\n\n/** Doubling from a quarter second to five, which then repeats. */\nconst DEFAULT_DELAYS_MS: readonly number[] = [\n 250, 500, 1_000, 2_000, 4_000, 5_000,\n];\n\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000;\n\n/** Attempts between \"still trying\" lines, so an outage does not fill stderr. */\nconst ATTEMPTS_PER_LOG = 12;\n\n/**\n * JSON-RPC's implementation-defined server error range starts here. The\n * protocol defines no code for a lost connection, and this is the one the\n * SDK's own `RequestError` family leaves free.\n */\nconst CONNECTION_LOST = -32000;\n\n/**\n * The protocol's resource-not-found code, which the instance answers a\n * `session/resume` with when it no longer holds the conversation.\n *\n * The one refusal the relay drops a conversation on. Any other means the\n * conversation may still exist on the instance and dropping it would lose\n * the person's work on a false premise, so it is kept and tried again on\n * the next reconnection.\n */\nconst RESOURCE_NOT_FOUND = -32002;\n\n/** The prefix on ids of requests the relay makes for itself. */\nconst OWN_ID_PREFIX = \"craft-acp:\";\n\n/** How many editor messages the relay holds while no transport can take them. */\nconst MAX_QUEUED = 200;\n\n/**\n * The methods the relay must understand to restore a connection after an\n * outage. Mirrors `@routecraft/ai`'s ACP mount; the CLI does not depend on\n * that package (see `ACP_AGENT_HEADER`'s own note in `acp.ts`), so this is\n * duplicated rather than shared, and is spelled once here rather than\n * scattered through the module.\n */\nconst METHOD = {\n initialize: \"initialize\",\n newSession: \"session/new\",\n loadSession: \"session/load\",\n resumeSession: \"session/resume\",\n closeSession: \"session/close\",\n prompt: \"session/prompt\",\n} as const;\n\ntype State = \"connected\" | \"connecting\" | \"lost\" | \"done\";\n\ninterface EditorRequest {\n readonly id: string | number;\n readonly method: string;\n readonly sessionId?: string;\n readonly cwd?: string;\n}\n\ninterface OwnRequest {\n resolve(message: RpcMessage): void;\n reject(error: unknown): void;\n}\n\n/** Run the relay until the editor closes its side, or the first connection fails. */\nexport function runBridge(options: BridgeOptions): Promise<BridgeOutcome> {\n return new Bridge(options).run();\n}\n\nclass Bridge {\n private state: State = \"connected\";\n private everConnected = false;\n private writer: WritableStreamDefaultWriter<RpcMessage> | undefined;\n private readonly editorWriter: WritableStreamDefaultWriter<RpcMessage>;\n private editorChain: Promise<void> = Promise.resolve();\n\n /** The editor's `initialize`, replayed on every reconnection. */\n private initialize: RpcMessage | undefined;\n /** Conversations the editor has attached, with the cwd it named. */\n private readonly sessions = new Map<string, string>();\n /** Editor requests the instance has not answered. */\n private readonly editorRequests = new Map<string, EditorRequest>();\n /** Instance requests the editor has not answered. */\n private readonly instanceRequests = new Set<string>();\n /** The relay's own handshake requests. */\n private readonly ownRequests = new Map<string, OwnRequest>();\n /** What the editor sent while no transport could take it. */\n private readonly queue: RpcMessage[] = [];\n private ownSequence = 0;\n private reconnecting = false;\n /**\n * True while {@link flush} is draining the outage queue. A message that\n * arrives from the editor during the drain must wait behind whatever is\n * still queued ahead of it, not overtake it by going straight to the\n * instance just because the state has already turned \"connected\".\n */\n private flushing = false;\n /**\n * Bumped on every `attach()` and on every connecting-phase failure. A\n * transport's read loop tags itself with the value current when it\n * started; if that value has moved on by the time the loop ends, this\n * transport has already been superseded and its failure must not be\n * acted on against whatever replaced it.\n */\n private generation = 0;\n /** The last reason a connecting-phase attempt failed, for the periodic line. */\n private lastFailureReason: string | undefined;\n\n private readonly delays: readonly number[];\n private readonly handshakeTimeoutMs: number;\n private readonly stopped = new AbortController();\n private settle!: (outcome: BridgeOutcome) => void;\n private readonly settled = new Promise<BridgeOutcome>((resolve) => {\n this.settle = resolve;\n });\n\n constructor(private readonly options: BridgeOptions) {\n this.editorWriter = options.editor.writable.getWriter();\n this.delays = options.delays ?? DEFAULT_DELAYS_MS;\n this.handshakeTimeoutMs =\n options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;\n }\n\n async run(): Promise<BridgeOutcome> {\n // A synchronous throw here (a malformed url, a transport already\n // locked) is the same \"address to check\" as a connection that never\n // answers, so it goes through the same `lost()` path rather than\n // rejecting this promise and leaving `acpCommand` without its\n // documented unreachable result.\n try {\n this.attach(this.options.connect());\n } catch (error: unknown) {\n this.lost(error);\n return this.settled;\n }\n void this.readEditor();\n return this.settled;\n }\n\n // ------------------------------------------------------------ the editor\n\n private async readEditor(): Promise<void> {\n try {\n await readEach(this.options.editor.readable, (message) =>\n this.fromEditor(message),\n );\n } catch (error: unknown) {\n // Reported once, by the caller building the command's result from\n // the `editor-error` outcome; a line here too would say the same\n // thing twice for a failure the process is exiting on regardless.\n this.editorClosed(true, error);\n return;\n }\n this.editorClosed(false);\n }\n\n private async fromEditor(message: RpcMessage): Promise<void> {\n if (this.state === \"done\") return;\n\n if (isResponse(message)) {\n // The editor answering the instance. An answer to a request a dead\n // connection made has nowhere to go: the connection that asked is\n // gone, and posting it to the new one would be posting an id it\n // never issued.\n const key = keyOf(message.id);\n if (!this.instanceRequests.has(key)) return;\n this.instanceRequests.delete(key);\n await this.toInstance(message);\n return;\n }\n\n if (message.method === METHOD.initialize && isRequest(message)) {\n this.initialize = message;\n }\n if (isRequest(message)) {\n const params = paramsOf(message);\n this.editorRequests.set(keyOf(message.id), {\n id: message.id,\n method: message.method,\n ...(typeof params[\"sessionId\"] === \"string\"\n ? { sessionId: params[\"sessionId\"] }\n : {}),\n ...(typeof params[\"cwd\"] === \"string\" ? { cwd: params[\"cwd\"] } : {}),\n });\n if (message.method === METHOD.closeSession) {\n const sessionId = params[\"sessionId\"];\n if (typeof sessionId === \"string\") this.sessions.delete(sessionId);\n }\n }\n\n if (this.state !== \"connected\" || this.flushing) {\n if (this.queue.length >= MAX_QUEUED) {\n if (isRequest(message)) {\n this.editorRequests.delete(keyOf(message.id));\n this.toEditor({\n jsonrpc: \"2.0\",\n id: message.id,\n error: {\n code: CONNECTION_LOST,\n message: `Lost the connection to the instance; too many messages queued during the outage, \"${message.method}\" was dropped rather than queued.`,\n },\n });\n }\n return;\n }\n this.queue.push(message);\n return;\n }\n await this.toInstance(message);\n }\n\n /**\n * Write to the current transport.\n *\n * A write that fails never reached the instance, so the message goes\n * back to the front of the queue for the next transport, and its\n * `editorRequests` entry is left exactly as it was: {@link failInFlight}\n * is what decides, from queue membership, which requests get an outage\n * answer, and this message is unshifted back before {@link lost} runs so\n * it is judged still-queued rather than in flight. The one exception is\n * a request the read side already found the same loss for first: its\n * entry is gone by the time this catch runs, and it must not be queued\n * again on top of the answer it already got.\n */\n private async toInstance(message: RpcMessage): Promise<void> {\n const writer = this.writer;\n if (writer === undefined) {\n this.queue.unshift(message);\n return;\n }\n try {\n await writer.write(message);\n } catch (error: unknown) {\n const key = isRequest(message) ? keyOf(message.id) : undefined;\n const alreadyAnswered =\n key !== undefined && !this.editorRequests.has(key);\n if (!alreadyAnswered) this.queue.unshift(message);\n this.lost(error);\n }\n }\n\n /**\n * A failed write means the editor's output pipe is broken; its input\n * side can stay open regardless, so nothing else would ever notice.\n * Settling here is what ends the relay instead of it running on,\n * silently dropping everything meant for an editor that can no longer\n * hear it. Already-settled is a safe no-op the guard in `editorClosed`\n * handles, including the ordinary case where this write lost a race\n * with the writer's own clean close.\n */\n private toEditor(message: RpcMessage): void {\n this.editorChain = this.editorChain\n .then(() => this.editorWriter.write(message))\n .catch((error: unknown) => this.editorClosed(true, error));\n }\n\n /**\n * The editor's side ended: cleanly (its stream closed, `hadError`\n * false) or not (its stream broke, `hadError` true). Either way there\n * is nothing left to relay to, so the teardown is the same; only the\n * outcome the command reports differs. `hadError` carries that,\n * separately from `error` itself: a rejection can be `undefined` (a\n * bare `controller.error()` is valid), so the error's own value can\n * never be what tells a genuine failure apart from a clean close.\n */\n private editorClosed(hadError: boolean, error?: unknown): void {\n if (this.state === \"done\") return;\n this.state = \"done\";\n this.stopped.abort();\n for (const own of this.ownRequests.values()) {\n own.reject(new Error(\"the editor closed\"));\n }\n this.ownRequests.clear();\n // Closing the transport's writable closes the transport, which ends\n // the readable the instance loop is reading, so both sides settle.\n void this.writer?.close().catch(() => undefined);\n void this.editorWriter.close().catch(() => undefined);\n this.settle(\n hadError ? { kind: \"editor-error\", error } : { kind: \"editor-closed\" },\n );\n }\n\n // ---------------------------------------------------------- the instance\n\n private attach(transport: BridgeTransport): void {\n this.generation += 1;\n const generation = this.generation;\n this.writer = transport.writable.getWriter();\n void this.readInstance(transport, generation);\n }\n\n private async readInstance(\n transport: BridgeTransport,\n generation: number,\n ): Promise<void> {\n let failure: unknown = new Error(\"the instance closed the connection\");\n try {\n await readEach(transport.readable, (message) => {\n this.fromInstance(message, generation);\n });\n } catch (error: unknown) {\n failure = error;\n }\n // A transport that has already been superseded by a newer `attach()`\n // (this one lost the race, or was explicitly aborted by `lost()`) must\n // not report its own end as a fresh loss against whatever replaced it.\n if (this.state === \"done\" || generation !== this.generation) return;\n this.lost(failure);\n }\n\n private fromInstance(message: RpcMessage, generation: number): void {\n if (this.state === \"done\" || generation !== this.generation) return;\n this.everConnected = true;\n\n if (isResponse(message)) {\n const key = keyOf(message.id);\n const own = this.ownRequests.get(key);\n if (own !== undefined) {\n this.ownRequests.delete(key);\n own.resolve(message);\n return;\n }\n const request = this.editorRequests.get(key);\n this.editorRequests.delete(key);\n if (request !== undefined && message.error === undefined) {\n this.noteAttached(request, message);\n }\n this.toEditor(message);\n return;\n }\n\n if (isRequest(message)) {\n this.instanceRequests.add(keyOf(message.id));\n }\n this.toEditor(message);\n }\n\n /** A conversation the editor now holds, from the request that attached it. */\n private noteAttached(request: EditorRequest, response: RpcMessage): void {\n if (request.cwd === undefined) return;\n if (request.method === METHOD.newSession) {\n const result = response.result as { sessionId?: unknown } | undefined;\n if (typeof result?.sessionId === \"string\") {\n this.sessions.set(result.sessionId, request.cwd);\n }\n return;\n }\n if (\n (request.method === METHOD.loadSession ||\n request.method === METHOD.resumeSession) &&\n request.sessionId !== undefined\n ) {\n this.sessions.set(request.sessionId, request.cwd);\n }\n }\n\n /**\n * The current transport is gone.\n *\n * Before the first message ever arrived, that is an address nothing\n * answers on, reported as such. During a handshake, it is one failed\n * attempt and the reconnection loop takes the next. Otherwise it is an\n * outage: what was in flight is answered for, and the loop starts.\n */\n private lost(error: unknown): void {\n if (this.state === \"done\" || this.state === \"lost\") return;\n\n if (!this.everConnected) {\n this.state = \"done\";\n this.stopped.abort();\n void this.editorWriter.close().catch(() => undefined);\n this.settle({ kind: \"unreachable\", error });\n return;\n }\n\n // Bumping the generation here, not only in `attach()`, means a\n // transport whose read loop is still unwinding when a *different*\n // failure (a write, or a handshake timeout) calls `lost()` first is\n // marked stale immediately, before its own `readInstance` callback\n // can run and act on a connection that is no longer current.\n const wasConnecting = this.state === \"connecting\";\n this.generation += 1;\n this.state = \"lost\";\n void this.writer?.abort().catch(() => undefined);\n this.writer = undefined;\n for (const own of this.ownRequests.values()) own.reject(error);\n this.ownRequests.clear();\n if (wasConnecting) {\n this.lastFailureReason = messageOf(error);\n return;\n }\n\n this.lastFailureReason = undefined;\n this.options.log(\n `Lost the connection to ${this.options.target}: ${messageOf(error)}. Waiting for it to come back.`,\n );\n this.failInFlight();\n this.instanceRequests.clear();\n void this.reconnect();\n }\n\n /**\n * Answer for every editor request the dead transport never will: one it\n * had already sent, or was in the middle of sending, when it died. A\n * request still waiting in the outage queue was never handed to this\n * transport at all; it stays registered and is answered fresh when a\n * later transport actually sends it, so it is left alone here rather\n * than answered twice.\n */\n private failInFlight(): void {\n const stillQueued = new Set(\n this.queue.filter(isRequest).map((message) => keyOf(message.id)),\n );\n for (const [key, request] of this.editorRequests) {\n if (stillQueued.has(key)) continue;\n this.toEditor(\n request.method === METHOD.prompt\n ? {\n jsonrpc: \"2.0\",\n id: request.id,\n result: { stopReason: \"cancelled\" },\n }\n : {\n jsonrpc: \"2.0\",\n id: request.id,\n error: {\n code: CONNECTION_LOST,\n message: `Lost the connection to the instance while \"${request.method}\" was in flight; the request was not answered.`,\n },\n },\n );\n this.editorRequests.delete(key);\n }\n }\n\n private async reconnect(): Promise<void> {\n if (this.reconnecting) return;\n this.reconnecting = true;\n const startedAt = Date.now();\n let attempt = 0;\n try {\n while (this.state === \"lost\") {\n await sleep(\n this.delays[Math.min(attempt, this.delays.length - 1)] ?? 0,\n this.stopped.signal,\n );\n if (this.state !== \"lost\") return;\n attempt += 1;\n if (attempt > 1 && attempt % ATTEMPTS_PER_LOG === 1) {\n this.options.log(\n `Still waiting for ${this.options.target} (attempt ${attempt}, ${elapsed(startedAt)})${this.lastFailureReason !== undefined ? `: ${this.lastFailureReason}` : \"\"}.`,\n );\n }\n this.state = \"connecting\";\n try {\n const transport = this.options.connect();\n this.attach(transport);\n await withTimeout(this.handshake(), this.handshakeTimeoutMs);\n } catch (error: unknown) {\n // Covers a `connect()`/`attach()` throw as well as a handshake\n // timeout; a transport failure surfaces through `readInstance`\n // instead and calls `lost()` itself, which this is a no-op\n // against once that has already moved the state on.\n this.lost(error);\n continue;\n }\n if (this.state !== \"connecting\") continue;\n this.state = \"connected\";\n this.options.log(\n `Reconnected to ${this.options.target} after ${attempt} attempt${attempt === 1 ? \"\" : \"s\"} (${elapsed(startedAt)}).`,\n );\n await this.flush();\n }\n } finally {\n this.reconnecting = false;\n }\n }\n\n /**\n * What a fresh transport needs before the editor's messages may cross\n * it: the editor's own `initialize`, then every conversation it had\n * attached, resumed rather than loaded so nothing is replayed onto a\n * screen that already shows it.\n */\n private async handshake(): Promise<void> {\n if (this.initialize === undefined) {\n throw new Error(\"the editor never initialized\");\n }\n const initialized = await this.own(\n METHOD.initialize,\n this.initialize.params,\n );\n if (initialized.error !== undefined) {\n throw new Error(`initialize was refused: ${initialized.error.message}`);\n }\n for (const [sessionId, cwd] of [...this.sessions]) {\n const resumed = await this.own(METHOD.resumeSession, {\n sessionId,\n cwd,\n mcpServers: [],\n });\n if (resumed.error === undefined) continue;\n if (resumed.error.code === RESOURCE_NOT_FOUND) {\n // The instance came back without it: a store that does not\n // outlive the process. Nothing to attach; the editor's next\n // message about it is refused by the instance, by name.\n this.sessions.delete(sessionId);\n this.options.log(\n `Conversation ${sessionId} is not on ${this.options.target} any more (${resumed.error.message}). Start a new one from the editor.`,\n );\n continue;\n }\n this.options.log(\n `Conversation ${sessionId} could not be resumed on ${this.options.target} (${resumed.error.message}). It is kept, and the next reconnection tries again.`,\n );\n }\n }\n\n private own(method: string, params: unknown): Promise<RpcMessage> {\n const writer = this.writer;\n if (writer === undefined) {\n return Promise.reject(new Error(\"no transport\"));\n }\n this.ownSequence += 1;\n const id = `${OWN_ID_PREFIX}${this.ownSequence}`;\n const response = new Promise<RpcMessage>((resolve, reject) => {\n this.ownRequests.set(keyOf(id), { resolve, reject });\n });\n // A transport that dies mid-write rejects the write and, through\n // `lost`, this response too; the caller sees the first and nobody\n // would otherwise see the second.\n response.catch(() => undefined);\n return writer\n .write({ jsonrpc: \"2.0\", id, method, params })\n .then(() => response);\n }\n\n /**\n * Deliver what the editor sent while no transport could take it, oldest\n * first. Writes straight to the transport rather than back through\n * {@link fromEditor}: each message was already registered when it was\n * first queued, and re-entering `fromEditor` would only requeue it,\n * since `flushing` holds every new arrival behind the drain.\n *\n * A response is the one shape dropped rather than replayed: it answers\n * an instance-initiated request, and `instanceRequests` is cleared on\n * every loss, so the new transport never made the request this would be\n * answering. Posting it anyway would be posting an id the new instance\n * never issued, the same case `fromEditor` itself already guards for a\n * response arriving live.\n */\n private async flush(): Promise<void> {\n this.flushing = true;\n try {\n while (this.queue.length > 0 && this.state === \"connected\") {\n const message = this.queue.shift() as RpcMessage;\n if (isResponse(message)) continue;\n await this.toInstance(message);\n }\n } finally {\n this.flushing = false;\n }\n }\n}\n\n// ------------------------------------------------------------------ helpers\n\n/** Every message a stream yields until it closes; a failed stream throws. */\nasync function readEach(\n readable: ReadableStream<RpcMessage>,\n each: (message: RpcMessage) => void | Promise<void>,\n): Promise<void> {\n const reader = readable.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return;\n await each(value);\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nfunction isRequest(\n message: RpcMessage,\n): message is RpcMessage & { id: string | number; method: string } {\n return (\n typeof message.method === \"string\" &&\n message.id !== undefined &&\n message.id !== null\n );\n}\n\nfunction isResponse(\n message: RpcMessage,\n): message is RpcMessage & { id: string | number } {\n return (\n message.method === undefined &&\n message.id !== undefined &&\n message.id !== null &&\n (\"result\" in message || \"error\" in message)\n );\n}\n\n/** The map key for a request or response id known not to be null. */\nfunction keyOf(id: string | number): string {\n return `${typeof id}:${String(id)}`;\n}\n\nfunction paramsOf(message: RpcMessage): Record<string, unknown> {\n return message.params !== null && typeof message.params === \"object\"\n ? (message.params as Record<string, unknown>)\n : {};\n}\n\nfunction elapsed(since: number): string {\n return `${Math.round((Date.now() - since) / 1000)}s`;\n}\n\nfunction sleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal.aborted) {\n resolve();\n return;\n }\n const done = (): void => {\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", done);\n resolve();\n };\n const timer = setTimeout(done, ms);\n signal.addEventListener(\"abort\", done, { once: true });\n });\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(\n () => reject(new Error(`the handshake took longer than ${ms}ms`)),\n ms,\n );\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error: unknown) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n}\n","/**\n * `craft acp`: the pipe an editor runs.\n *\n * An editor starts one process and speaks the Agent Client Protocol to it\n * over its standard input and output. This is that process: newline-\n * delimited JSON on the editor side, Streamable HTTP on the instance side,\n * every message forwarded verbatim in both directions.\n *\n * It is a pipe that re-establishes itself. An editor keeps the process for\n * as long as its window is open and does not start another when the\n * instance behind it restarts, so the relay stays up through an outage\n * and reconnects on its own; what that involves is in `acp-bridge.ts`.\n *\n * It never starts an app. `craft start` owns running, which is what makes\n * one editor entry reach a laptop or a company instance by switching a\n * profile and changing nothing else.\n *\n * There is no login. A token in the profile authenticates, exactly as\n * `token:` does today for every other command, and an agent that\n * advertises no authentication method is conforming.\n */\n\nimport { Readable } from \"node:stream\";\n\nimport { runBridge, type BridgeTransport } from \"./acp-bridge.js\";\nimport {\n describeSource,\n resolveSettings,\n SettingsError,\n type SettingsOverrides,\n} from \"./settings.js\";\nimport { EXEC_EXIT } from \"./exec.js\";\nimport { refuseClearTextBearer } from \"./ops-client.js\";\nimport { messageOf } from \"./util.js\";\n\n/**\n * The path the protocol is mounted on.\n *\n * Not configurable from here on purpose: the mount's own `path` option is\n * the instance's to choose, and a person pointing an editor at an instance\n * that moved it says so with the full URL.\n */\nconst ACP_PATH = \"/acp\";\n\n/**\n * Request header naming the agent this connection wants to talk to.\n *\n * The protocol has no field for it, and inventing one in `_meta` would be\n * a private extension a conforming client could not be expected to send.\n * A header keeps the bridge a pipe: it adds one, exactly as it adds\n * `Authorization`.\n *\n * Spelled once here rather than shared with the mount, because the CLI\n * does not depend on `@routecraft/ai`: the mount exports the same constant\n * and a test pins the two together.\n */\nexport const ACP_AGENT_HEADER = \"Routecraft-Agent\";\n\n/** What `craft acp` answers with when it stops. */\nexport interface AcpResult {\n code: number;\n /** Written to standard error. Standard output belongs to the protocol. */\n error?: string;\n}\n\nexport interface AcpOptions extends SettingsOverrides {\n /** The editor's side of the pipe. Defaults to this process's streams. */\n stdin?: ReadableStream<Uint8Array>;\n stdout?: WritableStream<Uint8Array>;\n /**\n * Where a diagnostic line goes while the bridge runs: an outage, a\n * reconnection, a conversation the instance came back without. Standard\n * error by default; standard output belongs to the protocol.\n */\n stderr?: (line: string) => void;\n}\n\n/**\n * Standard output as a writable stream of bytes.\n *\n * Written through `process.stdout` rather than a Bun file handle: the\n * protocol's frames are small and frequent, and the node stream is what\n * flushes each one as it is written rather than when a buffer fills.\n */\nfunction stdoutStream(): WritableStream<Uint8Array> {\n return new WritableStream<Uint8Array>({\n write(chunk) {\n return new Promise<void>((resolve, reject) => {\n process.stdout.write(chunk, (error) =>\n error ? reject(error) : resolve(),\n );\n });\n },\n });\n}\n\n/**\n * Standard input as a readable stream of bytes.\n *\n * Through `node:stream` rather than a Bun-only global: the CLI runs on Bun\n * today, and there is no reason for the one line that reads a pipe to be\n * the thing that pins it there.\n */\nfunction stdinStream(): ReadableStream<Uint8Array> {\n // The node types describe a web stream of `any`; the bytes are what a\n // pipe carries either way, and the SDK reads them as bytes.\n return Readable.toWeb(process.stdin) as unknown as ReadableStream<Uint8Array>;\n}\n\n/**\n * The SDK types its transports by message shape rather than by\n * {@link BridgeTransport}; the relay reads only the JSON-RPC envelope, which\n * every shape carries. Routing both the editor pipe and the instance\n * connection through this one cast means an SDK shape change that drops\n * `readable`/`writable` fails to compile here, rather than silently at two\n * separate call sites.\n */\nfunction asBridgeTransport(transport: {\n readonly readable: ReadableStream<unknown>;\n readonly writable: WritableStream<never>;\n}): BridgeTransport {\n return transport as unknown as BridgeTransport;\n}\n\n/**\n * Run the bridge until the editor closes its side.\n *\n * Both halves are the SDK's own transports. The relay reads each message\n * only far enough to know what it is (a request, its answer, which\n * conversation it names), so a version of the protocol this build has\n * never heard of still passes through unchanged. An instance that drops\n * after the first exchange is waited for and reconnected to; one that\n * never answers at all is reported, because that is an address to check.\n */\nexport async function acpCommand(options: AcpOptions = {}): Promise<AcpResult> {\n let settings;\n try {\n settings = resolveSettings(options);\n } catch (error: unknown) {\n if (error instanceof SettingsError)\n return { code: 2, error: error.message };\n throw error;\n }\n\n const { ndJsonStream } = await import(\"@agentclientprotocol/sdk\");\n const { createHttpStream, MemoryAcpCookieStore } =\n await import(\"@agentclientprotocol/sdk/experimental/http-client\");\n\n const headers: Record<string, string> = {};\n if (settings.token !== undefined) {\n // The same refusal `craft exec` and `craft ops` make: a bearer does not\n // go over cleartext to anything but this machine. An editor holds this\n // connection open all day, so it is the longest-lived place a token\n // would be on the wire.\n try {\n refuseClearTextBearer(settings);\n } catch (error: unknown) {\n if (error instanceof SettingsError)\n return { code: 2, error: error.message };\n throw error;\n }\n headers[\"Authorization\"] = `Bearer ${settings.token.value}`;\n }\n if (settings.agent !== undefined) {\n headers[ACP_AGENT_HEADER] = settings.agent.value;\n }\n\n const url = `${settings.url.value.replace(/\\/+$/, \"\")}${ACP_PATH}`;\n // One cookie store for the process: the SDK uses it for routing\n // affinity, so a reconnect lands on the instance that holds the\n // conversation rather than on a sibling behind the same address.\n const cookieStore = new MemoryAcpCookieStore();\n const editor = ndJsonStream(\n options.stdout ?? stdoutStream(),\n options.stdin ?? stdinStream(),\n );\n\n const outcome = await runBridge({\n editor: asBridgeTransport(editor),\n connect: () =>\n asBridgeTransport(createHttpStream(url, { headers, cookieStore })),\n target: url,\n log: options.stderr ?? ((line) => process.stderr.write(`${line}\\n`)),\n });\n if (outcome.kind === \"editor-closed\") return { code: 0 };\n if (outcome.kind === \"editor-error\") {\n return {\n code: EXEC_EXIT.failed,\n error: `The editor's side of the pipe failed: ${messageOf(outcome.error)}`,\n };\n }\n // The family's code for an address nothing answered on, so a script\n // that already branches on `craft exec`'s exit codes reads this one\n // the same way.\n return {\n code: EXEC_EXIT.unreachable,\n error: `Could not reach ${url} (from the ${describeSource(settings.url)}): ${messageOf(outcome.error)}`,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/acp-bridge.ts","../src/acp.ts"],"names":["DEFAULT_DELAYS_MS","DEFAULT_HANDSHAKE_TIMEOUT_MS","ATTEMPTS_PER_LOG","CONNECTION_LOST","RESOURCE_NOT_FOUND","OWN_ID_PREFIX","MAX_QUEUED","METHOD","runBridge","options","Bridge","resolve","error","readEach","message","isResponse","key","keyOf","isRequest","params","paramsOf","sessionId","writer","hadError","own","transport","generation","failure","request","response","result","wasConnecting","messageOf","stillQueued","startedAt","attempt","sleep","elapsed","withTimeout","initialized","cwd","resumed","method","id","reject","readable","each","reader","done","value","since","ms","signal","timer","promise","ACP_PATH","ACP_AGENT_HEADER","stdoutStream","chunk","stdinStream","Readable","acpCommand","settings","resolveSettings","SettingsError","ndJsonStream","createHttpStream","MemoryAcpCookieStore","headers","refuseClearTextBearer","url","cookieStore","editor","outcome","line","EXEC_EXIT","describeSource"],"mappings":"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","file":"acp-RSGIIDTK.js","sourcesContent":["/**\n * The relay behind `craft acp`: a pipe that re-establishes itself.\n *\n * Every message crosses it verbatim while the instance is up. When the\n * instance goes away (a restart under an open editor window is the usual\n * case), the editor's side stays open and the relay reconnects: a fresh\n * transport, the editor's own `initialize` replayed, and `session/resume`\n * for every conversation the editor had attached, so the editor keeps\n * talking to the same conversation without knowing anything happened.\n *\n * The protocol does not replay what was in flight when the transport\n * died, so the relay answers for it: a `session/prompt` that was waiting\n * gets `cancelled` (the protocol's own word for a turn that ended with no\n * reply), any other request gets an error naming the outage, and an\n * answer the editor later gives to a request the dead connection made is\n * dropped rather than posted to a connection that never asked. Everything\n * the editor sends during the outage is queued and delivered once the\n * handshake on the new transport has completed.\n *\n * The first connection is different: an address nothing answers on is a\n * configuration mistake, and the relay reports it rather than waiting for\n * it to come right. Reconnection is for a connection that once worked.\n */\n\nimport { messageOf } from \"./util.js\";\n\n/** A JSON-RPC 2.0 message as it crosses the relay. */\nexport interface RpcMessage {\n readonly jsonrpc: \"2.0\";\n readonly id?: string | number | null;\n readonly method?: string;\n readonly params?: unknown;\n readonly result?: unknown;\n readonly error?: { code: number; message: string; data?: unknown };\n}\n\n/** One side of the relay: what it reads from and writes to. */\nexport interface BridgeTransport {\n readonly readable: ReadableStream<RpcMessage>;\n readonly writable: WritableStream<RpcMessage>;\n}\n\nexport interface BridgeOptions {\n /** The editor's side, open for the life of the relay. */\n readonly editor: BridgeTransport;\n /**\n * A fresh transport to the instance. Called once at the start and once\n * per reconnection attempt; a transport is never reused after it fails.\n */\n readonly connect: () => BridgeTransport;\n /** Where the instance is, for the diagnostics. */\n readonly target: string;\n /** One diagnostic line, to standard error in the command. */\n readonly log: (line: string) => void;\n /**\n * Delays between reconnection attempts, in milliseconds. The last one\n * repeats until the editor closes.\n */\n readonly delays?: readonly number[];\n /** How long one handshake may take before the attempt is abandoned. */\n readonly handshakeTimeoutMs?: number;\n}\n\n/** How the relay ended. */\nexport type BridgeOutcome =\n | { readonly kind: \"editor-closed\" }\n | { readonly kind: \"editor-error\"; readonly error: unknown }\n | { readonly kind: \"unreachable\"; readonly error: unknown };\n\n/** Doubling from a quarter second to five, which then repeats. */\nconst DEFAULT_DELAYS_MS: readonly number[] = [\n 250, 500, 1_000, 2_000, 4_000, 5_000,\n];\n\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000;\n\n/** Attempts between \"still trying\" lines, so an outage does not fill stderr. */\nconst ATTEMPTS_PER_LOG = 12;\n\n/**\n * JSON-RPC's implementation-defined server error range starts here. The\n * protocol defines no code for a lost connection, and this is the one the\n * SDK's own `RequestError` family leaves free.\n */\nconst CONNECTION_LOST = -32000;\n\n/**\n * The protocol's resource-not-found code, which the instance answers a\n * `session/resume` with when it no longer holds the conversation.\n *\n * The one refusal the relay drops a conversation on. Any other means the\n * conversation may still exist on the instance and dropping it would lose\n * the person's work on a false premise, so it is kept and tried again on\n * the next reconnection.\n */\nconst RESOURCE_NOT_FOUND = -32002;\n\n/** The prefix on ids of requests the relay makes for itself. */\nconst OWN_ID_PREFIX = \"craft-acp:\";\n\n/** How many editor messages the relay holds while no transport can take them. */\nconst MAX_QUEUED = 200;\n\n/**\n * The methods the relay must understand to restore a connection after an\n * outage. Mirrors `@routecraft/ai`'s ACP mount; the CLI does not depend on\n * that package (see `ACP_AGENT_HEADER`'s own note in `acp.ts`), so this is\n * duplicated rather than shared, and is spelled once here rather than\n * scattered through the module.\n */\nconst METHOD = {\n initialize: \"initialize\",\n newSession: \"session/new\",\n loadSession: \"session/load\",\n resumeSession: \"session/resume\",\n closeSession: \"session/close\",\n prompt: \"session/prompt\",\n} as const;\n\ntype State = \"connected\" | \"connecting\" | \"lost\" | \"done\";\n\ninterface EditorRequest {\n readonly id: string | number;\n readonly method: string;\n readonly sessionId?: string;\n readonly cwd?: string;\n}\n\ninterface OwnRequest {\n resolve(message: RpcMessage): void;\n reject(error: unknown): void;\n}\n\n/** Run the relay until the editor closes its side, or the first connection fails. */\nexport function runBridge(options: BridgeOptions): Promise<BridgeOutcome> {\n return new Bridge(options).run();\n}\n\nclass Bridge {\n private state: State = \"connected\";\n private everConnected = false;\n private writer: WritableStreamDefaultWriter<RpcMessage> | undefined;\n private readonly editorWriter: WritableStreamDefaultWriter<RpcMessage>;\n private editorChain: Promise<void> = Promise.resolve();\n\n /** The editor's `initialize`, replayed on every reconnection. */\n private initialize: RpcMessage | undefined;\n /** Conversations the editor has attached, with the cwd it named. */\n private readonly sessions = new Map<string, string>();\n /** Editor requests the instance has not answered. */\n private readonly editorRequests = new Map<string, EditorRequest>();\n /** Instance requests the editor has not answered. */\n private readonly instanceRequests = new Set<string>();\n /** The relay's own handshake requests. */\n private readonly ownRequests = new Map<string, OwnRequest>();\n /** What the editor sent while no transport could take it. */\n private readonly queue: RpcMessage[] = [];\n private ownSequence = 0;\n private reconnecting = false;\n /**\n * True while {@link flush} is draining the outage queue. A message that\n * arrives from the editor during the drain must wait behind whatever is\n * still queued ahead of it, not overtake it by going straight to the\n * instance just because the state has already turned \"connected\".\n */\n private flushing = false;\n /**\n * Bumped on every `attach()` and on every connecting-phase failure. A\n * transport's read loop tags itself with the value current when it\n * started; if that value has moved on by the time the loop ends, this\n * transport has already been superseded and its failure must not be\n * acted on against whatever replaced it.\n */\n private generation = 0;\n /** The last reason a connecting-phase attempt failed, for the periodic line. */\n private lastFailureReason: string | undefined;\n\n private readonly delays: readonly number[];\n private readonly handshakeTimeoutMs: number;\n private readonly stopped = new AbortController();\n private settle!: (outcome: BridgeOutcome) => void;\n private readonly settled = new Promise<BridgeOutcome>((resolve) => {\n this.settle = resolve;\n });\n\n constructor(private readonly options: BridgeOptions) {\n this.editorWriter = options.editor.writable.getWriter();\n this.delays = options.delays ?? DEFAULT_DELAYS_MS;\n this.handshakeTimeoutMs =\n options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;\n }\n\n async run(): Promise<BridgeOutcome> {\n // A synchronous throw here (a malformed url, a transport already\n // locked) is the same \"address to check\" as a connection that never\n // answers, so it goes through the same `lost()` path rather than\n // rejecting this promise and leaving `acpCommand` without its\n // documented unreachable result.\n try {\n this.attach(this.options.connect());\n } catch (error: unknown) {\n this.lost(error);\n return this.settled;\n }\n void this.readEditor();\n return this.settled;\n }\n\n // ------------------------------------------------------------ the editor\n\n private async readEditor(): Promise<void> {\n try {\n await readEach(this.options.editor.readable, (message) =>\n this.fromEditor(message),\n );\n } catch (error: unknown) {\n // Reported once, by the caller building the command's result from\n // the `editor-error` outcome; a line here too would say the same\n // thing twice for a failure the process is exiting on regardless.\n this.editorClosed(true, error);\n return;\n }\n this.editorClosed(false);\n }\n\n private async fromEditor(message: RpcMessage): Promise<void> {\n if (this.state === \"done\") return;\n\n if (isResponse(message)) {\n // The editor answering the instance. An answer to a request a dead\n // connection made has nowhere to go: the connection that asked is\n // gone, and posting it to the new one would be posting an id it\n // never issued.\n const key = keyOf(message.id);\n if (!this.instanceRequests.has(key)) return;\n this.instanceRequests.delete(key);\n await this.toInstance(message);\n return;\n }\n\n if (message.method === METHOD.initialize && isRequest(message)) {\n this.initialize = message;\n }\n if (isRequest(message)) {\n const params = paramsOf(message);\n this.editorRequests.set(keyOf(message.id), {\n id: message.id,\n method: message.method,\n ...(typeof params[\"sessionId\"] === \"string\"\n ? { sessionId: params[\"sessionId\"] }\n : {}),\n ...(typeof params[\"cwd\"] === \"string\" ? { cwd: params[\"cwd\"] } : {}),\n });\n if (message.method === METHOD.closeSession) {\n const sessionId = params[\"sessionId\"];\n if (typeof sessionId === \"string\") this.sessions.delete(sessionId);\n }\n }\n\n if (this.state !== \"connected\" || this.flushing) {\n if (this.queue.length >= MAX_QUEUED) {\n if (isRequest(message)) {\n this.editorRequests.delete(keyOf(message.id));\n this.toEditor({\n jsonrpc: \"2.0\",\n id: message.id,\n error: {\n code: CONNECTION_LOST,\n message: `Lost the connection to the instance; too many messages queued during the outage, \"${message.method}\" was dropped rather than queued.`,\n },\n });\n }\n return;\n }\n this.queue.push(message);\n return;\n }\n await this.toInstance(message);\n }\n\n /**\n * Write to the current transport.\n *\n * A write that fails never reached the instance, so the message goes\n * back to the front of the queue for the next transport, and its\n * `editorRequests` entry is left exactly as it was: {@link failInFlight}\n * is what decides, from queue membership, which requests get an outage\n * answer, and this message is unshifted back before {@link lost} runs so\n * it is judged still-queued rather than in flight. The one exception is\n * a request the read side already found the same loss for first: its\n * entry is gone by the time this catch runs, and it must not be queued\n * again on top of the answer it already got.\n */\n private async toInstance(message: RpcMessage): Promise<void> {\n const writer = this.writer;\n if (writer === undefined) {\n this.queue.unshift(message);\n return;\n }\n try {\n await writer.write(message);\n } catch (error: unknown) {\n const key = isRequest(message) ? keyOf(message.id) : undefined;\n const alreadyAnswered =\n key !== undefined && !this.editorRequests.has(key);\n if (!alreadyAnswered) this.queue.unshift(message);\n this.lost(error);\n }\n }\n\n /**\n * A failed write means the editor's output pipe is broken; its input\n * side can stay open regardless, so nothing else would ever notice.\n * Settling here is what ends the relay instead of it running on,\n * silently dropping everything meant for an editor that can no longer\n * hear it. Already-settled is a safe no-op the guard in `editorClosed`\n * handles, including the ordinary case where this write lost a race\n * with the writer's own clean close.\n */\n private toEditor(message: RpcMessage): void {\n this.editorChain = this.editorChain\n .then(() => this.editorWriter.write(message))\n .catch((error: unknown) => this.editorClosed(true, error));\n }\n\n /**\n * The editor's side ended: cleanly (its stream closed, `hadError`\n * false) or not (its stream broke, `hadError` true). Either way there\n * is nothing left to relay to, so the teardown is the same; only the\n * outcome the command reports differs. `hadError` carries that,\n * separately from `error` itself: a rejection can be `undefined` (a\n * bare `controller.error()` is valid), so the error's own value can\n * never be what tells a genuine failure apart from a clean close.\n */\n private editorClosed(hadError: boolean, error?: unknown): void {\n if (this.state === \"done\") return;\n this.state = \"done\";\n this.stopped.abort();\n for (const own of this.ownRequests.values()) {\n own.reject(new Error(\"the editor closed\"));\n }\n this.ownRequests.clear();\n // Closing the transport's writable closes the transport, which ends\n // the readable the instance loop is reading, so both sides settle.\n void this.writer?.close().catch(() => undefined);\n void this.editorWriter.close().catch(() => undefined);\n this.settle(\n hadError ? { kind: \"editor-error\", error } : { kind: \"editor-closed\" },\n );\n }\n\n // ---------------------------------------------------------- the instance\n\n private attach(transport: BridgeTransport): void {\n this.generation += 1;\n const generation = this.generation;\n this.writer = transport.writable.getWriter();\n void this.readInstance(transport, generation);\n }\n\n private async readInstance(\n transport: BridgeTransport,\n generation: number,\n ): Promise<void> {\n let failure: unknown = new Error(\"the instance closed the connection\");\n try {\n await readEach(transport.readable, (message) => {\n this.fromInstance(message, generation);\n });\n } catch (error: unknown) {\n failure = error;\n }\n // A transport that has already been superseded by a newer `attach()`\n // (this one lost the race, or was explicitly aborted by `lost()`) must\n // not report its own end as a fresh loss against whatever replaced it.\n if (this.state === \"done\" || generation !== this.generation) return;\n this.lost(failure);\n }\n\n private fromInstance(message: RpcMessage, generation: number): void {\n if (this.state === \"done\" || generation !== this.generation) return;\n this.everConnected = true;\n\n if (isResponse(message)) {\n const key = keyOf(message.id);\n const own = this.ownRequests.get(key);\n if (own !== undefined) {\n this.ownRequests.delete(key);\n own.resolve(message);\n return;\n }\n const request = this.editorRequests.get(key);\n this.editorRequests.delete(key);\n if (request !== undefined && message.error === undefined) {\n this.noteAttached(request, message);\n }\n this.toEditor(message);\n return;\n }\n\n if (isRequest(message)) {\n this.instanceRequests.add(keyOf(message.id));\n }\n this.toEditor(message);\n }\n\n /** A conversation the editor now holds, from the request that attached it. */\n private noteAttached(request: EditorRequest, response: RpcMessage): void {\n if (request.cwd === undefined) return;\n if (request.method === METHOD.newSession) {\n const result = response.result as { sessionId?: unknown } | undefined;\n if (typeof result?.sessionId === \"string\") {\n this.sessions.set(result.sessionId, request.cwd);\n }\n return;\n }\n if (\n (request.method === METHOD.loadSession ||\n request.method === METHOD.resumeSession) &&\n request.sessionId !== undefined\n ) {\n this.sessions.set(request.sessionId, request.cwd);\n }\n }\n\n /**\n * The current transport is gone.\n *\n * Before the first message ever arrived, that is an address nothing\n * answers on, reported as such. During a handshake, it is one failed\n * attempt and the reconnection loop takes the next. Otherwise it is an\n * outage: what was in flight is answered for, and the loop starts.\n */\n private lost(error: unknown): void {\n if (this.state === \"done\" || this.state === \"lost\") return;\n\n if (!this.everConnected) {\n this.state = \"done\";\n this.stopped.abort();\n void this.editorWriter.close().catch(() => undefined);\n this.settle({ kind: \"unreachable\", error });\n return;\n }\n\n // Bumping the generation here, not only in `attach()`, means a\n // transport whose read loop is still unwinding when a *different*\n // failure (a write, or a handshake timeout) calls `lost()` first is\n // marked stale immediately, before its own `readInstance` callback\n // can run and act on a connection that is no longer current.\n const wasConnecting = this.state === \"connecting\";\n this.generation += 1;\n this.state = \"lost\";\n void this.writer?.abort().catch(() => undefined);\n this.writer = undefined;\n for (const own of this.ownRequests.values()) own.reject(error);\n this.ownRequests.clear();\n if (wasConnecting) {\n this.lastFailureReason = messageOf(error);\n return;\n }\n\n this.lastFailureReason = undefined;\n this.options.log(\n `Lost the connection to ${this.options.target}: ${messageOf(error)}. Waiting for it to come back.`,\n );\n this.failInFlight();\n this.instanceRequests.clear();\n void this.reconnect();\n }\n\n /**\n * Answer for every editor request the dead transport never will: one it\n * had already sent, or was in the middle of sending, when it died. A\n * request still waiting in the outage queue was never handed to this\n * transport at all; it stays registered and is answered fresh when a\n * later transport actually sends it, so it is left alone here rather\n * than answered twice.\n */\n private failInFlight(): void {\n const stillQueued = new Set(\n this.queue.filter(isRequest).map((message) => keyOf(message.id)),\n );\n for (const [key, request] of this.editorRequests) {\n if (stillQueued.has(key)) continue;\n this.toEditor(\n request.method === METHOD.prompt\n ? {\n jsonrpc: \"2.0\",\n id: request.id,\n result: { stopReason: \"cancelled\" },\n }\n : {\n jsonrpc: \"2.0\",\n id: request.id,\n error: {\n code: CONNECTION_LOST,\n message: `Lost the connection to the instance while \"${request.method}\" was in flight; the request was not answered.`,\n },\n },\n );\n this.editorRequests.delete(key);\n }\n }\n\n private async reconnect(): Promise<void> {\n if (this.reconnecting) return;\n this.reconnecting = true;\n const startedAt = Date.now();\n let attempt = 0;\n try {\n while (this.state === \"lost\") {\n await sleep(\n this.delays[Math.min(attempt, this.delays.length - 1)] ?? 0,\n this.stopped.signal,\n );\n if (this.state !== \"lost\") return;\n attempt += 1;\n if (attempt > 1 && attempt % ATTEMPTS_PER_LOG === 1) {\n this.options.log(\n `Still waiting for ${this.options.target} (attempt ${attempt}, ${elapsed(startedAt)})${this.lastFailureReason !== undefined ? `: ${this.lastFailureReason}` : \"\"}.`,\n );\n }\n this.state = \"connecting\";\n try {\n const transport = this.options.connect();\n this.attach(transport);\n await withTimeout(this.handshake(), this.handshakeTimeoutMs);\n } catch (error: unknown) {\n // Covers a `connect()`/`attach()` throw as well as a handshake\n // timeout; a transport failure surfaces through `readInstance`\n // instead and calls `lost()` itself, which this is a no-op\n // against once that has already moved the state on.\n this.lost(error);\n continue;\n }\n if (this.state !== \"connecting\") continue;\n this.state = \"connected\";\n this.options.log(\n `Reconnected to ${this.options.target} after ${attempt} attempt${attempt === 1 ? \"\" : \"s\"} (${elapsed(startedAt)}).`,\n );\n await this.flush();\n }\n } finally {\n this.reconnecting = false;\n }\n }\n\n /**\n * What a fresh transport needs before the editor's messages may cross\n * it: the editor's own `initialize`, then every conversation it had\n * attached, resumed rather than loaded so nothing is replayed onto a\n * screen that already shows it.\n */\n private async handshake(): Promise<void> {\n if (this.initialize === undefined) {\n throw new Error(\"the editor never initialized\");\n }\n const initialized = await this.own(\n METHOD.initialize,\n this.initialize.params,\n );\n if (initialized.error !== undefined) {\n throw new Error(`initialize was refused: ${initialized.error.message}`);\n }\n for (const [sessionId, cwd] of [...this.sessions]) {\n const resumed = await this.own(METHOD.resumeSession, {\n sessionId,\n cwd,\n mcpServers: [],\n });\n if (resumed.error === undefined) continue;\n if (resumed.error.code === RESOURCE_NOT_FOUND) {\n // The instance came back without it: a store that does not\n // outlive the process. Nothing to attach; the editor's next\n // message about it is refused by the instance, by name.\n this.sessions.delete(sessionId);\n this.options.log(\n `Conversation ${sessionId} is not on ${this.options.target} any more (${resumed.error.message}). Start a new one from the editor.`,\n );\n continue;\n }\n this.options.log(\n `Conversation ${sessionId} could not be resumed on ${this.options.target} (${resumed.error.message}). It is kept, and the next reconnection tries again.`,\n );\n }\n }\n\n private own(method: string, params: unknown): Promise<RpcMessage> {\n const writer = this.writer;\n if (writer === undefined) {\n return Promise.reject(new Error(\"no transport\"));\n }\n this.ownSequence += 1;\n const id = `${OWN_ID_PREFIX}${this.ownSequence}`;\n const response = new Promise<RpcMessage>((resolve, reject) => {\n this.ownRequests.set(keyOf(id), { resolve, reject });\n });\n // A transport that dies mid-write rejects the write and, through\n // `lost`, this response too; the caller sees the first and nobody\n // would otherwise see the second.\n response.catch(() => undefined);\n return writer\n .write({ jsonrpc: \"2.0\", id, method, params })\n .then(() => response);\n }\n\n /**\n * Deliver what the editor sent while no transport could take it, oldest\n * first. Writes straight to the transport rather than back through\n * {@link fromEditor}: each message was already registered when it was\n * first queued, and re-entering `fromEditor` would only requeue it,\n * since `flushing` holds every new arrival behind the drain.\n *\n * A response is the one shape dropped rather than replayed: it answers\n * an instance-initiated request, and `instanceRequests` is cleared on\n * every loss, so the new transport never made the request this would be\n * answering. Posting it anyway would be posting an id the new instance\n * never issued, the same case `fromEditor` itself already guards for a\n * response arriving live.\n */\n private async flush(): Promise<void> {\n this.flushing = true;\n try {\n while (this.queue.length > 0 && this.state === \"connected\") {\n const message = this.queue.shift() as RpcMessage;\n if (isResponse(message)) continue;\n await this.toInstance(message);\n }\n } finally {\n this.flushing = false;\n }\n }\n}\n\n// ------------------------------------------------------------------ helpers\n\n/** Every message a stream yields until it closes; a failed stream throws. */\nasync function readEach(\n readable: ReadableStream<RpcMessage>,\n each: (message: RpcMessage) => void | Promise<void>,\n): Promise<void> {\n const reader = readable.getReader();\n try {\n for (;;) {\n const { done, value } = await reader.read();\n if (done) return;\n await each(value);\n }\n } finally {\n reader.releaseLock();\n }\n}\n\nfunction isRequest(\n message: RpcMessage,\n): message is RpcMessage & { id: string | number; method: string } {\n return (\n typeof message.method === \"string\" &&\n message.id !== undefined &&\n message.id !== null\n );\n}\n\nfunction isResponse(\n message: RpcMessage,\n): message is RpcMessage & { id: string | number } {\n return (\n message.method === undefined &&\n message.id !== undefined &&\n message.id !== null &&\n (\"result\" in message || \"error\" in message)\n );\n}\n\n/** The map key for a request or response id known not to be null. */\nfunction keyOf(id: string | number): string {\n return `${typeof id}:${String(id)}`;\n}\n\nfunction paramsOf(message: RpcMessage): Record<string, unknown> {\n return message.params !== null && typeof message.params === \"object\"\n ? (message.params as Record<string, unknown>)\n : {};\n}\n\nfunction elapsed(since: number): string {\n return `${Math.round((Date.now() - since) / 1000)}s`;\n}\n\nfunction sleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n if (signal.aborted) {\n resolve();\n return;\n }\n const done = (): void => {\n clearTimeout(timer);\n signal.removeEventListener(\"abort\", done);\n resolve();\n };\n const timer = setTimeout(done, ms);\n signal.addEventListener(\"abort\", done, { once: true });\n });\n}\n\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n return new Promise<T>((resolve, reject) => {\n const timer = setTimeout(\n () => reject(new Error(`the handshake took longer than ${ms}ms`)),\n ms,\n );\n promise.then(\n (value) => {\n clearTimeout(timer);\n resolve(value);\n },\n (error: unknown) => {\n clearTimeout(timer);\n reject(error);\n },\n );\n });\n}\n","/**\n * `craft acp`: the pipe an editor runs.\n *\n * An editor starts one process and speaks the Agent Client Protocol to it\n * over its standard input and output. This is that process: newline-\n * delimited JSON on the editor side, Streamable HTTP on the instance side,\n * every message forwarded verbatim in both directions.\n *\n * It is a pipe that re-establishes itself. An editor keeps the process for\n * as long as its window is open and does not start another when the\n * instance behind it restarts, so the relay stays up through an outage\n * and reconnects on its own; what that involves is in `acp-bridge.ts`.\n *\n * It never starts an app. `craft start` owns running, which is what makes\n * one editor entry reach a laptop or a company instance by switching a\n * profile and changing nothing else.\n *\n * There is no login. A token in the profile authenticates, exactly as\n * `token:` does today for every other command, and an agent that\n * advertises no authentication method is conforming.\n */\n\nimport { Readable } from \"node:stream\";\n\nimport { runBridge, type BridgeTransport } from \"./acp-bridge.js\";\nimport {\n describeSource,\n resolveSettings,\n SettingsError,\n type SettingsOverrides,\n} from \"./settings.js\";\nimport { EXEC_EXIT } from \"./exec.js\";\nimport { refuseClearTextBearer } from \"./ops-client.js\";\nimport { messageOf } from \"./util.js\";\n\n/**\n * The path the protocol is mounted on.\n *\n * Not configurable from here on purpose: the mount's own `path` option is\n * the instance's to choose, and a person pointing an editor at an instance\n * that moved it says so with the full URL.\n */\nconst ACP_PATH = \"/acp\";\n\n/**\n * Request header naming the agent this connection wants to talk to.\n *\n * The protocol has no field for it, and inventing one in `_meta` would be\n * a private extension a conforming client could not be expected to send.\n * A header keeps the bridge a pipe: it adds one, exactly as it adds\n * `Authorization`.\n *\n * Spelled once here rather than shared with the mount, because the CLI\n * does not depend on `@routecraft/ai`: the mount exports the same constant\n * and a test pins the two together.\n */\nexport const ACP_AGENT_HEADER = \"Routecraft-Agent\";\n\n/** What `craft acp` answers with when it stops. */\nexport interface AcpResult {\n code: number;\n /** Written to standard error. Standard output belongs to the protocol. */\n error?: string;\n}\n\nexport interface AcpOptions extends SettingsOverrides {\n /** The editor's side of the pipe. Defaults to this process's streams. */\n stdin?: ReadableStream<Uint8Array>;\n stdout?: WritableStream<Uint8Array>;\n /**\n * Where a diagnostic line goes while the bridge runs: an outage, a\n * reconnection, a conversation the instance came back without. Standard\n * error by default; standard output belongs to the protocol.\n */\n stderr?: (line: string) => void;\n}\n\n/**\n * Standard output as a writable stream of bytes.\n *\n * Written through `process.stdout` rather than a Bun file handle: the\n * protocol's frames are small and frequent, and the node stream is what\n * flushes each one as it is written rather than when a buffer fills.\n */\nfunction stdoutStream(): WritableStream<Uint8Array> {\n return new WritableStream<Uint8Array>({\n write(chunk) {\n return new Promise<void>((resolve, reject) => {\n process.stdout.write(chunk, (error) =>\n error ? reject(error) : resolve(),\n );\n });\n },\n });\n}\n\n/**\n * Standard input as a readable stream of bytes.\n *\n * Through `node:stream` rather than a Bun-only global: the CLI runs on Bun\n * today, and there is no reason for the one line that reads a pipe to be\n * the thing that pins it there.\n */\nfunction stdinStream(): ReadableStream<Uint8Array> {\n // The node types describe a web stream of `any`; the bytes are what a\n // pipe carries either way, and the SDK reads them as bytes.\n return Readable.toWeb(process.stdin) as unknown as ReadableStream<Uint8Array>;\n}\n\n/**\n * The SDK types its transports by message shape rather than by\n * {@link BridgeTransport}; the relay reads only the JSON-RPC envelope, which\n * every shape carries. Routing both the editor pipe and the instance\n * connection through this one cast means an SDK shape change that drops\n * `readable`/`writable` fails to compile here, rather than silently at two\n * separate call sites.\n */\nfunction asBridgeTransport(transport: {\n readonly readable: ReadableStream<unknown>;\n readonly writable: WritableStream<never>;\n}): BridgeTransport {\n return transport as unknown as BridgeTransport;\n}\n\n/**\n * Run the bridge until the editor closes its side.\n *\n * Both halves are the SDK's own transports. The relay reads each message\n * only far enough to know what it is (a request, its answer, which\n * conversation it names), so a version of the protocol this build has\n * never heard of still passes through unchanged. An instance that drops\n * after the first exchange is waited for and reconnected to; one that\n * never answers at all is reported, because that is an address to check.\n */\nexport async function acpCommand(options: AcpOptions = {}): Promise<AcpResult> {\n let settings;\n try {\n settings = resolveSettings(options);\n } catch (error: unknown) {\n if (error instanceof SettingsError)\n return { code: 2, error: error.message };\n throw error;\n }\n\n const { ndJsonStream } = await import(\"@agentclientprotocol/sdk\");\n const { createHttpStream, MemoryAcpCookieStore } =\n await import(\"@agentclientprotocol/sdk/experimental/http-client\");\n\n const headers: Record<string, string> = {};\n if (settings.token !== undefined) {\n // The same refusal `craft exec` and `craft ops` make: a bearer does not\n // go over cleartext to anything but this machine. An editor holds this\n // connection open all day, so it is the longest-lived place a token\n // would be on the wire.\n try {\n refuseClearTextBearer(settings);\n } catch (error: unknown) {\n if (error instanceof SettingsError)\n return { code: 2, error: error.message };\n throw error;\n }\n headers[\"Authorization\"] = `Bearer ${settings.token.value}`;\n }\n if (settings.agent !== undefined) {\n headers[ACP_AGENT_HEADER] = settings.agent.value;\n }\n\n const url = `${settings.url.value.replace(/\\/+$/, \"\")}${ACP_PATH}`;\n // One cookie store for the process: the SDK uses it for routing\n // affinity, so a reconnect lands on the instance that holds the\n // conversation rather than on a sibling behind the same address.\n const cookieStore = new MemoryAcpCookieStore();\n const editor = ndJsonStream(\n options.stdout ?? stdoutStream(),\n options.stdin ?? stdinStream(),\n );\n\n const outcome = await runBridge({\n editor: asBridgeTransport(editor),\n connect: () =>\n asBridgeTransport(createHttpStream(url, { headers, cookieStore })),\n target: url,\n log: options.stderr ?? ((line) => process.stderr.write(`${line}\\n`)),\n });\n if (outcome.kind === \"editor-closed\") return { code: 0 };\n if (outcome.kind === \"editor-error\") {\n return {\n code: EXEC_EXIT.failed,\n error: `The editor's side of the pipe failed: ${messageOf(outcome.error)}`,\n };\n }\n // The family's code for an address nothing answered on, so a script\n // that already branches on `craft exec`'s exit codes reads this one\n // the same way.\n return {\n code: EXEC_EXIT.unreachable,\n error: `Could not reach ${url} (from the ${describeSource(settings.url)}): ${messageOf(outcome.error)}`,\n };\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {resolve}from'path';import {config}from'dotenv';var r=class extends Error{},v={quiet:true};function l(e,n,t,o=false){let i=config({path:n,override:o,...v});if(i.error)return i.error;e.push({level:"debug",message:`Loaded ${Object.keys(i.parsed??{}).length} environment variables from ${t}`});}function d(e,n,t){let o=l(e,resolve(process.cwd(),n),n);if(o)throw new r(`Cannot load the environment file ${n} (from ${t}): ${o.message}`)}function s(e,n,t,o){let i=l(e,resolve(n,t),t,o);if(i===void 0)return true;if(i.code==="ENOENT")return false;throw new r(`Cannot load the environment file ${t}: ${i.message}`)}function c(e,n,t){s(e,n,".env",false)||e.push({level:"debug",message:"No .env file found"}),t!==void 0&&s(e,n,`.env.${t}`,true),s(e,n,".env.local",true);}function p(e){return e instanceof Error?e.message:typeof e=="object"&&e!==null&&"message"in e?String(e.message):String(e)}function E(e){let n=[],t=e.defaultsFrom??process.cwd();if(e.explicit!==void 0)return d(n,e.explicit,"--env"),n;let o=e.env;if(typeof o=="string")return d(n,resolve(t,o),"the selected profile's env"),n;if(o!==void 0){for(let[i,u]of Object.entries(o))process.env[i]===void 0&&(process.env[i]=u);return n.push({level:"debug",message:`Applied ${Object.keys(o).length} inline environment values from the selected profile`}),n}return c(n,t,e.profile),n}export{r as a,p as b,E as c};//# sourceMappingURL=chunk-FFSMJDUF.js.map
|
|
2
|
+
//# sourceMappingURL=chunk-FFSMJDUF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/util.ts"],"names":["EnvFileError","dotenvOpts","loadEnvFile","notes","path","label","override","result","loadDotenv","loadNamedEnvFile","origin","error","resolve","loadCascadeFile","defaultsFrom","name","loadEnvCascade","profile","messageOf","loadEnvironment","selection","env","value"],"mappings":"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","file":"chunk-FFSMJDUF.js","sourcesContent":["import { resolve } from \"node:path\";\nimport { config as loadDotenv } from \"dotenv\";\n\nimport type { ProfileEnv } from \"./settings.js\";\n\n/**\n * A line the environment loader has to report, held until the logger exists.\n *\n * Loading the environment is what configures the logger: core builds it when\n * it first loads, from `LOG_LEVEL` and `LOG_FILE` as they stand then, and an\n * env file is one of the places those come from. So nothing here may load\n * core, and the loader cannot log as it goes. The caller logs these once core\n * is loaded.\n */\nexport interface EnvironmentNote {\n level: \"debug\" | \"info\";\n message: string;\n}\n\n/**\n * An environment file that could not be loaded.\n *\n * Unlike the conventional `.env` cascade, where a missing file is skipped, a\n * file somebody named (`--env <path>`, or a profile's `env: \"<file>\"`) is\n * required: running on without it would start the command against an\n * environment nobody asked for.\n */\nexport class EnvFileError extends Error {}\n\nconst dotenvOpts = { quiet: true };\n\n/**\n * Load one env file and note how many variables it set.\n *\n * @param notes Collects what the load has to report; see {@link EnvironmentNote}\n * @param path The file, absolute\n * @param label How the file is named in the note\n * @param override Whether the file wins over a variable the process already carries\n * @returns Why the file could not be loaded, or undefined when it was\n */\nfunction loadEnvFile(\n notes: EnvironmentNote[],\n path: string,\n label: string,\n override = false,\n): Error | undefined {\n const result = loadDotenv({ path, override, ...dotenvOpts });\n // dotenv returns an empty `parsed` beside the error for a missing file.\n if (result.error) return result.error;\n notes.push({\n level: \"debug\",\n message: `Loaded ${Object.keys(result.parsed ?? {}).length} environment variables from ${label}`,\n });\n return undefined;\n}\n\n/**\n * Load one named env file, refusing when it cannot be read.\n *\n * @param notes Collects what the load has to report; see {@link EnvironmentNote}\n * @param path The file, resolved against the working directory\n * @param origin Where the name came from, for the refusal message\n * @throws {EnvFileError} When the file cannot be loaded\n */\nfunction loadNamedEnvFile(\n notes: EnvironmentNote[],\n path: string,\n origin: string,\n): void {\n const error = loadEnvFile(notes, resolve(process.cwd(), path), path);\n if (error) {\n throw new EnvFileError(\n `Cannot load the environment file ${path} (from ${origin}): ${error.message}`,\n );\n }\n}\n\n/**\n * Load one file of the cascade, where absence is normal.\n *\n * @returns Whether the file was loaded\n * @throws {EnvFileError} When the file exists but cannot be read, which is\n * not absence: running on would silently drop settings somebody wrote\n */\nfunction loadCascadeFile(\n notes: EnvironmentNote[],\n defaultsFrom: string,\n name: string,\n override: boolean,\n): boolean {\n const error = loadEnvFile(notes, resolve(defaultsFrom, name), name, override);\n if (error === undefined) return true;\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return false;\n throw new EnvFileError(\n `Cannot load the environment file ${name}: ${error.message}`,\n );\n}\n\n/**\n * Load the conventional cascade: `.env`, then `.env.<profile>` when a\n * profile is selected, then `.env.local`, each overriding the one before.\n * A missing file is skipped; one that exists but cannot be read is not.\n *\n * The files resolve against `defaultsFrom`, so `craft start ./apps/eywa`\n * picks up that project's own environment rather than the one beside the\n * shell.\n *\n * @param notes Collects what the load has to report; see {@link EnvironmentNote}\n * @param defaultsFrom Directory the conventional .env files are read from\n * @param profile The selected profile, which names the middle file of the cascade\n * @throws {EnvFileError} When a file of the cascade exists but cannot be read\n */\nfunction loadEnvCascade(\n notes: EnvironmentNote[],\n defaultsFrom: string,\n profile?: string,\n): void {\n if (!loadCascadeFile(notes, defaultsFrom, \".env\", false)) {\n notes.push({ level: \"debug\", message: \"No .env file found\" });\n }\n // The profile is the mode: `--profile ing` reads `.env.ing` between the\n // pair, so one selection picks the instance and its environment together.\n if (profile !== undefined) {\n loadCascadeFile(notes, defaultsFrom, `.env.${profile}`, true);\n }\n loadCascadeFile(notes, defaultsFrom, \".env.local\", true);\n}\n\n/**\n * Turn a thrown value into a printable message. Non-Error throws (Bun's\n * `ResolveMessage` for a missing package, most usefully) still carry a\n * message, so surfacing it beats reporting \"Unknown error\". Shared by\n * `run` and `start` because both fail the same ways.\n */\nexport function messageOf(error: unknown): string {\n if (error instanceof Error) return error.message;\n return typeof error === \"object\" && error !== null && \"message\" in error\n ? String((error as { message: unknown }).message)\n : String(error);\n}\n\n/**\n * What decides the environment for one invocation.\n *\n * The profile IS the mode: selecting one selects the environment files\n * that go with it, so there is one concept to learn rather than two\n * overlapping ones. `--env` still wins, because a path typed on the\n * command line is the most specific thing anybody said.\n */\nexport interface EnvironmentSelection {\n /** `--env <path>`, resolved against the working directory. */\n explicit?: string | undefined;\n /** The selected profile's name, which names the middle file of the cascade. */\n profile?: string | undefined;\n /** What the selected profile says about its environment, if anything. */\n env?: ProfileEnv | undefined;\n /** Directory the conventional files are read from. */\n defaultsFrom?: string;\n}\n\n/**\n * Load the environment for one invocation, in the order the settings\n * documentation states.\n *\n * 1. `--env <path>`: that file, and nothing else.\n * 2. The selected profile's `env`: one exact file, or an inline map.\n * 3. The cascade: `.env`, then `.env.<profile>` when a profile is\n * selected, then `.env.local`, each overriding the one before.\n *\n * An inline map does not override a variable the process already carries,\n * matching how a `.env` file behaves: what is exported on a server wins\n * over what a settings file suggests.\n *\n * @returns What the load has to report, for the caller to log once core is loaded\n * @throws {EnvFileError} When a named file (`--env`, or a profile's `env` path) cannot be loaded, or a cascade file exists but cannot be read\n */\nexport function loadEnvironment(\n selection: EnvironmentSelection,\n): EnvironmentNote[] {\n const notes: EnvironmentNote[] = [];\n const defaultsFrom = selection.defaultsFrom ?? process.cwd();\n if (selection.explicit !== undefined) {\n loadNamedEnvFile(notes, selection.explicit, \"--env\");\n return notes;\n }\n const env = selection.env;\n if (typeof env === \"string\") {\n // Against the project root rather than the declaring file: an env file\n // is a project artefact, and a path relative to somebody's home\n // directory would name nothing on another machine.\n loadNamedEnvFile(\n notes,\n resolve(defaultsFrom, env),\n \"the selected profile's env\",\n );\n return notes;\n }\n if (env !== undefined) {\n for (const [name, value] of Object.entries(env)) {\n if (process.env[name] === undefined) process.env[name] = value;\n }\n notes.push({\n level: \"debug\",\n message: `Applied ${Object.keys(env).length} inline environment values from the selected profile`,\n });\n return notes;\n }\n loadEnvCascade(notes, defaultsFrom, selection.profile);\n return notes;\n}\n"]}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {b as b$1}from'./chunk-
|
|
2
|
-
//# sourceMappingURL=chunk-
|
|
1
|
+
import {b as b$1}from'./chunk-FFSMJDUF.js';import {relative,sep,join,resolve,extname}from'path';import {logger,ContextBuilder,RUNNER_ARGV,shutdownHandler,isRouteBuilder,isRouteDefinition}from'@routecraft/routecraft';import {statSync,readdirSync}from'fs';var i=[".ts",".mts",".cts",".js",".mjs",".cjs"],D="route",b=new Set(["node_modules","__tests__","__fixtures__","__mocks__","__snapshots__"]);function L(e){try{return statSync(e).isDirectory()}catch{return false}}function d(e){try{return statSync(e).isFile()}catch{return false}}function S(e){return e.startsWith(".")||b.has(e)}function m(e){return !i.includes(extname(e))||/\.d\.[cm]?ts$/.test(e)?false:!/\.(test|spec)\.[^.]+$/.test(e)}function h(e,n){let t=sep==="/"?e:e.split(sep).join("/"),r=sep==="/"?n:n.split(sep).join("/");return t<r?-1:t>r?1:0}function I(e){for(let n of i){let t=join(e,`${D}${n}`);if(d(t))return t}}function p(e,n){let t=[];for(let r of readdirSync(e,{withFileTypes:true})){let o=join(e,r.name);if(r.isFile()){m(r.name)&&t.push(o);continue}if(r.isSymbolicLink()){m(r.name)&&d(o)&&t.push(o);continue}if(!r.isDirectory()||S(r.name))continue;let s=n?.(o);s!==void 0?t.push(s):t.push(...p(o,n));}return t}function M(e){return p(e,I).sort(h)}function v(e){return p(e).sort(h)}function U(e,n){let t=relative(e,n);return t===""||t===".."||t.startsWith(`..${sep}`)?n:t}var P="craft.config";function G(e){for(let n of i){let t=join(e,`${P}${n}`);if(d(t))return t}}function W(e){return typeof e=="object"&&e!==null&&typeof e.apply=="function"}async function q(e,n=[]){let t=resolve(process.cwd(),e),r=extname(t);if(!i.includes(r))return {success:false,code:1,message:`Error: Only the following file types are supported: ${i.join(", ")}`};try{let o=await import(t),s=o.craftConfig;logger.info(`Loading file: ${t}`);let f=new ContextBuilder;s&&(logger.info("Found craftConfig export, applying configuration"),f.with(s));let g=j(f,o.default);if(!g.success)return g;let{context:a}=await f.build();return a.setStore(RUNNER_ARGV,n),shutdownHandler(a),await a.start(),{success:!0}}catch(o){let s=b$1(o);return logger.error(`Failed to run ${t}: ${s}`),{success:false,code:1,message:s}}}function N(e){return e?isRouteBuilder(e)?{ok:true,routes:[e]}:isRouteDefinition(e)?{ok:true,routes:[e]}:Array.isArray(e)?e.every(n=>isRouteBuilder(n)||isRouteDefinition(n))?{ok:true,routes:e}:{ok:false,reason:"All items in the default export array must be a RouteDefinition or a RouteBuilder."}:{ok:false,reason:"Invalid default export. Expected: RouteDefinition, RouteBuilder, or array of those."}:{ok:false,reason:"No default export found. Expected routes as default export."}}function j(e,n){let t=N(n);return t.ok?(t.routes.forEach(r=>e.routes(r)),logger.info(`Loaded ${t.routes.length} route(s) from default export`),{success:true}):(logger.error(t.reason),{success:false,code:1,message:t.reason})}export{L as a,M as b,v as c,U as d,P as e,G as f,W as g,q as h,N as i};//# sourceMappingURL=chunk-HNPTPH33.js.map
|
|
2
|
+
//# sourceMappingURL=chunk-HNPTPH33.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/project.ts","../src/run.ts"],"names":["MODULE_EXTENSIONS","CAPABILITY_SENTINEL","SKIPPED_DIRECTORIES","isDirectory","path","statSync","isFile","isSkippedProjectDirectory","name","isImportableModule","extname","byPath","a","b","left","sep","right","capabilityFile","dir","ext","candidate","join","collectModules","claim","out","entry","readdirSync","child","claimed","discoverCapabilityModules","discoverPluginModules","displayPath","root","rel","relative","CONFIG_STEM","findConfigFile","isPluginInstance","value","runCommand","filePath","cliArgs","absFilePath","resolve","module","craftConfig","logger","contextBuilder","ContextBuilder","configured","configureRoutes","context","RUNNER_ARGV","shutdownHandler","error","message","messageOf","collectRoutes","defaultExport","isRouteBuilder","isRouteDefinition","item","collected","route"],"mappings":"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","file":"chunk-5YILB3H6.js","sourcesContent":["import { readdirSync, statSync } from \"node:fs\";\nimport { extname, join, relative, sep } from \"node:path\";\nimport type { CraftPlugin } from \"@routecraft/routecraft\";\n\n/**\n * Module extensions the CLI will import, shared by `run` (its entry\n * file) and `start` (every discovered module) so one path is never\n * classified two ways.\n *\n * @internal\n */\nexport const MODULE_EXTENSIONS = [\".ts\", \".mts\", \".cts\", \".js\", \".mjs\", \".cjs\"];\n\n/**\n * Filename stem that marks a directory as one capability. The\n * directory is the capability's home: colocated helpers, fixtures and\n * tests live beside it and are never imported by discovery.\n *\n * @internal\n */\nconst CAPABILITY_SENTINEL = \"route\";\n\n/**\n * Directories discovery never walks into, at any depth. `__tests__`\n * and `__fixtures__` hold code that is not a capability;\n * `node_modules` and dot-folders are not authored project content.\n *\n * @internal\n */\nconst SKIPPED_DIRECTORIES = new Set([\n \"node_modules\",\n \"__tests__\",\n \"__fixtures__\",\n \"__mocks__\",\n \"__snapshots__\",\n]);\n\n/** True when `path` exists and is a directory. */\nexport function isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** True when `path` exists and is a file. */\nfunction isFile(path: string): boolean {\n try {\n return statSync(path).isFile();\n } catch {\n return false;\n }\n}\n\nfunction isSkippedProjectDirectory(name: string): boolean {\n return name.startsWith(\".\") || SKIPPED_DIRECTORIES.has(name);\n}\n\n/**\n * True for a module file discovery may import: a supported extension,\n * not a declaration file, and not a test or spec.\n *\n * The test check matches the final `.test.<ext>` / `.spec.<ext>`\n * segment pair, so `route.test.ts` and `route.bun.test.ts` are both\n * excluded.\n */\nfunction isImportableModule(name: string): boolean {\n if (!MODULE_EXTENSIONS.includes(extname(name))) return false;\n if (/\\.d\\.[cm]?ts$/.test(name)) return false;\n return !/\\.(test|spec)\\.[^.]+$/.test(name);\n}\n\n/**\n * Order two paths by code unit, never by locale. `localeCompare`\n * answers a different question on a different machine, and this order\n * decides registration order, which decides config precedence.\n *\n * Separators are normalised first because `\\` and `/` sort differently\n * against the rest of the alphabet, so a nested module and its sibling\n * would otherwise swap places between Windows and POSIX. Mirrors the\n * sort in the markdown walk for the same reason.\n */\nfunction byPath(a: string, b: string): number {\n const left = sep === \"/\" ? a : a.split(sep).join(\"/\");\n const right = sep === \"/\" ? b : b.split(sep).join(\"/\");\n return left < right ? -1 : left > right ? 1 : 0;\n}\n\n/**\n * Return the capability sentinel file inside `dir`, if one is there.\n * A directory holding `route.ts` is one capability and everything else\n * inside it is private to that capability.\n */\nfunction capabilityFile(dir: string): string | undefined {\n for (const ext of MODULE_EXTENSIONS) {\n const candidate = join(dir, `${CAPABILITY_SENTINEL}${ext}`);\n if (isFile(candidate)) return candidate;\n }\n return undefined;\n}\n\n/**\n * Walk a convention folder and return every module to import, in path\n * order.\n *\n * `claim` lets a directory stand for one unit rather than being walked\n * into: the capability walk passes {@link capabilityFile}, so a folder\n * holding `route.ts` yields that file and its colocated tests, fixtures\n * and helpers are never seen. The root is never offered to `claim`,\n * which is what keeps `capabilities/` itself from being mistaken for a\n * capability.\n */\nfunction collectModules(\n dir: string,\n claim?: (directory: string) => string | undefined,\n): string[] {\n const out: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const child = join(dir, entry.name);\n // A symlink is neither a file nor a directory to `readdirSync`, so a\n // symlinked capability would be dropped without a word. Resolve file\n // links and load them; leave directory links unfollowed, which is\n // what keeps the walk loop-free and matches the markdown walk.\n if (entry.isFile()) {\n if (isImportableModule(entry.name)) out.push(child);\n continue;\n }\n if (entry.isSymbolicLink()) {\n // Only a link needs the follow-the-target stat; a regular file is\n // already proven to be one, and statting it again would let a file\n // that vanished mid-walk disappear silently.\n if (isImportableModule(entry.name) && isFile(child)) out.push(child);\n continue;\n }\n if (!entry.isDirectory()) continue;\n if (isSkippedProjectDirectory(entry.name)) continue;\n const claimed = claim?.(child);\n if (claimed !== undefined) out.push(claimed);\n else out.push(...collectModules(child, claim));\n }\n return out;\n}\n\n/**\n * Walk a `capabilities/` tree. Two forms, mixable in one tree: a\n * directory holding `route.ts` is one capability, and any other\n * importable module is a single-file capability. Every other directory\n * is a grouping folder.\n */\nexport function discoverCapabilityModules(dir: string): string[] {\n return collectModules(dir, capabilityFile).sort(byPath);\n}\n\n/**\n * Walk a `plugins/` tree. Subfolders are grouping only; there is no\n * sentinel form, because a plugin is one module.\n */\nexport function discoverPluginModules(dir: string): string[] {\n return collectModules(dir).sort(byPath);\n}\n\n/**\n * Name a module by its path relative to the project root, for log and\n * error messages that have to be actionable in a terminal.\n */\nexport function displayPath(root: string, path: string): string {\n const rel = relative(root, path);\n // A leading `..` segment, not a `..` prefix: a directory legitimately\n // named `..shared` is inside the root and should read as relative.\n return rel === \"\" || rel === \"..\" || rel.startsWith(`..${sep}`) ? path : rel;\n}\n\n/** Filename stem of the project configuration file. */\nexport const CONFIG_STEM = \"craft.config\";\n\n/**\n * Locate the project configuration file directly under `root`.\n * Returns undefined when there is none.\n */\nexport function findConfigFile(root: string): string | undefined {\n for (const ext of MODULE_EXTENSIONS) {\n const candidate = join(root, `${CONFIG_STEM}${ext}`);\n if (isFile(candidate)) return candidate;\n }\n return undefined;\n}\n\n/**\n * True when `value` looks like a `CraftPlugin`: an object carrying a\n * callable `apply`. Structural rather than branded, matching how the\n * context itself consumes plugins.\n */\nexport function isPluginInstance(value: unknown): value is CraftPlugin {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { apply?: unknown }).apply === \"function\"\n );\n}\n","import { resolve, extname } from \"node:path\";\nimport {\n ContextBuilder,\n type CraftConfig,\n isRouteBuilder,\n isRouteDefinition,\n logger,\n shutdownHandler,\n RUNNER_ARGV,\n type AnyRouteBuilder,\n type RouteDefinition,\n} from \"@routecraft/routecraft\";\nimport { MODULE_EXTENSIONS } from \"./project.js\";\nimport { messageOf } from \"./util.js\";\n\n/**\n * Result of a CLI command that boots a context. `success: false`\n * carries the message the CLI prints and the exit code it uses.\n *\n * @internal\n */\nexport type RunResult =\n { success: true } | { success: false; code?: number; message: string };\n\n/**\n * Load a routecraft file, build a context, and start it.\n *\n * Adapter-agnostic: the runner knows nothing about which adapters (CLI, HTTP,\n * cron, etc.) are used. It sets `RUNNER_ARGV` in the context store so that\n * adapters can read remaining CLI tokens if needed.\n *\n * @param filePath - Path to the routecraft file to run\n * @param cliArgs - Remaining CLI arguments after the file path\n */\nexport async function runCommand(\n filePath: string,\n cliArgs: string[] = [],\n): Promise<RunResult> {\n const absFilePath = resolve(process.cwd(), filePath);\n const ext = extname(absFilePath);\n\n if (!MODULE_EXTENSIONS.includes(ext)) {\n return {\n success: false,\n code: 1,\n message: `Error: Only the following file types are supported: ${MODULE_EXTENSIONS.join(\", \")}`,\n };\n }\n\n try {\n // Load the module (CLI already set LOG_LEVEL / LOG_FILE from argv in index.ts).\n // Logger uses env first; context will apply craftConfig.log when built (env wins if set).\n const module = await import(absFilePath);\n const craftConfig = module.craftConfig as CraftConfig | undefined;\n\n logger.info(`Loading file: ${absFilePath}`);\n\n // Create context builder\n const contextBuilder = new ContextBuilder();\n\n // Apply craftConfig (routes, plugins, etc.); context applies config.log when built.\n if (craftConfig) {\n logger.info(\"Found craftConfig export, applying configuration\");\n contextBuilder.with(craftConfig);\n }\n\n // Handle routes from the default export\n const configured = configureRoutes(contextBuilder, module.default);\n if (!configured.success) {\n return configured;\n }\n\n // Build and start the context. Adapters handle their own lifecycle.\n // RUNNER_ARGV lets adapters (e.g. CLI) read remaining args without\n // the runner needing to know which adapters are in use.\n const { context } = await contextBuilder.build();\n context.setStore(RUNNER_ARGV, cliArgs);\n shutdownHandler(context);\n await context.start();\n\n return { success: true };\n } catch (error: unknown) {\n const message = messageOf(error);\n logger.error(`Failed to run ${absFilePath}: ${message}`);\n return { success: false, code: 1, message };\n }\n}\n\n/**\n * Outcome of interpreting a module's default export as routes. Shared\n * by `run` (one entry file) and `start` (one capability file at a\n * time) so both accept exactly the same shapes.\n *\n * @internal\n */\nexport type CollectRoutesResult =\n | { ok: true; routes: (RouteDefinition | AnyRouteBuilder)[] }\n | { ok: false; reason: string };\n\n/**\n * Interpret a module's default export as routes. Accepts a single\n * `RouteBuilder`, a single `RouteDefinition`, or an array of either.\n * Uses brand-based guards so a builder created by another copy of the\n * package still passes.\n *\n * @internal\n */\nexport function collectRoutes(defaultExport: unknown): CollectRoutesResult {\n if (!defaultExport) {\n return {\n ok: false,\n reason: \"No default export found. Expected routes as default export.\",\n };\n }\n if (isRouteBuilder(defaultExport)) {\n return { ok: true, routes: [defaultExport as AnyRouteBuilder] };\n }\n if (isRouteDefinition(defaultExport)) {\n return { ok: true, routes: [defaultExport as RouteDefinition] };\n }\n if (Array.isArray(defaultExport)) {\n if (\n !defaultExport.every(\n (item) => isRouteBuilder(item) || isRouteDefinition(item),\n )\n ) {\n return {\n ok: false,\n reason:\n \"All items in the default export array must be a RouteDefinition or a RouteBuilder.\",\n };\n }\n return {\n ok: true,\n routes: defaultExport as (RouteDefinition | AnyRouteBuilder)[],\n };\n }\n return {\n ok: false,\n reason:\n \"Invalid default export. Expected: RouteDefinition, RouteBuilder, or array of those.\",\n };\n}\n\nfunction configureRoutes(\n contextBuilder: InstanceType<typeof ContextBuilder>,\n defaultExport: unknown,\n): RunResult {\n const collected = collectRoutes(defaultExport);\n if (!collected.ok) {\n logger.error(collected.reason);\n return { success: false, code: 1, message: collected.reason };\n }\n collected.routes.forEach((route) => contextBuilder.routes(route));\n logger.info(`Loaded ${collected.routes.length} route(s) from default export`);\n return { success: true };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/project.ts","../src/run.ts"],"names":["MODULE_EXTENSIONS","CAPABILITY_SENTINEL","SKIPPED_DIRECTORIES","isDirectory","path","statSync","isFile","isSkippedProjectDirectory","name","isImportableModule","extname","byPath","a","b","left","sep","right","capabilityFile","dir","ext","candidate","join","collectModules","claim","out","entry","readdirSync","child","claimed","discoverCapabilityModules","discoverPluginModules","displayPath","root","rel","relative","CONFIG_STEM","findConfigFile","isPluginInstance","value","runCommand","filePath","cliArgs","absFilePath","resolve","module","craftConfig","logger","contextBuilder","ContextBuilder","configured","configureRoutes","context","RUNNER_ARGV","shutdownHandler","error","message","messageOf","collectRoutes","defaultExport","isRouteBuilder","isRouteDefinition","item","collected","route"],"mappings":"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","file":"chunk-HNPTPH33.js","sourcesContent":["import { readdirSync, statSync } from \"node:fs\";\nimport { extname, join, relative, sep } from \"node:path\";\nimport type { CraftPlugin } from \"@routecraft/routecraft\";\n\n/**\n * Module extensions the CLI will import, shared by `run` (its entry\n * file) and `start` (every discovered module) so one path is never\n * classified two ways.\n *\n * @internal\n */\nexport const MODULE_EXTENSIONS = [\".ts\", \".mts\", \".cts\", \".js\", \".mjs\", \".cjs\"];\n\n/**\n * Filename stem that marks a directory as one capability. The\n * directory is the capability's home: colocated helpers, fixtures and\n * tests live beside it and are never imported by discovery.\n *\n * @internal\n */\nconst CAPABILITY_SENTINEL = \"route\";\n\n/**\n * Directories discovery never walks into, at any depth. `__tests__`\n * and `__fixtures__` hold code that is not a capability;\n * `node_modules` and dot-folders are not authored project content.\n *\n * @internal\n */\nconst SKIPPED_DIRECTORIES = new Set([\n \"node_modules\",\n \"__tests__\",\n \"__fixtures__\",\n \"__mocks__\",\n \"__snapshots__\",\n]);\n\n/** True when `path` exists and is a directory. */\nexport function isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** True when `path` exists and is a file. */\nfunction isFile(path: string): boolean {\n try {\n return statSync(path).isFile();\n } catch {\n return false;\n }\n}\n\nfunction isSkippedProjectDirectory(name: string): boolean {\n return name.startsWith(\".\") || SKIPPED_DIRECTORIES.has(name);\n}\n\n/**\n * True for a module file discovery may import: a supported extension,\n * not a declaration file, and not a test or spec.\n *\n * The test check matches the final `.test.<ext>` / `.spec.<ext>`\n * segment pair, so `route.test.ts` and `route.bun.test.ts` are both\n * excluded.\n */\nfunction isImportableModule(name: string): boolean {\n if (!MODULE_EXTENSIONS.includes(extname(name))) return false;\n if (/\\.d\\.[cm]?ts$/.test(name)) return false;\n return !/\\.(test|spec)\\.[^.]+$/.test(name);\n}\n\n/**\n * Order two paths by code unit, never by locale. `localeCompare`\n * answers a different question on a different machine, and this order\n * decides registration order, which decides config precedence.\n *\n * Separators are normalised first because `\\` and `/` sort differently\n * against the rest of the alphabet, so a nested module and its sibling\n * would otherwise swap places between Windows and POSIX. Mirrors the\n * sort in the markdown walk for the same reason.\n */\nfunction byPath(a: string, b: string): number {\n const left = sep === \"/\" ? a : a.split(sep).join(\"/\");\n const right = sep === \"/\" ? b : b.split(sep).join(\"/\");\n return left < right ? -1 : left > right ? 1 : 0;\n}\n\n/**\n * Return the capability sentinel file inside `dir`, if one is there.\n * A directory holding `route.ts` is one capability and everything else\n * inside it is private to that capability.\n */\nfunction capabilityFile(dir: string): string | undefined {\n for (const ext of MODULE_EXTENSIONS) {\n const candidate = join(dir, `${CAPABILITY_SENTINEL}${ext}`);\n if (isFile(candidate)) return candidate;\n }\n return undefined;\n}\n\n/**\n * Walk a convention folder and return every module to import, in path\n * order.\n *\n * `claim` lets a directory stand for one unit rather than being walked\n * into: the capability walk passes {@link capabilityFile}, so a folder\n * holding `route.ts` yields that file and its colocated tests, fixtures\n * and helpers are never seen. The root is never offered to `claim`,\n * which is what keeps `capabilities/` itself from being mistaken for a\n * capability.\n */\nfunction collectModules(\n dir: string,\n claim?: (directory: string) => string | undefined,\n): string[] {\n const out: string[] = [];\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const child = join(dir, entry.name);\n // A symlink is neither a file nor a directory to `readdirSync`, so a\n // symlinked capability would be dropped without a word. Resolve file\n // links and load them; leave directory links unfollowed, which is\n // what keeps the walk loop-free and matches the markdown walk.\n if (entry.isFile()) {\n if (isImportableModule(entry.name)) out.push(child);\n continue;\n }\n if (entry.isSymbolicLink()) {\n // Only a link needs the follow-the-target stat; a regular file is\n // already proven to be one, and statting it again would let a file\n // that vanished mid-walk disappear silently.\n if (isImportableModule(entry.name) && isFile(child)) out.push(child);\n continue;\n }\n if (!entry.isDirectory()) continue;\n if (isSkippedProjectDirectory(entry.name)) continue;\n const claimed = claim?.(child);\n if (claimed !== undefined) out.push(claimed);\n else out.push(...collectModules(child, claim));\n }\n return out;\n}\n\n/**\n * Walk a `capabilities/` tree. Two forms, mixable in one tree: a\n * directory holding `route.ts` is one capability, and any other\n * importable module is a single-file capability. Every other directory\n * is a grouping folder.\n */\nexport function discoverCapabilityModules(dir: string): string[] {\n return collectModules(dir, capabilityFile).sort(byPath);\n}\n\n/**\n * Walk a `plugins/` tree. Subfolders are grouping only; there is no\n * sentinel form, because a plugin is one module.\n */\nexport function discoverPluginModules(dir: string): string[] {\n return collectModules(dir).sort(byPath);\n}\n\n/**\n * Name a module by its path relative to the project root, for log and\n * error messages that have to be actionable in a terminal.\n */\nexport function displayPath(root: string, path: string): string {\n const rel = relative(root, path);\n // A leading `..` segment, not a `..` prefix: a directory legitimately\n // named `..shared` is inside the root and should read as relative.\n return rel === \"\" || rel === \"..\" || rel.startsWith(`..${sep}`) ? path : rel;\n}\n\n/** Filename stem of the project configuration file. */\nexport const CONFIG_STEM = \"craft.config\";\n\n/**\n * Locate the project configuration file directly under `root`.\n * Returns undefined when there is none.\n */\nexport function findConfigFile(root: string): string | undefined {\n for (const ext of MODULE_EXTENSIONS) {\n const candidate = join(root, `${CONFIG_STEM}${ext}`);\n if (isFile(candidate)) return candidate;\n }\n return undefined;\n}\n\n/**\n * True when `value` looks like a `CraftPlugin`: an object carrying a\n * callable `apply`. Structural rather than branded, matching how the\n * context itself consumes plugins.\n */\nexport function isPluginInstance(value: unknown): value is CraftPlugin {\n return (\n typeof value === \"object\" &&\n value !== null &&\n typeof (value as { apply?: unknown }).apply === \"function\"\n );\n}\n","import { resolve, extname } from \"node:path\";\nimport {\n ContextBuilder,\n type CraftConfig,\n isRouteBuilder,\n isRouteDefinition,\n logger,\n shutdownHandler,\n RUNNER_ARGV,\n type AnyRouteBuilder,\n type RouteDefinition,\n} from \"@routecraft/routecraft\";\nimport { MODULE_EXTENSIONS } from \"./project.js\";\nimport { messageOf } from \"./util.js\";\n\n/**\n * Result of a CLI command that boots a context. `success: false`\n * carries the message the CLI prints and the exit code it uses.\n *\n * @internal\n */\nexport type RunResult =\n { success: true } | { success: false; code?: number; message: string };\n\n/**\n * Load a routecraft file, build a context, and start it.\n *\n * Adapter-agnostic: the runner knows nothing about which adapters (CLI, HTTP,\n * cron, etc.) are used. It sets `RUNNER_ARGV` in the context store so that\n * adapters can read remaining CLI tokens if needed.\n *\n * @param filePath - Path to the routecraft file to run\n * @param cliArgs - Remaining CLI arguments after the file path\n */\nexport async function runCommand(\n filePath: string,\n cliArgs: string[] = [],\n): Promise<RunResult> {\n const absFilePath = resolve(process.cwd(), filePath);\n const ext = extname(absFilePath);\n\n if (!MODULE_EXTENSIONS.includes(ext)) {\n return {\n success: false,\n code: 1,\n message: `Error: Only the following file types are supported: ${MODULE_EXTENSIONS.join(\", \")}`,\n };\n }\n\n try {\n // Load the module (CLI already set LOG_LEVEL / LOG_FILE from argv in index.ts).\n // Logger uses env first; context will apply craftConfig.log when built (env wins if set).\n const module = await import(absFilePath);\n const craftConfig = module.craftConfig as CraftConfig | undefined;\n\n logger.info(`Loading file: ${absFilePath}`);\n\n // Create context builder\n const contextBuilder = new ContextBuilder();\n\n // Apply craftConfig (routes, plugins, etc.); context applies config.log when built.\n if (craftConfig) {\n logger.info(\"Found craftConfig export, applying configuration\");\n contextBuilder.with(craftConfig);\n }\n\n // Handle routes from the default export\n const configured = configureRoutes(contextBuilder, module.default);\n if (!configured.success) {\n return configured;\n }\n\n // Build and start the context. Adapters handle their own lifecycle.\n // RUNNER_ARGV lets adapters (e.g. CLI) read remaining args without\n // the runner needing to know which adapters are in use.\n const { context } = await contextBuilder.build();\n context.setStore(RUNNER_ARGV, cliArgs);\n shutdownHandler(context);\n await context.start();\n\n return { success: true };\n } catch (error: unknown) {\n const message = messageOf(error);\n logger.error(`Failed to run ${absFilePath}: ${message}`);\n return { success: false, code: 1, message };\n }\n}\n\n/**\n * Outcome of interpreting a module's default export as routes. Shared\n * by `run` (one entry file) and `start` (one capability file at a\n * time) so both accept exactly the same shapes.\n *\n * @internal\n */\nexport type CollectRoutesResult =\n | { ok: true; routes: (RouteDefinition | AnyRouteBuilder)[] }\n | { ok: false; reason: string };\n\n/**\n * Interpret a module's default export as routes. Accepts a single\n * `RouteBuilder`, a single `RouteDefinition`, or an array of either.\n * Uses brand-based guards so a builder created by another copy of the\n * package still passes.\n *\n * @internal\n */\nexport function collectRoutes(defaultExport: unknown): CollectRoutesResult {\n if (!defaultExport) {\n return {\n ok: false,\n reason: \"No default export found. Expected routes as default export.\",\n };\n }\n if (isRouteBuilder(defaultExport)) {\n return { ok: true, routes: [defaultExport as AnyRouteBuilder] };\n }\n if (isRouteDefinition(defaultExport)) {\n return { ok: true, routes: [defaultExport as RouteDefinition] };\n }\n if (Array.isArray(defaultExport)) {\n if (\n !defaultExport.every(\n (item) => isRouteBuilder(item) || isRouteDefinition(item),\n )\n ) {\n return {\n ok: false,\n reason:\n \"All items in the default export array must be a RouteDefinition or a RouteBuilder.\",\n };\n }\n return {\n ok: true,\n routes: defaultExport as (RouteDefinition | AnyRouteBuilder)[],\n };\n }\n return {\n ok: false,\n reason:\n \"Invalid default export. Expected: RouteDefinition, RouteBuilder, or array of those.\",\n };\n}\n\nfunction configureRoutes(\n contextBuilder: InstanceType<typeof ContextBuilder>,\n defaultExport: unknown,\n): RunResult {\n const collected = collectRoutes(defaultExport);\n if (!collected.ok) {\n logger.error(collected.reason);\n return { success: false, code: 1, message: collected.reason };\n }\n collected.routes.forEach((route) => contextBuilder.routes(route));\n logger.info(`Loaded ${collected.routes.length} route(s) from default export`);\n return { success: true };\n}\n"]}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
var t="https://bun.com/docs/installation",o="https://routecraft.dev/docs/advanced/programmatic-invocation",r=`[routecraft] The craft CLI requires Bun. Install Bun from ${t}, or embed @routecraft/routecraft programmatically (see ${o}).`;
|
|
2
|
+
export{t as a,r as b};//# sourceMappingURL=chunk-OKX5YME7.js.map
|
|
3
|
+
//# sourceMappingURL=chunk-OKX5YME7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bun-requirement.ts"],"names":["INSTALL_URL","EMBEDDING_DOC_URL","MISSING_BUN_MESSAGE"],"mappings":"AAQO,IAAMA,CAAAA,CAAc,oCAEdC,CAAAA,CACX,8DAAA,CAEWC,EACX,CAAA,0DAAA,EACoBF,CAAW,2DAC0BC,CAAiB,CAAA,EAAA","file":"chunk-OKX5YME7.js","sourcesContent":["/**\n * What the craft CLI tells someone who runs it without Bun.\n *\n * Kept apart from `runtime-gate.ts` because the entry point checks for Bun\n * before anything else, and that check must not load core: `runtime-gate.ts`\n * reads the version with core's parser, and loading core builds the logger.\n */\n\nexport const INSTALL_URL = \"https://bun.com/docs/installation\";\n\nexport const EMBEDDING_DOC_URL =\n \"https://routecraft.dev/docs/advanced/programmatic-invocation\";\n\nexport const MISSING_BUN_MESSAGE =\n `[routecraft] The craft CLI requires Bun. ` +\n `Install Bun from ${INSTALL_URL}, ` +\n `or embed @routecraft/routecraft programmatically (see ${EMBEDDING_DOC_URL}).`;\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../package.json"],"names":["version"],"mappings":"AAEE,IAAAA,CAAAA,CAAW","file":"chunk-TSLKIWOL.js","sourcesContent":["{\n \"name\": \"@routecraft/cli\",\n \"version\": \"0.7.1-canary-20260924104103\",\n \"description\": \"CLI for running Routecraft capabilities\",\n \"private\": false,\n \"type\": \"module\",\n \"main\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"exports\": {\n \".\": {\n \"import\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\"\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"scripts\": {\n \"build\": \"tsup src/index.ts src/tui/app.tsx src/tui/db.ts --format esm --dts --external react --external ink --external bun:sqlite\",\n \"test\": \"vitest\",\n \"test:coverage\": \"vitest --coverage\",\n \"prepublishOnly\": \"bun run build\"\n },\n \"dependencies\": {\n \"@agentclientprotocol/sdk\": \"^1.4.0\",\n \"@opentelemetry/sdk-trace-base\": \"^2.10.0\",\n \"@routecraft/routecraft\": \"0.7.1-canary-20260924104103\",\n \"agent-browser\": \"^0.17.1\",\n \"cheerio\": \"^1.2.0\",\n \"commander\": \"^14.0.3\",\n \"croner\": \"^10.0.1\",\n \"dotenv\": \"^17.4.2\",\n \"execa\": \"^9.6.0\",\n \"fast-xml-parser\": \"^5.10.1\",\n \"imapflow\": \"^1.4.7\",\n \"ink\": \"^5.2.1\",\n \"jose\": \"^6.2.8\",\n \"mailparser\": \"^3.9.15\",\n \"nodemailer\": \"^6.10.1\",\n \"papaparse\": \"^5.5.4\",\n \"pino-pretty\": \"^13.1.3\",\n \"react\": \"^18.3.1\",\n \"shescape\": \"^3.0.2\",\n \"tsdav\": \"^2.3.1\",\n \"yaml\": \"^2.9.0\"\n },\n \"devDependencies\": {\n \"@types/mailparser\": \"^3.4.6\",\n \"@types/nodemailer\": \"^6.4.24\",\n \"@types/react\": \"^18.3.28\",\n \"ink-testing-library\": \"^4.0.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\"\n },\n \"repository\": \"https://github.com/routecraftjs/routecraft\",\n \"bin\": {\n \"craft\": \"./dist/index.js\"\n },\n \"engines\": {\n \"bun\": \">=1.1.0\"\n },\n \"keywords\": [\n \"routecraft\",\n \"cli\",\n \"routes\",\n \"automation\",\n \"typescript\"\n ],\n \"author\": \"routecraftjs\",\n \"license\": \"Apache-2.0\",\n \"homepage\": \"https://routecraft.dev\"\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {b as b$1}from'./chunk-
|
|
1
|
+
import {b as b$1}from'./chunk-FFSMJDUF.js';import {homedir}from'os';import {join,resolve}from'path';import {existsSync,readFileSync}from'fs';import {parse}from'yaml';var _="http://127.0.0.1:8080",I=[join(".routecraft","settings.yaml"),join(".routecraft","settings.yml")];function $(e){let n=I.map(e),t=n.filter(r=>existsSync(r));if(t.length>1)throw new o(`Both ${t[0]} and ${t[1]} exist. Settings are read from exactly one file per location; keep one and remove the other.`);return t[0]??n[0]}var b=["pretty","json","raw"],G="CRAFT_URL",M="CRAFT_TOKEN",D="CRAFT_FORMAT",J="CRAFT_AGENT",z="CRAFT_PROFILE",o=class extends Error{};function c(e){let n=e?.trim();return n===void 0||n===""?void 0:n}function k(e){let n;try{n=readFileSync(e,"utf8");}catch(s){let i=s?.code;if(i==="ENOENT"||i==="ENOTDIR")return;throw new o(`${e} exists but could not be read: ${b$1(s)}`)}let t;try{t=parse(n);}catch(s){let i=b$1(s).split(`
|
|
2
2
|
`,1)[0];throw new o(`${e} is not valid YAML: ${i}`)}if(t==null)return {};if(typeof t!="object"||Array.isArray(t))throw new o(`${e} must contain a mapping of settings (for example \`url: http://127.0.0.1:8080\`).`);let r=t.profiles;if(r===null&&delete t.profiles,r!=null){if(!N(r))throw new o(`The "profiles" in ${e} must be a map of profile names to their settings.`);for(let[s,i]of Object.entries(r))if(i!=null&&!N(i))throw new o(`The profile "${s}" in ${e} must be a map of settings, not a single value.`)}return t}function B(e,n){if(b.includes(e))return e;throw new o(`${n} must be one of ${b.join(", ")}; received "${e}".`)}function te(e={}){let n=e.cwd??process.cwd(),t=e.env??process.env,r=$(g=>resolve(n,g)),s=e.home??homedir(),i=$(g=>join(s,g)),f=k(r),p=r===i?void 0:k(i),w=Y(e.profile,t[z],{settings:f,path:r},{settings:p,path:i}),m=(g,A,L)=>{let S=c(A),T=c(L);return S!==void 0?{value:S,source:"flag"}:T!==void 0?{value:T,source:"environment"}:x(g,E(w,f,r,p,i))},a=m("url",e.url,t[G])??{value:_,source:"default"};if(typeof a.value!="string"||a.value.trim()==="")throw new o(`The instance URL from the ${u(a)} is empty. Give a full base URL, for example http://127.0.0.1:8080.`);let h;try{h=new URL(a.value);}catch{throw new o(`The instance URL from the ${u(a)} is not a URL: "${a.value}". Give a full base URL, for example http://127.0.0.1:8080.`)}if(h.protocol!=="http:"&&h.protocol!=="https:")throw new o(`The instance URL from the ${u(a)} uses "${h.protocol}"; the ops server is reached over http or https.`);let l=m("token",e.token,t[M]);if(l!==void 0&&typeof l.value!="string")throw new o(`The token from the ${l.source} must be a string.`);if(l!==void 0&&l.value.trim()==="")throw new o(`The token from the ${u(l)} is empty. Put a credential there, or remove the key.`);let v=m("format",e.format,t[D]),O=v===void 0?{value:"pretty",source:"default"}:{...v,value:B(String(v.value),`The output format from the ${v.source}`)},d=m("agent",e.agent,t[J]);if(d!==void 0&&typeof d.value!="string")throw new o(`The agent name from the ${u(d)} must be a string.`);if(d!==void 0&&d.value.trim()==="")throw new o(`The agent name from the ${u(d)} is empty. Name an agent, or remove the key.`);let F=q(w,f,r,p,i);return {url:a,token:l,format:O,agent:d,profile:w,env:F}}function Y(e,n,t,r){let s=new Set([...Object.keys(t.settings?.profiles??{}),...Object.keys(r.settings?.profiles??{})]),i=()=>s.size===0?"no profiles are defined in either settings file":`defined profiles: ${[...s].sort().join(", ")}`,f=c(e)!==void 0?{value:c(e),source:"flag"}:c(n)!==void 0?{value:c(n),source:"environment"}:t.settings?.profile!==void 0?{value:t.settings.profile,source:"project file",path:t.path}:r.settings?.profile!==void 0?{value:r.settings.profile,source:"global file",path:r.path}:void 0;if(f!==void 0){if(typeof f.value!="string"||f.value.trim()==="")throw new o(`The profile from the ${u(f)} is not a name; ${i()}.`);if(!s.has(f.value))throw new o(`No profile "${f.value}" (named by the ${u(f)}); ${i()}.`);return f}}function C(e,n){if(e===void 0||n===void 0)return;let t=e.profiles;if(t!==void 0)return Object.prototype.hasOwnProperty.call(t,n)?t[n]:void 0}function q(e,n,t,r,s){let i=x("env",E(e,n,t,r,s));if(i===void 0)return;let{value:f}=i,p=typeof f=="string"&&f.trim()==="";if(typeof f!="string"&&!H(f)||p)throw new o(`The "env" from the ${u(i)} must be a path to an env file or a map of names to string values.`);return i}function x(e,n){for(let t of n){let r=t.values?.[e];if(r!==void 0)return {value:r,source:t.source,path:t.path}}}function E(e,n,t,r,s){return [{values:C(n,e?.value),source:"project profile",path:t},{values:n,source:"project file",path:t},{values:C(r,e?.value),source:"global profile",path:s},{values:r,source:"global file",path:s}]}function N(e){return typeof e=="object"&&e!==null&&!Array.isArray(e)}function H(e){return e===null||typeof e!="object"||Array.isArray(e)?false:Object.values(e).every(n=>typeof n=="string")}function u(e){return e.path===void 0?e.source:`${e.source} ${e.path}`}
|
|
3
|
-
export{_ as a,o as b,c,te as d,u as e};//# sourceMappingURL=chunk-
|
|
4
|
-
//# sourceMappingURL=chunk-
|
|
3
|
+
export{_ as a,o as b,c,te as d,u as e};//# sourceMappingURL=chunk-V56CL7XK.js.map
|
|
4
|
+
//# sourceMappingURL=chunk-V56CL7XK.js.map
|