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
3 changes: 3 additions & 0 deletions src/commands/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface CreateOptions {
env?: boolean;
/** What the agent is building; sent as agent_goal attribution when provided. */
goal?: string;
/** The LLM model driving the session; overrides env detection (which usually finds nothing). */
model?: string;
/** Set false (--no-agent-metadata) to omit attribution fields entirely. */
agentMetadata?: boolean;
/** Test seam for the public-IP echo lookup. */
Expand Down Expand Up @@ -51,6 +53,7 @@ export async function runCreate(options: CreateOptions): Promise<CreateResult> {
// Experimental agent attribution — remove with lib/agent-metadata.ts.
if (options.agentMetadata !== false) {
request.agentMetadata = detectAgentMetadata(process.env, options.goal);
if (options.model) request.agentMetadata.agent_llm_model = options.model;
}

const envPath = join(process.cwd(), '.env');
Expand Down
19 changes: 16 additions & 3 deletions src/lib/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ export type ParsedArgs =
| { kind: 'error'; message: string };

/**
* Hand-rolled argv parser — the CLI has one command and seven flags, which is
* not worth a runtime dependency. Supports `--flag value` and `--flag=value`,
* and `--ip` may repeat.
* Hand-rolled argv parser — the CLI has one command and a handful of flags,
* which is not worth a runtime dependency. Supports `--flag value` and
* `--flag=value`, and `--ip` may repeat.
*/
export function parseArgs(argv: string[]): ParsedArgs {
const options: CreateOptions = {};
Expand Down Expand Up @@ -61,6 +61,12 @@ export function parseArgs(argv: string[]): ParsedArgs {
options.goal = v;
break;
}
case '--model': {
const v = takeValue();
if (typeof v !== 'string') return { kind: 'error', message: v.error };
options.model = v;
break;
}
case '--force':
options.force = true;
break;
Expand Down Expand Up @@ -106,11 +112,18 @@ Options:
at creation)
--goal <text> what you are building — sent as attribution alongside the
auto-detected agent framework and model
--model <name> the LLM model driving this session — sent as attribution.
Only needed when it cannot be auto-detected from the
environment, which is common
--force replace an existing CLOUDINARY_URL in ./.env
--no-env do not write ./.env — credentials are only printed
--no-agent-metadata do not send agent attribution fields
--json print the raw provisioning response as JSON
--api-host <url> override the Cloudinary API host (or set CLOUDINARY_API_HOST)
-V, --version print the version
-h, --help show this help

If you are an AI agent, pass --goal and --model so the cloud can be attributed
to what you are building. For example:
npx @cloudinary/cloud --goal "auto-crop and deliver product images" --model <your-model-id>
`;
3 changes: 2 additions & 1 deletion test/args.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ test('parses all flags, space and equals forms', () => {
const parsed = parseArgs([
'create', '--ip', '203.0.113.7', '--ip=requester_ip',
'--email=dev@example.com', '--force', '--no-env', '--json',
'--goal', 'add image optimization', '--no-agent-metadata',
'--goal', 'add image optimization', '--model=claude-fable-5', '--no-agent-metadata',
'--api-host', 'http://localhost:9999',
]);
assert.equal(parsed.kind, 'create');
Expand All @@ -22,6 +22,7 @@ test('parses all flags, space and equals forms', () => {
env: false,
json: true,
goal: 'add image optimization',
model: 'claude-fable-5',
agentMetadata: false,
apiHost: 'http://localhost:9999',
});
Expand Down
13 changes: 12 additions & 1 deletion test/create.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -309,11 +309,22 @@ test('sends detected agent attribution and --goal by default', () =>
),
));

test('--model overrides env detection for agent_llm_model', () =>
withAgentEnv(() =>
inTempCwd(() =>
withBodyCapture(async (host, bodies) => {
await runCreate({ apiHost: host, ipEchoFetch: noEcho, model: 'gpt-6-codex' });
assert.equal(bodies[0].agent_llm_model, 'gpt-6-codex');
assert.equal(bodies[0].agent_framework, 'claude-code');
}),
),
));

test('--no-agent-metadata omits attribution fields entirely', () =>
withAgentEnv(() =>
inTempCwd(() =>
withBodyCapture(async (host, bodies) => {
await runCreate({ apiHost: host, ipEchoFetch: noEcho, agentMetadata: false, goal: 'ignored' });
await runCreate({ apiHost: host, ipEchoFetch: noEcho, agentMetadata: false, goal: 'ignored', model: 'ignored' });
assert.equal('agent_framework' in bodies[0], false);
assert.equal('agent_llm_model' in bodies[0], false);
assert.equal('agent_goal' in bodies[0], false);
Expand Down
Loading