stow-cli 2.2.0 → 2.2.3
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 +10 -10
- package/dist/app-ZIHTOHXL.js +255 -0
- package/dist/backfill-BG65X4TP.js +65 -0
- package/dist/backfill-JCNPLFJU.js +67 -0
- package/dist/backfill-KW46AEAL.js +67 -0
- package/dist/backfill-VAORMLMY.js +67 -0
- package/dist/buckets-AFNX7FV3.js +137 -0
- package/dist/buckets-FPMMPRR2.js +130 -0
- package/dist/buckets-JJBWUVKF.js +137 -0
- package/dist/buckets-VYI2QVLO.js +137 -0
- package/dist/chunk-533UGNLM.js +42 -0
- package/dist/chunk-5BVMPHKH.js +147 -0
- package/dist/chunk-5IX3ASXH.js +153 -0
- package/dist/chunk-AHBVZRDR.js +29 -0
- package/dist/chunk-KPIQZBTO.js +151 -0
- package/dist/chunk-MYFLRBWC.js +312 -0
- package/dist/chunk-NBHBVKP5.js +54 -0
- package/dist/chunk-PE6V3MVP.js +46 -0
- package/dist/chunk-RH4BOSYB.js +153 -0
- package/dist/chunk-XVKIRHTX.js +29 -0
- package/dist/cli.js +184 -200
- package/dist/delete-3UDS4RMH.js +34 -0
- package/dist/delete-CQJEGLP3.js +34 -0
- package/dist/describe-CU5FBHZS.js +79 -0
- package/dist/describe-HSEHMJVD.js +79 -0
- package/dist/describe-NH3K3LLW.js +79 -0
- package/dist/describe-W3ED4VW3.js +79 -0
- package/dist/drops-XO4CZ4BH.js +39 -0
- package/dist/files-BIMA5L2G.js +206 -0
- package/dist/files-CFOTEASC.js +206 -0
- package/dist/files-SQURZ7VO.js +194 -0
- package/dist/files-XU6MDPP4.js +206 -0
- package/dist/health-3U3RHXFS.js +56 -0
- package/dist/health-SH6T6DZS.js +61 -0
- package/dist/health-TIJU6U2D.js +61 -0
- package/dist/health-YLNNKAFP.js +61 -0
- package/dist/jobs-HUW6Z6A7.js +87 -0
- package/dist/jobs-KK5IZYO5.js +99 -0
- package/dist/jobs-RMRGXLAA.js +90 -0
- package/dist/jobs-ROJFRPMR.js +90 -0
- package/dist/jobs-SX7DIN6T.js +90 -0
- package/dist/jobs-TND5AHCL.js +102 -0
- package/dist/jobs-TOLVGN6K.js +102 -0
- package/dist/jobs-XUAXWUAK.js +102 -0
- package/dist/maintenance-6XNJ56LL.js +79 -0
- package/dist/maintenance-7UBKZOR3.js +79 -0
- package/dist/maintenance-US3PUKFF.js +79 -0
- package/dist/maintenance-V2TXPXQE.js +79 -0
- package/dist/mcp-TUZZB2C7.js +189 -0
- package/dist/profiles-FOLKZZRU.js +53 -0
- package/dist/profiles-MB3TZQE4.js +53 -0
- package/dist/profiles-NVCJCYXR.js +53 -0
- package/dist/profiles-XXVM3UKI.js +53 -0
- package/dist/queues-AUGTAFBT.js +61 -0
- package/dist/queues-MTA2RWUP.js +56 -0
- package/dist/queues-NR25TGT7.js +61 -0
- package/dist/queues-X6IU3KBZ.js +61 -0
- package/dist/search-ETC2EXKM.js +135 -0
- package/dist/search-ICJO264J.js +135 -0
- package/dist/search-ULMFDWHE.js +135 -0
- package/dist/search-UWLK4OL2.js +119 -0
- package/dist/tags-75SSHS26.js +90 -0
- package/dist/tags-OFZQ2XCX.js +90 -0
- package/dist/tags-TBFPDHIQ.js +90 -0
- package/dist/tags-V43DCLPQ.js +90 -0
- package/dist/upload-F5I2SJRB.js +126 -0
- package/dist/upload-N7NAVN3Q.js +126 -0
- package/dist/whoami-WUQDFC5P.js +28 -0
- package/package.json +20 -20
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -31,11 +31,11 @@ stow search text 'sunset beach' -b photos
|
|
|
31
31
|
|
|
32
32
|
Stow CLI uses environment variables for authentication:
|
|
33
33
|
|
|
34
|
-
| Variable
|
|
35
|
-
|
|
36
|
-
| `STOW_API_KEY`
|
|
37
|
-
| `STOW_API_URL`
|
|
38
|
-
| `STOW_ADMIN_SECRET` | No
|
|
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
39
|
|
|
40
40
|
## Commands
|
|
41
41
|
|
|
@@ -352,11 +352,11 @@ stow search text 'landscape' -b photos --json | jq length
|
|
|
352
352
|
|
|
353
353
|
## Environment Variables
|
|
354
354
|
|
|
355
|
-
| Variable
|
|
356
|
-
|
|
357
|
-
| `STOW_API_KEY`
|
|
358
|
-
| `STOW_API_URL`
|
|
359
|
-
| `STOW_ADMIN_SECRET` | --
|
|
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
360
|
|
|
361
361
|
## License
|
|
362
362
|
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatBytes
|
|
3
|
+
} from "./chunk-PE6V3MVP.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 [errorMessage, setErrorMessage] = useState(null);
|
|
64
|
+
const [cursor, setCursor] = useState(0);
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
async function loadBuckets() {
|
|
67
|
+
try {
|
|
68
|
+
const stow = createStow();
|
|
69
|
+
const data = await stow.listBuckets();
|
|
70
|
+
setBuckets(data.buckets.map(toBucket));
|
|
71
|
+
} catch (error) {
|
|
72
|
+
setErrorMessage(error instanceof Error ? error.message : String(error));
|
|
73
|
+
} finally {
|
|
74
|
+
setLoading(false);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
loadBuckets();
|
|
78
|
+
}, []);
|
|
79
|
+
useInput((input, key) => {
|
|
80
|
+
if (input === "q") {
|
|
81
|
+
exit();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (key.upArrow) {
|
|
85
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
86
|
+
} else if (key.downArrow) {
|
|
87
|
+
setCursor((c) => Math.min(buckets.length - 1, c + 1));
|
|
88
|
+
} else if (key.return && buckets.length > 0) {
|
|
89
|
+
onSelect(buckets[cursor]);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
if (loading) {
|
|
93
|
+
return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
94
|
+
/* @__PURE__ */ jsx3(Spinner, { type: "dots" }),
|
|
95
|
+
" Loading buckets..."
|
|
96
|
+
] }) });
|
|
97
|
+
}
|
|
98
|
+
if (errorMessage) {
|
|
99
|
+
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
|
|
100
|
+
"Error: ",
|
|
101
|
+
errorMessage
|
|
102
|
+
] }) });
|
|
103
|
+
}
|
|
104
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
|
|
105
|
+
/* @__PURE__ */ jsx3(Header, { email }),
|
|
106
|
+
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: [
|
|
107
|
+
i === cursor ? "\u25B8 " : " ",
|
|
108
|
+
b.name.padEnd(20),
|
|
109
|
+
`${b.fileCount} files`.padEnd(14),
|
|
110
|
+
formatBytes(b.usageBytes)
|
|
111
|
+
] }) }, b.id)),
|
|
112
|
+
/* @__PURE__ */ jsx3(
|
|
113
|
+
StatusBar,
|
|
114
|
+
{
|
|
115
|
+
hints: [
|
|
116
|
+
{ key: "\u2191\u2193", label: "navigate" },
|
|
117
|
+
{ key: "\u21B5", label: "open" },
|
|
118
|
+
{ key: "q", label: "uit" }
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
] });
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/interactive/components/file-list.tsx
|
|
126
|
+
import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
|
|
127
|
+
import Spinner2 from "ink-spinner";
|
|
128
|
+
import { useEffect as useEffect2, useState as useState2 } from "react";
|
|
129
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
130
|
+
function FileList({ bucket, onBack }) {
|
|
131
|
+
const [files, setFiles] = useState2([]);
|
|
132
|
+
const [loading, setLoading] = useState2(true);
|
|
133
|
+
const [errorMessage, setErrorMessage] = useState2(null);
|
|
134
|
+
const [cursor, setCursor] = useState2(0);
|
|
135
|
+
const [nextCursor, setNextCursor] = useState2(null);
|
|
136
|
+
const [copied, setCopied] = useState2(false);
|
|
137
|
+
async function loadFiles(pageCursor) {
|
|
138
|
+
setLoading(true);
|
|
139
|
+
try {
|
|
140
|
+
const stow = createStow();
|
|
141
|
+
const data = await stow.listFiles({
|
|
142
|
+
bucket: bucket.name,
|
|
143
|
+
...pageCursor ? { cursor: pageCursor } : {}
|
|
144
|
+
});
|
|
145
|
+
setFiles((prev) => pageCursor ? [...prev, ...data.files] : data.files);
|
|
146
|
+
setNextCursor(data.nextCursor);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
setErrorMessage(error instanceof Error ? error.message : String(error));
|
|
149
|
+
} finally {
|
|
150
|
+
setLoading(false);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
useEffect2(() => {
|
|
154
|
+
loadFiles();
|
|
155
|
+
}, []);
|
|
156
|
+
async function copyCurrentFileUrl() {
|
|
157
|
+
const currentFile = files[cursor];
|
|
158
|
+
if (!currentFile) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (!currentFile.url) {
|
|
162
|
+
setErrorMessage("Selected file has no URL");
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const { default: clipboard } = await import("clipboardy");
|
|
166
|
+
await clipboard.write(currentFile.url);
|
|
167
|
+
setCopied(true);
|
|
168
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
169
|
+
}
|
|
170
|
+
useInput2((input, key) => {
|
|
171
|
+
if (key.escape || key.backspace || key.leftArrow && !loading) {
|
|
172
|
+
onBack();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (key.upArrow) {
|
|
176
|
+
setCursor((c) => Math.max(0, c - 1));
|
|
177
|
+
} else if (key.downArrow) {
|
|
178
|
+
setCursor((c) => {
|
|
179
|
+
const next = Math.min(files.length - 1, c + 1);
|
|
180
|
+
if (next >= files.length - 3 && nextCursor && !loading) {
|
|
181
|
+
loadFiles(nextCursor);
|
|
182
|
+
}
|
|
183
|
+
return next;
|
|
184
|
+
});
|
|
185
|
+
} else if (input === "c" && files.length > 0) {
|
|
186
|
+
copyCurrentFileUrl();
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
if (errorMessage) {
|
|
190
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
191
|
+
/* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
|
|
192
|
+
"Error: ",
|
|
193
|
+
errorMessage
|
|
194
|
+
] }),
|
|
195
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press Esc to go back." })
|
|
196
|
+
] });
|
|
197
|
+
}
|
|
198
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
|
|
199
|
+
/* @__PURE__ */ jsx4(Header, { path: bucket.name, size: formatBytes(bucket.usageBytes) }),
|
|
200
|
+
loading && files.length === 0 && /* @__PURE__ */ jsxs4(Text4, { children: [
|
|
201
|
+
/* @__PURE__ */ jsx4(Spinner2, { type: "dots" }),
|
|
202
|
+
" Loading files..."
|
|
203
|
+
] }),
|
|
204
|
+
!loading && files.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No files in this bucket." }),
|
|
205
|
+
files.length > 0 && files.map((f, i) => /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color: i === cursor ? "cyan" : void 0, children: [
|
|
206
|
+
i === cursor ? "\u25B8 " : " ",
|
|
207
|
+
f.key.padEnd(28),
|
|
208
|
+
formatBytes(f.size).padEnd(10),
|
|
209
|
+
f.lastModified.split("T")[0]
|
|
210
|
+
] }) }, f.key)),
|
|
211
|
+
loading && files.length > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
212
|
+
/* @__PURE__ */ jsx4(Spinner2, { type: "dots" }),
|
|
213
|
+
" Loading more..."
|
|
214
|
+
] }),
|
|
215
|
+
copied && /* @__PURE__ */ jsx4(Text4, { color: "green", children: "Copied URL to clipboard!" }),
|
|
216
|
+
/* @__PURE__ */ jsx4(
|
|
217
|
+
StatusBar,
|
|
218
|
+
{
|
|
219
|
+
hints: [
|
|
220
|
+
{ key: "\u2191\u2193", label: "navigate" },
|
|
221
|
+
{ key: "c", label: "opy url" },
|
|
222
|
+
{ key: "\u2190", label: "back" }
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
)
|
|
226
|
+
] });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// src/interactive/app.tsx
|
|
230
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
231
|
+
function App() {
|
|
232
|
+
const [screen, setScreen] = useState3({ type: "buckets" });
|
|
233
|
+
const [email, setEmail] = useState3();
|
|
234
|
+
useEffect3(() => {
|
|
235
|
+
async function loadEmail() {
|
|
236
|
+
const stow = createStow();
|
|
237
|
+
try {
|
|
238
|
+
const data = await stow.whoami();
|
|
239
|
+
setEmail(data.user.email);
|
|
240
|
+
} catch {
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
loadEmail();
|
|
244
|
+
}, []);
|
|
245
|
+
if (screen.type === "files") {
|
|
246
|
+
return /* @__PURE__ */ jsx5(FileList, { bucket: screen.bucket, onBack: () => setScreen({ type: "buckets" }) });
|
|
247
|
+
}
|
|
248
|
+
return /* @__PURE__ */ jsx5(BucketList, { email, onSelect: (bucket) => setScreen({ type: "files", bucket }) });
|
|
249
|
+
}
|
|
250
|
+
function startInteractive() {
|
|
251
|
+
render(/* @__PURE__ */ jsx5(App, {}));
|
|
252
|
+
}
|
|
253
|
+
export {
|
|
254
|
+
startInteractive
|
|
255
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adminRequest
|
|
3
|
+
} from "./chunk-QF7PVPWQ.js";
|
|
4
|
+
import {
|
|
5
|
+
output
|
|
6
|
+
} from "./chunk-KPIQZBTO.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(`[dry run] ${result.remaining} files need ${type} processing`);
|
|
33
|
+
return lines.join("\n");
|
|
34
|
+
}
|
|
35
|
+
lines.push(`Enqueued: ${result.enqueued ?? 0}`);
|
|
36
|
+
lines.push(`Remaining: ${result.remaining}`);
|
|
37
|
+
if (result.errors && result.errors.length > 0) {
|
|
38
|
+
lines.push(`
|
|
39
|
+
Errors (${result.errors.length}):`);
|
|
40
|
+
for (const err of result.errors) {
|
|
41
|
+
lines.push(` ${err.fileId}: ${err.error}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (result.nextCursor) {
|
|
45
|
+
lines.push("\n(more files available -- run again to continue)");
|
|
46
|
+
}
|
|
47
|
+
return lines.join("\n");
|
|
48
|
+
},
|
|
49
|
+
{ json: options.json }
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
async function backfillDimensions(options) {
|
|
53
|
+
await runBackfill("dimensions", options);
|
|
54
|
+
}
|
|
55
|
+
async function backfillColors(options) {
|
|
56
|
+
await runBackfill("colors", options);
|
|
57
|
+
}
|
|
58
|
+
async function backfillEmbeddings(options) {
|
|
59
|
+
await runBackfill("embeddings", options);
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
backfillColors,
|
|
63
|
+
backfillDimensions,
|
|
64
|
+
backfillEmbeddings
|
|
65
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adminRequest
|
|
3
|
+
} from "./chunk-QF7PVPWQ.js";
|
|
4
|
+
import {
|
|
5
|
+
output
|
|
6
|
+
} from "./chunk-5BVMPHKH.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
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adminRequest
|
|
3
|
+
} from "./chunk-QF7PVPWQ.js";
|
|
4
|
+
import {
|
|
5
|
+
output
|
|
6
|
+
} from "./chunk-RH4BOSYB.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
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adminRequest
|
|
3
|
+
} from "./chunk-QF7PVPWQ.js";
|
|
4
|
+
import {
|
|
5
|
+
output
|
|
6
|
+
} from "./chunk-5IX3ASXH.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
|
+
};
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseJsonInput
|
|
3
|
+
} from "./chunk-3BLL5SQJ.js";
|
|
4
|
+
import {
|
|
5
|
+
validateBucketName
|
|
6
|
+
} from "./chunk-PLZFHPLC.js";
|
|
7
|
+
import {
|
|
8
|
+
isJsonOutput,
|
|
9
|
+
output
|
|
10
|
+
} from "./chunk-5IX3ASXH.js";
|
|
11
|
+
import {
|
|
12
|
+
formatBytes,
|
|
13
|
+
formatTable
|
|
14
|
+
} from "./chunk-FZGOTXTE.js";
|
|
15
|
+
import {
|
|
16
|
+
createStow
|
|
17
|
+
} from "./chunk-5LU25QZK.js";
|
|
18
|
+
import "./chunk-TOADDO2F.js";
|
|
19
|
+
|
|
20
|
+
// src/commands/buckets.ts
|
|
21
|
+
async function listBuckets() {
|
|
22
|
+
const stow = createStow();
|
|
23
|
+
const data = await stow.listBuckets();
|
|
24
|
+
if (data.buckets.length === 0) {
|
|
25
|
+
if (isJsonOutput()) {
|
|
26
|
+
output(data);
|
|
27
|
+
} else {
|
|
28
|
+
console.log(
|
|
29
|
+
"No buckets yet. Create one with: stow buckets create <name>"
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
output(data, () => {
|
|
35
|
+
const rows = data.buckets.map((b) => [
|
|
36
|
+
b.name,
|
|
37
|
+
b.isPublic ? "public" : "private",
|
|
38
|
+
b.searchable ? "yes" : "no",
|
|
39
|
+
`${b.fileCount ?? 0} files`,
|
|
40
|
+
formatBytes(b.usageBytes ?? 0),
|
|
41
|
+
b.description || ""
|
|
42
|
+
]);
|
|
43
|
+
return formatTable(
|
|
44
|
+
["Name", "Access", "Search", "Files", "Size", "Description"],
|
|
45
|
+
rows
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function createBucket(name, options) {
|
|
50
|
+
const input = parseJsonInput(options.inputJson, {
|
|
51
|
+
name,
|
|
52
|
+
description: options.description,
|
|
53
|
+
public: options.public
|
|
54
|
+
});
|
|
55
|
+
validateBucketName(input.name);
|
|
56
|
+
if (options.dryRun) {
|
|
57
|
+
console.log(
|
|
58
|
+
JSON.stringify(
|
|
59
|
+
{
|
|
60
|
+
dryRun: true,
|
|
61
|
+
action: "createBucket",
|
|
62
|
+
details: {
|
|
63
|
+
name: input.name,
|
|
64
|
+
description: input.description ?? null,
|
|
65
|
+
isPublic: input.public ?? false
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
null,
|
|
69
|
+
2
|
|
70
|
+
)
|
|
71
|
+
);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const stow = createStow();
|
|
75
|
+
const bucket = await stow.createBucket({
|
|
76
|
+
name: input.name,
|
|
77
|
+
...input.description ? { description: input.description } : {},
|
|
78
|
+
...input.public ? { isPublic: true } : {}
|
|
79
|
+
});
|
|
80
|
+
console.log(`Created bucket: ${bucket.name}`);
|
|
81
|
+
}
|
|
82
|
+
async function renameBucket(name, newName, options) {
|
|
83
|
+
const input = parseJsonInput(
|
|
84
|
+
options.inputJson,
|
|
85
|
+
{ name, newName }
|
|
86
|
+
);
|
|
87
|
+
validateBucketName(input.name);
|
|
88
|
+
validateBucketName(input.newName);
|
|
89
|
+
if (options.dryRun) {
|
|
90
|
+
console.log(
|
|
91
|
+
JSON.stringify(
|
|
92
|
+
{
|
|
93
|
+
dryRun: true,
|
|
94
|
+
action: "renameBucket",
|
|
95
|
+
details: { name: input.name, newName: input.newName }
|
|
96
|
+
},
|
|
97
|
+
null,
|
|
98
|
+
2
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (!options.yes) {
|
|
104
|
+
console.error(
|
|
105
|
+
"Warning: Renaming a bucket will break any existing URLs using the old name."
|
|
106
|
+
);
|
|
107
|
+
console.error("Use --yes to skip this warning.");
|
|
108
|
+
}
|
|
109
|
+
const stow = createStow();
|
|
110
|
+
const bucket = await stow.renameBucket(input.name, input.newName);
|
|
111
|
+
console.log(`Renamed bucket: ${input.name} \u2192 ${bucket.name}`);
|
|
112
|
+
}
|
|
113
|
+
async function deleteBucket(id, options = {}) {
|
|
114
|
+
if (options.dryRun) {
|
|
115
|
+
console.log(
|
|
116
|
+
JSON.stringify(
|
|
117
|
+
{
|
|
118
|
+
dryRun: true,
|
|
119
|
+
action: "deleteBucket",
|
|
120
|
+
details: { id }
|
|
121
|
+
},
|
|
122
|
+
null,
|
|
123
|
+
2
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const stow = createStow();
|
|
129
|
+
await stow.deleteBucket(id);
|
|
130
|
+
console.log(`Deleted bucket: ${id}`);
|
|
131
|
+
}
|
|
132
|
+
export {
|
|
133
|
+
createBucket,
|
|
134
|
+
deleteBucket,
|
|
135
|
+
listBuckets,
|
|
136
|
+
renameBucket
|
|
137
|
+
};
|