ccqa 0.10.2 → 0.11.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/README.md +41 -141
- package/dist/bin/ccqa.mjs +6107 -1936
- package/dist/hub-client/index.d.mts +246 -0
- package/dist/hub-client/index.mjs +212 -0
- package/dist/package.json +5 -1
- package/dist/runtime/vitest.config.d.mts +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
//#region src/hub/contract/schema.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* The hub's public REST contract (docs/hub-api.md). These schemas are
|
|
6
|
+
* consumed on both sides of the wire: the hub validates request/response
|
|
7
|
+
* bodies against them, and `ccqa/hub-client` re-exports them so any HTTP
|
|
8
|
+
* client — the ccqa CLI, an intranet web app, the hub's own bundled UI —
|
|
9
|
+
* gets the same types.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* A run's outcome. The hub never executes — a run is created when a client
|
|
13
|
+
* pushes the report of an already-finished `ccqa run`, so the only two
|
|
14
|
+
* terminal states that reach the hub are "passed" and "failed".
|
|
15
|
+
*/
|
|
16
|
+
declare const RunStatusSchema: z.ZodEnum<{
|
|
17
|
+
passed: "passed";
|
|
18
|
+
failed: "failed";
|
|
19
|
+
}>;
|
|
20
|
+
type RunStatus = z.infer<typeof RunStatusSchema>;
|
|
21
|
+
/**
|
|
22
|
+
* A pushed run. All fields are derived server-side from the report the client
|
|
23
|
+
* pushed (`POST /runs`) — the run is immutable once created.
|
|
24
|
+
*/
|
|
25
|
+
declare const RunSchema: z.ZodObject<{
|
|
26
|
+
id: z.ZodString;
|
|
27
|
+
project: z.ZodString;
|
|
28
|
+
profile: z.ZodNullable<z.ZodString>;
|
|
29
|
+
branch: z.ZodNullable<z.ZodString>;
|
|
30
|
+
status: z.ZodEnum<{
|
|
31
|
+
passed: "passed";
|
|
32
|
+
failed: "failed";
|
|
33
|
+
}>;
|
|
34
|
+
specs: z.ZodObject<{
|
|
35
|
+
total: z.ZodNumber;
|
|
36
|
+
passed: z.ZodNumber;
|
|
37
|
+
failed: z.ZodNumber;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
gitHead: z.ZodNullable<z.ZodString>;
|
|
40
|
+
promptVersion: z.ZodString;
|
|
41
|
+
ciRunId: z.ZodNullable<z.ZodString>;
|
|
42
|
+
reportCreatedAt: z.ZodString;
|
|
43
|
+
createdAt: z.ZodString;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
type Run = z.infer<typeof RunSchema>;
|
|
46
|
+
/**
|
|
47
|
+
* One failing spec's triage: the AI's prediction (read-only, sourced from
|
|
48
|
+
* the run's report) paired with the human-recorded actual cause (write-only
|
|
49
|
+
* from the client's perspective — the API is how it gets in).
|
|
50
|
+
*/
|
|
51
|
+
declare const TriageCaseSchema: z.ZodObject<{
|
|
52
|
+
feature: z.ZodString;
|
|
53
|
+
spec: z.ZodString;
|
|
54
|
+
predicted: z.ZodObject<{
|
|
55
|
+
label: z.ZodEnum<{
|
|
56
|
+
TEST_DRIFT: "TEST_DRIFT";
|
|
57
|
+
SPEC_CHANGE: "SPEC_CHANGE";
|
|
58
|
+
PRODUCT_BUG: "PRODUCT_BUG";
|
|
59
|
+
UNKNOWN: "UNKNOWN";
|
|
60
|
+
}>;
|
|
61
|
+
confidence: z.ZodNumber;
|
|
62
|
+
subDiagnosis: z.ZodOptional<z.ZodString>;
|
|
63
|
+
headline: z.ZodString;
|
|
64
|
+
}, z.core.$strip>;
|
|
65
|
+
actual: z.ZodNullable<z.ZodObject<{
|
|
66
|
+
cause: z.ZodEnum<{
|
|
67
|
+
TEST_DRIFT: "TEST_DRIFT";
|
|
68
|
+
SPEC_CHANGE: "SPEC_CHANGE";
|
|
69
|
+
PRODUCT_BUG: "PRODUCT_BUG";
|
|
70
|
+
}>;
|
|
71
|
+
note: z.ZodOptional<z.ZodString>;
|
|
72
|
+
recordedAt: z.ZodString;
|
|
73
|
+
}, z.core.$strip>>;
|
|
74
|
+
}, z.core.$strip>;
|
|
75
|
+
type TriageCase = z.infer<typeof TriageCaseSchema>;
|
|
76
|
+
declare const RunTriageSchema: z.ZodObject<{
|
|
77
|
+
runId: z.ZodString;
|
|
78
|
+
promptVersion: z.ZodString;
|
|
79
|
+
cases: z.ZodArray<z.ZodObject<{
|
|
80
|
+
feature: z.ZodString;
|
|
81
|
+
spec: z.ZodString;
|
|
82
|
+
predicted: z.ZodObject<{
|
|
83
|
+
label: z.ZodEnum<{
|
|
84
|
+
TEST_DRIFT: "TEST_DRIFT";
|
|
85
|
+
SPEC_CHANGE: "SPEC_CHANGE";
|
|
86
|
+
PRODUCT_BUG: "PRODUCT_BUG";
|
|
87
|
+
UNKNOWN: "UNKNOWN";
|
|
88
|
+
}>;
|
|
89
|
+
confidence: z.ZodNumber;
|
|
90
|
+
subDiagnosis: z.ZodOptional<z.ZodString>;
|
|
91
|
+
headline: z.ZodString;
|
|
92
|
+
}, z.core.$strip>;
|
|
93
|
+
actual: z.ZodNullable<z.ZodObject<{
|
|
94
|
+
cause: z.ZodEnum<{
|
|
95
|
+
TEST_DRIFT: "TEST_DRIFT";
|
|
96
|
+
SPEC_CHANGE: "SPEC_CHANGE";
|
|
97
|
+
PRODUCT_BUG: "PRODUCT_BUG";
|
|
98
|
+
}>;
|
|
99
|
+
note: z.ZodOptional<z.ZodString>;
|
|
100
|
+
recordedAt: z.ZodString;
|
|
101
|
+
}, z.core.$strip>>;
|
|
102
|
+
}, z.core.$strip>>;
|
|
103
|
+
recorded: z.ZodNumber;
|
|
104
|
+
total: z.ZodNumber;
|
|
105
|
+
}, z.core.$strip>;
|
|
106
|
+
type RunTriage = z.infer<typeof RunTriageSchema>;
|
|
107
|
+
declare const PutActualCauseRequestSchema: z.ZodObject<{
|
|
108
|
+
cause: z.ZodEnum<{
|
|
109
|
+
TEST_DRIFT: "TEST_DRIFT";
|
|
110
|
+
SPEC_CHANGE: "SPEC_CHANGE";
|
|
111
|
+
PRODUCT_BUG: "PRODUCT_BUG";
|
|
112
|
+
}>;
|
|
113
|
+
note: z.ZodOptional<z.ZodString>;
|
|
114
|
+
}, z.core.$strip>;
|
|
115
|
+
type PutActualCauseRequest = z.infer<typeof PutActualCauseRequestSchema>;
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region src/prompts/prompt-names.d.ts
|
|
118
|
+
/**
|
|
119
|
+
* The fixed set of prompt assets the hub stores per project/profile, and which
|
|
120
|
+
* each is fetched at run time by the CLI. Kept as a tiny, dependency-free
|
|
121
|
+
* module so both the hub side (store/handlers) and the client/CLI can share
|
|
122
|
+
* the names without a circular import.
|
|
123
|
+
*
|
|
124
|
+
* Two kinds share one namespace:
|
|
125
|
+
* - "guidance": the record/live prompt bundle — `.user.md` (human-maintained)
|
|
126
|
+
* and `.agent.md` (auto-rewritten by `ccqa run --update-agent-prompt`).
|
|
127
|
+
* - "custom-prompt": `analysis-custom-prompt` — Claude-written calibration guidance
|
|
128
|
+
* learned from graded triage cases, injected into the failure-analysis
|
|
129
|
+
* prompt at run time.
|
|
130
|
+
*
|
|
131
|
+
* Hub names are extensionless (`record.agent`), local files keep their real
|
|
132
|
+
* extensions; `PROMPT_LOCAL_PATHS` is the single mapping every caller (push,
|
|
133
|
+
* pull, learn, UI) goes through so the two never drift.
|
|
134
|
+
*/
|
|
135
|
+
declare const PROMPT_NAMES: readonly ["record.user", "record.agent", "live.user", "live.agent", "analysis-custom-prompt"];
|
|
136
|
+
type PromptName = (typeof PROMPT_NAMES)[number];
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/report/schema.d.ts
|
|
139
|
+
declare const LabelsExportSchema: z.ZodObject<{
|
|
140
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
141
|
+
runId: z.ZodNullable<z.ZodString>;
|
|
142
|
+
promptVersion: z.ZodString;
|
|
143
|
+
exportedAt: z.ZodString;
|
|
144
|
+
labels: z.ZodArray<z.ZodObject<{
|
|
145
|
+
feature: z.ZodString;
|
|
146
|
+
spec: z.ZodString;
|
|
147
|
+
predicted: z.ZodEnum<{
|
|
148
|
+
TEST_DRIFT: "TEST_DRIFT";
|
|
149
|
+
SPEC_CHANGE: "SPEC_CHANGE";
|
|
150
|
+
PRODUCT_BUG: "PRODUCT_BUG";
|
|
151
|
+
UNKNOWN: "UNKNOWN";
|
|
152
|
+
}>;
|
|
153
|
+
label: z.ZodEnum<{
|
|
154
|
+
TEST_DRIFT: "TEST_DRIFT";
|
|
155
|
+
SPEC_CHANGE: "SPEC_CHANGE";
|
|
156
|
+
PRODUCT_BUG: "PRODUCT_BUG";
|
|
157
|
+
}>;
|
|
158
|
+
note: z.ZodOptional<z.ZodString>;
|
|
159
|
+
}, z.core.$strip>>;
|
|
160
|
+
}, z.core.$strip>;
|
|
161
|
+
type LabelsExport = z.infer<typeof LabelsExportSchema>;
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/hub-client/index.d.ts
|
|
164
|
+
/**
|
|
165
|
+
* TypeScript client for the ccqa hub's public REST API (docs/hub-api.md).
|
|
166
|
+
* Uses the global `fetch` only — no `node:*` imports — so this same module
|
|
167
|
+
* works unmodified as a browser bundle (an intranet dashboard) or in a
|
|
168
|
+
* Node script (the `ccqa hub push/pull` CLI, which is itself just one more
|
|
169
|
+
* consumer of this client).
|
|
170
|
+
*/
|
|
171
|
+
interface HubClientOptions {
|
|
172
|
+
baseUrl: string;
|
|
173
|
+
token: string;
|
|
174
|
+
/** Override for testing; defaults to the global `fetch`. */
|
|
175
|
+
fetchImpl?: typeof fetch;
|
|
176
|
+
}
|
|
177
|
+
declare class HubApiError extends Error {
|
|
178
|
+
readonly status: number;
|
|
179
|
+
readonly code: string;
|
|
180
|
+
constructor(status: number, code: string, message: string);
|
|
181
|
+
}
|
|
182
|
+
interface HubVariable {
|
|
183
|
+
name: string;
|
|
184
|
+
sensitive: boolean;
|
|
185
|
+
updatedAt: string;
|
|
186
|
+
value?: string;
|
|
187
|
+
}
|
|
188
|
+
interface HubPromptMeta {
|
|
189
|
+
name: string;
|
|
190
|
+
kind: "guidance" | "custom-prompt";
|
|
191
|
+
updatedAt: string;
|
|
192
|
+
meta: Record<string, unknown>;
|
|
193
|
+
}
|
|
194
|
+
interface HubClient {
|
|
195
|
+
/** Push a report directory (as a tar.gz) for an already-finished `ccqa run`. */
|
|
196
|
+
pushRun(archive: Uint8Array, meta: {
|
|
197
|
+
project: string;
|
|
198
|
+
branch?: string;
|
|
199
|
+
profile?: string;
|
|
200
|
+
}): Promise<Run>;
|
|
201
|
+
listRuns(q?: {
|
|
202
|
+
project?: string;
|
|
203
|
+
branch?: string;
|
|
204
|
+
status?: RunStatus;
|
|
205
|
+
limit?: number;
|
|
206
|
+
}): Promise<Run[]>;
|
|
207
|
+
getRun(id: string): Promise<Run>;
|
|
208
|
+
getReport(id: string): Promise<unknown>;
|
|
209
|
+
downloadArtifacts(id: string): Promise<Uint8Array>;
|
|
210
|
+
getTriage(id: string): Promise<RunTriage>;
|
|
211
|
+
putActualCause(id: string, c: {
|
|
212
|
+
feature: string;
|
|
213
|
+
spec: string;
|
|
214
|
+
}, v: PutActualCauseRequest): Promise<TriageCase>;
|
|
215
|
+
deleteActualCause(id: string, c: {
|
|
216
|
+
feature: string;
|
|
217
|
+
spec: string;
|
|
218
|
+
}): Promise<void>;
|
|
219
|
+
importActualCauses(id: string, labels: LabelsExport): Promise<{
|
|
220
|
+
imported: number;
|
|
221
|
+
}>;
|
|
222
|
+
/** Every project the hub knows (from runs and stored secrets). */
|
|
223
|
+
listProjects(): Promise<string[]>;
|
|
224
|
+
putSession(project: string, profile: string, name: string, storageState: unknown): Promise<void>;
|
|
225
|
+
getSession(project: string, profile: string, name: string): Promise<unknown>;
|
|
226
|
+
listSessions(project: string, profile: string): Promise<{
|
|
227
|
+
name: string;
|
|
228
|
+
updatedAt: string;
|
|
229
|
+
}[]>;
|
|
230
|
+
deleteSession(project: string, profile: string, name: string): Promise<void>;
|
|
231
|
+
putVariable(project: string, profile: string, name: string, v: {
|
|
232
|
+
value: string;
|
|
233
|
+
sensitive: boolean;
|
|
234
|
+
}): Promise<void>;
|
|
235
|
+
listVariables(project: string, profile: string, opts?: {
|
|
236
|
+
includeValues?: boolean;
|
|
237
|
+
}): Promise<HubVariable[]>;
|
|
238
|
+
deleteVariable(project: string, profile: string, name: string): Promise<void>;
|
|
239
|
+
putPrompt(project: string, name: PromptName, body: string): Promise<void>;
|
|
240
|
+
getPrompt(project: string, name: PromptName): Promise<string | null>;
|
|
241
|
+
listPrompts(project: string): Promise<HubPromptMeta[]>;
|
|
242
|
+
deletePrompt(project: string, name: PromptName): Promise<void>;
|
|
243
|
+
}
|
|
244
|
+
declare function createHubClient(opts: HubClientOptions): HubClient;
|
|
245
|
+
//#endregion
|
|
246
|
+
export { HubApiError, HubClient, HubClientOptions, HubPromptMeta, HubVariable, createHubClient };
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
//#region src/hub-client/index.ts
|
|
2
|
+
var HubApiError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
code;
|
|
5
|
+
constructor(status, code, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.code = code;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
/** Per-attempt fetch timeout. Bounds how long a stalled socket can block a poll loop. */
|
|
12
|
+
const REQUEST_TIMEOUT_MS = 3e4;
|
|
13
|
+
/**
|
|
14
|
+
* HTTP methods safe to retry: GET is a pure read, and DELETE is idempotent
|
|
15
|
+
* (deleting an already-deleted resource is a no-op, not a new side effect).
|
|
16
|
+
* POST/PUT are never retried — a POST that "failed" after the server
|
|
17
|
+
* already committed it (e.g. a dropped response to pushRun) would create a
|
|
18
|
+
* duplicate run on retry, and PUT-driven imports would double-apply.
|
|
19
|
+
*/
|
|
20
|
+
const RETRYABLE_METHODS = new Set(["GET", "DELETE"]);
|
|
21
|
+
/** Fixed backoff between retry attempts, in ms. */
|
|
22
|
+
const RETRY_BACKOFF_MS = [
|
|
23
|
+
100,
|
|
24
|
+
300,
|
|
25
|
+
900
|
|
26
|
+
];
|
|
27
|
+
function sleep(ms) {
|
|
28
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
29
|
+
}
|
|
30
|
+
function createHubClient(opts) {
|
|
31
|
+
const baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
32
|
+
const doFetch = opts.fetchImpl ?? fetch;
|
|
33
|
+
async function throwHubApiError(res) {
|
|
34
|
+
let code = "unknown_error";
|
|
35
|
+
let message = res.statusText;
|
|
36
|
+
try {
|
|
37
|
+
const body = await res.json();
|
|
38
|
+
if (body.error?.code) code = body.error.code;
|
|
39
|
+
if (body.error?.message) message = body.error.message;
|
|
40
|
+
} catch {}
|
|
41
|
+
throw new HubApiError(res.status, code, message);
|
|
42
|
+
}
|
|
43
|
+
async function request(path, init = {}) {
|
|
44
|
+
const method = (init.method ?? "GET").toUpperCase();
|
|
45
|
+
const maxAttempts = RETRYABLE_METHODS.has(method) ? RETRY_BACKOFF_MS.length + 1 : 1;
|
|
46
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
47
|
+
const signal = init.signal ?? AbortSignal.timeout(REQUEST_TIMEOUT_MS);
|
|
48
|
+
let res;
|
|
49
|
+
try {
|
|
50
|
+
res = await doFetch(`${baseUrl}${path}`, {
|
|
51
|
+
...init,
|
|
52
|
+
signal,
|
|
53
|
+
headers: {
|
|
54
|
+
...init.headers,
|
|
55
|
+
Authorization: `Bearer ${opts.token}`
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (attempt < maxAttempts - 1) {
|
|
60
|
+
await sleep(RETRY_BACKOFF_MS[attempt]);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
if (res.ok) return res;
|
|
66
|
+
if (res.status >= 500 && attempt < maxAttempts - 1) {
|
|
67
|
+
await sleep(RETRY_BACKOFF_MS[attempt]);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
return throwHubApiError(res);
|
|
71
|
+
}
|
|
72
|
+
throw new Error("unreachable");
|
|
73
|
+
}
|
|
74
|
+
async function json(path, init) {
|
|
75
|
+
return (await request(path, init)).json();
|
|
76
|
+
}
|
|
77
|
+
async function bytes(path, init) {
|
|
78
|
+
const buf = await (await request(path, init)).arrayBuffer();
|
|
79
|
+
return new Uint8Array(buf);
|
|
80
|
+
}
|
|
81
|
+
async function text(path, init) {
|
|
82
|
+
return (await request(path, init)).text();
|
|
83
|
+
}
|
|
84
|
+
function noBody(path, method) {
|
|
85
|
+
return request(path, { method }).then(() => void 0);
|
|
86
|
+
}
|
|
87
|
+
function putJson(path, body) {
|
|
88
|
+
return request(path, {
|
|
89
|
+
method: "PUT",
|
|
90
|
+
headers: { "Content-Type": "application/json" },
|
|
91
|
+
body: JSON.stringify(body)
|
|
92
|
+
}).then(() => void 0);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
pushRun(archive, meta) {
|
|
96
|
+
const params = new URLSearchParams({ project: meta.project });
|
|
97
|
+
if (meta.branch) params.set("branch", meta.branch);
|
|
98
|
+
if (meta.profile) params.set("profile", meta.profile);
|
|
99
|
+
return json(`/api/v1/runs?${params}`, {
|
|
100
|
+
method: "POST",
|
|
101
|
+
headers: { "Content-Type": "application/gzip" },
|
|
102
|
+
body: toBodyInit(archive)
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
async listRuns(q = {}) {
|
|
106
|
+
const { runs } = await json(`/api/v1/runs?${queryString(q)}`);
|
|
107
|
+
return runs;
|
|
108
|
+
},
|
|
109
|
+
getRun(id) {
|
|
110
|
+
return json(`/api/v1/runs/${encodeURIComponent(id)}`);
|
|
111
|
+
},
|
|
112
|
+
getReport(id) {
|
|
113
|
+
return json(`/api/v1/runs/${encodeURIComponent(id)}/report`);
|
|
114
|
+
},
|
|
115
|
+
downloadArtifacts(id) {
|
|
116
|
+
return bytes(`/api/v1/runs/${encodeURIComponent(id)}/artifacts`);
|
|
117
|
+
},
|
|
118
|
+
getTriage(id) {
|
|
119
|
+
return json(`/api/v1/runs/${encodeURIComponent(id)}/triage`);
|
|
120
|
+
},
|
|
121
|
+
putActualCause(id, c, v) {
|
|
122
|
+
return json(`/api/v1/runs/${encodeURIComponent(id)}/triage/${encodeURIComponent(c.feature)}/${encodeURIComponent(c.spec)}/actual-cause`, {
|
|
123
|
+
method: "PUT",
|
|
124
|
+
headers: { "Content-Type": "application/json" },
|
|
125
|
+
body: JSON.stringify(v)
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
deleteActualCause(id, c) {
|
|
129
|
+
return noBody(`/api/v1/runs/${encodeURIComponent(id)}/triage/${encodeURIComponent(c.feature)}/${encodeURIComponent(c.spec)}/actual-cause`, "DELETE");
|
|
130
|
+
},
|
|
131
|
+
importActualCauses(id, labels) {
|
|
132
|
+
return json(`/api/v1/runs/${encodeURIComponent(id)}/triage/actual-causes`, {
|
|
133
|
+
method: "PUT",
|
|
134
|
+
headers: { "Content-Type": "application/json" },
|
|
135
|
+
body: JSON.stringify(labels)
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
async listProjects() {
|
|
139
|
+
const { projects } = await json("/api/v1/projects");
|
|
140
|
+
return projects;
|
|
141
|
+
},
|
|
142
|
+
putSession(project, profile, name, storageState) {
|
|
143
|
+
return putJson(`${scopePath(project, "sessions", profile)}/${encodeURIComponent(name)}`, storageState);
|
|
144
|
+
},
|
|
145
|
+
getSession(project, profile, name) {
|
|
146
|
+
return json(`${scopePath(project, "sessions", profile)}/${encodeURIComponent(name)}`);
|
|
147
|
+
},
|
|
148
|
+
async listSessions(project, profile) {
|
|
149
|
+
const { sessions } = await json(scopePath(project, "sessions", profile));
|
|
150
|
+
return sessions;
|
|
151
|
+
},
|
|
152
|
+
deleteSession(project, profile, name) {
|
|
153
|
+
return noBody(`${scopePath(project, "sessions", profile)}/${encodeURIComponent(name)}`, "DELETE");
|
|
154
|
+
},
|
|
155
|
+
putVariable(project, profile, name, v) {
|
|
156
|
+
return putJson(`${scopePath(project, "variables", profile)}/${encodeURIComponent(name)}`, v);
|
|
157
|
+
},
|
|
158
|
+
async listVariables(project, profile, opts = {}) {
|
|
159
|
+
const query = opts.includeValues ? "?include=values" : "";
|
|
160
|
+
const { variables } = await json(`${scopePath(project, "variables", profile)}${query}`);
|
|
161
|
+
return variables;
|
|
162
|
+
},
|
|
163
|
+
deleteVariable(project, profile, name) {
|
|
164
|
+
return noBody(`${scopePath(project, "variables", profile)}/${encodeURIComponent(name)}`, "DELETE");
|
|
165
|
+
},
|
|
166
|
+
putPrompt(project, name, body) {
|
|
167
|
+
return request(`${promptsPath(project)}/${encodeURIComponent(name)}`, {
|
|
168
|
+
method: "PUT",
|
|
169
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8" },
|
|
170
|
+
body
|
|
171
|
+
}).then(() => void 0);
|
|
172
|
+
},
|
|
173
|
+
async getPrompt(project, name) {
|
|
174
|
+
try {
|
|
175
|
+
return await text(`${promptsPath(project)}/${encodeURIComponent(name)}`);
|
|
176
|
+
} catch (err) {
|
|
177
|
+
if (err instanceof HubApiError && err.status === 404) return null;
|
|
178
|
+
throw err;
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
async listPrompts(project) {
|
|
182
|
+
const { prompts } = await json(promptsPath(project));
|
|
183
|
+
return prompts;
|
|
184
|
+
},
|
|
185
|
+
deletePrompt(project, name) {
|
|
186
|
+
return noBody(`${promptsPath(project)}/${encodeURIComponent(name)}`, "DELETE");
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/** `/api/v1/projects/<project>/<kind>/<profile>` — the scope prefix secret endpoints share. */
|
|
191
|
+
function scopePath(project, kind, profile) {
|
|
192
|
+
return `/api/v1/projects/${encodeURIComponent(project)}/${kind}/${encodeURIComponent(profile)}`;
|
|
193
|
+
}
|
|
194
|
+
/** Prompts are project-scoped (not per-profile): `/api/v1/projects/<project>/prompts`. */
|
|
195
|
+
function promptsPath(project) {
|
|
196
|
+
return `/api/v1/projects/${encodeURIComponent(project)}/prompts`;
|
|
197
|
+
}
|
|
198
|
+
function queryString(params) {
|
|
199
|
+
const out = new URLSearchParams();
|
|
200
|
+
for (const [key, value] of Object.entries(params)) if (value !== void 0) out.set(key, String(value));
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* `Uint8Array` isn't a valid `BodyInit` in every fetch implementation's
|
|
205
|
+
* types (browser lib.dom vs Node's undici disagree) — go through a plain
|
|
206
|
+
* `ArrayBuffer` slice, which every implementation accepts.
|
|
207
|
+
*/
|
|
208
|
+
function toBodyInit(bytes) {
|
|
209
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
210
|
+
}
|
|
211
|
+
//#endregion
|
|
212
|
+
export { HubApiError, createHubClient };
|
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccqa",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Browser test recorder powered by Claude Code and agent-browser",
|
|
6
6
|
"repository": {
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"./test-helpers": {
|
|
19
19
|
"types": "./dist/runtime/test-helpers.d.mts",
|
|
20
20
|
"import": "./dist/runtime/test-helpers.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./hub-client": {
|
|
23
|
+
"types": "./dist/hub-client/index.d.mts",
|
|
24
|
+
"import": "./dist/hub-client/index.mjs"
|
|
21
25
|
}
|
|
22
26
|
},
|
|
23
27
|
"files": [
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import { URL as URL$1 } from "node:url";
|
|
4
|
+
import { ZlibOptions } from "node:zlib";
|
|
4
5
|
import * as http from "node:http";
|
|
5
6
|
import { Agent, ClientRequest, ClientRequestArgs, OutgoingHttpHeaders } from "node:http";
|
|
6
7
|
import { Http2SecureServer } from "node:http2";
|
|
@@ -10,7 +11,6 @@ import * as net from "node:net";
|
|
|
10
11
|
import { Duplex, DuplexOptions, Stream } from "node:stream";
|
|
11
12
|
import esbuild from "esbuild";
|
|
12
13
|
import { SecureContextOptions } from "node:tls";
|
|
13
|
-
import { ZlibOptions } from "node:zlib";
|
|
14
14
|
import * as Terser from "terser";
|
|
15
15
|
import DartSass from "sass";
|
|
16
16
|
import SassEmbedded from "sass-embedded";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccqa",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Browser test recorder powered by Claude Code and agent-browser",
|
|
6
6
|
"repository": {
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
"./test-helpers": {
|
|
19
19
|
"types": "./dist/runtime/test-helpers.d.mts",
|
|
20
20
|
"import": "./dist/runtime/test-helpers.mjs"
|
|
21
|
+
},
|
|
22
|
+
"./hub-client": {
|
|
23
|
+
"types": "./dist/hub-client/index.d.mts",
|
|
24
|
+
"import": "./dist/hub-client/index.mjs"
|
|
21
25
|
}
|
|
22
26
|
},
|
|
23
27
|
"files": [
|