🚀. Socket Launch Week Day 3:Socket Firewall Now Blocks Malicious VS Code and Open VSX Extensions.Learn more
Sign In

stow-cli

Package Overview
Dependencies
Maintainers
1
Versions
13
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

stow-cli - npm Package Compare versions

Package was removed
Sorry, it seems this package was removed from the registry
Comparing version
2.2.1
to
2.2.3
+255
dist/app-ZIHTOHXL.js
import {
formatBytes
} from "./chunk-PE6V3MVP.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/interactive/app.tsx
import { render } from "ink";
import { useEffect as useEffect3, useState as useState3 } from "react";
// src/interactive/components/bucket-list.tsx
import { Box as Box3, Text as Text3, useApp, useInput } from "ink";
import Spinner from "ink-spinner";
import { useEffect, useState } from "react";
// src/interactive/components/header.tsx
import { Box, Text } from "ink";
import { jsx, jsxs } from "react/jsx-runtime";
function Header({ path, email, size }) {
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
/* @__PURE__ */ jsxs(Box, { justifyContent: "space-between", width: "100%", children: [
/* @__PURE__ */ jsxs(Text, { bold: true, children: [
"STOW",
path ? ` > ${path}` : ""
] }),
/* @__PURE__ */ jsx(Text, { dimColor: true, children: size || email || "" })
] }),
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(50) })
] });
}
// src/interactive/components/status-bar.tsx
import { Box as Box2, Text as Text2 } from "ink";
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
function StatusBar({ hints }) {
return /* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: hints.map((h, i) => /* @__PURE__ */ jsx2(Box2, { marginRight: 2, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
"[",
h.key,
"]",
h.label,
i < hints.length - 1 ? "" : ""
] }) }, h.key)) });
}
// src/interactive/components/bucket-list.tsx
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
function toBucket(result) {
return {
id: result.id,
name: result.name,
description: result.description ?? null,
fileCount: result.fileCount ?? 0,
isPublic: result.isPublic ?? false,
usageBytes: result.usageBytes ?? 0
};
}
function BucketList({ onSelect, email }) {
const { exit } = useApp();
const [buckets, setBuckets] = useState([]);
const [loading, setLoading] = useState(true);
const [errorMessage, setErrorMessage] = useState(null);
const [cursor, setCursor] = useState(0);
useEffect(() => {
async function loadBuckets() {
try {
const stow = createStow();
const data = await stow.listBuckets();
setBuckets(data.buckets.map(toBucket));
} catch (error) {
setErrorMessage(error instanceof Error ? error.message : String(error));
} finally {
setLoading(false);
}
}
loadBuckets();
}, []);
useInput((input, key) => {
if (input === "q") {
exit();
return;
}
if (key.upArrow) {
setCursor((c) => Math.max(0, c - 1));
} else if (key.downArrow) {
setCursor((c) => Math.min(buckets.length - 1, c + 1));
} else if (key.return && buckets.length > 0) {
onSelect(buckets[cursor]);
}
});
if (loading) {
return /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsxs3(Text3, { children: [
/* @__PURE__ */ jsx3(Spinner, { type: "dots" }),
" Loading buckets..."
] }) });
}
if (errorMessage) {
return /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsxs3(Text3, { color: "red", children: [
"Error: ",
errorMessage
] }) });
}
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
/* @__PURE__ */ jsx3(Header, { email }),
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: [
i === cursor ? "\u25B8 " : " ",
b.name.padEnd(20),
`${b.fileCount} files`.padEnd(14),
formatBytes(b.usageBytes)
] }) }, b.id)),
/* @__PURE__ */ jsx3(
StatusBar,
{
hints: [
{ key: "\u2191\u2193", label: "navigate" },
{ key: "\u21B5", label: "open" },
{ key: "q", label: "uit" }
]
}
)
] });
}
// src/interactive/components/file-list.tsx
import { Box as Box4, Text as Text4, useInput as useInput2 } from "ink";
import Spinner2 from "ink-spinner";
import { useEffect as useEffect2, useState as useState2 } from "react";
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
function FileList({ bucket, onBack }) {
const [files, setFiles] = useState2([]);
const [loading, setLoading] = useState2(true);
const [errorMessage, setErrorMessage] = useState2(null);
const [cursor, setCursor] = useState2(0);
const [nextCursor, setNextCursor] = useState2(null);
const [copied, setCopied] = useState2(false);
async function loadFiles(pageCursor) {
setLoading(true);
try {
const stow = createStow();
const data = await stow.listFiles({
bucket: bucket.name,
...pageCursor ? { cursor: pageCursor } : {}
});
setFiles((prev) => pageCursor ? [...prev, ...data.files] : data.files);
setNextCursor(data.nextCursor);
} catch (error) {
setErrorMessage(error instanceof Error ? error.message : String(error));
} finally {
setLoading(false);
}
}
useEffect2(() => {
loadFiles();
}, []);
async function copyCurrentFileUrl() {
const currentFile = files[cursor];
if (!currentFile) {
return;
}
if (!currentFile.url) {
setErrorMessage("Selected file has no URL");
return;
}
const { default: clipboard } = await import("clipboardy");
await clipboard.write(currentFile.url);
setCopied(true);
setTimeout(() => setCopied(false), 2e3);
}
useInput2((input, key) => {
if (key.escape || key.backspace || key.leftArrow && !loading) {
onBack();
return;
}
if (key.upArrow) {
setCursor((c) => Math.max(0, c - 1));
} else if (key.downArrow) {
setCursor((c) => {
const next = Math.min(files.length - 1, c + 1);
if (next >= files.length - 3 && nextCursor && !loading) {
loadFiles(nextCursor);
}
return next;
});
} else if (input === "c" && files.length > 0) {
copyCurrentFileUrl();
}
});
if (errorMessage) {
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
/* @__PURE__ */ jsxs4(Text4, { color: "red", children: [
"Error: ",
errorMessage
] }),
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Press Esc to go back." })
] });
}
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
/* @__PURE__ */ jsx4(Header, { path: bucket.name, size: formatBytes(bucket.usageBytes) }),
loading && files.length === 0 && /* @__PURE__ */ jsxs4(Text4, { children: [
/* @__PURE__ */ jsx4(Spinner2, { type: "dots" }),
" Loading files..."
] }),
!loading && files.length === 0 && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "No files in this bucket." }),
files.length > 0 && files.map((f, i) => /* @__PURE__ */ jsx4(Box4, { children: /* @__PURE__ */ jsxs4(Text4, { color: i === cursor ? "cyan" : void 0, children: [
i === cursor ? "\u25B8 " : " ",
f.key.padEnd(28),
formatBytes(f.size).padEnd(10),
f.lastModified.split("T")[0]
] }) }, f.key)),
loading && files.length > 0 && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
/* @__PURE__ */ jsx4(Spinner2, { type: "dots" }),
" Loading more..."
] }),
copied && /* @__PURE__ */ jsx4(Text4, { color: "green", children: "Copied URL to clipboard!" }),
/* @__PURE__ */ jsx4(
StatusBar,
{
hints: [
{ key: "\u2191\u2193", label: "navigate" },
{ key: "c", label: "opy url" },
{ key: "\u2190", label: "back" }
]
}
)
] });
}
// src/interactive/app.tsx
import { jsx as jsx5 } from "react/jsx-runtime";
function App() {
const [screen, setScreen] = useState3({ type: "buckets" });
const [email, setEmail] = useState3();
useEffect3(() => {
async function loadEmail() {
const stow = createStow();
try {
const data = await stow.whoami();
setEmail(data.user.email);
} catch {
}
}
loadEmail();
}, []);
if (screen.type === "files") {
return /* @__PURE__ */ jsx5(FileList, { bucket: screen.bucket, onBack: () => setScreen({ type: "buckets" }) });
}
return /* @__PURE__ */ jsx5(BucketList, { email, onSelect: (bucket) => setScreen({ type: "files", bucket }) });
}
function startInteractive() {
render(/* @__PURE__ */ jsx5(App, {}));
}
export {
startInteractive
};
import {
adminRequest
} from "./chunk-QF7PVPWQ.js";
import {
output
} from "./chunk-KPIQZBTO.js";
import "./chunk-TOADDO2F.js";
// src/commands/admin/backfill.ts
async function runBackfill(type, options) {
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const body = {};
if (options.bucket) {
body.bucketId = options.bucket;
}
if (parsedLimit && parsedLimit > 0) {
body.limit = parsedLimit;
}
if (options.dryRun) {
body.dryRun = true;
}
const result = await adminRequest({
method: "POST",
path: `/internal/files/${type}/backfill`,
body
});
output(
result,
() => {
const lines = [];
if (result.dryRun) {
lines.push(`[dry run] ${result.remaining} files need ${type} processing`);
return lines.join("\n");
}
lines.push(`Enqueued: ${result.enqueued ?? 0}`);
lines.push(`Remaining: ${result.remaining}`);
if (result.errors && result.errors.length > 0) {
lines.push(`
Errors (${result.errors.length}):`);
for (const err of result.errors) {
lines.push(` ${err.fileId}: ${err.error}`);
}
}
if (result.nextCursor) {
lines.push("\n(more files available -- run again to continue)");
}
return lines.join("\n");
},
{ json: options.json }
);
}
async function backfillDimensions(options) {
await runBackfill("dimensions", options);
}
async function backfillColors(options) {
await runBackfill("colors", options);
}
async function backfillEmbeddings(options) {
await runBackfill("embeddings", options);
}
export {
backfillColors,
backfillDimensions,
backfillEmbeddings
};
import {
adminRequest
} from "./chunk-QF7PVPWQ.js";
import {
output
} from "./chunk-RH4BOSYB.js";
import "./chunk-TOADDO2F.js";
// src/commands/admin/backfill.ts
async function runBackfill(type, options) {
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const body = {};
if (options.bucket) {
body.bucketId = options.bucket;
}
if (parsedLimit && parsedLimit > 0) {
body.limit = parsedLimit;
}
if (options.dryRun) {
body.dryRun = true;
}
const result = await adminRequest({
method: "POST",
path: `/internal/files/${type}/backfill`,
body
});
output(
result,
() => {
const lines = [];
if (result.dryRun) {
lines.push(
`[dry run] ${result.remaining} files need ${type} processing`
);
return lines.join("\n");
}
lines.push(`Enqueued: ${result.enqueued ?? 0}`);
lines.push(`Remaining: ${result.remaining}`);
if (result.errors && result.errors.length > 0) {
lines.push(`
Errors (${result.errors.length}):`);
for (const err of result.errors) {
lines.push(` ${err.fileId}: ${err.error}`);
}
}
if (result.nextCursor) {
lines.push("\n(more files available -- run again to continue)");
}
return lines.join("\n");
},
{ json: options.json }
);
}
async function backfillDimensions(options) {
await runBackfill("dimensions", options);
}
async function backfillColors(options) {
await runBackfill("colors", options);
}
async function backfillEmbeddings(options) {
await runBackfill("embeddings", options);
}
export {
backfillColors,
backfillDimensions,
backfillEmbeddings
};
import {
parseJsonInput
} from "./chunk-XVKIRHTX.js";
import {
validateBucketName
} from "./chunk-NBHBVKP5.js";
import {
isJsonOutput,
output
} from "./chunk-KPIQZBTO.js";
import {
formatBytes,
formatTable
} from "./chunk-PE6V3MVP.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/buckets.ts
async function listBuckets() {
const stow = createStow();
const data = await stow.listBuckets();
if (data.buckets.length === 0) {
if (isJsonOutput()) {
output(data);
} else {
console.log("No buckets yet. Create one with: stow buckets create <name>");
}
return;
}
output(data, () => {
const rows = data.buckets.map((b) => [
b.name,
b.isPublic ? "public" : "private",
b.searchable ? "yes" : "no",
`${b.fileCount ?? 0} files`,
formatBytes(b.usageBytes ?? 0),
b.description || ""
]);
return formatTable(["Name", "Access", "Search", "Files", "Size", "Description"], rows);
});
}
async function createBucket(name, options) {
const input = parseJsonInput(options.inputJson, {
name,
description: options.description,
public: options.public
});
validateBucketName(input.name);
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "createBucket",
details: {
name: input.name,
description: input.description ?? null,
isPublic: input.public ?? false
}
},
null,
2
)
);
return;
}
const stow = createStow();
const bucket = await stow.createBucket({
name: input.name,
...input.description ? { description: input.description } : {},
...input.public ? { isPublic: true } : {}
});
console.log(`Created bucket: ${bucket.name}`);
}
async function renameBucket(name, newName, options) {
const input = parseJsonInput(options.inputJson, {
name,
newName
});
validateBucketName(input.name);
validateBucketName(input.newName);
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "renameBucket",
details: { name: input.name, newName: input.newName }
},
null,
2
)
);
return;
}
if (!options.yes) {
console.error("Warning: Renaming a bucket will break any existing URLs using the old name.");
console.error("Use --yes to skip this warning.");
}
const stow = createStow();
const bucket = await stow.renameBucket(input.name, input.newName);
console.log(`Renamed bucket: ${input.name} \u2192 ${bucket.name}`);
}
async function deleteBucket(id, options = {}) {
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "deleteBucket",
details: { id }
},
null,
2
)
);
return;
}
const stow = createStow();
await stow.deleteBucket(id);
console.log(`Deleted bucket: ${id}`);
}
export {
createBucket,
deleteBucket,
listBuckets,
renameBucket
};
import {
parseJsonInput
} from "./chunk-AHBVZRDR.js";
import {
validateBucketName
} from "./chunk-533UGNLM.js";
import {
isJsonOutput,
output
} from "./chunk-RH4BOSYB.js";
import {
formatBytes,
formatTable
} from "./chunk-FZGOTXTE.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/buckets.ts
async function listBuckets() {
const stow = createStow();
const data = await stow.listBuckets();
if (data.buckets.length === 0) {
if (isJsonOutput()) {
output(data);
} else {
console.log(
"No buckets yet. Create one with: stow buckets create <name>"
);
}
return;
}
output(data, () => {
const rows = data.buckets.map((b) => [
b.name,
b.isPublic ? "public" : "private",
b.searchable ? "yes" : "no",
`${b.fileCount ?? 0} files`,
formatBytes(b.usageBytes ?? 0),
b.description || ""
]);
return formatTable(
["Name", "Access", "Search", "Files", "Size", "Description"],
rows
);
});
}
async function createBucket(name, options) {
const input = parseJsonInput(options.inputJson, {
name,
description: options.description,
public: options.public
});
validateBucketName(input.name);
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "createBucket",
details: {
name: input.name,
description: input.description ?? null,
isPublic: input.public ?? false
}
},
null,
2
)
);
return;
}
const stow = createStow();
const bucket = await stow.createBucket({
name: input.name,
...input.description ? { description: input.description } : {},
...input.public ? { isPublic: true } : {}
});
console.log(`Created bucket: ${bucket.name}`);
}
async function renameBucket(name, newName, options) {
const input = parseJsonInput(
options.inputJson,
{ name, newName }
);
validateBucketName(input.name);
validateBucketName(input.newName);
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "renameBucket",
details: { name: input.name, newName: input.newName }
},
null,
2
)
);
return;
}
if (!options.yes) {
console.error(
"Warning: Renaming a bucket will break any existing URLs using the old name."
);
console.error("Use --yes to skip this warning.");
}
const stow = createStow();
const bucket = await stow.renameBucket(input.name, input.newName);
console.log(`Renamed bucket: ${input.name} \u2192 ${bucket.name}`);
}
async function deleteBucket(id, options = {}) {
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "deleteBucket",
details: { id }
},
null,
2
)
);
return;
}
const stow = createStow();
await stow.deleteBucket(id);
console.log(`Deleted bucket: ${id}`);
}
export {
createBucket,
deleteBucket,
listBuckets,
renameBucket
};
// src/lib/validate-input.ts
import path from "path";
var InputValidationError = class extends Error {
constructor(message) {
super(message);
this.name = "InputValidationError";
}
};
function validateInput(value, context) {
if (value.includes("../") || value.includes("..\\")) {
throw new InputValidationError(`${context}: path traversal not allowed`);
}
if (context !== "url" && value.includes("?")) {
throw new InputValidationError(`${context}: embedded query parameters not allowed`);
}
if (/[\x00-\x08\x0B\x0C\x0E-\x1F]/.test(value)) {
throw new InputValidationError(`${context}: control characters not allowed`);
}
if (/%25/.test(value)) {
throw new InputValidationError(`${context}: double-encoded values not allowed`);
}
if (/%2[fF]/.test(value) || /%2[eE]/.test(value)) {
throw new InputValidationError(`${context}: percent-encoded path characters not allowed`);
}
if (context !== "url" && value.includes("#")) {
throw new InputValidationError(`${context}: embedded hash fragments not allowed`);
}
return value;
}
function validateBucketName(name) {
return validateInput(name, "bucket name");
}
function validateFileKey(key) {
return validateInput(key, "file key");
}
export {
InputValidationError,
validateInput,
validateBucketName,
validateFileKey
};
// src/lib/parse-json-input.ts
function parseJsonInput(jsonStr, flagValues) {
if (!jsonStr) {
return flagValues;
}
let parsed;
try {
parsed = JSON.parse(jsonStr);
} catch {
throw new Error(`Invalid JSON in --input-json: ${jsonStr.slice(0, 100)}`);
}
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
throw new Error("--input-json must be a JSON object");
}
return { ...parsed, ...stripUndefined(flagValues) };
}
function stripUndefined(obj) {
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (value !== void 0) {
result[key] = value;
}
}
return result;
}
export {
parseJsonInput
};
// src/lib/sanitize-response.ts
var INJECTION_PATTERNS = [
// Direct instruction injection
/\b(?:ignore|disregard|forget)\b.*\b(?:previous|above|prior)\b.*\b(?:instructions?|rules?|context)\b/i,
// System prompt extraction attempts
/\b(?:reveal|show|print|output|display)\b.*\b(?:system\s*prompt|instructions?|rules?)\b/i,
// Role hijacking
/\byou\s+are\s+(?:now|a)\b/i,
// Tool/action injection
/\b(?:execute|run|call)\b.*\b(?:command|tool|function|bash|shell)\b/i,
// Markdown/XML injection that could affect agent parsing
/<\/?(?:system|user|assistant|tool_use|tool_result)\b/i
];
var USER_CONTENT_FIELDS = /* @__PURE__ */ new Set([
"originalFilename",
"filename",
"name",
"description",
"label",
"text",
"slug",
"webhookUrl"
]);
function detectInjection(value) {
return INJECTION_PATTERNS.some((pattern) => pattern.test(value));
}
function sanitizeValue(value) {
if (detectInjection(value)) {
return `[FLAGGED: potential prompt injection] ${value}`;
}
return value;
}
function sanitizeResponse(data) {
if (data === null || data === void 0) {
return data;
}
if (typeof data === "string") {
return sanitizeValue(data);
}
if (Array.isArray(data)) {
return data.map((item) => sanitizeResponse(item));
}
if (typeof data === "object") {
const result = {};
for (const [key, value] of Object.entries(data)) {
if (USER_CONTENT_FIELDS.has(key) && typeof value === "string") {
result[key] = sanitizeValue(value);
} else if (typeof value === "object" && value !== null) {
result[key] = sanitizeResponse(value);
} else {
result[key] = value;
}
}
return result;
}
return data;
}
// src/lib/output.ts
var _forceHuman = false;
var _globalFields;
var _globalNdjson = false;
function setForceHuman(value) {
_forceHuman = value;
}
function setGlobalFields(fields) {
_globalFields = fields;
}
function setGlobalNdjson(value) {
_globalNdjson = value;
}
function isJsonOutput() {
if (_forceHuman) {
return false;
}
return !process.stdout.isTTY;
}
function unwrapArray(data) {
if (typeof data !== "object" || data === null || Array.isArray(data)) {
return data;
}
const entries = Object.entries(data);
if (entries.length === 1 && Array.isArray(entries[0][1])) {
return entries[0][1];
}
return data;
}
function pickFields(obj, fieldSet) {
if (typeof obj !== "object" || obj === null) {
return {};
}
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (fieldSet.has(key)) {
result[key] = value;
}
}
return result;
}
function applyFieldMask(data, fields) {
if (!fields) {
return data;
}
const fieldSet = new Set(fields.split(",").map((f) => f.trim()));
if (Array.isArray(data)) {
return data.map((item) => pickFields(item, fieldSet));
}
if (typeof data === "object" && data !== null) {
return pickFields(data, fieldSet);
}
return data;
}
function outputNdjson(items) {
for (const item of items) {
const sanitized = sanitizeResponse(item);
console.log(JSON.stringify(sanitized));
}
}
function output(data, humanFormatter, options) {
const sanitized = sanitizeResponse(data);
const unwrapped = _globalFields || _globalNdjson ? unwrapArray(sanitized) : sanitized;
const masked = applyFieldMask(unwrapped, _globalFields);
if (_globalNdjson && Array.isArray(masked)) {
outputNdjson(masked);
return;
}
if (options?.json || isJsonOutput()) {
console.log(JSON.stringify(masked, null, 2));
} else if (humanFormatter && !_globalFields) {
console.log(humanFormatter());
} else {
console.log(JSON.stringify(masked, null, 2));
}
}
function outputError(error, code, details) {
if (isJsonOutput()) {
console.error(JSON.stringify({ error, ...code ? { code } : {}, ...details }));
} else {
console.error(`Error: ${error}`);
}
process.exit(1);
}
export {
setForceHuman,
setGlobalFields,
setGlobalNdjson,
isJsonOutput,
output,
outputError
};
// src/lib/cli-docs.ts
var CLI_DOCS = {
root: {
description: "CLI for Stow file storage",
usage: "stow [command] [options]",
examples: [
"stow whoami",
"stow upload ./photo.jpg --bucket photos",
"stow drop ./screenshot.png",
"stow buckets",
"stow files photos --limit 50",
"stow search text 'sunset beach'",
"stow admin health"
],
notes: [
"Set STOW_API_KEY before running commands that call the API.",
"Set STOW_API_URL to target a non-default environment.",
"Set STOW_ADMIN_SECRET for admin commands."
]
},
drop: {
description: "Upload a file and get a short URL (quick share)",
usage: "stow drop <file> [options]",
examples: ["stow drop ./video.mp4", "stow drop ./notes.txt --quiet"]
},
upload: {
description: "Upload a file to a bucket",
usage: "stow upload <file> [options]",
examples: ["stow upload ./logo.png --bucket brand-assets", "stow upload ./clip.mov --quiet"]
},
buckets: {
description: "List your buckets",
usage: "stow buckets",
examples: ["stow buckets"]
},
bucketsCreate: {
description: "Create a new bucket",
usage: "stow buckets create <name> [options]",
examples: [
"stow buckets create photos",
'stow buckets create docs --description "Product docs"',
"stow buckets create public-media --public"
]
},
bucketsRename: {
description: "Rename a bucket",
usage: "stow buckets rename <name> <new-name> [options]",
examples: ["stow buckets rename old-name new-name --yes"],
notes: ["Renaming a bucket can break existing public URLs."]
},
bucketsDelete: {
description: "Delete a bucket by ID",
usage: "stow buckets delete <id>",
examples: ["stow buckets delete 8f3d1ab4-..."]
},
files: {
description: "List files in a bucket",
usage: "stow files <bucket> [options]",
examples: [
"stow files photos",
"stow files photos --search avatars/ --limit 100",
"stow files photos --json"
]
},
filesGet: {
description: "Get details for a single file",
usage: "stow files get <bucket> <key>",
examples: ["stow files get photos hero.png", "stow files get photos hero.png --json"]
},
filesUpdate: {
description: "Update file metadata",
usage: "stow files update <bucket> <key> -m key=value",
examples: [
"stow files update photos hero.png -m alt='Hero image'",
"stow files update photos hero.png -m category=banner -m priority=high"
]
},
filesMissing: {
description: "List files missing processing data",
usage: "stow files missing <bucket> <type>",
examples: [
"stow files missing brera dimensions",
"stow files missing brera embeddings --limit 200",
"stow files missing brera colors --json"
],
notes: ["Valid types: dimensions, embeddings, colors"]
},
filesEnrich: {
description: "Generate title, description, and alt text for an image",
usage: "stow files enrich <bucket> <key>",
examples: ["stow files enrich photos hero.jpg", "stow files enrich next l5igro4iutep3"],
notes: [
"Triggers title, description, and alt text generation in parallel.",
"Requires a searchable bucket with image files."
]
},
drops: {
description: "List your drops with usage info",
usage: "stow drops",
examples: ["stow drops"]
},
dropsDelete: {
description: "Delete a drop by ID",
usage: "stow drops delete <id>",
examples: ["stow drops delete drop_abc123"]
},
delete: {
description: "Delete a file from a bucket",
usage: "stow delete <bucket> <key>",
examples: ["stow delete photos hero/banner.png"]
},
whoami: {
description: "Show account info, usage stats, and API key details",
usage: "stow whoami",
examples: ["stow whoami"]
},
open: {
description: "Open a bucket in the browser",
usage: "stow open <bucket>",
examples: ["stow open photos"]
},
interactive: {
description: "Launch interactive TUI mode",
usage: "stow --interactive",
examples: ["stow", "stow --interactive"]
},
search: {
description: "Search files across buckets",
usage: "stow search <subcommand>",
examples: [
"stow search text 'sunset beach' -b photos",
"stow search similar --file hero.png -b photos",
'stow search color --hex "#ff0000" -b photos',
"stow search diverse -b photos"
]
},
searchText: {
description: "Semantic text search",
usage: "stow search text <query> [options]",
examples: ["stow search text 'sunset beach' -b photos --limit 10 --json"]
},
searchSimilar: {
description: "Find files similar to a given file",
usage: "stow search similar --file <key> [options]",
examples: ["stow search similar --file hero.png -b photos"]
},
searchColor: {
description: "Search by color",
usage: 'stow search color --hex "#ff0000" [options]',
examples: ['stow search color --hex "#ff0000" -b photos --limit 20']
},
searchDiverse: {
description: "Diversity-aware search",
usage: "stow search diverse [options]",
examples: ["stow search diverse -b photos --limit 20"]
},
tags: {
description: "Manage tags",
usage: "stow tags",
examples: ["stow tags", "stow tags create 'Hero Images'", "stow tags delete <id>"]
},
tagsCreate: {
description: "Create a new tag",
usage: "stow tags create <name> [options]",
examples: ['stow tags create "Hero Images"', 'stow tags create "Featured" --color "#ff6600"']
},
tagsDelete: {
description: "Delete a tag by ID",
usage: "stow tags delete <id>",
examples: ["stow tags delete tag_abc123"]
},
profiles: {
description: "Manage taste profiles",
usage: "stow profiles <subcommand>",
examples: [
'stow profiles create --name "My Profile"',
"stow profiles get <id>",
"stow profiles delete <id>"
]
},
profilesCreate: {
description: "Create a taste profile",
usage: 'stow profiles create --name "My Profile" [options]',
examples: ['stow profiles create --name "My Profile" -b photos']
},
profilesGet: {
description: "Get a taste profile with clusters",
usage: "stow profiles get <id>",
examples: ["stow profiles get profile_abc123 --json"]
},
profilesDelete: {
description: "Delete a taste profile",
usage: "stow profiles delete <id>",
examples: ["stow profiles delete profile_abc123"]
},
jobs: {
description: "List processing jobs for a bucket",
usage: "stow jobs --bucket <id> [options]",
examples: [
"stow jobs --bucket <id>",
"stow jobs --bucket <id> --status failed",
"stow jobs --bucket <id> --queue extract-colors --json"
]
},
jobsRetry: {
description: "Retry a failed job",
usage: "stow jobs retry <id> --queue <name> --bucket <id>",
examples: ["stow jobs retry job123 --queue generate-title --bucket <id>"]
},
jobsDelete: {
description: "Remove a job",
usage: "stow jobs delete <id> --queue <name> --bucket <id>",
examples: ["stow jobs delete job123 --queue extract-colors --bucket <id>"]
},
admin: {
description: "Admin commands (requires STOW_ADMIN_SECRET)",
usage: "stow admin <subcommand>",
examples: [
"stow admin health",
"stow admin backfill dimensions --bucket <id> --dry-run",
"stow admin cleanup-drops --dry-run"
],
notes: ["Requires STOW_ADMIN_SECRET environment variable."]
},
adminHealth: {
description: "Check system health and queue depths",
usage: "stow admin health",
examples: ["stow admin health", "stow admin health --json"]
},
adminBackfill: {
description: "Backfill processing data for files",
usage: "stow admin backfill <type> [options]",
examples: [
"stow admin backfill dimensions --bucket <id> --dry-run",
"stow admin backfill colors --bucket <id> --limit 200",
"stow admin backfill embeddings --bucket <id> --limit 100 --json"
],
notes: ["Valid types: dimensions, colors, embeddings"]
},
adminCleanupDrops: {
description: "Remove expired drops",
usage: "stow admin cleanup-drops [options]",
examples: ["stow admin cleanup-drops --max-age-hours 24 --dry-run"]
},
adminPurgeEvents: {
description: "Purge old webhook events",
usage: "stow admin purge-events [options]",
examples: ["stow admin purge-events --dry-run"]
},
adminReconcileFiles: {
description: "Reconcile files between R2 and database",
usage: "stow admin reconcile-files --bucket <id>",
examples: ["stow admin reconcile-files --bucket <id> --dry-run"]
},
adminRetrySyncFailures: {
description: "Retry failed S3 sync operations",
usage: "stow admin retry-sync-failures",
examples: ["stow admin retry-sync-failures"]
},
adminJobs: {
description: "List and manage processing jobs",
usage: "stow admin jobs [options]",
examples: [
"stow admin jobs",
"stow admin jobs --status failed",
"stow admin jobs --org <id> --queue generate-title",
"stow admin jobs --json"
]
},
adminJobsRetry: {
description: "Retry a failed job",
usage: "stow admin jobs retry <id> --queue <name>",
examples: ["stow admin jobs retry job123 --queue generate-title"]
},
adminJobsDelete: {
description: "Remove a job",
usage: "stow admin jobs delete <id> --queue <name>",
examples: ["stow admin jobs delete job123 --queue extract-colors"]
},
adminQueues: {
description: "Show queue depths and counts",
usage: "stow admin queues",
examples: ["stow admin queues", "stow admin queues --json"]
},
adminQueuesClean: {
description: "Clean jobs from a queue",
usage: "stow admin queues clean <name> --failed|--completed",
examples: [
"stow admin queues clean generate-title --failed",
"stow admin queues clean extract-colors --completed --grace 3600"
]
}
};
function renderCommandHelp(key) {
const doc = CLI_DOCS[key];
const lines = [
"",
`Usage: ${doc.usage}`,
"",
"Examples:",
...doc.examples.map((example) => ` ${example}`)
];
if (doc.notes?.length) {
lines.push("", "Notes:", ...doc.notes.map((note) => ` ${note}`));
}
return lines.join("\n");
}
export {
CLI_DOCS,
renderCommandHelp
};
// src/lib/validate-input.ts
import path from "path";
var InputValidationError = class extends Error {
constructor(message) {
super(message);
this.name = "InputValidationError";
}
};
function hasControlCharacters(value) {
for (const char of value) {
const codePoint = char.codePointAt(0);
if (codePoint === void 0) {
continue;
}
if (codePoint >= 0 && codePoint <= 8 || codePoint === 11 || codePoint === 12 || codePoint >= 14 && codePoint <= 31) {
return true;
}
}
return false;
}
function validateInput(value, context) {
if (value.includes("../") || value.includes("..\\")) {
throw new InputValidationError(`${context}: path traversal not allowed`);
}
if (context !== "url" && value.includes("?")) {
throw new InputValidationError(`${context}: embedded query parameters not allowed`);
}
if (hasControlCharacters(value)) {
throw new InputValidationError(`${context}: control characters not allowed`);
}
if (/%25/.test(value)) {
throw new InputValidationError(`${context}: double-encoded values not allowed`);
}
if (/%2[fF]/.test(value) || /%2[eE]/.test(value)) {
throw new InputValidationError(`${context}: percent-encoded path characters not allowed`);
}
if (context !== "url" && value.includes("#")) {
throw new InputValidationError(`${context}: embedded hash fragments not allowed`);
}
return value;
}
function validateBucketName(name) {
return validateInput(name, "bucket name");
}
function validateFileKey(key) {
return validateInput(key, "file key");
}
export {
InputValidationError,
validateInput,
validateBucketName,
validateFileKey
};
// src/lib/format.ts
function padCell(str, width) {
return str.padEnd(width);
}
function formatBytes(bytes) {
if (bytes === 0) {
return "0 B";
}
const units = ["B", "KB", "MB", "GB", "TB"];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / 1024 ** i).toFixed(i > 0 ? 1 : 0)} ${units[i]}`;
}
function formatTable(headers, rows) {
if (rows.length === 0) {
return "";
}
const widths = headers.map((h, i) => {
let dataMax = 0;
for (const row of rows) {
const cellLength = (row[i] || "").length;
if (cellLength > dataMax) {
dataMax = cellLength;
}
}
return Math.max(h.length, dataMax);
});
const sep = widths.map((w) => "\u2500".repeat(w)).join("\u2500\u2500");
const lines = [headers.map((h, i) => padCell(h, widths[i] ?? 0)).join(" "), sep];
for (const row of rows) {
lines.push(row.map((cell, i) => padCell(cell || "", widths[i] ?? 0)).join(" "));
}
return lines.join("\n");
}
function usageBar(used, total, width = 20) {
const ratio = Math.min(used / total, 1);
const filled = Math.round(ratio * width);
const empty = width - filled;
const pct = Math.round(ratio * 100);
return `[${"\u2588".repeat(filled)}${"\u2591".repeat(empty)}] ${pct}%`;
}
export {
formatBytes,
formatTable,
usageBar
};
// src/lib/sanitize-response.ts
var INJECTION_PATTERNS = [
// Direct instruction injection
/\b(?:ignore|disregard|forget)\b.*\b(?:previous|above|prior)\b.*\b(?:instructions?|rules?|context)\b/i,
// System prompt extraction attempts
/\b(?:reveal|show|print|output|display)\b.*\b(?:system\s*prompt|instructions?|rules?)\b/i,
// Role hijacking
/\byou\s+are\s+(?:now|a)\b/i,
// Tool/action injection
/\b(?:execute|run|call)\b.*\b(?:command|tool|function|bash|shell)\b/i,
// Markdown/XML injection that could affect agent parsing
/<\/?(?:system|user|assistant|tool_use|tool_result)\b/i
];
var USER_CONTENT_FIELDS = /* @__PURE__ */ new Set([
"originalFilename",
"filename",
"name",
"description",
"label",
"text",
"slug",
"webhookUrl"
]);
function detectInjection(value) {
return INJECTION_PATTERNS.some((pattern) => pattern.test(value));
}
function sanitizeValue(value) {
if (detectInjection(value)) {
return `[FLAGGED: potential prompt injection] ${value}`;
}
return value;
}
function sanitizeResponse(data) {
if (data === null || data === void 0) {
return data;
}
if (typeof data === "string") {
return sanitizeValue(data);
}
if (Array.isArray(data)) {
return data.map((item) => sanitizeResponse(item));
}
if (typeof data === "object") {
const result = {};
for (const [key, value] of Object.entries(data)) {
if (USER_CONTENT_FIELDS.has(key) && typeof value === "string") {
result[key] = sanitizeValue(value);
} else if (typeof value === "object" && value !== null) {
result[key] = sanitizeResponse(value);
} else {
result[key] = value;
}
}
return result;
}
return data;
}
// src/lib/output.ts
var _forceHuman = false;
var _globalFields;
var _globalNdjson = false;
function setForceHuman(value) {
_forceHuman = value;
}
function setGlobalFields(fields) {
_globalFields = fields;
}
function setGlobalNdjson(value) {
_globalNdjson = value;
}
function isJsonOutput() {
if (_forceHuman) {
return false;
}
return !process.stdout.isTTY;
}
function output(data, humanFormatter, options) {
const sanitized = sanitizeResponse(data);
const unwrapped = _globalFields || _globalNdjson ? unwrapArray(sanitized) : sanitized;
const masked = applyFieldMask(unwrapped, _globalFields);
if (_globalNdjson && Array.isArray(masked)) {
outputNdjson(masked);
return;
}
if (options?.json || isJsonOutput()) {
console.log(JSON.stringify(masked, null, 2));
} else if (humanFormatter && !_globalFields) {
console.log(humanFormatter());
} else {
console.log(JSON.stringify(masked, null, 2));
}
}
function outputError(error, code, details) {
if (isJsonOutput()) {
console.error(
JSON.stringify({ error, ...code ? { code } : {}, ...details })
);
} else {
console.error(`Error: ${error}`);
}
process.exit(1);
}
function outputNdjson(items) {
for (const item of items) {
const sanitized = sanitizeResponse(item);
console.log(JSON.stringify(sanitized));
}
}
function applyFieldMask(data, fields) {
if (!fields) {
return data;
}
const fieldSet = new Set(fields.split(",").map((f) => f.trim()));
if (Array.isArray(data)) {
return data.map((item) => pickFields(item, fieldSet));
}
if (typeof data === "object" && data !== null) {
return pickFields(data, fieldSet);
}
return data;
}
function unwrapArray(data) {
if (typeof data !== "object" || data === null || Array.isArray(data)) {
return data;
}
const entries = Object.entries(data);
if (entries.length === 1 && Array.isArray(entries[0][1])) {
return entries[0][1];
}
return data;
}
function pickFields(obj, fieldSet) {
if (typeof obj !== "object" || obj === null) {
return {};
}
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (fieldSet.has(key)) {
result[key] = value;
}
}
return result;
}
export {
setForceHuman,
setGlobalFields,
setGlobalNdjson,
isJsonOutput,
output,
outputError
};
// src/lib/parse-json-input.ts
function stripUndefined(obj) {
const result = {};
for (const [key, value] of Object.entries(obj)) {
if (value !== void 0) {
result[key] = value;
}
}
return result;
}
function parseJsonInput(jsonStr, flagValues) {
if (!jsonStr) {
return flagValues;
}
let parsed;
try {
parsed = JSON.parse(jsonStr);
} catch {
throw new Error(`Invalid JSON in --input-json: ${jsonStr.slice(0, 100)}`);
}
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
throw new Error("--input-json must be a JSON object");
}
return { ...parsed, ...stripUndefined(flagValues) };
}
export {
parseJsonInput
};
import {
validateBucketName,
validateFileKey
} from "./chunk-533UGNLM.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/delete.ts
async function deleteFile(bucket, key, options = {}) {
validateBucketName(bucket);
validateFileKey(key);
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "deleteFile",
details: { bucket, key }
},
null,
2
)
);
return;
}
const stow = createStow();
await stow.deleteFile(key, { bucket });
console.log(`Deleted: ${key} from ${bucket}`);
}
export {
deleteFile
};
import {
validateBucketName,
validateFileKey
} from "./chunk-NBHBVKP5.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/delete.ts
async function deleteFile(bucket, key, options = {}) {
validateBucketName(bucket);
validateFileKey(key);
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "deleteFile",
details: { bucket, key }
},
null,
2
)
);
return;
}
const stow = createStow();
await stow.deleteFile(key, { bucket });
console.log(`Deleted: ${key} from ${bucket}`);
}
export {
deleteFile
};
import {
CLI_DOCS
} from "./chunk-MYFLRBWC.js";
import {
output
} from "./chunk-KPIQZBTO.js";
// src/commands/describe.ts
var COMMAND_MAP = {
upload: "upload",
drop: "drop",
delete: "delete",
whoami: "whoami",
open: "open",
buckets: "buckets",
"buckets.create": "bucketsCreate",
"buckets.rename": "bucketsRename",
"buckets.delete": "bucketsDelete",
files: "files",
"files.get": "filesGet",
"files.update": "filesUpdate",
"files.enrich": "filesEnrich",
"files.missing": "filesMissing",
search: "search",
"search.text": "searchText",
"search.similar": "searchSimilar",
"search.color": "searchColor",
"search.diverse": "searchDiverse",
tags: "tags",
"tags.create": "tagsCreate",
"tags.delete": "tagsDelete",
drops: "drops",
"drops.delete": "dropsDelete",
profiles: "profiles",
"profiles.create": "profilesCreate",
"profiles.get": "profilesGet",
"profiles.delete": "profilesDelete",
jobs: "jobs",
"jobs.retry": "jobsRetry",
"jobs.delete": "jobsDelete",
admin: "admin",
"admin.health": "adminHealth",
"admin.backfill": "adminBackfill",
"admin.cleanup-drops": "adminCleanupDrops",
"admin.purge-events": "adminPurgeEvents",
"admin.reconcile-files": "adminReconcileFiles",
"admin.retry-sync-failures": "adminRetrySyncFailures",
"admin.jobs": "adminJobs",
"admin.jobs.retry": "adminJobsRetry",
"admin.jobs.delete": "adminJobsDelete",
"admin.queues": "adminQueues",
"admin.queues.clean": "adminQueuesClean"
};
function describeCommand(commandPath) {
if (!commandPath) {
output({
commands: Object.keys(COMMAND_MAP).sort()
});
return;
}
const docKey = COMMAND_MAP[commandPath];
if (!(docKey && CLI_DOCS[docKey])) {
console.error(`Unknown command: ${commandPath}`);
console.error(`Available: ${Object.keys(COMMAND_MAP).sort().join(", ")}`);
process.exit(1);
return;
}
const doc = CLI_DOCS[docKey];
output({
command: commandPath,
description: doc.description,
usage: doc.usage,
examples: doc.examples,
notes: doc.notes ?? []
});
}
export {
describeCommand
};
import {
CLI_DOCS
} from "./chunk-XJDK2CBE.js";
import {
output
} from "./chunk-RH4BOSYB.js";
// src/commands/describe.ts
var COMMAND_MAP = {
upload: "upload",
drop: "drop",
delete: "delete",
whoami: "whoami",
open: "open",
buckets: "buckets",
"buckets.create": "bucketsCreate",
"buckets.rename": "bucketsRename",
"buckets.delete": "bucketsDelete",
files: "files",
"files.get": "filesGet",
"files.update": "filesUpdate",
"files.enrich": "filesEnrich",
"files.missing": "filesMissing",
search: "search",
"search.text": "searchText",
"search.similar": "searchSimilar",
"search.color": "searchColor",
"search.diverse": "searchDiverse",
tags: "tags",
"tags.create": "tagsCreate",
"tags.delete": "tagsDelete",
drops: "drops",
"drops.delete": "dropsDelete",
profiles: "profiles",
"profiles.create": "profilesCreate",
"profiles.get": "profilesGet",
"profiles.delete": "profilesDelete",
jobs: "jobs",
"jobs.retry": "jobsRetry",
"jobs.delete": "jobsDelete",
admin: "admin",
"admin.health": "adminHealth",
"admin.backfill": "adminBackfill",
"admin.cleanup-drops": "adminCleanupDrops",
"admin.purge-events": "adminPurgeEvents",
"admin.reconcile-files": "adminReconcileFiles",
"admin.retry-sync-failures": "adminRetrySyncFailures",
"admin.jobs": "adminJobs",
"admin.jobs.retry": "adminJobsRetry",
"admin.jobs.delete": "adminJobsDelete",
"admin.queues": "adminQueues",
"admin.queues.clean": "adminQueuesClean"
};
function describeCommand(commandPath) {
if (!commandPath) {
output({
commands: Object.keys(COMMAND_MAP).sort()
});
return;
}
const docKey = COMMAND_MAP[commandPath];
if (!(docKey && CLI_DOCS[docKey])) {
console.error(`Unknown command: ${commandPath}`);
console.error(`Available: ${Object.keys(COMMAND_MAP).sort().join(", ")}`);
process.exit(1);
return;
}
const doc = CLI_DOCS[docKey];
output({
command: commandPath,
description: doc.description,
usage: doc.usage,
examples: doc.examples,
notes: doc.notes ?? []
});
}
export {
describeCommand
};
import {
formatBytes,
formatTable,
usageBar
} from "./chunk-PE6V3MVP.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/drops.ts
async function listDrops() {
const stow = createStow();
const data = await stow.listDrops();
if (data.drops.length === 0) {
console.log("No drops yet. Create one with: stow drop <file>");
return;
}
const rows = data.drops.map((d) => [
d.filename,
formatBytes(Number(d.size)),
d.contentType,
d.url
]);
console.log(formatTable(["Filename", "Size", "Type", "URL"], rows));
console.log(
`
Storage: ${usageBar(data.usage.bytes, data.usage.limit)} ${formatBytes(data.usage.bytes)} / ${formatBytes(data.usage.limit)}`
);
}
async function deleteDrop(id) {
const stow = createStow();
await stow.deleteDrop(id);
console.log(`Deleted drop: ${id}`);
}
export {
deleteDrop,
listDrops
};
import {
parseJsonInput
} from "./chunk-AHBVZRDR.js";
import {
validateBucketName,
validateFileKey
} from "./chunk-533UGNLM.js";
import {
isJsonOutput,
output
} from "./chunk-RH4BOSYB.js";
import {
formatBytes,
formatTable
} from "./chunk-FZGOTXTE.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import {
getApiKey,
getBaseUrl
} from "./chunk-TOADDO2F.js";
// src/commands/files.ts
async function listFiles(bucket, options) {
const stow = createStow();
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const data = await stow.listFiles({
bucket,
...options.search ? { prefix: options.search } : {},
...parsedLimit && Number.isFinite(parsedLimit) && parsedLimit > 0 ? { limit: parsedLimit } : {}
});
if (data.files.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log(`No files in bucket '${bucket}'.`);
}
return;
}
if (options.json || isJsonOutput()) {
output(data);
return;
}
const rows = data.files.map((f) => [
f.key,
formatBytes(f.size),
f.lastModified.split("T")[0] ?? f.lastModified
]);
console.log(formatTable(["Key", "Size", "Modified"], rows));
if (data.nextCursor) {
console.log("\n(more files available \u2014 use --limit to see more)");
}
}
async function getFile(bucket, key, _options) {
const stow = createStow();
const file = await stow.getFile(key, { bucket });
output(file, () => {
const lines = [
formatTable(
["Field", "Value"],
[
["Key", file.key],
["Size", formatBytes(file.size)],
["Type", file.contentType],
["Created", file.createdAt],
["URL", file.url ?? "(private)"],
[
"Dimensions",
file.width && file.height ? `${file.width}\xD7${file.height}` : "\u2014"
],
["Duration", file.duration ? `${file.duration}s` : "\u2014"],
["Embedding", file.embeddingStatus ?? "\u2014"]
]
)
];
if (file.metadata && Object.keys(file.metadata).length > 0) {
lines.push("\nMetadata:");
for (const [k, v] of Object.entries(file.metadata)) {
lines.push(` ${k}: ${v}`);
}
}
return lines.join("\n");
});
}
async function updateFile(bucket, key, options) {
validateBucketName(bucket);
validateFileKey(key);
const flagMetadata = {};
if (options.metadata && options.metadata.length > 0) {
for (const pair of options.metadata) {
const idx = pair.indexOf("=");
if (idx === -1) {
console.error(
`Error: Invalid metadata format '${pair}'. Use key=value.`
);
process.exit(1);
}
flagMetadata[pair.slice(0, idx)] = pair.slice(idx + 1);
}
}
const jsonInput = parseJsonInput(
options.inputJson,
{}
);
const hasFlagMetadata = Object.keys(flagMetadata).length > 0;
const metadata = hasFlagMetadata ? { ...jsonInput.metadata ?? {}, ...flagMetadata } : jsonInput.metadata;
if (!metadata || Object.keys(metadata).length === 0) {
console.error(
"Error: At least one -m key=value pair or --input-json with metadata is required."
);
process.exit(1);
}
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "updateFile",
details: { bucket, key, metadata }
},
null,
2
)
);
return;
}
const stow = createStow();
const file = await stow.updateFileMetadata(key, metadata, { bucket });
output(file, () => `Updated ${key}`);
}
async function enrichFile(bucket, key) {
const stow = createStow();
const results = await Promise.allSettled([
stow.generateTitle(key, { bucket }),
stow.generateDescription(key, { bucket }),
stow.generateAltText(key, { bucket })
]);
const labels = ["Title", "Description", "Alt text"];
for (let i = 0; i < results.length; i++) {
const result = results[i];
const label = labels[i];
if (result.status === "fulfilled") {
console.log(` ${label}: triggered`);
} else {
console.error(` ${label}: failed \u2014 ${result.reason}`);
}
}
const succeeded = results.filter((r) => r.status === "fulfilled").length;
console.log(
`
Enriched ${key}: ${succeeded}/${results.length} tasks dispatched`
);
}
async function listMissing(bucket, type, options) {
const validTypes = ["dimensions", "embeddings", "colors"];
if (!validTypes.includes(type)) {
console.error(
`Error: Invalid type '${type}'. Must be one of: ${validTypes.join(", ")}`
);
process.exit(1);
}
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const baseUrl = getBaseUrl();
const apiKey = getApiKey();
const params = new URLSearchParams({
bucket,
missing: type,
...parsedLimit && Number.isFinite(parsedLimit) && parsedLimit > 0 ? { limit: String(parsedLimit) } : {}
});
const res = await fetch(`${baseUrl}/files?${params}`, {
headers: { "x-api-key": apiKey }
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error ?? `HTTP ${res.status}`);
}
const data = await res.json();
if (data.files.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log(`No files missing ${type} in bucket '${bucket}'.`);
}
return;
}
if (options.json || isJsonOutput()) {
output(data);
return;
}
const rows = data.files.map((f) => [
f.key,
formatBytes(f.size),
f.lastModified.split("T")[0] ?? f.lastModified
]);
console.log(formatTable(["Key", "Size", "Modified"], rows));
console.log(`
${data.files.length} files missing ${type}`);
}
export {
enrichFile,
getFile,
listFiles,
listMissing,
updateFile
};
import {
parseJsonInput
} from "./chunk-XVKIRHTX.js";
import {
validateBucketName,
validateFileKey
} from "./chunk-NBHBVKP5.js";
import {
isJsonOutput,
output
} from "./chunk-KPIQZBTO.js";
import {
formatBytes,
formatTable
} from "./chunk-PE6V3MVP.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import {
getApiKey,
getBaseUrl
} from "./chunk-TOADDO2F.js";
// src/commands/files.ts
async function listFiles(bucket, options) {
const stow = createStow();
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const data = await stow.listFiles({
bucket,
...options.search ? { prefix: options.search } : {},
...parsedLimit && Number.isFinite(parsedLimit) && parsedLimit > 0 ? { limit: parsedLimit } : {}
});
if (data.files.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log(`No files in bucket '${bucket}'.`);
}
return;
}
if (options.json || isJsonOutput()) {
output(data);
return;
}
const rows = data.files.map((f) => [
f.key,
formatBytes(f.size),
f.lastModified.split("T")[0] ?? f.lastModified
]);
console.log(formatTable(["Key", "Size", "Modified"], rows));
if (data.nextCursor) {
console.log("\n(more files available \u2014 use --limit to see more)");
}
}
async function getFile(bucket, key, _options) {
const stow = createStow();
const file = await stow.getFile(key, { bucket });
output(file, () => {
const lines = [
formatTable(
["Field", "Value"],
[
["Key", file.key],
["Size", formatBytes(file.size)],
["Type", file.contentType],
["Created", file.createdAt],
["URL", file.url ?? "(private)"],
["Dimensions", file.width && file.height ? `${file.width}\xD7${file.height}` : "\u2014"],
["Duration", file.duration ? `${file.duration}s` : "\u2014"],
["Embedding", file.embeddingStatus ?? "\u2014"]
]
)
];
if (file.metadata && Object.keys(file.metadata).length > 0) {
lines.push("\nMetadata:");
for (const [k, v] of Object.entries(file.metadata)) {
lines.push(` ${k}: ${v}`);
}
}
return lines.join("\n");
});
}
async function updateFile(bucket, key, options) {
validateBucketName(bucket);
validateFileKey(key);
const flagMetadata = {};
if (options.metadata && options.metadata.length > 0) {
for (const pair of options.metadata) {
const idx = pair.indexOf("=");
if (idx === -1) {
console.error(`Error: Invalid metadata format '${pair}'. Use key=value.`);
process.exit(1);
}
flagMetadata[pair.slice(0, idx)] = pair.slice(idx + 1);
}
}
const jsonInput = parseJsonInput(options.inputJson, {});
const hasFlagMetadata = Object.keys(flagMetadata).length > 0;
const metadata = hasFlagMetadata ? { ...jsonInput.metadata, ...flagMetadata } : jsonInput.metadata;
if (!metadata || Object.keys(metadata).length === 0) {
console.error(
"Error: At least one -m key=value pair or --input-json with metadata is required."
);
process.exit(1);
}
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "updateFile",
details: { bucket, key, metadata }
},
null,
2
)
);
return;
}
const stow = createStow();
const file = await stow.updateFileMetadata(key, metadata, { bucket });
output(file, () => `Updated ${key}`);
}
async function enrichFile(bucket, key) {
const stow = createStow();
const results = await Promise.allSettled([
stow.generateTitle(key, { bucket }),
stow.generateDescription(key, { bucket }),
stow.generateAltText(key, { bucket })
]);
const labels = ["Title", "Description", "Alt text"];
for (let i = 0; i < results.length; i += 1) {
const result = results[i];
const label = labels[i];
if (result.status === "fulfilled") {
console.log(` ${label}: triggered`);
} else {
console.error(` ${label}: failed \u2014 ${result.reason}`);
}
}
const succeeded = results.filter((r) => r.status === "fulfilled").length;
console.log(`
Enriched ${key}: ${succeeded}/${results.length} tasks dispatched`);
}
async function listMissing(bucket, type, options) {
const validTypes = ["dimensions", "embeddings", "colors"];
if (!validTypes.includes(type)) {
console.error(`Error: Invalid type '${type}'. Must be one of: ${validTypes.join(", ")}`);
process.exit(1);
}
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const baseUrl = getBaseUrl();
const apiKey = getApiKey();
const params = new URLSearchParams({
bucket,
missing: type,
...parsedLimit && Number.isFinite(parsedLimit) && parsedLimit > 0 ? { limit: String(parsedLimit) } : {}
});
const res = await fetch(`${baseUrl}/files?${params}`, {
headers: { "x-api-key": apiKey }
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error ?? `HTTP ${res.status}`);
}
const data = await res.json();
if (data.files.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log(`No files missing ${type} in bucket '${bucket}'.`);
}
return;
}
if (options.json || isJsonOutput()) {
output(data);
return;
}
const rows = data.files.map((f) => [
f.key,
formatBytes(f.size),
f.lastModified.split("T")[0] ?? f.lastModified
]);
console.log(formatTable(["Key", "Size", "Modified"], rows));
console.log(`
${data.files.length} files missing ${type}`);
}
export {
enrichFile,
getFile,
listFiles,
listMissing,
updateFile
};
import {
adminRequest
} from "./chunk-QF7PVPWQ.js";
import {
output
} from "./chunk-KPIQZBTO.js";
import {
formatTable
} from "./chunk-PE6V3MVP.js";
import "./chunk-TOADDO2F.js";
// src/commands/admin/health.ts
async function health(options) {
const result = await adminRequest({
method: "GET",
path: "/health"
});
output(
result,
() => {
const lines = [];
const statusIcon = result.status === "ok" ? "+" : "x";
lines.push(`${statusIcon} ${result.status} (${result.version})`);
lines.push(` ${result.timestamp}`);
lines.push("\nChecks:");
for (const [name, status] of Object.entries(result.checks)) {
const icon = status === "ok" ? "+" : "x";
lines.push(` ${icon} ${name}`);
}
if (result.queues) {
lines.push("\nQueues:");
const rows = [];
for (const [name, counts] of Object.entries(result.queues)) {
if (counts === "unavailable") {
rows.push([name, "--", "--", "--", "--"]);
} else {
const c = counts;
rows.push([
name,
String(c.waiting ?? 0),
String(c.active ?? 0),
String(c.completed ?? 0),
String(c.failed ?? 0)
]);
}
}
lines.push(formatTable(["Queue", "Waiting", "Active", "Completed", "Failed"], rows));
}
return lines.join("\n");
},
{ json: options.json }
);
}
export {
health
};
import {
adminRequest
} from "./chunk-QF7PVPWQ.js";
import {
output
} from "./chunk-RH4BOSYB.js";
import {
formatTable
} from "./chunk-FZGOTXTE.js";
import "./chunk-TOADDO2F.js";
// src/commands/admin/health.ts
async function health(options) {
const result = await adminRequest({
method: "GET",
path: "/health"
});
output(
result,
() => {
const lines = [];
const statusIcon = result.status === "ok" ? "+" : "x";
lines.push(`${statusIcon} ${result.status} (${result.version})`);
lines.push(` ${result.timestamp}`);
lines.push("\nChecks:");
for (const [name, status] of Object.entries(result.checks)) {
const icon = status === "ok" ? "+" : "x";
lines.push(` ${icon} ${name}`);
}
if (result.queues) {
lines.push("\nQueues:");
const rows = [];
for (const [name, counts] of Object.entries(result.queues)) {
if (counts === "unavailable") {
rows.push([name, "--", "--", "--", "--"]);
} else {
const c = counts;
rows.push([
name,
String(c.waiting ?? 0),
String(c.active ?? 0),
String(c.completed ?? 0),
String(c.failed ?? 0)
]);
}
}
lines.push(
formatTable(
["Queue", "Waiting", "Active", "Completed", "Failed"],
rows
)
);
}
return lines.join("\n");
},
{ json: options.json }
);
}
export {
health
};
import {
adminRequest
} from "./chunk-QF7PVPWQ.js";
import {
isJsonOutput,
output
} from "./chunk-KPIQZBTO.js";
import {
formatTable
} from "./chunk-PE6V3MVP.js";
import "./chunk-TOADDO2F.js";
// src/commands/admin/jobs.ts
function formatTimestamp(ts) {
return new Date(ts).toISOString().replace("T", " ").slice(0, 19);
}
async function listAdminJobs(options) {
const params = new URLSearchParams();
if (options.org) {
params.set("orgId", options.org);
}
if (options.bucket) {
params.set("bucketId", options.bucket);
}
if (options.status) {
params.set("status", options.status);
}
if (options.queue) {
params.set("queue", options.queue);
}
if (options.limit) {
params.set("limit", options.limit);
}
const qs = params.toString();
const result = await adminRequest({
method: "GET",
path: `/admin/jobs${qs ? `?${qs}` : ""}`
});
if (result.jobs.length === 0) {
if (isJsonOutput() || options.json) {
output(result.jobs, void 0, { json: options.json });
} else {
console.log("No jobs found.");
}
return;
}
output(
result.jobs,
() => {
const rows = result.jobs.map((job) => [
job.jobId,
job.queueName,
job.status,
`${job.data.fileId.slice(0, 8)}...`,
`${job.data.orgId.slice(0, 8)}...`,
formatTimestamp(job.timestamp),
job.failedReason ? job.failedReason.slice(0, 40) : ""
]);
return formatTable(["ID", "Queue", "Status", "File", "Org", "Created", "Error"], rows);
},
{ json: options.json }
);
}
async function retryAdminJob(jobId, options) {
const result = await adminRequest({
method: "POST",
path: `/admin/jobs/${jobId}/retry`,
body: { queue: options.queue }
});
if (result.retried) {
console.log(`Job ${jobId} retried.`);
}
}
async function deleteAdminJob(jobId, options) {
const result = await adminRequest({
method: "DELETE",
path: `/admin/jobs/${jobId}?queue=${encodeURIComponent(options.queue)}`
});
if (result.deleted) {
console.log(`Job ${jobId} removed.`);
}
}
export {
deleteAdminJob,
listAdminJobs,
retryAdminJob
};
import {
isJsonOutput,
output
} from "./chunk-KPIQZBTO.js";
import {
formatTable
} from "./chunk-PE6V3MVP.js";
import {
getApiKey,
getBaseUrl
} from "./chunk-TOADDO2F.js";
// src/commands/jobs.ts
async function bucketRequest(opts) {
const baseUrl = getBaseUrl();
const apiKey = getApiKey();
const res = await fetch(`${baseUrl}${opts.path}`, {
method: opts.method,
headers: {
"x-api-key": apiKey,
...opts.body ? { "Content-Type": "application/json" } : {}
},
...opts.body ? { body: JSON.stringify(opts.body) } : {}
});
if (!res.ok) {
const body = await res.json().catch(() => ({ error: res.statusText }));
const message = body.error ?? `HTTP ${res.status}`;
throw new Error(message);
}
return await res.json();
}
function formatTimestamp(ts) {
return new Date(ts).toISOString().replace("T", " ").slice(0, 19);
}
async function listJobs(bucketId, options) {
const params = new URLSearchParams();
if (options.status) {
params.set("status", options.status);
}
if (options.queue) {
params.set("queue", options.queue);
}
if (options.limit) {
params.set("limit", options.limit);
}
const qs = params.toString();
const path = `/buckets/${bucketId}/jobs${qs ? `?${qs}` : ""}`;
const result = await bucketRequest({
method: "GET",
path
});
if (result.jobs.length === 0) {
if (isJsonOutput() || options.json) {
output(result.jobs);
} else {
console.log("No jobs found.");
}
return;
}
output(
result.jobs,
() => {
const rows = result.jobs.map((job) => [
job.jobId,
job.queueName,
job.status,
`${job.data.fileId.slice(0, 8)}...`,
formatTimestamp(job.timestamp),
job.failedReason ? job.failedReason.slice(0, 40) : ""
]);
return formatTable(["ID", "Queue", "Status", "File", "Created", "Error"], rows);
},
{ json: options.json }
);
}
async function retryJob(jobId, options) {
const result = await bucketRequest({
method: "POST",
path: `/buckets/${options.bucket}/jobs/${jobId}/retry`,
body: { queue: options.queue }
});
if (result.retried) {
console.log(`Job ${jobId} retried.`);
}
}
async function deleteJob(jobId, options) {
const result = await bucketRequest({
method: "DELETE",
path: `/buckets/${options.bucket}/jobs/${jobId}?queue=${encodeURIComponent(options.queue)}`
});
if (result.deleted) {
console.log(`Job ${jobId} removed.`);
}
}
export {
deleteJob,
listJobs,
retryJob
};
import {
adminRequest
} from "./chunk-QF7PVPWQ.js";
import {
isJsonOutput,
output
} from "./chunk-RH4BOSYB.js";
import {
formatTable
} from "./chunk-FZGOTXTE.js";
import "./chunk-TOADDO2F.js";
// src/commands/admin/jobs.ts
function formatTimestamp(ts) {
return new Date(ts).toISOString().replace("T", " ").slice(0, 19);
}
async function listAdminJobs(options) {
const params = new URLSearchParams();
if (options.org) {
params.set("orgId", options.org);
}
if (options.bucket) {
params.set("bucketId", options.bucket);
}
if (options.status) {
params.set("status", options.status);
}
if (options.queue) {
params.set("queue", options.queue);
}
if (options.limit) {
params.set("limit", options.limit);
}
const qs = params.toString();
const result = await adminRequest({
method: "GET",
path: `/admin/jobs${qs ? `?${qs}` : ""}`
});
if (result.jobs.length === 0) {
if (isJsonOutput() || options.json) {
output(result.jobs, void 0, { json: options.json });
} else {
console.log("No jobs found.");
}
return;
}
output(
result.jobs,
() => {
const rows = result.jobs.map((job) => [
job.jobId,
job.queueName,
job.status,
`${job.data.fileId.slice(0, 8)}...`,
`${job.data.orgId.slice(0, 8)}...`,
formatTimestamp(job.timestamp),
job.failedReason ? job.failedReason.slice(0, 40) : ""
]);
return formatTable(
["ID", "Queue", "Status", "File", "Org", "Created", "Error"],
rows
);
},
{ json: options.json }
);
}
async function retryAdminJob(jobId, options) {
const result = await adminRequest({
method: "POST",
path: `/admin/jobs/${jobId}/retry`,
body: { queue: options.queue }
});
if (result.retried) {
console.log(`Job ${jobId} retried.`);
}
}
async function deleteAdminJob(jobId, options) {
const result = await adminRequest({
method: "DELETE",
path: `/admin/jobs/${jobId}?queue=${encodeURIComponent(options.queue)}`
});
if (result.deleted) {
console.log(`Job ${jobId} removed.`);
}
}
export {
deleteAdminJob,
listAdminJobs,
retryAdminJob
};
import {
isJsonOutput,
output
} from "./chunk-RH4BOSYB.js";
import {
formatTable
} from "./chunk-FZGOTXTE.js";
import {
getApiKey,
getBaseUrl
} from "./chunk-TOADDO2F.js";
// src/commands/jobs.ts
async function bucketRequest(opts) {
const baseUrl = getBaseUrl();
const apiKey = getApiKey();
const res = await fetch(`${baseUrl}${opts.path}`, {
method: opts.method,
headers: {
"x-api-key": apiKey,
...opts.body ? { "Content-Type": "application/json" } : {}
},
...opts.body ? { body: JSON.stringify(opts.body) } : {}
});
if (!res.ok) {
const body = await res.json().catch(() => ({ error: res.statusText }));
const message = body.error ?? `HTTP ${res.status}`;
throw new Error(message);
}
return await res.json();
}
function formatTimestamp(ts) {
return new Date(ts).toISOString().replace("T", " ").slice(0, 19);
}
async function listJobs(bucketId, options) {
const params = new URLSearchParams();
if (options.status) {
params.set("status", options.status);
}
if (options.queue) {
params.set("queue", options.queue);
}
if (options.limit) {
params.set("limit", options.limit);
}
const qs = params.toString();
const path = `/buckets/${bucketId}/jobs${qs ? `?${qs}` : ""}`;
const result = await bucketRequest({
method: "GET",
path
});
if (result.jobs.length === 0) {
if (isJsonOutput() || options.json) {
output(result.jobs);
} else {
console.log("No jobs found.");
}
return;
}
output(
result.jobs,
() => {
const rows = result.jobs.map((job) => [
job.jobId,
job.queueName,
job.status,
`${job.data.fileId.slice(0, 8)}...`,
formatTimestamp(job.timestamp),
job.failedReason ? job.failedReason.slice(0, 40) : ""
]);
return formatTable(
["ID", "Queue", "Status", "File", "Created", "Error"],
rows
);
},
{ json: options.json }
);
}
async function retryJob(jobId, options) {
const result = await bucketRequest({
method: "POST",
path: `/buckets/${options.bucket}/jobs/${jobId}/retry`,
body: { queue: options.queue }
});
if (result.retried) {
console.log(`Job ${jobId} retried.`);
}
}
async function deleteJob(jobId, options) {
const result = await bucketRequest({
method: "DELETE",
path: `/buckets/${options.bucket}/jobs/${jobId}?queue=${encodeURIComponent(options.queue)}`
});
if (result.deleted) {
console.log(`Job ${jobId} removed.`);
}
}
export {
deleteJob,
listJobs,
retryJob
};
import {
adminRequest
} from "./chunk-QF7PVPWQ.js";
import {
output
} from "./chunk-KPIQZBTO.js";
import "./chunk-TOADDO2F.js";
// src/commands/admin/maintenance.ts
async function cleanupDrops(options) {
const params = new URLSearchParams();
if (options.maxAgeHours) {
params.set("maxAgeHours", options.maxAgeHours);
}
if (options.dryRun) {
params.set("dryRun", "true");
}
const qs = params.toString();
const result = await adminRequest({
method: "POST",
path: `/admin/cleanup-drops${qs ? `?${qs}` : ""}`
});
output(
result,
() => options.dryRun ? `[dry run] Would clean up ${result.count ?? 0} drops` : `Cleaned up ${result.deleted ?? 0} drops`,
{ json: options.json }
);
}
async function purgeEvents(options) {
const params = new URLSearchParams();
if (options.dryRun) {
params.set("dryRun", "true");
}
const qs = params.toString();
const result = await adminRequest({
method: "POST",
path: `/admin/purge-events${qs ? `?${qs}` : ""}`
});
output(
result,
() => options.dryRun ? `[dry run] Would purge ${result.count ?? 0} events` : `Purged ${result.deleted ?? 0} events`,
{ json: options.json }
);
}
async function reconcileFiles(options) {
const params = new URLSearchParams({ bucketId: options.bucket });
if (options.dryRun) {
params.set("dryRun", "true");
}
const result = await adminRequest({
method: "POST",
path: `/admin/reconcile-files?${params}`
});
output(
result,
() => {
if (options.dryRun) {
return `[dry run] ${result.mismatched ?? 0} files need reconciliation`;
}
return `Reconciled ${result.reconciled ?? 0} files`;
},
{ json: options.json }
);
}
async function retrySyncFailures(options) {
const result = await adminRequest({
method: "POST",
path: "/admin/retry-sync-failures"
});
output(result, () => `Retried ${result.retried ?? 0} sync failures`, {
json: options.json
});
}
export {
cleanupDrops,
purgeEvents,
reconcileFiles,
retrySyncFailures
};
import {
adminRequest
} from "./chunk-QF7PVPWQ.js";
import {
output
} from "./chunk-RH4BOSYB.js";
import "./chunk-TOADDO2F.js";
// src/commands/admin/maintenance.ts
async function cleanupDrops(options) {
const params = new URLSearchParams();
if (options.maxAgeHours) {
params.set("maxAgeHours", options.maxAgeHours);
}
if (options.dryRun) {
params.set("dryRun", "true");
}
const qs = params.toString();
const result = await adminRequest({
method: "POST",
path: `/admin/cleanup-drops${qs ? `?${qs}` : ""}`
});
output(
result,
() => options.dryRun ? `[dry run] Would clean up ${result.count ?? 0} drops` : `Cleaned up ${result.deleted ?? 0} drops`,
{ json: options.json }
);
}
async function purgeEvents(options) {
const params = new URLSearchParams();
if (options.dryRun) {
params.set("dryRun", "true");
}
const qs = params.toString();
const result = await adminRequest({
method: "POST",
path: `/admin/purge-events${qs ? `?${qs}` : ""}`
});
output(
result,
() => options.dryRun ? `[dry run] Would purge ${result.count ?? 0} events` : `Purged ${result.deleted ?? 0} events`,
{ json: options.json }
);
}
async function reconcileFiles(options) {
const params = new URLSearchParams({ bucketId: options.bucket });
if (options.dryRun) {
params.set("dryRun", "true");
}
const result = await adminRequest({
method: "POST",
path: `/admin/reconcile-files?${params}`
});
output(
result,
() => {
if (options.dryRun) {
return `[dry run] ${result.mismatched ?? 0} files need reconciliation`;
}
return `Reconciled ${result.reconciled ?? 0} files`;
},
{ json: options.json }
);
}
async function retrySyncFailures(options) {
const result = await adminRequest({
method: "POST",
path: "/admin/retry-sync-failures"
});
output(result, () => `Retried ${result.retried ?? 0} sync failures`, {
json: options.json
});
}
export {
cleanupDrops,
purgeEvents,
reconcileFiles,
retrySyncFailures
};
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/mcp.ts
import { z } from "zod";
async function startMcpServer() {
const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
const server = new McpServer({
name: "stow",
version: "2.0.4"
});
server.tool("stow_whoami", "Show current user and organization", {}, async () => {
const stow = createStow();
const result = await stow.whoami();
return { content: [{ type: "text", text: JSON.stringify(result) }] };
});
server.tool("stow_buckets_list", "List all buckets", {}, async () => {
const stow = createStow();
const result = await stow.listBuckets();
return { content: [{ type: "text", text: JSON.stringify(result) }] };
});
server.tool(
"stow_files_list",
"List files in a bucket",
{
bucket: z.string().describe("Bucket name"),
limit: z.number().optional().describe("Max results (default 50)")
},
async (params) => {
const stow = createStow();
const result = await stow.listFiles({
bucket: params.bucket,
limit: params.limit
});
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
);
server.tool(
"stow_files_get",
"Get file details",
{
bucket: z.string().describe("Bucket name"),
key: z.string().describe("File key")
},
async (params) => {
const stow = createStow();
const result = await stow.getFile(params.key, {
bucket: params.bucket
});
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
);
server.tool(
"stow_search_text",
"Search files by text query",
{
query: z.string().describe("Search query"),
bucket: z.string().optional().describe("Bucket name (optional)"),
limit: z.number().optional().describe("Max results (default 10)")
},
async (params) => {
const stow = createStow();
const result = await stow.search.text({
query: params.query,
bucket: params.bucket,
limit: params.limit
});
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
);
server.tool(
"stow_search_similar",
"Find visually similar files",
{
fileKey: z.string().describe("File key to find similar files for"),
bucket: z.string().optional().describe("Bucket name (optional)"),
limit: z.number().optional().describe("Max results (default 10)")
},
async (params) => {
const stow = createStow();
const result = await stow.search.similar({
fileKey: params.fileKey,
bucket: params.bucket,
limit: params.limit
});
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
);
server.tool(
"stow_search_color",
"Search files by hex color",
{
hex: z.string().describe("Hex color code (e.g. #ff0000)"),
bucket: z.string().optional().describe("Bucket name (optional)"),
limit: z.number().optional().describe("Max results (default 10)")
},
async (params) => {
const stow = createStow();
const result = await stow.search.color({
hex: params.hex,
bucket: params.bucket,
limit: params.limit
});
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
);
server.tool(
"stow_search_diverse",
"Get a diverse set of files from a bucket",
{
bucket: z.string().optional().describe("Bucket name (optional)"),
limit: z.number().optional().describe("Max results (default 10)")
},
async (params) => {
const stow = createStow();
const result = await stow.search.diverse({
bucket: params.bucket,
limit: params.limit
});
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
);
server.tool(
"stow_search_image",
"Search by image URL or existing file key",
{
url: z.string().optional().describe("Image URL to search by"),
fileKey: z.string().optional().describe("Existing file key to search by"),
bucket: z.string().optional().describe("Bucket name (optional)"),
limit: z.number().optional().describe("Max results (default 12)")
},
async (params) => {
const stow = createStow();
const input = {};
if (params.url) {
input.url = params.url;
}
if (params.fileKey) {
input.fileKey = params.fileKey;
}
const result = await stow.search.image(input, {
bucket: params.bucket,
limit: params.limit ?? 12
});
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
);
server.tool("stow_tags_list", "List all tags", {}, async () => {
const stow = createStow();
const result = await stow.tags.list();
return { content: [{ type: "text", text: JSON.stringify(result) }] };
});
server.tool(
"stow_anchors_list",
"List anchors in a bucket",
{
bucket: z.string().optional().describe("Bucket name (optional)")
},
async (params) => {
const stow = createStow();
const result = await stow.anchors.list({ bucket: params.bucket });
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
);
server.tool(
"stow_anchors_create",
"Create a text anchor for semantic search",
{
text: z.string().describe("Anchor text to embed"),
label: z.string().optional().describe("Human-readable label (optional)")
},
async (params) => {
const stow = createStow();
const result = await stow.anchors.create({
text: params.text,
label: params.label
});
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
}
export {
startMcpServer
};
import {
output
} from "./chunk-RH4BOSYB.js";
import {
formatTable
} from "./chunk-FZGOTXTE.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/profiles.ts
async function createProfile(options) {
const stow = createStow();
const profile = await stow.profiles.create({
name: options.name,
...options.bucket ? { bucketId: options.bucket } : {}
});
output(profile, () => `Created profile: ${profile.name} (${profile.id})`, {
json: options.json
});
}
async function getProfile(id, options) {
const stow = createStow();
const profile = await stow.profiles.get(id);
output(
profile,
() => {
const lines = [`Profile: ${profile.name} (${profile.id})`];
if (profile.clusters && profile.clusters.length > 0) {
lines.push("\nClusters:");
const rows = profile.clusters.map((c) => [
String(c.index),
c.name ?? "(unnamed)",
String(c.signalCount)
]);
lines.push(formatTable(["Index", "Name", "Signals"], rows));
}
return lines.join("\n");
},
{ json: options.json }
);
}
async function deleteProfile(id) {
const stow = createStow();
await stow.profiles.delete(id);
console.log(`Deleted profile: ${id}`);
}
export {
createProfile,
deleteProfile,
getProfile
};
import {
output
} from "./chunk-KPIQZBTO.js";
import {
formatTable
} from "./chunk-PE6V3MVP.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/profiles.ts
async function createProfile(options) {
const stow = createStow();
const profile = await stow.profiles.create({
name: options.name,
...options.bucket ? { bucketId: options.bucket } : {}
});
output(profile, () => `Created profile: ${profile.name} (${profile.id})`, {
json: options.json
});
}
async function getProfile(id, options) {
const stow = createStow();
const profile = await stow.profiles.get(id);
output(
profile,
() => {
const lines = [`Profile: ${profile.name} (${profile.id})`];
if (profile.clusters && profile.clusters.length > 0) {
lines.push("\nClusters:");
const rows = profile.clusters.map((c) => [
String(c.index),
c.name ?? "(unnamed)",
String(c.signalCount)
]);
lines.push(formatTable(["Index", "Name", "Signals"], rows));
}
return lines.join("\n");
},
{ json: options.json }
);
}
async function deleteProfile(id) {
const stow = createStow();
await stow.profiles.delete(id);
console.log(`Deleted profile: ${id}`);
}
export {
createProfile,
deleteProfile,
getProfile
};
import {
adminRequest
} from "./chunk-QF7PVPWQ.js";
import {
isJsonOutput,
output
} from "./chunk-KPIQZBTO.js";
import {
formatTable
} from "./chunk-PE6V3MVP.js";
import "./chunk-TOADDO2F.js";
// src/commands/admin/queues.ts
async function listQueues(options) {
const result = await adminRequest({
method: "GET",
path: "/admin/queues"
});
const entries = Object.entries(result.queues);
if (entries.length === 0) {
if (isJsonOutput() || options.json) {
output(result.queues, void 0, { json: options.json });
} else {
console.log("No queues found.");
}
return;
}
output(
result.queues,
() => {
const rows = entries.map(([name, counts]) => [
name,
String(counts.waiting),
String(counts.active),
String(counts.completed),
String(counts.failed)
]);
return formatTable(["Queue", "Waiting", "Active", "Completed", "Failed"], rows);
},
{ json: options.json }
);
}
async function cleanQueue(queueName, options) {
const status = options.failed ? "failed" : "completed";
const grace = options.grace ? Number(options.grace) : 0;
const result = await adminRequest({
method: "POST",
path: `/admin/queues/${encodeURIComponent(queueName)}/clean`,
body: { status, grace }
});
console.log(`Cleaned ${result.cleaned} ${result.status} jobs from ${result.queue}.`);
}
export {
cleanQueue,
listQueues
};
import {
adminRequest
} from "./chunk-QF7PVPWQ.js";
import {
isJsonOutput,
output
} from "./chunk-RH4BOSYB.js";
import {
formatTable
} from "./chunk-FZGOTXTE.js";
import "./chunk-TOADDO2F.js";
// src/commands/admin/queues.ts
async function listQueues(options) {
const result = await adminRequest({
method: "GET",
path: "/admin/queues"
});
const entries = Object.entries(result.queues);
if (entries.length === 0) {
if (isJsonOutput() || options.json) {
output(result.queues, void 0, { json: options.json });
} else {
console.log("No queues found.");
}
return;
}
output(
result.queues,
() => {
const rows = entries.map(([name, counts]) => [
name,
String(counts.waiting),
String(counts.active),
String(counts.completed),
String(counts.failed)
]);
return formatTable(
["Queue", "Waiting", "Active", "Completed", "Failed"],
rows
);
},
{ json: options.json }
);
}
async function cleanQueue(queueName, options) {
const status = options.failed ? "failed" : "completed";
const grace = options.grace ? Number(options.grace) : 0;
const result = await adminRequest({
method: "POST",
path: `/admin/queues/${encodeURIComponent(queueName)}/clean`,
body: { status, grace }
});
console.log(
`Cleaned ${result.cleaned} ${result.status} jobs from ${result.queue}.`
);
}
export {
cleanQueue,
listQueues
};
import {
isJsonOutput,
output
} from "./chunk-RH4BOSYB.js";
import {
formatBytes,
formatTable
} from "./chunk-FZGOTXTE.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/search.ts
async function textSearch(query, options) {
const stow = createStow();
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const data = await stow.search.text({
query,
...options.bucket ? { bucket: options.bucket } : {},
...parsedLimit && parsedLimit > 0 ? { limit: parsedLimit } : {}
});
if (data.results.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log("No results found.");
}
return;
}
output(
data,
() => {
const rows = data.results.map((r) => [
r.key,
formatBytes(r.size),
r.similarity.toFixed(3)
]);
return formatTable(["Key", "Size", "Similarity"], rows);
},
{ json: options.json }
);
}
async function similarSearch(options) {
const stow = createStow();
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const data = await stow.search.similar({
fileKey: options.file,
...options.bucket ? { bucket: options.bucket } : {},
...parsedLimit && parsedLimit > 0 ? { limit: parsedLimit } : {}
});
if (data.results.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log("No similar files found.");
}
return;
}
output(
data,
() => {
const rows = data.results.map((r) => [
r.key,
formatBytes(r.size),
r.similarity.toFixed(3)
]);
return formatTable(["Key", "Size", "Similarity"], rows);
},
{ json: options.json }
);
}
async function colorSearch(options) {
const stow = createStow();
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const data = await stow.search.color({
hex: options.hex,
...options.bucket ? { bucket: options.bucket } : {},
...parsedLimit && parsedLimit > 0 ? { limit: parsedLimit } : {}
});
if (data.results.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log("No results found.");
}
return;
}
output(
data,
() => {
const rows = data.results.map((r) => [
r.key,
r.contentType,
r.colorDistance.toFixed(3)
]);
return formatTable(["Key", "Type", "Distance"], rows);
},
{ json: options.json }
);
}
async function diverseSearch(options) {
const stow = createStow();
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const data = await stow.search.diverse({
...options.bucket ? { bucket: options.bucket } : {},
...parsedLimit && parsedLimit > 0 ? { limit: parsedLimit } : {}
});
if (data.results.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log("No results found.");
}
return;
}
output(
data,
() => {
const rows = data.results.map((r) => [
r.key,
formatBytes(r.size),
r.similarity.toFixed(3)
]);
return formatTable(["Key", "Size", "Similarity"], rows);
},
{ json: options.json }
);
}
export {
colorSearch,
diverseSearch,
similarSearch,
textSearch
};
import {
isJsonOutput,
output
} from "./chunk-KPIQZBTO.js";
import {
formatBytes,
formatTable
} from "./chunk-PE6V3MVP.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/search.ts
async function textSearch(query, options) {
const stow = createStow();
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const data = await stow.search.text({
query,
...options.bucket ? { bucket: options.bucket } : {},
...parsedLimit && parsedLimit > 0 ? { limit: parsedLimit } : {}
});
if (data.results.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log("No results found.");
}
return;
}
output(
data,
() => {
const rows = data.results.map((r) => [r.key, formatBytes(r.size), r.similarity.toFixed(3)]);
return formatTable(["Key", "Size", "Similarity"], rows);
},
{ json: options.json }
);
}
async function similarSearch(options) {
const stow = createStow();
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const data = await stow.search.similar({
fileKey: options.file,
...options.bucket ? { bucket: options.bucket } : {},
...parsedLimit && parsedLimit > 0 ? { limit: parsedLimit } : {}
});
if (data.results.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log("No similar files found.");
}
return;
}
output(
data,
() => {
const rows = data.results.map((r) => [r.key, formatBytes(r.size), r.similarity.toFixed(3)]);
return formatTable(["Key", "Size", "Similarity"], rows);
},
{ json: options.json }
);
}
async function colorSearch(options) {
const stow = createStow();
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const data = await stow.search.color({
hex: options.hex,
...options.bucket ? { bucket: options.bucket } : {},
...parsedLimit && parsedLimit > 0 ? { limit: parsedLimit } : {}
});
if (data.results.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log("No results found.");
}
return;
}
output(
data,
() => {
const rows = data.results.map((r) => [r.key, r.contentType, r.colorDistance.toFixed(3)]);
return formatTable(["Key", "Type", "Distance"], rows);
},
{ json: options.json }
);
}
async function diverseSearch(options) {
const stow = createStow();
const parsedLimit = options.limit ? Number.parseInt(options.limit, 10) : null;
const data = await stow.search.diverse({
...options.bucket ? { bucket: options.bucket } : {},
...parsedLimit && parsedLimit > 0 ? { limit: parsedLimit } : {}
});
if (data.results.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log("No results found.");
}
return;
}
output(
data,
() => {
const rows = data.results.map((r) => [r.key, formatBytes(r.size), r.similarity.toFixed(3)]);
return formatTable(["Key", "Size", "Similarity"], rows);
},
{ json: options.json }
);
}
export {
colorSearch,
diverseSearch,
similarSearch,
textSearch
};
import {
parseJsonInput
} from "./chunk-AHBVZRDR.js";
import {
validateInput
} from "./chunk-533UGNLM.js";
import {
isJsonOutput,
output
} from "./chunk-RH4BOSYB.js";
import {
formatTable
} from "./chunk-FZGOTXTE.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/tags.ts
async function listTags(options) {
const stow = createStow();
const data = await stow.tags.list();
if (data.tags.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log("No tags yet. Create one with: stow tags create <name>");
}
return;
}
output(data, () => {
const rows = data.tags.map((t) => [t.name, t.slug, t.color ?? "\u2014", t.id]);
return formatTable(["Name", "Slug", "Color", "ID"], rows);
});
}
async function createTag(name, options) {
const input = parseJsonInput(
options.inputJson,
{ name, color: options.color }
);
validateInput(input.name, "tag name");
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "createTag",
details: {
name: input.name,
color: input.color ?? null
}
},
null,
2
)
);
return;
}
const stow = createStow();
const tag = await stow.tags.create({
name: input.name,
...input.color ? { color: input.color } : {}
});
output(tag, () => `Created tag: ${tag.name}`);
}
async function deleteTag(id, options = {}) {
validateInput(id, "tag id");
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "deleteTag",
details: { id }
},
null,
2
)
);
return;
}
const stow = createStow();
await stow.tags.delete(id);
console.log(`Deleted tag: ${id}`);
}
export {
createTag,
deleteTag,
listTags
};
import {
parseJsonInput
} from "./chunk-XVKIRHTX.js";
import {
validateInput
} from "./chunk-NBHBVKP5.js";
import {
isJsonOutput,
output
} from "./chunk-KPIQZBTO.js";
import {
formatTable
} from "./chunk-PE6V3MVP.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/tags.ts
async function listTags(options) {
const stow = createStow();
const data = await stow.tags.list();
if (data.tags.length === 0) {
if (isJsonOutput() || options.json) {
output(data);
} else {
console.log("No tags yet. Create one with: stow tags create <name>");
}
return;
}
output(data, () => {
const rows = data.tags.map((t) => [t.name, t.slug, t.color ?? "\u2014", t.id]);
return formatTable(["Name", "Slug", "Color", "ID"], rows);
});
}
async function createTag(name, options) {
const input = parseJsonInput(options.inputJson, {
name,
color: options.color
});
validateInput(input.name, "tag name");
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "createTag",
details: {
name: input.name,
color: input.color ?? null
}
},
null,
2
)
);
return;
}
const stow = createStow();
const tag = await stow.tags.create({
name: input.name,
...input.color ? { color: input.color } : {}
});
output(tag, () => `Created tag: ${tag.name}`);
}
async function deleteTag(id, options = {}) {
validateInput(id, "tag id");
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "deleteTag",
details: { id }
},
null,
2
)
);
return;
}
const stow = createStow();
await stow.tags.delete(id);
console.log(`Deleted tag: ${id}`);
}
export {
createTag,
deleteTag,
listTags
};
import {
validateBucketName
} from "./chunk-533UGNLM.js";
import {
formatBytes
} from "./chunk-FZGOTXTE.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/upload.ts
import { existsSync, readFileSync, statSync } from "fs";
import { basename, resolve } from "path";
var CONTENT_TYPES = {
png: "image/png",
jpg: "image/jpeg",
jpeg: "image/jpeg",
gif: "image/gif",
webp: "image/webp",
svg: "image/svg+xml",
ico: "image/x-icon",
avif: "image/avif",
pdf: "application/pdf",
mp4: "video/mp4",
webm: "video/webm",
mov: "video/quicktime",
mp3: "audio/mpeg",
wav: "audio/wav",
ogg: "audio/ogg",
zip: "application/zip",
tar: "application/x-tar",
gz: "application/gzip",
txt: "text/plain",
json: "application/json",
xml: "application/xml",
html: "text/html",
css: "text/css",
js: "application/javascript"
};
function getContentType(filename) {
const ext = filename.toLowerCase().split(".").pop();
return CONTENT_TYPES[ext || ""] || "application/octet-stream";
}
function readFile(filePath) {
const resolvedPath = resolve(filePath);
if (!existsSync(resolvedPath)) {
console.error(`Error: File not found: ${filePath}`);
process.exit(1);
}
const buffer = readFileSync(resolvedPath);
const filename = basename(resolvedPath);
const contentType = getContentType(filename);
return { buffer, filename, contentType };
}
function printUploadResult(url, options, opts) {
if (options.quiet) {
console.log(url);
return;
}
console.error(opts?.deduped ? "Done! (deduped)" : "Done!");
console.log("");
console.log(url);
}
async function uploadDrop(filePath, options) {
const { buffer, filename, contentType } = readFile(filePath);
if (!options.quiet) {
console.error(`Uploading ${filename} (${formatBytes(buffer.length)})...`);
}
const stow = createStow();
const result = await stow.drop(buffer, {
filename,
contentType
});
printUploadResult(result.url, options);
}
async function uploadFile(filePath, options) {
if (options.bucket) {
validateBucketName(options.bucket);
}
const resolvedPath = resolve(filePath);
if (!existsSync(resolvedPath)) {
console.error(`Error: File not found: ${filePath}`);
process.exit(1);
}
const filename = basename(resolvedPath);
const contentType = getContentType(filename);
const size = statSync(resolvedPath).size;
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "upload",
details: {
file: resolvedPath,
filename,
contentType,
size,
bucket: options.bucket ?? null
}
},
null,
2
)
);
return;
}
const buffer = readFileSync(resolvedPath);
if (!options.quiet) {
console.error(`Uploading ${filename} (${formatBytes(buffer.length)})...`);
}
const stow = createStow();
const result = await stow.uploadFile(buffer, {
...options.bucket ? { bucket: options.bucket } : {},
filename,
contentType
});
printUploadResult(result.url ?? result.key, options, {
deduped: result.deduped
});
}
export {
uploadDrop,
uploadFile
};
import {
validateBucketName
} from "./chunk-NBHBVKP5.js";
import {
formatBytes
} from "./chunk-PE6V3MVP.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/upload.ts
import { existsSync, readFileSync, statSync } from "fs";
import { basename, resolve } from "path";
var CONTENT_TYPES = {
png: "image/png",
jpg: "image/jpeg",
jpeg: "image/jpeg",
gif: "image/gif",
webp: "image/webp",
svg: "image/svg+xml",
ico: "image/x-icon",
avif: "image/avif",
pdf: "application/pdf",
mp4: "video/mp4",
webm: "video/webm",
mov: "video/quicktime",
mp3: "audio/mpeg",
wav: "audio/wav",
ogg: "audio/ogg",
zip: "application/zip",
tar: "application/x-tar",
gz: "application/gzip",
txt: "text/plain",
json: "application/json",
xml: "application/xml",
html: "text/html",
css: "text/css",
js: "application/javascript"
};
function getContentType(filename) {
const ext = filename.toLowerCase().split(".").pop();
return CONTENT_TYPES[ext || ""] || "application/octet-stream";
}
function readFile(filePath) {
const resolvedPath = resolve(filePath);
if (!existsSync(resolvedPath)) {
console.error(`Error: File not found: ${filePath}`);
process.exit(1);
}
const buffer = readFileSync(resolvedPath);
const filename = basename(resolvedPath);
const contentType = getContentType(filename);
return { buffer, filename, contentType };
}
function printUploadResult(url, options, opts) {
if (options.quiet) {
console.log(url);
return;
}
console.error(opts?.deduped ? "Done! (deduped)" : "Done!");
console.log("");
console.log(url);
}
async function uploadDrop(filePath, options) {
const { buffer, filename, contentType } = readFile(filePath);
if (!options.quiet) {
console.error(`Uploading ${filename} (${formatBytes(buffer.length)})...`);
}
const stow = createStow();
const result = await stow.drop(buffer, {
filename,
contentType
});
printUploadResult(result.url, options);
}
async function uploadFile(filePath, options) {
if (options.bucket) {
validateBucketName(options.bucket);
}
const resolvedPath = resolve(filePath);
if (!existsSync(resolvedPath)) {
console.error(`Error: File not found: ${filePath}`);
process.exit(1);
}
const filename = basename(resolvedPath);
const contentType = getContentType(filename);
const { size } = statSync(resolvedPath);
if (options.dryRun) {
console.log(
JSON.stringify(
{
dryRun: true,
action: "upload",
details: {
file: resolvedPath,
filename,
contentType,
size,
bucket: options.bucket ?? null
}
},
null,
2
)
);
return;
}
const buffer = readFileSync(resolvedPath);
if (!options.quiet) {
console.error(`Uploading ${filename} (${formatBytes(buffer.length)})...`);
}
const stow = createStow();
const result = await stow.uploadFile(buffer, {
...options.bucket ? { bucket: options.bucket } : {},
filename,
contentType
});
printUploadResult(result.url ?? result.key, options, {
deduped: result.deduped
});
}
export {
uploadDrop,
uploadFile
};
import {
formatBytes
} from "./chunk-PE6V3MVP.js";
import {
createStow
} from "./chunk-5LU25QZK.js";
import "./chunk-TOADDO2F.js";
// src/commands/whoami.ts
async function whoami() {
const stow = createStow();
const data = await stow.whoami();
console.log(`Account: ${data.user.email}`);
console.log("");
console.log(`Buckets: ${data.stats.bucketCount}`);
console.log(`Files: ${data.stats.totalFiles}`);
console.log(`Storage: ${formatBytes(data.stats.totalBytes)}`);
if (data.key) {
console.log("");
console.log(`API Key: ${data.key.name}`);
console.log(`Scope: ${data.key.scope}`);
const perms = Object.entries(data.key.permissions).filter(([, v]) => v).map(([k]) => k);
console.log(`Perms: ${perms.join(", ")}`);
}
}
export {
whoami
};
+181
-199

@@ -5,6 +5,6 @@ #!/usr/bin/env node

renderCommandHelp
} from "./chunk-XJDK2CBE.js";
} from "./chunk-MYFLRBWC.js";
import {
InputValidationError
} from "./chunk-PLZFHPLC.js";
} from "./chunk-NBHBVKP5.js";
import {

@@ -15,3 +15,3 @@ outputError,

setGlobalNdjson
} from "./chunk-5IX3ASXH.js";
} from "./chunk-KPIQZBTO.js";

@@ -36,6 +36,3 @@ // src/cli.ts

var program = new Command();
program.name("stow").description(CLI_DOCS.root.description).version(VERSION).option("--human", "Force human-readable output (default when TTY)").option(
"--fields <fields>",
"Comma-separated fields to include in output (e.g. key,similarity)"
).option("--ndjson", "Output as newline-delimited JSON (one object per line)").addHelpText("after", renderCommandHelp("root"));
program.name("stow").description(CLI_DOCS.root.description).version(VERSION).option("--human", "Force human-readable output (default when TTY)").option("--fields <fields>", "Comma-separated fields to include in output (e.g. key,similarity)").option("--ndjson", "Output as newline-delimited JSON (one object per line)").addHelpText("after", renderCommandHelp("root"));
program.hook("preAction", () => {

@@ -55,24 +52,22 @@ const opts2 = program.opts();

try {
const { uploadDrop } = await import("./upload-OS6Q6LW5.js");
const { uploadDrop } = await import("./upload-N7NAVN3Q.js");
await uploadDrop(file, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}
});
program.command("upload").description(CLI_DOCS.upload.description).argument("<file>", "File to upload").option("-b, --bucket <name>", "Bucket name or ID").option("-q, --quiet", "Only output the URL").option("--dry-run", "Preview without uploading").addHelpText("after", renderCommandHelp("upload")).action(
async (file, options) => {
try {
const { uploadFile } = await import("./upload-OS6Q6LW5.js");
await uploadFile(file, options);
} catch (err) {
handleError(err);
}
program.command("upload").description(CLI_DOCS.upload.description).argument("<file>", "File to upload").option("-b, --bucket <name>", "Bucket name or ID").option("-q, --quiet", "Only output the URL").option("--dry-run", "Preview without uploading").addHelpText("after", renderCommandHelp("upload")).action(async (file, options) => {
try {
const { uploadFile } = await import("./upload-N7NAVN3Q.js");
await uploadFile(file, options);
} catch (error) {
handleError(error);
}
);
});
var bucketsCmd = program.command("buckets").description(CLI_DOCS.buckets.description).addHelpText("after", renderCommandHelp("buckets")).action(async () => {
try {
const { listBuckets } = await import("./buckets-AFNX7FV3.js");
const { listBuckets } = await import("./buckets-FPMMPRR2.js");
await listBuckets();
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -83,6 +78,6 @@ });

try {
const { createBucket } = await import("./buckets-AFNX7FV3.js");
const { createBucket } = await import("./buckets-FPMMPRR2.js");
await createBucket(name, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -94,6 +89,6 @@ }

try {
const { renameBucket } = await import("./buckets-AFNX7FV3.js");
const { renameBucket } = await import("./buckets-FPMMPRR2.js");
await renameBucket(name, newName, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -104,6 +99,6 @@ }

try {
const { deleteBucket } = await import("./buckets-AFNX7FV3.js");
const { deleteBucket } = await import("./buckets-FPMMPRR2.js");
await deleteBucket(id, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -118,6 +113,6 @@ });

try {
const { listFiles } = await import("./files-XU6MDPP4.js");
const { listFiles } = await import("./files-SQURZ7VO.js");
await listFiles(bucket, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -128,6 +123,6 @@ }

try {
const { getFile } = await import("./files-XU6MDPP4.js");
const { getFile } = await import("./files-SQURZ7VO.js");
await getFile(bucket, key, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -138,6 +133,6 @@ });

try {
const { updateFile } = await import("./files-XU6MDPP4.js");
const { updateFile } = await import("./files-SQURZ7VO.js");
await updateFile(bucket, key, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -148,24 +143,22 @@ }

try {
const { enrichFile } = await import("./files-XU6MDPP4.js");
const { enrichFile } = await import("./files-SQURZ7VO.js");
await enrichFile(bucket, key);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}
});
filesCmd.command("missing").description(CLI_DOCS.filesMissing.description).argument("<bucket>", "Bucket name").argument("<type>", "dimensions | embeddings | colors").option("-l, --limit <count>", "Max files to return").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("filesMissing")).action(
async (bucket, type, options) => {
try {
const { listMissing } = await import("./files-XU6MDPP4.js");
await listMissing(bucket, type, options);
} catch (err) {
handleError(err);
}
filesCmd.command("missing").description(CLI_DOCS.filesMissing.description).argument("<bucket>", "Bucket name").argument("<type>", "dimensions | embeddings | colors").option("-l, --limit <count>", "Max files to return").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("filesMissing")).action(async (bucket, type, options) => {
try {
const { listMissing } = await import("./files-SQURZ7VO.js");
await listMissing(bucket, type, options);
} catch (error) {
handleError(error);
}
);
});
var dropsCmd = program.command("drops").description(CLI_DOCS.drops.description).addHelpText("after", renderCommandHelp("drops")).action(async () => {
try {
const { listDrops } = await import("./drops-5VIEW3XZ.js");
const { listDrops } = await import("./drops-XO4CZ4BH.js");
await listDrops();
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -175,55 +168,47 @@ });

try {
const { deleteDrop } = await import("./drops-5VIEW3XZ.js");
const { deleteDrop } = await import("./drops-XO4CZ4BH.js");
await deleteDrop(id);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}
});
var searchCmd = program.command("search").description(CLI_DOCS.search.description).addHelpText("after", renderCommandHelp("search"));
searchCmd.command("text").description(CLI_DOCS.searchText.description).argument("<query>", "Search query").option("-b, --bucket <name>", "Bucket name").option("-l, --limit <count>", "Max results").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("searchText")).action(
async (query, options) => {
try {
const { textSearch } = await import("./search-ETC2EXKM.js");
await textSearch(query, options);
} catch (err) {
handleError(err);
}
searchCmd.command("text").description(CLI_DOCS.searchText.description).argument("<query>", "Search query").option("-b, --bucket <name>", "Bucket name").option("-l, --limit <count>", "Max results").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("searchText")).action(async (query, options) => {
try {
const { textSearch } = await import("./search-UWLK4OL2.js");
await textSearch(query, options);
} catch (error) {
handleError(error);
}
);
searchCmd.command("similar").description(CLI_DOCS.searchSimilar.description).requiredOption("--file <key>", "File key to search from").option("-b, --bucket <name>", "Bucket name").option("-l, --limit <count>", "Max results").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("searchSimilar")).action(
async (options) => {
try {
const { similarSearch } = await import("./search-ETC2EXKM.js");
await similarSearch(options);
} catch (err) {
handleError(err);
}
});
searchCmd.command("similar").description(CLI_DOCS.searchSimilar.description).requiredOption("--file <key>", "File key to search from").option("-b, --bucket <name>", "Bucket name").option("-l, --limit <count>", "Max results").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("searchSimilar")).action(async (options) => {
try {
const { similarSearch } = await import("./search-UWLK4OL2.js");
await similarSearch(options);
} catch (error) {
handleError(error);
}
);
searchCmd.command("color").description(CLI_DOCS.searchColor.description).requiredOption("--hex <color>", "Hex color code").option("-b, --bucket <name>", "Bucket name").option("-l, --limit <count>", "Max results").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("searchColor")).action(
async (options) => {
try {
const { colorSearch } = await import("./search-ETC2EXKM.js");
await colorSearch(options);
} catch (err) {
handleError(err);
}
});
searchCmd.command("color").description(CLI_DOCS.searchColor.description).requiredOption("--hex <color>", "Hex color code").option("-b, --bucket <name>", "Bucket name").option("-l, --limit <count>", "Max results").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("searchColor")).action(async (options) => {
try {
const { colorSearch } = await import("./search-UWLK4OL2.js");
await colorSearch(options);
} catch (error) {
handleError(error);
}
);
searchCmd.command("diverse").description(CLI_DOCS.searchDiverse.description).option("-b, --bucket <name>", "Bucket name").option("-l, --limit <count>", "Max results").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("searchDiverse")).action(
async (options) => {
try {
const { diverseSearch } = await import("./search-ETC2EXKM.js");
await diverseSearch(options);
} catch (err) {
handleError(err);
}
});
searchCmd.command("diverse").description(CLI_DOCS.searchDiverse.description).option("-b, --bucket <name>", "Bucket name").option("-l, --limit <count>", "Max results").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("searchDiverse")).action(async (options) => {
try {
const { diverseSearch } = await import("./search-UWLK4OL2.js");
await diverseSearch(options);
} catch (error) {
handleError(error);
}
);
});
var tagsCmd = program.command("tags").description(CLI_DOCS.tags.description).option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("tags")).action(async (options) => {
try {
const { listTags } = await import("./tags-TBFPDHIQ.js");
const { listTags } = await import("./tags-V43DCLPQ.js");
await listTags(options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -234,6 +219,6 @@ });

try {
const { createTag } = await import("./tags-TBFPDHIQ.js");
const { createTag } = await import("./tags-V43DCLPQ.js");
await createTag(name, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -244,25 +229,23 @@ }

try {
const { deleteTag } = await import("./tags-TBFPDHIQ.js");
const { deleteTag } = await import("./tags-V43DCLPQ.js");
await deleteTag(id, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}
});
var profilesCmd = program.command("profiles").description(CLI_DOCS.profiles.description).addHelpText("after", renderCommandHelp("profiles"));
profilesCmd.command("create").description(CLI_DOCS.profilesCreate.description).requiredOption("--name <name>", "Profile name").option("-b, --bucket <id>", "Bucket ID").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("profilesCreate")).action(
async (options) => {
try {
const { createProfile } = await import("./profiles-MB3TZQE4.js");
await createProfile(options);
} catch (err) {
handleError(err);
}
profilesCmd.command("create").description(CLI_DOCS.profilesCreate.description).requiredOption("--name <name>", "Profile name").option("-b, --bucket <id>", "Bucket ID").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("profilesCreate")).action(async (options) => {
try {
const { createProfile } = await import("./profiles-XXVM3UKI.js");
await createProfile(options);
} catch (error) {
handleError(error);
}
);
});
profilesCmd.command("get").description(CLI_DOCS.profilesGet.description).argument("<id>", "Profile ID").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("profilesGet")).action(async (id, options) => {
try {
const { getProfile } = await import("./profiles-MB3TZQE4.js");
const { getProfile } = await import("./profiles-XXVM3UKI.js");
await getProfile(id, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -272,6 +255,6 @@ });

try {
const { deleteProfile } = await import("./profiles-MB3TZQE4.js");
const { deleteProfile } = await import("./profiles-XXVM3UKI.js");
await deleteProfile(id);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -282,6 +265,6 @@ });

try {
const { listJobs } = await import("./jobs-TND5AHCL.js");
const { listJobs } = await import("./jobs-KK5IZYO5.js");
await listJobs(options.bucket, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -292,6 +275,6 @@ }

try {
const { retryJob } = await import("./jobs-TND5AHCL.js");
const { retryJob } = await import("./jobs-KK5IZYO5.js");
await retryJob(id, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -301,6 +284,6 @@ });

try {
const { deleteJob } = await import("./jobs-TND5AHCL.js");
const { deleteJob } = await import("./jobs-KK5IZYO5.js");
await deleteJob(id, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -311,6 +294,6 @@ });

try {
const { health } = await import("./health-SH6T6DZS.js");
const { health } = await import("./health-3U3RHXFS.js");
await health(options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -322,6 +305,6 @@ });

try {
const { backfillDimensions } = await import("./backfill-VAORMLMY.js");
const { backfillDimensions } = await import("./backfill-BG65X4TP.js");
await backfillDimensions(options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -333,6 +316,6 @@ }

try {
const { backfillColors } = await import("./backfill-VAORMLMY.js");
const { backfillColors } = await import("./backfill-BG65X4TP.js");
await backfillColors(options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -344,43 +327,39 @@ }

try {
const { backfillEmbeddings } = await import("./backfill-VAORMLMY.js");
const { backfillEmbeddings } = await import("./backfill-BG65X4TP.js");
await backfillEmbeddings(options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}
}
);
adminCmd.command("cleanup-drops").description(CLI_DOCS.adminCleanupDrops.description).option("--max-age-hours <hours>", "Max age in hours").option("--dry-run", "Preview without deleting").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("adminCleanupDrops")).action(
async (options) => {
try {
const { cleanupDrops } = await import("./maintenance-V2TXPXQE.js");
await cleanupDrops(options);
} catch (err) {
handleError(err);
}
adminCmd.command("cleanup-drops").description(CLI_DOCS.adminCleanupDrops.description).option("--max-age-hours <hours>", "Max age in hours").option("--dry-run", "Preview without deleting").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("adminCleanupDrops")).action(async (options) => {
try {
const { cleanupDrops } = await import("./maintenance-7UBKZOR3.js");
await cleanupDrops(options);
} catch (error) {
handleError(error);
}
);
});
adminCmd.command("purge-events").description(CLI_DOCS.adminPurgeEvents.description).option("--dry-run", "Preview without deleting").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("adminPurgeEvents")).action(async (options) => {
try {
const { purgeEvents } = await import("./maintenance-V2TXPXQE.js");
const { purgeEvents } = await import("./maintenance-7UBKZOR3.js");
await purgeEvents(options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}
});
adminCmd.command("reconcile-files").description(CLI_DOCS.adminReconcileFiles.description).requiredOption("--bucket <id>", "Bucket ID").option("--dry-run", "Preview without reconciling").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("adminReconcileFiles")).action(
async (options) => {
try {
const { reconcileFiles } = await import("./maintenance-V2TXPXQE.js");
await reconcileFiles(options);
} catch (err) {
handleError(err);
}
adminCmd.command("reconcile-files").description(CLI_DOCS.adminReconcileFiles.description).requiredOption("--bucket <id>", "Bucket ID").option("--dry-run", "Preview without reconciling").option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("adminReconcileFiles")).action(async (options) => {
try {
const { reconcileFiles } = await import("./maintenance-7UBKZOR3.js");
await reconcileFiles(options);
} catch (error) {
handleError(error);
}
);
});
adminCmd.command("retry-sync-failures").description(CLI_DOCS.adminRetrySyncFailures.description).option("--json", "Output as JSON").addHelpText("after", renderCommandHelp("adminRetrySyncFailures")).action(async (options) => {
try {
const { retrySyncFailures } = await import("./maintenance-V2TXPXQE.js");
const { retrySyncFailures } = await import("./maintenance-7UBKZOR3.js");
await retrySyncFailures(options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -391,6 +370,6 @@ });

try {
const { listAdminJobs } = await import("./jobs-ROJFRPMR.js");
const { listAdminJobs } = await import("./jobs-HUW6Z6A7.js");
await listAdminJobs(options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -401,6 +380,6 @@ }

try {
const { retryAdminJob } = await import("./jobs-ROJFRPMR.js");
const { retryAdminJob } = await import("./jobs-HUW6Z6A7.js");
await retryAdminJob(id, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -410,6 +389,6 @@ });

try {
const { deleteAdminJob } = await import("./jobs-ROJFRPMR.js");
const { deleteAdminJob } = await import("./jobs-HUW6Z6A7.js");
await deleteAdminJob(id, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -419,6 +398,6 @@ });

try {
const { listQueues } = await import("./queues-NR25TGT7.js");
const { listQueues } = await import("./queues-MTA2RWUP.js");
await listQueues(options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -429,25 +408,23 @@ });

try {
const { cleanQueue } = await import("./queues-NR25TGT7.js");
const { cleanQueue } = await import("./queues-MTA2RWUP.js");
await cleanQueue(name, options);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}
}
);
program.command("delete").description(CLI_DOCS.delete.description).argument("<bucket>", "Bucket name").argument("<key>", "File key").option("--dry-run", "Preview without deleting").addHelpText("after", renderCommandHelp("delete")).action(
async (bucket, key, options) => {
try {
const { deleteFile } = await import("./delete-YEXSMG4I.js");
await deleteFile(bucket, key, options);
} catch (err) {
handleError(err);
}
program.command("delete").description(CLI_DOCS.delete.description).argument("<bucket>", "Bucket name").argument("<key>", "File key").option("--dry-run", "Preview without deleting").addHelpText("after", renderCommandHelp("delete")).action(async (bucket, key, options) => {
try {
const { deleteFile } = await import("./delete-CQJEGLP3.js");
await deleteFile(bucket, key, options);
} catch (error) {
handleError(error);
}
);
});
program.command("whoami").description(CLI_DOCS.whoami.description).addHelpText("after", renderCommandHelp("whoami")).action(async () => {
try {
const { whoami } = await import("./whoami-TVRKBM74.js");
const { whoami } = await import("./whoami-WUQDFC5P.js");
await whoami();
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -459,4 +436,4 @@ });

await openBucket(bucket);
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -466,6 +443,6 @@ });

try {
const { describeCommand } = await import("./describe-HSEHMJVD.js");
const { describeCommand } = await import("./describe-NH3K3LLW.js");
describeCommand(command ?? "");
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -475,6 +452,6 @@ });

try {
const { startMcpServer } = await import("./mcp-RZT4TJEX.js");
const { startMcpServer } = await import("./mcp-TUZZB2C7.js");
await startMcpServer();
} catch (err) {
handleError(err);
} catch (error) {
handleError(error);
}

@@ -487,3 +464,8 @@ });

if (args.length === 0 || args.length === 1 && opts.interactive) {
import("./app-Q6EW7VSM.js").then(({ startInteractive }) => startInteractive()).catch(handleError);
try {
const { startInteractive } = await import("./app-ZIHTOHXL.js");
await startInteractive();
} catch (error) {
handleError(error);
}
}
{
"name": "stow-cli",
"version": "2.2.1",
"type": "module",
"version": "2.2.3",
"description": "CLI for Stow file storage",
"keywords": [
"cli",
"file-storage",
"stow",
"upload"
],
"homepage": "https://stow.sh",
"license": "MIT",

@@ -12,9 +18,2 @@ "repository": {

},
"homepage": "https://stow.sh",
"keywords": [
"stow",
"file-storage",
"cli",
"upload"
],
"bin": {

@@ -26,7 +25,8 @@ "stow": "./dist/cli.js"

],
"type": "module",
"scripts": {
"build": "tsup src/cli.ts --format esm --shims",
"dev": "tsup src/cli.ts --format esm --shims --watch",
"test": "vitest run",
"test:watch": "vitest"
"test": "vp test run",
"test:watch": "vp test"
},

@@ -52,4 +52,4 @@ "dependencies": {

"typescript": "^5.9.3",
"vitest": "^4.1.0"
"vite-plus": "catalog:"
}
}
+10
-10

@@ -34,7 +34,7 @@ # stow-cli

| Variable | Required | Description |
|---|---|---|
| `STOW_API_KEY` | Yes | Your Stow API key (get one at `app.stow.sh/dashboard/api-keys`) |
| `STOW_API_URL` | No | Override the default API URL (`https://api.stow.sh`) |
| `STOW_ADMIN_SECRET` | No | Required for `admin` commands only |
| Variable | Required | Description |
| ------------------- | -------- | --------------------------------------------------------------- |
| `STOW_API_KEY` | Yes | Your Stow API key (get one at `app.stow.sh/dashboard/api-keys`) |
| `STOW_API_URL` | No | Override the default API URL (`https://api.stow.sh`) |
| `STOW_ADMIN_SECRET` | No | Required for `admin` commands only |

@@ -356,7 +356,7 @@ ## Commands

| Variable | Default | Description |
|---|---|---|
| `STOW_API_KEY` | -- | API key for authentication |
| `STOW_API_URL` | `https://api.stow.sh` | API base URL |
| `STOW_ADMIN_SECRET` | -- | Secret for admin commands |
| Variable | Default | Description |
| ------------------- | --------------------- | -------------------------- |
| `STOW_API_KEY` | -- | API key for authentication |
| `STOW_API_URL` | `https://api.stow.sh` | API base URL |
| `STOW_ADMIN_SECRET` | -- | Secret for admin commands |

@@ -363,0 +363,0 @@ ## License