@swifty.js/swifty 0.0.30 → 0.0.32

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.
Files changed (53) hide show
  1. package/README.md +60 -3
  2. package/dist/agent-2DUTUJFX.js +4 -0
  3. package/dist/anthropic-KR5MXB5I.js +4 -0
  4. package/dist/checker-RKAWOOXC.js +4 -0
  5. package/dist/chunk-2EV72T5X.js +4 -0
  6. package/dist/chunk-4YVJP2CO.js +4 -0
  7. package/dist/chunk-BWWNI2FK.js +121 -0
  8. package/dist/{chunk-K5ZK27BX.js → chunk-JMF2DWGY.js} +1 -1
  9. package/dist/chunk-LTFPTWX4.js +612 -0
  10. package/dist/chunk-NERVCUWE.js +4 -0
  11. package/dist/chunk-S35E7OYL.js +5 -0
  12. package/dist/{chunk-QFSCPD63.js → chunk-TCQ4EB4G.js} +1 -1
  13. package/dist/{chunk-C3WGLVVZ.js → chunk-TKQ5T5VO.js} +1 -1
  14. package/dist/chunk-TW7RHCDS.js +150 -0
  15. package/dist/lib/agent-3OEC4SOB.js +10 -0
  16. package/dist/lib/{anthropic-CL5IK7KN.js → anthropic-B33M2AVN.js} +5 -6
  17. package/dist/lib/{checker-BS4MK3O6.js → checker-QNMXPZ7C.js} +3 -4
  18. package/dist/lib/{chunk-OQIQOU5S.js → chunk-3OFLDUAW.js} +121 -1
  19. package/dist/lib/{chunk-EY7HE52Q.js → chunk-3XCGUKGJ.js} +18 -24
  20. package/dist/lib/{chunk-7QPDFSQE.js → chunk-4BPKIIQD.js} +29 -10
  21. package/dist/lib/{chunk-KG4MJGKQ.js → chunk-BCBD34D2.js} +2 -5
  22. package/dist/lib/{chunk-IYPTOY3Y.js → chunk-FODSMOWP.js} +40 -31
  23. package/dist/lib/{chunk-RMB3J46W.js → chunk-SOOSTGHI.js} +198 -37
  24. package/dist/lib/{chunk-I7OPU4K2.js → chunk-TJLAJGG4.js} +194 -34
  25. package/dist/lib/{chunk-OOSNCCI7.js → chunk-UH5M7FDP.js} +151 -152
  26. package/dist/lib/{chunk-PIL7M52F.js → chunk-XT67KGWE.js} +16 -3
  27. package/dist/lib/index.d.ts +2103 -1344
  28. package/dist/lib/index.js +11869 -9629
  29. package/dist/lib/{openai-LH4E7ZQS.js → openai-C2PZFGJ2.js} +3 -3
  30. package/dist/lib/{tool-filter-CS6W2GMU.js → tool-filter-IMIU7CW2.js} +3 -2
  31. package/dist/main.js +186 -187
  32. package/dist/{node-4LTSRXL2.js → node-GXMM4JG3.js} +1 -1
  33. package/dist/openai-M7B2TITO.js +34 -0
  34. package/dist/seatbelt-3GM2YGXV.js +5 -0
  35. package/dist/{server-IVIRQTO7.js → server-QMMIYNXW.js} +16 -23
  36. package/dist/tool-filter-7LLVAHD5.js +4 -0
  37. package/package.json +9 -12
  38. package/dist/agent-TCNIRVHD.js +0 -4
  39. package/dist/anthropic-7RIVSEDF.js +0 -4
  40. package/dist/checker-3LHDOATB.js +0 -4
  41. package/dist/chunk-2IBMB3ZM.js +0 -150
  42. package/dist/chunk-AQ3A5CF4.js +0 -5
  43. package/dist/chunk-E55KH72M.js +0 -4
  44. package/dist/chunk-FA26MQ7K.js +0 -257
  45. package/dist/chunk-OTVZGPHD.js +0 -121
  46. package/dist/chunk-QCKJLO6X.js +0 -4
  47. package/dist/chunk-TKADLB2Q.js +0 -4
  48. package/dist/chunk-VA64DJNN.js +0 -4
  49. package/dist/lib/agent-IRY7KR5D.js +0 -11
  50. package/dist/lib/chunk-GHF2PSEW.js +0 -8
  51. package/dist/openai-RZLCY55V.js +0 -34
  52. package/dist/seatbelt-YXUQ5XR3.js +0 -5
  53. package/dist/tool-filter-CG3KGQQW.js +0 -4
package/README.md CHANGED
@@ -60,6 +60,20 @@ Beyond interactive use, Swifty supports a non-interactive print mode for scripti
60
60
  npm install -g @swifty.js/swifty
61
61
  ```
62
62
 
63
+ Or use the one-line installers ([macOS / Linux](../../install.sh), [Windows](../../install.ps1)):
64
+
65
+ ```bash
66
+ # macOS / Linux
67
+ curl -fsSL https://raw.githubusercontent.com/hangtiancheng/swifty-code/main/install.sh | bash
68
+ ```
69
+
70
+ ```powershell
71
+ # Windows (PowerShell)
72
+ irm https://raw.githubusercontent.com/hangtiancheng/swifty-code/main/install.ps1 | iex
73
+ ```
74
+
75
+ Both installers write a default `~/.swifty/config.yaml` if none exists, verify Node.js >= 20, and support uninstall / pinned-version / dist-tag options (see their `--help` / `-Help` output).
76
+
63
77
  Or run directly from the monorepo:
64
78
 
65
79
  ```bash
@@ -89,7 +103,7 @@ mcp_servers:
89
103
  - name: database
90
104
  command: npx
91
105
  args: ["-y", "@swifty-db/mcp@latest"]
