@anvil-works/anvil-cli 0.5.8 → 0.5.10
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 +280 -367
- package/dist/cli.js +241 -85
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
# anvil-cli
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
### macOS/Linux
|
|
1
|
+
# anvil-cli
|
|
2
|
+
|
|
3
|
+
`anvil-cli` is the command-line tool for developing Anvil apps locally.
|
|
4
|
+
|
|
5
|
+
Use it to:
|
|
6
|
+
|
|
7
|
+
- check out an Anvil app into a local folder
|
|
8
|
+
- edit code in your own editor
|
|
9
|
+
- keep that local folder synced with Anvil while you work
|
|
10
|
+
- switch between local code editing and the Anvil designer in the same workflow
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
### macOS or Linux
|
|
17
15
|
|
|
18
16
|
```bash
|
|
19
17
|
curl -fsSL https://anvil.works/install-cli.sh | sh
|
|
@@ -31,433 +29,348 @@ irm https://anvil.works/install-cli.ps1 | iex
|
|
|
31
29
|
curl -fsSL https://anvil.works/install-cli.cmd -o install.cmd && install.cmd && del install.cmd
|
|
32
30
|
```
|
|
33
31
|
|
|
34
|
-
###
|
|
32
|
+
### npm fallback
|
|
35
33
|
|
|
36
34
|
```bash
|
|
37
35
|
npm install -g @anvil-works/anvil-cli@latest
|
|
38
36
|
```
|
|
39
|
-
|
|
40
|
-
## Quick Start
|
|
41
37
|
|
|
42
|
-
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
This is the main happy path:
|
|
41
|
+
|
|
42
|
+
1. Install `anvil-cli`.
|
|
43
|
+
2. Run `anvil configure`.
|
|
44
|
+
3. Run `anvil checkout`.
|
|
45
|
+
4. Change into the app directory.
|
|
46
|
+
5. Run `anvil watch`.
|
|
47
|
+
|
|
48
|
+
### 1. Configure the CLI
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
Run:
|
|
45
51
|
|
|
46
52
|
```bash
|
|
47
53
|
anvil configure
|
|
48
54
|
```
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
running a dedicated enterprise/on-prem Anvil installation.
|
|
52
|
-
`anvil configure` does not run checkout automatically; it ends by suggesting `anvil checkout`.
|
|
56
|
+
This guided setup helps you:
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
- set your default Anvil server URL
|
|
59
|
+
- choose a preferred editor, if you want one
|
|
60
|
+
- log in to Anvil
|
|
55
61
|
|
|
56
|
-
|
|
57
|
-
|
|
62
|
+
If you use `anvil.works`, you can usually accept the default server when prompted.
|
|
63
|
+
|
|
64
|
+
### 2. Check out your app
|
|
65
|
+
|
|
66
|
+
Run:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
anvil checkout
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This opens an interactive app picker and creates the app directory for you.
|
|
73
|
+
|
|
74
|
+
If you already have the app URL from the Anvil editor, you can also run:
|
|
58
75
|
|
|
59
76
|
```bash
|
|
60
77
|
anvil checkout https://anvil.works/build/apps/W36XUTXGNPDK6VEA my-app
|
|
61
78
|
cd my-app
|
|
62
79
|
```
|
|
63
80
|
|
|
64
|
-
|
|
81
|
+
You can also check out by app ID:
|
|
65
82
|
|
|
66
|
-
|
|
83
|
+
```bash
|
|
84
|
+
anvil checkout W36XUTXGNPDK6VEA my-app --url anvil.works
|
|
85
|
+
cd my-app
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 3. Start syncing
|
|
89
|
+
|
|
90
|
+
Inside the app directory, run:
|
|
67
91
|
|
|
68
92
|
```bash
|
|
69
93
|
anvil watch
|
|
70
94
|
```
|
|
71
95
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
│ Local Files │◄────►│ anvil-cli │◄────►│ Anvil Server │◄────►│ Anvil IDE │
|
|
91
|
-
│(your editor) │ │ (Node.js CLI)│ │(anvil.works) │ │ (Browser) │
|
|
92
|
-
└──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
The diagram shows bidirectional sync:
|
|
96
|
-
|
|
97
|
-
- **Local → Anvil**: Your local file changes are synced to Anvil via API calls through anvil-cli
|
|
98
|
-
- **Anvil → Local**: Changes made in the Anvil IDE are automatically detected and synced back to your local files via git fetch through anvil-cli
|
|
99
|
-
|
|
100
|
-
### CLI Commands
|
|
101
|
-
|
|
102
|
-
| Command | Description |
|
|
103
|
-
| -------------------------------------- | -------------------------------------------------------------------- |
|
|
104
|
-
| `anvil watch [path]` | Watch directory for changes and sync to Anvil |
|
|
105
|
-
| `anvil watch [path] -O` | Open watched path in preferred editor/default app |
|
|
106
|
-
| `anvil watch -V` or `--verbose` | Watch with verbose logging (detailed output) |
|
|
107
|
-
| `anvil w [path]` | Short form for watch |
|
|
108
|
-
| `anvil configure` | Guided setup for default URL and login |
|
|
109
|
-
| `anvil checkout [input] [directory]` | Check out an app by URL/app ID, or interactively with no input |
|
|
110
|
-
| `anvil login [anvil-server-url]` | Authenticate with Anvil using OAuth (supports smart URL handling) |
|
|
111
|
-
| `anvil l [anvil-server-url]` | Short form for login |
|
|
112
|
-
| `anvil logout [anvil-server-url]` | Logout from Anvil (optionally specify URL for specific installation) |
|
|
113
|
-
| `anvil config <action> [key] [value]` | Manage configuration (set, get, list) |
|
|
114
|
-
| `anvil c <action> [key] [value]` | Short form for config |
|
|
115
|
-
| `anvil version` | Show version information |
|
|
116
|
-
| `anvil -h, --help` | Show help message |
|
|
117
|
-
| `anvil -v, --version` | Show version number |
|
|
118
|
-
|
|
119
|
-
### App ID Detection
|
|
120
|
-
|
|
121
|
-
anvil-cli can auto-detect your app ID using multiple strategies:
|
|
122
|
-
|
|
123
|
-
1. **Git Remotes**: Checks for Anvil git remotes in the repository
|
|
124
|
-
|
|
125
|
-
2. **Commit Lookup**: Queries Anvil API with current commit ID and branch to find matching app
|
|
126
|
-
|
|
127
|
-
If auto-detection fails, you can specify the app ID explicitly:
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
anvil watch -A YOUR_APP_ID
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### Anvil URL Detection
|
|
134
|
-
|
|
135
|
-
anvil-cli automatically detects which Anvil server to use:
|
|
136
|
-
|
|
137
|
-
1. **Explicit URL**: If you specify `--url` or `-u` flag
|
|
138
|
-
2. **Git Remotes**: Automatically extracts URL from git remotes in your repository
|
|
139
|
-
3. **Logged-in URLs**:
|
|
140
|
-
- If one logged-in URL is available, uses it automatically
|
|
141
|
-
- If multiple logged-in URLs are available, prompts you to select one
|
|
142
|
-
4. **Global Config**: Falls back to `anvilUrl` in config file
|
|
143
|
-
5. **Default**: Uses `https://anvil.works` (or `http://localhost:3000` in dev mode)
|
|
144
|
-
|
|
145
|
-
**Examples:**
|
|
146
|
-
|
|
147
|
-
```bash
|
|
148
|
-
# Auto-detect from git remote
|
|
149
|
-
anvil watch
|
|
150
|
-
|
|
151
|
-
# Specify URL explicitly
|
|
152
|
-
anvil watch --url anvil.works
|
|
153
|
-
anvil watch --url localhost:3000
|
|
154
|
-
anvil watch --url anvil.mycompany.com
|
|
155
|
-
|
|
156
|
-
# If multiple logged-in URLs, you'll be prompted to select
|
|
157
|
-
anvil watch
|
|
158
|
-
# ? Multiple Anvil installations found. Which one would you like to use?
|
|
159
|
-
# ❯ https://anvil.works
|
|
160
|
-
# https://anvil.company.com
|
|
161
|
-
# Cancel
|
|
96
|
+
Leave this running while you work.
|
|
97
|
+
|
|
98
|
+
With `anvil watch` running:
|
|
99
|
+
|
|
100
|
+
- local code changes sync into Anvil
|
|
101
|
+
- compatible changes made in Anvil sync back into your local files
|
|
102
|
+
- you can keep using the Anvil designer when you need it
|
|
103
|
+
|
|
104
|
+
## Core Commands
|
|
105
|
+
|
|
106
|
+
These are the commands that matter most for normal local development:
|
|
107
|
+
|
|
108
|
+
### `anvil configure`
|
|
109
|
+
|
|
110
|
+
Guided setup for your default server URL, editor preference, and login.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
anvil configure
|
|
162
114
|
```
|
|
163
115
|
|
|
164
|
-
###
|
|
116
|
+
### `anvil checkout`
|
|
117
|
+
|
|
118
|
+
Create a local app folder from an editor URL, Git URL, app ID, or interactive picker.
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
anvil checkout [input] [directory]
|
|
122
|
+
anvil co [input] [directory]
|
|
123
|
+
```
|
|
165
124
|
|
|
166
|
-
|
|
125
|
+
Common examples:
|
|
167
126
|
|
|
168
127
|
```bash
|
|
169
|
-
# Interactive search/select (ordered by most recently edited)
|
|
170
128
|
anvil checkout
|
|
129
|
+
anvil checkout https://anvil.works/build/apps/W36XUTXGNPDK6VEA my-app
|
|
130
|
+
anvil checkout W36XUTXGNPDK6VEA my-app --url anvil.works
|
|
131
|
+
anvil co -Q "dashboard"
|
|
171
132
|
```
|
|
172
133
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
134
|
+
Useful options:
|
|
135
|
+
|
|
136
|
+
- `-O, --open` open the app folder after checkout
|
|
137
|
+
- `-b, --branch <BRANCH>` check out a specific branch
|
|
138
|
+
- `-Q, --query <QUERY>` prefill the interactive search
|
|
139
|
+
- `-u, --url <ANVIL_URL>` choose the Anvil installation explicitly
|
|
140
|
+
- `-U, --user <USERNAME>` choose the logged-in account explicitly
|
|
141
|
+
- `-f, --force` override destination safety checks
|
|
177
142
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
143
|
+
### `anvil watch`
|
|
144
|
+
|
|
145
|
+
Keep your local app folder synced with Anvil while you work.
|
|
181
146
|
|
|
182
147
|
```bash
|
|
183
|
-
|
|
184
|
-
anvil
|
|
148
|
+
anvil watch [path]
|
|
149
|
+
anvil w [path]
|
|
150
|
+
```
|
|
185
151
|
|
|
186
|
-
|
|
187
|
-
anvil checkout https://anvil.works/git/MHVELZG5SZXK2POE.git
|
|
152
|
+
Common examples:
|
|
188
153
|
|
|
189
|
-
|
|
190
|
-
anvil
|
|
154
|
+
```bash
|
|
155
|
+
anvil watch
|
|
156
|
+
anvil watch -s
|
|
157
|
+
anvil watch -a
|
|
158
|
+
anvil watch -O
|
|
159
|
+
anvil watch --url anvil.company.com
|
|
160
|
+
```
|
|
191
161
|
|
|
192
|
-
|
|
193
|
-
anvil checkout MHVELZG5SZXK2POE my-local-app --url anvil.works
|
|
162
|
+
Useful options:
|
|
194
163
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
164
|
+
- `-A, --appid <APP_ID>` specify the app directly
|
|
165
|
+
- `-s, --staged-only` sync only staged files
|
|
166
|
+
- `-a, --auto` handle some branch and sync transitions automatically
|
|
167
|
+
- `-O, --open` open the watched path in your preferred editor
|
|
168
|
+
- `-u, --url <ANVIL_URL>` choose the Anvil installation explicitly
|
|
169
|
+
- `-U, --user <USERNAME>` choose the logged-in account explicitly
|
|
199
170
|
|
|
200
|
-
|
|
201
|
-
anvil checkout -Q "dashboard"
|
|
202
|
-
anvil checkout --query "dashboard"
|
|
171
|
+
### Other useful commands
|
|
203
172
|
|
|
204
|
-
|
|
205
|
-
anvil
|
|
206
|
-
anvil
|
|
173
|
+
```bash
|
|
174
|
+
anvil login
|
|
175
|
+
anvil logout
|
|
176
|
+
anvil config list
|
|
177
|
+
anvil config get <key>
|
|
178
|
+
anvil config set <key> <value>
|
|
179
|
+
anvil config reset
|
|
180
|
+
anvil version
|
|
181
|
+
anvil update
|
|
182
|
+
anvil --help
|
|
183
|
+
anvil <command> --help
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Global options:
|
|
187
|
+
|
|
188
|
+
- `-V, --verbose` show detailed output
|
|
189
|
+
- `--json` output NDJSON for scripting or LLM tooling
|
|
190
|
+
|
|
191
|
+
## Recommended Workflows
|
|
192
|
+
|
|
193
|
+
### Work between your editor and the Anvil designer
|
|
194
|
+
|
|
195
|
+
The basic loop is:
|
|
196
|
+
|
|
197
|
+
1. Run `anvil watch` in your app folder.
|
|
198
|
+
2. Edit Python locally in your editor.
|
|
199
|
+
3. Open the same app in Anvil when you need the designer.
|
|
200
|
+
4. Keep `anvil watch` running while you move between them.
|
|
201
|
+
|
|
202
|
+
### Use branches by default
|
|
203
|
+
|
|
204
|
+
Create or switch to a branch before you start watching:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
git checkout -b my-feature
|
|
208
|
+
anvil watch
|
|
207
209
|
```
|
|
208
210
|
|
|
209
|
-
|
|
211
|
+
If you switch branches while watching, `anvil watch` may prompt you to restart or resync. If you want the CLI to handle that automatically, use:
|
|
210
212
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
+
```bash
|
|
214
|
+
anvil watch --auto
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Use staged-only mode when experimenting
|
|
218
|
+
|
|
219
|
+
If you want tighter control over what syncs to Anvil, use:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
anvil watch --staged-only
|
|
223
|
+
git add server_code/MyModule.py
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Only staged files will sync.
|
|
213
227
|
|
|
214
|
-
|
|
228
|
+
### Work with AI assistants
|
|
215
229
|
|
|
216
|
-
|
|
217
|
-
- The helper retrieves short-lived access tokens from your logged-in account and refreshes them as needed.
|
|
218
|
-
- Per-app bindings are stored in local git config (`anvil.auth.<APPID>.url` and `anvil.auth.<APPID>.username`) so multi-account repos stay deterministic.
|
|
230
|
+
A simple pattern is:
|
|
219
231
|
|
|
220
|
-
|
|
232
|
+
1. Start `anvil watch`.
|
|
233
|
+
2. Open the app folder in Cursor, VS Code, or another editor.
|
|
234
|
+
3. Make or review AI-generated changes locally.
|
|
235
|
+
4. Test the synced result in Anvil.
|
|
221
236
|
|
|
222
|
-
|
|
223
|
-
- `-b, --branch <BRANCH>` - Checkout a specific branch
|
|
224
|
-
- `--depth <N>` - Shallow clone with `N` commits of history
|
|
225
|
-
- `--single-branch` - Clone only one branch
|
|
226
|
-
- `--origin <NAME>` - Use custom remote name instead of `origin`
|
|
227
|
-
- `--quiet` - Suppress git clone progress output
|
|
228
|
-
- `--verbose` - Verbose git clone output
|
|
229
|
-
- `-f, --force` - Override destination safety checks
|
|
230
|
-
- `-u, --url <ANVIL_URL>` - Override detected Anvil URL
|
|
231
|
-
- `-U, --user <USERNAME>` - Use a specific logged-in account
|
|
237
|
+
If you are iterating on messy changes, combine this with `--staged-only`.
|
|
232
238
|
|
|
233
|
-
|
|
234
|
-
prompt you to select one (interactive mode) or require `--user` in
|
|
235
|
-
non-interactive mode.
|
|
239
|
+
## Multiple Accounts and Enterprise Installs
|
|
236
240
|
|
|
237
|
-
|
|
241
|
+
Most users on `anvil.works` do not need to think about this.
|
|
238
242
|
|
|
239
|
-
|
|
243
|
+
If you work across multiple Anvil installations or multiple logged-in accounts, the CLI can usually infer the right choice. If it cannot, you can override it with:
|
|
240
244
|
|
|
241
|
-
|
|
245
|
+
- `--url` to choose the Anvil installation
|
|
246
|
+
- `--user` to choose the logged-in account
|
|
247
|
+
|
|
248
|
+
Examples:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
anvil login anvil.company.com
|
|
252
|
+
anvil checkout APPID my-app --url anvil.company.com
|
|
253
|
+
anvil watch --url anvil.company.com --user user@example.com
|
|
254
|
+
anvil logout --url anvil.company.com --user user@example.com
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
If you want to change the default server permanently:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
anvil config set anvilUrl https://anvil.company.com
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Troubleshooting
|
|
264
|
+
|
|
265
|
+
### `anvil` command not found
|
|
266
|
+
|
|
267
|
+
Try the install command again, then open a new terminal and run:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
anvil --help
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
If the install script fails, use the npm fallback.
|
|
274
|
+
|
|
275
|
+
### Login or account selection problems
|
|
276
|
+
|
|
277
|
+
Try:
|
|
242
278
|
|
|
243
279
|
```bash
|
|
244
280
|
anvil login
|
|
281
|
+
anvil config list
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
If you use a self-hosted installation:
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
anvil login anvil.company.com
|
|
245
288
|
```
|
|
246
289
|
|
|
247
|
-
|
|
290
|
+
### `anvil checkout` cannot find the app
|
|
248
291
|
|
|
249
|
-
|
|
250
|
-
2. Prompt you to authorize anvil-cli
|
|
251
|
-
3. Complete authentication automatically in the CLI
|
|
292
|
+
Try using the app URL directly from the Anvil editor, or pass `--url` and `--user` if the CLI is looking at the wrong installation or account.
|
|
252
293
|
|
|
253
|
-
|
|
254
|
-
- **macOS**: `~/Library/Preferences/anvil-cli/config.json`
|
|
255
|
-
- **Linux**: `~/.config/anvil-cli/config.json`
|
|
256
|
-
- **Windows**: `%APPDATA%\anvil-cli\Config\config.json`
|
|
294
|
+
### Local changes are not appearing in Anvil
|
|
257
295
|
|
|
258
|
-
|
|
296
|
+
Make sure you are in the correct app directory and restart:
|
|
259
297
|
|
|
260
298
|
```bash
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
299
|
+
anvil watch
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
If you are using `--staged-only`, remember that only staged files sync.
|
|
303
|
+
|
|
304
|
+
### Changes from Anvil are not appearing locally
|
|
264
305
|
|
|
265
|
-
|
|
266
|
-
anvil login localhost:3000
|
|
306
|
+
Make sure `anvil watch` is still running. If the watch session looks unhealthy or branch state changed underneath it, stop it and start it again from the correct branch and app folder.
|
|
267
307
|
|
|
268
|
-
|
|
269
|
-
|
|
308
|
+
### The CLI is using the wrong server
|
|
309
|
+
|
|
310
|
+
Check your current config:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
anvil config list
|
|
270
314
|
```
|
|
271
315
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
- **macOS**: `~/Library/Preferences/anvil-cli/config.json`
|
|
278
|
-
- **Linux**: `~/.config/anvil-cli/config.json`
|
|
279
|
-
- **Windows**: `%APPDATA%\anvil-cli\Config\config.json`
|
|
280
|
-
|
|
281
|
-
### Stored Configuration
|
|
282
|
-
|
|
283
|
-
`config.json` stores settings and auth metadata. OAuth secrets are stored in the
|
|
284
|
-
OS keychain when available.
|
|
285
|
-
|
|
286
|
-
```json
|
|
287
|
-
{
|
|
288
|
-
"anvilUrl": "https://anvil.works",
|
|
289
|
-
"verbose": false,
|
|
290
|
-
"preferredEditor": "cursor",
|
|
291
|
-
"authTokens": {
|
|
292
|
-
"https://anvil.works": {
|
|
293
|
-
"user@example.com": {
|
|
294
|
-
"authToken": null,
|
|
295
|
-
"refreshToken": null,
|
|
296
|
-
"authTokenExpiresAt": 1735689600
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
316
|
+
Set the default explicitly if needed:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
anvil config set anvilUrl https://anvil.company.com
|
|
301
320
|
```
|
|
302
321
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
322
|
+
## Config File
|
|
323
|
+
|
|
324
|
+
`anvil-cli` stores local settings in `config.json`.
|
|
325
|
+
|
|
326
|
+
Common locations:
|
|
327
|
+
|
|
328
|
+
- macOS: `~/Library/Preferences/anvil-cli/config.json`
|
|
329
|
+
- Linux: `~/.config/anvil-cli/config.json`
|
|
330
|
+
- Windows: `%APPDATA%\\anvil-cli\\Config\\config.json`
|
|
331
|
+
|
|
332
|
+
This includes settings such as:
|
|
333
|
+
|
|
334
|
+
- `anvilUrl`
|
|
335
|
+
- `preferredEditor`
|
|
336
|
+
- `verbose`
|
|
337
|
+
|
|
338
|
+
Authentication secrets are stored in the OS keychain when available.
|
|
339
|
+
|
|
340
|
+
## Developing `anvil-cli`
|
|
341
|
+
|
|
342
|
+
Build the CLI:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
pnpm build
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
Run in watch mode:
|
|
349
|
+
|
|
350
|
+
```bash
|
|
351
|
+
pnpm dev
|
|
320
352
|
```
|
|
321
353
|
|
|
322
|
-
|
|
354
|
+
Type-check:
|
|
323
355
|
|
|
324
|
-
|
|
356
|
+
```bash
|
|
357
|
+
pnpm tsc --noEmit
|
|
358
|
+
```
|
|
325
359
|
|
|
326
|
-
|
|
360
|
+
Run tests:
|
|
327
361
|
|
|
328
362
|
```bash
|
|
329
|
-
|
|
363
|
+
source tests/setup-test-env.sh
|
|
364
|
+
pnpm test
|
|
330
365
|
```
|
|
331
366
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
### Enterprise Configuration
|
|
338
|
-
|
|
339
|
-
If you're using an Anvil server other than `anvil.works` (e.g., enterprise/on-premise installation), you can work with multiple installations:
|
|
340
|
-
|
|
341
|
-
**Option 1: Login with URL** (recommended)
|
|
342
|
-
|
|
343
|
-
```bash
|
|
344
|
-
# Login to your enterprise installation
|
|
345
|
-
anvil login anvil.mycompany.com
|
|
346
|
-
|
|
347
|
-
# Or with full URL
|
|
348
|
-
anvil login https://anvil.mycompany.com
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
**Option 2: Specify URL when syncing**
|
|
352
|
-
|
|
353
|
-
```bash
|
|
354
|
-
# Sync with specific URL
|
|
355
|
-
anvil watch --url anvil.mycompany.com
|
|
356
|
-
```
|
|
357
|
-
|
|
358
|
-
**Option 3: Set global default**
|
|
359
|
-
|
|
360
|
-
```bash
|
|
361
|
-
anvil config set anvilUrl https://anvil.mycompany.com
|
|
362
|
-
anvil login
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
**Multiple Installations:**
|
|
366
|
-
|
|
367
|
-
anvil-cli supports multiple Anvil installations simultaneously. Each installation has its own authentication tokens:
|
|
368
|
-
|
|
369
|
-
```bash
|
|
370
|
-
# Login to multiple installations
|
|
371
|
-
anvil login anvil.works
|
|
372
|
-
anvil login anvil.company-a.com
|
|
373
|
-
anvil login anvil.company-b.com
|
|
374
|
-
|
|
375
|
-
# If URL is not resolved from git remotes and multiple logged-in URLs exist,
|
|
376
|
-
# you'll be prompted to select which one to use
|
|
377
|
-
# Or specify explicitly:
|
|
378
|
-
anvil watch --url anvil.company-a.com
|
|
367
|
+
Run one test file:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
source tests/setup-test-env.sh
|
|
371
|
+
pnpm rstest tests/some.test.ts
|
|
379
372
|
```
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
1. Explicit `--url` flag
|
|
384
|
-
2. Git remote detection (from current repository)
|
|
385
|
-
3. Logged-in URL selection:
|
|
386
|
-
- One URL: auto-select
|
|
387
|
-
- Multiple URLs: prompt to select
|
|
388
|
-
4. `anvilUrl` in config file
|
|
389
|
-
5. Default based on `NODE_ENV`:
|
|
390
|
-
- Production: `https://anvil.works`
|
|
391
|
-
- Development: `http://localhost:3000`
|
|
392
|
-
|
|
393
|
-
View all configuration:
|
|
394
|
-
|
|
395
|
-
```bash
|
|
396
|
-
anvil config list
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
### Logout
|
|
400
|
-
|
|
401
|
-
Logout from Anvil installations:
|
|
402
|
-
|
|
403
|
-
**Logout from all accounts:**
|
|
404
|
-
|
|
405
|
-
```bash
|
|
406
|
-
anvil logout
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
**Logout from specific installation:**
|
|
410
|
-
|
|
411
|
-
```bash
|
|
412
|
-
anvil logout anvil.works
|
|
413
|
-
anvil logout anvil.mycompany.com
|
|
414
|
-
anvil logout --url localhost:3000
|
|
415
|
-
```
|
|
416
|
-
|
|
417
|
-
**Without URL:** `anvil logout` uses total logged-in account count across all URLs:
|
|
418
|
-
|
|
419
|
-
- If you have **one account total**, it logs out immediately
|
|
420
|
-
- If you have **multiple accounts total**, you'll be prompted to:
|
|
421
|
-
|
|
422
|
-
- Logout from one account (then select which one)
|
|
423
|
-
- Logout from all accounts
|
|
424
|
-
- Cancel
|
|
425
|
-
|
|
426
|
-
## Troubleshooting
|
|
427
|
-
|
|
428
|
-
### "No app ID found"
|
|
429
|
-
|
|
430
|
-
- Ensure you're in an Anvil app directory with `anvil.yaml`
|
|
431
|
-
- Check git remotes: `git remote -v`
|
|
432
|
-
- Specify app ID explicitly: `anvil watch -A YOUR_APP_ID`
|
|
433
|
-
- Use `-f, --first` to auto-select the first detected app ID: `anvil watch -f`
|
|
434
|
-
|
|
435
|
-
### "Authentication failed" or "Not logged in to [URL]"
|
|
436
|
-
|
|
437
|
-
- Run `anvil login [url]` to authenticate with the specific Anvil installation
|
|
438
|
-
- If you see "Not logged in to [URL]", you need to login to that specific URL:
|
|
439
|
-
|
|
440
|
-
```bash
|
|
441
|
-
anvil login https://anvil.mycompany.com
|
|
442
|
-
```
|
|
443
|
-
|
|
444
|
-
- Your access token may have expired and refresh failed - re-login to get new tokens
|
|
445
|
-
- For multiple installations, make sure you're logged in to the correct one
|
|
446
|
-
|
|
447
|
-
### Switch back to hosted Anvil
|
|
448
|
-
|
|
449
|
-
- Reset the server URL to the default cloud host:
|
|
450
|
-
|
|
451
|
-
```bash
|
|
452
|
-
anvil config set anvilUrl https://anvil.works
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
- Or reset all config values (also clears custom URLs and logs out all accounts):
|
|
456
|
-
|
|
457
|
-
```bash
|
|
458
|
-
anvil config reset
|
|
459
|
-
```
|
|
460
|
-
|
|
461
|
-
## License
|
|
462
|
-
|
|
373
|
+
|
|
374
|
+
## License
|
|
375
|
+
|
|
463
376
|
MIT
|
package/dist/cli.js
CHANGED
|
@@ -42538,6 +42538,13 @@ var __webpack_exports__ = {};
|
|
|
42538
42538
|
function getConfig(key) {
|
|
42539
42539
|
return config_config.get(key);
|
|
42540
42540
|
}
|
|
42541
|
+
function deleteConfig(key) {
|
|
42542
|
+
if (key in configDefaults) config_config.set(key, configDefaults[key]);
|
|
42543
|
+
else {
|
|
42544
|
+
const store = config_config.store;
|
|
42545
|
+
if (store && "object" == typeof store && key in store) config_config.delete(key);
|
|
42546
|
+
}
|
|
42547
|
+
}
|
|
42541
42548
|
function resetConfig() {
|
|
42542
42549
|
for (const [key, value] of Object.entries(configDefaults))config_config.set(key, value);
|
|
42543
42550
|
const currentStore = config_config.store;
|
|
@@ -42670,7 +42677,7 @@ var __webpack_exports__ = {};
|
|
|
42670
42677
|
...config
|
|
42671
42678
|
};
|
|
42672
42679
|
}
|
|
42673
|
-
function
|
|
42680
|
+
function getGlobalOutputConfig() {
|
|
42674
42681
|
return globalOutputConfig;
|
|
42675
42682
|
}
|
|
42676
42683
|
const logger_timestamp = ()=>new Date().toISOString();
|
|
@@ -42691,10 +42698,20 @@ var __webpack_exports__ = {};
|
|
|
42691
42698
|
function writeJson(entry) {
|
|
42692
42699
|
console.log(JSON.stringify(entry));
|
|
42693
42700
|
}
|
|
42701
|
+
function logJsonResult(success, options) {
|
|
42702
|
+
if (getGlobalOutputConfig().jsonMode) writeJson({
|
|
42703
|
+
type: "result",
|
|
42704
|
+
success,
|
|
42705
|
+
message: options?.message,
|
|
42706
|
+
data: options?.data,
|
|
42707
|
+
error: options?.error,
|
|
42708
|
+
timestamp: logger_timestamp()
|
|
42709
|
+
});
|
|
42710
|
+
}
|
|
42694
42711
|
class DefaultLogger {
|
|
42695
42712
|
paused = false;
|
|
42696
42713
|
get jsonMode() {
|
|
42697
|
-
return
|
|
42714
|
+
return getGlobalOutputConfig().jsonMode;
|
|
42698
42715
|
}
|
|
42699
42716
|
log(...args) {
|
|
42700
42717
|
if (this.paused) return;
|
|
@@ -42796,7 +42813,7 @@ var __webpack_exports__ = {};
|
|
|
42796
42813
|
this.sessionVerbose = options.verbose ?? false;
|
|
42797
42814
|
}
|
|
42798
42815
|
get jsonMode() {
|
|
42799
|
-
return
|
|
42816
|
+
return getGlobalOutputConfig().jsonMode;
|
|
42800
42817
|
}
|
|
42801
42818
|
isVerbose() {
|
|
42802
42819
|
return this.sessionVerbose || getConfig("verbose");
|
|
@@ -50593,7 +50610,7 @@ var __webpack_exports__ = {};
|
|
|
50593
50610
|
const execFile = (0, external_node_util_namespaceObject.promisify)(external_node_child_process_.execFile);
|
|
50594
50611
|
const open_dirname = external_node_path_.dirname((0, external_node_url_namespaceObject.fileURLToPath)(__rslib_import_meta_url__));
|
|
50595
50612
|
const localXdgOpenPath = external_node_path_.join(open_dirname, 'xdg-open');
|
|
50596
|
-
const { platform, arch } = external_node_process_;
|
|
50613
|
+
const { platform: open_platform, arch } = external_node_process_;
|
|
50597
50614
|
async function getWindowsDefaultBrowserFromWsl() {
|
|
50598
50615
|
const powershellPath = await powerShellPath();
|
|
50599
50616
|
const rawCommand = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
|
|
@@ -50686,13 +50703,13 @@ var __webpack_exports__ = {};
|
|
|
50686
50703
|
let command;
|
|
50687
50704
|
const cliArguments = [];
|
|
50688
50705
|
const childProcessOptions = {};
|
|
50689
|
-
if ('darwin' ===
|
|
50706
|
+
if ('darwin' === open_platform) {
|
|
50690
50707
|
command = 'open';
|
|
50691
50708
|
if (options.wait) cliArguments.push('--wait-apps');
|
|
50692
50709
|
if (options.background) cliArguments.push('--background');
|
|
50693
50710
|
if (options.newInstance) cliArguments.push('--new');
|
|
50694
50711
|
if (app) cliArguments.push('-a', app);
|
|
50695
|
-
} else if ('win32' !==
|
|
50712
|
+
} else if ('win32' !== open_platform && (!is_wsl || isInsideContainer() || app)) {
|
|
50696
50713
|
if (app) command = app;
|
|
50697
50714
|
else {
|
|
50698
50715
|
const isBundled = !open_dirname || '/' === open_dirname;
|
|
@@ -50701,7 +50718,7 @@ var __webpack_exports__ = {};
|
|
|
50701
50718
|
await external_node_fs_promises_namespaceObject.access(localXdgOpenPath, external_node_fs_promises_namespaceObject.constants.X_OK);
|
|
50702
50719
|
exeLocalXdgOpen = true;
|
|
50703
50720
|
} catch {}
|
|
50704
|
-
const useSystemXdgOpen = external_node_process_.versions.electron ?? ('android' ===
|
|
50721
|
+
const useSystemXdgOpen = external_node_process_.versions.electron ?? ('android' === open_platform || isBundled || !exeLocalXdgOpen);
|
|
50705
50722
|
command = useSystemXdgOpen ? 'xdg-open' : localXdgOpenPath;
|
|
50706
50723
|
}
|
|
50707
50724
|
if (appArguments.length > 0) cliArguments.push(...appArguments);
|
|
@@ -50727,7 +50744,7 @@ var __webpack_exports__ = {};
|
|
|
50727
50744
|
}
|
|
50728
50745
|
options.target = external_node_buffer_namespaceObject.Buffer.from(encodedArguments.join(' '), 'utf16le').toString('base64');
|
|
50729
50746
|
}
|
|
50730
|
-
if ('darwin' ===
|
|
50747
|
+
if ('darwin' === open_platform && appArguments.length > 0) cliArguments.push('--args', ...appArguments);
|
|
50731
50748
|
if (options.target) cliArguments.push(options.target);
|
|
50732
50749
|
const subprocess = external_node_child_process_.spawn(command, cliArguments, childProcessOptions);
|
|
50733
50750
|
if (options.wait) return new Promise((resolve, reject)=>{
|
|
@@ -50753,9 +50770,9 @@ var __webpack_exports__ = {};
|
|
|
50753
50770
|
if (!archBinary) throw new Error(`${arch} is not supported`);
|
|
50754
50771
|
return archBinary;
|
|
50755
50772
|
}
|
|
50756
|
-
function detectPlatformBinary({ [
|
|
50773
|
+
function detectPlatformBinary({ [open_platform]: platformBinary }, { wsl }) {
|
|
50757
50774
|
if (wsl && is_wsl) return detectArchBinary(wsl);
|
|
50758
|
-
if (!platformBinary) throw new Error(`${
|
|
50775
|
+
if (!platformBinary) throw new Error(`${open_platform} is not supported`);
|
|
50759
50776
|
return detectArchBinary(platformBinary);
|
|
50760
50777
|
}
|
|
50761
50778
|
const open_apps = {};
|
|
@@ -50882,6 +50899,11 @@ var __webpack_exports__ = {};
|
|
|
50882
50899
|
if ("win32" === process.platform) return tokens.map(quoteForCmdShell).join(" ");
|
|
50883
50900
|
return tokens.map(quoteForPosixShell).join(" ");
|
|
50884
50901
|
}
|
|
50902
|
+
function formatFallbackEditorLabel(preferredEditorCommand) {
|
|
50903
|
+
const trimmed = preferredEditorCommand.trim();
|
|
50904
|
+
if (!trimmed) return "your editor";
|
|
50905
|
+
return trimmed;
|
|
50906
|
+
}
|
|
50885
50907
|
async function openPathInEditorOrDefault(targetPath, preferredEditorCommand, deps = defaultDeps) {
|
|
50886
50908
|
if (preferredEditorCommand) if (deps.isCommandAvailable(preferredEditorCommand)) try {
|
|
50887
50909
|
const tokens = parseCommandTokens(preferredEditorCommand);
|
|
@@ -50892,9 +50914,9 @@ var __webpack_exports__ = {};
|
|
|
50892
50914
|
]));
|
|
50893
50915
|
return;
|
|
50894
50916
|
} catch (error) {
|
|
50895
|
-
logger_logger.warn(`Failed to open
|
|
50917
|
+
logger_logger.warn(`Failed to open ${formatFallbackEditorLabel(preferredEditorCommand)} automatically: ${errors_getErrorMessage(error)}.`);
|
|
50896
50918
|
}
|
|
50897
|
-
else logger_logger.
|
|
50919
|
+
else logger_logger.info(`Open ${formatFallbackEditorLabel(preferredEditorCommand)} to edit your app.`);
|
|
50898
50920
|
await deps.openSystem(targetPath);
|
|
50899
50921
|
}
|
|
50900
50922
|
function decideFallbackUrl(explicitUrl, availableUrls = getAvailableAnvilUrls()) {
|
|
@@ -51613,7 +51635,7 @@ var __webpack_exports__ = {};
|
|
|
51613
51635
|
if (username) logger_logger.error(`Not logged in to ${anvilUrl} as ${username}`);
|
|
51614
51636
|
else logger_logger.error(`Not logged in to ${anvilUrl}`);
|
|
51615
51637
|
logger_logger.verbose(chalk_source.yellow("Please log in first:"));
|
|
51616
|
-
|
|
51638
|
+
logger_logger.info(chalk_source.cyan(` anvil login ${anvilUrl.replace(/^https?:\/\//, "")}`));
|
|
51617
51639
|
process.exit(1);
|
|
51618
51640
|
}
|
|
51619
51641
|
logger_logger.verbose(chalk_source.green("✓ Authentication tokens found"));
|
|
@@ -52803,6 +52825,54 @@ var __webpack_exports__ = {};
|
|
|
52803
52825
|
await deps.logout();
|
|
52804
52826
|
deps.resetConfig();
|
|
52805
52827
|
}
|
|
52828
|
+
function performConfigDelete(key) {
|
|
52829
|
+
const allConfig = getAllConfig();
|
|
52830
|
+
const existed = Object.prototype.hasOwnProperty.call(allConfig, key);
|
|
52831
|
+
deleteConfig(key);
|
|
52832
|
+
return {
|
|
52833
|
+
existed
|
|
52834
|
+
};
|
|
52835
|
+
}
|
|
52836
|
+
function getConfigListData() {
|
|
52837
|
+
const allConfig = getAllConfig();
|
|
52838
|
+
const resolvedUrl = resolveAnvilUrl();
|
|
52839
|
+
const defaultUrl = isDevMode() ? "http://localhost:3000" : "https://anvil.works";
|
|
52840
|
+
const authTokens = allConfig.authTokens;
|
|
52841
|
+
const urls = authTokens && "object" == typeof authTokens ? Object.keys(authTokens).sort() : [];
|
|
52842
|
+
const accounts = [];
|
|
52843
|
+
for (const url of urls){
|
|
52844
|
+
const urlData = authTokens?.[url];
|
|
52845
|
+
if (!(!urlData || "object" != typeof urlData || Array.isArray(urlData))) for (const username of Object.keys(urlData).sort()){
|
|
52846
|
+
const accountData = urlData[username];
|
|
52847
|
+
const hasConfigTokens = !!(accountData?.authToken || accountData?.refreshToken);
|
|
52848
|
+
const hasAnyTokens = hasTokensForUrl(url, username);
|
|
52849
|
+
accounts.push({
|
|
52850
|
+
url,
|
|
52851
|
+
username,
|
|
52852
|
+
status: hasConfigTokens ? "config" : hasAnyTokens ? "keychain" : "none"
|
|
52853
|
+
});
|
|
52854
|
+
}
|
|
52855
|
+
}
|
|
52856
|
+
const skipKeys = new Set([
|
|
52857
|
+
"anvilUrl",
|
|
52858
|
+
"authTokens"
|
|
52859
|
+
]);
|
|
52860
|
+
const values = {};
|
|
52861
|
+
for (const [key, value] of Object.entries(allConfig))if (!skipKeys.has(key)) values[key] = value;
|
|
52862
|
+
return {
|
|
52863
|
+
anvilUrl: {
|
|
52864
|
+
value: resolvedUrl,
|
|
52865
|
+
defaultValue: defaultUrl,
|
|
52866
|
+
isDefault: resolvedUrl === defaultUrl
|
|
52867
|
+
},
|
|
52868
|
+
authTokens: {
|
|
52869
|
+
totalUrls: urls.length,
|
|
52870
|
+
totalAccounts: accounts.filter((account)=>"none" !== account.status).length,
|
|
52871
|
+
accounts
|
|
52872
|
+
},
|
|
52873
|
+
values
|
|
52874
|
+
};
|
|
52875
|
+
}
|
|
52806
52876
|
function registerConfigCommand(program) {
|
|
52807
52877
|
const configCommand = program.command("config").description("Manage configuration").alias("c");
|
|
52808
52878
|
configCommand.command("get <key>").description("Get a configuration value").action(async (key)=>{
|
|
@@ -52829,49 +52899,35 @@ var __webpack_exports__ = {};
|
|
|
52829
52899
|
process.exit(1);
|
|
52830
52900
|
}
|
|
52831
52901
|
});
|
|
52902
|
+
configCommand.command("delete <key>").alias("unset").description("Delete a configuration value or reset it to its default").action(async (key)=>{
|
|
52903
|
+
try {
|
|
52904
|
+
const result = performConfigDelete(key);
|
|
52905
|
+
if (result.existed) logger_logger.success(`Deleted ${key}`);
|
|
52906
|
+
else logger_logger.warn(`Key '${key}' was not set; applied default/empty value if applicable.`);
|
|
52907
|
+
} catch (error) {
|
|
52908
|
+
logger_logger.error(error instanceof Error ? error.message : String(error));
|
|
52909
|
+
process.exit(1);
|
|
52910
|
+
}
|
|
52911
|
+
});
|
|
52832
52912
|
configCommand.command("list").description("List all configuration values").action(async ()=>{
|
|
52833
52913
|
try {
|
|
52834
|
-
const
|
|
52914
|
+
const configData = getConfigListData();
|
|
52915
|
+
if (getGlobalOutputConfig().jsonMode) return void logJsonResult(true, {
|
|
52916
|
+
data: configData
|
|
52917
|
+
});
|
|
52835
52918
|
console.log(chalk_source.bold("\nConfiguration:"));
|
|
52836
52919
|
console.log(chalk_source.gray("─".repeat(50)));
|
|
52837
|
-
const
|
|
52838
|
-
|
|
52839
|
-
|
|
52840
|
-
|
|
52841
|
-
|
|
52842
|
-
|
|
52843
|
-
|
|
52844
|
-
|
|
52845
|
-
let totalAccounts = 0;
|
|
52846
|
-
for (const url of urls){
|
|
52847
|
-
const urlData = authTokens[url];
|
|
52848
|
-
if (urlData && "object" == typeof urlData && !Array.isArray(urlData)) {
|
|
52849
|
-
const usernames = Object.keys(urlData);
|
|
52850
|
-
totalAccounts += usernames.filter((username)=>hasTokensForUrl(url, username)).length;
|
|
52851
|
-
}
|
|
52852
|
-
}
|
|
52853
|
-
console.log(chalk_source.cyan("authTokens") + ` = ${chalk_source.gray(`${totalAccounts} logged-in account(s) across ${urls.length} URL(s)`)}`);
|
|
52854
|
-
for (const url of urls){
|
|
52855
|
-
const urlData = authTokens[url];
|
|
52856
|
-
if (urlData && "object" == typeof urlData && !Array.isArray(urlData)) {
|
|
52857
|
-
const usernames = Object.keys(urlData).sort();
|
|
52858
|
-
if (usernames.length > 0) for (const username of usernames){
|
|
52859
|
-
const accountData = urlData[username];
|
|
52860
|
-
const hasConfigTokens = !!(accountData?.authToken || accountData?.refreshToken);
|
|
52861
|
-
const hasAnyTokens = hasTokensForUrl(url, username);
|
|
52862
|
-
const status = hasConfigTokens ? chalk_source.green("✓ logged in (config)") : hasAnyTokens ? chalk_source.green("✓ logged in (keychain)") : chalk_source.gray("(no tokens)");
|
|
52863
|
-
console.log(chalk_source.gray(` ${url}: ${username} ${status}`));
|
|
52864
|
-
}
|
|
52865
|
-
}
|
|
52866
|
-
}
|
|
52867
|
-
} else console.log(chalk_source.cyan("authTokens") + ` = ${chalk_source.gray("(no accounts)")}`);
|
|
52920
|
+
const defaultHint = configData.anvilUrl.isDefault ? "" : chalk_source.gray(` (default: ${configData.anvilUrl.defaultValue})`);
|
|
52921
|
+
console.log(chalk_source.cyan("anvilUrl") + ` = ${configData.anvilUrl.value}${defaultHint}`);
|
|
52922
|
+
if (configData.authTokens.totalUrls > 0) {
|
|
52923
|
+
console.log(chalk_source.cyan("authTokens") + ` = ${chalk_source.gray(`${configData.authTokens.totalAccounts} logged-in account(s) across ${configData.authTokens.totalUrls} URL(s)`)}`);
|
|
52924
|
+
for (const account of configData.authTokens.accounts){
|
|
52925
|
+
const status = "config" === account.status ? chalk_source.green("✓ logged in (config)") : "keychain" === account.status ? chalk_source.green("✓ logged in (keychain)") : chalk_source.gray("(no tokens)");
|
|
52926
|
+
console.log(chalk_source.gray(` ${account.url}: ${account.username} ${status}`));
|
|
52927
|
+
}
|
|
52868
52928
|
} else console.log(chalk_source.cyan("authTokens") + ` = ${chalk_source.gray("(none)")}`);
|
|
52869
|
-
const
|
|
52870
|
-
|
|
52871
|
-
"authTokens"
|
|
52872
|
-
]);
|
|
52873
|
-
for (const [k, v] of Object.entries(allConfig))if (!skipKeys.has(k)) if (null === v) console.log(chalk_source.cyan(k) + ` = ${chalk_source.gray("null")}`);
|
|
52874
|
-
else console.log(chalk_source.cyan(k) + ` = ${v}`);
|
|
52929
|
+
for (const [key, value] of Object.entries(configData.values))if (null === value) console.log(chalk_source.cyan(key) + ` = ${chalk_source.gray("null")}`);
|
|
52930
|
+
else console.log(chalk_source.cyan(key) + ` = ${value}`);
|
|
52875
52931
|
console.log(chalk_source.gray("─".repeat(50)));
|
|
52876
52932
|
console.log();
|
|
52877
52933
|
} catch (e) {
|
|
@@ -52889,11 +52945,23 @@ var __webpack_exports__ = {};
|
|
|
52889
52945
|
}
|
|
52890
52946
|
});
|
|
52891
52947
|
}
|
|
52948
|
+
function getVersionInfo(version) {
|
|
52949
|
+
return {
|
|
52950
|
+
version,
|
|
52951
|
+
nodeVersion: process.version,
|
|
52952
|
+
platform: process.platform,
|
|
52953
|
+
arch: process.arch
|
|
52954
|
+
};
|
|
52955
|
+
}
|
|
52892
52956
|
function registerVersionCommand(program, version) {
|
|
52893
52957
|
program.command("version").description("Show version information").action(()=>{
|
|
52894
|
-
|
|
52895
|
-
|
|
52896
|
-
|
|
52958
|
+
const info = getVersionInfo(version);
|
|
52959
|
+
if (getGlobalOutputConfig().jsonMode) return void logJsonResult(true, {
|
|
52960
|
+
data: info
|
|
52961
|
+
});
|
|
52962
|
+
console.log(chalk_source.cyan.bold(`\n⚙ anvil ${info.version}`));
|
|
52963
|
+
console.log(chalk_source.gray(` Node.js ${info.nodeVersion}`));
|
|
52964
|
+
console.log(chalk_source.gray(` Platform: ${info.platform} ${info.arch}`));
|
|
52897
52965
|
console.log();
|
|
52898
52966
|
});
|
|
52899
52967
|
}
|
|
@@ -52903,6 +52971,68 @@ var __webpack_exports__ = {};
|
|
|
52903
52971
|
runInteractiveLoginFlow: runInteractiveLoginFlow
|
|
52904
52972
|
};
|
|
52905
52973
|
const DEFAULT_ANVIL_SERVER_URL_PROMPT = "Default Anvil server URL";
|
|
52974
|
+
const CUSTOM_EDITOR_CHOICE = "__custom_editor_command__";
|
|
52975
|
+
function formatCustomEditorChoice(currentEditor) {
|
|
52976
|
+
if (!currentEditor) return "Enter custom editor command";
|
|
52977
|
+
return `Custom editor command (${currentEditor})`;
|
|
52978
|
+
}
|
|
52979
|
+
async function promptForPreferredEditor(currentEditor, installedEditors, ui) {
|
|
52980
|
+
if (0 === installedEditors.length) {
|
|
52981
|
+
ui.warn("No supported editor command was found in PATH.");
|
|
52982
|
+
ui.info(chalk_source.gray("If your editor is installed, its shell command may not be available in this terminal."));
|
|
52983
|
+
const action = await ui.select("Preferred editor", [
|
|
52984
|
+
{
|
|
52985
|
+
name: "None",
|
|
52986
|
+
value: ""
|
|
52987
|
+
},
|
|
52988
|
+
{
|
|
52989
|
+
name: formatCustomEditorChoice(currentEditor),
|
|
52990
|
+
value: CUSTOM_EDITOR_CHOICE
|
|
52991
|
+
}
|
|
52992
|
+
], currentEditor ? CUSTOM_EDITOR_CHOICE : "");
|
|
52993
|
+
if (action !== CUSTOM_EDITOR_CHOICE) return action;
|
|
52994
|
+
const customDefault = currentEditor && !preferredEditors.includes(currentEditor) ? currentEditor : "";
|
|
52995
|
+
const answer = await ui.prompt([
|
|
52996
|
+
{
|
|
52997
|
+
type: "input",
|
|
52998
|
+
name: "preferredEditorCommand",
|
|
52999
|
+
message: "Custom editor command",
|
|
53000
|
+
default: customDefault
|
|
53001
|
+
}
|
|
53002
|
+
]);
|
|
53003
|
+
return answer.preferredEditorCommand.trim();
|
|
53004
|
+
}
|
|
53005
|
+
if (installedEditors.length < preferredEditors.length) {
|
|
53006
|
+
const unavailableCount = preferredEditors.length - installedEditors.length;
|
|
53007
|
+
ui.verbose(chalk_source.gray(`${unavailableCount} supported editor(s) are not installed and were hidden.`));
|
|
53008
|
+
}
|
|
53009
|
+
const editorChoices = [
|
|
53010
|
+
{
|
|
53011
|
+
name: "None",
|
|
53012
|
+
value: ""
|
|
53013
|
+
},
|
|
53014
|
+
...installedEditors.map((editor)=>({
|
|
53015
|
+
name: editor,
|
|
53016
|
+
value: editor
|
|
53017
|
+
})),
|
|
53018
|
+
{
|
|
53019
|
+
name: formatCustomEditorChoice(currentEditor),
|
|
53020
|
+
value: CUSTOM_EDITOR_CHOICE
|
|
53021
|
+
}
|
|
53022
|
+
];
|
|
53023
|
+
const defaultValue = editorChoices.some((choice)=>choice.value === currentEditor) ? currentEditor : currentEditor ? CUSTOM_EDITOR_CHOICE : "";
|
|
53024
|
+
const selectedEditor = await ui.select("Preferred editor", editorChoices, defaultValue);
|
|
53025
|
+
if (selectedEditor !== CUSTOM_EDITOR_CHOICE) return selectedEditor;
|
|
53026
|
+
const answer = await ui.prompt([
|
|
53027
|
+
{
|
|
53028
|
+
type: "input",
|
|
53029
|
+
name: "preferredEditorCommand",
|
|
53030
|
+
message: "Custom editor command",
|
|
53031
|
+
default: currentEditor && !installedEditors.includes(currentEditor) ? currentEditor : ""
|
|
53032
|
+
}
|
|
53033
|
+
]);
|
|
53034
|
+
return answer.preferredEditorCommand.trim();
|
|
53035
|
+
}
|
|
52906
53036
|
async function getConfigureVersionStatus(currentVersion, latestVersionGetter = getLatestVersion) {
|
|
52907
53037
|
const latestVersion = await latestVersionGetter();
|
|
52908
53038
|
if (!latestVersion) return {
|
|
@@ -52961,25 +53091,11 @@ var __webpack_exports__ = {};
|
|
|
52961
53091
|
const configuredAnvilUrl = parseConfigSetValue("anvilUrl", urlAnswer.anvilUrl);
|
|
52962
53092
|
setConfig("anvilUrl", configuredAnvilUrl);
|
|
52963
53093
|
logger_logger.success(`Set default Anvil server URL (anvilUrl) = ${configuredAnvilUrl}`);
|
|
52964
|
-
const currentEditor = String(getConfig("preferredEditor") || "").trim()
|
|
53094
|
+
const currentEditor = String(getConfig("preferredEditor") || "").trim();
|
|
53095
|
+
const currentEditorNormalized = currentEditor.toLowerCase();
|
|
52965
53096
|
const installedEditors = getInstalledPreferredEditors();
|
|
52966
|
-
if (0
|
|
52967
|
-
|
|
52968
|
-
const unavailableCount = preferredEditors.length - installedEditors.length;
|
|
52969
|
-
logger_logger.verbose(chalk_source.gray(`${unavailableCount} supported editor(s) are not installed and were hidden.`));
|
|
52970
|
-
}
|
|
52971
|
-
const editorChoices = [
|
|
52972
|
-
{
|
|
52973
|
-
name: "None",
|
|
52974
|
-
value: ""
|
|
52975
|
-
},
|
|
52976
|
-
...installedEditors.map((editor)=>({
|
|
52977
|
-
name: editor,
|
|
52978
|
-
value: editor
|
|
52979
|
-
}))
|
|
52980
|
-
];
|
|
52981
|
-
if (currentEditor && !editorChoices.some((choice)=>choice.value === currentEditor)) logger_logger.warn(`Configured preferredEditor '${currentEditor}' is not currently available in PATH.`);
|
|
52982
|
-
const selectedEditor = await logger_logger.select("Preferred editor", editorChoices, editorChoices.some((choice)=>choice.value === currentEditor) ? currentEditor : "");
|
|
53097
|
+
if (currentEditor && installedEditors.length > 0 && !installedEditors.includes(currentEditorNormalized)) logger_logger.warn(`Configured preferredEditor '${currentEditor}' is not currently available in PATH.`);
|
|
53098
|
+
const selectedEditor = await promptForPreferredEditor(currentEditor, installedEditors, logger_logger);
|
|
52983
53099
|
setConfig("preferredEditor", selectedEditor);
|
|
52984
53100
|
if (selectedEditor) logger_logger.success(`Set preferredEditor = ${selectedEditor}`);
|
|
52985
53101
|
else logger_logger.info("Left preferredEditor unset.");
|
|
@@ -53025,11 +53141,23 @@ var __webpack_exports__ = {};
|
|
|
53025
53141
|
getLatestVersion().then((latestVersion)=>{
|
|
53026
53142
|
if (latestVersion && semver_default().valid(VERSION) && semver_default().valid(latestVersion) && semver_default().lt(VERSION, latestVersion)) {
|
|
53027
53143
|
logger_logger.warn("A new version of anvil is available!");
|
|
53028
|
-
|
|
53029
|
-
console.log();
|
|
53144
|
+
logger_logger.info(chalk_source.gray(` Current: ${VERSION} → Latest: ${latestVersion}`) + chalk_source.cyan("\n Run 'anvil update' to see update commands."));
|
|
53030
53145
|
}
|
|
53031
53146
|
}).catch(()=>{});
|
|
53032
53147
|
}
|
|
53148
|
+
function getUpdateInstructions(platform) {
|
|
53149
|
+
if ("win32" === platform) return [
|
|
53150
|
+
"try { irm https://anvil.works/install-cli.ps1 | iex } catch { npm install -g @anvil-works/anvil-cli@latest }",
|
|
53151
|
+
"curl -fsSL https://anvil.works/install-cli.cmd -o install.cmd && install.cmd && del install.cmd || npm install -g @anvil-works/anvil-cli@latest",
|
|
53152
|
+
"npm install -g @anvil-works/anvil-cli@latest",
|
|
53153
|
+
"pnpm add -g @anvil-works/anvil-cli@latest"
|
|
53154
|
+
];
|
|
53155
|
+
return [
|
|
53156
|
+
"curl -fsSL https://anvil.works/install-cli.sh | sh || npm install -g @anvil-works/anvil-cli@latest",
|
|
53157
|
+
"npm install -g @anvil-works/anvil-cli@latest",
|
|
53158
|
+
"pnpm add -g @anvil-works/anvil-cli@latest"
|
|
53159
|
+
];
|
|
53160
|
+
}
|
|
53033
53161
|
async function handleUpdateCommand() {
|
|
53034
53162
|
try {
|
|
53035
53163
|
logger_logger.progress("update", "Checking for updates...");
|
|
@@ -53044,26 +53172,54 @@ var __webpack_exports__ = {};
|
|
|
53044
53172
|
process.exit(1);
|
|
53045
53173
|
}
|
|
53046
53174
|
if (semver_default().eq(VERSION, latestVersion)) {
|
|
53047
|
-
|
|
53048
|
-
|
|
53175
|
+
if (getGlobalOutputConfig().jsonMode) logJsonResult(true, {
|
|
53176
|
+
data: {
|
|
53177
|
+
currentVersion: VERSION,
|
|
53178
|
+
latestVersion,
|
|
53179
|
+
status: "up-to-date"
|
|
53180
|
+
}
|
|
53181
|
+
});
|
|
53182
|
+
else {
|
|
53183
|
+
logger_logger.success(`You're already using the latest version: ${VERSION}`);
|
|
53184
|
+
console.log();
|
|
53185
|
+
}
|
|
53049
53186
|
return;
|
|
53050
53187
|
}
|
|
53051
53188
|
if (semver_default().gt(VERSION, latestVersion)) {
|
|
53052
|
-
|
|
53053
|
-
|
|
53054
|
-
|
|
53189
|
+
if (getGlobalOutputConfig().jsonMode) logJsonResult(true, {
|
|
53190
|
+
data: {
|
|
53191
|
+
currentVersion: VERSION,
|
|
53192
|
+
latestVersion,
|
|
53193
|
+
status: "ahead",
|
|
53194
|
+
note: "This might be a development build."
|
|
53195
|
+
}
|
|
53196
|
+
});
|
|
53197
|
+
else {
|
|
53198
|
+
logger_logger.warn(`You're using version ${VERSION}, which is newer than the latest published version ${latestVersion}.`);
|
|
53199
|
+
console.log(chalk_source.gray(" This might be a development build."));
|
|
53200
|
+
console.log();
|
|
53201
|
+
}
|
|
53055
53202
|
return;
|
|
53056
53203
|
}
|
|
53204
|
+
const instructions = getUpdateInstructions(process.platform);
|
|
53205
|
+
if (getGlobalOutputConfig().jsonMode) return void logJsonResult(true, {
|
|
53206
|
+
data: {
|
|
53207
|
+
currentVersion: VERSION,
|
|
53208
|
+
latestVersion,
|
|
53209
|
+
status: "update-available",
|
|
53210
|
+
instructions
|
|
53211
|
+
}
|
|
53212
|
+
});
|
|
53057
53213
|
logger_logger.warn(`↑ Update available: ${VERSION} → ${latestVersion}`);
|
|
53058
53214
|
console.log();
|
|
53059
53215
|
console.log(chalk_source.cyan("To update, run one of the following commands:"));
|
|
53060
53216
|
console.log();
|
|
53061
53217
|
if ("win32" === process.platform) {
|
|
53062
|
-
console.log(chalk_source.gray(" PowerShell: ") + chalk_source.white(
|
|
53063
|
-
console.log(chalk_source.gray(" CMD: ") + chalk_source.white(
|
|
53064
|
-
} else console.log(chalk_source.gray(" macOS/Linux: ") + chalk_source.white(
|
|
53065
|
-
console.log(chalk_source.gray(" npm: ") + chalk_source.white(
|
|
53066
|
-
console.log(chalk_source.gray(" pnpm: ") + chalk_source.white(
|
|
53218
|
+
console.log(chalk_source.gray(" PowerShell: ") + chalk_source.white(instructions[0]));
|
|
53219
|
+
console.log(chalk_source.gray(" CMD: ") + chalk_source.white(instructions[1]));
|
|
53220
|
+
} else console.log(chalk_source.gray(" macOS/Linux: ") + chalk_source.white(instructions[0]));
|
|
53221
|
+
console.log(chalk_source.gray(" npm: ") + chalk_source.white(instructions[instructions.length - 2]));
|
|
53222
|
+
console.log(chalk_source.gray(" pnpm: ") + chalk_source.white(instructions[instructions.length - 1]));
|
|
53067
53223
|
console.log();
|
|
53068
53224
|
} catch (e) {
|
|
53069
53225
|
logger_logger.error("Error: " + e.message);
|
|
@@ -53071,7 +53227,7 @@ var __webpack_exports__ = {};
|
|
|
53071
53227
|
}
|
|
53072
53228
|
}
|
|
53073
53229
|
function addGlobalOptionsHelp(command) {
|
|
53074
|
-
if ("help" !== command.name()) command.addHelpText("after", "\n" + chalk_source.bold("Global Options:") + "\n -V, --verbose Show detailed output\n");
|
|
53230
|
+
if ("help" !== command.name()) command.addHelpText("after", "\n" + chalk_source.bold("Global Options:") + "\n --json Output in JSON format (NDJSON) for scripting/LLM consumption\n -V, --verbose Show detailed output\n");
|
|
53075
53231
|
command.commands.forEach((subcommand)=>addGlobalOptionsHelp(subcommand));
|
|
53076
53232
|
}
|
|
53077
53233
|
function buildProgram() {
|