swixter 0.1.0 → 0.1.1
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 +200 -358
- package/dist/cli/index.js +132 -87
- package/package.json +5 -6
package/README.md
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/swixter)
|
|
4
4
|
[](https://github.com/dawnswwwww/swixter/actions/workflows/test.yml)
|
|
5
|
-
[](https://github.com/dawnswwwww/swixter/actions/workflows/
|
|
5
|
+
[](https://github.com/dawnswwwww/swixter/actions/workflows/release.yml)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
|
-
[](https://nodejs.org)
|
|
8
7
|
|
|
9
8
|
> Make AI coding tools effortlessly switchable
|
|
10
9
|
|
|
@@ -12,58 +11,42 @@ A lightweight CLI tool that makes it easy to switch between AI providers for Cla
|
|
|
12
11
|
|
|
13
12
|
## Why Swixter?
|
|
14
13
|
|
|
15
|
-
Working with AI coding tools shouldn't be complicated. Swixter lets you:
|
|
16
|
-
|
|
17
14
|
- **Switch providers instantly** - Change between Anthropic, Ollama, or custom APIs with one command
|
|
18
|
-
- **
|
|
19
|
-
- **
|
|
20
|
-
- **
|
|
21
|
-
- **
|
|
22
|
-
- **
|
|
23
|
-
- **Web UI** - Browser-based interface for visual configuration management
|
|
15
|
+
- **Automatic failover** - Group profiles by priority, auto-retry on provider failure
|
|
16
|
+
- **Local proxy** - Transparent proxy with circuit breaker, your tools won't even notice a provider goes down
|
|
17
|
+
- **Multiple coders** - Works with Claude Code, Codex, Continue
|
|
18
|
+
- **Web UI** - Browser-based interface for visual management
|
|
19
|
+
- **All local** - No cloud dependencies, your keys stay on your machine
|
|
24
20
|
|
|
25
21
|
## Installation
|
|
26
22
|
|
|
27
|
-
### npm (Recommended)
|
|
28
|
-
|
|
29
23
|
```bash
|
|
24
|
+
# npm (Recommended)
|
|
30
25
|
npm install -g swixter
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### npx (No Install Needed)
|
|
34
26
|
|
|
35
|
-
|
|
27
|
+
# npx (No Install Needed)
|
|
36
28
|
npx swixter --help
|
|
37
29
|
```
|
|
38
30
|
|
|
39
|
-
### Platform Support
|
|
40
|
-
|
|
41
31
|
| Platform | Status | Notes |
|
|
42
32
|
|----------|--------|-------|
|
|
43
|
-
| **Linux** |
|
|
44
|
-
| **macOS** |
|
|
45
|
-
| **Windows 10/11** |
|
|
33
|
+
| **Linux** | Full | |
|
|
34
|
+
| **macOS** | Full | |
|
|
35
|
+
| **Windows 10/11** | Full | Requires Node.js 18+ |
|
|
46
36
|
|
|
47
37
|
Config stored at:
|
|
48
38
|
- **Linux/macOS**: `~/.config/swixter/`
|
|
49
|
-
- **Windows**: `~/swixter/`
|
|
50
|
-
|
|
51
|
-
See [docs/WINDOWS.md](docs/WINDOWS.md) for detailed Windows guide.
|
|
39
|
+
- **Windows**: `~/swixter/`
|
|
52
40
|
|
|
53
41
|
## Quick Start
|
|
54
42
|
|
|
55
43
|
```bash
|
|
56
|
-
# Interactive mode
|
|
57
|
-
swixter
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
swixter claude
|
|
61
|
-
swixter
|
|
62
|
-
swixter claude switch my-profile # Switch profile
|
|
63
|
-
swixter claude apply # Apply to Claude Code
|
|
64
|
-
|
|
65
|
-
# Launch Web UI (browser-based management)
|
|
66
|
-
swixter ui
|
|
44
|
+
swixter # Interactive mode
|
|
45
|
+
swixter claude create # Create profile
|
|
46
|
+
swixter claude list # List profiles
|
|
47
|
+
swixter claude switch my-profile # Switch profile
|
|
48
|
+
swixter claude apply # Apply to Claude Code
|
|
49
|
+
swixter ui # Launch Web UI
|
|
67
50
|
```
|
|
68
51
|
|
|
69
52
|
## Built-in Providers
|
|
@@ -71,338 +54,257 @@ swixter ui
|
|
|
71
54
|
| Provider | API Type | Description |
|
|
72
55
|
|----------|----------|-------------|
|
|
73
56
|
| **Anthropic** | responses | Official Claude API |
|
|
74
|
-
| **Ollama** | chat | Run
|
|
57
|
+
| **Ollama** | chat | Run models locally |
|
|
75
58
|
| **OpenAI** | chat | OpenAI API |
|
|
76
59
|
| **OpenRouter** | chat | Unified access to 100+ models |
|
|
77
|
-
| **Custom** | chat |
|
|
78
|
-
|
|
79
|
-
## Web UI
|
|
60
|
+
| **Custom** | chat | Any OpenAI-compatible API |
|
|
80
61
|
|
|
81
|
-
|
|
62
|
+
## Profile Management
|
|
82
63
|
|
|
83
|
-
|
|
64
|
+
Profiles are configuration templates containing provider, API key, base URL, and model settings. Each coder maintains its own active profile.
|
|
84
65
|
|
|
85
|
-
|
|
86
|
-
- **Profiles** - Create, edit, delete configuration profiles
|
|
87
|
-
- **Providers** - Manage custom API providers
|
|
88
|
-
- **Settings** - Import/export configurations
|
|
89
|
-
|
|
90
|
-
### Running the Web UI
|
|
66
|
+
### Commands (take Claude Code as example)
|
|
91
67
|
|
|
92
68
|
```bash
|
|
93
|
-
#
|
|
94
|
-
swixter
|
|
95
|
-
|
|
96
|
-
#
|
|
97
|
-
swixter
|
|
69
|
+
swixter claude create # Interactive creation (alias: new)
|
|
70
|
+
swixter claude create --quiet --name my --provider anthropic --api-key sk-ant-xxx # Non-interactive
|
|
71
|
+
swixter claude list # List profiles (alias: ls)
|
|
72
|
+
swixter claude switch <name> # Switch active profile (alias: sw)
|
|
73
|
+
swixter claude apply # Write config to ~/.claude/settings.json
|
|
74
|
+
swixter claude run # Run Claude Code with current profile (alias: r)
|
|
75
|
+
swixter claude edit <name> # Edit profile (alias: update)
|
|
76
|
+
swixter claude current # Show active profile
|
|
77
|
+
swixter claude delete <name> # Delete profile (alias: rm)
|
|
78
|
+
swixter claude install # Install Claude Code CLI
|
|
79
|
+
swixter claude update-cli # Update CLI (alias: upgrade)
|
|
98
80
|
```
|
|
99
81
|
|
|
100
|
-
|
|
82
|
+
Codex and Qwen/Continue have the same command structure: `swixter codex <command>` / `swixter qwen <command>`.
|
|
101
83
|
|
|
102
|
-
###
|
|
84
|
+
### Model Configuration
|
|
103
85
|
|
|
104
|
-
|
|
105
|
-
- All installed coders (Claude Code, Codex, Continue)
|
|
106
|
-
- Current active profile per coder
|
|
107
|
-
- Quick profile switching via dropdown
|
|
108
|
-
- One-click Apply to write config to coder's settings file
|
|
86
|
+
**Claude Code** - set per-profile models:
|
|
109
87
|
|
|
110
|
-
|
|
88
|
+
```bash
|
|
89
|
+
swixter claude create \
|
|
90
|
+
--name production \
|
|
91
|
+
--provider anthropic \
|
|
92
|
+
--api-key sk-ant-xxx \
|
|
93
|
+
--anthropic-model claude-sonnet-4-20250514 \
|
|
94
|
+
--default-haiku-model claude-haiku-4-20250506 \
|
|
95
|
+
--default-opus-model claude-opus-4-20250514
|
|
96
|
+
```
|
|
111
97
|
|
|
112
|
-
|
|
98
|
+
| Flag | Env Variable | Description |
|
|
99
|
+
|------|-------------|-------------|
|
|
100
|
+
| `--anthropic-model` | `ANTHROPIC_MODEL` | Default model |
|
|
101
|
+
| `--default-haiku-model` | `ANTHROPIC_DEFAULT_HAIKU_MODEL` | Haiku model |
|
|
102
|
+
| `--default-opus-model` | `ANTHROPIC_DEFAULT_OPUS_MODEL` | Opus model |
|
|
103
|
+
| `--default-sonnet-model` | `ANTHROPIC_DEFAULT_SONNET_MODEL` | Sonnet model |
|
|
104
|
+
|
|
105
|
+
**Codex/Qwen** - single model field:
|
|
113
106
|
|
|
114
107
|
```bash
|
|
115
|
-
swixter
|
|
116
|
-
swixter claude list # List all profiles (alias: ls)
|
|
117
|
-
swixter claude switch <name> # Switch active profile (alias: sw)
|
|
118
|
-
swixter claude apply # Apply to Claude Code
|
|
119
|
-
swixter claude run # Run Claude Code with current profile (alias: r)
|
|
120
|
-
swixter claude edit <name> # Edit existing profile (alias: update)
|
|
121
|
-
swixter claude current # Show current active profile
|
|
122
|
-
swixter claude delete <name> # Delete profile (alias: rm)
|
|
123
|
-
swixter claude install # Install Claude Code CLI
|
|
124
|
-
swixter claude update-cli # Update Claude Code CLI (alias: upgrade)
|
|
108
|
+
swixter codex create --name local --provider ollama --model qwen3:32b
|
|
125
109
|
```
|
|
126
110
|
|
|
127
|
-
###
|
|
111
|
+
### Custom Providers
|
|
128
112
|
|
|
129
113
|
```bash
|
|
130
|
-
swixter
|
|
131
|
-
swixter
|
|
132
|
-
swixter
|
|
133
|
-
swixter
|
|
134
|
-
swixter codex run # Apply + set env + run codex (alias: r)
|
|
135
|
-
swixter codex edit <name> # Edit existing profile (alias: update)
|
|
136
|
-
swixter codex current # Show current active profile
|
|
137
|
-
swixter codex delete <name> # Delete profile (alias: rm)
|
|
138
|
-
swixter codex install # Install Codex CLI
|
|
139
|
-
swixter codex update-cli # Update Codex CLI (alias: upgrade)
|
|
114
|
+
swixter providers add
|
|
115
|
+
swixter providers list
|
|
116
|
+
swixter providers remove <id>
|
|
117
|
+
swixter providers show <id>
|
|
140
118
|
```
|
|
141
119
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
1. **Quick way** (recommended): `swixter codex r`
|
|
145
|
-
- Automatically applies profile to config.toml
|
|
146
|
-
- Sets environment variables
|
|
147
|
-
- Runs codex in one command
|
|
120
|
+
## Groups (Failover)
|
|
148
121
|
|
|
149
|
-
|
|
150
|
-
- Good for debugging or custom setups
|
|
122
|
+
Groups are ordered lists of profiles used for **automatic failover**. When one provider fails, the next profile in the group is tried automatically.
|
|
151
123
|
|
|
152
|
-
###
|
|
124
|
+
### How It Works
|
|
153
125
|
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
swixter qwen list # List all profiles (alias: ls)
|
|
157
|
-
swixter qwen switch <name> # Switch active profile (alias: sw)
|
|
158
|
-
swixter qwen apply # Apply to Continue
|
|
159
|
-
swixter qwen run # Run Qwen Code with current profile (alias: r)
|
|
160
|
-
swixter qwen edit <name> # Edit existing profile (alias: update)
|
|
161
|
-
swixter qwen current # Show current active profile
|
|
162
|
-
swixter qwen delete <name> # Delete profile (alias: rm)
|
|
163
|
-
swixter qwen install # Install Qwen Code CLI
|
|
164
|
-
swixter qwen update-cli # Update Qwen Code CLI (alias: upgrade)
|
|
126
|
+
```
|
|
127
|
+
Request → Proxy → Profile 1 (Anthropic) → Fail! → Profile 2 (OpenRouter) → Success → Response
|
|
165
128
|
```
|
|
166
129
|
|
|
167
|
-
|
|
130
|
+
Each group defines a priority order. The proxy tries profiles from highest to lowest priority until one succeeds.
|
|
131
|
+
|
|
132
|
+
### Commands
|
|
168
133
|
|
|
169
134
|
```bash
|
|
170
|
-
swixter
|
|
171
|
-
swixter
|
|
172
|
-
swixter
|
|
173
|
-
swixter
|
|
135
|
+
swixter group create # Interactive creation (alias: new)
|
|
136
|
+
swixter group list # List groups (alias: ls)
|
|
137
|
+
swixter group show <name> # Show group details with profile order (alias: info)
|
|
138
|
+
swixter group edit <name> # Edit group name and profiles (alias: update)
|
|
139
|
+
swixter group set-default <name> # Set as default group
|
|
140
|
+
swixter group delete <name> # Delete group (alias: rm)
|
|
174
141
|
```
|
|
175
142
|
|
|
176
|
-
###
|
|
143
|
+
### Example
|
|
177
144
|
|
|
178
145
|
```bash
|
|
179
|
-
|
|
180
|
-
swixter
|
|
181
|
-
swixter
|
|
182
|
-
swixter
|
|
183
|
-
swixter import <file> # Import configs
|
|
184
|
-
swixter import <file> --overwrite # Overwrite existing profiles
|
|
185
|
-
swixter completion bash # Bash completion
|
|
186
|
-
swixter completion zsh # Zsh completion
|
|
187
|
-
swixter completion fish # Fish completion
|
|
188
|
-
```
|
|
146
|
+
# Create profiles for different providers
|
|
147
|
+
swixter claude create --name anthropic-primary --provider anthropic --api-key sk-ant-xxx
|
|
148
|
+
swixter claude create --name openrouter-backup --provider openrouter --api-key sk-or-xxx
|
|
149
|
+
swixter claude create --name ollama-local --provider ollama
|
|
189
150
|
|
|
190
|
-
|
|
151
|
+
# Create a group with failover priority
|
|
152
|
+
swixter group create --name ha-group --profiles anthropic-primary,openrouter-backup,ollama-local
|
|
191
153
|
|
|
192
|
-
|
|
154
|
+
# Set as default
|
|
155
|
+
swixter group set-default ha-group
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Web UI
|
|
193
159
|
|
|
194
|
-
|
|
160
|
+
Manage groups visually with drag-and-drop profile reordering:
|
|
195
161
|
|
|
196
162
|
```bash
|
|
197
|
-
swixter
|
|
198
|
-
--name production \
|
|
199
|
-
--provider anthropic \
|
|
200
|
-
--api-key sk-ant-xxx \
|
|
201
|
-
--anthropic-model claude-sonnet-4-20250514 \
|
|
202
|
-
--default-haiku-model claude-haiku-4-20250506 \
|
|
203
|
-
--default-opus-model claude-opus-4-20250514 \
|
|
204
|
-
--default-sonnet-model claude-sonnet-4-20250514
|
|
163
|
+
swixter ui # Open Groups page to create/reorder groups
|
|
205
164
|
```
|
|
206
165
|
|
|
207
|
-
|
|
208
|
-
|------|---------------------|-------------|
|
|
209
|
-
| `--anthropic-model` | `ANTHROPIC_MODEL` | Default model |
|
|
210
|
-
| `--default-haiku-model` | `ANTHROPIC_DEFAULT_HAIKU_MODEL` | Haiku model |
|
|
211
|
-
| `--default-opus-model` | `ANTHROPIC_DEFAULT_OPUS_MODEL` | Opus model |
|
|
212
|
-
| `--default-sonnet-model` | `ANTHROPIC_DEFAULT_SONNET_MODEL` | Sonnet model |
|
|
166
|
+
## Proxy
|
|
213
167
|
|
|
214
|
-
|
|
168
|
+
The local proxy server sits between your AI coding tools and upstream providers, enabling automatic failover, circuit breaking, and unified access.
|
|
215
169
|
|
|
216
|
-
|
|
217
|
-
swixter codex create \
|
|
218
|
-
--name local-ollama \
|
|
219
|
-
--provider ollama \
|
|
220
|
-
--base-url http://localhost:11434 \
|
|
221
|
-
--model qwen3:32b
|
|
222
|
-
```
|
|
170
|
+
### How It Works
|
|
223
171
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
```json
|
|
231
|
-
{
|
|
232
|
-
"profiles": {
|
|
233
|
-
"my-profile": {
|
|
234
|
-
"name": "my-profile",
|
|
235
|
-
"providerId": "anthropic",
|
|
236
|
-
"apiKey": "sk-ant-xxx",
|
|
237
|
-
"authToken": "sk-ant-auth-xxx",
|
|
238
|
-
"baseURL": "https://api.anthropic.com",
|
|
239
|
-
"models": {
|
|
240
|
-
"anthropicModel": "claude-sonnet-4-20250514",
|
|
241
|
-
"defaultHaikuModel": "claude-haiku-4-20250506",
|
|
242
|
-
"defaultOpusModel": "claude-opus-4-20250514",
|
|
243
|
-
"defaultSonnetModel": "claude-sonnet-4-20250514"
|
|
244
|
-
},
|
|
245
|
-
"envKey": "CUSTOM_API_KEY",
|
|
246
|
-
"headers": {
|
|
247
|
-
"X-Custom-Header": "value"
|
|
248
|
-
},
|
|
249
|
-
"createdAt": "2024-01-01T00:00:00.000Z",
|
|
250
|
-
"updatedAt": "2024-01-01T00:00:00.000Z"
|
|
251
|
-
},
|
|
252
|
-
"ollama-local": {
|
|
253
|
-
"name": "ollama-local",
|
|
254
|
-
"providerId": "ollama",
|
|
255
|
-
"apiKey": "",
|
|
256
|
-
"baseURL": "http://localhost:11434",
|
|
257
|
-
"model": "qwen3:32b"
|
|
258
|
-
}
|
|
259
|
-
},
|
|
260
|
-
"coders": {
|
|
261
|
-
"claude": {
|
|
262
|
-
"activeProfile": "my-profile"
|
|
263
|
-
},
|
|
264
|
-
"codex": {
|
|
265
|
-
"activeProfile": "ollama-local"
|
|
266
|
-
},
|
|
267
|
-
"qwen": {
|
|
268
|
-
"activeProfile": ""
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
172
|
+
```
|
|
173
|
+
┌─────────────┐ ┌──────────────────┐ ┌──────────────┐
|
|
174
|
+
│ Claude Code │────▶│ Swixter Proxy │────▶│ Anthropic │
|
|
175
|
+
│ Codex │ │ (localhost) │─ ─ ▶│ OpenRouter │
|
|
176
|
+
│ Continue │ │ Circuit Breaker │─ ─ ▶│ Ollama │
|
|
177
|
+
└─────────────┘ └──────────────────┘ └──────────────┘
|
|
272
178
|
```
|
|
273
179
|
|
|
274
|
-
|
|
180
|
+
Key capabilities:
|
|
275
181
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
182
|
+
- **Failover** - Tries profiles in group priority order, returns first successful response
|
|
183
|
+
- **Circuit Breaker** - Skips providers that failed 3 consecutive times, auto-recovers after 60s
|
|
184
|
+
- **Streaming** - Transparently forwards SSE/NDJSON streaming responses
|
|
185
|
+
- **Multi-format** - Supports both OpenAI Chat (`/v1/chat/completions`) and Anthropic (`/v1/messages`, `/v1/responses`) APIs
|
|
186
|
+
- **Model Rewriting** - Different providers in a group can use different model names; the proxy rewrites them automatically
|
|
279
187
|
|
|
280
|
-
|
|
281
|
-
swixter providers add \
|
|
282
|
-
--id openrouter \
|
|
283
|
-
--name "OpenRouter" \
|
|
284
|
-
--display-name "OpenRouter" \
|
|
285
|
-
--base-url "https://openrouter.ai/api/v1" \
|
|
286
|
-
--auth-type bearer
|
|
188
|
+
### Commands
|
|
287
189
|
|
|
288
|
-
|
|
289
|
-
|
|
190
|
+
```bash
|
|
191
|
+
# Start proxy as a background service
|
|
192
|
+
swixter proxy start # Start with default group
|
|
193
|
+
swixter proxy start --group ha-group # Start with specific group
|
|
194
|
+
swixter proxy start --port 8080 # Custom port (default: 15721)
|
|
195
|
+
swixter proxy start --daemon # Run in background
|
|
196
|
+
|
|
197
|
+
# Stop proxy
|
|
198
|
+
swixter proxy stop # Stop default instance
|
|
199
|
+
swixter proxy stop run-15722 # Stop specific instance
|
|
200
|
+
|
|
201
|
+
# View status
|
|
202
|
+
swixter proxy status # Show all running instances
|
|
203
|
+
|
|
204
|
+
# One-shot: start proxy + run coder tool
|
|
205
|
+
swixter proxy run -- claude # Proxy + Claude Code
|
|
206
|
+
swixter proxy run -- codex # Proxy + Codex
|
|
207
|
+
swixter proxy run --group ha-group -- claude # With specific group
|
|
290
208
|
```
|
|
291
209
|
|
|
292
|
-
|
|
210
|
+
### The `proxy run` Command
|
|
211
|
+
|
|
212
|
+
The easiest way to use the proxy. It:
|
|
293
213
|
|
|
294
|
-
|
|
214
|
+
1. Starts a temporary proxy instance (on next available port)
|
|
215
|
+
2. Points the coder tool's API URL to the local proxy
|
|
216
|
+
3. Spawns the coder tool
|
|
217
|
+
4. Stops the proxy when the coder exits
|
|
295
218
|
|
|
296
219
|
```bash
|
|
297
|
-
#
|
|
298
|
-
|
|
220
|
+
swixter proxy run -- claude # One command, everything automatic
|
|
221
|
+
```
|
|
299
222
|
|
|
300
|
-
|
|
301
|
-
swixter claude create --name personal --provider anthropic --api-key sk-ant-personal-xxx
|
|
223
|
+
### Circuit Breaker
|
|
302
224
|
|
|
303
|
-
|
|
304
|
-
swixter claude sw work && swixter claude apply
|
|
225
|
+
Per-profile circuit breaker prevents wasting time on failing providers:
|
|
305
226
|
|
|
306
|
-
|
|
307
|
-
|
|
227
|
+
| State | Behavior | Transition |
|
|
228
|
+
|-------|----------|------------|
|
|
229
|
+
| **Closed** | Requests pass through | 3 consecutive failures → Open |
|
|
230
|
+
| **Open** | Requests skipped | After 60s → Half-Open |
|
|
231
|
+
| **Half-Open** | One probe request allowed | Success → Closed, Failure → Open |
|
|
308
232
|
|
|
309
|
-
|
|
310
|
-
swixter claude sw personal && swixter claude apply
|
|
311
|
-
swixter claude r
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### Example 2: Try Qwen locally
|
|
233
|
+
### Monitoring (Web UI)
|
|
315
234
|
|
|
316
235
|
```bash
|
|
317
|
-
#
|
|
318
|
-
swixter qwen create \
|
|
319
|
-
--name local \
|
|
320
|
-
--provider ollama \
|
|
321
|
-
--base-url http://localhost:11434
|
|
322
|
-
|
|
323
|
-
# Switch and run
|
|
324
|
-
swixter qwen sw local
|
|
325
|
-
swixter qwen r
|
|
236
|
+
swixter ui # Proxy page shows live status, request counts, and real-time logs
|
|
326
237
|
```
|
|
327
238
|
|
|
328
|
-
|
|
239
|
+
## Web UI
|
|
329
240
|
|
|
330
241
|
```bash
|
|
331
|
-
#
|
|
332
|
-
swixter
|
|
333
|
-
|
|
334
|
-
--provider ollama \
|
|
335
|
-
--base-url http://localhost:11434
|
|
242
|
+
swixter ui # Default port 3141
|
|
243
|
+
swixter ui --port 8080 # Custom port
|
|
244
|
+
```
|
|
336
245
|
|
|
337
|
-
|
|
338
|
-
swixter codex r
|
|
246
|
+
### Pages
|
|
339
247
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
248
|
+
- **Dashboard** - All coders at a glance, quick switch and apply
|
|
249
|
+
- **Profiles** - CRUD for configuration profiles
|
|
250
|
+
- **Groups** - Manage failover groups with drag-and-drop priority
|
|
251
|
+
- **Proxy** - Start/stop proxy, live logs, status monitoring
|
|
252
|
+
- **Providers** - Manage custom providers
|
|
253
|
+
- **Settings** - Import/export configurations
|
|
345
254
|
|
|
346
|
-
|
|
255
|
+
## Other Commands
|
|
347
256
|
|
|
348
257
|
```bash
|
|
349
|
-
#
|
|
350
|
-
swixter
|
|
351
|
-
|
|
352
|
-
#
|
|
353
|
-
|
|
354
|
-
# 2. Go to Dashboard → Select profile from dropdown
|
|
355
|
-
# 3. Click APPLY to write config
|
|
258
|
+
swixter export <file> # Export configs
|
|
259
|
+
swixter export <file> --sanitize # Export without API keys
|
|
260
|
+
swixter import <file> # Import configs
|
|
261
|
+
swixter import <file> --overwrite # Overwrite existing
|
|
262
|
+
swixter completion bash # Shell completions (zsh/fish supported)
|
|
356
263
|
```
|
|
357
264
|
|
|
358
|
-
##
|
|
265
|
+
## Command Aliases
|
|
359
266
|
|
|
360
|
-
|
|
267
|
+
| Alias | Full Command | Description |
|
|
268
|
+
|-------|-------------|-------------|
|
|
269
|
+
| `r` | `run` | Execute the AI coder |
|
|
270
|
+
| `ls` | `list` | List profiles/groups |
|
|
271
|
+
| `sw` | `switch` | Switch profiles |
|
|
272
|
+
| `rm` | `delete` | Delete profiles/groups |
|
|
273
|
+
| `new` | `create` | Create new |
|
|
274
|
+
| `update` | `edit` | Edit existing |
|
|
275
|
+
| `upgrade` | `update-cli` | Update CLI tool |
|
|
361
276
|
|
|
362
|
-
|
|
277
|
+
## Examples
|
|
363
278
|
|
|
364
|
-
|
|
365
|
-
# Install
|
|
366
|
-
swixter completion bash > ~/.local/share/bash-completion/completions/swixter
|
|
279
|
+
### Switch between work and personal
|
|
367
280
|
|
|
368
|
-
|
|
369
|
-
|
|
281
|
+
```bash
|
|
282
|
+
swixter claude create --name work --provider anthropic --api-key sk-ant-work-xxx
|
|
283
|
+
swixter claude create --name personal --provider anthropic --api-key sk-ant-personal-xxx
|
|
284
|
+
swixter claude sw work && swixter claude apply
|
|
370
285
|
```
|
|
371
286
|
|
|
372
|
-
###
|
|
287
|
+
### Failover setup: Anthropic primary + OpenRouter backup
|
|
373
288
|
|
|
374
289
|
```bash
|
|
375
|
-
#
|
|
376
|
-
swixter
|
|
290
|
+
# Create profiles
|
|
291
|
+
swixter claude create --name primary --provider anthropic --api-key sk-ant-xxx
|
|
292
|
+
swixter claude create --name backup --provider openrouter --api-key sk-or-xxx
|
|
377
293
|
|
|
378
|
-
#
|
|
379
|
-
|
|
294
|
+
# Create failover group
|
|
295
|
+
swixter group create --name failover --profiles primary,backup
|
|
296
|
+
|
|
297
|
+
# Run with proxy (auto-failover)
|
|
298
|
+
swixter proxy run --group failover -- claude
|
|
380
299
|
```
|
|
381
300
|
|
|
382
|
-
###
|
|
301
|
+
### Run Codex with local Ollama
|
|
383
302
|
|
|
384
303
|
```bash
|
|
385
|
-
|
|
386
|
-
swixter
|
|
387
|
-
|
|
388
|
-
# Reload
|
|
389
|
-
fish
|
|
304
|
+
swixter codex create --name local --provider ollama --base-url http://localhost:11434
|
|
305
|
+
swixter codex r
|
|
390
306
|
```
|
|
391
307
|
|
|
392
|
-
## Command Aliases
|
|
393
|
-
|
|
394
|
-
Save keystrokes with short aliases:
|
|
395
|
-
|
|
396
|
-
| Alias | Full Command | Description |
|
|
397
|
-
|-------|-------------|-------------|
|
|
398
|
-
| `r` | `run` | Execute the AI coder |
|
|
399
|
-
| `ls` | `list` | List all profiles |
|
|
400
|
-
| `sw` | `switch` | Switch profiles |
|
|
401
|
-
| `rm` | `delete` | Delete profiles |
|
|
402
|
-
| `new` | `create` | Create new profile |
|
|
403
|
-
| `update` | `edit` | Edit existing profile |
|
|
404
|
-
| `upgrade` | `update-cli` | Update CLI tool |
|
|
405
|
-
|
|
406
308
|
## Architecture
|
|
407
309
|
|
|
408
310
|
```
|
|
@@ -410,45 +312,34 @@ swixter/
|
|
|
410
312
|
├── src/
|
|
411
313
|
│ ├── cli/ # CLI command handlers
|
|
412
314
|
│ ├── config/ # Config file management
|
|
413
|
-
│ ├── adapters/ # Coder
|
|
414
|
-
│ ├── providers/ # Provider presets
|
|
315
|
+
│ ├── adapters/ # Coder adapters (Claude, Codex, Continue)
|
|
316
|
+
│ ├── providers/ # Provider presets + user-defined providers
|
|
317
|
+
│ ├── groups/ # Group management (failover profiles)
|
|
318
|
+
│ ├── proxy/ # Local proxy server (failover, circuit breaker)
|
|
415
319
|
│ ├── server/ # Web UI API server
|
|
416
320
|
│ └── utils/ # Shared utilities
|
|
417
|
-
├── ui/ # Web UI (React + Vite)
|
|
321
|
+
├── ui/ # Web UI (React + Vite + Tailwind)
|
|
418
322
|
└── tests/ # Unit tests
|
|
419
323
|
```
|
|
420
324
|
|
|
421
|
-
**Data Flow**:
|
|
422
|
-
1. `swixter create/switch` → Updates `~/.config/swixter/config.json`
|
|
423
|
-
2. `swixter apply` → Writes to coder config (`~/.claude/settings.json`, etc.)
|
|
424
|
-
3. `swixter run` → Sets env vars + spawns coder CLI
|
|
425
|
-
|
|
426
325
|
## Development
|
|
427
326
|
|
|
428
|
-
Built with modern tools:
|
|
429
|
-
|
|
430
327
|
```bash
|
|
431
|
-
# Clone repo
|
|
432
328
|
git clone https://github.com/dawnswwwww/swixter.git
|
|
433
329
|
cd swixter
|
|
434
|
-
|
|
435
|
-
# Install dependencies
|
|
436
330
|
bun install
|
|
331
|
+
bun run cli:dev # Dev mode with hot reload
|
|
332
|
+
bun test # Run tests
|
|
333
|
+
bun run build # Build all (UI + CLI)
|
|
334
|
+
```
|
|
437
335
|
|
|
438
|
-
|
|
439
|
-
bun run cli:dev
|
|
440
|
-
|
|
441
|
-
# Run tests
|
|
442
|
-
bun test
|
|
443
|
-
|
|
444
|
-
# Run specific test
|
|
445
|
-
bun test tests/adapters/claude.test.ts
|
|
446
|
-
|
|
447
|
-
# E2E tests (requires Docker)
|
|
448
|
-
bun run test:e2e
|
|
336
|
+
### Release
|
|
449
337
|
|
|
450
|
-
|
|
451
|
-
|
|
338
|
+
```bash
|
|
339
|
+
# Update CHANGELOG.md first, then:
|
|
340
|
+
bun run release:patch # Bug fixes (0.1.0 → 0.1.1)
|
|
341
|
+
bun run release:minor # Features (0.1.0 → 0.2.0)
|
|
342
|
+
bun run release:major # Breaking changes (0.1.0 → 1.0.0)
|
|
452
343
|
```
|
|
453
344
|
|
|
454
345
|
## Tech Stack
|
|
@@ -459,7 +350,6 @@ bun run build
|
|
|
459
350
|
| **Language** | TypeScript |
|
|
460
351
|
| **CLI UI** | @clack/prompts |
|
|
461
352
|
| **Validation** | Zod |
|
|
462
|
-
| **Version** | semver |
|
|
463
353
|
| **Web UI** | React + Vite + Tailwind CSS |
|
|
464
354
|
| **Testing** | Bun test, Docker E2E |
|
|
465
355
|
|
|
@@ -467,54 +357,6 @@ bun run build
|
|
|
467
357
|
|
|
468
358
|
See [CHANGELOG.md](CHANGELOG.md) for version history.
|
|
469
359
|
|
|
470
|
-
## Roadmap
|
|
471
|
-
|
|
472
|
-
- [ ] Profile templates for common setups
|
|
473
|
-
- [ ] Configuration validation and migration tools
|
|
474
|
-
- [x] Web UI for profile management (v0.0.9+)
|
|
475
|
-
- [ ] Cloud sync for profiles (optional, planned)
|
|
476
|
-
|
|
477
|
-
## Contributing
|
|
478
|
-
|
|
479
|
-
Contributions welcome!
|
|
480
|
-
|
|
481
|
-
### Development Setup
|
|
482
|
-
|
|
483
|
-
```bash
|
|
484
|
-
git clone https://github.com/dawnswwwww/swixter.git
|
|
485
|
-
cd swixter
|
|
486
|
-
bun install
|
|
487
|
-
bun run cli:dev
|
|
488
|
-
bun test
|
|
489
|
-
bun run build
|
|
490
|
-
```
|
|
491
|
-
|
|
492
|
-
### Release Process
|
|
493
|
-
|
|
494
|
-
1. Update `CHANGELOG.md` under `[Unreleased]`
|
|
495
|
-
2. Run release command:
|
|
496
|
-
```bash
|
|
497
|
-
bun run release:patch # Bug fixes (0.0.10 → 0.0.11)
|
|
498
|
-
bun run release:minor # Features (0.0.10 → 0.1.0)
|
|
499
|
-
bun run release:major # Breaking changes
|
|
500
|
-
```
|
|
501
|
-
3. GitHub Actions automatically:
|
|
502
|
-
- Runs tests on Linux/macOS/Windows
|
|
503
|
-
- Publishes to npm
|
|
504
|
-
- Creates GitHub Release with changelog
|
|
505
|
-
|
|
506
|
-
See [CLAUDE.md](CLAUDE.md) for detailed development documentation.
|
|
507
|
-
|
|
508
360
|
## License
|
|
509
361
|
|
|
510
362
|
MIT License - see [LICENSE](LICENSE)
|
|
511
|
-
|
|
512
|
-
## Links
|
|
513
|
-
|
|
514
|
-
- [GitHub](https://github.com/dawnswwwww/swixter)
|
|
515
|
-
- [npm](https://www.npmjs.com/package/swixter)
|
|
516
|
-
- [Issues](https://github.com/dawnswwwww/swixter/issues)
|
|
517
|
-
|
|
518
|
-
---
|
|
519
|
-
|
|
520
|
-
**Made with ❤️ to make AI coding tools more accessible**
|
package/dist/cli/index.js
CHANGED
|
@@ -13950,7 +13950,7 @@ var CONFIG_VERSION = "2.0.0", EXPORT_VERSION = "1.0.0";
|
|
|
13950
13950
|
var init_versions2 = () => {};
|
|
13951
13951
|
|
|
13952
13952
|
// src/constants/meta.ts
|
|
13953
|
-
var APP_VERSION = "0.1.
|
|
13953
|
+
var APP_VERSION = "0.1.1";
|
|
13954
13954
|
var init_meta = () => {};
|
|
13955
13955
|
|
|
13956
13956
|
// src/constants/install.ts
|
|
@@ -19048,8 +19048,8 @@ __export(exports_manager, {
|
|
|
19048
19048
|
deleteProfile: () => deleteProfile
|
|
19049
19049
|
});
|
|
19050
19050
|
import { existsSync as existsSync5 } from "fs";
|
|
19051
|
-
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
19052
|
-
import { dirname as dirname5 } from "path";
|
|
19051
|
+
import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile5, rename } from "fs/promises";
|
|
19052
|
+
import { dirname as dirname5, join as join4 } from "path";
|
|
19053
19053
|
function getConfigPath2() {
|
|
19054
19054
|
return process.env.SWIXTER_CONFIG_PATH || getConfigPath("swixter");
|
|
19055
19055
|
}
|
|
@@ -19102,7 +19102,9 @@ async function saveConfig(config2) {
|
|
|
19102
19102
|
try {
|
|
19103
19103
|
ConfigFileSchema.parse(config2);
|
|
19104
19104
|
const content = JSON.stringify(config2, null, SERIALIZATION.jsonIndent);
|
|
19105
|
-
|
|
19105
|
+
const tmpPath = join4(dirname5(configPath), `.config.tmp-${Date.now()}`);
|
|
19106
|
+
await writeFile5(tmpPath, content, "utf-8");
|
|
19107
|
+
await rename(tmpPath, configPath);
|
|
19106
19108
|
} catch (error46) {
|
|
19107
19109
|
throw new Error(`Failed to save configuration: ${error46}`);
|
|
19108
19110
|
}
|
|
@@ -19156,6 +19158,10 @@ async function deleteProfile(profileName) {
|
|
|
19156
19158
|
if (!config2.profiles[profileName]) {
|
|
19157
19159
|
throw new Error(`Profile "${profileName}" does not exist`);
|
|
19158
19160
|
}
|
|
19161
|
+
const referencingGroups = Object.values(config2.groups || {}).filter((group) => group.profiles.includes(profileName)).map((group) => group.name);
|
|
19162
|
+
if (referencingGroups.length > 0) {
|
|
19163
|
+
throw new Error(`Profile "${profileName}" is used in group(s): ${referencingGroups.join(", ")}. Remove it from the group(s) first.`);
|
|
19164
|
+
}
|
|
19159
19165
|
const allCoders = Object.keys(CODER_REGISTRY);
|
|
19160
19166
|
for (const coder of allCoders) {
|
|
19161
19167
|
try {
|
|
@@ -19168,8 +19174,7 @@ async function deleteProfile(profileName) {
|
|
|
19168
19174
|
delete config2.profiles[profileName];
|
|
19169
19175
|
for (const coder in config2.coders) {
|
|
19170
19176
|
if (config2.coders[coder].activeProfile === profileName) {
|
|
19171
|
-
|
|
19172
|
-
config2.coders[coder].activeProfile = remainingProfiles.length > 0 ? remainingProfiles[0] : "";
|
|
19177
|
+
config2.coders[coder].activeProfile = "";
|
|
19173
19178
|
}
|
|
19174
19179
|
}
|
|
19175
19180
|
await saveConfig(config2);
|
|
@@ -19424,13 +19429,15 @@ var init_commands2 = __esm(() => {
|
|
|
19424
19429
|
|
|
19425
19430
|
// src/utils/process.ts
|
|
19426
19431
|
import { spawn } from "child_process";
|
|
19432
|
+
import os from "os";
|
|
19427
19433
|
function spawnCLI(options) {
|
|
19428
19434
|
const { command, args, env, displayName, onExit } = options;
|
|
19429
19435
|
const finalEnv = env ? { ...process.env, ...env } : process.env;
|
|
19436
|
+
const isWin32 = os.platform() === "win32";
|
|
19430
19437
|
const child = spawn(command, args, {
|
|
19431
19438
|
env: finalEnv,
|
|
19432
19439
|
stdio: "inherit",
|
|
19433
|
-
shell:
|
|
19440
|
+
shell: isWin32
|
|
19434
19441
|
});
|
|
19435
19442
|
child.on("exit", async (code) => {
|
|
19436
19443
|
await onExit?.();
|
|
@@ -21949,7 +21956,7 @@ async function cmdCreateQuiet(params) {
|
|
|
21949
21956
|
console.log(import_picocolors8.default.dim("Usage: swixter claude create --quiet --name <name> --provider <id> [--api-key <key>] [--auth-token <token>] [--base-url <url>] [--anthropic-model <model>] [--default-haiku-model <model>] [--default-opus-model <model>] [--default-sonnet-model <model>] [--apply]"));
|
|
21950
21957
|
process.exit(1);
|
|
21951
21958
|
}
|
|
21952
|
-
const preset =
|
|
21959
|
+
const preset = await getPresetByIdAsync(params.provider);
|
|
21953
21960
|
if (!preset) {
|
|
21954
21961
|
console.log(import_picocolors8.default.red(`Error: Unknown provider ID: ${params.provider}`));
|
|
21955
21962
|
console.log(import_picocolors8.default.dim("Run 'swixter providers' to see all supported providers"));
|
|
@@ -22020,7 +22027,7 @@ async function cmdList() {
|
|
|
22020
22027
|
console.log(import_picocolors8.default.bold(LABELS.profileList));
|
|
22021
22028
|
console.log();
|
|
22022
22029
|
for (const profile of profiles) {
|
|
22023
|
-
const preset =
|
|
22030
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
22024
22031
|
const isCurrent = current?.name === profile.name;
|
|
22025
22032
|
const marker = isCurrent ? import_picocolors8.default.green(MARKERS.active) : import_picocolors8.default.dim(MARKERS.inactive);
|
|
22026
22033
|
const baseUrl = profile.baseURL || preset?.baseURL || MISC_DEFAULTS.baseUrlFallback;
|
|
@@ -22042,7 +22049,11 @@ async function cmdSwitch(profileName, args = []) {
|
|
|
22042
22049
|
try {
|
|
22043
22050
|
await setActiveProfileForCoder(CODER_NAME, profileName);
|
|
22044
22051
|
const profile = await getActiveProfileForCoder(CODER_NAME);
|
|
22045
|
-
|
|
22052
|
+
if (!profile) {
|
|
22053
|
+
console.log(import_picocolors8.default.red("Error: Profile not found after switch"));
|
|
22054
|
+
process.exit(1);
|
|
22055
|
+
}
|
|
22056
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
22046
22057
|
const baseUrl = profile.baseURL || preset?.baseURL || "Default";
|
|
22047
22058
|
console.log();
|
|
22048
22059
|
console.log(import_picocolors8.default.green("\u2713") + " Switched successfully!");
|
|
@@ -22093,10 +22104,13 @@ async function cmdEdit(profileName) {
|
|
|
22093
22104
|
}
|
|
22094
22105
|
const selected = await ve({
|
|
22095
22106
|
message: "Select profile to edit",
|
|
22096
|
-
options: profiles2.map((profile2) =>
|
|
22097
|
-
|
|
22098
|
-
|
|
22099
|
-
|
|
22107
|
+
options: await Promise.all(profiles2.map(async (profile2) => {
|
|
22108
|
+
const preset = await getPresetByIdAsync(profile2.providerId);
|
|
22109
|
+
return {
|
|
22110
|
+
value: profile2.name,
|
|
22111
|
+
label: profile2.name,
|
|
22112
|
+
hint: preset?.displayName || ""
|
|
22113
|
+
};
|
|
22100
22114
|
}))
|
|
22101
22115
|
});
|
|
22102
22116
|
if (pD(selected)) {
|
|
@@ -22117,7 +22131,7 @@ async function cmdEdit(profileName) {
|
|
|
22117
22131
|
console.log();
|
|
22118
22132
|
const { allPresets: allPresets2 } = await Promise.resolve().then(() => (init_presets(), exports_presets));
|
|
22119
22133
|
const presets = allPresets2;
|
|
22120
|
-
const currentPreset =
|
|
22134
|
+
const currentPreset = await getPresetByIdAsync(profile.providerId);
|
|
22121
22135
|
const shouldChangeProvider = await ye({
|
|
22122
22136
|
message: `Change provider? Current: ${currentPreset?.displayName}`,
|
|
22123
22137
|
initialValue: false
|
|
@@ -22288,7 +22302,7 @@ async function cmdApply() {
|
|
|
22288
22302
|
}
|
|
22289
22303
|
try {
|
|
22290
22304
|
const adapter = getAdapter(CODER_NAME);
|
|
22291
|
-
const preset =
|
|
22305
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
22292
22306
|
console.log();
|
|
22293
22307
|
console.log(import_picocolors8.default.dim(`Applying profile to ${adapter.configPath}...`));
|
|
22294
22308
|
await applyClaudeProfile(profile);
|
|
@@ -22318,7 +22332,7 @@ async function cmdCurrent() {
|
|
|
22318
22332
|
console.log(import_picocolors8.default.yellow("No active profile"));
|
|
22319
22333
|
return;
|
|
22320
22334
|
}
|
|
22321
|
-
const preset =
|
|
22335
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
22322
22336
|
const baseUrl = profile.baseURL || preset?.baseURL || "Default";
|
|
22323
22337
|
console.log();
|
|
22324
22338
|
console.log(import_picocolors8.default.bold("Current active profile:"));
|
|
@@ -22402,10 +22416,13 @@ async function cmdSwitchInteractive() {
|
|
|
22402
22416
|
const current = await getActiveProfileForCoder(CODER_NAME);
|
|
22403
22417
|
const profileName = await ve({
|
|
22404
22418
|
message: "Select profile to switch to",
|
|
22405
|
-
options: profiles.map((profile) =>
|
|
22406
|
-
|
|
22407
|
-
|
|
22408
|
-
|
|
22419
|
+
options: await Promise.all(profiles.map(async (profile) => {
|
|
22420
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
22421
|
+
return {
|
|
22422
|
+
value: profile.name,
|
|
22423
|
+
label: profile.name,
|
|
22424
|
+
hint: profile.name === current?.name ? "(current)" : preset?.displayName || ""
|
|
22425
|
+
};
|
|
22409
22426
|
}))
|
|
22410
22427
|
});
|
|
22411
22428
|
if (pD(profileName)) {
|
|
@@ -22422,10 +22439,13 @@ async function cmdDeleteInteractive() {
|
|
|
22422
22439
|
}
|
|
22423
22440
|
const profileName = await ve({
|
|
22424
22441
|
message: "Select profile to delete",
|
|
22425
|
-
options: profiles.map((profile) =>
|
|
22426
|
-
|
|
22427
|
-
|
|
22428
|
-
|
|
22442
|
+
options: await Promise.all(profiles.map(async (profile) => {
|
|
22443
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
22444
|
+
return {
|
|
22445
|
+
value: profile.name,
|
|
22446
|
+
label: profile.name,
|
|
22447
|
+
hint: preset?.displayName || ""
|
|
22448
|
+
};
|
|
22429
22449
|
}))
|
|
22430
22450
|
});
|
|
22431
22451
|
if (pD(profileName)) {
|
|
@@ -22500,7 +22520,7 @@ async function cmdRun(args) {
|
|
|
22500
22520
|
process.exit(1);
|
|
22501
22521
|
}
|
|
22502
22522
|
}
|
|
22503
|
-
const preset =
|
|
22523
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
22504
22524
|
const baseURL = profile.baseURL || preset?.baseURL || "";
|
|
22505
22525
|
const env = {};
|
|
22506
22526
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -22603,7 +22623,7 @@ class ProxyRouter {
|
|
|
22603
22623
|
|
|
22604
22624
|
// src/proxy/logger.ts
|
|
22605
22625
|
import { appendFileSync, existsSync as existsSync6, mkdirSync, renameSync, rmSync, statSync } from "fs";
|
|
22606
|
-
import { dirname as dirname6, join as
|
|
22626
|
+
import { dirname as dirname6, join as join6 } from "path";
|
|
22607
22627
|
function formatMeta(meta2) {
|
|
22608
22628
|
return meta2 ? { ...meta2 } : {};
|
|
22609
22629
|
}
|
|
@@ -22628,7 +22648,7 @@ function writeProxyLog(logPath, record2) {
|
|
|
22628
22648
|
} catch {}
|
|
22629
22649
|
}
|
|
22630
22650
|
function getProxyLogPath(instanceId) {
|
|
22631
|
-
return
|
|
22651
|
+
return join6(dirname6(getConfigPath2()), `proxy-${instanceId}.log`);
|
|
22632
22652
|
}
|
|
22633
22653
|
function createProxyLogger(instanceId) {
|
|
22634
22654
|
const logPath = getProxyLogPath(instanceId);
|
|
@@ -22748,9 +22768,6 @@ class CircuitBreaker {
|
|
|
22748
22768
|
}
|
|
22749
22769
|
isAvailable(profileId) {
|
|
22750
22770
|
const state = this.getState(profileId);
|
|
22751
|
-
if (state.state === "half_open") {
|
|
22752
|
-
return true;
|
|
22753
|
-
}
|
|
22754
22771
|
return !state.isOpen;
|
|
22755
22772
|
}
|
|
22756
22773
|
recordSuccess(profileId) {
|
|
@@ -22797,6 +22814,7 @@ class CircuitBreaker {
|
|
|
22797
22814
|
if (!state || state.state !== "open")
|
|
22798
22815
|
return;
|
|
22799
22816
|
state.state = "half_open";
|
|
22817
|
+
state.isOpen = false;
|
|
22800
22818
|
}
|
|
22801
22819
|
forceHalfOpen(profileId) {
|
|
22802
22820
|
const state = this.getOrCreateState(profileId);
|
|
@@ -23140,9 +23158,9 @@ var init_handler = __esm(() => {
|
|
|
23140
23158
|
|
|
23141
23159
|
// src/proxy/server.ts
|
|
23142
23160
|
import { existsSync as existsSync7, readFileSync, writeFileSync } from "fs";
|
|
23143
|
-
import { dirname as dirname7, join as
|
|
23161
|
+
import { dirname as dirname7, join as join7 } from "path";
|
|
23144
23162
|
function getRegistryPath() {
|
|
23145
|
-
return
|
|
23163
|
+
return join7(dirname7(getConfigPath2()), "proxy-instances.json");
|
|
23146
23164
|
}
|
|
23147
23165
|
function loadRegistry() {
|
|
23148
23166
|
const path = getRegistryPath();
|
|
@@ -23191,7 +23209,7 @@ function cleanStaleInstances() {
|
|
|
23191
23209
|
saveRegistry(registry2);
|
|
23192
23210
|
}
|
|
23193
23211
|
function migrateLegacyRuntime() {
|
|
23194
|
-
const legacyPath =
|
|
23212
|
+
const legacyPath = join7(dirname7(getConfigPath2()), "proxy-runtime.json");
|
|
23195
23213
|
if (!existsSync7(legacyPath))
|
|
23196
23214
|
return;
|
|
23197
23215
|
const registryPath = getRegistryPath();
|
|
@@ -24455,7 +24473,7 @@ async function cmdCreateQuiet2(params) {
|
|
|
24455
24473
|
console.log(import_picocolors9.default.dim("Usage: swixter qwen create --quiet --name <name> --provider <id> --model <model> [--api-key <key>] [--base-url <url>]"));
|
|
24456
24474
|
process.exit(1);
|
|
24457
24475
|
}
|
|
24458
|
-
const preset =
|
|
24476
|
+
const preset = await getPresetByIdAsync(params.provider);
|
|
24459
24477
|
if (!preset) {
|
|
24460
24478
|
console.log(import_picocolors9.default.red(`Error: Unknown provider ID: ${params.provider}`));
|
|
24461
24479
|
console.log(import_picocolors9.default.dim("Run 'swixter providers' to see all supported providers"));
|
|
@@ -24517,7 +24535,7 @@ async function cmdList2() {
|
|
|
24517
24535
|
console.log(import_picocolors9.default.bold(LABELS.profileList));
|
|
24518
24536
|
console.log();
|
|
24519
24537
|
for (const profile of profiles) {
|
|
24520
|
-
const preset =
|
|
24538
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
24521
24539
|
const isCurrent = current?.name === profile.name;
|
|
24522
24540
|
const marker = isCurrent ? import_picocolors9.default.green(MARKERS.active) : import_picocolors9.default.dim(MARKERS.inactive);
|
|
24523
24541
|
const baseUrl = profile.baseURL || preset?.baseURLChat || preset?.baseURL || MISC_DEFAULTS.baseUrlFallback;
|
|
@@ -24539,7 +24557,11 @@ async function cmdSwitch2(profileName, args = []) {
|
|
|
24539
24557
|
try {
|
|
24540
24558
|
await setActiveProfileForCoder(CODER_NAME2, profileName);
|
|
24541
24559
|
const profile = await getActiveProfileForCoder(CODER_NAME2);
|
|
24542
|
-
|
|
24560
|
+
if (!profile) {
|
|
24561
|
+
console.log(import_picocolors9.default.red("Error: Profile not found after switch"));
|
|
24562
|
+
process.exit(1);
|
|
24563
|
+
}
|
|
24564
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
24543
24565
|
const baseUrl = profile.baseURL || preset?.baseURLChat || preset?.baseURL || "Default";
|
|
24544
24566
|
console.log();
|
|
24545
24567
|
console.log(import_picocolors9.default.green("\u2713") + " Switched successfully!");
|
|
@@ -24590,10 +24612,13 @@ async function cmdEdit2(profileName) {
|
|
|
24590
24612
|
}
|
|
24591
24613
|
const selected = await ve({
|
|
24592
24614
|
message: "Select profile to edit",
|
|
24593
|
-
options: profiles2.map((profile2) =>
|
|
24594
|
-
|
|
24595
|
-
|
|
24596
|
-
|
|
24615
|
+
options: await Promise.all(profiles2.map(async (profile2) => {
|
|
24616
|
+
const preset = await getPresetByIdAsync(profile2.providerId);
|
|
24617
|
+
return {
|
|
24618
|
+
value: profile2.name,
|
|
24619
|
+
label: profile2.name,
|
|
24620
|
+
hint: preset?.displayName || ""
|
|
24621
|
+
};
|
|
24597
24622
|
}))
|
|
24598
24623
|
});
|
|
24599
24624
|
if (pD(selected)) {
|
|
@@ -24614,7 +24639,7 @@ async function cmdEdit2(profileName) {
|
|
|
24614
24639
|
console.log();
|
|
24615
24640
|
const { getProvidersByWireApi: getProvidersByWireApi2 } = await Promise.resolve().then(() => (init_presets(), exports_presets));
|
|
24616
24641
|
const presets = await getProvidersByWireApi2("chat");
|
|
24617
|
-
const currentPreset =
|
|
24642
|
+
const currentPreset = await getPresetByIdAsync(profile.providerId);
|
|
24618
24643
|
const shouldChangeProvider = await ye({
|
|
24619
24644
|
message: `Change provider? Current: ${currentPreset?.displayName}`,
|
|
24620
24645
|
initialValue: false
|
|
@@ -24730,7 +24755,7 @@ async function cmdApply2() {
|
|
|
24730
24755
|
}
|
|
24731
24756
|
try {
|
|
24732
24757
|
const adapter = getAdapter(CODER_NAME2);
|
|
24733
|
-
const preset =
|
|
24758
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
24734
24759
|
console.log();
|
|
24735
24760
|
console.log(import_picocolors9.default.dim(`Applying profile to ${adapter.configPath}...`));
|
|
24736
24761
|
await adapter.apply(profile);
|
|
@@ -24760,7 +24785,7 @@ async function cmdCurrent2() {
|
|
|
24760
24785
|
console.log(import_picocolors9.default.yellow("No active profile"));
|
|
24761
24786
|
return;
|
|
24762
24787
|
}
|
|
24763
|
-
const preset =
|
|
24788
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
24764
24789
|
const baseUrl = profile.baseURL || preset?.baseURLChat || preset?.baseURL || "Default";
|
|
24765
24790
|
console.log();
|
|
24766
24791
|
console.log(import_picocolors9.default.bold("Current active profile:"));
|
|
@@ -24844,10 +24869,13 @@ async function cmdSwitchInteractive2() {
|
|
|
24844
24869
|
const current = await getActiveProfileForCoder(CODER_NAME2);
|
|
24845
24870
|
const profileName = await ve({
|
|
24846
24871
|
message: "Select profile to switch to",
|
|
24847
|
-
options: profiles.map((profile) =>
|
|
24848
|
-
|
|
24849
|
-
|
|
24850
|
-
|
|
24872
|
+
options: await Promise.all(profiles.map(async (profile) => {
|
|
24873
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
24874
|
+
return {
|
|
24875
|
+
value: profile.name,
|
|
24876
|
+
label: profile.name,
|
|
24877
|
+
hint: profile.name === current?.name ? "(current)" : preset?.displayName || ""
|
|
24878
|
+
};
|
|
24851
24879
|
}))
|
|
24852
24880
|
});
|
|
24853
24881
|
if (pD(profileName)) {
|
|
@@ -24864,10 +24892,13 @@ async function cmdDeleteInteractive2() {
|
|
|
24864
24892
|
}
|
|
24865
24893
|
const profileName = await ve({
|
|
24866
24894
|
message: "Select profile to delete",
|
|
24867
|
-
options: profiles.map((profile) =>
|
|
24868
|
-
|
|
24869
|
-
|
|
24870
|
-
|
|
24895
|
+
options: await Promise.all(profiles.map(async (profile) => {
|
|
24896
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
24897
|
+
return {
|
|
24898
|
+
value: profile.name,
|
|
24899
|
+
label: profile.name,
|
|
24900
|
+
hint: preset?.displayName || ""
|
|
24901
|
+
};
|
|
24871
24902
|
}))
|
|
24872
24903
|
});
|
|
24873
24904
|
if (pD(profileName)) {
|
|
@@ -24908,7 +24939,7 @@ async function cmdRun2(args) {
|
|
|
24908
24939
|
process.exit(1);
|
|
24909
24940
|
}
|
|
24910
24941
|
}
|
|
24911
|
-
const preset =
|
|
24942
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
24912
24943
|
const baseURL = profile.baseURL || preset?.baseURLChat || preset?.baseURL || "";
|
|
24913
24944
|
const env = {};
|
|
24914
24945
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -25264,7 +25295,7 @@ async function cmdCreateQuiet3(params) {
|
|
|
25264
25295
|
console.log(import_picocolors10.default.dim(`Usage: swixter ${CODER_NAME3} create --quiet --name <name> --provider <id> [--api-key <key>] [--base-url <url>] [--model <model>] [--env-key <var>] [--apply]`));
|
|
25265
25296
|
process.exit(1);
|
|
25266
25297
|
}
|
|
25267
|
-
const preset =
|
|
25298
|
+
const preset = await getPresetByIdAsync(params.provider);
|
|
25268
25299
|
if (!preset) {
|
|
25269
25300
|
console.log(import_picocolors10.default.red(`Error: Unknown provider ID: ${params.provider}`));
|
|
25270
25301
|
console.log(import_picocolors10.default.dim("Run 'swixter providers' to see all supported providers"));
|
|
@@ -25337,7 +25368,7 @@ async function cmdList3() {
|
|
|
25337
25368
|
console.log(import_picocolors10.default.bold(LABELS.profileList));
|
|
25338
25369
|
console.log();
|
|
25339
25370
|
for (const profile of profiles) {
|
|
25340
|
-
const preset =
|
|
25371
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
25341
25372
|
const isCurrent = current?.name === profile.name;
|
|
25342
25373
|
const marker = isCurrent ? import_picocolors10.default.green(MARKERS.active) : import_picocolors10.default.dim(MARKERS.inactive);
|
|
25343
25374
|
const baseUrl = profile.baseURL || preset?.baseURLChat || preset?.baseURL || MISC_DEFAULTS.baseUrlFallback;
|
|
@@ -25359,7 +25390,11 @@ async function cmdSwitch3(profileName, args = []) {
|
|
|
25359
25390
|
try {
|
|
25360
25391
|
await setActiveProfileForCoder(CODER_NAME3, profileName);
|
|
25361
25392
|
const profile = await getActiveProfileForCoder(CODER_NAME3);
|
|
25362
|
-
|
|
25393
|
+
if (!profile) {
|
|
25394
|
+
console.log(import_picocolors10.default.red("Error: Profile not found after switch"));
|
|
25395
|
+
process.exit(1);
|
|
25396
|
+
}
|
|
25397
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
25363
25398
|
const baseUrl = profile.baseURL || preset?.baseURLChat || preset?.baseURL || "Default";
|
|
25364
25399
|
console.log();
|
|
25365
25400
|
console.log(import_picocolors10.default.green("\u2713") + " Switched successfully!");
|
|
@@ -25410,10 +25445,13 @@ async function cmdEdit3(profileName) {
|
|
|
25410
25445
|
}
|
|
25411
25446
|
const selected = await ve({
|
|
25412
25447
|
message: "Select profile to edit",
|
|
25413
|
-
options: profiles2.map((profile2) =>
|
|
25414
|
-
|
|
25415
|
-
|
|
25416
|
-
|
|
25448
|
+
options: await Promise.all(profiles2.map(async (profile2) => {
|
|
25449
|
+
const preset = await getPresetByIdAsync(profile2.providerId);
|
|
25450
|
+
return {
|
|
25451
|
+
value: profile2.name,
|
|
25452
|
+
label: profile2.name,
|
|
25453
|
+
hint: preset?.displayName || ""
|
|
25454
|
+
};
|
|
25417
25455
|
}))
|
|
25418
25456
|
});
|
|
25419
25457
|
if (pD(selected)) {
|
|
@@ -25434,7 +25472,7 @@ async function cmdEdit3(profileName) {
|
|
|
25434
25472
|
console.log();
|
|
25435
25473
|
const { getProvidersByWireApi: getProvidersByWireApi2 } = await Promise.resolve().then(() => (init_presets(), exports_presets));
|
|
25436
25474
|
const presets = await getProvidersByWireApi2("chat");
|
|
25437
|
-
const currentPreset =
|
|
25475
|
+
const currentPreset = await getPresetByIdAsync(profile.providerId);
|
|
25438
25476
|
const shouldChangeProvider = await ye({
|
|
25439
25477
|
message: `Change provider? Current: ${currentPreset?.displayName}`,
|
|
25440
25478
|
initialValue: false
|
|
@@ -25611,7 +25649,7 @@ async function cmdApply3() {
|
|
|
25611
25649
|
}
|
|
25612
25650
|
try {
|
|
25613
25651
|
const adapter = getAdapter(CODER_NAME3);
|
|
25614
|
-
const preset =
|
|
25652
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
25615
25653
|
console.log();
|
|
25616
25654
|
console.log(import_picocolors10.default.dim(`Applying profile to ${adapter.configPath}...`));
|
|
25617
25655
|
await adapter.apply(profile);
|
|
@@ -25654,7 +25692,7 @@ async function cmdCurrent3() {
|
|
|
25654
25692
|
console.log(import_picocolors10.default.yellow("No active profile"));
|
|
25655
25693
|
return;
|
|
25656
25694
|
}
|
|
25657
|
-
const preset =
|
|
25695
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
25658
25696
|
const baseUrl = profile.baseURL || preset?.baseURLChat || preset?.baseURL || "Default";
|
|
25659
25697
|
console.log();
|
|
25660
25698
|
console.log(import_picocolors10.default.bold("Current active profile:"));
|
|
@@ -25738,10 +25776,13 @@ async function cmdSwitchInteractive3() {
|
|
|
25738
25776
|
const current = await getActiveProfileForCoder(CODER_NAME3);
|
|
25739
25777
|
const profileName = await ve({
|
|
25740
25778
|
message: "Select profile to switch to",
|
|
25741
|
-
options: profiles.map((profile) =>
|
|
25742
|
-
|
|
25743
|
-
|
|
25744
|
-
|
|
25779
|
+
options: await Promise.all(profiles.map(async (profile) => {
|
|
25780
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
25781
|
+
return {
|
|
25782
|
+
value: profile.name,
|
|
25783
|
+
label: profile.name,
|
|
25784
|
+
hint: profile.name === current?.name ? "(current)" : preset?.displayName || ""
|
|
25785
|
+
};
|
|
25745
25786
|
}))
|
|
25746
25787
|
});
|
|
25747
25788
|
if (pD(profileName)) {
|
|
@@ -25758,10 +25799,13 @@ async function cmdDeleteInteractive3() {
|
|
|
25758
25799
|
}
|
|
25759
25800
|
const profileName = await ve({
|
|
25760
25801
|
message: "Select profile to delete",
|
|
25761
|
-
options: profiles.map((profile) =>
|
|
25762
|
-
|
|
25763
|
-
|
|
25764
|
-
|
|
25802
|
+
options: await Promise.all(profiles.map(async (profile) => {
|
|
25803
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
25804
|
+
return {
|
|
25805
|
+
value: profile.name,
|
|
25806
|
+
label: profile.name,
|
|
25807
|
+
hint: preset?.displayName || ""
|
|
25808
|
+
};
|
|
25765
25809
|
}))
|
|
25766
25810
|
});
|
|
25767
25811
|
if (pD(profileName)) {
|
|
@@ -25805,7 +25849,7 @@ async function cmdRun3(args) {
|
|
|
25805
25849
|
try {
|
|
25806
25850
|
const adapter = getAdapter(CODER_NAME3);
|
|
25807
25851
|
await adapter.apply(profile);
|
|
25808
|
-
const preset =
|
|
25852
|
+
const preset = await getPresetByIdAsync(profile.providerId);
|
|
25809
25853
|
const envKey = profile.envKey || preset?.env_key || "OPENAI_API_KEY";
|
|
25810
25854
|
const env = {};
|
|
25811
25855
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -25863,9 +25907,8 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
|
25863
25907
|
|
|
25864
25908
|
// src/server/index.ts
|
|
25865
25909
|
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
25866
|
-
import { dirname as dirname9, join as
|
|
25910
|
+
import { dirname as dirname9, join as join9 } from "path";
|
|
25867
25911
|
import { fileURLToPath } from "url";
|
|
25868
|
-
import { exec } from "child_process";
|
|
25869
25912
|
|
|
25870
25913
|
// src/server/middleware.ts
|
|
25871
25914
|
function corsMiddleware(req, res, next) {
|
|
@@ -26150,7 +26193,7 @@ class NodeResShim {
|
|
|
26150
26193
|
}
|
|
26151
26194
|
|
|
26152
26195
|
// src/server/bun-static.ts
|
|
26153
|
-
import { extname, join as
|
|
26196
|
+
import { extname, join as join5 } from "path";
|
|
26154
26197
|
import { stat } from "fs/promises";
|
|
26155
26198
|
var MIME_TYPES = {
|
|
26156
26199
|
".html": "text/html; charset=utf-8",
|
|
@@ -26173,11 +26216,11 @@ var MIME_TYPES = {
|
|
|
26173
26216
|
async function serveStaticRequest(request, options) {
|
|
26174
26217
|
const { root, index = "index.html", spa = true } = options;
|
|
26175
26218
|
const url2 = new URL(request.url);
|
|
26176
|
-
let filePath =
|
|
26219
|
+
let filePath = join5(root, url2.pathname);
|
|
26177
26220
|
try {
|
|
26178
26221
|
const stats = await stat(filePath);
|
|
26179
26222
|
if (stats.isDirectory()) {
|
|
26180
|
-
filePath =
|
|
26223
|
+
filePath = join5(filePath, index);
|
|
26181
26224
|
}
|
|
26182
26225
|
const file2 = Bun.file(filePath);
|
|
26183
26226
|
const exists = await file2.exists();
|
|
@@ -26190,7 +26233,7 @@ async function serveStaticRequest(request, options) {
|
|
|
26190
26233
|
});
|
|
26191
26234
|
} catch {
|
|
26192
26235
|
if (spa) {
|
|
26193
|
-
const indexPath =
|
|
26236
|
+
const indexPath = join5(root, index);
|
|
26194
26237
|
const file2 = Bun.file(indexPath);
|
|
26195
26238
|
const exists = await file2.exists();
|
|
26196
26239
|
if (exists) {
|
|
@@ -26640,7 +26683,7 @@ init_paths();
|
|
|
26640
26683
|
init_export();
|
|
26641
26684
|
import { existsSync as existsSync9, statSync as statSync2 } from "fs";
|
|
26642
26685
|
import { readFile as readFile7, writeFile as writeFile7, unlink } from "fs/promises";
|
|
26643
|
-
import { join as
|
|
26686
|
+
import { join as join8 } from "path";
|
|
26644
26687
|
async function getVersion(req, res) {
|
|
26645
26688
|
sendJson(res, {
|
|
26646
26689
|
appVersion: APP_VERSION,
|
|
@@ -26692,7 +26735,7 @@ async function exportConfigFile(req, res) {
|
|
|
26692
26735
|
const sanitize = url2.searchParams.get("sanitize") === "true";
|
|
26693
26736
|
const { exportConfig: exportConfig3 } = await Promise.resolve().then(() => (init_export(), exports_export));
|
|
26694
26737
|
const tempDir = getConfigDir("swixter");
|
|
26695
|
-
const tempPath =
|
|
26738
|
+
const tempPath = join8(tempDir, `.export-${Date.now()}.json`);
|
|
26696
26739
|
try {
|
|
26697
26740
|
await exportConfig3(tempPath, { sanitizeKeys: sanitize });
|
|
26698
26741
|
const content = await readFile7(tempPath, "utf-8");
|
|
@@ -26717,7 +26760,7 @@ async function importConfigFile(req, res) {
|
|
|
26717
26760
|
}
|
|
26718
26761
|
try {
|
|
26719
26762
|
const tempDir = getConfigDir("swixter");
|
|
26720
|
-
const tempPath =
|
|
26763
|
+
const tempPath = join8(tempDir, `.import-${Date.now()}.json`);
|
|
26721
26764
|
await writeFile7(tempPath, JSON.stringify(body.config), "utf-8");
|
|
26722
26765
|
try {
|
|
26723
26766
|
const result = await importConfig(tempPath, { overwrite: body.overwrite !== false });
|
|
@@ -26968,18 +27011,20 @@ async function findAvailablePort(startPort = 3141) {
|
|
|
26968
27011
|
}
|
|
26969
27012
|
function openBrowser(url2) {
|
|
26970
27013
|
const command = process.platform === "win32" ? "start" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
26971
|
-
|
|
26972
|
-
|
|
26973
|
-
|
|
26974
|
-
|
|
27014
|
+
import("child_process").then(({ execFile }) => {
|
|
27015
|
+
execFile(command, [url2], (error46) => {
|
|
27016
|
+
if (error46) {
|
|
27017
|
+
console.warn(import_picocolors11.default.yellow(`Could not open browser automatically: ${error46.message}`));
|
|
27018
|
+
}
|
|
27019
|
+
});
|
|
26975
27020
|
});
|
|
26976
27021
|
}
|
|
26977
27022
|
function getUiDir() {
|
|
26978
27023
|
const isDev = true;
|
|
26979
27024
|
if (isDev) {
|
|
26980
|
-
return
|
|
27025
|
+
return join9(__dirname2, "..", "..", "ui", "dist");
|
|
26981
27026
|
}
|
|
26982
|
-
return
|
|
27027
|
+
return join9(__dirname2, "..", "..", "ui");
|
|
26983
27028
|
}
|
|
26984
27029
|
async function startServer(portArg) {
|
|
26985
27030
|
const port = portArg || await findAvailablePort(3141);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "swixter",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "CLI tool for managing AI coding assistant configurations - easily switch between providers (Claude Code, Codex, Continue) with Anthropic, Ollama, or custom APIs",
|
|
5
5
|
"main": "dist/cli/index.js",
|
|
6
6
|
"module": "dist/cli/index.js",
|
|
@@ -14,13 +14,12 @@
|
|
|
14
14
|
"LICENSE"
|
|
15
15
|
],
|
|
16
16
|
"scripts": {
|
|
17
|
-
"build": "bun build src/cli/index.ts --outdir dist/cli --target bun --standalone",
|
|
18
|
-
"build:ui": "cd ui &&
|
|
19
|
-
"build:
|
|
20
|
-
"prepublishOnly": "bun run build:all && bun test",
|
|
17
|
+
"build": "bun run build:ui && bun build src/cli/index.ts --outdir dist/cli --target bun --standalone",
|
|
18
|
+
"build:ui": "cd ui && bun install && bun run build && mkdir -p ../dist/ui && cp -r dist/* ../dist/ui/",
|
|
19
|
+
"build:cli": "bun build src/cli/index.ts --outdir dist/cli --target bun --standalone",
|
|
21
20
|
"cli": "bun src/cli/index.ts",
|
|
22
21
|
"cli:dev": "bun --hot src/cli/index.ts",
|
|
23
|
-
"ui:dev": "cd ui &&
|
|
22
|
+
"ui:dev": "cd ui && bun run dev",
|
|
24
23
|
"test": "bun test",
|
|
25
24
|
"test:e2e": "bash test/e2e-docker.sh",
|
|
26
25
|
"preversion": "bun test",
|