@claudexor/harness-raw-api 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 joi-lab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # @claudexor/harness-raw-api
2
+
3
+ Internal package of [Claudexor](https://github.com/razzant/claudexor) — Raw-API harness: OpenAI-compatible chat completions (planner/reviewer; no native edit tools).
4
+
5
+ Published as part of the Claudexor toolchain; it follows the monorepo's
6
+ lockstep version and has no separate semver contract. Use the `claudexor`
7
+ CLI (or `@claudexor/cli`) as the supported entry point.
@@ -0,0 +1,16 @@
1
+ import type { ProviderFamily } from "@claudexor/schema";
2
+ import type { HarnessAdapter } from "@claudexor/core";
3
+ export interface RawApiConfig {
4
+ id?: string;
5
+ providerFamily?: ProviderFamily;
6
+ baseUrl?: string;
7
+ keyEnv?: string;
8
+ defaultModel?: string;
9
+ }
10
+ /**
11
+ * A raw-API harness backed by an OpenAI-compatible chat-completions endpoint.
12
+ * It has no native edit tools, so it serves as a planner/reviewer (useful as a
13
+ * cross-family reviewer via API key when no CLI of that family is installed).
14
+ */
15
+ export declare function createRawApiAdapter(config?: RawApiConfig): HarnessAdapter;
16
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAkF,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExI,OAAO,KAAK,EAAc,cAAc,EAAE,MAAM,iBAAiB,CAAC;AA+BlE,MAAM,WAAW,YAAY;IAC3B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,GAAE,YAAiB,GAAG,cAAc,CAmL7E"}
package/dist/index.js ADDED
@@ -0,0 +1,224 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { ConformanceReport as ConformanceReportSchema, HarnessManifest as HarnessManifestSchema } from "@claudexor/schema";
3
+ import { HarnessUnavailableError, abortSignalFromSpec } from "@claudexor/core";
4
+ import { resolveSecret } from "@claudexor/secrets";
5
+ import { CLAUDEXOR_VERSION, nowIso, redactSecrets } from "@claudexor/util";
6
+ import { parseChatCompletion, parseModelsList } from "./parse.js";
7
+ /** A stalled remote endpoint must not hang a run forever. */
8
+ const RAW_API_TIMEOUT_MS = 180_000;
9
+ /** Model enumeration is interactive (a picker waits on it): keep it snappy. */
10
+ const RAW_API_MODELS_TIMEOUT_MS = 15_000;
11
+ const RAW_API_TRANSIENT_STATUS = new Set([408, 502, 503, 504]);
12
+ /**
13
+ * Build the chat-completions user `content`. With image attachments, switch
14
+ * from a plain string to the OpenAI-compatible parts array (`text` +
15
+ * `image_url` data URLs). Non-image attachments have no inline chat shape.
16
+ */
17
+ function rawApiUserContent(spec) {
18
+ const images = (spec.attachments ?? []).filter((a) => a.kind === "image");
19
+ if (images.length === 0)
20
+ return spec.prompt;
21
+ const parts = [{ type: "text", text: spec.prompt }];
22
+ for (const a of images) {
23
+ try {
24
+ parts.push({ type: "image_url", image_url: { url: `data:${a.mime};base64,${readFileSync(a.path).toString("base64")}` } });
25
+ }
26
+ catch {
27
+ // Late-deleted attachment: skip; the text prompt still goes through.
28
+ }
29
+ }
30
+ return parts.length > 1 ? parts : spec.prompt;
31
+ }
32
+ /**
33
+ * A raw-API harness backed by an OpenAI-compatible chat-completions endpoint.
34
+ * It has no native edit tools, so it serves as a planner/reviewer (useful as a
35
+ * cross-family reviewer via API key when no CLI of that family is installed).
36
+ */
37
+ export function createRawApiAdapter(config = {}) {
38
+ const id = config.id ?? "raw-api";
39
+ const providerFamily = config.providerFamily ?? "openai";
40
+ const baseUrl = config.baseUrl ?? process.env.CLAUDEXOR_RAWAPI_BASE_URL ?? "https://api.openai.com/v1";
41
+ const keyEnv = config.keyEnv ?? (process.env.CLAUDEXOR_RAWAPI_KEY ? "CLAUDEXOR_RAWAPI_KEY" : "OPENAI_API_KEY");
42
+ const defaultModel = config.defaultModel ?? process.env.CLAUDEXOR_RAWAPI_MODEL ?? "gpt-4o-mini";
43
+ function apiKey() {
44
+ const env = process.env[keyEnv];
45
+ if (env)
46
+ return env;
47
+ // Secret fallback is INSTANCE-SCOPED so a key for one provider is never sent
48
+ // to another (e.g. an OpenAI "raw" key must not reach openrouter.ai). The
49
+ // default OpenAI-compatible instance owns the managed "raw" (and "openai")
50
+ // secret; every named instance (openrouter, …) resolves ONLY its own slot.
51
+ if (id === "raw-api") {
52
+ return resolveSecret("raw") ?? (keyEnv === "OPENAI_API_KEY" ? (resolveSecret("openai") ?? undefined) : undefined);
53
+ }
54
+ return resolveSecret(id) ?? undefined;
55
+ }
56
+ return {
57
+ id,
58
+ async discover() {
59
+ if (!apiKey()) {
60
+ throw new HarnessUnavailableError(`raw-api unavailable: set ${keyEnv}`);
61
+ }
62
+ return HarnessManifestSchema.parse({
63
+ id,
64
+ display_name: `Raw API (${providerFamily})`,
65
+ kind: "remote_api",
66
+ // A remote HTTP API has no CLI binary version; the endpoint contract
67
+ // is the closest honest "version" (UIs render this field verbatim —
68
+ // a model id here would read as a bogus harness version).
69
+ version: `${providerFamily} chat-completions API`,
70
+ adapter_version: CLAUDEXOR_VERSION,
71
+ provider_family: providerFamily,
72
+ capabilities: {
73
+ plan: true,
74
+ implement: false,
75
+ create_from_scratch: false,
76
+ review: true,
77
+ verify: false,
78
+ synthesize: true,
79
+ orchestrate: true,
80
+ read_files: false,
81
+ browser_tool: false,
82
+ web_policy: "none",
83
+ // The chat-completions request sends no `reasoning_effort` field
84
+ // (body is {model, messages} only) -> effort is not a tunable surface.
85
+ effort_levels: [],
86
+ },
87
+ capability_profile: {
88
+ auth: {
89
+ supported_sources: ["api_key_env"],
90
+ preferred_source: "api_key_env",
91
+ credential_transports: [{ source: "api_key_env", kind: "http_header", relocatable_by: ["ENV"] }],
92
+ },
93
+ access_control: { readonly_mechanism: "none" },
94
+ isolation: { supported_containment: ["env_or_file_injection"] },
95
+ // OpenAI-compatible chat-completions accept images as an inline `image_url` data URL part.
96
+ image_input: "base64_inline",
97
+ },
98
+ auth_modes: ["api_key"],
99
+ access_profiles_supported: ["readonly"],
100
+ });
101
+ },
102
+ async doctor(_spec) {
103
+ if (!apiKey()) {
104
+ return ConformanceReportSchema.parse({
105
+ harness_id: id,
106
+ status: "unavailable",
107
+ checks: [{ id: "api_key", status: "fail", detail: `${keyEnv} not set` }],
108
+ reasons: [`set ${keyEnv} to enable the raw-api harness`],
109
+ });
110
+ }
111
+ // A key STRING is source availability, not proven readiness: no isolated
112
+ // smoke runs here (doctor must not spend paid API calls), so the honest
113
+ // status is degraded-with-reason rather than ok-by-key-presence.
114
+ return ConformanceReportSchema.parse({
115
+ harness_id: id,
116
+ status: "degraded",
117
+ checks: [
118
+ { id: "api_key", status: "pass", detail: "key available (unproven without isolated smoke)" },
119
+ { id: "isolated_smoke", status: "skip", detail: "doctor does not spend paid API calls" },
120
+ ],
121
+ // No native edit tools: planner/reviewer roles only.
122
+ enabled_intents: ["plan", "spec", "review", "synthesize", "explain", "orchestrate"],
123
+ disabled_intents: ["implement", "create_from_scratch", "repair", "verify"],
124
+ reasons: ["key present but route unproven (no isolated smoke)"],
125
+ });
126
+ },
127
+ // The REAL enumeration producer (ADP4): an OpenAI-compatible endpoint
128
+ // exposes `GET <baseURL>/models`. Fails SOFT — a picker must never see a
129
+ // throw, so network/auth/parse errors collapse to an empty list.
130
+ async models() {
131
+ const key = apiKey();
132
+ if (!key)
133
+ return [];
134
+ try {
135
+ const res = await fetch(`${baseUrl.replace(/\/$/, "")}/models`, {
136
+ method: "GET",
137
+ headers: { accept: "application/json", authorization: `Bearer ${key}` },
138
+ signal: AbortSignal.timeout(RAW_API_MODELS_TIMEOUT_MS),
139
+ });
140
+ if (!res.ok)
141
+ return [];
142
+ const json = await res.json();
143
+ return parseModelsList(json).map((m) => ({ id: m.id, label: m.label, context_window: m.context_window }));
144
+ }
145
+ catch {
146
+ return [];
147
+ }
148
+ },
149
+ async *run(spec) {
150
+ const key = apiKey();
151
+ if (!key) {
152
+ yield { type: "error", session_id: spec.session_id, ts: nowIso(), error: `raw-api: ${keyEnv} not set` };
153
+ yield { type: "completed", session_id: spec.session_id, ts: nowIso() };
154
+ return;
155
+ }
156
+ const model = spec.model_hint ?? defaultModel;
157
+ // The model is only REQUESTED here; the observed model comes from the response.
158
+ yield { type: "started", session_id: spec.session_id, ts: nowIso(), payload: { requested_model: model } };
159
+ try {
160
+ const specSignal = abortSignalFromSpec(spec);
161
+ const timeoutSignal = AbortSignal.timeout(RAW_API_TIMEOUT_MS);
162
+ const signal = specSignal ? AbortSignal.any([specSignal, timeoutSignal]) : timeoutSignal;
163
+ const res = await fetch(`${baseUrl.replace(/\/$/, "")}/chat/completions`, {
164
+ method: "POST",
165
+ headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
166
+ body: JSON.stringify({ model, messages: [{ role: "user", content: rawApiUserContent(spec) }] }),
167
+ signal,
168
+ });
169
+ if (!res.ok) {
170
+ const body = await res.text().catch(() => "");
171
+ const retryAfter = res.headers.get("retry-after");
172
+ const resetsAt = retryAfter ? resetsAtFromRetryAfter(retryAfter) : null;
173
+ const retryDelayMs = retryAfter && Number.isFinite(Number(retryAfter)) ? Number(retryAfter) * 1000 : null;
174
+ yield {
175
+ type: "error",
176
+ session_id: spec.session_id,
177
+ ts: nowIso(),
178
+ error: `raw-api HTTP ${res.status}`,
179
+ // A 429 is a TYPED rate-limit signal (cooldown/fallback governance
180
+ // reads ev.rate_limit, never prose), with the native reset when known.
181
+ ...(res.status === 429 ? { rate_limit: { resets_at: resetsAt, retry_delay_ms: retryDelayMs } } : {}),
182
+ ...(res.status === 429 || RAW_API_TRANSIENT_STATUS.has(res.status)
183
+ ? { transient: { kind: res.status === 408 ? "timeout" : "service_unavailable", retry_delay_ms: retryDelayMs } }
184
+ : {}),
185
+ payload: res.status === 429 ? { resets_at: resetsAt } : { body: redactSecrets(body.slice(0, 500)) },
186
+ };
187
+ yield { type: "completed", session_id: spec.session_id, ts: nowIso() };
188
+ return;
189
+ }
190
+ const json = await res.json();
191
+ const parsed = parseChatCompletion(json);
192
+ if (parsed.text)
193
+ yield { type: "message", session_id: spec.session_id, ts: nowIso(), text: parsed.text };
194
+ yield {
195
+ type: "usage",
196
+ session_id: spec.session_id,
197
+ ts: nowIso(),
198
+ usage: { input_tokens: parsed.usage.input_tokens, output_tokens: parsed.usage.output_tokens },
199
+ observed_model: parsed.model ?? undefined,
200
+ };
201
+ yield { type: "completed", session_id: spec.session_id, ts: nowIso(), observed_model: parsed.model ?? undefined };
202
+ }
203
+ catch (err) {
204
+ const message = redactSecrets(err instanceof Error ? err.message : String(err));
205
+ yield {
206
+ type: "error",
207
+ session_id: spec.session_id,
208
+ ts: nowIso(),
209
+ error: message,
210
+ transient: { kind: err instanceof DOMException && err.name === "TimeoutError" ? "timeout" : "network", retry_delay_ms: null },
211
+ };
212
+ yield { type: "completed", session_id: spec.session_id, ts: nowIso() };
213
+ }
214
+ },
215
+ };
216
+ }
217
+ function resetsAtFromRetryAfter(header) {
218
+ const seconds = Number(header);
219
+ if (Number.isFinite(seconds) && seconds >= 0)
220
+ return new Date(Date.now() + seconds * 1000).toISOString();
221
+ const date = new Date(header);
222
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
223
+ }
224
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export interface ChatResult {
2
+ text: string;
3
+ model: string | null;
4
+ usage: {
5
+ input_tokens?: number;
6
+ output_tokens?: number;
7
+ };
8
+ }
9
+ export interface ParsedModel {
10
+ id: string;
11
+ label: string | null;
12
+ context_window: number | null;
13
+ }
14
+ /**
15
+ * Parse an OpenAI-compatible `GET /v1/models` response: `{ data: [{ id, ... }] }`.
16
+ * Only `id` is guaranteed by the OpenAI shape; `context_window`/`label` are
17
+ * populated opportunistically (some compatible providers, e.g. OpenRouter,
18
+ * carry richer fields). Entries without a usable string `id` are dropped.
19
+ */
20
+ export declare function parseModelsList(json: any): ParsedModel[];
21
+ /** Parse an OpenAI-compatible /chat/completions response. */
22
+ export declare function parseChatCompletion(json: any): ChatResult;
23
+ //# sourceMappingURL=parse.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,KAAK,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC1D;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,GAAG,GAAG,WAAW,EAAE,CAYxD;AAED,6DAA6D;AAC7D,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,GAAG,GAAG,UAAU,CAYzD"}
package/dist/parse.js ADDED
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Parse an OpenAI-compatible `GET /v1/models` response: `{ data: [{ id, ... }] }`.
3
+ * Only `id` is guaranteed by the OpenAI shape; `context_window`/`label` are
4
+ * populated opportunistically (some compatible providers, e.g. OpenRouter,
5
+ * carry richer fields). Entries without a usable string `id` are dropped.
6
+ */
7
+ export function parseModelsList(json) {
8
+ const data = Array.isArray(json?.data) ? json.data : [];
9
+ const out = [];
10
+ for (const entry of data) {
11
+ const id = typeof entry?.id === "string" ? entry.id : null;
12
+ if (!id)
13
+ continue;
14
+ const label = typeof entry?.name === "string" ? entry.name : null;
15
+ const ctxRaw = entry?.context_length ?? entry?.context_window;
16
+ const context_window = typeof ctxRaw === "number" && Number.isInteger(ctxRaw) && ctxRaw > 0 ? ctxRaw : null;
17
+ out.push({ id, label, context_window });
18
+ }
19
+ return out;
20
+ }
21
+ /** Parse an OpenAI-compatible /chat/completions response. */
22
+ export function parseChatCompletion(json) {
23
+ const choice = json?.choices?.[0];
24
+ const text = String(choice?.message?.content ?? "");
25
+ const usage = json?.usage ?? {};
26
+ return {
27
+ text,
28
+ model: typeof json?.model === "string" ? json.model : null,
29
+ usage: {
30
+ input_tokens: typeof usage.prompt_tokens === "number" ? usage.prompt_tokens : undefined,
31
+ output_tokens: typeof usage.completion_tokens === "number" ? usage.completion_tokens : undefined,
32
+ },
33
+ };
34
+ }
35
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"AAaA;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,IAAS;IACvC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;IACxD,MAAM,GAAG,GAAkB,EAAE,CAAC;IAC9B,KAAK,MAAM,KAAK,IAAI,IAAI,EAAE,CAAC;QACzB,MAAM,EAAE,GAAG,OAAO,KAAK,EAAE,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QAC3D,IAAI,CAAC,EAAE;YAAE,SAAS;QAClB,MAAM,KAAK,GAAG,OAAO,KAAK,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;QAClE,MAAM,MAAM,GAAG,KAAK,EAAE,cAAc,IAAI,KAAK,EAAE,cAAc,CAAC;QAC9D,MAAM,cAAc,GAAG,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;QAC5G,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,mBAAmB,CAAC,IAAS;IAC3C,MAAM,MAAM,GAAG,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IAClC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC;IAChC,OAAO;QACL,IAAI;QACJ,KAAK,EAAE,OAAO,IAAI,EAAE,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;QAC1D,KAAK,EAAE;YACL,YAAY,EAAE,OAAO,KAAK,CAAC,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS;YACvF,aAAa,EAAE,OAAO,KAAK,CAAC,iBAAiB,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS;SACjG;KACF,CAAC;AACJ,CAAC"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@claudexor/harness-raw-api",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "description": "Raw-API harness: OpenAI-compatible chat completions (planner/reviewer; no native edit tools).",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "dependencies": {
19
+ "@claudexor/schema": "1.0.0",
20
+ "@claudexor/core": "1.0.0",
21
+ "@claudexor/secrets": "1.0.0",
22
+ "@claudexor/util": "1.0.0"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/razzant/claudexor.git",
27
+ "directory": "packages/harness-raw-api"
28
+ },
29
+ "homepage": "https://github.com/razzant/claudexor#readme",
30
+ "bugs": {
31
+ "url": "https://github.com/razzant/claudexor/issues"
32
+ },
33
+ "engines": {
34
+ "node": ">=20.19"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc",
41
+ "typecheck": "tsc --noEmit"
42
+ }
43
+ }