92
- env: # map<string, string>; ${VAR} / $VAR expands from the environment
106
+ env: # map<string, string>; supports ${VAR}, ${VAR:-default}, and $VAR
93
107
  API_BASE_URL: "https://swifty-db.dev"
94
108
  API_KEY: "${DATABASE_API_KEY}"
95
109
 
@@ -125,6 +139,31 @@ The thinking level controls reasoning depth. For `anthropic` it maps to a thinki
125
139
 
126
140
  API keys are resolved in this order: explicit api_key field, then environment variables (ANTHROPIC_API_KEY for anthropic, OPENAI_API_KEY for openai and openai-compat).
127
141
 
142
+ ### Project-level MCP servers (.mcp.json)
143
+
144
+ In addition to `mcp_servers` in `config.yaml`, Swifty reads a project-level `.mcp.json` from the working directory, using the Claude Code-compatible format:
145
+
146
+ ```json
147
+ {
148
+ "mcpServers": {
149
+ "database": {
150
+ "command": "npx",
151
+ "args": ["-y", "@swifty-db/mcp@latest"],
152
+ "env": { "API_KEY": "${DATABASE_API_KEY}" }
153
+ },
154
+ "remote": {
155
+ "type": "http",
156
+ "url": "https://example.com/mcp",
157
+ "headers": { "Authorization": "Bearer ${TOKEN}" }
158
+ }
159
+ }
160
+ }
161
+ ```
162
+
163
+ Each entry takes `command`/`args`/`env` (stdio) or `url`/`headers` with `type` of `http` or `sse`. Environment references support `${VAR}`, `${VAR:-default}`, and `$VAR` in commands, arguments, environment values, URLs, and headers; an unset variable without a default prevents that server from connecting. These servers are merged with the user-level `mcp_servers`; on a name collision the user-level entry wins. At startup, a malformed `.mcp.json` is ignored (logged), while an invalid individual server entry is skipped without disabling valid siblings. During `/mcp reload`, invalid config aborts the reload so the current connections remain intact.
164
+
165
+ After editing `.mcp.json` or `config.yaml`, use `/mcp reload` in the TUI to re-read both sources. Unchanged connections stay live, removed servers disconnect, new servers connect, and servers whose settings changed restart.
166
+
128
167
  ## Usage
129
168
 
130
169
  ### Interactive TUI Mode
@@ -135,9 +174,9 @@ swifty
135
174
 
136
175
  Launches the terminal interface. If multiple providers are configured, a provider selection screen appears first.
137
176
 
138
- Use `/login` to configure and activate a provider from the TUI. When no provider is configured, the login form opens automatically. Name, protocol, base URL, API key, and model are required in the form. Use ↑↓ or Tab to move between fields, ←→ to select protocol or cycle the thinking level, Enter to save, and Esc to cancel. Changing the protocol also moves an untouched thinking level to that protocol's default. Duplicate names receive numeric suffixes (`name2`, `name3`, …).
177
+ Use `/login` to configure and activate a provider from the TUI. When no provider is configured, the login form opens automatically. Name, protocol, base URL, API key, and model are required in the form. Use ↑↓ or Tab to move between fields, ←→ to select protocol or cycle the thinking level, Enter to save, and Esc to cancel. Changing the protocol also moves an untouched thinking level to that protocol's default.
139
178
 
140
- The form saves to `~/.swifty/config.yaml`, retaining existing providers and other settings. Context window accepts integers from 1000 to 10000000; max output accepts integers from 1 to 1000000 and must not exceed the context window. Empty optional fields use the defaults above.
179
+ The form saves to `~/.swifty/config.yaml`, retaining existing providers and other settings. `base_url` is the provider identity: saving a provider whose `base_url` already exists replaces that entry in place instead of adding another one, and names may repeat freely. Context window accepts integers from 1000 to 10000000; max output accepts integers from 1 to 1000000 and must not exceed the context window. Empty optional fields use the defaults above.
141
180
 
142
181
  ### Print Mode (Non-Interactive)
143
182
 
@@ -181,6 +220,7 @@ Inside the TUI, these commands are available:
181
220
  | /sandbox [1/2/3] | Configure sandbox (1=on+auto, 2=on+manual, 3=off) |
182
221
  | /worktree | List git worktrees |
183
222
  | /mcp | Show MCP server status |
223
+ | /mcp reload | Re-read MCP config; reconcile unchanged, removed, new, and changed servers |
184
224
  | /thinking [level] | Show or set the thinking level (off, minimal, low, medium, high, xhigh, max); setting persists to `~/.swifty/config.yaml` |
185
225
  | /quit | Exit the application |
186
226
 
@@ -197,3 +237,20 @@ Inside the TUI, these commands are available:
197
237
  Pastes longer than 10 lines or 1,000 characters collapse to `[paste #1 +124 lines]` or `[paste #1 1234 chars]`. Clipboard images appear as `[Image #1]`. Arrow keys move across each placeholder as a unit, and Backspace/Delete remove it as a unit. Placeholders survive dialog switches; submitting restores the full text and image attachments.
198
238
 
199
239
  Pasting an image saves it as a PNG under `.swifty/file-history/<session-id>/`. Its placeholder expands to a workDir-relative `@` reference on submit and loads as an inline image block. Linux requires `wl-clipboard` (Wayland) or `xclip` (X11).
