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
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateBucketName,
|
|
3
|
+
validateFileKey
|
|
4
|
+
} from "./chunk-OHAFRKN5.js";
|
|
5
|
+
import {
|
|
6
|
+
createStow
|
|
7
|
+
} from "./chunk-5LU25QZK.js";
|
|
8
|
+
import "./chunk-TOADDO2F.js";
|
|
9
|
+
|
|
10
|
+
// src/commands/delete.ts
|
|
11
|
+
async function deleteFile(bucket, key, options = {}) {
|
|
12
|
+
validateBucketName(bucket);
|
|
13
|
+
validateFileKey(key);
|
|
14
|
+
if (options.dryRun) {
|
|
15
|
+
console.log(
|
|
16
|
+
JSON.stringify(
|
|
17
|
+
{
|
|
18
|
+
dryRun: true,
|
|
19
|
+
action: "deleteFile",
|
|
20
|
+
details: { bucket, key }
|
|
21
|
+
},
|
|
22
|
+
null,
|
|
23
|
+
2
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const stow = createStow();
|
|
29
|
+
await stow.deleteFile(key, { bucket });
|
|
30
|
+
console.log(`Deleted: ${key} from ${bucket}`);
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
deleteFile
|
|
34
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateBucketName,
|
|
3
|
+
validateFileKey
|
|
4
|
+
} from "./chunk-PLZFHPLC.js";
|
|
5
|
+
import {
|
|
6
|
+
createStow
|
|
7
|
+
} from "./chunk-5LU25QZK.js";
|
|
8
|
+
import "./chunk-TOADDO2F.js";
|
|
9
|
+
|
|
10
|
+
// src/commands/delete.ts
|
|
11
|
+
async function deleteFile(bucket, key, options = {}) {
|
|
12
|
+
validateBucketName(bucket);
|
|
13
|
+
validateFileKey(key);
|
|
14
|
+
if (options.dryRun) {
|
|
15
|
+
console.log(
|
|
16
|
+
JSON.stringify(
|
|
17
|
+
{
|
|
18
|
+
dryRun: true,
|
|
19
|
+
action: "deleteFile",
|
|
20
|
+
details: { bucket, key }
|
|
21
|
+
},
|
|
22
|
+
null,
|
|
23
|
+
2
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const stow = createStow();
|
|
29
|
+
await stow.deleteFile(key, { bucket });
|
|
30
|
+
console.log(`Deleted: ${key} from ${bucket}`);
|
|
31
|
+
}
|
|
32
|
+
export {
|
|
33
|
+
deleteFile
|
|
34
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLI_DOCS
|
|
3
|
+
} from "./chunk-XJDK2CBE.js";
|
|
4
|
+
import {
|
|
5
|
+
output
|
|
6
|
+
} from "./chunk-P2BKGBQE.js";
|
|
7
|
+
|
|
8
|
+
// src/commands/describe.ts
|
|
9
|
+
var COMMAND_MAP = {
|
|
10
|
+
upload: "upload",
|
|
11
|
+
drop: "drop",
|
|
12
|
+
delete: "delete",
|
|
13
|
+
whoami: "whoami",
|
|
14
|
+
open: "open",
|
|
15
|
+
buckets: "buckets",
|
|
16
|
+
"buckets.create": "bucketsCreate",
|
|
17
|
+
"buckets.rename": "bucketsRename",
|
|
18
|
+
"buckets.delete": "bucketsDelete",
|
|
19
|
+
files: "files",
|
|
20
|
+
"files.get": "filesGet",
|
|
21
|
+
"files.update": "filesUpdate",
|
|
22
|
+
"files.enrich": "filesEnrich",
|
|
23
|
+
"files.missing": "filesMissing",
|
|
24
|
+
search: "search",
|
|
25
|
+
"search.text": "searchText",
|
|
26
|
+
"search.similar": "searchSimilar",
|
|
27
|
+
"search.color": "searchColor",
|
|
28
|
+
"search.diverse": "searchDiverse",
|
|
29
|
+
tags: "tags",
|
|
30
|
+
"tags.create": "tagsCreate",
|
|
31
|
+
"tags.delete": "tagsDelete",
|
|
32
|
+
drops: "drops",
|
|
33
|
+
"drops.delete": "dropsDelete",
|
|
34
|
+
profiles: "profiles",
|
|
35
|
+
"profiles.create": "profilesCreate",
|
|
36
|
+
"profiles.get": "profilesGet",
|
|
37
|
+
"profiles.delete": "profilesDelete",
|
|
38
|
+
jobs: "jobs",
|
|
39
|
+
"jobs.retry": "jobsRetry",
|
|
40
|
+
"jobs.delete": "jobsDelete",
|
|
41
|
+
admin: "admin",
|
|
42
|
+
"admin.health": "adminHealth",
|
|
43
|
+
"admin.backfill": "adminBackfill",
|
|
44
|
+
"admin.cleanup-drops": "adminCleanupDrops",
|
|
45
|
+
"admin.purge-events": "adminPurgeEvents",
|
|
46
|
+
"admin.reconcile-files": "adminReconcileFiles",
|
|
47
|
+
"admin.retry-sync-failures": "adminRetrySyncFailures",
|
|
48
|
+
"admin.jobs": "adminJobs",
|
|
49
|
+
"admin.jobs.retry": "adminJobsRetry",
|
|
50
|
+
"admin.jobs.delete": "adminJobsDelete",
|
|
51
|
+
"admin.queues": "adminQueues",
|
|
52
|
+
"admin.queues.clean": "adminQueuesClean"
|
|
53
|
+
};
|
|
54
|
+
function describeCommand(commandPath) {
|
|
55
|
+
if (!commandPath) {
|
|
56
|
+
output({
|
|
57
|
+
commands: Object.keys(COMMAND_MAP).sort()
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const docKey = COMMAND_MAP[commandPath];
|
|
62
|
+
if (!(docKey && CLI_DOCS[docKey])) {
|
|
63
|
+
console.error(`Unknown command: ${commandPath}`);
|
|
64
|
+
console.error(`Available: ${Object.keys(COMMAND_MAP).sort().join(", ")}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const doc = CLI_DOCS[docKey];
|
|
69
|
+
output({
|
|
70
|
+
command: commandPath,
|
|
71
|
+
description: doc.description,
|
|
72
|
+
usage: doc.usage,
|
|
73
|
+
examples: doc.examples,
|
|
74
|
+
notes: doc.notes ?? []
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
describeCommand
|
|
79
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLI_DOCS
|
|
3
|
+
} from "./chunk-XJDK2CBE.js";
|
|
4
|
+
import {
|
|
5
|
+
outputJson
|
|
6
|
+
} from "./chunk-ELSDWMEB.js";
|
|
7
|
+
|
|
8
|
+
// src/commands/describe.ts
|
|
9
|
+
var COMMAND_MAP = {
|
|
10
|
+
upload: "upload",
|
|
11
|
+
drop: "drop",
|
|
12
|
+
delete: "delete",
|
|
13
|
+
whoami: "whoami",
|
|
14
|
+
open: "open",
|
|
15
|
+
buckets: "buckets",
|
|
16
|
+
"buckets.create": "bucketsCreate",
|
|
17
|
+
"buckets.rename": "bucketsRename",
|
|
18
|
+
"buckets.delete": "bucketsDelete",
|
|
19
|
+
files: "files",
|
|
20
|
+
"files.get": "filesGet",
|
|
21
|
+
"files.update": "filesUpdate",
|
|
22
|
+
"files.enrich": "filesEnrich",
|
|
23
|
+
"files.missing": "filesMissing",
|
|
24
|
+
search: "search",
|
|
25
|
+
"search.text": "searchText",
|
|
26
|
+
"search.similar": "searchSimilar",
|
|
27
|
+
"search.color": "searchColor",
|
|
28
|
+
"search.diverse": "searchDiverse",
|
|
29
|
+
tags: "tags",
|
|
30
|
+
"tags.create": "tagsCreate",
|
|
31
|
+
"tags.delete": "tagsDelete",
|
|
32
|
+
drops: "drops",
|
|
33
|
+
"drops.delete": "dropsDelete",
|
|
34
|
+
profiles: "profiles",
|
|
35
|
+
"profiles.create": "profilesCreate",
|
|
36
|
+
"profiles.get": "profilesGet",
|
|
37
|
+
"profiles.delete": "profilesDelete",
|
|
38
|
+
jobs: "jobs",
|
|
39
|
+
"jobs.retry": "jobsRetry",
|
|
40
|
+
"jobs.delete": "jobsDelete",
|
|
41
|
+
admin: "admin",
|
|
42
|
+
"admin.health": "adminHealth",
|
|
43
|
+
"admin.backfill": "adminBackfill",
|
|
44
|
+
"admin.cleanup-drops": "adminCleanupDrops",
|
|
45
|
+
"admin.purge-events": "adminPurgeEvents",
|
|
46
|
+
"admin.reconcile-files": "adminReconcileFiles",
|
|
47
|
+
"admin.retry-sync-failures": "adminRetrySyncFailures",
|
|
48
|
+
"admin.jobs": "adminJobs",
|
|
49
|
+
"admin.jobs.retry": "adminJobsRetry",
|
|
50
|
+
"admin.jobs.delete": "adminJobsDelete",
|
|
51
|
+
"admin.queues": "adminQueues",
|
|
52
|
+
"admin.queues.clean": "adminQueuesClean"
|
|
53
|
+
};
|
|
54
|
+
function describeCommand(commandPath) {
|
|
55
|
+
if (!commandPath) {
|
|
56
|
+
outputJson({
|
|
57
|
+
commands: Object.keys(COMMAND_MAP).sort()
|
|
58
|
+
});
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const docKey = COMMAND_MAP[commandPath];
|
|
62
|
+
if (!docKey || !CLI_DOCS[docKey]) {
|
|
63
|
+
console.error(`Unknown command: ${commandPath}`);
|
|
64
|
+
console.error(`Available: ${Object.keys(COMMAND_MAP).sort().join(", ")}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const doc = CLI_DOCS[docKey];
|
|
69
|
+
outputJson({
|
|
70
|
+
command: commandPath,
|
|
71
|
+
description: doc.description,
|
|
72
|
+
usage: doc.usage,
|
|
73
|
+
examples: doc.examples,
|
|
74
|
+
notes: doc.notes ?? []
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export {
|
|
78
|
+
describeCommand
|
|
79
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {
|
|
2
|
+
formatBytes,
|
|
3
|
+
formatTable,
|
|
4
|
+
usageBar
|
|
5
|
+
} from "./chunk-FZGOTXTE.js";
|
|
6
|
+
import {
|
|
7
|
+
createStow
|
|
8
|
+
} from "./chunk-5LU25QZK.js";
|
|
9
|
+
import "./chunk-TOADDO2F.js";
|
|
10
|
+
|
|
11
|
+
// src/commands/drops.ts
|
|
12
|
+
async function listDrops() {
|
|
13
|
+
const stow = createStow();
|
|
14
|
+
const data = await stow.listDrops();
|
|
15
|
+
if (data.drops.length === 0) {
|
|
16
|
+
console.log("No drops yet. Create one with: stow drop <file>");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const rows = data.drops.map((d) => [
|
|
20
|
+
d.filename,
|
|
21
|
+
formatBytes(Number(d.size)),
|
|
22
|
+
d.contentType,
|
|
23
|
+
d.url
|
|
24
|
+
]);
|
|
25
|
+
console.log(formatTable(["Filename", "Size", "Type", "URL"], rows));
|
|
26
|
+
console.log(
|
|
27
|
+
`
|
|
28
|
+
Storage: ${usageBar(data.usage.bytes, data.usage.limit)} ${formatBytes(data.usage.bytes)} / ${formatBytes(data.usage.limit)}`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
async function deleteDrop(id) {
|
|
32
|
+
const stow = createStow();
|
|
33
|
+
await stow.deleteDrop(id);
|
|
34
|
+
console.log(`Deleted drop: ${id}`);
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
deleteDrop,
|
|
38
|
+
listDrops
|
|
39
|
+
};
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateBucketName,
|
|
3
|
+
validateFileKey
|
|
4
|
+
} from "./chunk-OHAFRKN5.js";
|
|
5
|
+
import {
|
|
6
|
+
formatBytes,
|
|
7
|
+
formatTable,
|
|
8
|
+
outputJson
|
|
9
|
+
} from "./chunk-ELSDWMEB.js";
|
|
10
|
+
import {
|
|
11
|
+
createStow
|
|
12
|
+
} from "./chunk-5LU25QZK.js";
|
|
13
|
+
import {
|
|
14
|
+
getApiKey,
|
|
15
|
+
getBaseUrl
|
|
16
|
+
} from "./chunk-TOADDO2F.js";
|
|
17
|
+
|
|
18
|
+
// src/commands/files.ts
|
|
19
|
+
async function listFiles(bucket, options) {
|
|
20
|
+
const stow = createStow();
|
|
21
|
+
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
|
|
22
|
+
const data = await stow.listFiles({
|
|
23
|
+
bucket,
|
|
24
|
+
...options.search ? { prefix: options.search } : {},
|
|
25
|
+
...parsedLimit && Number.isFinite(parsedLimit) && parsedLimit > 0 ? { limit: parsedLimit } : {}
|
|
26
|
+
});
|
|
27
|
+
if (options.json) {
|
|
28
|
+
outputJson(data);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (data.files.length === 0) {
|
|
32
|
+
console.log(`No files in bucket '${bucket}'.`);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const rows = data.files.map((f) => [
|
|
36
|
+
f.key,
|
|
37
|
+
formatBytes(f.size),
|
|
38
|
+
f.lastModified.split("T")[0] ?? f.lastModified
|
|
39
|
+
]);
|
|
40
|
+
console.log(formatTable(["Key", "Size", "Modified"], rows));
|
|
41
|
+
if (data.nextCursor) {
|
|
42
|
+
console.log("\n(more files available \u2014 use --limit to see more)");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function getFile(bucket, key, options) {
|
|
46
|
+
const stow = createStow();
|
|
47
|
+
const file = await stow.getFile(key, { bucket });
|
|
48
|
+
if (options.json) {
|
|
49
|
+
outputJson(file);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
console.log(
|
|
53
|
+
formatTable(
|
|
54
|
+
["Field", "Value"],
|
|
55
|
+
[
|
|
56
|
+
["Key", file.key],
|
|
57
|
+
["Size", formatBytes(file.size)],
|
|
58
|
+
["Type", file.contentType],
|
|
59
|
+
["Created", file.createdAt],
|
|
60
|
+
["URL", file.url ?? "(private)"],
|
|
61
|
+
[
|
|
62
|
+
"Dimensions",
|
|
63
|
+
file.width && file.height ? `${file.width}\xD7${file.height}` : "\u2014"
|
|
64
|
+
],
|
|
65
|
+
["Duration", file.duration ? `${file.duration}s` : "\u2014"],
|
|
66
|
+
["Embedding", file.embeddingStatus ?? "\u2014"]
|
|
67
|
+
]
|
|
68
|
+
)
|
|
69
|
+
);
|
|
70
|
+
if (file.metadata && Object.keys(file.metadata).length > 0) {
|
|
71
|
+
console.log("\nMetadata:");
|
|
72
|
+
for (const [k, v] of Object.entries(file.metadata)) {
|
|
73
|
+
console.log(` ${k}: ${v}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function updateFile(bucket, key, options) {
|
|
78
|
+
validateBucketName(bucket);
|
|
79
|
+
validateFileKey(key);
|
|
80
|
+
if (!options.metadata || options.metadata.length === 0) {
|
|
81
|
+
console.error("Error: At least one -m key=value pair is required.");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
const metadata = {};
|
|
85
|
+
for (const pair of options.metadata) {
|
|
86
|
+
const idx = pair.indexOf("=");
|
|
87
|
+
if (idx === -1) {
|
|
88
|
+
console.error(`Error: Invalid metadata format '${pair}'. Use key=value.`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
metadata[pair.slice(0, idx)] = pair.slice(idx + 1);
|
|
92
|
+
}
|
|
93
|
+
if (options.dryRun) {
|
|
94
|
+
console.log(
|
|
95
|
+
JSON.stringify(
|
|
96
|
+
{
|
|
97
|
+
dryRun: true,
|
|
98
|
+
action: "updateFile",
|
|
99
|
+
details: { bucket, key, metadata }
|
|
100
|
+
},
|
|
101
|
+
null,
|
|
102
|
+
2
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const stow = createStow();
|
|
108
|
+
const file = await stow.updateFileMetadata(key, metadata, { bucket });
|
|
109
|
+
if (options.json) {
|
|
110
|
+
outputJson(file);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
console.log(`Updated ${key}`);
|
|
114
|
+
}
|
|
115
|
+
async function enrichFile(bucket, key) {
|
|
116
|
+
const stow = createStow();
|
|
117
|
+
const results = await Promise.allSettled([
|
|
118
|
+
stow.generateTitle(key, { bucket }),
|
|
119
|
+
stow.generateDescription(key, { bucket }),
|
|
120
|
+
stow.generateAltText(key, { bucket })
|
|
121
|
+
]);
|
|
122
|
+
const labels = ["Title", "Description", "Alt text"];
|
|
123
|
+
for (let i = 0; i < results.length; i++) {
|
|
124
|
+
const result = results[i];
|
|
125
|
+
const label = labels[i];
|
|
126
|
+
if (result.status === "fulfilled") {
|
|
127
|
+
console.log(` ${label}: triggered`);
|
|
128
|
+
} else {
|
|
129
|
+
console.error(` ${label}: failed \u2014 ${result.reason}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
const succeeded = results.filter((r) => r.status === "fulfilled").length;
|
|
133
|
+
console.log(
|
|
134
|
+
`
|
|
135
|
+
Enriched ${key}: ${succeeded}/${results.length} tasks dispatched`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
async function listMissing(bucket, type, options) {
|
|
139
|
+
const validTypes = ["dimensions", "embeddings", "colors"];
|
|
140
|
+
if (!validTypes.includes(type)) {
|
|
141
|
+
console.error(
|
|
142
|
+
`Error: Invalid type '${type}'. Must be one of: ${validTypes.join(", ")}`
|
|
143
|
+
);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
|
|
147
|
+
const baseUrl = getBaseUrl();
|
|
148
|
+
const apiKey = getApiKey();
|
|
149
|
+
const params = new URLSearchParams({
|
|
150
|
+
bucket,
|
|
151
|
+
missing: type,
|
|
152
|
+
...parsedLimit && Number.isFinite(parsedLimit) && parsedLimit > 0 ? { limit: String(parsedLimit) } : {}
|
|
153
|
+
});
|
|
154
|
+
const res = await fetch(`${baseUrl}/files?${params}`, {
|
|
155
|
+
headers: { "x-api-key": apiKey }
|
|
156
|
+
});
|
|
157
|
+
if (!res.ok) {
|
|
158
|
+
const body = await res.json().catch(() => ({}));
|
|
159
|
+
throw new Error(body.error ?? `HTTP ${res.status}`);
|
|
160
|
+
}
|
|
161
|
+
const data = await res.json();
|
|
162
|
+
if (options.json) {
|
|
163
|
+
outputJson(data);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
if (data.files.length === 0) {
|
|
167
|
+
console.log(`No files missing ${type} in bucket '${bucket}'.`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const rows = data.files.map((f) => [
|
|
171
|
+
f.key,
|
|
172
|
+
formatBytes(f.size),
|
|
173
|
+
f.lastModified.split("T")[0] ?? f.lastModified
|
|
174
|
+
]);
|
|
175
|
+
console.log(formatTable(["Key", "Size", "Modified"], rows));
|
|
176
|
+
console.log(`
|
|
177
|
+
${data.files.length} files missing ${type}`);
|
|
178
|
+
}
|
|
179
|
+
export {
|
|
180
|
+
enrichFile,
|
|
181
|
+
getFile,
|
|
182
|
+
listFiles,
|
|
183
|
+
listMissing,
|
|
184
|
+
updateFile
|
|
185
|
+
};
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseJsonInput
|
|
3
|
+
} from "./chunk-3BLL5SQJ.js";
|
|
4
|
+
import {
|
|
5
|
+
validateBucketName,
|
|
6
|
+
validateFileKey
|
|
7
|
+
} from "./chunk-PLZFHPLC.js";
|
|
8
|
+
import {
|
|
9
|
+
isJsonOutput,
|
|
10
|
+
output
|
|
11
|
+
} from "./chunk-P2BKGBQE.js";
|
|
12
|
+
import {
|
|
13
|
+
formatBytes,
|
|
14
|
+
formatTable
|
|
15
|
+
} from "./chunk-FZGOTXTE.js";
|
|
16
|
+
import {
|
|
17
|
+
createStow
|
|
18
|
+
} from "./chunk-5LU25QZK.js";
|
|
19
|
+
import {
|
|
20
|
+
getApiKey,
|
|
21
|
+
getBaseUrl
|
|
22
|
+
} from "./chunk-TOADDO2F.js";
|
|
23
|
+
|
|
24
|
+
// src/commands/files.ts
|
|
25
|
+
async function listFiles(bucket, options) {
|
|
26
|
+
const stow = createStow();
|
|
27
|
+
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
|
|
28
|
+
const data = await stow.listFiles({
|
|
29
|
+
bucket,
|
|
30
|
+
...options.search ? { prefix: options.search } : {},
|
|
31
|
+
...parsedLimit && Number.isFinite(parsedLimit) && parsedLimit > 0 ? { limit: parsedLimit } : {}
|
|
32
|
+
});
|
|
33
|
+
if (data.files.length === 0) {
|
|
34
|
+
if (isJsonOutput() || options.json) {
|
|
35
|
+
output(data);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(`No files in bucket '${bucket}'.`);
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (options.json || isJsonOutput()) {
|
|
42
|
+
output(data);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const rows = data.files.map((f) => [
|
|
46
|
+
f.key,
|
|
47
|
+
formatBytes(f.size),
|
|
48
|
+
f.lastModified.split("T")[0] ?? f.lastModified
|
|
49
|
+
]);
|
|
50
|
+
console.log(formatTable(["Key", "Size", "Modified"], rows));
|
|
51
|
+
if (data.nextCursor) {
|
|
52
|
+
console.log("\n(more files available \u2014 use --limit to see more)");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function getFile(bucket, key, _options) {
|
|
56
|
+
const stow = createStow();
|
|
57
|
+
const file = await stow.getFile(key, { bucket });
|
|
58
|
+
output(file, () => {
|
|
59
|
+
const lines = [
|
|
60
|
+
formatTable(
|
|
61
|
+
["Field", "Value"],
|
|
62
|
+
[
|
|
63
|
+
["Key", file.key],
|
|
64
|
+
["Size", formatBytes(file.size)],
|
|
65
|
+
["Type", file.contentType],
|
|
66
|
+
["Created", file.createdAt],
|
|
67
|
+
["URL", file.url ?? "(private)"],
|
|
68
|
+
[
|
|
69
|
+
"Dimensions",
|
|
70
|
+
file.width && file.height ? `${file.width}\xD7${file.height}` : "\u2014"
|
|
71
|
+
],
|
|
72
|
+
["Duration", file.duration ? `${file.duration}s` : "\u2014"],
|
|
73
|
+
["Embedding", file.embeddingStatus ?? "\u2014"]
|
|
74
|
+
]
|
|
75
|
+
)
|
|
76
|
+
];
|
|
77
|
+
if (file.metadata && Object.keys(file.metadata).length > 0) {
|
|
78
|
+
lines.push("\nMetadata:");
|
|
79
|
+
for (const [k, v] of Object.entries(file.metadata)) {
|
|
80
|
+
lines.push(` ${k}: ${v}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return lines.join("\n");
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async function updateFile(bucket, key, options) {
|
|
87
|
+
validateBucketName(bucket);
|
|
88
|
+
validateFileKey(key);
|
|
89
|
+
const flagMetadata = {};
|
|
90
|
+
if (options.metadata && options.metadata.length > 0) {
|
|
91
|
+
for (const pair of options.metadata) {
|
|
92
|
+
const idx = pair.indexOf("=");
|
|
93
|
+
if (idx === -1) {
|
|
94
|
+
console.error(
|
|
95
|
+
`Error: Invalid metadata format '${pair}'. Use key=value.`
|
|
96
|
+
);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
flagMetadata[pair.slice(0, idx)] = pair.slice(idx + 1);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const jsonInput = parseJsonInput(
|
|
103
|
+
options.inputJson,
|
|
104
|
+
{}
|
|
105
|
+
);
|
|
106
|
+
const hasFlagMetadata = Object.keys(flagMetadata).length > 0;
|
|
107
|
+
const metadata = hasFlagMetadata ? { ...jsonInput.metadata ?? {}, ...flagMetadata } : jsonInput.metadata;
|
|
108
|
+
if (!metadata || Object.keys(metadata).length === 0) {
|
|
109
|
+
console.error(
|
|
110
|
+
"Error: At least one -m key=value pair or --input-json with metadata is required."
|
|
111
|
+
);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
if (options.dryRun) {
|
|
115
|
+
console.log(
|
|
116
|
+
JSON.stringify(
|
|
117
|
+
{
|
|
118
|
+
dryRun: true,
|
|
119
|
+
action: "updateFile",
|
|
120
|
+
details: { bucket, key, metadata }
|
|
121
|
+
},
|
|
122
|
+
null,
|
|
123
|
+
2
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
const stow = createStow();
|
|
129
|
+
const file = await stow.updateFileMetadata(key, metadata, { bucket });
|
|
130
|
+
output(file, () => `Updated ${key}`);
|
|
131
|
+
}
|
|
132
|
+
async function enrichFile(bucket, key) {
|
|
133
|
+
const stow = createStow();
|
|
134
|
+
const results = await Promise.allSettled([
|
|
135
|
+
stow.generateTitle(key, { bucket }),
|
|
136
|
+
stow.generateDescription(key, { bucket }),
|
|
137
|
+
stow.generateAltText(key, { bucket })
|
|
138
|
+
]);
|
|
139
|
+
const labels = ["Title", "Description", "Alt text"];
|
|
140
|
+
for (let i = 0; i < results.length; i++) {
|
|
141
|
+
const result = results[i];
|
|
142
|
+
const label = labels[i];
|
|
143
|
+
if (result.status === "fulfilled") {
|
|
144
|
+
console.log(` ${label}: triggered`);
|
|
145
|
+
} else {
|
|
146
|
+
console.error(` ${label}: failed \u2014 ${result.reason}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const succeeded = results.filter((r) => r.status === "fulfilled").length;
|
|
150
|
+
console.log(
|
|
151
|
+
`
|
|
152
|
+
Enriched ${key}: ${succeeded}/${results.length} tasks dispatched`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
async function listMissing(bucket, type, options) {
|
|
156
|
+
const validTypes = ["dimensions", "embeddings", "colors"];
|
|
157
|
+
if (!validTypes.includes(type)) {
|
|
158
|
+
console.error(
|
|
159
|
+
`Error: Invalid type '${type}'. Must be one of: ${validTypes.join(", ")}`
|
|
160
|
+
);
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
|
|
164
|
+
const baseUrl = getBaseUrl();
|
|
165
|
+
const apiKey = getApiKey();
|
|
166
|
+
const params = new URLSearchParams({
|
|
167
|
+
bucket,
|
|
168
|
+
missing: type,
|
|
169
|
+
...parsedLimit && Number.isFinite(parsedLimit) && parsedLimit > 0 ? { limit: String(parsedLimit) } : {}
|
|
170
|
+
});
|
|
171
|
+
const res = await fetch(`${baseUrl}/files?${params}`, {
|
|
172
|
+
headers: { "x-api-key": apiKey }
|
|
173
|
+
});
|
|
174
|
+
if (!res.ok) {
|
|
175
|
+
const body = await res.json().catch(() => ({}));
|
|
176
|
+
throw new Error(body.error ?? `HTTP ${res.status}`);
|
|
177
|
+
}
|
|
178
|
+
const data = await res.json();
|
|
179
|
+
if (data.files.length === 0) {
|
|
180
|
+
if (isJsonOutput() || options.json) {
|
|
181
|
+
output(data);
|
|
182
|
+
} else {
|
|
183
|
+
console.log(`No files missing ${type} in bucket '${bucket}'.`);
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (options.json || isJsonOutput()) {
|
|
188
|
+
output(data);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const rows = data.files.map((f) => [
|
|
192
|
+
f.key,
|
|
193
|
+
formatBytes(f.size),
|
|
194
|
+
f.lastModified.split("T")[0] ?? f.lastModified
|
|
195
|
+
]);
|
|
196
|
+
console.log(formatTable(["Key", "Size", "Modified"], rows));
|
|
197
|
+
console.log(`
|
|
198
|
+
${data.files.length} files missing ${type}`);
|
|
199
|
+
}
|
|
200
|
+
export {
|
|
201
|
+
enrichFile,
|
|
202
|
+
getFile,
|
|
203
|
+
listFiles,
|
|
204
|
+
listMissing,
|
|
205
|
+
updateFile
|
|
206
|
+
};
|