diff --git a/src/commands/create.ts b/src/commands/create.ts index 25afcdf..8079385 100644 --- a/src/commands/create.ts +++ b/src/commands/create.ts @@ -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. */ @@ -51,6 +53,7 @@ export async function runCreate(options: CreateOptions): Promise { // 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'); diff --git a/src/lib/args.ts b/src/lib/args.ts index 7e21a47..7c381d9 100644 --- a/src/lib/args.ts +++ b/src/lib/args.ts @@ -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 = {}; @@ -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; @@ -106,6 +112,9 @@ Options: at creation) --goal what you are building — sent as attribution alongside the auto-detected agent framework and model + --model 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 @@ -113,4 +122,8 @@ Options: --api-host 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 `; diff --git a/test/args.test.mjs b/test/args.test.mjs index ce85dd4..77b7d7e 100644 --- a/test/args.test.mjs +++ b/test/args.test.mjs @@ -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'); @@ -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', }); diff --git a/test/create.test.mjs b/test/create.test.mjs index 0b6dbb3..df1f85f 100644 --- a/test/create.test.mjs +++ b/test/create.test.mjs @@ -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);