stow-cli 2.2.1 → 2.3.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/README.md +10 -10
- package/dist/app-ZIHTOHXL.js +255 -0
- package/dist/backfill-BG65X4TP.js +65 -0
- package/dist/backfill-KW46AEAL.js +67 -0
- package/dist/buckets-FPMMPRR2.js +130 -0
- package/dist/buckets-JJBWUVKF.js +137 -0
- package/dist/chunk-533UGNLM.js +42 -0
- package/dist/chunk-AHBVZRDR.js +29 -0
- package/dist/chunk-JXITFFWI.js +318 -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 +178 -194
- package/dist/delete-3UDS4RMH.js +34 -0
- package/dist/delete-CQJEGLP3.js +34 -0
- package/dist/describe-NH3K3LLW.js +79 -0
- package/dist/describe-TGIXQHN6.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-SQURZ7VO.js +194 -0
- package/dist/health-3U3RHXFS.js +56 -0
- package/dist/health-TIJU6U2D.js +61 -0
- package/dist/jobs-HUW6Z6A7.js +87 -0
- package/dist/jobs-KK5IZYO5.js +99 -0
- package/dist/jobs-SX7DIN6T.js +90 -0
- package/dist/jobs-XUAXWUAK.js +102 -0
- package/dist/maintenance-7UBKZOR3.js +79 -0
- package/dist/maintenance-US3PUKFF.js +79 -0
- package/dist/mcp-TUZZB2C7.js +189 -0
- package/dist/profiles-FOLKZZRU.js +53 -0
- package/dist/profiles-XXVM3UKI.js +53 -0
- package/dist/queues-MTA2RWUP.js +56 -0
- package/dist/queues-X6IU3KBZ.js +61 -0
- package/dist/search-KBJSWH35.js +134 -0
- package/dist/search-ULMFDWHE.js +135 -0
- package/dist/search-UWLK4OL2.js +119 -0
- package/dist/tags-OFZQ2XCX.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 +12 -12
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseJsonInput
|
|
3
|
+
} from "./chunk-XVKIRHTX.js";
|
|
4
|
+
import {
|
|
5
|
+
validateBucketName,
|
|
6
|
+
validateFileKey
|
|
7
|
+
} from "./chunk-NBHBVKP5.js";
|
|
8
|
+
import {
|
|
9
|
+
isJsonOutput,
|
|
10
|
+
output
|
|
11
|
+
} from "./chunk-KPIQZBTO.js";
|
|
12
|
+
import {
|
|
13
|
+
formatBytes,
|
|
14
|
+
formatTable
|
|
15
|
+
} from "./chunk-PE6V3MVP.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
|
+
["Dimensions", file.width && file.height ? `${file.width}\xD7${file.height}` : "\u2014"],
|
|
69
|
+
["Duration", file.duration ? `${file.duration}s` : "\u2014"],
|
|
70
|
+
["Embedding", file.embeddingStatus ?? "\u2014"]
|
|
71
|
+
]
|
|
72
|
+
)
|
|
73
|
+
];
|
|
74
|
+
if (file.metadata && Object.keys(file.metadata).length > 0) {
|
|
75
|
+
lines.push("\nMetadata:");
|
|
76
|
+
for (const [k, v] of Object.entries(file.metadata)) {
|
|
77
|
+
lines.push(` ${k}: ${v}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return lines.join("\n");
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async function updateFile(bucket, key, options) {
|
|
84
|
+
validateBucketName(bucket);
|
|
85
|
+
validateFileKey(key);
|
|
86
|
+
const flagMetadata = {};
|
|
87
|
+
if (options.metadata && options.metadata.length > 0) {
|
|
88
|
+
for (const pair of options.metadata) {
|
|
89
|
+
const idx = pair.indexOf("=");
|
|
90
|
+
if (idx === -1) {
|
|
91
|
+
console.error(`Error: Invalid metadata format '${pair}'. Use key=value.`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
flagMetadata[pair.slice(0, idx)] = pair.slice(idx + 1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const jsonInput = parseJsonInput(options.inputJson, {});
|
|
98
|
+
const hasFlagMetadata = Object.keys(flagMetadata).length > 0;
|
|
99
|
+
const metadata = hasFlagMetadata ? { ...jsonInput.metadata, ...flagMetadata } : jsonInput.metadata;
|
|
100
|
+
if (!metadata || Object.keys(metadata).length === 0) {
|
|
101
|
+
console.error(
|
|
102
|
+
"Error: At least one -m key=value pair or --input-json with metadata is required."
|
|
103
|
+
);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
if (options.dryRun) {
|
|
107
|
+
console.log(
|
|
108
|
+
JSON.stringify(
|
|
109
|
+
{
|
|
110
|
+
dryRun: true,
|
|
111
|
+
action: "updateFile",
|
|
112
|
+
details: { bucket, key, metadata }
|
|
113
|
+
},
|
|
114
|
+
null,
|
|
115
|
+
2
|
|
116
|
+
)
|
|
117
|
+
);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const stow = createStow();
|
|
121
|
+
const file = await stow.updateFileMetadata(key, metadata, { bucket });
|
|
122
|
+
output(file, () => `Updated ${key}`);
|
|
123
|
+
}
|
|
124
|
+
async function enrichFile(bucket, key) {
|
|
125
|
+
const stow = createStow();
|
|
126
|
+
const results = await Promise.allSettled([
|
|
127
|
+
stow.generateTitle(key, { bucket }),
|
|
128
|
+
stow.generateDescription(key, { bucket }),
|
|
129
|
+
stow.generateAltText(key, { bucket })
|
|
130
|
+
]);
|
|
131
|
+
const labels = ["Title", "Description", "Alt text"];
|
|
132
|
+
for (let i = 0; i < results.length; i += 1) {
|
|
133
|
+
const result = results[i];
|
|
134
|
+
const label = labels[i];
|
|
135
|
+
if (result.status === "fulfilled") {
|
|
136
|
+
console.log(` ${label}: triggered`);
|
|
137
|
+
} else {
|
|
138
|
+
console.error(` ${label}: failed \u2014 ${result.reason}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const succeeded = results.filter((r) => r.status === "fulfilled").length;
|
|
142
|
+
console.log(`
|
|
143
|
+
Enriched ${key}: ${succeeded}/${results.length} tasks dispatched`);
|
|
144
|
+
}
|
|
145
|
+
async function listMissing(bucket, type, options) {
|
|
146
|
+
const validTypes = ["dimensions", "embeddings", "colors"];
|
|
147
|
+
if (!validTypes.includes(type)) {
|
|
148
|
+
console.error(`Error: Invalid type '${type}'. Must be one of: ${validTypes.join(", ")}`);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
|
|
152
|
+
const baseUrl = getBaseUrl();
|
|
153
|
+
const apiKey = getApiKey();
|
|
154
|
+
const params = new URLSearchParams({
|
|
155
|
+
bucket,
|
|
156
|
+
missing: type,
|
|
157
|
+
...parsedLimit && Number.isFinite(parsedLimit) && parsedLimit > 0 ? { limit: String(parsedLimit) } : {}
|
|
158
|
+
});
|
|
159
|
+
const res = await fetch(`${baseUrl}/files?${params}`, {
|
|
160
|
+
headers: { "x-api-key": apiKey }
|
|
161
|
+
});
|
|
162
|
+
if (!res.ok) {
|
|
163
|
+
const body = await res.json().catch(() => ({}));
|
|
164
|
+
throw new Error(body.error ?? `HTTP ${res.status}`);
|
|
165
|
+
}
|
|
166
|
+
const data = await res.json();
|
|
167
|
+
if (data.files.length === 0) {
|
|
168
|
+
if (isJsonOutput() || options.json) {
|
|
169
|
+
output(data);
|
|
170
|
+
} else {
|
|
171
|
+
console.log(`No files missing ${type} in bucket '${bucket}'.`);
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (options.json || isJsonOutput()) {
|
|
176
|
+
output(data);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const rows = data.files.map((f) => [
|
|
180
|
+
f.key,
|
|
181
|
+
formatBytes(f.size),
|
|
182
|
+
f.lastModified.split("T")[0] ?? f.lastModified
|
|
183
|
+
]);
|
|
184
|
+
console.log(formatTable(["Key", "Size", "Modified"], rows));
|
|
185
|
+
console.log(`
|
|
186
|
+
${data.files.length} files missing ${type}`);
|
|
187
|
+
}
|
|
188
|
+
export {
|
|
189
|
+
enrichFile,
|
|
190
|
+
getFile,
|
|
191
|
+
listFiles,
|
|
192
|
+
listMissing,
|
|
193
|
+
updateFile
|
|
194
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adminRequest
|
|
3
|
+
} from "./chunk-QF7PVPWQ.js";
|
|
4
|
+
import {
|
|
5
|
+
output
|
|
6
|
+
} from "./chunk-KPIQZBTO.js";
|
|
7
|
+
import {
|
|
8
|
+
formatTable
|
|
9
|
+
} from "./chunk-PE6V3MVP.js";
|
|
10
|
+
import "./chunk-TOADDO2F.js";
|
|
11
|
+
|
|
12
|
+
// src/commands/admin/health.ts
|
|
13
|
+
async function health(options) {
|
|
14
|
+
const result = await adminRequest({
|
|
15
|
+
method: "GET",
|
|
16
|
+
path: "/health"
|
|
17
|
+
});
|
|
18
|
+
output(
|
|
19
|
+
result,
|
|
20
|
+
() => {
|
|
21
|
+
const lines = [];
|
|
22
|
+
const statusIcon = result.status === "ok" ? "+" : "x";
|
|
23
|
+
lines.push(`${statusIcon} ${result.status} (${result.version})`);
|
|
24
|
+
lines.push(` ${result.timestamp}`);
|
|
25
|
+
lines.push("\nChecks:");
|
|
26
|
+
for (const [name, status] of Object.entries(result.checks)) {
|
|
27
|
+
const icon = status === "ok" ? "+" : "x";
|
|
28
|
+
lines.push(` ${icon} ${name}`);
|
|
29
|
+
}
|
|
30
|
+
if (result.queues) {
|
|
31
|
+
lines.push("\nQueues:");
|
|
32
|
+
const rows = [];
|
|
33
|
+
for (const [name, counts] of Object.entries(result.queues)) {
|
|
34
|
+
if (counts === "unavailable") {
|
|
35
|
+
rows.push([name, "--", "--", "--", "--"]);
|
|
36
|
+
} else {
|
|
37
|
+
const c = counts;
|
|
38
|
+
rows.push([
|
|
39
|
+
name,
|
|
40
|
+
String(c.waiting ?? 0),
|
|
41
|
+
String(c.active ?? 0),
|
|
42
|
+
String(c.completed ?? 0),
|
|
43
|
+
String(c.failed ?? 0)
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
lines.push(formatTable(["Queue", "Waiting", "Active", "Completed", "Failed"], rows));
|
|
48
|
+
}
|
|
49
|
+
return lines.join("\n");
|
|
50
|
+
},
|
|
51
|
+
{ json: options.json }
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
export {
|
|
55
|
+
health
|
|
56
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adminRequest
|
|
3
|
+
} from "./chunk-QF7PVPWQ.js";
|
|
4
|
+
import {
|
|
5
|
+
output
|
|
6
|
+
} from "./chunk-RH4BOSYB.js";
|
|
7
|
+
import {
|
|
8
|
+
formatTable
|
|
9
|
+
} from "./chunk-FZGOTXTE.js";
|
|
10
|
+
import "./chunk-TOADDO2F.js";
|
|
11
|
+
|
|
12
|
+
// src/commands/admin/health.ts
|
|
13
|
+
async function health(options) {
|
|
14
|
+
const result = await adminRequest({
|
|
15
|
+
method: "GET",
|
|
16
|
+
path: "/health"
|
|
17
|
+
});
|
|
18
|
+
output(
|
|
19
|
+
result,
|
|
20
|
+
() => {
|
|
21
|
+
const lines = [];
|
|
22
|
+
const statusIcon = result.status === "ok" ? "+" : "x";
|
|
23
|
+
lines.push(`${statusIcon} ${result.status} (${result.version})`);
|
|
24
|
+
lines.push(` ${result.timestamp}`);
|
|
25
|
+
lines.push("\nChecks:");
|
|
26
|
+
for (const [name, status] of Object.entries(result.checks)) {
|
|
27
|
+
const icon = status === "ok" ? "+" : "x";
|
|
28
|
+
lines.push(` ${icon} ${name}`);
|
|
29
|
+
}
|
|
30
|
+
if (result.queues) {
|
|
31
|
+
lines.push("\nQueues:");
|
|
32
|
+
const rows = [];
|
|
33
|
+
for (const [name, counts] of Object.entries(result.queues)) {
|
|
34
|
+
if (counts === "unavailable") {
|
|
35
|
+
rows.push([name, "--", "--", "--", "--"]);
|
|
36
|
+
} else {
|
|
37
|
+
const c = counts;
|
|
38
|
+
rows.push([
|
|
39
|
+
name,
|
|
40
|
+
String(c.waiting ?? 0),
|
|
41
|
+
String(c.active ?? 0),
|
|
42
|
+
String(c.completed ?? 0),
|
|
43
|
+
String(c.failed ?? 0)
|
|
44
|
+
]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
lines.push(
|
|
48
|
+
formatTable(
|
|
49
|
+
["Queue", "Waiting", "Active", "Completed", "Failed"],
|
|
50
|
+
rows
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
},
|
|
56
|
+
{ json: options.json }
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
health
|
|
61
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adminRequest
|
|
3
|
+
} from "./chunk-QF7PVPWQ.js";
|
|
4
|
+
import {
|
|
5
|
+
isJsonOutput,
|
|
6
|
+
output
|
|
7
|
+
} from "./chunk-KPIQZBTO.js";
|
|
8
|
+
import {
|
|
9
|
+
formatTable
|
|
10
|
+
} from "./chunk-PE6V3MVP.js";
|
|
11
|
+
import "./chunk-TOADDO2F.js";
|
|
12
|
+
|
|
13
|
+
// src/commands/admin/jobs.ts
|
|
14
|
+
function formatTimestamp(ts) {
|
|
15
|
+
return new Date(ts).toISOString().replace("T", " ").slice(0, 19);
|
|
16
|
+
}
|
|
17
|
+
async function listAdminJobs(options) {
|
|
18
|
+
const params = new URLSearchParams();
|
|
19
|
+
if (options.org) {
|
|
20
|
+
params.set("orgId", options.org);
|
|
21
|
+
}
|
|
22
|
+
if (options.bucket) {
|
|
23
|
+
params.set("bucketId", options.bucket);
|
|
24
|
+
}
|
|
25
|
+
if (options.status) {
|
|
26
|
+
params.set("status", options.status);
|
|
27
|
+
}
|
|
28
|
+
if (options.queue) {
|
|
29
|
+
params.set("queue", options.queue);
|
|
30
|
+
}
|
|
31
|
+
if (options.limit) {
|
|
32
|
+
params.set("limit", options.limit);
|
|
33
|
+
}
|
|
34
|
+
const qs = params.toString();
|
|
35
|
+
const result = await adminRequest({
|
|
36
|
+
method: "GET",
|
|
37
|
+
path: `/admin/jobs${qs ? `?${qs}` : ""}`
|
|
38
|
+
});
|
|
39
|
+
if (result.jobs.length === 0) {
|
|
40
|
+
if (isJsonOutput() || options.json) {
|
|
41
|
+
output(result.jobs, void 0, { json: options.json });
|
|
42
|
+
} else {
|
|
43
|
+
console.log("No jobs found.");
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
output(
|
|
48
|
+
result.jobs,
|
|
49
|
+
() => {
|
|
50
|
+
const rows = result.jobs.map((job) => [
|
|
51
|
+
job.jobId,
|
|
52
|
+
job.queueName,
|
|
53
|
+
job.status,
|
|
54
|
+
`${job.data.fileId.slice(0, 8)}...`,
|
|
55
|
+
`${job.data.orgId.slice(0, 8)}...`,
|
|
56
|
+
formatTimestamp(job.timestamp),
|
|
57
|
+
job.failedReason ? job.failedReason.slice(0, 40) : ""
|
|
58
|
+
]);
|
|
59
|
+
return formatTable(["ID", "Queue", "Status", "File", "Org", "Created", "Error"], rows);
|
|
60
|
+
},
|
|
61
|
+
{ json: options.json }
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
async function retryAdminJob(jobId, options) {
|
|
65
|
+
const result = await adminRequest({
|
|
66
|
+
method: "POST",
|
|
67
|
+
path: `/admin/jobs/${jobId}/retry`,
|
|
68
|
+
body: { queue: options.queue }
|
|
69
|
+
});
|
|
70
|
+
if (result.retried) {
|
|
71
|
+
console.log(`Job ${jobId} retried.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async function deleteAdminJob(jobId, options) {
|
|
75
|
+
const result = await adminRequest({
|
|
76
|
+
method: "DELETE",
|
|
77
|
+
path: `/admin/jobs/${jobId}?queue=${encodeURIComponent(options.queue)}`
|
|
78
|
+
});
|
|
79
|
+
if (result.deleted) {
|
|
80
|
+
console.log(`Job ${jobId} removed.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
deleteAdminJob,
|
|
85
|
+
listAdminJobs,
|
|
86
|
+
retryAdminJob
|
|
87
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isJsonOutput,
|
|
3
|
+
output
|
|
4
|
+
} from "./chunk-KPIQZBTO.js";
|
|
5
|
+
import {
|
|
6
|
+
formatTable
|
|
7
|
+
} from "./chunk-PE6V3MVP.js";
|
|
8
|
+
import {
|
|
9
|
+
getApiKey,
|
|
10
|
+
getBaseUrl
|
|
11
|
+
} from "./chunk-TOADDO2F.js";
|
|
12
|
+
|
|
13
|
+
// src/commands/jobs.ts
|
|
14
|
+
async function bucketRequest(opts) {
|
|
15
|
+
const baseUrl = getBaseUrl();
|
|
16
|
+
const apiKey = getApiKey();
|
|
17
|
+
const res = await fetch(`${baseUrl}${opts.path}`, {
|
|
18
|
+
method: opts.method,
|
|
19
|
+
headers: {
|
|
20
|
+
"x-api-key": apiKey,
|
|
21
|
+
...opts.body ? { "Content-Type": "application/json" } : {}
|
|
22
|
+
},
|
|
23
|
+
...opts.body ? { body: JSON.stringify(opts.body) } : {}
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
27
|
+
const message = body.error ?? `HTTP ${res.status}`;
|
|
28
|
+
throw new Error(message);
|
|
29
|
+
}
|
|
30
|
+
return await res.json();
|
|
31
|
+
}
|
|
32
|
+
function formatTimestamp(ts) {
|
|
33
|
+
return new Date(ts).toISOString().replace("T", " ").slice(0, 19);
|
|
34
|
+
}
|
|
35
|
+
async function listJobs(bucketId, options) {
|
|
36
|
+
const params = new URLSearchParams();
|
|
37
|
+
if (options.status) {
|
|
38
|
+
params.set("status", options.status);
|
|
39
|
+
}
|
|
40
|
+
if (options.queue) {
|
|
41
|
+
params.set("queue", options.queue);
|
|
42
|
+
}
|
|
43
|
+
if (options.limit) {
|
|
44
|
+
params.set("limit", options.limit);
|
|
45
|
+
}
|
|
46
|
+
const qs = params.toString();
|
|
47
|
+
const path = `/buckets/${bucketId}/jobs${qs ? `?${qs}` : ""}`;
|
|
48
|
+
const result = await bucketRequest({
|
|
49
|
+
method: "GET",
|
|
50
|
+
path
|
|
51
|
+
});
|
|
52
|
+
if (result.jobs.length === 0) {
|
|
53
|
+
if (isJsonOutput() || options.json) {
|
|
54
|
+
output(result.jobs);
|
|
55
|
+
} else {
|
|
56
|
+
console.log("No jobs found.");
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
output(
|
|
61
|
+
result.jobs,
|
|
62
|
+
() => {
|
|
63
|
+
const rows = result.jobs.map((job) => [
|
|
64
|
+
job.jobId,
|
|
65
|
+
job.queueName,
|
|
66
|
+
job.status,
|
|
67
|
+
`${job.data.fileId.slice(0, 8)}...`,
|
|
68
|
+
formatTimestamp(job.timestamp),
|
|
69
|
+
job.failedReason ? job.failedReason.slice(0, 40) : ""
|
|
70
|
+
]);
|
|
71
|
+
return formatTable(["ID", "Queue", "Status", "File", "Created", "Error"], rows);
|
|
72
|
+
},
|
|
73
|
+
{ json: options.json }
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
async function retryJob(jobId, options) {
|
|
77
|
+
const result = await bucketRequest({
|
|
78
|
+
method: "POST",
|
|
79
|
+
path: `/buckets/${options.bucket}/jobs/${jobId}/retry`,
|
|
80
|
+
body: { queue: options.queue }
|
|
81
|
+
});
|
|
82
|
+
if (result.retried) {
|
|
83
|
+
console.log(`Job ${jobId} retried.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function deleteJob(jobId, options) {
|
|
87
|
+
const result = await bucketRequest({
|
|
88
|
+
method: "DELETE",
|
|
89
|
+
path: `/buckets/${options.bucket}/jobs/${jobId}?queue=${encodeURIComponent(options.queue)}`
|
|
90
|
+
});
|
|
91
|
+
if (result.deleted) {
|
|
92
|
+
console.log(`Job ${jobId} removed.`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export {
|
|
96
|
+
deleteJob,
|
|
97
|
+
listJobs,
|
|
98
|
+
retryJob
|
|
99
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import {
|
|
2
|
+
adminRequest
|
|
3
|
+
} from "./chunk-QF7PVPWQ.js";
|
|
4
|
+
import {
|
|
5
|
+
isJsonOutput,
|
|
6
|
+
output
|
|
7
|
+
} from "./chunk-RH4BOSYB.js";
|
|
8
|
+
import {
|
|
9
|
+
formatTable
|
|
10
|
+
} from "./chunk-FZGOTXTE.js";
|
|
11
|
+
import "./chunk-TOADDO2F.js";
|
|
12
|
+
|
|
13
|
+
// src/commands/admin/jobs.ts
|
|
14
|
+
function formatTimestamp(ts) {
|
|
15
|
+
return new Date(ts).toISOString().replace("T", " ").slice(0, 19);
|
|
16
|
+
}
|
|
17
|
+
async function listAdminJobs(options) {
|
|
18
|
+
const params = new URLSearchParams();
|
|
19
|
+
if (options.org) {
|
|
20
|
+
params.set("orgId", options.org);
|
|
21
|
+
}
|
|
22
|
+
if (options.bucket) {
|
|
23
|
+
params.set("bucketId", options.bucket);
|
|
24
|
+
}
|
|
25
|
+
if (options.status) {
|
|
26
|
+
params.set("status", options.status);
|
|
27
|
+
}
|
|
28
|
+
if (options.queue) {
|
|
29
|
+
params.set("queue", options.queue);
|
|
30
|
+
}
|
|
31
|
+
if (options.limit) {
|
|
32
|
+
params.set("limit", options.limit);
|
|
33
|
+
}
|
|
34
|
+
const qs = params.toString();
|
|
35
|
+
const result = await adminRequest({
|
|
36
|
+
method: "GET",
|
|
37
|
+
path: `/admin/jobs${qs ? `?${qs}` : ""}`
|
|
38
|
+
});
|
|
39
|
+
if (result.jobs.length === 0) {
|
|
40
|
+
if (isJsonOutput() || options.json) {
|
|
41
|
+
output(result.jobs, void 0, { json: options.json });
|
|
42
|
+
} else {
|
|
43
|
+
console.log("No jobs found.");
|
|
44
|
+
}
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
output(
|
|
48
|
+
result.jobs,
|
|
49
|
+
() => {
|
|
50
|
+
const rows = result.jobs.map((job) => [
|
|
51
|
+
job.jobId,
|
|
52
|
+
job.queueName,
|
|
53
|
+
job.status,
|
|
54
|
+
`${job.data.fileId.slice(0, 8)}...`,
|
|
55
|
+
`${job.data.orgId.slice(0, 8)}...`,
|
|
56
|
+
formatTimestamp(job.timestamp),
|
|
57
|
+
job.failedReason ? job.failedReason.slice(0, 40) : ""
|
|
58
|
+
]);
|
|
59
|
+
return formatTable(
|
|
60
|
+
["ID", "Queue", "Status", "File", "Org", "Created", "Error"],
|
|
61
|
+
rows
|
|
62
|
+
);
|
|
63
|
+
},
|
|
64
|
+
{ json: options.json }
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
async function retryAdminJob(jobId, options) {
|
|
68
|
+
const result = await adminRequest({
|
|
69
|
+
method: "POST",
|
|
70
|
+
path: `/admin/jobs/${jobId}/retry`,
|
|
71
|
+
body: { queue: options.queue }
|
|
72
|
+
});
|
|
73
|
+
if (result.retried) {
|
|
74
|
+
console.log(`Job ${jobId} retried.`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function deleteAdminJob(jobId, options) {
|
|
78
|
+
const result = await adminRequest({
|
|
79
|
+
method: "DELETE",
|
|
80
|
+
path: `/admin/jobs/${jobId}?queue=${encodeURIComponent(options.queue)}`
|
|
81
|
+
});
|
|
82
|
+
if (result.deleted) {
|
|
83
|
+
console.log(`Job ${jobId} removed.`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export {
|
|
87
|
+
deleteAdminJob,
|
|
88
|
+
listAdminJobs,
|
|
89
|
+
retryAdminJob
|
|
90
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isJsonOutput,
|
|
3
|
+
output
|
|
4
|
+
} from "./chunk-RH4BOSYB.js";
|
|
5
|
+
import {
|
|
6
|
+
formatTable
|
|
7
|
+
} from "./chunk-FZGOTXTE.js";
|
|
8
|
+
import {
|
|
9
|
+
getApiKey,
|
|
10
|
+
getBaseUrl
|
|
11
|
+
} from "./chunk-TOADDO2F.js";
|
|
12
|
+
|
|
13
|
+
// src/commands/jobs.ts
|
|
14
|
+
async function bucketRequest(opts) {
|
|
15
|
+
const baseUrl = getBaseUrl();
|
|
16
|
+
const apiKey = getApiKey();
|
|
17
|
+
const res = await fetch(`${baseUrl}${opts.path}`, {
|
|
18
|
+
method: opts.method,
|
|
19
|
+
headers: {
|
|
20
|
+
"x-api-key": apiKey,
|
|
21
|
+
...opts.body ? { "Content-Type": "application/json" } : {}
|
|
22
|
+
},
|
|
23
|
+
...opts.body ? { body: JSON.stringify(opts.body) } : {}
|
|
24
|
+
});
|
|
25
|
+
if (!res.ok) {
|
|
26
|
+
const body = await res.json().catch(() => ({ error: res.statusText }));
|
|
27
|
+
const message = body.error ?? `HTTP ${res.status}`;
|
|
28
|
+
throw new Error(message);
|
|
29
|
+
}
|
|
30
|
+
return await res.json();
|
|
31
|
+
}
|
|
32
|
+
function formatTimestamp(ts) {
|
|
33
|
+
return new Date(ts).toISOString().replace("T", " ").slice(0, 19);
|
|
34
|
+
}
|
|
35
|
+
async function listJobs(bucketId, options) {
|
|
36
|
+
const params = new URLSearchParams();
|
|
37
|
+
if (options.status) {
|
|
38
|
+
params.set("status", options.status);
|
|
39
|
+
}
|
|
40
|
+
if (options.queue) {
|
|
41
|
+
params.set("queue", options.queue);
|
|
42
|
+
}
|
|
43
|
+
if (options.limit) {
|
|
44
|
+
params.set("limit", options.limit);
|
|
45
|
+
}
|
|
46
|
+
const qs = params.toString();
|
|
47
|
+
const path = `/buckets/${bucketId}/jobs${qs ? `?${qs}` : ""}`;
|
|
48
|
+
const result = await bucketRequest({
|
|
49
|
+
method: "GET",
|
|
50
|
+
path
|
|
51
|
+
});
|
|
52
|
+
if (result.jobs.length === 0) {
|
|
53
|
+
if (isJsonOutput() || options.json) {
|
|
54
|
+
output(result.jobs);
|
|
55
|
+
} else {
|
|
56
|
+
console.log("No jobs found.");
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
output(
|
|
61
|
+
result.jobs,
|
|
62
|
+
() => {
|
|
63
|
+
const rows = result.jobs.map((job) => [
|
|
64
|
+
job.jobId,
|
|
65
|
+
job.queueName,
|
|
66
|
+
job.status,
|
|
67
|
+
`${job.data.fileId.slice(0, 8)}...`,
|
|
68
|
+
formatTimestamp(job.timestamp),
|
|
69
|
+
job.failedReason ? job.failedReason.slice(0, 40) : ""
|
|
70
|
+
]);
|
|
71
|
+
return formatTable(
|
|
72
|
+
["ID", "Queue", "Status", "File", "Created", "Error"],
|
|
73
|
+
rows
|
|
74
|
+
);
|
|
75
|
+
},
|
|
76
|
+
{ json: options.json }
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
async function retryJob(jobId, options) {
|
|
80
|
+
const result = await bucketRequest({
|
|
81
|
+
method: "POST",
|
|
82
|
+
path: `/buckets/${options.bucket}/jobs/${jobId}/retry`,
|
|
83
|
+
body: { queue: options.queue }
|
|
84
|
+
});
|
|
85
|
+
if (result.retried) {
|
|
86
|
+
console.log(`Job ${jobId} retried.`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async function deleteJob(jobId, options) {
|
|
90
|
+
const result = await bucketRequest({
|
|
91
|
+
method: "DELETE",
|
|
92
|
+
path: `/buckets/${options.bucket}/jobs/${jobId}?queue=${encodeURIComponent(options.queue)}`
|
|
93
|
+
});
|
|
94
|
+
if (result.deleted) {
|
|
95
|
+
console.log(`Job ${jobId} removed.`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
export {
|
|
99
|
+
deleteJob,
|
|
100
|
+
listJobs,
|
|
101
|
+
retryJob
|
|
102
|
+
};
|