Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ one actions execute stripe <actionId> <connectionKey> \
| `--dry-run` | Show the request without executing it |
| `--mock` | Return example response without making an API call |
| `--skip-validation` | Skip input validation against the action schema |
| `--output <path>` | Save response to a file (for binary downloads) |

The CLI validates required parameters (path variables, query params, body fields) against the action schema before executing. Missing params return a clear error with the flag name and description. Pass `--skip-validation` to bypass.

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@withone/cli",
"version": "1.35.0",
"version": "1.36.0",
"description": "CLI for managing One",
"type": "module",
"files": [
Expand Down
1 change: 1 addition & 0 deletions skills/one/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ Options:
- `--dry-run` — Preview the request without executing
- `--mock` — Return example response without making an API call (useful for building UI)
- `--skip-validation` — Skip input validation against the action schema
- `--output <path>` — Save response to a file (for binary downloads like PDFs, images, documents)

The CLI validates required parameters before executing. Missing params return a structured error with the flag name, parameter name, and description. Pass `--skip-validation` to bypass.

Expand Down
2 changes: 2 additions & 0 deletions src/commands/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ export async function actionsExecuteCommand(
dryRun?: boolean;
mock?: boolean;
skipValidation?: boolean;
output?: string;
}
): Promise<void> {
output.intro(pc.bgCyan(pc.black(' One ')));
Expand Down Expand Up @@ -468,6 +469,7 @@ export async function actionsExecuteCommand(
isFormData: options.formData,
isFormUrlEncoded: options.formUrlEncoded,
dryRun: options.dryRun,
output: options.output,
},
actionDetails
);
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ actions
.option('--dry-run', 'Show request that would be sent without executing')
.option('--mock', 'Return example response without making an API call')
.option('--skip-validation', 'Skip input validation against the action schema')
.option('--output <path>', 'Save binary response to a file (for non-JSON responses like file downloads)')
.option('--parallel', 'Execute multiple actions concurrently (separate actions with --)')
.option('--max-concurrency <n>', 'Max concurrent actions when using --parallel (default: 5)', '5')
.action(async (platform: string | undefined, actionId: string | undefined, connectionKey: string | undefined, options: any) => {
Expand All @@ -470,6 +471,7 @@ actions
dryRun: options.dryRun,
mock: options.mock,
skipValidation: options.skipValidation,
output: options.output,
});
});

Expand Down
57 changes: 51 additions & 6 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { createWriteStream } from 'node:fs';
import { resolve } from 'node:path';
import { pipeline } from 'node:stream/promises';
import { Readable } from 'node:stream';
import type {
Connection,
ConnectionsResponse,
Expand Down Expand Up @@ -422,13 +426,54 @@ export class OneApi {
throw new ApiError(response.status, text || `HTTP ${response.status}`);
}

const responseText = await response.text();
const responseData = responseText ? JSON.parse(responseText) : {};
const responseContentType = response.headers.get('content-type') || '';
const isExplicitJson = responseContentType.includes('application/json') || responseContentType.includes('text/');

return {
requestConfig: sanitizedConfig,
responseData,
};
if (isExplicitJson || !responseContentType) {
// Explicit JSON/text content-type — fast path
const responseText = await response.text();
const responseData = responseText ? JSON.parse(responseText) : {};
return { requestConfig: sanitizedConfig, responseData };
}

// Ambiguous content-type (e.g. application/octet-stream from the API proxy).
// With --output: stream to disk (memory-safe for large binary files).
if (args.output) {
const outputPath = resolve(args.output);
const body = response.body;
if (!body) {
throw new ApiError(0, 'Response body is null — cannot save to file');
}
const nodeReadable = Readable.fromWeb(body as any);
await pipeline(nodeReadable, createWriteStream(outputPath));
const contentLength = response.headers.get('content-length');
const size = contentLength ? parseInt(contentLength, 10) : undefined;
return {
requestConfig: sanitizedConfig,
responseData: { saved: true, path: outputPath, size, contentType: responseContentType },
};
}

// Without --output: try JSON parse, fall back to binary metadata.
// The API proxy often returns application/octet-stream for JSON responses,
// so we must attempt parsing before assuming binary.
const responseText = await response.text();
try {
const responseData = responseText ? JSON.parse(responseText) : {};
return { requestConfig: sanitizedConfig, responseData };
} catch {
const contentLength = response.headers.get('content-length');
const size = contentLength ? parseInt(contentLength, 10) : responseText.length;
return {
requestConfig: sanitizedConfig,
responseData: {
binary: true,
size,
contentType: responseContentType,
message: 'Binary response received. Use --output <path> to save to a file.',
},
};
}
}

// Webhook Relay methods
Expand Down
2 changes: 2 additions & 0 deletions src/lib/guide-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ one --agent actions execute <platform> <actionId> <key> -d '{}' # Execute it
- \`--dry-run\` — Preview request without executing
- \`--mock\` — Return example response without making an API call (useful for building UI against a response shape)
- \`--skip-validation\` — Skip input validation against the action schema
- \`--output <path>\` — Save response to a file (for binary downloads like PDFs, images, documents)

The CLI validates required parameters against the action schema before executing. If you're missing a required path variable, query param, or body field, you'll get a clear error listing what's missing and which flag to use. Pass \`--skip-validation\` to bypass.

Expand Down Expand Up @@ -171,6 +172,7 @@ one --agent actions execute <platform> <actionId> <connectionKey> [options]
- \`--dry-run\` — Preview without executing
- \`--mock\` — Return example response without making an API call
- \`--skip-validation\` — Skip input validation against the action schema
- \`--output <path>\` — Save response to a file (for binary downloads like PDFs, images, documents)

**Do NOT** pass path or query parameters in \`-d\`. Use the correct flags.

Expand Down
1 change: 1 addition & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export interface ExecuteActionArgs {
isFormData?: boolean;
isFormUrlEncoded?: boolean;
dryRun?: boolean;
output?: string;
}

export interface SanitizedRequestConfig {
Expand Down