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 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
+ };