Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/commands/annotations/create.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from '@cliffy/command';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { createAuthenticatedMuxClient } from '@/lib/mux.ts';

Expand Down Expand Up @@ -37,7 +38,7 @@ export const createCommand = new Command()

const annotation = await mux.data.annotations.create(body as never);

if (options.json) {
if (wantsJson(options)) {
console.log(JSON.stringify(annotation, null, 2));
} else {
console.log(`Annotation ID: ${annotation.id}`);
Expand Down
5 changes: 3 additions & 2 deletions src/commands/annotations/delete.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from '@cliffy/command';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { createAuthenticatedMuxClient } from '@/lib/mux.ts';
import { confirmPrompt } from '@/lib/prompt.ts';
Expand All @@ -22,7 +23,7 @@ export const deleteCommand = new Command()
// Confirm deletion unless --force flag is provided
if (!options.force) {
// For JSON mode, require explicit --force flag for safety
if (options.json) {
if (wantsJson(options)) {
throw new Error(
'Deletion requires --force flag when using --json output',
);
Expand All @@ -41,7 +42,7 @@ export const deleteCommand = new Command()

await mux.data.annotations.delete(annotationId);

if (options.json) {
if (wantsJson(options)) {
console.log(JSON.stringify({ success: true, annotationId }, null, 2));
} else {
console.log(`Annotation ${annotationId} deleted successfully.`);
Expand Down
3 changes: 2 additions & 1 deletion src/commands/annotations/get.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from '@cliffy/command';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { createAuthenticatedMuxClient } from '@/lib/mux.ts';

Expand All @@ -16,7 +17,7 @@ export const getCommand = new Command()

const annotation = await mux.data.annotations.retrieve(annotationId);

if (options.json) {
if (wantsJson(options)) {
console.log(JSON.stringify(annotation, null, 2));
} else {
console.log(`Annotation ID: ${annotation.id}`);
Expand Down
3 changes: 2 additions & 1 deletion src/commands/annotations/list.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from '@cliffy/command';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { createAuthenticatedMuxClient } from '@/lib/mux.ts';

Expand Down Expand Up @@ -56,7 +57,7 @@ export const listCommand = new Command()

const response = await mux.data.annotations.list(params as never);

if (options.json) {
if (wantsJson(options)) {
console.log(JSON.stringify(response, null, 2));
return;
}
Expand Down
3 changes: 2 additions & 1 deletion src/commands/annotations/update.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from '@cliffy/command';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { createAuthenticatedMuxClient } from '@/lib/mux.ts';

Expand Down Expand Up @@ -41,7 +42,7 @@ export const updateCommand = new Command()
body as never,
);

if (options.json) {
if (wantsJson(options)) {
console.log(JSON.stringify(annotation, null, 2));
} else {
console.log('Annotation updated successfully.');
Expand Down
22 changes: 22 additions & 0 deletions src/commands/assets/create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { setAgentMode } from '@/lib/context.ts';
import { createCommand } from './create.ts';

// Note: These tests focus on CLI flag parsing and input validation
Expand All @@ -36,6 +37,7 @@ describe('mux assets create command', () => {
await rm(tempDir, { recursive: true, force: true });
exitSpy?.mockRestore();
consoleErrorSpy?.mockRestore();
setAgentMode(false);
});

describe('Flag combinations and validation', () => {
Expand Down Expand Up @@ -206,6 +208,26 @@ describe('mux assets create command', () => {
.find((opt) => opt.name === 'yes');
expect(yesOption).toBeDefined();
});

test('emits machine-readable JSON errors in agent mode without --json', async () => {
setAgentMode(true);

try {
await createCommand.parse([
'--url',
'https://example.com/video.mp4',
'--file',
join(tempDir, 'config.json'),
]);
} catch (_error) {
// Expected to throw via mocked process.exit
}

expect(exitSpy).toHaveBeenCalledWith(1);
const errorMessage = String(consoleErrorSpy.mock.calls[0][0]);
const parsed = JSON.parse(errorMessage);
expect(parsed.error).toMatch(/Cannot use multiple input methods/);
});
});

describe('Optional flags', () => {
Expand Down
26 changes: 14 additions & 12 deletions src/commands/assets/create.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Command } from '@cliffy/command';
import type Mux from '@mux/mux-node';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { expandGlobPattern, uploadFile } from '@/lib/file-upload.ts';
import { parseAssetConfig } from '@/lib/json-config.ts';
Expand Down Expand Up @@ -112,7 +113,7 @@ async function createFromUploads(

// Show files and confirm (unless -y flag)
if (!options.yes && files.length > 1) {
if (!options.json) {
if (!wantsJson(options)) {
Comment thread
cursor[bot] marked this conversation as resolved.
console.log(`Found ${files.length} files to upload:`);
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
for (const file of files) {
Expand All @@ -136,7 +137,7 @@ async function createFromUploads(

// Upload each file
for (const file of files) {
if (!options.json) {
if (!wantsJson(options)) {
console.log(`Uploading ${file.name}...`);
}

Expand Down Expand Up @@ -187,7 +188,7 @@ async function createFromUploads(

// Upload the file
await uploadFile(file.path, upload.url, upload.id, (percent) => {
if (!options.json && percent === 100) {
if (!wantsJson(options) && percent === 100) {
Comment thread
cursor[bot] marked this conversation as resolved.
console.log(`${file.name} uploaded`);
}
});
Expand Down Expand Up @@ -348,6 +349,7 @@ export const createCommand = new Command()
// biome-ignore lint: Cliffy infers upload as string but collect makes it string[]
.action(async (options: any, ...args: unknown[]) => {
const opts = options as CreateOptions;
const json = wantsJson(opts);
// Merge positional args (from shell glob expansion) into upload list
const extraFiles = args
.flat()
Expand Down Expand Up @@ -392,7 +394,7 @@ export const createCommand = new Command()
if (opts.url) {
result = await createFromUrl(mux, opts.url, opts);

if (opts.json) {
if (json) {
console.log(JSON.stringify(result, null, 2));
} else {
console.log(`Asset created: ${result.id}`);
Expand All @@ -406,7 +408,7 @@ export const createCommand = new Command()
} else if (opts.upload) {
result = await createFromUploads(mux, opts.upload, opts);

if (opts.json) {
if (json) {
console.log(JSON.stringify(result, null, 2));
} else {
console.log(`\n${result.length} file(s) uploaded successfully`);
Expand All @@ -417,7 +419,7 @@ export const createCommand = new Command()
} else if (opts.file) {
result = await createFromConfig(mux, opts.file, opts);

if (opts.json) {
if (json) {
console.log(JSON.stringify(result, null, 2));
} else {
console.log(`Asset created: ${result.id}`);
Expand All @@ -435,7 +437,7 @@ export const createCommand = new Command()

// Wait for asset processing if requested
if (opts.wait && !Array.isArray(result) && result.id) {
if (!opts.json) {
if (!json) {
console.log('\nWaiting for asset to be ready...');
}

Expand All @@ -448,32 +450,32 @@ export const createCommand = new Command()
asset = await mux.video.assets.retrieve(result.id);
attempts++;

if (!opts.json) {
if (!json) {
process.stdout.write('.');
}
}

if (!opts.json) {
if (!json) {
console.log();
}

if (asset.status === 'ready') {
if (!opts.json) {
if (!json) {
console.log('Asset is ready!');
}
} else if (asset.status === 'errored') {
throw new Error(
`Asset processing failed: ${asset.errors?.messages?.join(', ') || 'Unknown error'}`,
);
} else {
if (!opts.json) {
if (!json) {
console.log(
`WARNING: Asset is still processing. Run 'mux assets get ${asset.id}' to check status.`,
);
}
}

if (opts.json) {
if (json) {
console.log(JSON.stringify(asset, null, 2));
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/commands/assets/delete.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from '@cliffy/command';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { createAuthenticatedMuxClient } from '@/lib/mux.ts';
import { confirmPrompt } from '@/lib/prompt.ts';
Expand All @@ -21,7 +22,7 @@ export const deleteCommand = new Command()
// Confirm deletion unless --force flag is provided
if (!options.force) {
// For JSON mode, require explicit --force flag for safety
if (options.json) {
if (wantsJson(options)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agent errors still blame --json

Low Severity

Destructive command guards now key off wantsJson(), so --agent alone triggers the --force requirement, but the thrown message still says the failure is from --json output. Recovery text added for assets create and login correctly names agent mode; these delete/reset/cancel paths were left with the old wording, which misleads agent callers about why confirmation was skipped.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4981255. Configure here.

throw new Error(
'Deletion requires --force flag when using --json output',
);
Expand All @@ -41,7 +42,7 @@ export const deleteCommand = new Command()
// Delete the asset
await mux.video.assets.delete(assetId);

if (options.json) {
if (wantsJson(options)) {
console.log(JSON.stringify({ success: true, assetId }, null, 2));
} else {
console.log(`Asset ${assetId} deleted successfully`);
Expand Down
3 changes: 2 additions & 1 deletion src/commands/assets/get.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from '@cliffy/command';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { formatAsset } from '@/lib/formatters.ts';
import { createAuthenticatedMuxClient } from '@/lib/mux.ts';
Expand All @@ -19,7 +20,7 @@ export const getCommand = new Command()
// Fetch asset details
const asset = await mux.video.assets.retrieve(assetId);

if (options.json) {
if (wantsJson(options)) {
console.log(JSON.stringify(asset, null, 2));
} else {
formatAsset(asset);
Expand Down
3 changes: 2 additions & 1 deletion src/commands/assets/input-info.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from '@cliffy/command';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { createAuthenticatedMuxClient } from '@/lib/mux.ts';

Expand All @@ -16,7 +17,7 @@ export const inputInfoCommand = new Command()

const inputInfo = await mux.video.assets.retrieveInputInfo(assetId);

if (options.json) {
if (wantsJson(options)) {
console.log(JSON.stringify(inputInfo, null, 2));
} else {
if (inputInfo.length === 0) {
Expand Down
3 changes: 2 additions & 1 deletion src/commands/assets/list.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Command } from '@cliffy/command';
import type { Asset } from '@mux/mux-node/resources/video/assets';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import {
formatAssetStatus,
Expand Down Expand Up @@ -59,7 +60,7 @@ export const listCommand = new Command()
// Fetch assets
const response = await mux.video.assets.list(params);

if (options.json) {
if (wantsJson(options)) {
console.log(JSON.stringify(response, null, 2));
} else if (options.compact) {
// Compact output - one line per asset, grep-friendly
Expand Down
3 changes: 2 additions & 1 deletion src/commands/assets/playback-ids/create.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from '@cliffy/command';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { createAuthenticatedMuxClient } from '@/lib/mux.ts';
import { createPlaybackId, type PlaybackIdPolicy } from '@/lib/playback-ids.ts';
Expand Down Expand Up @@ -35,7 +36,7 @@ export const createCommand = new Command()

const playbackId = await createPlaybackId(mux, assetId, policy);

if (options.json) {
if (wantsJson(options)) {
console.log(
JSON.stringify(
{
Expand Down
5 changes: 3 additions & 2 deletions src/commands/assets/playback-ids/delete.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from '@cliffy/command';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { createAuthenticatedMuxClient } from '@/lib/mux.ts';
import { deletePlaybackId } from '@/lib/playback-ids.ts';
Expand All @@ -20,7 +21,7 @@ export const deleteCommand = new Command()
const mux = await createAuthenticatedMuxClient();

if (!options.force) {
if (options.json) {
if (wantsJson(options)) {
throw new Error(
'Deletion requires --force flag when using --json output',
);
Expand All @@ -39,7 +40,7 @@ export const deleteCommand = new Command()

await deletePlaybackId(mux, assetId, playbackId);

if (options.json) {
if (wantsJson(options)) {
console.log(
JSON.stringify({ success: true, assetId, playbackId }, null, 2),
);
Expand Down
3 changes: 2 additions & 1 deletion src/commands/assets/playback-ids/list.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from '@cliffy/command';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { createAuthenticatedMuxClient } from '@/lib/mux.ts';
import { getPlayerUrl, getStreamUrl } from '@/lib/urls.ts';
Expand All @@ -18,7 +19,7 @@ export const listCommand = new Command()
const asset = await mux.video.assets.retrieve(assetId);
const playbackIds = asset.playback_ids ?? [];

if (options.json) {
if (wantsJson(options)) {
const output = playbackIds.map((p) => ({
id: p.id,
policy: p.policy,
Expand Down
7 changes: 4 additions & 3 deletions src/commands/assets/static-renditions/create.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Command } from '@cliffy/command';
import type Mux from '@mux/mux-node';
import { wantsJson } from '@/lib/context.ts';
import { handleCommandError } from '@/lib/errors.ts';
import { createAuthenticatedMuxClient } from '@/lib/mux.ts';

Expand Down Expand Up @@ -79,11 +80,11 @@ export const createCommand = new Command()
mux,
assetId,
rendition.id as string,
options.json,
wantsJson(options),
);
outputRendition(finalRendition, options.json, false);
outputRendition(finalRendition, wantsJson(options), false);
} else {
outputRendition(rendition, options.json, !options.wait);
outputRendition(rendition, wantsJson(options), !options.wait);
}
} catch (error) {
await handleCommandError(error, 'assets', 'create', options);
Expand Down
Loading
Loading