stow-cli 2.0.4 → 2.2.0
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/LICENSE +21 -0
- package/README.md +363 -0
- package/dist/app-Q6EW7VSM.js +249 -0
- package/dist/backfill-EVZT7RH6.js +67 -0
- package/dist/buckets-ESAOL6CH.js +115 -0
- package/dist/buckets-ZHP3LBLC.js +137 -0
- package/dist/chunk-3BLL5SQJ.js +27 -0
- package/dist/chunk-FZGOTXTE.js +45 -0
- package/dist/chunk-OHAFRKN5.js +40 -0
- package/dist/chunk-P2BKGBQE.js +136 -0
- package/dist/chunk-PLZFHPLC.js +52 -0
- package/dist/chunk-XJDK2CBE.js +328 -0
- package/dist/cli.js +123 -398
- package/dist/delete-4JSVNETO.js +34 -0
- package/dist/delete-YEXSMG4I.js +34 -0
- package/dist/describe-J4ZMUXK7.js +79 -0
- package/dist/describe-UFMXNNUB.js +79 -0
- package/dist/drops-5VIEW3XZ.js +39 -0
- package/dist/files-TDIGJDN3.js +185 -0
- package/dist/files-UQODXWNT.js +206 -0
- package/dist/health-RICGWQGN.js +61 -0
- package/dist/jobs-PTV2W5PJ.js +102 -0
- package/dist/jobs-ZWSEXNFA.js +90 -0
- package/dist/maintenance-ZJW2ES4L.js +79 -0
- package/dist/mcp-RZT4TJEX.js +190 -0
- package/dist/profiles-CHBGKQOE.js +53 -0
- package/dist/queues-EZ2VZGXQ.js +61 -0
- package/dist/search-TRTPX2SQ.js +135 -0
- package/dist/tags-MCFL5M2J.js +82 -0
- package/dist/tags-VH44BGQL.js +90 -0
- package/dist/upload-5TAWJU5N.js +126 -0
- package/dist/upload-OS6Q6LW5.js +126 -0
- package/dist/whoami-TVRKBM74.js +28 -0
- package/package.json +14 -12
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Stow
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
# stow-cli
|
|
2
|
+
|
|
3
|
+
CLI for [Stow](https://stow.sh) file storage. Upload, search, and manage files from the terminal.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g stow-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# Set your API key
|
|
15
|
+
export STOW_API_KEY="stow_..."
|
|
16
|
+
|
|
17
|
+
# Check connection
|
|
18
|
+
stow whoami
|
|
19
|
+
|
|
20
|
+
# Upload a file
|
|
21
|
+
stow upload ./photo.jpg --bucket photos
|
|
22
|
+
|
|
23
|
+
# Quick share (returns a short URL)
|
|
24
|
+
stow drop ./screenshot.png
|
|
25
|
+
|
|
26
|
+
# Search
|
|
27
|
+
stow search text 'sunset beach' -b photos
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Authentication
|
|
31
|
+
|
|
32
|
+
Stow CLI uses environment variables for authentication:
|
|
33
|
+
|
|
34
|
+
| Variable | Required | Description |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `STOW_API_KEY` | Yes | Your Stow API key (get one at `app.stow.sh/dashboard/api-keys`) |
|
|
37
|
+
| `STOW_API_URL` | No | Override the default API URL (`https://api.stow.sh`) |
|
|
38
|
+
| `STOW_ADMIN_SECRET` | No | Required for `admin` commands only |
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
### Upload and Share
|
|
43
|
+
|
|
44
|
+
**`stow upload <file>`** -- Upload a file to a bucket.
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
stow upload ./logo.png --bucket brand-assets
|
|
48
|
+
stow upload ./clip.mov --quiet
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Options: `-b, --bucket <name>`, `-q, --quiet`
|
|
52
|
+
|
|
53
|
+
**`stow drop <file>`** -- Upload a file and get a short URL (quick share).
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
stow drop ./video.mp4
|
|
57
|
+
stow drop ./notes.txt --quiet
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Options: `-q, --quiet`
|
|
61
|
+
|
|
62
|
+
### Buckets
|
|
63
|
+
|
|
64
|
+
**`stow buckets`** -- List your buckets.
|
|
65
|
+
|
|
66
|
+
**`stow buckets create <name>`** -- Create a new bucket.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
stow buckets create photos
|
|
70
|
+
stow buckets create docs --description "Product docs"
|
|
71
|
+
stow buckets create public-media --public
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Options: `-d, --description <text>`, `--public`
|
|
75
|
+
|
|
76
|
+
**`stow buckets rename <name> <new-name>`** -- Rename a bucket. Note: renaming can break existing public URLs.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
stow buckets rename old-name new-name --yes
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Options: `-y, --yes` (skip confirmation)
|
|
83
|
+
|
|
84
|
+
**`stow buckets delete <id>`** -- Delete a bucket by ID.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
stow buckets delete 8f3d1ab4-...
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Files
|
|
91
|
+
|
|
92
|
+
**`stow files <bucket>`** -- List files in a bucket.
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
stow files photos
|
|
96
|
+
stow files photos --search avatars/ --limit 100
|
|
97
|
+
stow files photos --json
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Options: `-s, --search <prefix>`, `-l, --limit <count>`, `--json`
|
|
101
|
+
|
|
102
|
+
**`stow files get <bucket> <key>`** -- Get details for a single file.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
stow files get photos hero.png
|
|
106
|
+
stow files get photos hero.png --json
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Options: `--json`
|
|
110
|
+
|
|
111
|
+
**`stow files update <bucket> <key>`** -- Update file metadata.
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
stow files update photos hero.png -m alt='Hero image'
|
|
115
|
+
stow files update photos hero.png -m category=banner -m priority=high
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Options: `-m, --metadata <kv...>`, `--json`
|
|
119
|
+
|
|
120
|
+
**`stow files enrich <bucket> <key>`** -- Generate title, description, and alt text for an image. Requires a searchable bucket with image files.
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
stow files enrich photos hero.jpg
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**`stow files missing <bucket> <type>`** -- List files missing processing data. Valid types: `dimensions`, `embeddings`, `colors`.
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
stow files missing brera dimensions
|
|
130
|
+
stow files missing brera embeddings --limit 200
|
|
131
|
+
stow files missing brera colors --json
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Options: `-l, --limit <count>`, `--json`
|
|
135
|
+
|
|
136
|
+
### Search
|
|
137
|
+
|
|
138
|
+
**`stow search text <query>`** -- Semantic text search.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
stow search text 'sunset beach' -b photos --limit 10 --json
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**`stow search similar --file <key>`** -- Find files similar to a given file.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
stow search similar --file hero.png -b photos
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**`stow search color --hex <color>`** -- Search by color.
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
stow search color --hex "#ff0000" -b photos --limit 20
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**`stow search diverse`** -- Diversity-aware search.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
stow search diverse -b photos --limit 20
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
All search commands accept: `-b, --bucket <name>`, `-l, --limit <count>`, `--json`
|
|
163
|
+
|
|
164
|
+
### Tags
|
|
165
|
+
|
|
166
|
+
**`stow tags`** -- List tags.
|
|
167
|
+
|
|
168
|
+
**`stow tags create <name>`** -- Create a new tag.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
stow tags create "Hero Images"
|
|
172
|
+
stow tags create "Featured" --color "#ff6600"
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Options: `--color <hex>`, `--json`
|
|
176
|
+
|
|
177
|
+
**`stow tags delete <id>`** -- Delete a tag by ID.
|
|
178
|
+
|
|
179
|
+
### Drops
|
|
180
|
+
|
|
181
|
+
**`stow drops`** -- List your drops with usage info.
|
|
182
|
+
|
|
183
|
+
**`stow drops delete <id>`** -- Delete a drop by ID.
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
stow drops delete drop_abc123
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Profiles
|
|
190
|
+
|
|
191
|
+
**`stow profiles create`** -- Create a taste profile.
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
stow profiles create --name "My Profile" -b photos
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Options: `--name <name>` (required), `-b, --bucket <id>`, `--json`
|
|
198
|
+
|
|
199
|
+
**`stow profiles get <id>`** -- Get a taste profile with clusters.
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
stow profiles get profile_abc123 --json
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Options: `--json`
|
|
206
|
+
|
|
207
|
+
**`stow profiles delete <id>`** -- Delete a taste profile.
|
|
208
|
+
|
|
209
|
+
### Jobs
|
|
210
|
+
|
|
211
|
+
**`stow jobs`** -- List processing jobs for a bucket.
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
stow jobs --bucket <id>
|
|
215
|
+
stow jobs --bucket <id> --status failed
|
|
216
|
+
stow jobs --bucket <id> --queue extract-colors --json
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Options: `-b, --bucket <id>` (required), `-s, --status <status>`, `-q, --queue <name>`, `-l, --limit <count>`, `--json`
|
|
220
|
+
|
|
221
|
+
**`stow jobs retry <id>`** -- Retry a failed job.
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
stow jobs retry job123 --queue generate-title --bucket <id>
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Options: `-q, --queue <name>` (required), `-b, --bucket <id>` (required)
|
|
228
|
+
|
|
229
|
+
**`stow jobs delete <id>`** -- Remove a job.
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
stow jobs delete job123 --queue extract-colors --bucket <id>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Options: `-q, --queue <name>` (required), `-b, --bucket <id>` (required)
|
|
236
|
+
|
|
237
|
+
### Admin
|
|
238
|
+
|
|
239
|
+
All admin commands require the `STOW_ADMIN_SECRET` environment variable.
|
|
240
|
+
|
|
241
|
+
**`stow admin health`** -- Check system health and queue depths.
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
stow admin health
|
|
245
|
+
stow admin health --json
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**`stow admin backfill <type>`** -- Backfill processing data. Valid types: `dimensions`, `colors`, `embeddings`.
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
stow admin backfill dimensions --bucket <id> --dry-run
|
|
252
|
+
stow admin backfill colors --bucket <id> --limit 200
|
|
253
|
+
stow admin backfill embeddings --bucket <id> --limit 100 --json
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Options: `--bucket <id>`, `-l, --limit <count>`, `--dry-run`, `--json`
|
|
257
|
+
|
|
258
|
+
**`stow admin cleanup-drops`** -- Remove expired drops.
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
stow admin cleanup-drops --max-age-hours 24 --dry-run
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Options: `--max-age-hours <hours>`, `--dry-run`, `--json`
|
|
265
|
+
|
|
266
|
+
**`stow admin purge-events`** -- Purge old webhook events.
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
stow admin purge-events --dry-run
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Options: `--dry-run`, `--json`
|
|
273
|
+
|
|
274
|
+
**`stow admin reconcile-files`** -- Reconcile files between R2 and database.
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
stow admin reconcile-files --bucket <id> --dry-run
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Options: `--bucket <id>` (required), `--dry-run`, `--json`
|
|
281
|
+
|
|
282
|
+
**`stow admin retry-sync-failures`** -- Retry failed S3 sync operations.
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
stow admin retry-sync-failures
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Options: `--json`
|
|
289
|
+
|
|
290
|
+
**`stow admin jobs`** -- List and manage processing jobs (cross-org).
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
stow admin jobs
|
|
294
|
+
stow admin jobs --status failed
|
|
295
|
+
stow admin jobs --org <id> --queue generate-title
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
Options: `--org <id>`, `--bucket <id>`, `-s, --status`, `-q, --queue`, `-l, --limit`, `--json`
|
|
299
|
+
|
|
300
|
+
**`stow admin jobs retry <id>`** / **`stow admin jobs delete <id>`** -- Retry or remove a job.
|
|
301
|
+
|
|
302
|
+
```bash
|
|
303
|
+
stow admin jobs retry job123 --queue generate-title
|
|
304
|
+
stow admin jobs delete job123 --queue extract-colors
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
**`stow admin queues`** -- Show queue depths and counts.
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
stow admin queues
|
|
311
|
+
stow admin queues --json
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**`stow admin queues clean <name>`** -- Clean jobs from a queue.
|
|
315
|
+
|
|
316
|
+
```bash
|
|
317
|
+
stow admin queues clean generate-title --failed
|
|
318
|
+
stow admin queues clean extract-colors --completed --grace 3600
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Options: `--failed`, `--completed`, `--grace <seconds>`
|
|
322
|
+
|
|
323
|
+
### Utility
|
|
324
|
+
|
|
325
|
+
**`stow whoami`** -- Show account info, usage stats, and API key details.
|
|
326
|
+
|
|
327
|
+
**`stow open <bucket>`** -- Open a bucket in the browser.
|
|
328
|
+
|
|
329
|
+
**`stow delete <bucket> <key>`** -- Delete a file from a bucket.
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
stow delete photos hero/banner.png
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Interactive Mode
|
|
336
|
+
|
|
337
|
+
Run `stow` with no arguments or `stow -i` to launch the interactive TUI. Browse buckets, files, and perform actions with a keyboard-driven interface.
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
stow
|
|
341
|
+
stow --interactive
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
## JSON Output
|
|
345
|
+
|
|
346
|
+
Most commands support `--json` for machine-readable output. Useful for scripting and piping into tools like `jq`:
|
|
347
|
+
|
|
348
|
+
```bash
|
|
349
|
+
stow files photos --json | jq '.[].key'
|
|
350
|
+
stow search text 'landscape' -b photos --json | jq length
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## Environment Variables
|
|
354
|
+
|
|
355
|
+
| Variable | Default | Description |
|
|
356
|
+
|---|---|---|
|
|
357
|
+
| `STOW_API_KEY` | -- | API key for authentication |
|
|
358
|
+
| `STOW_API_URL` | `https://api.stow.sh` | API base URL |
|
|
359
|
+
| `STOW_ADMIN_SECRET` | -- | Secret for admin commands |
|
|
360
|
+
|
|
361
|
+
## License
|
|
362
|
+
|
|
363
|
+
MIT
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatBytes
|
|
3
|
+
} from "./chunk-FZGOTXTE.js";
|
|
4
|
+
import {
|
|
5
|
+
createStow
|
|
6
|
+
} from "./chunk-5LU25QZK.js";
|
|
7
|
+
import "./chunk-TOADDO2F.js";
|
|
8
|
+
|
|
9
|
+
// src/interactive/app.tsx
|
|
10
|
+
import { render } from "ink";
|
|
11
|
+
import { useEffect as useEffect3, useState as useState3 } from "react";
|
|
12
|
+
|
|
13
|
+
// src/interactive/components/bucket-list.tsx
|
|
14
|
+
import { Box as Box3, Text as Text3, useApp, useInput } from "ink";
|
|
15
|
+
import Spinner from "ink-spinner";
|
|
16
|
+
import { useEffect, useState } from "react";
|
|
17
|
+
|
|
18
|
+
// src/interactive/components/header.tsx
|
|
19
|
+
import { Box, Text } from "ink";
|
|
20
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
21
|
+
function Header({ path, email, size }) {
|
|
22
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
23
|
+
/* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", width: "100%", children: [
|
|
24
|
+
/* @__PURE__ */ jsxs(Text, { bold: true, children: [
|
|
25
|
+
"STOW",
|
|
26
|
+
path ? ` > ${path}` : ""
|
|
27
|
+
] }),
|
|
28
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: size || email || "" })
|
|
29
|
+
] }),
|
|
30
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(50) })
|
|
31
|
+
] });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/interactive/components/status-bar.tsx
|
|
35
|
+
import { Box as Box2, Text as Text2 } from "ink";
|
|
36
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
37
|
+
function StatusBar({ hints }) {
|
|
38
|
+
return /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: hints.map((h, i) => /* @__PURE__ */ jsx2(Box2, { marginRight: 2, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
39
|
+
"[",
|
|
40
|
+
h.key,
|
|
41
|
+
"]",
|
|
42
|
+
h.label,
|
|
43
|
+
i < hints.length - 1 ? "" : ""
|
|
44
|
+
] }) }, h.key)) });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// src/interactive/components/bucket-list.tsx
|
|
48
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
49
|
+
function toBucket(result) {
|
|
50
|
+
return {
|
|
51
|
+
id: result.id,
|
|
52
|
+
name: result.name,
|
|
53
|
+
description: result.description ?? null,
|
|
54
|
+
fileCount: result.fileCount ?? 0,
|
|
55
|
+
isPublic: result.isPublic ?? false,
|
|
56
|
+
usageBytes: result.usageBytes ?? 0
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function BucketList({ onSelect, email }) {
|
|
60
|
+
const { exit } = useApp();
|
|
61
|
+
const [buckets, setBuckets] = useState([]);
|
|
62
|
+
const [loading, setLoading] = useState(true);
|
|
63
|
+
const [error, setError] = useState(null);
|
|
64
|
+
const [cursor, setCursor] = useState(0);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const stow = createStow();
|
|
67
|
+
stow.listBuckets().then((data) => {
|
|
68
|
+
setBuckets(data.buckets.map(toBucket));
|
|
69
|
+
setLoading(false);
|
|
70
|
+
}).catch((err) => {
|
|
71
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
72
|
+
setLoading(false);
|
|
73
|
+
});
|
|
74
|
+
}, []);
|
|
75
|
+
useInput((input, key) => {
|
|
76
|
+
if (input === "q") {
|
|
77
|
+
exit();
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (key.upArrow) {
|
|
81
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
82
|
+
} else if (key.downArrow) {
|
|
83
|
+
setCursor((c) => Math.min(buckets.length - 1, c + 1));
|
|
84
|
+
} else if (key.return && buckets.length > 0) {
|
|
85
|
+
onSelect(buckets[cursor]);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
if (loading) {
|
|
89
|
+
return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
90
|
+
/* @__PURE__ */ jsx3(Spinner, { type: "dots" }),
|
|
91
|
+
" Loading buckets..."
|
|
92
|
+
] }) });
|
|
93
|
+
}
|
|
94
|
+
if (error) {
|
|
95
|
+
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
|
|
96
|
+
"Error: ",
|
|
97
|
+
error
|
|
98
|
+
] }) });
|
|
99
|
+
}
|
|
100
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
101
|
+
/* @__PURE__ */ jsx3(Header, { email }),
|
|
102
|
+
buckets.length === 0 ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "No buckets yet. Press 'n' to create one." }) : buckets.map((b, i) => /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { color: i === cursor ? "cyan" : void 0, children: [
|
|
103
|
+
i === cursor ? "\u25B8 " : " ",
|
|
104
|
+
b.name.padEnd(20),
|
|
105
|
+
`${b.fileCount} files`.padEnd(14),
|
|
106
|
+
formatBytes(b.usageBytes)
|
|
107
|
+
] }) }, b.id)),
|
|
108
|
+
/* @__PURE__ */ jsx3(
|
|
109
|
+
StatusBar,
|
|
110
|
+
{
|
|
111
|
+
hints: [
|
|
112
|
+
{ key: "\u2191\u2193", label: "navigate" },
|
|
113
|
+
{ key: "\u21B5", label: "open" },
|
|
114
|
+
{ key: "q", label: "uit" }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
] });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/interactive/components/file-list.tsx
|
|
122
|
+
import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
|
|
123
|
+
import Spinner2 from "ink-spinner";
|
|
124
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
125
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
126
|
+
function FileList({ bucket, onBack }) {
|
|
127
|
+
const [files, setFiles] = useState2([]);
|
|
128
|
+
const [loading, setLoading] = useState2(true);
|
|
129
|
+
const [error, setError] = useState2(null);
|
|
130
|
+
const [cursor, setCursor] = useState2(0);
|
|
131
|
+
const [nextCursor, setNextCursor] = useState2(null);
|
|
132
|
+
const [copied, setCopied] = useState2(false);
|
|
133
|
+
useEffect2(() => {
|
|
134
|
+
loadFiles();
|
|
135
|
+
}, []);
|
|
136
|
+
function loadFiles(pageCursor) {
|
|
137
|
+
setLoading(true);
|
|
138
|
+
const stow = createStow();
|
|
139
|
+
stow.listFiles({
|
|
140
|
+
bucket: bucket.name,
|
|
141
|
+
...pageCursor ? { cursor: pageCursor } : {}
|
|
142
|
+
}).then((data) => {
|
|
143
|
+
setFiles(
|
|
144
|
+
(prev) => pageCursor ? [...prev, ...data.files] : data.files
|
|
145
|
+
);
|
|
146
|
+
setNextCursor(data.nextCursor);
|
|
147
|
+
setLoading(false);
|
|
148
|
+
}).catch((err) => {
|
|
149
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
150
|
+
setLoading(false);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
useInput2((input, key) => {
|
|
154
|
+
if (key.escape || key.backspace || key.leftArrow && !loading) {
|
|
155
|
+
onBack();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (key.upArrow) {
|
|
159
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
160
|
+
} else if (key.downArrow) {
|
|
161
|
+
setCursor((c) => {
|
|
162
|
+
const next = Math.min(files.length - 1, c + 1);
|
|
163
|
+
if (next >= files.length - 3 && nextCursor && !loading) {
|
|
164
|
+
loadFiles(nextCursor);
|
|
165
|
+
}
|
|
166
|
+
return next;
|
|
167
|
+
});
|
|
168
|
+
} else if (input === "c" && files.length > 0) {
|
|
169
|
+
import("clipboardy").then(({ default: clipboard }) => {
|
|
170
|
+
clipboard.write(files[cursor].url).then(() => {
|
|
171
|
+
setCopied(true);
|
|
172
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
if (error) {
|
|
178
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
179
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
|
|
180
|
+
"Error: ",
|
|
181
|
+
error
|
|
182
|
+
] }),
|
|
183
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press Esc to go back." })
|
|
184
|
+
] });
|
|
185
|
+
}
|
|
186
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
187
|
+
/* @__PURE__ */ jsx4(Header, { path: bucket.name, size: formatBytes(bucket.usageBytes) }),
|
|
188
|
+
loading && files.length === 0 && /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
189
|
+
/* @__PURE__ */ jsx4(Spinner2, { type: "dots" }),
|
|
190
|
+
" Loading files..."
|
|
191
|
+
] }),
|
|
192
|
+
!loading && files.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No files in this bucket." }),
|
|
193
|
+
files.length > 0 && files.map((f, i) => /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color: i === cursor ? "cyan" : void 0, children: [
|
|
194
|
+
i === cursor ? "\u25B8 " : " ",
|
|
195
|
+
f.key.padEnd(28),
|
|
196
|
+
formatBytes(f.size).padEnd(10),
|
|
197
|
+
f.lastModified.split("T")[0]
|
|
198
|
+
] }) }, f.key)),
|
|
199
|
+
loading && files.length > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
200
|
+
/* @__PURE__ */ jsx4(Spinner2, { type: "dots" }),
|
|
201
|
+
" Loading more..."
|
|
202
|
+
] }),
|
|
203
|
+
copied && /* @__PURE__ */ jsx4(Text4, { color: "green", children: "Copied URL to clipboard!" }),
|
|
204
|
+
/* @__PURE__ */ jsx4(
|
|
205
|
+
StatusBar,
|
|
206
|
+
{
|
|
207
|
+
hints: [
|
|
208
|
+
{ key: "\u2191\u2193", label: "navigate" },
|
|
209
|
+
{ key: "c", label: "opy url" },
|
|
210
|
+
{ key: "\u2190", label: "back" }
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
)
|
|
214
|
+
] });
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/interactive/app.tsx
|
|
218
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
219
|
+
function App() {
|
|
220
|
+
const [screen, setScreen] = useState3({ type: "buckets" });
|
|
221
|
+
const [email, setEmail] = useState3();
|
|
222
|
+
useEffect3(() => {
|
|
223
|
+
const stow = createStow();
|
|
224
|
+
stow.whoami().then((data) => setEmail(data.user.email)).catch(() => {
|
|
225
|
+
});
|
|
226
|
+
}, []);
|
|
227
|
+
if (screen.type === "files") {
|
|
228
|
+
return /* @__PURE__ */ jsx5(
|
|
229
|
+
FileList,
|
|
230
|
+
{
|
|
231
|
+
bucket: screen.bucket,
|
|
232
|
+
onBack: () => setScreen({ type: "buckets" })
|
|
233
|
+
}
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
return /* @__PURE__ */ jsx5(
|
|
237
|
+
BucketList,
|
|
238
|
+
{
|
|
239
|
+
email,
|
|
240
|
+
onSelect: (bucket) => setScreen({ type: "files", bucket })
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
function startInteractive() {
|
|
245
|
+
render(/* @__PURE__ */ jsx5(App, {}));
|
|
246
|
+
}
|
|
247
|
+
export {
|
|
248
|
+
startInteractive
|
|
249
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adminRequest
|
|
3
|
+
} from "./chunk-QF7PVPWQ.js";
|
|
4
|
+
import {
|
|
5
|
+
output
|
|
6
|
+
} from "./chunk-P2BKGBQE.js";
|
|
7
|
+
import "./chunk-TOADDO2F.js";
|
|
8
|
+
|
|
9
|
+
// src/commands/admin/backfill.ts
|
|
10
|
+
async function runBackfill(type, options) {
|
|
11
|
+
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
|
|
12
|
+
const body = {};
|
|
13
|
+
if (options.bucket) {
|
|
14
|
+
body.bucketId = options.bucket;
|
|
15
|
+
}
|
|
16
|
+
if (parsedLimit && parsedLimit > 0) {
|
|
17
|
+
body.limit = parsedLimit;
|
|
18
|
+
}
|
|
19
|
+
if (options.dryRun) {
|
|
20
|
+
body.dryRun = true;
|
|
21
|
+
}
|
|
22
|
+
const result = await adminRequest({
|
|
23
|
+
method: "POST",
|
|
24
|
+
path: `/internal/files/${type}/backfill`,
|
|
25
|
+
body
|
|
26
|
+
});
|
|
27
|
+
output(
|
|
28
|
+
result,
|
|
29
|
+
() => {
|
|
30
|
+
const lines = [];
|
|
31
|
+
if (result.dryRun) {
|
|
32
|
+
lines.push(
|
|
33
|
+
`[dry run] ${result.remaining} files need ${type} processing`
|
|
34
|
+
);
|
|
35
|
+
return lines.join("\n");
|
|
36
|
+
}
|
|
37
|
+
lines.push(`Enqueued: ${result.enqueued ?? 0}`);
|
|
38
|
+
lines.push(`Remaining: ${result.remaining}`);
|
|
39
|
+
if (result.errors && result.errors.length > 0) {
|
|
40
|
+
lines.push(`
|
|
41
|
+
Errors (${result.errors.length}):`);
|
|
42
|
+
for (const err of result.errors) {
|
|
43
|
+
lines.push(` ${err.fileId}: ${err.error}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (result.nextCursor) {
|
|
47
|
+
lines.push("\n(more files available -- run again to continue)");
|
|
48
|
+
}
|
|
49
|
+
return lines.join("\n");
|
|
50
|
+
},
|
|
51
|
+
{ json: options.json }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
async function backfillDimensions(options) {
|
|
55
|
+
await runBackfill("dimensions", options);
|
|
56
|
+
}
|
|
57
|
+
async function backfillColors(options) {
|
|
58
|
+
await runBackfill("colors", options);
|
|
59
|
+
}
|
|
60
|
+
async function backfillEmbeddings(options) {
|
|
61
|
+
await runBackfill("embeddings", options);
|
|
62
|
+
}
|
|
63
|
+
export {
|
|
64
|
+
backfillColors,
|
|
65
|
+
backfillDimensions,
|
|
66
|
+
backfillEmbeddings
|
|
67
|
+
};
|