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.
Files changed (70) hide show
  1. package/README.md +10 -10
  2. package/dist/app-ZIHTOHXL.js +255 -0
  3. package/dist/backfill-BG65X4TP.js +65 -0
  4. package/dist/backfill-JCNPLFJU.js +67 -0
  5. package/dist/backfill-KW46AEAL.js +67 -0
  6. package/dist/backfill-VAORMLMY.js +67 -0
  7. package/dist/buckets-AFNX7FV3.js +137 -0
  8. package/dist/buckets-FPMMPRR2.js +130 -0
  9. package/dist/buckets-JJBWUVKF.js +137 -0
  10. package/dist/buckets-VYI2QVLO.js +137 -0
  11. package/dist/chunk-533UGNLM.js +42 -0
  12. package/dist/chunk-5BVMPHKH.js +147 -0
  13. package/dist/chunk-5IX3ASXH.js +153 -0
  14. package/dist/chunk-AHBVZRDR.js +29 -0
  15. package/dist/chunk-KPIQZBTO.js +151 -0
  16. package/dist/chunk-MYFLRBWC.js +312 -0
  17. package/dist/chunk-NBHBVKP5.js +54 -0
  18. package/dist/chunk-PE6V3MVP.js +46 -0
  19. package/dist/chunk-RH4BOSYB.js +153 -0
  20. package/dist/chunk-XVKIRHTX.js +29 -0
  21. package/dist/cli.js +184 -200
  22. package/dist/delete-3UDS4RMH.js +34 -0
  23. package/dist/delete-CQJEGLP3.js +34 -0
  24. package/dist/describe-CU5FBHZS.js +79 -0
  25. package/dist/describe-HSEHMJVD.js +79 -0
  26. package/dist/describe-NH3K3LLW.js +79 -0
  27. package/dist/describe-W3ED4VW3.js +79 -0
  28. package/dist/drops-XO4CZ4BH.js +39 -0
  29. package/dist/files-BIMA5L2G.js +206 -0
  30. package/dist/files-CFOTEASC.js +206 -0
  31. package/dist/files-SQURZ7VO.js +194 -0
  32. package/dist/files-XU6MDPP4.js +206 -0
  33. package/dist/health-3U3RHXFS.js +56 -0
  34. package/dist/health-SH6T6DZS.js +61 -0
  35. package/dist/health-TIJU6U2D.js +61 -0
  36. package/dist/health-YLNNKAFP.js +61 -0
  37. package/dist/jobs-HUW6Z6A7.js +87 -0
  38. package/dist/jobs-KK5IZYO5.js +99 -0
  39. package/dist/jobs-RMRGXLAA.js +90 -0
  40. package/dist/jobs-ROJFRPMR.js +90 -0
  41. package/dist/jobs-SX7DIN6T.js +90 -0
  42. package/dist/jobs-TND5AHCL.js +102 -0
  43. package/dist/jobs-TOLVGN6K.js +102 -0
  44. package/dist/jobs-XUAXWUAK.js +102 -0
  45. package/dist/maintenance-6XNJ56LL.js +79 -0
  46. package/dist/maintenance-7UBKZOR3.js +79 -0
  47. package/dist/maintenance-US3PUKFF.js +79 -0
  48. package/dist/maintenance-V2TXPXQE.js +79 -0
  49. package/dist/mcp-TUZZB2C7.js +189 -0
  50. package/dist/profiles-FOLKZZRU.js +53 -0
  51. package/dist/profiles-MB3TZQE4.js +53 -0
  52. package/dist/profiles-NVCJCYXR.js +53 -0
  53. package/dist/profiles-XXVM3UKI.js +53 -0
  54. package/dist/queues-AUGTAFBT.js +61 -0
  55. package/dist/queues-MTA2RWUP.js +56 -0
  56. package/dist/queues-NR25TGT7.js +61 -0
  57. package/dist/queues-X6IU3KBZ.js +61 -0
  58. package/dist/search-ETC2EXKM.js +135 -0
  59. package/dist/search-ICJO264J.js +135 -0
  60. package/dist/search-ULMFDWHE.js +135 -0
  61. package/dist/search-UWLK4OL2.js +119 -0
  62. package/dist/tags-75SSHS26.js +90 -0
  63. package/dist/tags-OFZQ2XCX.js +90 -0
  64. package/dist/tags-TBFPDHIQ.js +90 -0
  65. package/dist/tags-V43DCLPQ.js +90 -0
  66. package/dist/upload-F5I2SJRB.js +126 -0
  67. package/dist/upload-N7NAVN3Q.js +126 -0
  68. package/dist/whoami-WUQDFC5P.js +28 -0
  69. package/package.json +20 -20
  70. package/LICENSE +0 -21
@@ -0,0 +1,34 @@
1
+ import {
2
+ validateBucketName,
3
+ validateFileKey
4
+ } from "./chunk-533UGNLM.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-NBHBVKP5.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-5BVMPHKH.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
+ output
6
+ } from "./chunk-5IX3ASXH.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-MYFLRBWC.js";
4
+ import {
5
+ output
6
+ } from "./chunk-KPIQZBTO.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
+ output
6
+ } from "./chunk-RH4BOSYB.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,39 @@
1
+ import {
2
+ formatBytes,
3
+ formatTable,
4
+ usageBar
5
+ } from "./chunk-PE6V3MVP.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,206 @@
1
+ import {
2
+ parseJsonInput
3
+ } from "./chunk-AHBVZRDR.js";
4
+ import {
5
+ validateBucketName,
6
+ validateFileKey
7
+ } from "./chunk-533UGNLM.js";
8
+ import {
9
+ isJsonOutput,
10
+ output
11
+ } from "./chunk-RH4BOSYB.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
+ };