240
+
241
+ ## Library Build
242
+
243
+ Besides the `swifty` CLI, the package ships a library entry for embedding Swifty in another host process.
244
+
245
+ | Entry | Output | Contents |
246
+ | -------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------- |
247
+ | CLI (`swifty`) | `dist/main.js` | Fully bundled, minified single file with a shebang; only Node built-ins stay external. |
248
+ | Library | `dist/lib/index.js`, `dist/lib/index.d.ts` | The `src/index.ts` barrel; runtime dependencies stay external and resolve from the consumer's `node_modules`. |
249
+
250
+ The library entry is terminal-independent by contract: it must never reach `src/tui/**` or a terminal-only dependency, so a host without a TTY (a server, an editor extension, a test harness) can import it. `pnpm build` enforces that contract:
251
+
252
+ - **Terminal-only dependency ban** — the `ban-terminal-only-deps` plugin in `tsup.config.ts` fails the build when a bundle-reachable module imports one of the terminal-only packages (`ink`, `chalk`, `ansi-escapes`, …) or a path inside `src/tui`. The list is declared once in `tsup.config.ts` and re-derived from the actual import sites by `tests/build-guards.test.ts`, which fails if the two drift apart.
253
+ - **`react` and `react-dom` are allowed** — the cross-platform hooks under `src/ui/**` are public API and depend on them.
254
+ - **Ambiguous export scan** — once the bundle is written, the build runs the TypeScript ambiguous-export check (`TS2308`) over the library graph. A name exported by two `export *` sources is dropped by the bundler without any warning; the scan turns that into a build failure instead of a quietly smaller public API.
255
+
256
+ In a long-lived host process, prefer the composable modules (`Agent`, `ToolRegistry`, …) over the process-level entry points (`print-mode`, `recover`, `teammate`), which may write crash dumps or call `process.exit()`.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __swiftyCreateRequire } from "node:module";
3
+ const require = __swiftyCreateRequire(import.meta.url);
4
+ import{s as a}from"./chunk-TW7RHCDS.js";import"./chunk-TCQ4EB4G.js";import"./chunk-S35E7OYL.js";import"./chunk-2EV72T5X.js";import"./chunk-BWWNI2FK.js";import"./chunk-X3UA5OZL.js";export{a as Agent};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __swiftyCreateRequire } from "node:module";
3
+ const require = __swiftyCreateRequire(import.meta.url);
4
+ import{a as Z}from"./chunk-JMF2DWGY.js";import{a as Q}from"./chunk-TCQ4EB4G.js";import{g as M}from"./chunk-TKQ5T5VO.js";import{A as J,B as V,C as q,E as X,k as B,m as O,n as U,o as j,p as $,q as K,r as F,t as C,u as E,y as D,z as I}from"./chunk-S35E7OYL.js";import"./chunk-2EV72T5X.js";import{D as w,I as N,J as H,K as z,L as G,M as d,N as W,R as Y,g as R}from"./chunk-BWWNI2FK.js";import"./chunk-X3UA5OZL.js";function oe(o){for(let t=o.length-1;t>=0;t--){let e=o[t];if(e.defer_loading!==!0){e.cache_control={type:"ephemeral"};return}}}function ne(o){return o.some(t=>"defer_loading"in t&&t.defer_loading===!0)}var re="computer-use-2025-11-24";function se(o){if("type"in o&&o.type==="computer_20251124")return{...o};if("input_schema"in o){let t=o,{cache_control:e,...n}=t;return{...n,type:"custom",...t.defer_loading!==!0&&e?{cache_control:e}:{}}}throw new Error("Anthropic received a tool schema serialized for another protocol.")}var T=N({module:"llm"});var ie=3e3,ae=w.object({max_input_tokens:w.coerce.number()});async function Te(o){if(o.protocol!=="anthropic")return 0;let t=E(o),n=`${o.base_url.replace(/\/+$/,"")}/v1/models/${encodeURIComponent(o.model)}`,r=new AbortController,s=setTimeout(()=>{r.abort()},ie);try{let c=await fetch(n,{method:"GET",headers:{"anthropic-version":"2023-06-01",...t?{"x-api-key":t}:{}},signal:r.signal});if(!c.ok)return 0;let v=await c.json(),{success:p,error:u,data:h}=await R(ae,v);if(!p)return T.warn({message:u.message},"model context window schema validation failed"),0;let m=h.max_input_tokens;return Math.max(m,0)}catch(c){return T.error({err:c},"failed to fetch model context window"),0}finally{clearTimeout(s)}}function y(o){return typeof o=="string"?[{type:"text",text:o}]:o.map(t=>{let e=Q(t);if(!e||e.type==="tool_reference")throw new Error(`Unsupported user content block: ${Y(t,"type","unknown")}`);return e})}function le(o){let t=[];for(let e of o)if(e.role==="assistant"){let n=[];if(e.thinkingBlocks)for(let s of e.thinkingBlocks)n.push({type:"thinking",thinking:s.thinking,signature:s.signature});let r=typeof e.content=="string"?e.content:H(e.content);if(r&&n.push({type:"text",text:r}),e.toolUses)for(let s of e.toolUses)n.push({type:"tool_use",id:s.toolUseId,name:s.toolName==="ComputerUse"?"computer":s.toolName,input:s.arguments});n.length===0&&n.push({type:"text",text:""}),t.push({role:"assistant",content:n})}else if(e.toolResults&&e.toolResults.length>0){let n=[];for(let r of e.toolResults)n.push({type:"tool_result",tool_use_id:r.toolUseId,is_error:r.isError,content:r.contentBlocks?.length?r.contentBlocks:r.content});e.content.length>0&&n.push(...y(e.content)),t.push({role:"user",content:n})}else{if(t.length===0){t.push({role:"user",content:y(e.content)});continue}let n=!1,r=t[t.length-1],s=r.content;r.role==="user"&&(typeof s=="string"||Array.isArray(s)&&s.length>0&&(s[0].type==="text"||s[0].type==="image"))&&(n=!0),n?(typeof s=="string"&&(s=r.content=s.trim().length>0?[{type:"text",text:s}]:[]),s.push(...y(e.content))):t.push({role:"user",content:y(e.content)})}return t}var ee=class{protocol="anthropic";client;model;thinkingLevel;systemPrompt;maxOutputTokens;config;constructor(t,e){let n=E(t);if(!n)throw new I("Anthropic API key not found, set ANTHROPIC_API_KEY in ~/.swifty/config.yaml, or via ANTHROPIC_API_KEY env variable.");this.client=new M({apiKey:n,baseURL:t.base_url}),this.model=t.model,this.config={...t},this.thinkingLevel=O(t),this.systemPrompt=e,this.maxOutputTokens=C(t)}setSystemPrompt(t){this.systemPrompt=t}setMaxOutputTokens(t){this.config={...this.config,max_output_tokens:t},this.maxOutputTokens=C(this.config),this.setThinkingLevel(this.thinkingLevel)}setThinkingLevel(t){return this.thinkingLevel=F(this.config,t),this.thinkingLevel}getThinkingLevel(){return this.thinkingLevel}getSupportedThinkingLevels(){return K(this.config)}async*stream(t,e,n){let r=le(X(t.getMessages())),s=ne(e),c=e.map(se),v=c.some(l=>"type"in l&&l.type==="computer_20251124");oe(c),ce(r);let p={model:this.model,max_tokens:this.maxOutputTokens,stream:!0,system:[{type:"text",text:this.systemPrompt,cache_control:{type:"ephemeral"}}],messages:r,...c.length>0?{tools:c}:{}},u=this.getThinkingLevel();if(this.config.reasoning!==!1)if(u==="off")p.thinking={type:"disabled"};else if(this.config.thinking_mode==="adaptive"){let l=$(u,this.config);l!==null&&(p.thinking={type:"adaptive"},p.output_config={effort:l})}else{let l=j(u,this.config);l!==null&&l!=="none"&&(p.thinking={type:"enabled",budget_tokens:Math.min(U(l),this.maxOutputTokens-B)})}let h=0,m=0,x=0,A=0,S="end_turn",b="",L="",P=!1;try{let l=[...s?[Z]:[],...v?[re]:[]],te=this.client.messages.stream(p,{...n?{signal:n}:{},...l.length>0?{headers:{"anthropic-beta":l.join(",")}}:{}}),g="",k="",f="";for await(let a of te)switch(a.type){case"content_block_start":{let i=a.content_block;i.type==="thinking"?(P=!0,b="",L=""):i.type==="tool_use"&&(k=i.id,g=i.name==="computer"?"ComputerUse":i.name,f="",yield{type:"tool_call_start",toolName:g,toolId:k});break}case"content_block_delta":{let i=a.delta;i.type==="thinking_delta"?(b+=i.thinking,yield{type:"thinking_delta",text:i.thinking}):i.type==="signature_delta"?L+=i.signature:i.type==="text_delta"?yield{type:"text_delta",text:i.text}:i.type==="input_json_delta"&&(f+=i.partial_json,yield{type:"tool_call_delta",text:i.partial_json});break}case"content_block_stop":{if(P&&(yield{type:"thinking_complete",thinking:b,signature:L},P=!1),g){let i={};if(f)try{let _=JSON.parse(f);i=z(_)?G(_):{}}catch(_){T.error({err:_},"llm operation failed"),i={}}yield{type:"tool_call_complete",toolId:k,toolName:g,arguments:i},g="",k="",f=""}break}case"message_delta":{a.delta.stop_reason&&(S=a.delta.stop_reason),a.usage.output_tokens&&(m=a.usage.output_tokens,a.usage.input_tokens&&(h=a.usage.input_tokens),a.usage.cache_read_input_tokens&&(x=a.usage.cache_read_input_tokens),a.usage.cache_creation_input_tokens&&(A=a.usage.cache_creation_input_tokens));break}case"message_start":{h=a.message.usage.input_tokens,m=a.message.usage.output_tokens,x=a.message.usage.cache_read_input_tokens??0,A=a.message.usage.cache_creation_input_tokens??0;break}}yield{type:"stream_end",stopReason:S,usage:{inputTokens:h,outputTokens:m,cacheReadInputTokens:x,cacheCreationInputTokens:A}}}catch(l){throw T.error({err:l},"llm operation failed"),pe(l)}}};function ce(o){for(let t=o.length-1;t>=0;t--){if(o[t].role!=="user")continue;let e=o[t].content;if(typeof e=="string"&&e.length===0||Array.isArray(e)&&e.length===0)return;typeof e=="string"&&(e=o[t].content=[{type:"text",text:e}]);let n=e[e.length-1];for(let r=e.length-1;r>=0;r--)if(e[r].type!=="image"){n=e[r];break}Reflect.set(n,"cache_control",{type:"ephemeral"});return}}function pe(o){if(o instanceof M.APIError){if(o.status===413||/prompts?\s+too\s+long/i.test(o.message))return new q(`Prompt too long: ${o.message}`);if(o.status===401)return new I(`Invalid API key: ${o.message}`);if(o.status===429){let t=o.headers,e=t instanceof Headers?t.get("retry-after"):void 0,n="Rate Limited";if(e){let r=Number.parseInt(d(e));Number.isNaN(r)?n+=", please wait.":n+=`, retry after ${d(r)}s.`}else n+=", please wait.";return new J(n,e?d(e):void 0)}return new D(`Anthropic API error (${d(o.status)}): ${o.message}`)}return new V(`Network error: ${W(o)}`)}export{ee as AnthropicClient,le as buildAnthropicMessages,Te as fetchModelContextWindow,ce as markLastUserTailForCache,oe as markToolsForCache,ne as needsToolSearchBeta};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __swiftyCreateRequire } from "node:module";
3
+ const require = __swiftyCreateRequire(import.meta.url);
4
+ import{c as a,d as b,e as c,f as d,g as e,h as f}from"./chunk-4YVJP2CO.js";import"./chunk-2EV72T5X.js";import"./chunk-BWWNI2FK.js";import"./chunk-X3UA5OZL.js";export{b as PathSandbox,f as PermissionChecker,d as RuleEngine,c as evaluateRules,a as extractContent,e as isSafeCommand};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __swiftyCreateRequire } from "node:module";
3
+ const require = __swiftyCreateRequire(import.meta.url);
4
+ import{I as d,L as f,N as y,R as l}from"./chunk-BWWNI2FK.js";var S=d({module:"mcp"}),u="mcp__",p="__";function a(e){return e.replace(/[^a-zA-Z0-9_]/g,"_")}function M(e){return u+a(e)+p}function g(e,t){return M(e)+a(t)}var T=class{name;description;category="command";deferred=!0;mcpServerName;client;originalName;inputSchema;constructor(t,r,n){this.name=g(r,n.name),this.description=n.description,this.originalName=n.name,this.client=t,this.inputSchema=n.inputSchema,this.mcpServerName=r}mcpInputSchema(){return this.inputSchema??{}}setDeferLoading(t){this.deferred=t}schema(){return{name:this.name,description:this.description,input_schema:this.inputSchema}}async execute(t,r){try{return await this.client.callTool(this.originalName,r,t.abortSignal)}catch(n){return S.error({err:n},"mcp operation failed"),{output:`MCP tool error: ${y(n)}`,isError:!0}}}};function P(e,t){if(t==="string"&&typeof e=="number"&&Number.isFinite(e))return String(e);if((t==="integer"||t==="number")&&typeof e=="string"){let r=e.trim();if(r===""||!(t==="integer"?/^[+-]?\d+$/:/^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$/).test(r))return e;let s=t==="integer"?Number.parseInt(r,10):Number.parseFloat(r);return Number.isFinite(s)?s:e}if(t==="boolean"&&typeof e=="string"){let r=e.trim().toLowerCase();if(r==="true")return!0;if(r==="false")return!1}return e}function h(e,t){if(typeof t!="object"||t===null)return e;let r=f(t),n=l(r,"type","");if(n==="object"&&typeof e=="object"&&e!==null&&!Array.isArray(e)){let s=f(r.properties??{}),o={};for(let[i,c]of Object.entries(e))o[i]=i in s?h(c,s[i]):c;return o}if(n==="array"){let s=r.items??{},o=e;if(typeof o=="object"&&o!==null&&!Array.isArray(o)){let i=Object.values(o);i.length===1&&Array.isArray(i[0])&&(o=i[0])}else typeof o=="string"&&(o=o.split(",").map(i=>i.trim()).filter(i=>i!==""));return Array.isArray(o)?o.map(i=>h(i,s)):o}return n!==""?P(e,n):e}function A(e,t){if(t.startsWith(u)){let r=t.slice(u.length),n=r.indexOf(p);if(n>=0)return a(r.slice(0,n))+p+a(r.slice(n+p.length))}return a(e)+p+a(t)}function b(e){return"mcpInputSchema"in e&&typeof e.mcpInputSchema=="function"}var C=class{constructor(t){this.registry=t}registry;name="McpCall";description="Invoke a tool on a connected MCP server. Call ToolSearch first to load the tool's schema, then pass its arguments here exactly as that schema requires, using the same JSON types.";category="command";deferred=!1;schema(){return{name:this.name,description:this.description,input_schema:{type:"object",properties:{server:{type:"string",description:"MCP server name, e.g. 'linear'."},tool:{type:"string",description:"Full tool name as returned by ToolSearch, e.g. 'mcp__linear__create_issue'."},arguments:{type:"object",description:"The target tool's arguments. Must match that tool's input_schema exactly, including JSON types: bare numbers for integer fields, bare true/false for boolean fields, quoted strings for string fields, and plain JSON arrays for array fields."}},required:["server","tool","arguments"]}}}resolveTarget(t){let r=l(t,"server"),n=l(t,"tool");if(!r||!n)return;let s=n.startsWith(u)?n:g(r,n),o=this.registry.get(s);return o&&b(o)&&a(o.mcpServerName)===a(r)?o:void 0}availableNames(){return this.registry.listTools().filter(t=>t.name.startsWith(u)).map(t=>t.name).sort()}async execute(t,r){let n=l(r,"server",""),s=l(r,"tool","");if(s==="")return{output:"McpCall requires a 'tool' name",isError:!0};let o=this.resolveTarget(r);if(!o){let c=this.availableNames(),m=c.length>0?c.join(", "):"(none connected)";return{output:`Unknown MCP tool '${s}' on server '${n}'. Available tools: ${m}`,isError:!0}}if(typeof r.arguments!="object"||r.arguments===null||Array.isArray(r.arguments))return{output:"McpCall requires an 'arguments' object matching the target schema",isError:!0};let i=f(r.arguments);if(b(o)){let c=o.mcpInputSchema();if(Object.keys(c).length>0){let m=h(i,c);typeof m=="object"&&m!==null&&!Array.isArray(m)&&(i=f(m))}}return t.abortSignal?.throwIfAborted(),o.execute(t,i)}};export{u as a,T as b,A as c,b as d,C as e};
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __swiftyCreateRequire } from "node:module";
3
+ const require = __swiftyCreateRequire(import.meta.url);
4
+ import{c as C}from"./chunk-2EV72T5X.js";import{D as p,I as D,K as j,R as c,d as y,e as S}from"./chunk-BWWNI2FK.js";import{readFileSync as U,writeFileSync as q,mkdirSync as V,statSync as z}from"fs";import{homedir as G,tmpdir as Y}from"os";import{join as g,resolve as f,dirname as I}from"path";import{realpathSync as O}from"fs";import{basename as T,dirname as M,isAbsolute as _,join as N,relative as L,resolve as A,sep as H}from"path";function d(n){let e=A(n),s=[];for(;;)try{return N(O(e),...s)}catch(t){if(!(t instanceof Error)||!("code"in t)||t.code!=="ENOENT"&&t.code!=="ENOTDIR")throw t;let r=M(e);if(r===e)return A(n);s.unshift(T(e)),e=r}}function m(n,e){let s=L(n,e);return s===""||s!==".."&&!s.startsWith(`..${H}`)&&!_(s)}var k=D({module:"permissions"}),Z=[],B=["basename","cat","cksum","cmp","column","comm","cut","df","dirname","du","echo","expr","false","fold","fmt","grep","groups","head","id","jq","locate","ls","md5","md5sum","nl","od","paste","pgrep","printenv","printf","ps","pwd","readlink","realpath","sha1sum","sha224sum","sha256sum","sha384sum","sha512sum","shasum","stat","strings","tac","tail","tr","true","tty","type","uname","uptime","w","wc","whereis","which","who","whoami","ack","ag","alias","arch","base64","bat","bzcat","cal","col","cloc","diff","diff3","dig","dmesg","expand","factor","free","help","hexdump","host","iconv","info","locale","lscpu","lsblk","lsof","lspci","lsusb","man","mdfind","mdls","ncal","netstat","nproc","nslookup","objdump","otool","pbpaste","ping","readelf","rev","sdiff","seq","sum","sw_vers","tldr","traceroute","unexpand","vm_stat","whois","xxd","xzcat","zcat","zgrep","zipinfo",/^command\s+(?:-v|-V)\s+\S+(?:\s+\S+)*$/,/^date(?!.*\s(?:-s|--set)(?:=|\s|$))(?:\s.*)?$/,/^file(?!.*(?:\s--compile(?:=|\s|$)|\s-[^-\s]*C))(?:\s.*)?$/,/^find(?!.*\s(?:-delete|-exec(?:dir)?|-ok(?:dir)?|-fls|-fprint(?:0)?|-fprintf)(?:\s|$))(?:\s.*)?$/,/^fd(?!.*\s(?:-x|-X|--exec|--exec-batch)(?:=|\s|$))(?:\s.*)?$/,/^hostname(?:\s+(?:-[adfFiIsyVh]|--(?:alias|all-fqdns|all-ip-addresses|domain|fqdn|help|ip-address|long|nis|short|version|yp)))*$/,/^rg(?!.*\s(?:--pre|--pre-glob|--hostname-bin)(?:=|\s|$))(?:\s.*)?$/,/^sort(?!.*\s(?:-o|--output|--compress-program)(?:=|\s|$))(?:\s.*)?$/,/^ss(?!.*\s(?:--kill|-[^-\s]*K)(?:\s|$))(?:\s.*)?$/,/^tree(?!.*\s(?:-o|--output)(?:=|\s|$))(?:\s.*)?$/,/^git\s+(?:--version|version)$/,/^git\s+(?:blame|cat-file|count-objects|describe|for-each-ref|ls-files|ls-tree|merge-base|name-rev|rev-parse|shortlog|show-ref|status|verify-pack)(?:\s.*)?$/,/^git\s+(?:diff|log|show)(?!.*\s(?:--ext-diff|--output)(?:=|\s|$))(?:\s.*)?$/,/^git\s+branch$/,/^git\s+branch(?=.*\s(?:-a|--all|-r|--remotes|--list|--show-current|--contains|--no-contains|--merged|--no-merged)(?:=|\s|$))(?!.*\s(?:-[dDmMcCf]|--(?:copy|create-reflog|delete|edit-description|move|set-upstream-to|unset-upstream))(?:=|\s|$))(?:\s.*)?$/,/^git\s+config(?:\s+(?:--blob(?:=|\s)\S+|--file(?:=|\s)\S+|--fixed-value|--global|--local|--null|--show-names|--show-origin|--show-scope|--system|--worktree|-z))*\s+(?:--get|--get-all|--get-regexp|--get-urlmatch|--list|-l)(?:\s.*)?$/,/^git\s+notes\s+(?:list|show)(?:\s.*)?$/,/^git\s+reflog\s+show(?:\s.*)?$/,/^git\s+remote(?:\s+-v)?$/,/^git\s+remote\s+get-url(?:\s+(?:--all|--push))*\s+\S+$/,/^git\s+remote\s+show(?:\s+-n)?(?:\s+\S+)?$/,/^git\s+stash\s+(?:list|show)(?:\s.*)?$/,/^git\s+submodule\s+(?:status|summary)(?:\s.*)?$/,/^git\s+tag(?:\s+(?:-l|--list)(?:\s.*)?)?$/,/^git\s+worktree\s+list(?:\s.*)?$/,/^(?:bun|npm|pnpm|yarn)\s+(?:explain|help|info|list|ls|outdated|prefix|query|root|search|show|view|why)(?:\s.*)?$/,/^(?:npm|pnpm|yarn)\s+config\s+(?:get|list)(?:\s.*)?$/,/^npm\s+pkg\s+get(?:\s.*)?$/,/^bun\s+pm\s+ls(?:\s.*)?$/,/^(?:bun|cargo|clang|cmake|composer|deno|gcc|gem|node|npm|php|pip|pip3|pnpm|python|python3|ruby|rustc|swift|yarn)\s+(?:--version|-V)$/,/^(?:go|helm|kubectl|podman|terraform)\s+version(?:\s.*)?$/,/^(?:java|javac)\s+-version$/,/^dotnet\s+(?:--info|--list-runtimes|--list-sdks|--version)$/,/^(?:docker|podman)\s+(?:diff|events|images|info|inspect|logs|port|ps|stats|top|version)(?:\s.*)?$/,/^(?:docker|podman)\s+(?:container|image|network|volume)\s+(?:inspect|ls)(?:\s.*)?$/,/^docker\s+compose\s+(?:config|images|logs|ps|top|version)(?:\s.*)?$/,/^kubectl\s+(?:api-resources|api-versions|cluster-info|describe|explain|get|logs|top|version)(?:\s.*)?$/,/^kubectl\s+config\s+(?:current-context|get-contexts|view)(?:\s.*)?$/,/^helm\s+(?:env|get|history|list|search|show|status|version)(?:\s.*)?$/,/^terraform\s+(?:output|providers|show|version)(?:\s.*)?$/,/^terraform\s+workspace\s+(?:list|show)(?:\s.*)?$/,/^systemctl\s+(?:is-active|is-enabled|is-failed|list-dependencies|list-jobs|list-sockets|list-timers|list-unit-files|list-units|show|show-environment|status)(?:\s.*)?$/,/^launchctl\s+(?:error|hostinfo|list|managername|managerpid|manageruid|print|print-cache|procinfo|variant|version)(?:\s.*)?$/,/^journalctl(?!.*\s--(?:rotate|vacuum|flush|sync))(?:\s.*)?$/,/^defaults\s+read(?:\s.*)?$/,/^ifconfig$/,/^ipconfig(?:\s+\/(?:all|allcompartments|displaydns))?\s*$/,/^ip\s+(?:addr(?:ess)?|route)$/,/^ip\s+(?:addr(?:ess)?|link|route|neigh(?:bor)?)\s+(?:show|list)\b(?:\s.*)?$/,/^tar\s+(?:--list\b|-[a-zA-Z]*t[a-zA-Z]*)(?:\s.*)?$/,/^unzip\s+-[a-zA-Z]*l(?:\s.*)?$/,/^git\s+(?:grep|fsck|rev-list|whatchanged|help|diff-tree|diff-index|diff-files|ls-remote|verify-tag|verify-commit)(?:\s.*)?$/,/^git\s+archive(?!.*\s(?:-o|--output)(?:=|\s|$))(?:\s.*)?$/,/^svn\s+(?:status|stat|st|diff|di|log|info|list|ls|cat|blame|ann|annotate|proplist|propget|pg)(?:\s.*)?$/,/^hg\s+(?:status|st|log|diff|summary|id|identify|branches|tags|manifest|cat|files|locate|heads|tip|parents|paths|root)(?:\s.*)?$/,/^cargo\s+(?:tree|search|locate-project|verify-project|config\s+get)(?:\s.*)?$/,/^gem\s+(?:list|search|info|env|dependency|contents|specification|sources\s+(?:-l|--list))(?:\s.*)?$/,/^brew\s+(?:--version|-v|list|info|search|outdated|deps|uses|config|leaves|doctor)(?:\s.*)?$/,/^choco\s+(?:--version|list|info|search|outdated)(?:\s.*)?$/,/^apt(?:-get)?\s+list(?:\s.*)?$/,/^apt-cache\s+(?:search|show|showpkg|policy|depends|rdepends|pkgnames)(?:\s.*)?$/,/^dpkg\s+(?:-l|-L|-s|-S|--list|--listfiles|--status|--search)\b(?:\s.*)?$/,/^rpm\s+-q[a-zA-Z]*(?:\s.*)?$/,/^(?:aws|gh|gcloud|az)\s+--version$/,/^aws\s+(?:\S+\s+)?(?:describe|list|get|wait)-\S+(?:\s.*)?$/,/^aws\s+s3\s+ls(?:\s.*)?$/,/^gcloud\s+\S+(?:\s+\S+)*\s+(?:list|describe)(?:\s.*)?$/,/^az\s+\S+(?:\s+\S+)*\s+(?:list|show)(?:\s.*)?$/,/^gh\s+(?:pr|issue|repo|run|release|gist)\s+(?:view|list|status|checks|diff)(?:\s.*)?$/,/^(?:docker|podman)\s+(?:system\s+(?:df|info)|history|context\s+(?:ls|list|show|inspect))(?:\s.*)?$/,/^(?:Get-(?:Acl|Alias|AuthenticodeSignature|ChildItem|CimInstance|Clipboard|Command|ComputerInfo|Content|Counter|Culture|Date|DnsClientCache|EventLog|ExecutionPolicy|FileHash|Help|History|Host|HotFix|Item|ItemProperty|Location|Member|Module|NetAdapter|NetIPAddress|NetNeighbor|NetIPConfiguration|NetRoute|NetTCPConnection|NetUDPEndpoint|Package|PackageProvider|PnpDevice|Printer|Process|PSDrive|PSProvider|PSRepository|PSSnapin|ScheduledTask|Service|TimeZone|UICulture|Variable|Verb|WinEvent|WmiObject)|Compare-Object|Format-(?:Custom|Hex|List|Table|Wide)|Group-Object|Measure-Object|Out-String|Resolve-Path|Select-Object|Select-String|Sort-Object|Test-Path|Where-Object|Write-(?:Debug|Error|Host|Information|Output|Progress|Verbose|Warning))(?:\s.*)?$/i],X={Bash:"command",PowerShell:"command",ComputerUse:"action",ReadFile:"file_path",WriteFile:"file_path",EditFile:"file_path",Glob:"pattern",Grep:"pattern",InstallSkill:"source"},J=[];function v(n,e){if(n==="McpCall")return C(c(e,"server",""),c(e,"tool",""));let s=X[n];if(!s)return"";let t=e[s];return typeof t=="string"?t:n==="ComputerUse"&&Array.isArray(e.actions)?e.actions.map(r=>j(r)?c(r,"type"):"").filter(Boolean).join(","):""}var P=class{allowedRoots;denyWritePaths;projectDir;constructor(e){this.projectDir=f(e),this.allowedRoots=[this.projectDir,Y()],this.denyWritePaths=J.map(s=>g(this.projectDir,s))}addRoot(e){this.allowedRoots.push(f(e))}addDenyWrite(e){this.denyWritePaths.push(f(e))}checkDenyWrite(e){let s=f(this.projectDir,e),t=d(s);for(let r of this.denyWritePaths)if(m(r,s)||m(d(r),t))return{effect:"deny",reason:`Path ${e} is in deny-write list`};return null}check(e){let s=d(f(this.projectDir,e));for(let t of this.allowedRoots)if(m(d(t),s))return null;return{effect:"deny",reason:`Path ${e} is outside allowed directories`}}};function K(n,e){let s="^"+n.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*").replace(/\?/g,".")+"$";try{return new RegExp(s).test(e)}catch(t){return k.error({err:t},"permissions operation failed"),!1}}var Q=/^(\w+)\((.+)\)$/;function F(n){let e;try{e=U(n,"utf-8")}catch(i){return i.code!=="ENOENT"&&k.error({err:i},"permissions operation failed"),[]}let s=p.object({rule:p.string().optional(),effect:p.string().optional()}),t;try{let i=y.load(e);t=S(p.array(s),i)}catch(i){return k.error({err:i},"permissions operation failed"),[]}let r=[];for(let i of t){if(i.effect!=="allow"&&i.effect!=="deny"&&i.effect!=="ask")continue;let o=Q.exec((i.rule??"").trim());o&&r.push({tool:o[1],pattern:o[2],effect:i.effect})}return r}function h(n,e,s){let t=null;for(let r of n)if(!(r.tool!==e&&r.tool!=="*")&&K(r.pattern,s)){if(r.effect==="deny")return"deny";r.effect==="ask"?t="ask":t??="allow"}return t}var E=class{userPath;projectPath;cache=new Map;constructor(e){this.userPath=g(G(),".swifty","permissions.yaml"),this.projectPath=g(e,".swifty","permissions.yaml")}rulesFor(e){let s;try{s=z(e,{bigint:!0})}catch{return this.cache.delete(e),[]}let t=this.cache.get(e);if(t?.mtimeNs===s.mtimeNs&&t.size===s.size)return t.rules;let r=F(e);return this.cache.set(e,{mtimeNs:s.mtimeNs,size:s.size,rules:r}),r}snapshot(){return[this.userPath,this.projectPath].flatMap(e=>this.rulesFor(e))}evaluate(e,s){return h(this.snapshot(),e,s)}appendProjectRule(e){V(I(this.projectPath),{recursive:!0});let s=F(this.projectPath);if(s.some(i=>i.tool===e.tool&&i.pattern===e.pattern&&i.effect===e.effect))return;s.push(e);let r=s.map(i=>({rule:`${i.tool}(${i.pattern})`,effect:i.effect}));q(this.projectPath,y.dump(r),"utf-8")}};function ee(n){for(let e of Z)if(e.re.test(n))return e.reason;return""}function se(n){let e=n.trim();return/[\r\n&|;<>`(){}[\]]/.test(e)?!1:B.some(s=>typeof s=="string"?e===s||e.startsWith(s+" ")||e.startsWith(s+" "):(s.lastIndex=0,s.test(e)))}function te(n,e){switch(n){case"bypassPermissions":return"allow";case"plan":return e==="read"?"allow":"ask";case"acceptEdits":return e==="command"?"ask":"allow";default:return e==="read"?"allow":"ask"}}var W=class n{constructor(e,s="default"){this.workDir=e;this.mode=s,this.sandbox=new P(e),this.ruleEngine=new E(e)}workDir;mode;planFilePath="";sandboxEnabled=!1;sandboxAutoAllow=!1;sandbox;ruleEngine;forWorkDir(e){let s=new n(e,this.mode);return s.ruleEngine=this.ruleEngine,s.sandboxEnabled=this.sandboxEnabled,s.sandboxAutoAllow=this.sandboxAutoAllow,s}check(e,s,t){let r=v(e,t),i=null,o=()=>i??=this.ruleEngine.snapshot(),a=h(o(),e,r);if(a==="deny"||a==="ask")return{effect:a,reason:`Permission rule: ${a}`};if(this.mode==="plan"&&(e==="WriteFile"||e==="EditFile")){let u=c(t,"file_path","");if(this.planFilePath&&d(f(this.workDir,u))===d(f(this.workDir,this.planFilePath))&&!this.sandbox.checkDenyWrite(u))return{effect:"allow",reason:"Plan file write allowed in plan mode"}}if(s==="command"&&se(r))return{effect:"allow",reason:"Safe read-only command"};let x=s==="command"?ee(r):"";if(x)return{effect:"deny",reason:`Dangerous command blocked: ${x}`};if(this.sandboxEnabled&&this.sandboxAutoAllow&&e==="Bash"){let u=c(t,"command").split(/\s*(?:&&|\|\||[;|])\s*/).map(w=>w.trim()).filter(Boolean),l=!1;for(let w of u){let R=h(o(),e,w);if(R==="deny")return{effect:"deny",reason:"Permission rule: deny"};R==="ask"&&(l=!0)}return l?{effect:"ask",reason:"Permission rule: ask (sandbox does not override)"}:{effect:"allow",reason:"Sandbox auto-allow: OS sandbox active"}}let b=c(t,"file_path",c(t,"path",""));if((s==="read"||s==="write")&&b){if(s==="write"){let l=this.sandbox.checkDenyWrite(b);if(l)return l}let u=this.sandbox.check(b);if(u&&this.mode!=="bypassPermissions"){let l=this.ruleEngine.evaluate(e,r);return l?{effect:l,reason:`Permission rule: ${l}`}:{effect:"ask",reason:u.reason}}}let $=h(o(),e,r);return $?{effect:$,reason:`Permission rule: ${$}`}:{effect:te(this.mode,s),reason:`Mode: ${this.mode}`}}allowExtraRoot(e){this.sandbox.addRoot(e)}allowAlways(e,s){let t=v(e,s),r=e==="ReadFile"||e==="WriteFile"||e==="EditFile",i;if(r&&t){let o=f(this.workDir,t),a=!1;try{a=z(o).isDirectory()}catch{}i=g(a?o:I(o),"*")}else i=t.trim().split(/\s+/).slice(0,2).join(" ")+"*";this.ruleEngine.appendProjectRule({tool:e,pattern:i,effect:"allow"})}describeToolAction(e,s){let t=v(e,s);if(t)return t;let r=[];for(let[i,o]of Object.entries(s)){let a=String(o);a.length>80&&(a=a.slice(0,80)+"..."),r.push(`${i}: ${a}`)}return r.join(", ")}};export{d as a,m as b,v as c,P as d,h as e,E as f,se as g,W as h};