@swifty.js/swifty 0.0.1 → 0.0.2
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 +183 -0
- package/dist/agent-MICFDGUF.js +4 -0
- package/dist/anthropic-JDAGNAPR.js +4 -0
- package/dist/checker-PF3FEOF2.js +4 -0
- package/dist/chunk-4KVSJNS6.js +90 -0
- package/dist/chunk-6ARDOHBL.js +4 -0
- package/dist/chunk-7MHXMDYC.js +35 -0
- package/dist/chunk-F6HLYUZ4.js +355 -0
- package/dist/chunk-FZPTNGTU.js +4 -0
- package/dist/chunk-LPLPMGWW.js +336 -0
- package/dist/chunk-MQ5XOYLD.js +4 -0
- package/dist/chunk-RD3MICOU.js +4 -0
- package/dist/{cleanup-4R3534Z3.js → cleanup-BQJDUOKA.js} +1 -1
- package/dist/glob_addon.node +0 -0
- package/dist/main.js +240 -240
- package/dist/openai-I4WTRNNT.js +26 -0
- package/dist/{server-XPKSROM2.js → server-OGPKL2U2.js} +22 -21
- package/package.json +17 -24
- package/dist/anthropic-737END4X.js +0 -4
- package/dist/chunk-NCPOPA4A.js +0 -4
- package/dist/chunk-SC34YHMX.js +0 -521
- package/dist/chunk-Z3E5YV3P.js +0 -121
- package/dist/openai-4KX74QBZ.js +0 -27
- package/docs/ch1.md +0 -25
- package/docs/ch10.md +0 -122
- package/docs/ch11.md +0 -163
- package/docs/ch12.md +0 -289
- package/docs/ch13.md +0 -320
- package/docs/ch14.md +0 -152
- package/docs/ch15.md +0 -547
- package/docs/ch2.md +0 -273
- package/docs/ch3.md +0 -206
- package/docs/ch4.md +0 -125
- package/docs/ch5.md +0 -165
- package/docs/ch6.md +0 -201
- package/docs/ch7.md +0 -448
- package/docs/ch8.md +0 -217
- package/docs/ch9.md +0 -351
- package/docs/index.css +0 -23
- package/docs/index.md +0 -21
- package/docs/swifty.mdx +0 -7
package/README.md
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Swifty
|
|
2
|
+
|
|
3
|
+
Swifty is a terminal-based AI coding agent. It provides an interactive TUI (terminal user interface) for conversing with large language models, executing code, manipulating files, and orchestrating multi-agent workflows, all from the command line.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Swifty runs as a single CLI binary that connects to configurable LLM providers (Anthropic, OpenAI, or any OpenAI-compatible endpoint). It renders a rich terminal interface using React and Ink, giving you streaming responses, tool execution feedback, permission prompts, and slash commands in a single pane.
|
|
8
|
+
|
|
9
|
+
Beyond interactive use, Swifty supports a non-interactive print mode for scripting, a remote mode that serves a browser-based chat UI over WebSocket, and a teammate mode that lets one lead agent coordinate multiple subagents working in parallel.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
### Core Capabilities
|
|
14
|
+
|
|
15
|
+
- Multi-provider LLM support with Anthropic, OpenAI, and OpenAI-compatible protocols
|
|
16
|
+
- Interactive terminal UI with streaming text, thinking indicators, and tool execution display
|
|
17
|
+
- Built-in tool set: ReadFile, WriteFile, EditFile, Bash, Glob, Grep, ToolSearch, EnterWorktree, ExitWorktree, ExitPlanMode
|
|
18
|
+
- MCP (Model Context Protocol) server integration for extending the tool set with external services
|
|
19
|
+
- Permission system with four modes: default, acceptEdits, plan (read-only), and bypassPermissions
|
|
20
|
+
- Sandbox support via bwrap (Linux) and seatbelt (macOS) for isolated command execution
|
|
21
|
+
- Dangerous command pattern detection with human-in-the-loop approval dialogs
|
|
22
|
+
|
|
23
|
+
### Conversation and Memory
|
|
24
|
+
|
|
25
|
+
- Session persistence with JSONL-based storage for cross-session resume
|
|
26
|
+
- Automatic context compaction when conversations approach the model's context window
|
|
27
|
+
- Long-term memory extraction and recall across sessions
|
|
28
|
+
- Instructions file support for persistent project-level guidance
|
|
29
|
+
|
|
30
|
+
### Skills and Commands
|
|
31
|
+
|
|
32
|
+
- Skill catalog with three-tier loading: built-in, user-global (~/.swifty/skills/), and project-level (.swifty/skills/)
|
|
33
|
+
- Hot-reload support for skills edited on disk
|
|
34
|
+
- Inline and fork execution modes for skills
|
|
35
|
+
- Slash command system with built-in commands and user-defined commands from .swifty/commands/
|
|
36
|
+
- Skill installation from URLs
|
|
37
|
+
|
|
38
|
+
### Agent Orchestration
|
|
39
|
+
|
|
40
|
+
- Subagent spawning with built-in agent types: general-purpose, plan (read-only architect), explore (read-only code explorer)
|
|
41
|
+
- Team coordination with file-based mailboxes and lead/member communication
|
|
42
|
+
- Coordinator mode for managing multi-agent workflows
|
|
43
|
+
- Git worktree isolation for parallel agent tasks
|
|
44
|
+
|
|
45
|
+
### Hooks
|
|
46
|
+
|
|
47
|
+
- Event-driven hook engine supporting: session_start, session_end, turn_start, turn_end, pre_send, post_receive, pre_tool_use, post_tool_use, shutdown
|
|
48
|
+
- Hook actions: shell commands, HTTP requests, prompt injection
|
|
49
|
+
- Conditional execution, reject-on-failure, and async options
|
|
50
|
+
|
|
51
|
+
### Remote Mode
|
|
52
|
+
|
|
53
|
+
- Koa HTTP server with WebSocket bridge for browser-based access
|
|
54
|
+
- React frontend served at a configurable address
|
|
55
|
+
- Bidirectional message streaming between browser and agent
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install -g @swifty.js/swifty
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Or run directly from the monorepo:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pnpm dev
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Configuration
|
|
70
|
+
|
|
71
|
+
Swifty reads YAML configuration files from multiple locations, merged in order:
|
|
72
|
+
|
|
73
|
+
1. ~/.swifty/config.yml or ~/.swifty/config.yaml
|
|
74
|
+
2. .swifty/config.yml or .swifty/config.yaml (project root)
|
|
75
|
+
3. .swifty/config.local.yml or .swifty/config.local.yaml (project root, gitignored)
|
|
76
|
+
|
|
77
|
+
At least one provider must be configured. Example config.yml:
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
providers:
|
|
81
|
+
- name: anthropic
|
|
82
|
+
protocol: anthropic
|
|
83
|
+
base_url: https://api.anthropic.com
|
|
84
|
+
model: claude-sonnet-4-20250514
|
|
85
|
+
# api_key defaults to $ANTHROPIC_API_KEY
|
|
86
|
+
|
|
87
|
+
permission_mode: default
|
|
88
|
+
|
|
89
|
+
mcp_servers:
|
|
90
|
+
- name: my-server
|
|
91
|
+
command: npx
|
|
92
|
+
args: ["-y", "my-mcp-server"]
|
|
93
|
+
|
|
94
|
+
hooks:
|
|
95
|
+
- event: pre_tool_use
|
|
96
|
+
condition: "Bash"
|
|
97
|
+
action:
|
|
98
|
+
type: command
|
|
99
|
+
command: "echo tool about to run"
|
|
100
|
+
|
|
101
|
+
sandbox:
|
|
102
|
+
enabled: false
|
|
103
|
+
auto_allow: false
|
|
104
|
+
network_enabled: true
|
|
105
|
+
|
|
106
|
+
enable_coordinator_mode: false
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Provider fields:
|
|
110
|
+
|
|
111
|
+
| Field | Required | Description |
|
|
112
|
+
| ----------------- | -------- | -------------------------------------------------------------------- |
|
|
113
|
+
| name | yes | Display name for the provider |
|
|
114
|
+
| protocol | yes | One of: anthropic, openai, openai-compat |
|
|
115
|
+
| base_url | yes | API base URL |
|
|
116
|
+
| model | yes | Model identifier |
|
|
117
|
+
| api_key | no | API key (falls back to environment variable) |
|
|
118
|
+
| thinking | no | Enable extended thinking mode (increases max_output_tokens to 64000) |
|
|
119
|
+
| context_window | no | Override auto-detected context window size |
|
|
120
|
+
| max_output_tokens | no | Override default max output tokens |
|
|
121
|
+
|
|
122
|
+
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).
|
|
123
|
+
|
|
124
|
+
## Usage
|
|
125
|
+
|
|
126
|
+
### Interactive TUI Mode
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
swifty
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Launches the terminal interface. If multiple providers are configured, a provider selection screen appears first.
|
|
133
|
+
|
|
134
|
+
### Print Mode (Non-Interactive)
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
swifty -p "explain this codebase"
|
|
138
|
+
swifty -p "fix the failing test" --output-format stream-json
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
The -p flag sends a single prompt, runs the agent loop, and prints the result to stdout. Useful for scripting and CI pipelines.
|
|
142
|
+
|
|
143
|
+
### Remote Mode (Browser UI)
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
swifty --remote # listens on :18888
|
|
147
|
+
swifty --remote :9000 # custom address
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Starts a Koa HTTP server and WebSocket bridge. The bundled React frontend is served at the configured address for browser-based interaction.
|
|
151
|
+
|
|
152
|
+
### Slash Commands
|
|
153
|
+
|
|
154
|
+
Inside the TUI, these commands are available:
|
|
155
|
+
|
|
156
|
+
| Command | Description |
|
|
157
|
+
| ----------------------- | ---------------------------------------------------------------------------------- |
|
|
158
|
+
| /status | Show current session status (model, tokens, tools, sandbox, memories, skills, MCP) |
|
|
159
|
+
| /permission mode <mode> | Change permission mode (default, acceptEdits, plan, bypassPermissions) |
|
|
160
|
+
| /memory | List stored memories |
|
|
161
|
+
| /memory clear | Clear all memories |
|
|
162
|
+
| /skills | List available skills |
|
|
163
|
+
| /skills reload | Hot-reload skills from disk |
|
|
164
|
+
| /skill <name> [args] | Run a skill by name |
|
|
165
|
+
| /plan | Enter plan mode (read-only investigation) |
|
|
166
|
+
| /do | Exit plan mode and execute the approved plan |
|
|
167
|
+
| /compact | Force conversation compaction |
|
|
168
|
+
| /clear | Reset the session and clear the terminal |
|
|
169
|
+
| /resume [id] | List or restore a previous session |
|
|
170
|
+
| /rewind | Open checkpoint rewind dialog |
|
|
171
|
+
| /sandbox [1/2/3] | Configure sandbox (1=on+auto, 2=on+manual, 3=off) |
|
|
172
|
+
| /worktree | List git worktrees |
|
|
173
|
+
| /mcp | Show MCP server status |
|
|
174
|
+
| /quit | Exit the application |
|
|
175
|
+
|
|
176
|
+
### Keyboard Shortcuts
|
|
177
|
+
|
|
178
|
+
| Key | Action |
|
|
179
|
+
| --------- | -------------------------------------------------------------------- |
|
|
180
|
+
| Ctrl+C | Interrupt streaming (first press), exit app (second press within 2s) |
|
|
181
|
+
| Ctrl+O | Toggle full vs. truncated tool output |
|
|
182
|
+
| Ctrl+T | Toggle Teams dialog overlay |
|
|
183
|
+
| Shift+Tab | Cycle permission modes |
|
|
@@ -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{q as a}from"./chunk-F6HLYUZ4.js";import"./chunk-FZPTNGTU.js";import"./chunk-4KVSJNS6.js";import"./chunk-MQ5XOYLD.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{g as P}from"./chunk-H7VXSMJR.js";import{f as B,h as W,i as R}from"./chunk-RD3MICOU.js";import{b as O,c as j,d as M,e as U,f as $,g as K}from"./chunk-FZPTNGTU.js";import"./chunk-7MHXMDYC.js";import{H as w,J as v,K as S,L as I,M as k,N,e as L,l as E}from"./chunk-4KVSJNS6.js";import"./chunk-MQ5XOYLD.js";import"./chunk-X3UA5OZL.js";var x=L({module:"llm"});var H=3e3,Y=w.object({max_input_tokens:w.coerce.number()});async function ot(n){if(n.protocol!=="anthropic")return 0;let t=R(n),o=`${n.base_url.replace(/\/+$/,"")}/v1/models/${encodeURIComponent(n.model)}`,r=new AbortController,a=setTimeout(()=>{r.abort()},H);try{let l=await fetch(o,{method:"GET",headers:{"anthropic-version":"2023-06-01",...t?{"x-api-key":t}:{}},signal:r.signal});if(!l.ok)return 0;let g=await l.json(),{success:h,error:f,data:d}=await E(Y,g);if(!h)return console.error(f.message),0;let y=d.max_input_tokens;return Math.max(y,0)}catch(l){return console.error(l),0}finally{clearTimeout(a)}}function q(){return!0}function D(n){let t=[];for(let e of n)if(e.role==="assistant"){let o=[];if(e.thinkingBlocks)for(let r of e.thinkingBlocks)o.push({type:"thinking",thinking:r.thinking,signature:r.signature});if(e.content&&o.push({type:"text",text:e.content}),e.toolUses)for(let r of e.toolUses)o.push({type:"tool_use",id:r.toolUseId,name:r.toolName,input:r.arguments});o.length===0&&o.push({type:"text",text:""}),t.push({role:"assistant",content:o})}else if(e.toolResults&&e.toolResults.length>0){let o=[];for(let r of e.toolResults)o.push({type:"tool_result",tool_use_id:r.toolUseId,is_error:r.isError,content:r.content});t.push({role:"user",content:o})}else{if(t.length===0){t.push({role:"user",content:[{type:"text",text:e.content}]});continue}let o=!1,r=t[t.length-1],a=r.content;r.role==="user"&&(typeof a=="string"||Array.isArray(a)&&a.length>0&&a[0].type==="text")&&(o=!0),o?(typeof a=="string"&&(a=r.content=a.trim().length>0?[{type:"text",text:a}]:[]),a.push({type:"text",text:e.content})):t.push({role:"user",content:[{type:"text",text:e.content}]})}return t}var G=class{client;model;thinking;systemPrompt;maxOutputTokens;contextWindow;constructor(t,e){let o=R(t);if(!o)throw new M("Anthropic API key not found, set ANTHROPIC_API_KEY in .swifty/config.y(a)ml, or via ANTHROPIC_API_KEY env variable.");this.client=new P({apiKey:o,baseURL:t.base_url}),this.model=t.model,this.thinking=t.thinking??!0,this.systemPrompt=e,this.maxOutputTokens=W(t),this.contextWindow=B(t)}setSystemPrompt(t){this.systemPrompt=t}setMaxOutputTokens(t){this.maxOutputTokens=t}async*stream(t,e,o){let r=D(O(t.getMessages())),a=e.map(c=>{let p=c.input_schema;return{name:c.name,description:c.description,input_schema:{type:"object",properties:p.properties,required:p.required??[]}}});a.length>0&&(a[a.length-1].cache_control={type:"ephemeral"}),F(r);let l={model:this.model,max_tokens:this.maxOutputTokens,stream:!0,system:[{type:"text",text:this.systemPrompt,cache_control:{type:"ephemeral"}}],messages:r,...a.length>0?{tools:a}:{}};this.thinking?q()&&(l.thinking={type:"enabled",budget_tokens:this.maxOutputTokens-1}):l.thinking={type:"enabled",budget_tokens:this.maxOutputTokens-1};let g=0,h=0,f=0,d=0,y="end_turn",b="",T="",A=!1,C=0;try{let c=this.client.messages.stream(l,{...o?{signal:o}:{}}),p="",_="",u="";for await(let i of c)switch(i.type){case"content_block_start":{let s=i.content_block;s.type==="thinking"?(A=!0,b="",T=""):s.type==="tool_use"&&(_=s.id,p=s.name,u="",yield{type:"tool_call_start",toolName:p,toolId:_});break}case"content_block_delta":{let s=i.delta;s.type==="thinking_delta"?(b+=s.thinking,yield{type:"thinking_delta",text:s.thinking}):s.type==="signature_delta"?(x.debug({signature:s.signature},"thinking signature received"),T=s.signature):s.type==="text_delta"?yield{type:"text_delta",text:s.text}:s.type==="input_json_delta"&&(u+=s.partial_json,yield{type:"tool_call_delta",text:s.partial_json});break}case"content_block_stop":{if(A&&(yield{type:"thinking_complete",thinking:b,signature:T},A=!1),p){let s={};if(u)try{let m=JSON.parse(u);s=S(m)?I(m):{[v]:u}}catch(m){x.error({err:m},"llm operation failed"),s={[v]:u}}yield{type:"tool_call_complete",toolId:_,toolName:p,arguments:s},p="",_="",u=""}break}case"message_delta":{i.delta.stop_reason&&(y=i.delta.stop_reason),i.usage.output_tokens&&(h=i.usage.output_tokens,i.usage.input_tokens&&(g=i.usage.input_tokens),i.usage.cache_read_input_tokens&&(f=i.usage.cache_read_input_tokens),i.usage.cache_creation_input_tokens&&(d=i.usage.cache_creation_input_tokens));break}case"message_start":{C=performance.now(),g=i.message.usage.input_tokens,h=i.message.usage.output_tokens,f=i.message.usage.cache_read_input_tokens??0,d=i.message.usage.cache_creation_input_tokens??0;break}case"message_stop":{let m=performance.now()-C;x.debug({elapsedMs:m},"message stream complete");break}}yield{type:"stream_end",stopReason:y,usage:{inputTokens:g,outputTokens:h,cacheReadInputTokens:f,cacheCreationInputTokens:d}}}catch(c){throw x.error({err:c},"llm operation failed"),J(c)}}};function F(n){for(let t=n.length-1;t>=0;t--){if(n[t].role!=="user")continue;let e=n[t].content;if(typeof e=="string"&&e.length===0||Array.isArray(e)&&e.length===0)return;typeof e=="string"&&(e=n[t].content=[{type:"text",text:e}]);let o=e[e.length-1];Reflect.set(o,"cache_control",{type:"ephemeral"})}}function J(n){if(n instanceof P.APIError){if(n.status===413||/prompts?\s+too\s+long/i.test(n.message))return new K(`Prompt too long: ${n.message}`);if(n.status===401)return new M(`Invalid API key: ${n.message}`);if(n.status===429){let t=I(n.headers)["retry-after"],e="Rate Limited";if(t){let o=Number.parseInt(k(t));Number.isNaN(o)&&(e+=", please wait."),e+=`, retry after ${k(o)}s.`}else e+=", please wait.";return new U(e,t?k(t):void 0)}return new j(`Anthropic API error (${k(n.status)}): ${n.message}`)}return new $(`Network error: ${N(n)}`)}export{G as AnthropicClient,D as buildAnthropicMessages,ot as fetchModelContextWindow};
|
|
@@ -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,b,c,d}from"./chunk-6ARDOHBL.js";import"./chunk-7MHXMDYC.js";import"./chunk-4KVSJNS6.js";import"./chunk-MQ5XOYLD.js";import"./chunk-X3UA5OZL.js";export{b as PathSandbox,d as PermissionChecker,c as RuleEngine,a as extractContent};
|