@meterapp/car-image-sdk 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Shared MCP tool definitions for the Car Image API.
3
+ *
4
+ * Both MCP servers import from here so agents see identical tools:
5
+ * - the stdio server in `@meterapp/car-image` (`car-image mcp`), which calls the REST API, and
6
+ * - the remote Streamable HTTP server at https://car-imgs.vercel.app/api/mcp, which executes
7
+ * against the server libraries directly.
8
+ *
9
+ * This module needs `zod` (an optional peer dependency of the SDK). The main
10
+ * entry point of the SDK has no runtime dependencies.
11
+ */
12
+ import * as z from "zod";
13
+ export declare const MCP_SERVER_INFO: {
14
+ readonly name: "car-image-api";
15
+ readonly title: "Car Image API";
16
+ readonly version: "1.0.0";
17
+ readonly websiteUrl: "https://car-imgs.vercel.app";
18
+ };
19
+ export declare const MCP_INSTRUCTIONS: string;
20
+ export declare const imageInputSchema: z.ZodObject<{
21
+ make: z.ZodString;
22
+ model: z.ZodString;
23
+ year: z.ZodCoercedNumber<unknown>;
24
+ view: z.ZodDefault<z.ZodEnum<{
25
+ front: "front";
26
+ "front-3-4": "front-3-4";
27
+ side: "side";
28
+ "side-right": "side-right";
29
+ rear: "rear";
30
+ "rear-3-4": "rear-3-4";
31
+ }>>;
32
+ color: z.ZodDefault<z.ZodEnum<{
33
+ white: "white";
34
+ black: "black";
35
+ gray: "gray";
36
+ silver: "silver";
37
+ blue: "blue";
38
+ red: "red";
39
+ green: "green";
40
+ brown: "brown";
41
+ beige: "beige";
42
+ tan: "tan";
43
+ orange: "orange";
44
+ yellow: "yellow";
45
+ gold: "gold";
46
+ burgundy: "burgundy";
47
+ purple: "purple";
48
+ }>>;
49
+ size: z.ZodOptional<z.ZodEnum<{
50
+ thumb: "thumb";
51
+ small: "small";
52
+ medium: "medium";
53
+ large: "large";
54
+ }>>;
55
+ width: z.ZodOptional<z.ZodNumber>;
56
+ height: z.ZodOptional<z.ZodNumber>;
57
+ format: z.ZodDefault<z.ZodEnum<{
58
+ png: "png";
59
+ webp: "webp";
60
+ jpg: "jpg";
61
+ }>>;
62
+ }, z.core.$strip>;
63
+ export type ImageInput = z.infer<typeof imageInputSchema>;
64
+ export declare const MCP_TOOL_SCHEMAS: {
65
+ readonly get_car_image: z.ZodObject<{
66
+ make: z.ZodString;
67
+ model: z.ZodString;
68
+ year: z.ZodCoercedNumber<unknown>;
69
+ view: z.ZodDefault<z.ZodEnum<{
70
+ front: "front";
71
+ "front-3-4": "front-3-4";
72
+ side: "side";
73
+ "side-right": "side-right";
74
+ rear: "rear";
75
+ "rear-3-4": "rear-3-4";
76
+ }>>;
77
+ color: z.ZodDefault<z.ZodEnum<{
78
+ white: "white";
79
+ black: "black";
80
+ gray: "gray";
81
+ silver: "silver";
82
+ blue: "blue";
83
+ red: "red";
84
+ green: "green";
85
+ brown: "brown";
86
+ beige: "beige";
87
+ tan: "tan";
88
+ orange: "orange";
89
+ yellow: "yellow";
90
+ gold: "gold";
91
+ burgundy: "burgundy";
92
+ purple: "purple";
93
+ }>>;
94
+ size: z.ZodOptional<z.ZodEnum<{
95
+ thumb: "thumb";
96
+ small: "small";
97
+ medium: "medium";
98
+ large: "large";
99
+ }>>;
100
+ width: z.ZodOptional<z.ZodNumber>;
101
+ height: z.ZodOptional<z.ZodNumber>;
102
+ format: z.ZodDefault<z.ZodEnum<{
103
+ png: "png";
104
+ webp: "webp";
105
+ jpg: "jpg";
106
+ }>>;
107
+ }, z.core.$strip>;
108
+ readonly create_car_image_urls: z.ZodObject<{
109
+ images: z.ZodArray<z.ZodObject<{
110
+ make: z.ZodString;
111
+ model: z.ZodString;
112
+ year: z.ZodCoercedNumber<unknown>;
113
+ view: z.ZodDefault<z.ZodEnum<{
114
+ front: "front";
115
+ "front-3-4": "front-3-4";
116
+ side: "side";
117
+ "side-right": "side-right";
118
+ rear: "rear";
119
+ "rear-3-4": "rear-3-4";
120
+ }>>;
121
+ color: z.ZodDefault<z.ZodEnum<{
122
+ white: "white";
123
+ black: "black";
124
+ gray: "gray";
125
+ silver: "silver";
126
+ blue: "blue";
127
+ red: "red";
128
+ green: "green";
129
+ brown: "brown";
130
+ beige: "beige";
131
+ tan: "tan";
132
+ orange: "orange";
133
+ yellow: "yellow";
134
+ gold: "gold";
135
+ burgundy: "burgundy";
136
+ purple: "purple";
137
+ }>>;
138
+ size: z.ZodOptional<z.ZodEnum<{
139
+ thumb: "thumb";
140
+ small: "small";
141
+ medium: "medium";
142
+ large: "large";
143
+ }>>;
144
+ width: z.ZodOptional<z.ZodNumber>;
145
+ height: z.ZodOptional<z.ZodNumber>;
146
+ format: z.ZodDefault<z.ZodEnum<{
147
+ png: "png";
148
+ webp: "webp";
149
+ jpg: "jpg";
150
+ }>>;
151
+ }, z.core.$strip>>;
152
+ ttl_seconds: z.ZodOptional<z.ZodNumber>;
153
+ max_uses: z.ZodOptional<z.ZodNumber>;
154
+ }, z.core.$strip>;
155
+ readonly search_vehicles: z.ZodObject<{
156
+ query: z.ZodString;
157
+ year: z.ZodOptional<z.ZodNumber>;
158
+ limit: z.ZodDefault<z.ZodNumber>;
159
+ }, z.core.$strip>;
160
+ readonly resolve_vehicle: z.ZodObject<{
161
+ query: z.ZodString;
162
+ }, z.core.$strip>;
163
+ readonly list_image_options: z.ZodObject<{}, z.core.$strip>;
164
+ readonly get_account: z.ZodObject<{}, z.core.$strip>;
165
+ readonly rate_image: z.ZodObject<{
166
+ request_id: z.ZodOptional<z.ZodString>;
167
+ make: z.ZodOptional<z.ZodString>;
168
+ model: z.ZodOptional<z.ZodString>;
169
+ year: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
170
+ view: z.ZodOptional<z.ZodEnum<{
171
+ front: "front";
172
+ "front-3-4": "front-3-4";
173
+ side: "side";
174
+ "side-right": "side-right";
175
+ rear: "rear";
176
+ "rear-3-4": "rear-3-4";
177
+ }>>;
178
+ color: z.ZodOptional<z.ZodEnum<{
179
+ white: "white";
180
+ black: "black";
181
+ gray: "gray";
182
+ silver: "silver";
183
+ blue: "blue";
184
+ red: "red";
185
+ green: "green";
186
+ brown: "brown";
187
+ beige: "beige";
188
+ tan: "tan";
189
+ orange: "orange";
190
+ yellow: "yellow";
191
+ gold: "gold";
192
+ burgundy: "burgundy";
193
+ purple: "purple";
194
+ }>>;
195
+ rating: z.ZodOptional<z.ZodNumber>;
196
+ verdict: z.ZodOptional<z.ZodEnum<{
197
+ good: "good";
198
+ bad: "bad";
199
+ }>>;
200
+ reason: z.ZodOptional<z.ZodString>;
201
+ }, z.core.$strip>;
202
+ readonly describe_api: z.ZodObject<{
203
+ endpoint: z.ZodOptional<z.ZodString>;
204
+ }, z.core.$strip>;
205
+ };
206
+ export type McpToolName = keyof typeof MCP_TOOL_SCHEMAS;
207
+ export type McpToolInput<N extends McpToolName> = z.infer<(typeof MCP_TOOL_SCHEMAS)[N]>;
208
+ export interface McpToolAnnotations {
209
+ title?: string;
210
+ readOnlyHint?: boolean;
211
+ destructiveHint?: boolean;
212
+ idempotentHint?: boolean;
213
+ openWorldHint?: boolean;
214
+ }
215
+ export interface McpToolDefinition<N extends McpToolName = McpToolName> {
216
+ name: N;
217
+ title: string;
218
+ description: string;
219
+ inputSchema: (typeof MCP_TOOL_SCHEMAS)[N];
220
+ annotations: McpToolAnnotations;
221
+ }
222
+ export declare const MCP_TOOLS: {
223
+ [N in McpToolName]: McpToolDefinition<N>;
224
+ };
225
+ export declare const MCP_TOOL_LIST: McpToolDefinition[];
226
+ export interface ApiEndpointReference {
227
+ method: "GET" | "POST";
228
+ path: string;
229
+ summary: string;
230
+ auth: "bearer" | "public";
231
+ credits: string;
232
+ params?: string[];
233
+ returns?: string;
234
+ errors?: string[];
235
+ notes?: string;
236
+ sdk?: string;
237
+ cli?: string;
238
+ }
239
+ /** Compact, hand-maintained mirror of /openapi.json for agents that cannot fetch it. */
240
+ export declare const API_REFERENCE: ApiEndpointReference[];
241
+ /** Human/agent-readable rendering of {@link API_REFERENCE}, optionally filtered. */
242
+ export declare function formatApiReference(filter?: string): string;
243
+ /** Text content block as used in MCP tool results. */
244
+ export type McpTextContent = {
245
+ type: "text";
246
+ text: string;
247
+ };
248
+ /** Image content block as used in MCP tool results. */
249
+ export type McpImageContent = {
250
+ type: "image";
251
+ data: string;
252
+ mimeType: string;
253
+ };
254
+ export type McpToolResult = {
255
+ content: Array<McpTextContent | McpImageContent>;
256
+ structuredContent?: Record<string, unknown>;
257
+ isError?: boolean;
258
+ };
259
+ /** A JSON payload as both readable text and structured content. */
260
+ export declare function jsonToolResult(value: Record<string, unknown>, summary?: string): McpToolResult;
261
+ /** An `isError` result whose text tells the agent what to do next. */
262
+ export declare function errorToolResult(message: string, extra?: Record<string, unknown>): McpToolResult;
263
+ /** Standard guidance appended to error results, keyed by HTTP-ish status. */
264
+ export declare function nextStepFor(status: number | null | undefined): string;
265
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Shared MCP tool definitions for the Car Image API.
3
+ *
4
+ * Both MCP servers import from here so agents see identical tools:
5
+ * - the stdio server in `@meterapp/car-image` (`car-image mcp`), which calls the REST API, and
6
+ * - the remote Streamable HTTP server at https://car-imgs.vercel.app/api/mcp, which executes
7
+ * against the server libraries directly.
8
+ *
9
+ * This module needs `zod` (an optional peer dependency of the SDK). The main
10
+ * entry point of the SDK has no runtime dependencies.
11
+ */
12
+ import * as z from "zod";
13
+ import { COLORS, CREDITS_PER_DOLLAR, CREDITS_PER_IMAGE, DEFAULT_BASE_URL, FORMATS, FREE_CREDITS, MAX_DIMENSION, MIN_YEAR, SIZES, VIEWS, } from "../types.js";
14
+ import { SDK_VERSION } from "../version.js";
15
+ export const MCP_SERVER_INFO = {
16
+ name: "car-image-api",
17
+ title: "Car Image API",
18
+ version: SDK_VERSION,
19
+ websiteUrl: DEFAULT_BASE_URL,
20
+ };
21
+ export const MCP_INSTRUCTIONS = `Car Image API by Meter: studio-quality, transparent-background renders of any vehicle in the open @meterapp/vehicle-db catalog (1,145 makes, 14,841 models, model years 1990-2026). Six camera views, fifteen preset colors, PNG/WebP/JPG up to 1024 px.
22
+
23
+ Costs: every delivered image costs exactly ${CREDITS_PER_IMAGE} credit ($1 = ${CREDITS_PER_DOLLAR.toLocaleString("en-US")} credits; ${FREE_CREDITS} free credits per account), whether it is served from cache or generated on demand. create_car_image_urls charges 1 credit per URL when the URL is created; redemptions are free. search_vehicles, resolve_vehicle, list_image_options, get_account, rate_image and describe_api are free.
24
+
25
+ Workflow: (1) resolve_vehicle turns free text such as "red 2024 porsche 911 side view" into exact parameters; search_vehicles browses the catalog. (2) Call get_car_image when the image itself is needed in the conversation. (3) For web pages, emails, documents or anything a browser will load, call create_car_image_urls and embed the signed URLs; they need no key.
26
+
27
+ Security: never place the API key in URLs, HTML, logs, screenshots or client-side code. Signed delivery URLs are the only thing that should ever reach a browser.
28
+
29
+ Money: on an insufficient-credits error (HTTP 402) stop and ask the human to buy credits at ${DEFAULT_BASE_URL}/billing or with \`car-image billing\`; never purchase credits autonomously. On a rate limit (HTTP 429) wait the number of seconds in retry_after before trying again.
30
+
31
+ Quality: after a human judges a render, call rate_image (good/bad or 1-5) so future renders improve.`;
32
+ const yearSchema = z.coerce
33
+ .number()
34
+ .int()
35
+ .min(MIN_YEAR)
36
+ .max(2100)
37
+ .describe(`Model year, e.g. 2024. The catalog covers ${MIN_YEAR} through next year.`);
38
+ export const imageInputSchema = z.object({
39
+ make: z.string().min(1).max(80).describe('Manufacturer, e.g. "Porsche", "BMW", "Toyota". Case and punctuation do not matter.'),
40
+ model: z.string().min(1).max(80).describe('Model as listed in the catalog, e.g. "911", "M3", "Corolla".'),
41
+ year: yearSchema,
42
+ view: z
43
+ .enum(VIEWS)
44
+ .default("front-3-4")
45
+ .describe("Camera angle: front (0°), front-3-4 (35°, the classic hero angle), side (profile, nose left), side-right (profile, nose right), rear (180°), rear-3-4 (145°)."),
46
+ color: z.enum(COLORS).default("silver").describe("Paint color; one of the fifteen presets."),
47
+ size: z
48
+ .enum(["thumb", "small", "medium", "large"])
49
+ .optional()
50
+ .describe(`Square size preset: thumb=${SIZES.thumb}, small=${SIZES.small}, medium=${SIZES.medium}, large=${SIZES.large} px. Omit for ${MAX_DIMENSION} px.`),
51
+ width: z.number().int().min(1).max(MAX_DIMENSION).optional().describe(`Explicit width in px (1-${MAX_DIMENSION}); overrides size.`),
52
+ height: z.number().int().min(1).max(MAX_DIMENSION).optional().describe(`Explicit height in px (1-${MAX_DIMENSION}).`),
53
+ format: z
54
+ .enum(FORMATS)
55
+ .default("png")
56
+ .describe("png keeps the transparent background (default); webp is smallest; jpg is flattened onto white."),
57
+ });
58
+ export const MCP_TOOL_SCHEMAS = {
59
+ get_car_image: imageInputSchema,
60
+ create_car_image_urls: z.object({
61
+ images: z
62
+ .array(imageInputSchema)
63
+ .min(1)
64
+ .max(50)
65
+ .describe("One to fifty image requests. Each URL costs 1 credit when created."),
66
+ ttl_seconds: z
67
+ .number()
68
+ .int()
69
+ .min(60)
70
+ .max(7 * 24 * 60 * 60)
71
+ .optional()
72
+ .describe("How long the URLs stay valid, 60 to 604800 seconds (7 days). Default 3600."),
73
+ max_uses: z
74
+ .number()
75
+ .int()
76
+ .min(0)
77
+ .max(1_000_000)
78
+ .optional()
79
+ .describe("Maximum redemptions per URL; 0 (default) means unlimited until expiry."),
80
+ }),
81
+ search_vehicles: z.object({
82
+ query: z
83
+ .string()
84
+ .min(1)
85
+ .max(120)
86
+ .describe('Free text: make and/or model, optional year, typos tolerated, e.g. "porshe 911" or "2019 f150".'),
87
+ year: z.number().int().min(MIN_YEAR).max(2100).optional().describe("Restrict results to one model year."),
88
+ limit: z.number().int().min(1).max(50).default(10).describe("Maximum number of results (1-50)."),
89
+ }),
90
+ resolve_vehicle: z.object({
91
+ query: z
92
+ .string()
93
+ .min(1)
94
+ .max(200)
95
+ .describe('Natural-language description of the wanted image, e.g. "red 2024 porsche 911 side view".'),
96
+ }),
97
+ list_image_options: z.object({}),
98
+ get_account: z.object({}),
99
+ rate_image: z.object({
100
+ request_id: z
101
+ .string()
102
+ .min(1)
103
+ .max(128)
104
+ .optional()
105
+ .describe("The request_id returned by get_car_image or create_car_image_urls (preferred way to identify the image)."),
106
+ make: z.string().min(1).max(80).optional().describe("Alternative to request_id: identify the image by vehicle."),
107
+ model: z.string().min(1).max(80).optional(),
108
+ year: z.coerce.number().int().min(MIN_YEAR).max(2100).optional(),
109
+ view: z.enum(VIEWS).optional(),
110
+ color: z.enum(COLORS).optional(),
111
+ rating: z.number().int().min(1).max(5).optional().describe("1 (unusable) to 5 (perfect)."),
112
+ verdict: z.enum(["good", "bad"]).optional().describe("Quick thumbs up/down; provide this or rating."),
113
+ reason: z.string().max(500).optional().describe("What was wrong or right, e.g. 'wrong body style', 'perfect angle'."),
114
+ }),
115
+ describe_api: z.object({
116
+ endpoint: z
117
+ .string()
118
+ .max(120)
119
+ .optional()
120
+ .describe('Filter by path fragment or keyword, e.g. "images/car", "image-urls", "feedback". Omit for the full compact reference.'),
121
+ }),
122
+ };
123
+ export const MCP_TOOLS = {
124
+ get_car_image: {
125
+ name: "get_car_image",
126
+ title: "Get car image",
127
+ description: `Render or fetch a studio-quality, transparent-background image of a specific vehicle (make, model, year) from one of six camera angles in one of fifteen preset colors. Returns the image inline (PNG/WebP/JPG, up to ${MAX_DIMENSION} px) plus usage metadata (credits_charged, credits_remaining, source cache|generated, request_id). COSTS ${CREDITS_PER_IMAGE} CREDIT per call, cached or generated. Use resolve_vehicle first when the request is free text; use create_car_image_urls instead when the image must be embedded in a web page or document.`,
128
+ inputSchema: MCP_TOOL_SCHEMAS.get_car_image,
129
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
130
+ },
131
+ create_car_image_urls: {
132
+ name: "create_car_image_urls",
133
+ title: "Create signed car image URLs",
134
+ description: "Create 1-50 signed, key-free delivery URLs that browsers, emails or documents can load directly (<img src>). Each URL costs 1 credit at creation; redemptions within the TTL are free and publicly cacheable. Returns id, url, expires_at, max_uses and the normalized vehicle for each image. Prefer this over get_car_image whenever the output is HTML, Markdown, a document or a website.",
135
+ inputSchema: MCP_TOOL_SCHEMAS.create_car_image_urls,
136
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
137
+ },
138
+ search_vehicles: {
139
+ name: "search_vehicles",
140
+ title: "Search the vehicle catalog",
141
+ description: "Free. Fuzzy-search the open vehicle catalog (1,145 makes, 14,841 models, 1990-2026) by make and/or model with typo tolerance. Returns canonical make/model names, available years and match kind, ready to pass to get_car_image. Use it to confirm a vehicle exists or to list a make's models before rendering.",
142
+ inputSchema: MCP_TOOL_SCHEMAS.search_vehicles,
143
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
144
+ },
145
+ resolve_vehicle: {
146
+ name: "resolve_vehicle",
147
+ title: "Resolve free text to image parameters",
148
+ description: 'Free. Turn a natural-language request such as "red 2024 porsche 911 side view" into exact get_car_image parameters (make, model, year, view, color) plus alternative candidates and a 0-1 confidence. Call this before rendering when the user did not spell out the parameters; ask the user to choose when confidence is low or several candidates fit.',
149
+ inputSchema: MCP_TOOL_SCHEMAS.resolve_vehicle,
150
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
151
+ },
152
+ list_image_options: {
153
+ name: "list_image_options",
154
+ title: "List image options",
155
+ description: "Free. List the supported camera views (with yaw angles and aliases), the fifteen preset colors with hex swatches, size presets, output formats, current pricing and catalog coverage. Use it when you need to validate or explain what can be requested.",
156
+ inputSchema: MCP_TOOL_SCHEMAS.list_image_options,
157
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
158
+ },
159
+ get_account: {
160
+ name: "get_account",
161
+ title: "Get account and credits",
162
+ description: "Free. Show the caller's remaining credits (and the dollar equivalent), auto-reload status, whether a payment method is on file, recent usage and the key's scopes. Check this before large batches; if credits are insufficient, ask a human to top up rather than buying credits yourself.",
163
+ inputSchema: MCP_TOOL_SCHEMAS.get_account,
164
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
165
+ },
166
+ rate_image: {
167
+ name: "rate_image",
168
+ title: "Rate a delivered image",
169
+ description: "Free. Record quality feedback on a delivered image, either by the request_id returned with it or by vehicle (make, model, year, view, color). Provide verdict good|bad or rating 1-5 and an optional reason. Feedback drives re-renders and prompt improvements.",
170
+ inputSchema: MCP_TOOL_SCHEMAS.rate_image,
171
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
172
+ },
173
+ describe_api: {
174
+ name: "describe_api",
175
+ title: "Describe the REST API",
176
+ description: "Free. Return a compact reference of the Car Image REST API (methods, paths, parameters, credit costs, response headers, error codes, agent notes) for writing code that calls the API directly, with SDK (`@meterapp/car-image-sdk`) and CLI (`npx @meterapp/car-image`) equivalents. Optional endpoint filter.",
177
+ inputSchema: MCP_TOOL_SCHEMAS.describe_api,
178
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
179
+ },
180
+ };
181
+ export const MCP_TOOL_LIST = Object.values(MCP_TOOLS);
182
+ /** Compact, hand-maintained mirror of /openapi.json for agents that cannot fetch it. */
183
+ export const API_REFERENCE = [
184
+ {
185
+ method: "GET",
186
+ path: "/api/v1/images/car",
187
+ summary: "Get a billed vehicle image (bytes, or a signed URL with Accept: application/json).",
188
+ auth: "bearer",
189
+ credits: "1 per call (cached or generated)",
190
+ params: [
191
+ "make (required)",
192
+ "model (required)",
193
+ "year (required, 1990-2026)",
194
+ `view (${VIEWS.join("|")}; default front-3-4)`,
195
+ `color (${COLORS.join("|")}; default silver)`,
196
+ "size (thumb=256|small=512|medium=768|large=1024)",
197
+ `w, h (1-${MAX_DIMENSION} px)`,
198
+ `format (${FORMATS.join("|")}; default png)`,
199
+ ],
200
+ returns: "image bytes; headers X-Credits-Charged, X-Credits-Remaining, X-Image-Source (cache|generated), X-Image-Width, X-Image-Height, ETag, X-Request-Id. With Accept: application/json: {data:{url,expires_at,vehicle,width,height,format}, billing:{credits_charged,credits_remaining}, request_id}",
201
+ errors: ["400 invalid params", "401 missing/invalid key", "402 insufficient credits (balance, required_credits)", "404 vehicle not in catalog", "429 rate limited (Retry-After)", "502 render failed (refunded)"],
202
+ notes: "Do not purchase credits automatically on 402; ask the human. Respect Retry-After on 429.",
203
+ sdk: "client.getImage({ make, model, year, view, color, size, format }) / client.getImageUrl(...)",
204
+ cli: "car-image get --make Porsche --model 911 --year 2024 --view side --color red --out car.png",
205
+ },
206
+ {
207
+ method: "POST",
208
+ path: "/api/v1/image-urls",
209
+ summary: "Create 1-50 signed delivery URLs for browsers (no key needed to load them).",
210
+ auth: "bearer",
211
+ credits: "1 per URL, charged at creation; redemptions are free",
212
+ params: ["images: [{make, model, year, view?, color?, width?, height?, format?}] (or a single image object)", "ttl_seconds (60-604800; default 3600)", "max_uses (0 = unlimited; default 0)"],
213
+ returns: "201 {data:[{id,url,expires_at,max_uses,vehicle}], billing:{charged_on:'creation',credits_charged,credits_remaining}, request_id}",
214
+ errors: ["400 invalid body or batch > 50", "401", "402", "404 vehicle not in catalog", "429"],
215
+ sdk: "client.createImageUrls([{ make, model, year }], { ttlSeconds, maxUses })",
216
+ cli: "car-image url --make BMW --model M3 --year 2022 --ttl 86400",
217
+ },
218
+ {
219
+ method: "GET",
220
+ path: "/api/v1/delivery/{token}",
221
+ summary: "Redeem a signed URL; public within its TTL and max_uses.",
222
+ auth: "public",
223
+ credits: "free",
224
+ returns: "image bytes with public Cache-Control for the remaining lifetime",
225
+ errors: ["404/410 expired, exhausted or invalid token"],
226
+ },
227
+ {
228
+ method: "GET",
229
+ path: "/api/v1/images/options",
230
+ summary: "Views, colors, sizes, formats, pricing and catalog coverage.",
231
+ auth: "public",
232
+ credits: "free",
233
+ returns: "{data:{views:[{id,label,yaw_degrees,description,aliases}], colors:[{name,hex}], sizes:{presets,max}, formats, pricing:{credits_per_image,credits_per_dollar,free_credits}, catalog:{makes,models,years,sources}}}",
234
+ sdk: "client.options()",
235
+ cli: "car-image options",
236
+ },
237
+ {
238
+ method: "POST",
239
+ path: "/api/v1/images/resolve",
240
+ summary: "Resolve free text to exact image parameters.",
241
+ auth: "bearer",
242
+ credits: "free",
243
+ params: ['query: "red 2024 porsche 911 side view"'],
244
+ returns: "{data:{params:{make,model,year,view,color}, candidates:[...], confidence:0-1}}",
245
+ sdk: "client.resolve(query)",
246
+ cli: "car-image resolve red 2024 porsche 911 side view",
247
+ },
248
+ {
249
+ method: "GET",
250
+ path: "/api/v1/vehicles",
251
+ summary: "Browse or search the catalog.",
252
+ auth: "public",
253
+ credits: "free",
254
+ params: ["(none) -> years", "year -> makes [{id,name}]", "year + makeId -> models", "q (+ limit) -> fuzzy search results"],
255
+ returns: "{data:{years}} | {data:{year,makes}} | {data:{year,make_id,models}} | {data:{results}}",
256
+ sdk: "client.vehicles({ year, makeId }) / client.searchVehicles(q, { limit })",
257
+ cli: "car-image search porsche 911",
258
+ },
259
+ {
260
+ method: "POST",
261
+ path: "/api/v1/feedback",
262
+ summary: "Rate a delivered image.",
263
+ auth: "bearer",
264
+ credits: "free",
265
+ params: ["request_id OR make, model, year (+ view, color)", "rating 1-5 and/or verdict good|bad", "reason (optional)"],
266
+ returns: "201 {data:{...}, request_id}",
267
+ sdk: "client.feedback({ requestId, verdict: 'good' })",
268
+ cli: "car-image feedback --request-id <id> --good",
269
+ },
270
+ {
271
+ method: "GET",
272
+ path: "/api/v1/account",
273
+ summary: "Credits, auto-reload, payment method, 30-day usage, key scopes.",
274
+ auth: "bearer",
275
+ credits: "free",
276
+ returns: "{data:{credits, auto_reload, has_payment_method, pricing, usage_30d:{requests,images,cache_hit_rate}, key:{prefix,scopes}}, links, request_id}",
277
+ sdk: "client.account()",
278
+ cli: "car-image whoami",
279
+ },
280
+ {
281
+ method: "POST",
282
+ path: "/api/v1/billing/checkout",
283
+ summary: "Hosted Stripe Checkout URL for a credit pack (5000|10000|25000|100000).",
284
+ auth: "bearer",
285
+ credits: "free (scope billing:write; CLI keys have it)",
286
+ params: ["credits"],
287
+ returns: "201 {data:{url,credits,amount_cents}}",
288
+ notes: "A human must complete the purchase in the browser. Agents must not initiate purchases without explicit approval.",
289
+ sdk: "client.createCheckout(5000)",
290
+ cli: "car-image billing --credits 5000",
291
+ },
292
+ {
293
+ method: "POST",
294
+ path: "/api/v1/billing/portal",
295
+ summary: "Stripe Customer Portal URL (invoices, cards, auto-reload).",
296
+ auth: "bearer",
297
+ credits: "free (scope billing:write)",
298
+ returns: "201 {data:{url}}",
299
+ sdk: "client.billingPortal()",
300
+ cli: "car-image billing --portal",
301
+ },
302
+ {
303
+ method: "POST",
304
+ path: "/api/auth/device/code",
305
+ summary: "Start browser-assisted CLI login (device flow).",
306
+ auth: "public",
307
+ credits: "free",
308
+ params: ["client_name (optional)"],
309
+ returns: "201 {device_code,user_code,verification_uri,verification_uri_complete,expires_in,interval}",
310
+ cli: "car-image login",
311
+ },
312
+ {
313
+ method: "POST",
314
+ path: "/api/auth/device/token",
315
+ summary: "Poll the device flow; returns a revocable API key once approved.",
316
+ auth: "public",
317
+ credits: "free",
318
+ params: ["device_code"],
319
+ returns: "200 {access_token,token_type:'Bearer',scope} | 400 {error:'authorization_pending'|'expired_token'}",
320
+ },
321
+ {
322
+ method: "GET",
323
+ path: "/api/health",
324
+ summary: "Service health.",
325
+ auth: "public",
326
+ credits: "free",
327
+ returns: "{status:'ok'|'degraded', checks, version}",
328
+ cli: "car-image doctor",
329
+ },
330
+ {
331
+ method: "GET",
332
+ path: "/openapi.json",
333
+ summary: "Full OpenAPI 3.1 contract (also /agents.md, /llms.txt, /errors.md).",
334
+ auth: "public",
335
+ credits: "free",
336
+ cli: "car-image describe all",
337
+ },
338
+ ];
339
+ /** Human/agent-readable rendering of {@link API_REFERENCE}, optionally filtered. */
340
+ export function formatApiReference(filter) {
341
+ const needle = filter?.trim().toLowerCase();
342
+ const endpoints = needle
343
+ ? API_REFERENCE.filter((endpoint) => endpoint.path.toLowerCase().includes(needle) ||
344
+ endpoint.summary.toLowerCase().includes(needle) ||
345
+ `${endpoint.method} ${endpoint.path}`.toLowerCase().includes(needle))
346
+ : API_REFERENCE;
347
+ const header = [
348
+ `Car Image API — base URL ${DEFAULT_BASE_URL}`,
349
+ `Auth: Authorization: Bearer cimg_… (never in URLs). Errors: application/problem+json {type,title,status,detail,request_id}.`,
350
+ `Pricing: ${CREDITS_PER_IMAGE} credit per delivered image, $1 = ${CREDITS_PER_DOLLAR} credits, ${FREE_CREDITS} free credits, no subscription.`,
351
+ "",
352
+ ];
353
+ if (endpoints.length === 0) {
354
+ return `${header.join("\n")}No endpoint matches "${filter}". Known paths:\n${API_REFERENCE.map((e) => ` ${e.method} ${e.path}`).join("\n")}`;
355
+ }
356
+ const body = endpoints.map((endpoint) => {
357
+ const lines = [`${endpoint.method} ${endpoint.path}`, ` ${endpoint.summary}`, ` auth: ${endpoint.auth}; credits: ${endpoint.credits}`];
358
+ if (endpoint.params?.length)
359
+ lines.push(` params: ${endpoint.params.join("; ")}`);
360
+ if (endpoint.returns)
361
+ lines.push(` returns: ${endpoint.returns}`);
362
+ if (endpoint.errors?.length)
363
+ lines.push(` errors: ${endpoint.errors.join("; ")}`);
364
+ if (endpoint.notes)
365
+ lines.push(` notes: ${endpoint.notes}`);
366
+ if (endpoint.sdk)
367
+ lines.push(` sdk: ${endpoint.sdk}`);
368
+ if (endpoint.cli)
369
+ lines.push(` cli: ${endpoint.cli}`);
370
+ return lines.join("\n");
371
+ });
372
+ return `${header.join("\n")}${body.join("\n\n")}`;
373
+ }
374
+ /** A JSON payload as both readable text and structured content. */
375
+ export function jsonToolResult(value, summary) {
376
+ const text = summary ? `${summary}\n\n${JSON.stringify(value, null, 2)}` : JSON.stringify(value, null, 2);
377
+ return { content: [{ type: "text", text }], structuredContent: value };
378
+ }
379
+ /** An `isError` result whose text tells the agent what to do next. */
380
+ export function errorToolResult(message, extra = {}) {
381
+ return {
382
+ isError: true,
383
+ content: [{ type: "text", text: message }],
384
+ structuredContent: { error: message, ...extra },
385
+ };
386
+ }
387
+ /** Standard guidance appended to error results, keyed by HTTP-ish status. */
388
+ export function nextStepFor(status) {
389
+ switch (status) {
390
+ case 401:
391
+ return "The API key is missing or invalid. Set CAR_IMAGE_API_KEY (or run `car-image login`); never paste keys into prompts.";
392
+ case 402:
393
+ return `Insufficient credits. Stop and ask a human to buy credits at ${DEFAULT_BASE_URL}/billing (or run \`car-image billing\`). Do not purchase credits autonomously.`;
394
+ case 403:
395
+ return "This key lacks the required scope. Ask the human to create a key with the right scopes in the dashboard.";
396
+ case 404:
397
+ return "The vehicle is not in the catalog. Use search_vehicles or resolve_vehicle to find the canonical make/model/year.";
398
+ case 429:
399
+ return "Rate limited. Wait the retry_after seconds before retrying; do not hammer the API.";
400
+ default:
401
+ return "Retry once if this looks transient; otherwise report the detail and request_id to the human.";
402
+ }
403
+ }
404
+ //# sourceMappingURL=index.js.map