@withgauge/cli 0.1.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 +21 -0
- package/README.md +34 -0
- package/dist/index.js +2733 -0
- package/package.json +37 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2733 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __export = (target, all) => {
|
|
4
|
+
for (var name in all)
|
|
5
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// src/index.ts
|
|
9
|
+
import { Command, CommanderError, Option as Option2 } from "commander";
|
|
10
|
+
|
|
11
|
+
// src/commands/apply.ts
|
|
12
|
+
var apply_exports = {};
|
|
13
|
+
__export(apply_exports, {
|
|
14
|
+
register: () => register
|
|
15
|
+
});
|
|
16
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
17
|
+
|
|
18
|
+
// ../packages/api-schemas/src/batteries.ts
|
|
19
|
+
import { z as z2 } from "zod";
|
|
20
|
+
|
|
21
|
+
// ../packages/api-schemas/src/common.ts
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
var ERROR_CODES = [
|
|
24
|
+
"unauthorized",
|
|
25
|
+
"forbidden",
|
|
26
|
+
"not_found",
|
|
27
|
+
"invalid_request",
|
|
28
|
+
"insufficient_credits",
|
|
29
|
+
"needs_provider_key",
|
|
30
|
+
"rate_limited",
|
|
31
|
+
"conflict",
|
|
32
|
+
"internal"
|
|
33
|
+
];
|
|
34
|
+
var errorCodeSchema = z.enum(ERROR_CODES);
|
|
35
|
+
var errorEnvelopeSchema = z.object({
|
|
36
|
+
error: z.object({
|
|
37
|
+
code: errorCodeSchema,
|
|
38
|
+
message: z.string()
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
var listQuerySchema = z.object({
|
|
42
|
+
limit: z.coerce.number().int().min(1).max(200).default(50),
|
|
43
|
+
cursor: z.string().optional()
|
|
44
|
+
});
|
|
45
|
+
function listResponseSchema(item) {
|
|
46
|
+
return z.object({
|
|
47
|
+
items: z.array(item),
|
|
48
|
+
nextCursor: z.string().optional()
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ../packages/api-schemas/src/batteries.ts
|
|
53
|
+
var BATTERY_AGENTS = ["CLAUDE_CODE", "CODEX_CLI", "CURSOR"];
|
|
54
|
+
var BATTERY_CADENCES = [
|
|
55
|
+
"NONE",
|
|
56
|
+
"DAILY",
|
|
57
|
+
"WEEKLY",
|
|
58
|
+
"MONTHLY",
|
|
59
|
+
"DAYS_PER_WEEK"
|
|
60
|
+
];
|
|
61
|
+
var BATTERY_SCENARIOS = ["GREENFIELD", "MIGRATION"];
|
|
62
|
+
var batteryAgentSchema = z2.enum(BATTERY_AGENTS);
|
|
63
|
+
var BATTERY_MAX_LIMITS = {
|
|
64
|
+
maxTurns: 30,
|
|
65
|
+
wallClockS: 1800,
|
|
66
|
+
maxUsd: 10
|
|
67
|
+
};
|
|
68
|
+
var MAX_SAMPLES_PER_CYCLE = 50;
|
|
69
|
+
var batteryLimitsSchema = z2.object({
|
|
70
|
+
maxTurns: z2.number().positive().max(BATTERY_MAX_LIMITS.maxTurns).optional(),
|
|
71
|
+
wallClockS: z2.number().positive().max(BATTERY_MAX_LIMITS.wallClockS).optional(),
|
|
72
|
+
maxUsd: z2.number().positive().max(BATTERY_MAX_LIMITS.maxUsd).optional()
|
|
73
|
+
});
|
|
74
|
+
var batteryScheduleSpecSchema = z2.object({
|
|
75
|
+
repoUrl: z2.string().nullish(),
|
|
76
|
+
repoRef: z2.string().nullish(),
|
|
77
|
+
/** Pinned model id; `null` explicitly clears to the agent default (vs.
|
|
78
|
+
* omitted, which inherits `defaults.model`). */
|
|
79
|
+
model: z2.string().nullable().optional(),
|
|
80
|
+
agents: z2.array(batteryAgentSchema).optional(),
|
|
81
|
+
samplesPerCycle: z2.number().int().min(1).max(MAX_SAMPLES_PER_CYCLE).optional(),
|
|
82
|
+
cadence: z2.enum(BATTERY_CADENCES).optional(),
|
|
83
|
+
daysPerWeek: z2.number().int().min(1).max(7).optional(),
|
|
84
|
+
experiment: z2.string().optional(),
|
|
85
|
+
limits: batteryLimitsSchema.optional()
|
|
86
|
+
});
|
|
87
|
+
var batteryDefaultsSchema = z2.object({
|
|
88
|
+
scenario: z2.enum(BATTERY_SCENARIOS).optional(),
|
|
89
|
+
isBranded: z2.boolean().optional(),
|
|
90
|
+
topic: z2.string().optional(),
|
|
91
|
+
tags: z2.array(z2.string()).optional(),
|
|
92
|
+
model: z2.string().nullable().optional(),
|
|
93
|
+
agents: z2.array(batteryAgentSchema).optional(),
|
|
94
|
+
samplesPerCycle: z2.number().int().min(1).max(MAX_SAMPLES_PER_CYCLE).optional(),
|
|
95
|
+
cadence: z2.enum(BATTERY_CADENCES).optional(),
|
|
96
|
+
limits: batteryLimitsSchema.optional()
|
|
97
|
+
});
|
|
98
|
+
var batteryPromptSpecSchema = z2.object({
|
|
99
|
+
/** Idempotency key, unique per org (→ Prompt.externalKey). */
|
|
100
|
+
key: z2.string().min(1),
|
|
101
|
+
text: z2.string().min(1),
|
|
102
|
+
scenario: z2.enum(BATTERY_SCENARIOS).optional(),
|
|
103
|
+
isBranded: z2.boolean().optional(),
|
|
104
|
+
topic: z2.string().optional(),
|
|
105
|
+
tags: z2.array(z2.string()).optional(),
|
|
106
|
+
experiment: z2.string().optional(),
|
|
107
|
+
/** Omitted ⇒ one greenfield schedule from `defaults`. */
|
|
108
|
+
schedules: z2.array(batteryScheduleSpecSchema).optional()
|
|
109
|
+
});
|
|
110
|
+
var batterySpecSchema = z2.object({
|
|
111
|
+
version: z2.literal(1),
|
|
112
|
+
org: z2.string().optional(),
|
|
113
|
+
experiment: z2.string().optional(),
|
|
114
|
+
/** Expand + enqueue runs now (credit-gated on the API path). */
|
|
115
|
+
expand: z2.boolean().optional(),
|
|
116
|
+
defaults: batteryDefaultsSchema.optional(),
|
|
117
|
+
prompts: z2.array(batteryPromptSpecSchema).min(1)
|
|
118
|
+
});
|
|
119
|
+
var batteryPlanScheduleSchema = z2.object({
|
|
120
|
+
action: z2.enum(["create", "update"]),
|
|
121
|
+
repoUrl: z2.string().nullable(),
|
|
122
|
+
repoRef: z2.string().nullable(),
|
|
123
|
+
/** Runs this schedule would enqueue (agents × samples; 0 unless `expand`). */
|
|
124
|
+
runs: z2.number().int()
|
|
125
|
+
});
|
|
126
|
+
var batteryPlanPromptSchema = z2.object({
|
|
127
|
+
key: z2.string(),
|
|
128
|
+
action: z2.enum(["create", "update"]),
|
|
129
|
+
schedules: z2.array(batteryPlanScheduleSchema)
|
|
130
|
+
});
|
|
131
|
+
var batteryPlanSchema = z2.object({
|
|
132
|
+
dryRun: z2.literal(true),
|
|
133
|
+
org: z2.string(),
|
|
134
|
+
experiment: z2.string().nullable(),
|
|
135
|
+
expand: z2.boolean(),
|
|
136
|
+
prompts: z2.array(batteryPlanPromptSchema),
|
|
137
|
+
summary: z2.object({
|
|
138
|
+
promptsToCreate: z2.number().int(),
|
|
139
|
+
promptsToUpdate: z2.number().int(),
|
|
140
|
+
schedulesToCreate: z2.number().int(),
|
|
141
|
+
schedulesToUpdate: z2.number().int(),
|
|
142
|
+
runsToEnqueue: z2.number().int(),
|
|
143
|
+
/** Run count keyed by agent (only populated when `expand`). */
|
|
144
|
+
runsByAgent: z2.record(z2.string(), z2.number().int()),
|
|
145
|
+
/** Flat estimate (runs × credits-per-run) for ORG-funded applies; the real
|
|
146
|
+
* gate (key gate, balance truncation, overage) runs at apply time. */
|
|
147
|
+
estimatedCredits: z2.number().int()
|
|
148
|
+
})
|
|
149
|
+
});
|
|
150
|
+
var batteryApplyResponseSchema = z2.object({
|
|
151
|
+
batchIds: z2.array(z2.string()),
|
|
152
|
+
runIds: z2.array(z2.string()),
|
|
153
|
+
experiment: z2.string().nullable(),
|
|
154
|
+
/** Present when part of the expansion hit a billing gate but at least one
|
|
155
|
+
* run was enqueued (an all-blocked apply is a 402 error instead). */
|
|
156
|
+
blocked: z2.enum(["insufficient_credits", "needs_provider_key"]).optional()
|
|
157
|
+
});
|
|
158
|
+
var batchRunStatusSchema = z2.enum([
|
|
159
|
+
"QUEUED",
|
|
160
|
+
"CLAIMED",
|
|
161
|
+
"PROVISIONING",
|
|
162
|
+
"RUNNING",
|
|
163
|
+
"SUCCEEDED",
|
|
164
|
+
"FAILED",
|
|
165
|
+
"TIMED_OUT",
|
|
166
|
+
"CANCELED"
|
|
167
|
+
]);
|
|
168
|
+
var batchListQuerySchema = listQuerySchema.extend({
|
|
169
|
+
promptId: z2.string().optional(),
|
|
170
|
+
experiment: z2.string().optional()
|
|
171
|
+
});
|
|
172
|
+
var batchSummarySchema = z2.object({
|
|
173
|
+
id: z2.string(),
|
|
174
|
+
promptId: z2.string(),
|
|
175
|
+
/** Whitespace-collapsed prompt text, truncated (~80 chars) for list views;
|
|
176
|
+
* the full text is on GET /batches/{id}. */
|
|
177
|
+
promptText: z2.string(),
|
|
178
|
+
experiment: z2.string().nullable(),
|
|
179
|
+
createdAt: z2.string(),
|
|
180
|
+
// ISO 8601
|
|
181
|
+
/** Run count keyed by RunStatus (absent statuses omitted). */
|
|
182
|
+
runCounts: z2.record(z2.string(), z2.number().int())
|
|
183
|
+
});
|
|
184
|
+
var batchListResponseSchema = listResponseSchema(batchSummarySchema);
|
|
185
|
+
var batchRunSummarySchema = z2.object({
|
|
186
|
+
id: z2.string(),
|
|
187
|
+
agent: z2.string(),
|
|
188
|
+
model: z2.string(),
|
|
189
|
+
status: batchRunStatusSchema,
|
|
190
|
+
createdAt: z2.string(),
|
|
191
|
+
startedAt: z2.string().nullable(),
|
|
192
|
+
finishedAt: z2.string().nullable()
|
|
193
|
+
});
|
|
194
|
+
var batchDetailSchema = z2.object({
|
|
195
|
+
id: z2.string(),
|
|
196
|
+
promptId: z2.string(),
|
|
197
|
+
scheduleId: z2.string().nullable(),
|
|
198
|
+
promptText: z2.string(),
|
|
199
|
+
scenario: z2.string(),
|
|
200
|
+
repoUrl: z2.string().nullable(),
|
|
201
|
+
repoRef: z2.string().nullable(),
|
|
202
|
+
experiment: z2.string().nullable(),
|
|
203
|
+
funding: z2.string(),
|
|
204
|
+
createdAt: z2.string(),
|
|
205
|
+
runCounts: z2.record(z2.string(), z2.number().int()),
|
|
206
|
+
runs: z2.array(batchRunSummarySchema)
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// ../packages/api-schemas/src/billing.ts
|
|
210
|
+
import { z as z3 } from "zod";
|
|
211
|
+
var billingStatusSchema = z3.object({
|
|
212
|
+
/** When false, billing is disabled: unlimited runs. */
|
|
213
|
+
enforced: z3.boolean(),
|
|
214
|
+
balance: z3.number(),
|
|
215
|
+
/** Renewal allowance per cycle; null = free plan. */
|
|
216
|
+
allowance: z3.number().nullable(),
|
|
217
|
+
plan: z3.string().nullable(),
|
|
218
|
+
subscriptionStatus: z3.string().nullable(),
|
|
219
|
+
currentPeriodStart: z3.string().nullable(),
|
|
220
|
+
currentPeriodEnd: z3.string().nullable(),
|
|
221
|
+
overageEnabled: z3.boolean(),
|
|
222
|
+
overageCapCredits: z3.number().nullable(),
|
|
223
|
+
creditsUsedThisPeriod: z3.number(),
|
|
224
|
+
runsThisPeriod: z3.number(),
|
|
225
|
+
overageThisPeriod: z3.number(),
|
|
226
|
+
/** Can the org fund at least one more run right now. */
|
|
227
|
+
canRun: z3.boolean()
|
|
228
|
+
});
|
|
229
|
+
var LEDGER_KINDS = [
|
|
230
|
+
"SIGNUP_GRANT",
|
|
231
|
+
"RENEWAL_GRANT",
|
|
232
|
+
"RUN_DEBIT",
|
|
233
|
+
"RUN_REFUND",
|
|
234
|
+
"OVERAGE_DEBIT",
|
|
235
|
+
"ADMIN_ADJUST",
|
|
236
|
+
"EXPIRY"
|
|
237
|
+
];
|
|
238
|
+
var ledgerKindSchema = z3.enum(LEDGER_KINDS);
|
|
239
|
+
var usageEntrySchema = z3.object({
|
|
240
|
+
id: z3.string(),
|
|
241
|
+
kind: ledgerKindSchema,
|
|
242
|
+
delta: z3.number().int(),
|
|
243
|
+
runId: z3.string().nullable(),
|
|
244
|
+
batchId: z3.string().nullable(),
|
|
245
|
+
createdAt: z3.string()
|
|
246
|
+
});
|
|
247
|
+
var usageQuerySchema = listQuerySchema.extend({
|
|
248
|
+
/** Relative window ("30d", "24h", "15m", "2w") or an ISO-8601 date/datetime. */
|
|
249
|
+
since: z3.string().optional()
|
|
250
|
+
});
|
|
251
|
+
var usageListResponseSchema = listResponseSchema(usageEntrySchema);
|
|
252
|
+
|
|
253
|
+
// ../packages/api-schemas/src/brands.ts
|
|
254
|
+
var brands_exports = {};
|
|
255
|
+
__export(brands_exports, {
|
|
256
|
+
BRAND_KINDS: () => BRAND_KINDS,
|
|
257
|
+
BRAND_PACKAGE_ECOSYSTEMS: () => BRAND_PACKAGE_ECOSYSTEMS,
|
|
258
|
+
brandKindSchema: () => brandKindSchema,
|
|
259
|
+
brandListResponseSchema: () => brandListResponseSchema,
|
|
260
|
+
brandPackageEcosystemSchema: () => brandPackageEcosystemSchema,
|
|
261
|
+
brandPackageInputSchema: () => brandPackageInputSchema,
|
|
262
|
+
brandPackageSchema: () => brandPackageSchema,
|
|
263
|
+
brandSchema: () => brandSchema,
|
|
264
|
+
createBrandBodySchema: () => createBrandBodySchema,
|
|
265
|
+
listBrandsQuerySchema: () => listBrandsQuerySchema,
|
|
266
|
+
updateBrandBodySchema: () => updateBrandBodySchema
|
|
267
|
+
});
|
|
268
|
+
import { z as z4 } from "zod";
|
|
269
|
+
var BRAND_KINDS = ["OWNED", "COMPETITOR", "OTHER"];
|
|
270
|
+
var brandKindSchema = z4.enum(BRAND_KINDS);
|
|
271
|
+
var BRAND_PACKAGE_ECOSYSTEMS = [
|
|
272
|
+
"npm",
|
|
273
|
+
"pypi",
|
|
274
|
+
"cargo",
|
|
275
|
+
"go",
|
|
276
|
+
"rubygems",
|
|
277
|
+
"other"
|
|
278
|
+
];
|
|
279
|
+
var brandPackageEcosystemSchema = z4.enum(BRAND_PACKAGE_ECOSYSTEMS);
|
|
280
|
+
var brandPackageSchema = z4.object({
|
|
281
|
+
name: z4.string(),
|
|
282
|
+
ecosystem: z4.string().nullable()
|
|
283
|
+
});
|
|
284
|
+
var brandSchema = z4.object({
|
|
285
|
+
id: z4.string(),
|
|
286
|
+
name: z4.string(),
|
|
287
|
+
kind: brandKindSchema,
|
|
288
|
+
/** Provenance: MANUAL | BOOTSTRAP | OBSERVED (additive server-side). */
|
|
289
|
+
source: z4.string(),
|
|
290
|
+
aliases: z4.array(z4.string()),
|
|
291
|
+
websites: z4.array(z4.string()),
|
|
292
|
+
category: z4.string().nullable(),
|
|
293
|
+
packages: z4.array(brandPackageSchema),
|
|
294
|
+
createdAt: z4.string(),
|
|
295
|
+
updatedAt: z4.string()
|
|
296
|
+
});
|
|
297
|
+
var brandPackageInputSchema = z4.object({
|
|
298
|
+
name: z4.string().min(1),
|
|
299
|
+
/** Omitted/null = "not a package-manager entry". */
|
|
300
|
+
ecosystem: brandPackageEcosystemSchema.nullable().optional()
|
|
301
|
+
});
|
|
302
|
+
var createBrandBodySchema = z4.object({
|
|
303
|
+
name: z4.string().min(1),
|
|
304
|
+
kind: brandKindSchema,
|
|
305
|
+
aliases: z4.array(z4.string().min(1)).default([]),
|
|
306
|
+
websites: z4.array(z4.string().min(1)).default([]),
|
|
307
|
+
packages: z4.array(brandPackageInputSchema).default([])
|
|
308
|
+
});
|
|
309
|
+
var updateBrandBodySchema = z4.object({
|
|
310
|
+
name: z4.string().min(1).optional(),
|
|
311
|
+
kind: brandKindSchema.optional(),
|
|
312
|
+
aliases: z4.array(z4.string().min(1)).optional(),
|
|
313
|
+
websites: z4.array(z4.string().min(1)).optional(),
|
|
314
|
+
packages: z4.array(brandPackageInputSchema).optional()
|
|
315
|
+
});
|
|
316
|
+
var listBrandsQuerySchema = listQuerySchema.extend({
|
|
317
|
+
kind: brandKindSchema.optional()
|
|
318
|
+
});
|
|
319
|
+
var brandListResponseSchema = listResponseSchema(brandSchema);
|
|
320
|
+
|
|
321
|
+
// ../packages/api-schemas/src/keys.ts
|
|
322
|
+
var keys_exports = {};
|
|
323
|
+
__export(keys_exports, {
|
|
324
|
+
PROVIDER_TYPES: () => PROVIDER_TYPES,
|
|
325
|
+
providerKeyListResponseSchema: () => providerKeyListResponseSchema,
|
|
326
|
+
providerKeySchema: () => providerKeySchema,
|
|
327
|
+
providerTypeSchema: () => providerTypeSchema,
|
|
328
|
+
setProviderKeyBodySchema: () => setProviderKeyBodySchema,
|
|
329
|
+
setProviderKeyResponseSchema: () => setProviderKeyResponseSchema
|
|
330
|
+
});
|
|
331
|
+
import { z as z5 } from "zod";
|
|
332
|
+
var PROVIDER_TYPES = ["anthropic", "openai"];
|
|
333
|
+
var providerTypeSchema = z5.enum(PROVIDER_TYPES);
|
|
334
|
+
var setProviderKeyBodySchema = z5.object({
|
|
335
|
+
/** The raw provider API key. Verified + stored server-side (AWS Secrets
|
|
336
|
+
* Manager); never persisted in the app DB and never echoed back. */
|
|
337
|
+
key: z5.string().min(1)
|
|
338
|
+
});
|
|
339
|
+
var setProviderKeyResponseSchema = z5.object({
|
|
340
|
+
providerType: providerTypeSchema,
|
|
341
|
+
last4: z5.string()
|
|
342
|
+
});
|
|
343
|
+
var providerKeySchema = z5.object({
|
|
344
|
+
providerType: providerTypeSchema,
|
|
345
|
+
last4: z5.string(),
|
|
346
|
+
createdAt: z5.string(),
|
|
347
|
+
/** When the key was last saved/rotated. */
|
|
348
|
+
updatedAt: z5.string()
|
|
349
|
+
});
|
|
350
|
+
var providerKeyListResponseSchema = listResponseSchema(providerKeySchema);
|
|
351
|
+
|
|
352
|
+
// ../packages/api-schemas/src/me.ts
|
|
353
|
+
import { z as z6 } from "zod";
|
|
354
|
+
var meUserSchema = z6.object({
|
|
355
|
+
id: z6.string(),
|
|
356
|
+
email: z6.string().nullable(),
|
|
357
|
+
isSuperuser: z6.boolean()
|
|
358
|
+
});
|
|
359
|
+
var meOrgSchema = z6.object({
|
|
360
|
+
slug: z6.string(),
|
|
361
|
+
// Gauge's immutable Organization.public_id; null for LOCAL orgs.
|
|
362
|
+
publicId: z6.string().nullable(),
|
|
363
|
+
name: z6.string()
|
|
364
|
+
});
|
|
365
|
+
var meResponseSchema = z6.object({
|
|
366
|
+
user: meUserSchema,
|
|
367
|
+
// Membership-scoped for normal users; superusers see every org (mirrors the
|
|
368
|
+
// web tier's userOrgs).
|
|
369
|
+
orgs: z6.array(meOrgSchema)
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// ../packages/api-schemas/src/prompts.ts
|
|
373
|
+
import { z as z10 } from "zod";
|
|
374
|
+
|
|
375
|
+
// ../packages/api-schemas/src/schedules.ts
|
|
376
|
+
import { z as z7 } from "zod";
|
|
377
|
+
var AGENTS = ["CLAUDE_CODE", "CODEX_CLI", "CURSOR"];
|
|
378
|
+
var agentSchema = z7.enum(AGENTS);
|
|
379
|
+
var CADENCES = [
|
|
380
|
+
"NONE",
|
|
381
|
+
"DAILY",
|
|
382
|
+
"WEEKLY",
|
|
383
|
+
"MONTHLY",
|
|
384
|
+
"DAYS_PER_WEEK"
|
|
385
|
+
];
|
|
386
|
+
var cadenceSchema = z7.enum(CADENCES);
|
|
387
|
+
var daysPerWeekSchema = z7.number().int().min(1).max(7);
|
|
388
|
+
var scheduleSchema = z7.object({
|
|
389
|
+
id: z7.string(),
|
|
390
|
+
promptId: z7.string(),
|
|
391
|
+
/** Base repo to run against; null = the default scaffold. */
|
|
392
|
+
repoUrl: z7.string().nullable(),
|
|
393
|
+
/** Branch/ref; null = the repo's default branch. */
|
|
394
|
+
repoRef: z7.string().nullable(),
|
|
395
|
+
cadence: cadenceSchema,
|
|
396
|
+
/** Only set when cadence is DAYS_PER_WEEK (1–7). */
|
|
397
|
+
daysPerWeek: z7.number().int().nullable(),
|
|
398
|
+
samplesPerCycle: z7.number().int(),
|
|
399
|
+
agents: z7.array(agentSchema),
|
|
400
|
+
/** Pinned model id; null = the agent's default. */
|
|
401
|
+
model: z7.string().nullable(),
|
|
402
|
+
/** Cohort label for experiment analysis; null for ad-hoc schedules. */
|
|
403
|
+
experiment: z7.string().nullable(),
|
|
404
|
+
createdAt: z7.string(),
|
|
405
|
+
updatedAt: z7.string()
|
|
406
|
+
});
|
|
407
|
+
var scheduleListResponseSchema = listResponseSchema(scheduleSchema);
|
|
408
|
+
var createScheduleBodySchema = z7.object({
|
|
409
|
+
repoUrl: z7.string().min(1).nullish(),
|
|
410
|
+
repoRef: z7.string().min(1).nullish(),
|
|
411
|
+
cadence: cadenceSchema.default("NONE"),
|
|
412
|
+
daysPerWeek: daysPerWeekSchema.optional(),
|
|
413
|
+
agents: z7.array(agentSchema).min(1, "Select at least one agent")
|
|
414
|
+
});
|
|
415
|
+
var updateScheduleBodySchema = z7.object({
|
|
416
|
+
repoUrl: z7.string().min(1).nullish(),
|
|
417
|
+
repoRef: z7.string().min(1).nullish(),
|
|
418
|
+
cadence: cadenceSchema.optional(),
|
|
419
|
+
daysPerWeek: daysPerWeekSchema.optional(),
|
|
420
|
+
agents: z7.array(agentSchema).min(1, "Select at least one agent").optional()
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
// ../packages/api-schemas/src/tags.ts
|
|
424
|
+
import { z as z8 } from "zod";
|
|
425
|
+
var tagSchema = z8.object({
|
|
426
|
+
id: z8.string(),
|
|
427
|
+
name: z8.string(),
|
|
428
|
+
/** A Mantine palette name (e.g. "blue", "grape"); unknown values are
|
|
429
|
+
* normalized server-side to the default ("gray"). */
|
|
430
|
+
color: z8.string()
|
|
431
|
+
});
|
|
432
|
+
var createTagBodySchema = z8.object({
|
|
433
|
+
name: z8.string().min(1),
|
|
434
|
+
color: z8.string().optional()
|
|
435
|
+
});
|
|
436
|
+
var updateTagBodySchema = z8.object({
|
|
437
|
+
name: z8.string().min(1).optional(),
|
|
438
|
+
color: z8.string().optional()
|
|
439
|
+
});
|
|
440
|
+
var tagListResponseSchema = listResponseSchema(tagSchema);
|
|
441
|
+
|
|
442
|
+
// ../packages/api-schemas/src/topics.ts
|
|
443
|
+
import { z as z9 } from "zod";
|
|
444
|
+
var topicSchema = z9.object({
|
|
445
|
+
id: z9.string(),
|
|
446
|
+
name: z9.string()
|
|
447
|
+
});
|
|
448
|
+
var createTopicBodySchema = z9.object({
|
|
449
|
+
name: z9.string().min(1, "name is required")
|
|
450
|
+
});
|
|
451
|
+
var topicListResponseSchema = listResponseSchema(topicSchema);
|
|
452
|
+
|
|
453
|
+
// ../packages/api-schemas/src/prompts.ts
|
|
454
|
+
var SCENARIOS = ["GREENFIELD", "MIGRATION"];
|
|
455
|
+
var scenarioSchema = z10.enum(SCENARIOS);
|
|
456
|
+
var promptSummarySchema = z10.object({
|
|
457
|
+
id: z10.string(),
|
|
458
|
+
text: z10.string(),
|
|
459
|
+
scenario: scenarioSchema,
|
|
460
|
+
isBranded: z10.boolean(),
|
|
461
|
+
/** Battery-spec identity (idempotent applies); null for UI/CLI-created prompts. */
|
|
462
|
+
externalKey: z10.string().nullable(),
|
|
463
|
+
topic: topicSchema.nullable(),
|
|
464
|
+
tags: z10.array(tagSchema),
|
|
465
|
+
scheduleCount: z10.number().int(),
|
|
466
|
+
createdAt: z10.string()
|
|
467
|
+
});
|
|
468
|
+
var promptDetailSchema = promptSummarySchema.extend({
|
|
469
|
+
schedules: z10.array(scheduleSchema)
|
|
470
|
+
});
|
|
471
|
+
var promptListResponseSchema = listResponseSchema(promptSummarySchema);
|
|
472
|
+
var listPromptsQuerySchema = listQuerySchema.extend({
|
|
473
|
+
topic: z10.string().min(1).optional(),
|
|
474
|
+
tag: z10.string().min(1).optional(),
|
|
475
|
+
q: z10.string().min(1).optional()
|
|
476
|
+
});
|
|
477
|
+
var createPromptBodySchema = z10.object({
|
|
478
|
+
text: z10.string().min(1, "text is required"),
|
|
479
|
+
scenario: scenarioSchema.default("GREENFIELD"),
|
|
480
|
+
isBranded: z10.boolean().default(false),
|
|
481
|
+
topic: z10.string().min(1).optional(),
|
|
482
|
+
tags: z10.array(z10.string().min(1)).default([]),
|
|
483
|
+
schedules: z10.array(createScheduleBodySchema).default([])
|
|
484
|
+
});
|
|
485
|
+
var createPromptResponseSchema = promptDetailSchema.extend({
|
|
486
|
+
firstRunBlocked: z10.enum(["insufficient_credits", "needs_provider_key"]).optional()
|
|
487
|
+
});
|
|
488
|
+
var updatePromptBodySchema = z10.object({
|
|
489
|
+
text: z10.string().min(1).optional(),
|
|
490
|
+
isBranded: z10.boolean().optional(),
|
|
491
|
+
topicId: z10.string().min(1).nullish(),
|
|
492
|
+
tags: z10.array(z10.string().min(1)).optional()
|
|
493
|
+
});
|
|
494
|
+
var runPromptBodySchema = z10.object({
|
|
495
|
+
agents: z10.array(agentSchema).min(1).optional(),
|
|
496
|
+
scheduleIds: z10.array(z10.string().min(1)).min(1).optional()
|
|
497
|
+
});
|
|
498
|
+
var runPromptResponseSchema = z10.object({
|
|
499
|
+
batchIds: z10.array(z10.string()),
|
|
500
|
+
runIds: z10.array(z10.string()),
|
|
501
|
+
/** Present when SOME schedules hit a billing gate but at least one run was
|
|
502
|
+
* enqueued (mirrors the battery apply response; an all-blocked run is a
|
|
503
|
+
* 402 error instead). */
|
|
504
|
+
blocked: z10.enum(["insufficient_credits", "needs_provider_key"]).optional()
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
// ../packages/api-schemas/src/repos.ts
|
|
508
|
+
import { z as z11 } from "zod";
|
|
509
|
+
var REPO_SIZE_BUCKETS = [
|
|
510
|
+
"small",
|
|
511
|
+
"medium",
|
|
512
|
+
"large",
|
|
513
|
+
"xlarge"
|
|
514
|
+
];
|
|
515
|
+
var repoSizeBucketSchema = z11.enum(REPO_SIZE_BUCKETS);
|
|
516
|
+
var repoSchema = z11.object({
|
|
517
|
+
url: z11.string(),
|
|
518
|
+
name: z11.string(),
|
|
519
|
+
isSystem: z11.boolean(),
|
|
520
|
+
languages: z11.array(z11.string()),
|
|
521
|
+
frameworks: z11.array(z11.string()),
|
|
522
|
+
/** Faceted labels (complexity:L2, buildable:yes, stack:nextjs, …). */
|
|
523
|
+
tags: z11.array(z11.string()),
|
|
524
|
+
primaryLanguage: z11.string(),
|
|
525
|
+
sizeKb: z11.number(),
|
|
526
|
+
sizeBucket: repoSizeBucketSchema.nullable(),
|
|
527
|
+
stars: z11.number()
|
|
528
|
+
});
|
|
529
|
+
var repoListResponseSchema = listResponseSchema(repoSchema);
|
|
530
|
+
var addRepoBodySchema = z11.object({
|
|
531
|
+
url: z11.string().min(1, "url is required"),
|
|
532
|
+
/**
|
|
533
|
+
* Reserved (accepted, not yet persisted): the catalog keys repos by URL only
|
|
534
|
+
* — `fact_repo` has no ref column and metadata sync always reads the default
|
|
535
|
+
* branch. Kept in the wire format so `gauge repos add --ref` is stable once
|
|
536
|
+
* the catalog learns refs.
|
|
537
|
+
*/
|
|
538
|
+
ref: z11.string().min(1).optional(),
|
|
539
|
+
/** Display name; defaults to the `owner/repo` label of the URL. */
|
|
540
|
+
name: z11.string().min(1).optional(),
|
|
541
|
+
/** Manual override; defaults to frameworks detected from the repo's manifests. */
|
|
542
|
+
frameworks: z11.array(z11.string()).optional(),
|
|
543
|
+
tags: z11.array(z11.string()).optional()
|
|
544
|
+
});
|
|
545
|
+
var updateRepoBodySchema = z11.object({
|
|
546
|
+
url: z11.string().min(1, "url is required"),
|
|
547
|
+
name: z11.string().min(1).optional(),
|
|
548
|
+
frameworks: z11.array(z11.string()).optional(),
|
|
549
|
+
tags: z11.array(z11.string()).optional()
|
|
550
|
+
});
|
|
551
|
+
var resyncRepoBodySchema = z11.object({
|
|
552
|
+
url: z11.string().min(1, "url is required")
|
|
553
|
+
});
|
|
554
|
+
var deleteRepoQuerySchema = z11.object({
|
|
555
|
+
url: z11.string().min(1, "url is required")
|
|
556
|
+
});
|
|
557
|
+
var repoSyncResultSchema = z11.object({
|
|
558
|
+
repo: repoSchema,
|
|
559
|
+
synced: z11.boolean()
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// ../packages/api-schemas/src/runs.ts
|
|
563
|
+
import { z as z12 } from "zod";
|
|
564
|
+
var RUN_STATUSES = [
|
|
565
|
+
"QUEUED",
|
|
566
|
+
"CLAIMED",
|
|
567
|
+
"PROVISIONING",
|
|
568
|
+
"RUNNING",
|
|
569
|
+
"SUCCEEDED",
|
|
570
|
+
"FAILED",
|
|
571
|
+
"TIMED_OUT",
|
|
572
|
+
"CANCELED"
|
|
573
|
+
];
|
|
574
|
+
var runStatusSchema = z12.enum(RUN_STATUSES);
|
|
575
|
+
var AGENTS2 = ["CLAUDE_CODE", "CODEX_CLI", "CURSOR"];
|
|
576
|
+
var agentSchema2 = z12.enum(AGENTS2);
|
|
577
|
+
var runStatusFilterSchema = z12.string().transform(
|
|
578
|
+
(raw) => raw.split(",").map((s) => s.trim().toUpperCase()).filter(Boolean)
|
|
579
|
+
).pipe(z12.array(runStatusSchema).min(1));
|
|
580
|
+
var runListQuerySchema = listQuerySchema.extend({
|
|
581
|
+
status: runStatusFilterSchema.optional(),
|
|
582
|
+
batch: z12.string().optional(),
|
|
583
|
+
prompt: z12.string().optional(),
|
|
584
|
+
experiment: z12.string().optional(),
|
|
585
|
+
since: z12.string().optional()
|
|
586
|
+
});
|
|
587
|
+
var runListItemSchema = z12.object({
|
|
588
|
+
id: z12.string(),
|
|
589
|
+
status: runStatusSchema,
|
|
590
|
+
agent: agentSchema2,
|
|
591
|
+
model: z12.string(),
|
|
592
|
+
promptId: z12.string(),
|
|
593
|
+
/** First ~80 chars of the batch's prompt snapshot, whitespace-collapsed. */
|
|
594
|
+
promptSnippet: z12.string(),
|
|
595
|
+
batchId: z12.string(),
|
|
596
|
+
experiment: z12.string().nullable(),
|
|
597
|
+
turns: z12.number().int().nullable(),
|
|
598
|
+
usdCost: z12.number().nullable(),
|
|
599
|
+
durationMs: z12.number().int().nullable(),
|
|
600
|
+
exitReason: z12.string().nullable(),
|
|
601
|
+
createdAt: z12.string(),
|
|
602
|
+
startedAt: z12.string().nullable(),
|
|
603
|
+
finishedAt: z12.string().nullable()
|
|
604
|
+
});
|
|
605
|
+
var runListResponseSchema = listResponseSchema(runListItemSchema);
|
|
606
|
+
var runDetailSchema = runListItemSchema.omit({ promptSnippet: true }).extend({
|
|
607
|
+
promptText: z12.string(),
|
|
608
|
+
attempt: z12.number().int(),
|
|
609
|
+
cancelRequested: z12.boolean(),
|
|
610
|
+
failureReason: z12.string().nullable(),
|
|
611
|
+
repoUrl: z12.string().nullable(),
|
|
612
|
+
repoRef: z12.string().nullable(),
|
|
613
|
+
scenario: z12.string(),
|
|
614
|
+
funding: z12.string(),
|
|
615
|
+
/** Raw harness usage write-back (token counts etc.); shape may evolve. */
|
|
616
|
+
usage: z12.unknown().nullable(),
|
|
617
|
+
/** Artifact pointer: a working-tree diff exists at GET /runs/{id}/diff. */
|
|
618
|
+
hasDiff: z12.boolean(),
|
|
619
|
+
claimedAt: z12.string().nullable()
|
|
620
|
+
});
|
|
621
|
+
var cancelRunResponseSchema = z12.object({
|
|
622
|
+
id: z12.string(),
|
|
623
|
+
status: runStatusSchema,
|
|
624
|
+
cancelRequested: z12.boolean(),
|
|
625
|
+
note: z12.string().optional()
|
|
626
|
+
});
|
|
627
|
+
var runEventSchema = z12.looseObject({
|
|
628
|
+
run_id: z12.string(),
|
|
629
|
+
seq: z12.number(),
|
|
630
|
+
turn: z12.number(),
|
|
631
|
+
ts: z12.string(),
|
|
632
|
+
type: z12.string()
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
// ../packages/api-schemas/src/tokens.ts
|
|
636
|
+
import { z as z13 } from "zod";
|
|
637
|
+
var apiTokenSchema = z13.object({
|
|
638
|
+
id: z13.string(),
|
|
639
|
+
name: z13.string(),
|
|
640
|
+
/** Last 4 chars of the plaintext, for recognizability (non-secret). */
|
|
641
|
+
last4: z13.string(),
|
|
642
|
+
expiresAt: z13.iso.datetime().nullable(),
|
|
643
|
+
lastUsedAt: z13.iso.datetime().nullable(),
|
|
644
|
+
createdAt: z13.iso.datetime(),
|
|
645
|
+
revokedAt: z13.iso.datetime().nullable()
|
|
646
|
+
});
|
|
647
|
+
var createTokenRequestSchema = z13.object({
|
|
648
|
+
name: z13.string().min(1).max(100),
|
|
649
|
+
/** ISO-8601 datetime; omit for no expiry. */
|
|
650
|
+
expiresAt: z13.iso.datetime({ offset: true }).optional()
|
|
651
|
+
});
|
|
652
|
+
var createTokenResponseSchema = apiTokenSchema.extend({
|
|
653
|
+
token: z13.string()
|
|
654
|
+
});
|
|
655
|
+
var listTokensResponseSchema = listResponseSchema(apiTokenSchema);
|
|
656
|
+
|
|
657
|
+
// src/version.ts
|
|
658
|
+
import { createRequire } from "module";
|
|
659
|
+
var CLI_VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
660
|
+
|
|
661
|
+
// src/lib/config.ts
|
|
662
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
663
|
+
import { homedir } from "os";
|
|
664
|
+
import { join } from "path";
|
|
665
|
+
var CONFIG_KEYS = ["defaultOrg", "apiUrl"];
|
|
666
|
+
function configDir() {
|
|
667
|
+
const base = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
|
|
668
|
+
return join(base, "gauge");
|
|
669
|
+
}
|
|
670
|
+
function configPath() {
|
|
671
|
+
return join(configDir(), "config.json");
|
|
672
|
+
}
|
|
673
|
+
function readConfig() {
|
|
674
|
+
let raw;
|
|
675
|
+
try {
|
|
676
|
+
raw = readFileSync(configPath(), "utf8");
|
|
677
|
+
} catch {
|
|
678
|
+
return {};
|
|
679
|
+
}
|
|
680
|
+
try {
|
|
681
|
+
const parsed = JSON.parse(raw);
|
|
682
|
+
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
683
|
+
} catch {
|
|
684
|
+
return {};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function writeConfig(config) {
|
|
688
|
+
mkdirSync(configDir(), { recursive: true });
|
|
689
|
+
writeFileSync(configPath(), `${JSON.stringify(config, null, 2)}
|
|
690
|
+
`, "utf8");
|
|
691
|
+
}
|
|
692
|
+
function updateConfig(patch) {
|
|
693
|
+
const next = { ...readConfig(), ...patch };
|
|
694
|
+
for (const key of CONFIG_KEYS) {
|
|
695
|
+
if (next[key] === void 0) delete next[key];
|
|
696
|
+
}
|
|
697
|
+
writeConfig(next);
|
|
698
|
+
return next;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// src/lib/credentials.ts
|
|
702
|
+
import {
|
|
703
|
+
chmodSync,
|
|
704
|
+
mkdirSync as mkdirSync2,
|
|
705
|
+
readFileSync as readFileSync2,
|
|
706
|
+
rmSync,
|
|
707
|
+
writeFileSync as writeFileSync2
|
|
708
|
+
} from "fs";
|
|
709
|
+
import { join as join2 } from "path";
|
|
710
|
+
function credentialsPath() {
|
|
711
|
+
return join2(configDir(), "credentials");
|
|
712
|
+
}
|
|
713
|
+
function resolveToken() {
|
|
714
|
+
const fromEnv = process.env.GAUGE_API_TOKEN?.trim();
|
|
715
|
+
if (fromEnv) return fromEnv;
|
|
716
|
+
try {
|
|
717
|
+
const stored = readFileSync2(credentialsPath(), "utf8").trim();
|
|
718
|
+
return stored || null;
|
|
719
|
+
} catch {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function saveToken(token) {
|
|
724
|
+
mkdirSync2(configDir(), { recursive: true, mode: 448 });
|
|
725
|
+
writeFileSync2(credentialsPath(), `${token.trim()}
|
|
726
|
+
`, {
|
|
727
|
+
encoding: "utf8",
|
|
728
|
+
mode: 384
|
|
729
|
+
});
|
|
730
|
+
chmodSync(credentialsPath(), 384);
|
|
731
|
+
}
|
|
732
|
+
function clearToken() {
|
|
733
|
+
rmSync(credentialsPath(), { force: true });
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// src/lib/client.ts
|
|
737
|
+
var DEFAULT_API_URL = "https://agents.withgauge.com";
|
|
738
|
+
var USER_AGENT = `gauge-cli/${CLI_VERSION}`;
|
|
739
|
+
function exitCodeFor(code) {
|
|
740
|
+
switch (code) {
|
|
741
|
+
case "usage":
|
|
742
|
+
case "invalid_request":
|
|
743
|
+
return 2;
|
|
744
|
+
case "insufficient_credits":
|
|
745
|
+
case "needs_provider_key":
|
|
746
|
+
return 3;
|
|
747
|
+
default:
|
|
748
|
+
return 1;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
var CliError = class extends Error {
|
|
752
|
+
code;
|
|
753
|
+
exitCode;
|
|
754
|
+
constructor(code, message) {
|
|
755
|
+
super(message);
|
|
756
|
+
this.name = "CliError";
|
|
757
|
+
this.code = code;
|
|
758
|
+
this.exitCode = exitCodeFor(code);
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
function resolveBaseUrl() {
|
|
762
|
+
return process.env.GAUGE_API_URL || readConfig().apiUrl || DEFAULT_API_URL;
|
|
763
|
+
}
|
|
764
|
+
var ApiClient = class {
|
|
765
|
+
baseUrl;
|
|
766
|
+
token;
|
|
767
|
+
constructor(options = {}) {
|
|
768
|
+
this.baseUrl = (options.baseUrl ?? resolveBaseUrl()).replace(/\/+$/, "");
|
|
769
|
+
this.token = options.token !== void 0 ? options.token : resolveToken();
|
|
770
|
+
}
|
|
771
|
+
async get(path, query) {
|
|
772
|
+
return this.request("GET", path, { query });
|
|
773
|
+
}
|
|
774
|
+
async post(path, body, query) {
|
|
775
|
+
return this.request("POST", path, { body, query });
|
|
776
|
+
}
|
|
777
|
+
async patch(path, body, query) {
|
|
778
|
+
return this.request("PATCH", path, { body, query });
|
|
779
|
+
}
|
|
780
|
+
async put(path, body, query) {
|
|
781
|
+
return this.request("PUT", path, { body, query });
|
|
782
|
+
}
|
|
783
|
+
async delete(path, query) {
|
|
784
|
+
return this.request("DELETE", path, { query });
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* Tail a server-sent-event endpoint (e.g. GET /runs/{id}/events), invoking
|
|
788
|
+
* `onEvent` per data frame. Resolves when the server closes the stream or
|
|
789
|
+
* `options.signal` aborts.
|
|
790
|
+
*/
|
|
791
|
+
async sse(path, onEvent, options = {}) {
|
|
792
|
+
const res = await this.fetch(this.url(path, options.query), {
|
|
793
|
+
method: "GET",
|
|
794
|
+
headers: this.headers({ Accept: "text/event-stream" }),
|
|
795
|
+
signal: options.signal
|
|
796
|
+
});
|
|
797
|
+
if (!res.ok) throw await this.toError(res);
|
|
798
|
+
if (!res.body) {
|
|
799
|
+
throw new CliError("internal", "Event stream had no response body");
|
|
800
|
+
}
|
|
801
|
+
const reader = res.body.getReader();
|
|
802
|
+
const decoder = new TextDecoder();
|
|
803
|
+
let buffer = "";
|
|
804
|
+
try {
|
|
805
|
+
while (true) {
|
|
806
|
+
const { done, value } = await reader.read();
|
|
807
|
+
if (done) break;
|
|
808
|
+
buffer += decoder.decode(value, { stream: true });
|
|
809
|
+
let boundary = buffer.indexOf("\n\n");
|
|
810
|
+
while (boundary !== -1) {
|
|
811
|
+
const raw = buffer.slice(0, boundary);
|
|
812
|
+
const event = parseSseFrame(raw);
|
|
813
|
+
buffer = buffer.slice(boundary + 2);
|
|
814
|
+
if (event) onEvent(event);
|
|
815
|
+
else if (raw.trimStart().startsWith(":")) options.onComment?.();
|
|
816
|
+
boundary = buffer.indexOf("\n\n");
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
} catch (error) {
|
|
820
|
+
if (error instanceof Error && error.name === "AbortError") return;
|
|
821
|
+
throw new CliError("network", `Event stream failed: ${messageOf(error)}`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
url(path, query) {
|
|
825
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
826
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
827
|
+
if (value !== void 0) url.searchParams.set(key, String(value));
|
|
828
|
+
}
|
|
829
|
+
return url;
|
|
830
|
+
}
|
|
831
|
+
headers(extra) {
|
|
832
|
+
if (!this.token) {
|
|
833
|
+
throw new CliError(
|
|
834
|
+
"unauthorized",
|
|
835
|
+
"Not logged in. Run `gauge auth login` or set GAUGE_API_TOKEN."
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
return {
|
|
839
|
+
Authorization: `Bearer ${this.token}`,
|
|
840
|
+
"User-Agent": USER_AGENT,
|
|
841
|
+
Accept: "application/json",
|
|
842
|
+
...extra
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
async fetch(url, init) {
|
|
846
|
+
try {
|
|
847
|
+
return await fetch(url, init);
|
|
848
|
+
} catch (error) {
|
|
849
|
+
if (error instanceof Error && error.name === "AbortError") throw error;
|
|
850
|
+
throw new CliError(
|
|
851
|
+
"network",
|
|
852
|
+
`Request to ${url.host} failed: ${messageOf(error)}`
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
async request(method, path, options = {}) {
|
|
857
|
+
const headers = this.headers(
|
|
858
|
+
options.body === void 0 ? void 0 : { "Content-Type": "application/json" }
|
|
859
|
+
);
|
|
860
|
+
const res = await this.fetch(this.url(path, options.query), {
|
|
861
|
+
method,
|
|
862
|
+
headers,
|
|
863
|
+
body: options.body === void 0 ? void 0 : JSON.stringify(options.body)
|
|
864
|
+
});
|
|
865
|
+
if (!res.ok) throw await this.toError(res);
|
|
866
|
+
if (res.status === 204) return void 0;
|
|
867
|
+
const text = await res.text();
|
|
868
|
+
if (!text) return void 0;
|
|
869
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
870
|
+
if (contentType.includes("application/json")) {
|
|
871
|
+
return JSON.parse(text);
|
|
872
|
+
}
|
|
873
|
+
return text;
|
|
874
|
+
}
|
|
875
|
+
async toError(res) {
|
|
876
|
+
try {
|
|
877
|
+
const parsed = errorEnvelopeSchema.safeParse(await res.clone().json());
|
|
878
|
+
if (parsed.success) {
|
|
879
|
+
return new CliError(parsed.data.error.code, parsed.data.error.message);
|
|
880
|
+
}
|
|
881
|
+
} catch {
|
|
882
|
+
}
|
|
883
|
+
return new CliError(
|
|
884
|
+
codeForStatus(res.status),
|
|
885
|
+
`API request failed: HTTP ${res.status} ${res.statusText}`.trim()
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
function createClient() {
|
|
890
|
+
return new ApiClient();
|
|
891
|
+
}
|
|
892
|
+
function codeForStatus(status) {
|
|
893
|
+
switch (status) {
|
|
894
|
+
case 400:
|
|
895
|
+
return "invalid_request";
|
|
896
|
+
case 401:
|
|
897
|
+
return "unauthorized";
|
|
898
|
+
case 402:
|
|
899
|
+
return "insufficient_credits";
|
|
900
|
+
case 403:
|
|
901
|
+
return "forbidden";
|
|
902
|
+
case 404:
|
|
903
|
+
return "not_found";
|
|
904
|
+
case 409:
|
|
905
|
+
return "conflict";
|
|
906
|
+
case 429:
|
|
907
|
+
return "rate_limited";
|
|
908
|
+
default:
|
|
909
|
+
return "internal";
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
function parseSseFrame(frame) {
|
|
913
|
+
let eventName;
|
|
914
|
+
const dataLines = [];
|
|
915
|
+
for (const line of frame.split("\n")) {
|
|
916
|
+
if (line.startsWith(":")) continue;
|
|
917
|
+
if (line.startsWith("event:")) {
|
|
918
|
+
eventName = line.slice("event:".length).trim();
|
|
919
|
+
} else if (line.startsWith("data:")) {
|
|
920
|
+
dataLines.push(line.slice("data:".length).replace(/^ /, ""));
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (dataLines.length === 0) return null;
|
|
924
|
+
return { event: eventName, data: dataLines.join("\n") };
|
|
925
|
+
}
|
|
926
|
+
function messageOf(error) {
|
|
927
|
+
if (error instanceof Error) {
|
|
928
|
+
const cause = error.cause instanceof Error ? `: ${error.cause.message}` : "";
|
|
929
|
+
return `${error.message}${cause}`;
|
|
930
|
+
}
|
|
931
|
+
return String(error);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/lib/context.ts
|
|
935
|
+
function resolveOrg(command) {
|
|
936
|
+
const { org } = command.optsWithGlobals();
|
|
937
|
+
if (org) return org;
|
|
938
|
+
const { defaultOrg } = readConfig();
|
|
939
|
+
if (defaultOrg) return defaultOrg;
|
|
940
|
+
throw new CliError(
|
|
941
|
+
"usage",
|
|
942
|
+
"No organization selected. Pass --org <org> or set a default with `gauge orgs use <slug>`."
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
function resolveOutput(command) {
|
|
946
|
+
const { output } = command.optsWithGlobals();
|
|
947
|
+
return output === "json" ? "json" : "table";
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// src/lib/output.ts
|
|
951
|
+
var OUTPUT_FORMATS = ["table", "json"];
|
|
952
|
+
function printJson(value) {
|
|
953
|
+
console.log(JSON.stringify(value, null, 2));
|
|
954
|
+
}
|
|
955
|
+
function printItems(items, format, columns) {
|
|
956
|
+
if (format === "json") {
|
|
957
|
+
printJson(items);
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (items.length === 0) {
|
|
961
|
+
console.log("(no results)");
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const cols = columns ?? Object.keys(items[0]).map((key) => ({ key }));
|
|
965
|
+
const headers = cols.map((col) => col.header ?? col.key);
|
|
966
|
+
const rows = items.map((item) => cols.map((col) => cell(item[col.key])));
|
|
967
|
+
const widths = headers.map(
|
|
968
|
+
(header, i) => Math.max(header.length, ...rows.map((row) => row[i].length))
|
|
969
|
+
);
|
|
970
|
+
const line = (values) => values.map((value, i) => value.padEnd(widths[i])).join(" ").trimEnd();
|
|
971
|
+
console.log(line(headers));
|
|
972
|
+
for (const row of rows) console.log(line(row));
|
|
973
|
+
}
|
|
974
|
+
function cell(value) {
|
|
975
|
+
if (value === null || value === void 0 || value === "") return "\u2014";
|
|
976
|
+
if (value instanceof Date) return value.toISOString();
|
|
977
|
+
if (typeof value === "object") return JSON.stringify(value);
|
|
978
|
+
return String(value);
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// src/commands/apply.ts
|
|
982
|
+
function readSpec(path) {
|
|
983
|
+
let raw;
|
|
984
|
+
try {
|
|
985
|
+
raw = readFileSync3(path, "utf8");
|
|
986
|
+
} catch (e) {
|
|
987
|
+
throw new CliError(
|
|
988
|
+
"usage",
|
|
989
|
+
`cannot read spec file ${path}: ${e instanceof Error ? e.message : String(e)}`
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
let parsed;
|
|
993
|
+
try {
|
|
994
|
+
parsed = JSON.parse(raw);
|
|
995
|
+
} catch (e) {
|
|
996
|
+
throw new CliError(
|
|
997
|
+
"invalid_request",
|
|
998
|
+
`spec is not valid JSON: ${e instanceof Error ? e.message : String(e)}`
|
|
999
|
+
);
|
|
1000
|
+
}
|
|
1001
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1002
|
+
throw new CliError("invalid_request", "spec must be a JSON object");
|
|
1003
|
+
}
|
|
1004
|
+
return parsed;
|
|
1005
|
+
}
|
|
1006
|
+
function resolveApplyOrg(command, spec) {
|
|
1007
|
+
const { org } = command.optsWithGlobals();
|
|
1008
|
+
if (org) return org;
|
|
1009
|
+
if (typeof spec.org === "string" && spec.org) return spec.org;
|
|
1010
|
+
const { defaultOrg } = readConfig();
|
|
1011
|
+
if (defaultOrg) return defaultOrg;
|
|
1012
|
+
throw new CliError(
|
|
1013
|
+
"usage",
|
|
1014
|
+
"No organization selected. Pass --org <org>, set `org` in the spec, or run `gauge orgs use <slug>`."
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
function repoLabel(repoUrl, repoRef) {
|
|
1018
|
+
if (!repoUrl) return "greenfield";
|
|
1019
|
+
return repoRef ? `${repoUrl}@${repoRef}` : repoUrl;
|
|
1020
|
+
}
|
|
1021
|
+
function printPlan(plan) {
|
|
1022
|
+
console.log(
|
|
1023
|
+
`Plan for org ${plan.org}` + (plan.experiment ? ` (experiment: ${plan.experiment})` : "")
|
|
1024
|
+
);
|
|
1025
|
+
for (const prompt of plan.prompts) {
|
|
1026
|
+
console.log(` ${prompt.action} prompt "${prompt.key}"`);
|
|
1027
|
+
for (const s of prompt.schedules) {
|
|
1028
|
+
console.log(
|
|
1029
|
+
` ${s.action} schedule ${repoLabel(s.repoUrl, s.repoRef)}` + (plan.expand ? ` \u2192 ${s.runs} run(s)` : "")
|
|
1030
|
+
);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
const sum = plan.summary;
|
|
1034
|
+
console.log(
|
|
1035
|
+
`Prompts: ${sum.promptsToCreate} to create, ${sum.promptsToUpdate} to update; schedules: ${sum.schedulesToCreate} to create, ${sum.schedulesToUpdate} to update`
|
|
1036
|
+
);
|
|
1037
|
+
if (plan.expand) {
|
|
1038
|
+
const byAgent = Object.entries(sum.runsByAgent).map(([agent, n]) => `${agent}: ${n}`).join(", ");
|
|
1039
|
+
console.log(
|
|
1040
|
+
`Runs to enqueue: ${sum.runsToEnqueue}${byAgent ? ` (${byAgent})` : ""} \u2014 estimated ${sum.estimatedCredits} credits`
|
|
1041
|
+
);
|
|
1042
|
+
} else {
|
|
1043
|
+
console.log(
|
|
1044
|
+
'No runs will be enqueued (config only \u2014 set "expand": true to run).'
|
|
1045
|
+
);
|
|
1046
|
+
}
|
|
1047
|
+
console.log("Dry run \u2014 nothing was applied.");
|
|
1048
|
+
}
|
|
1049
|
+
function printApplied(res) {
|
|
1050
|
+
console.log(
|
|
1051
|
+
`Applied${res.experiment ? ` (experiment: ${res.experiment})` : ""} \u2014 ${res.batchIds.length} batch(es), ${res.runIds.length} run(s) enqueued`
|
|
1052
|
+
);
|
|
1053
|
+
for (const id of res.runIds) console.log(` run ${id}`);
|
|
1054
|
+
if (res.runIds.length > 0) {
|
|
1055
|
+
console.error(`(watch one live: gauge runs watch ${res.runIds[0]})`);
|
|
1056
|
+
}
|
|
1057
|
+
if (res.blocked) {
|
|
1058
|
+
console.error(
|
|
1059
|
+
`warning: some schedules were not expanded (${res.blocked}) \u2014 re-apply after resolving to enqueue the rest`
|
|
1060
|
+
);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
function register(program2) {
|
|
1064
|
+
program2.command("apply").description("Apply a declarative battery spec (see specs/battery.md)").requiredOption("-f, --file <path>", "battery spec JSON file").option("--dry-run", "print the plan without applying").action(
|
|
1065
|
+
async (opts, command) => {
|
|
1066
|
+
const spec = readSpec(opts.file);
|
|
1067
|
+
const org = resolveApplyOrg(command, spec);
|
|
1068
|
+
const output = resolveOutput(command);
|
|
1069
|
+
const result = await createClient().post(
|
|
1070
|
+
`/api/v1/orgs/${encodeURIComponent(org)}/batteries`,
|
|
1071
|
+
spec,
|
|
1072
|
+
opts.dryRun ? { dryRun: 1 } : void 0
|
|
1073
|
+
);
|
|
1074
|
+
if (output === "json") {
|
|
1075
|
+
printJson(result);
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
if (opts.dryRun) printPlan(result);
|
|
1079
|
+
else printApplied(result);
|
|
1080
|
+
}
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// src/commands/auth.ts
|
|
1085
|
+
var auth_exports = {};
|
|
1086
|
+
__export(auth_exports, {
|
|
1087
|
+
register: () => register2
|
|
1088
|
+
});
|
|
1089
|
+
import { spawn } from "child_process";
|
|
1090
|
+
import { randomBytes } from "crypto";
|
|
1091
|
+
import { createServer } from "http";
|
|
1092
|
+
import { hostname } from "os";
|
|
1093
|
+
|
|
1094
|
+
// src/lib/paginate.ts
|
|
1095
|
+
async function fetchAllPages(client, path, query = {}) {
|
|
1096
|
+
const items = [];
|
|
1097
|
+
let cursor;
|
|
1098
|
+
do {
|
|
1099
|
+
const page = await client.get(path, {
|
|
1100
|
+
limit: 200,
|
|
1101
|
+
...query,
|
|
1102
|
+
cursor
|
|
1103
|
+
});
|
|
1104
|
+
items.push(...page.items);
|
|
1105
|
+
cursor = page.nextCursor;
|
|
1106
|
+
} while (cursor);
|
|
1107
|
+
return items;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
// src/commands/auth.ts
|
|
1111
|
+
var LOGIN_TIMEOUT_MS = 2 * 60 * 1e3;
|
|
1112
|
+
var TOKEN_PREFIX = "gauge_";
|
|
1113
|
+
var MAX_CALLBACK_BODY_BYTES = 16 * 1024;
|
|
1114
|
+
function tokenSource() {
|
|
1115
|
+
if (process.env.GAUGE_API_TOKEN?.trim()) return "env";
|
|
1116
|
+
return resolveToken() ? "file" : "none";
|
|
1117
|
+
}
|
|
1118
|
+
async function fetchMe(client) {
|
|
1119
|
+
return client.get("/api/v1/me");
|
|
1120
|
+
}
|
|
1121
|
+
function printIdentity(me) {
|
|
1122
|
+
const orgs = me.orgs.map((o) => o.slug).join(", ") || "(none)";
|
|
1123
|
+
const su = me.user.isSuperuser ? " (superuser)" : "";
|
|
1124
|
+
console.log(`Logged in as ${me.user.email ?? me.user.id}${su}`);
|
|
1125
|
+
console.log(`Orgs: ${orgs}`);
|
|
1126
|
+
}
|
|
1127
|
+
function openBrowser(url) {
|
|
1128
|
+
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
1129
|
+
try {
|
|
1130
|
+
const child = spawn(cmd, args, {
|
|
1131
|
+
stdio: "ignore",
|
|
1132
|
+
detached: true
|
|
1133
|
+
});
|
|
1134
|
+
child.on("error", () => {
|
|
1135
|
+
});
|
|
1136
|
+
child.unref();
|
|
1137
|
+
} catch {
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
async function browserLogin() {
|
|
1141
|
+
const state = randomBytes(16).toString("base64url");
|
|
1142
|
+
const server = createServer();
|
|
1143
|
+
const tokenPromise = new Promise((resolve, reject) => {
|
|
1144
|
+
server.on("request", (req, res) => {
|
|
1145
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
1146
|
+
if (url.pathname !== "/callback") {
|
|
1147
|
+
res.writeHead(404).end();
|
|
1148
|
+
return;
|
|
1149
|
+
}
|
|
1150
|
+
if (req.method === "OPTIONS") {
|
|
1151
|
+
res.writeHead(204, {
|
|
1152
|
+
"access-control-allow-origin": "*",
|
|
1153
|
+
"access-control-allow-methods": "POST",
|
|
1154
|
+
"access-control-allow-headers": "content-type",
|
|
1155
|
+
"access-control-allow-private-network": "true"
|
|
1156
|
+
}).end();
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
if (req.method !== "POST") {
|
|
1160
|
+
res.writeHead(405, { allow: "POST" }).end();
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
let body = "";
|
|
1164
|
+
let overflowed = false;
|
|
1165
|
+
req.on("data", (chunk) => {
|
|
1166
|
+
if (overflowed) return;
|
|
1167
|
+
body += chunk;
|
|
1168
|
+
if (body.length > MAX_CALLBACK_BODY_BYTES) {
|
|
1169
|
+
overflowed = true;
|
|
1170
|
+
body = "";
|
|
1171
|
+
res.writeHead(413, {
|
|
1172
|
+
"content-type": "text/plain; charset=utf-8",
|
|
1173
|
+
"access-control-allow-origin": "*"
|
|
1174
|
+
});
|
|
1175
|
+
res.end("Callback body too large.");
|
|
1176
|
+
}
|
|
1177
|
+
});
|
|
1178
|
+
req.on("end", () => {
|
|
1179
|
+
if (overflowed) return;
|
|
1180
|
+
const params = new URLSearchParams(body);
|
|
1181
|
+
const token = params.get("token");
|
|
1182
|
+
if (params.get("state") !== state || !token) {
|
|
1183
|
+
res.writeHead(400, {
|
|
1184
|
+
"content-type": "text/plain; charset=utf-8",
|
|
1185
|
+
"access-control-allow-origin": "*"
|
|
1186
|
+
});
|
|
1187
|
+
res.end(
|
|
1188
|
+
"Invalid callback (state mismatch). Retry `gauge auth login`."
|
|
1189
|
+
);
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
res.writeHead(200, {
|
|
1193
|
+
"content-type": "text/plain; charset=utf-8",
|
|
1194
|
+
"access-control-allow-origin": "*"
|
|
1195
|
+
});
|
|
1196
|
+
res.end("Authorized \u2014 return to your terminal.");
|
|
1197
|
+
resolve(token);
|
|
1198
|
+
});
|
|
1199
|
+
});
|
|
1200
|
+
server.on(
|
|
1201
|
+
"error",
|
|
1202
|
+
(e) => reject(new CliError("network", `Loopback listener failed: ${e.message}`))
|
|
1203
|
+
);
|
|
1204
|
+
});
|
|
1205
|
+
await new Promise((resolve) => {
|
|
1206
|
+
server.listen(0, "127.0.0.1", resolve);
|
|
1207
|
+
});
|
|
1208
|
+
const address = server.address();
|
|
1209
|
+
if (!address || typeof address === "string") {
|
|
1210
|
+
server.close();
|
|
1211
|
+
throw new CliError("internal", "Could not determine the listener port");
|
|
1212
|
+
}
|
|
1213
|
+
const base = resolveBaseUrl().replace(/\/+$/, "");
|
|
1214
|
+
const authorizeUrl = new URL(`${base}/cli/authorize`);
|
|
1215
|
+
authorizeUrl.searchParams.set("port", String(address.port));
|
|
1216
|
+
authorizeUrl.searchParams.set("state", state);
|
|
1217
|
+
authorizeUrl.searchParams.set("name", hostname() || "this device");
|
|
1218
|
+
console.log("Opening your browser to authorize the Gauge CLI\u2026");
|
|
1219
|
+
console.log(`If it doesn't open, visit:
|
|
1220
|
+
|
|
1221
|
+
${authorizeUrl}
|
|
1222
|
+
`);
|
|
1223
|
+
openBrowser(authorizeUrl.toString());
|
|
1224
|
+
let timer;
|
|
1225
|
+
try {
|
|
1226
|
+
const token = await Promise.race([
|
|
1227
|
+
tokenPromise,
|
|
1228
|
+
new Promise((_, reject) => {
|
|
1229
|
+
timer = setTimeout(() => {
|
|
1230
|
+
reject(
|
|
1231
|
+
new CliError(
|
|
1232
|
+
"network",
|
|
1233
|
+
"Timed out waiting for browser authorization (2 minutes). Retry, or use `gauge auth login --with-token`."
|
|
1234
|
+
)
|
|
1235
|
+
);
|
|
1236
|
+
}, LOGIN_TIMEOUT_MS);
|
|
1237
|
+
})
|
|
1238
|
+
]);
|
|
1239
|
+
const me = await fetchMe(new ApiClient({ token }));
|
|
1240
|
+
saveToken(token);
|
|
1241
|
+
console.log(`Token saved to ${credentialsPath()}`);
|
|
1242
|
+
printIdentity(me);
|
|
1243
|
+
} finally {
|
|
1244
|
+
if (timer) clearTimeout(timer);
|
|
1245
|
+
server.close();
|
|
1246
|
+
server.closeAllConnections();
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
async function readStdin() {
|
|
1250
|
+
let data = "";
|
|
1251
|
+
for await (const chunk of process.stdin) data += chunk;
|
|
1252
|
+
return data;
|
|
1253
|
+
}
|
|
1254
|
+
async function withTokenLogin() {
|
|
1255
|
+
if (process.stdin.isTTY) {
|
|
1256
|
+
console.error("Paste your API token, then press Enter and Ctrl-D:");
|
|
1257
|
+
}
|
|
1258
|
+
const token = (await readStdin()).trim();
|
|
1259
|
+
if (!token.startsWith(TOKEN_PREFIX)) {
|
|
1260
|
+
throw new CliError(
|
|
1261
|
+
"usage",
|
|
1262
|
+
`Expected a token starting with ${TOKEN_PREFIX} on stdin`
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
const me = await fetchMe(new ApiClient({ token }));
|
|
1266
|
+
saveToken(token);
|
|
1267
|
+
console.log(`Token saved to ${credentialsPath()}`);
|
|
1268
|
+
printIdentity(me);
|
|
1269
|
+
}
|
|
1270
|
+
var TOKENS_PATH = "/api/v1/tokens";
|
|
1271
|
+
async function fetchAllTokens(client) {
|
|
1272
|
+
return fetchAllPages(client, TOKENS_PATH);
|
|
1273
|
+
}
|
|
1274
|
+
function tokenStatus(t) {
|
|
1275
|
+
if (t.revokedAt) return "revoked";
|
|
1276
|
+
if (t.expiresAt && Date.parse(t.expiresAt) <= Date.now()) return "expired";
|
|
1277
|
+
return "active";
|
|
1278
|
+
}
|
|
1279
|
+
function tokenRow(t) {
|
|
1280
|
+
return {
|
|
1281
|
+
id: t.id,
|
|
1282
|
+
name: t.name,
|
|
1283
|
+
token: `\u2026${t.last4}`,
|
|
1284
|
+
status: tokenStatus(t),
|
|
1285
|
+
created: t.createdAt.slice(0, 10),
|
|
1286
|
+
lastUsed: t.lastUsedAt ? t.lastUsedAt.slice(0, 10) : "never",
|
|
1287
|
+
expires: t.expiresAt ? t.expiresAt.slice(0, 10) : "never"
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
function parseExpiresIn(raw) {
|
|
1291
|
+
if (raw === "never") return void 0;
|
|
1292
|
+
const match = raw.match(/^(\d+)([dh])$/);
|
|
1293
|
+
if (!match) {
|
|
1294
|
+
throw new CliError(
|
|
1295
|
+
"usage",
|
|
1296
|
+
`Invalid --expires-in "${raw}" (expected e.g. 90d, 12h, or never)`
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
const n = Number(match[1]);
|
|
1300
|
+
const ms = match[2] === "d" ? n * 24 * 60 * 60 * 1e3 : n * 60 * 60 * 1e3;
|
|
1301
|
+
if (ms <= 0) {
|
|
1302
|
+
throw new CliError("usage", "--expires-in must be a positive duration");
|
|
1303
|
+
}
|
|
1304
|
+
return new Date(Date.now() + ms).toISOString();
|
|
1305
|
+
}
|
|
1306
|
+
async function whoami(output) {
|
|
1307
|
+
const me = await fetchMe(createClient());
|
|
1308
|
+
if (output === "json") {
|
|
1309
|
+
printJson(me);
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
printIdentity(me);
|
|
1313
|
+
}
|
|
1314
|
+
function register2(program2) {
|
|
1315
|
+
const auth = program2.command("auth").description("Authenticate the CLI (personal access tokens)");
|
|
1316
|
+
auth.command("login").description("Log in via the browser (localhost callback flow)").option("--with-token", "read a token from stdin (headless machines)").action(async (opts) => {
|
|
1317
|
+
if (opts.withToken) await withTokenLogin();
|
|
1318
|
+
else await browserLogin();
|
|
1319
|
+
});
|
|
1320
|
+
auth.command("logout").description("Remove the stored token").action(() => {
|
|
1321
|
+
clearToken();
|
|
1322
|
+
console.log(`Removed ${credentialsPath()}`);
|
|
1323
|
+
if (process.env.GAUGE_API_TOKEN?.trim()) {
|
|
1324
|
+
console.error(
|
|
1325
|
+
"warning: GAUGE_API_TOKEN is set in your environment and still wins; unset it to fully log out."
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
});
|
|
1329
|
+
auth.command("status").description("Show the current auth state").action(async (_opts, command) => {
|
|
1330
|
+
const output = resolveOutput(command);
|
|
1331
|
+
const source = tokenSource();
|
|
1332
|
+
if (source === "none") {
|
|
1333
|
+
throw new CliError(
|
|
1334
|
+
"unauthorized",
|
|
1335
|
+
"Not logged in. Run `gauge auth login` or set GAUGE_API_TOKEN."
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
let me;
|
|
1339
|
+
try {
|
|
1340
|
+
me = await fetchMe(createClient());
|
|
1341
|
+
} catch (e) {
|
|
1342
|
+
if (e instanceof CliError && e.code === "unauthorized") {
|
|
1343
|
+
throw new CliError(
|
|
1344
|
+
"unauthorized",
|
|
1345
|
+
`Token (source: ${source}) was rejected \u2014 it may be revoked or expired. Run \`gauge auth login\`.`
|
|
1346
|
+
);
|
|
1347
|
+
}
|
|
1348
|
+
throw e;
|
|
1349
|
+
}
|
|
1350
|
+
if (output === "json") {
|
|
1351
|
+
printJson({ authenticated: true, source, ...me });
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
printIdentity(me);
|
|
1355
|
+
console.log(
|
|
1356
|
+
`Token source: ${source === "env" ? "GAUGE_API_TOKEN environment variable" : credentialsPath()}`
|
|
1357
|
+
);
|
|
1358
|
+
});
|
|
1359
|
+
const tokens = auth.command("tokens").description("Manage API tokens");
|
|
1360
|
+
tokens.command("list").description("List your API tokens").action(async (_opts, command) => {
|
|
1361
|
+
const output = resolveOutput(command);
|
|
1362
|
+
const items = await fetchAllTokens(createClient());
|
|
1363
|
+
if (output === "json") {
|
|
1364
|
+
printJson(items);
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
printItems(items.map(tokenRow), "table");
|
|
1368
|
+
});
|
|
1369
|
+
tokens.command("create <name>").description("Create an API token (the secret is shown exactly once)").option("--expires-in <duration>", "e.g. 90d, 12h, or never", "never").action(
|
|
1370
|
+
async (name, opts, command) => {
|
|
1371
|
+
const output = resolveOutput(command);
|
|
1372
|
+
const expiresAt = parseExpiresIn(opts.expiresIn);
|
|
1373
|
+
const created = await createClient().post(
|
|
1374
|
+
TOKENS_PATH,
|
|
1375
|
+
{ name, ...expiresAt ? { expiresAt } : {} }
|
|
1376
|
+
);
|
|
1377
|
+
if (output === "json") {
|
|
1378
|
+
printJson(created);
|
|
1379
|
+
return;
|
|
1380
|
+
}
|
|
1381
|
+
console.log(
|
|
1382
|
+
`Created token "${created.name}" (id ${created.id}, expires ${created.expiresAt ?? "never"})`
|
|
1383
|
+
);
|
|
1384
|
+
console.log(`
|
|
1385
|
+
${created.token}
|
|
1386
|
+
`);
|
|
1387
|
+
console.log("Store it now \u2014 it won't be shown again.");
|
|
1388
|
+
}
|
|
1389
|
+
);
|
|
1390
|
+
tokens.command("revoke <id>").description("Revoke an API token").action(async (id, _opts, command) => {
|
|
1391
|
+
await createClient().delete(
|
|
1392
|
+
`${TOKENS_PATH}/${encodeURIComponent(id)}`
|
|
1393
|
+
);
|
|
1394
|
+
if (resolveOutput(command) !== "json") console.log(`Revoked ${id}`);
|
|
1395
|
+
});
|
|
1396
|
+
program2.command("whoami").description("Show the authenticated user and their orgs").action(async (_opts, command) => {
|
|
1397
|
+
await whoami(resolveOutput(command));
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// src/commands/batches.ts
|
|
1402
|
+
var batches_exports = {};
|
|
1403
|
+
__export(batches_exports, {
|
|
1404
|
+
register: () => register3
|
|
1405
|
+
});
|
|
1406
|
+
|
|
1407
|
+
// src/lib/flags.ts
|
|
1408
|
+
function parseLimit(raw) {
|
|
1409
|
+
const limit = Number(raw);
|
|
1410
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 200) {
|
|
1411
|
+
throw new CliError("usage", "--limit must be an integer between 1 and 200");
|
|
1412
|
+
}
|
|
1413
|
+
return limit;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
// src/commands/batches.ts
|
|
1417
|
+
function formatRunCounts(counts) {
|
|
1418
|
+
return Object.entries(counts).map(([status, n]) => `${n} ${status}`).join(", ");
|
|
1419
|
+
}
|
|
1420
|
+
function register3(program2) {
|
|
1421
|
+
const batches = program2.command("batches").description("Inspect run batches");
|
|
1422
|
+
batches.command("list").description("List batches (newest first)").option("--prompt <id>", "only batches of this prompt").option("--experiment <label>", "only batches with this cohort label").option("--limit <n>", "page size (1-200)", parseLimit, 50).option("--cursor <cursor>", "resume from a previous page's nextCursor").action(
|
|
1423
|
+
async (opts, command) => {
|
|
1424
|
+
const org = resolveOrg(command);
|
|
1425
|
+
const res = await createClient().get(
|
|
1426
|
+
`/api/v1/orgs/${encodeURIComponent(org)}/batches`,
|
|
1427
|
+
{
|
|
1428
|
+
promptId: opts.prompt,
|
|
1429
|
+
experiment: opts.experiment,
|
|
1430
|
+
limit: opts.limit,
|
|
1431
|
+
cursor: opts.cursor
|
|
1432
|
+
}
|
|
1433
|
+
);
|
|
1434
|
+
if (resolveOutput(command) === "json") {
|
|
1435
|
+
printJson(res);
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
printItems(
|
|
1439
|
+
res.items.map((b) => ({
|
|
1440
|
+
id: b.id,
|
|
1441
|
+
createdAt: b.createdAt,
|
|
1442
|
+
experiment: b.experiment ?? "",
|
|
1443
|
+
prompt: b.promptText,
|
|
1444
|
+
runs: formatRunCounts(b.runCounts)
|
|
1445
|
+
})),
|
|
1446
|
+
"table"
|
|
1447
|
+
);
|
|
1448
|
+
if (res.nextCursor) {
|
|
1449
|
+
process.stderr.write(
|
|
1450
|
+
`(more results \u2014 rerun with --cursor ${res.nextCursor})
|
|
1451
|
+
`
|
|
1452
|
+
);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
);
|
|
1456
|
+
batches.command("get <id>").description("Show a batch and its runs").action(async (id, _opts, command) => {
|
|
1457
|
+
const batch = await createClient().get(
|
|
1458
|
+
`/api/v1/batches/${encodeURIComponent(id)}`
|
|
1459
|
+
);
|
|
1460
|
+
if (resolveOutput(command) === "json") {
|
|
1461
|
+
printJson(batch);
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
console.log(`batch ${batch.id}`);
|
|
1465
|
+
console.log(`created ${batch.createdAt}`);
|
|
1466
|
+
console.log(`prompt ${batch.promptId}`);
|
|
1467
|
+
if (batch.scheduleId) console.log(`schedule ${batch.scheduleId}`);
|
|
1468
|
+
console.log(`scenario ${batch.scenario}`);
|
|
1469
|
+
if (batch.repoUrl) {
|
|
1470
|
+
console.log(
|
|
1471
|
+
`repo ${batch.repoUrl}${batch.repoRef ? `@${batch.repoRef}` : ""}`
|
|
1472
|
+
);
|
|
1473
|
+
}
|
|
1474
|
+
if (batch.experiment) console.log(`experiment ${batch.experiment}`);
|
|
1475
|
+
console.log(`funding ${batch.funding}`);
|
|
1476
|
+
console.log(`runs ${formatRunCounts(batch.runCounts) || "none"}`);
|
|
1477
|
+
console.log(`text ${batch.promptText}`);
|
|
1478
|
+
if (batch.runs.length > 0) {
|
|
1479
|
+
console.log("");
|
|
1480
|
+
printItems(
|
|
1481
|
+
batch.runs.map((r) => ({
|
|
1482
|
+
id: r.id,
|
|
1483
|
+
agent: r.agent,
|
|
1484
|
+
model: r.model || "(default)",
|
|
1485
|
+
status: r.status,
|
|
1486
|
+
startedAt: r.startedAt ?? "",
|
|
1487
|
+
finishedAt: r.finishedAt ?? ""
|
|
1488
|
+
})),
|
|
1489
|
+
"table"
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
// src/commands/billing.ts
|
|
1496
|
+
var billing_exports2 = {};
|
|
1497
|
+
__export(billing_exports2, {
|
|
1498
|
+
register: () => register4
|
|
1499
|
+
});
|
|
1500
|
+
function fmtDate(iso) {
|
|
1501
|
+
return iso ? iso.slice(0, 10) : "\u2014";
|
|
1502
|
+
}
|
|
1503
|
+
function statusLines(s) {
|
|
1504
|
+
return [
|
|
1505
|
+
["Plan", s.plan ?? "free"],
|
|
1506
|
+
["Subscription", s.subscriptionStatus ?? "\u2014"],
|
|
1507
|
+
["Balance", `${s.balance} credits`],
|
|
1508
|
+
[
|
|
1509
|
+
"Allowance / cycle",
|
|
1510
|
+
s.allowance === null ? "\u2014" : `${s.allowance} credits`
|
|
1511
|
+
],
|
|
1512
|
+
[
|
|
1513
|
+
"Period",
|
|
1514
|
+
s.currentPeriodStart || s.currentPeriodEnd ? `${fmtDate(s.currentPeriodStart)} \u2192 ${fmtDate(s.currentPeriodEnd)}` : "\u2014"
|
|
1515
|
+
],
|
|
1516
|
+
["Used this period", `${s.creditsUsedThisPeriod} credits`],
|
|
1517
|
+
["Runs this period", String(s.runsThisPeriod)],
|
|
1518
|
+
[
|
|
1519
|
+
"Overage",
|
|
1520
|
+
s.overageEnabled ? `enabled (${s.overageThisPeriod} used${s.overageCapCredits === null ? ", uncapped" : `, cap ${s.overageCapCredits}`})` : "disabled"
|
|
1521
|
+
],
|
|
1522
|
+
["Can run", s.canRun ? "yes" : "no (out of credits)"],
|
|
1523
|
+
["Billing enforced", s.enforced ? "yes" : "no"]
|
|
1524
|
+
];
|
|
1525
|
+
}
|
|
1526
|
+
function register4(program2) {
|
|
1527
|
+
const group = program2.command("billing").description("Billing & credits");
|
|
1528
|
+
group.command("status").description("Show credit balance, plan, and overage").action(async (_opts, cmd) => {
|
|
1529
|
+
const org = resolveOrg(cmd);
|
|
1530
|
+
const status = await createClient().get(
|
|
1531
|
+
`/api/v1/orgs/${encodeURIComponent(org)}/billing`
|
|
1532
|
+
);
|
|
1533
|
+
if (resolveOutput(cmd) === "json") {
|
|
1534
|
+
printJson(status);
|
|
1535
|
+
return;
|
|
1536
|
+
}
|
|
1537
|
+
const lines = statusLines(status);
|
|
1538
|
+
const width = Math.max(...lines.map(([label]) => label.length));
|
|
1539
|
+
for (const [label, value] of lines) {
|
|
1540
|
+
console.log(`${label.padEnd(width)} ${value}`);
|
|
1541
|
+
}
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// src/commands/brands.ts
|
|
1546
|
+
var brands_exports2 = {};
|
|
1547
|
+
__export(brands_exports2, {
|
|
1548
|
+
register: () => register5
|
|
1549
|
+
});
|
|
1550
|
+
import { Option } from "commander";
|
|
1551
|
+
var KINDS = brands_exports.BRAND_KINDS.map((k) => k.toLowerCase());
|
|
1552
|
+
var ECOSYSTEMS = brands_exports.BRAND_PACKAGE_ECOSYSTEMS;
|
|
1553
|
+
function brandsPath(org) {
|
|
1554
|
+
return `/api/v1/orgs/${encodeURIComponent(org)}/brands`;
|
|
1555
|
+
}
|
|
1556
|
+
function collect(value, previous) {
|
|
1557
|
+
return previous ? [...previous, value] : [value];
|
|
1558
|
+
}
|
|
1559
|
+
function splitList(raw) {
|
|
1560
|
+
return raw.split(",").map((v) => v.trim()).filter(Boolean);
|
|
1561
|
+
}
|
|
1562
|
+
function parsePackage(raw) {
|
|
1563
|
+
const sep = raw.lastIndexOf(":");
|
|
1564
|
+
if (sep === -1) return { name: raw.trim(), ecosystem: null };
|
|
1565
|
+
const name = raw.slice(0, sep).trim();
|
|
1566
|
+
const ecosystem = raw.slice(sep + 1).trim();
|
|
1567
|
+
if (!name) throw new CliError("usage", `Invalid package "${raw}"`);
|
|
1568
|
+
if (!ECOSYSTEMS.includes(ecosystem)) {
|
|
1569
|
+
throw new CliError(
|
|
1570
|
+
"usage",
|
|
1571
|
+
`Invalid ecosystem "${ecosystem}" (expected ${ECOSYSTEMS.join("|")})`
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
return { name, ecosystem };
|
|
1575
|
+
}
|
|
1576
|
+
function toRow(brand) {
|
|
1577
|
+
return {
|
|
1578
|
+
id: brand.id,
|
|
1579
|
+
name: brand.name,
|
|
1580
|
+
kind: brand.kind.toLowerCase(),
|
|
1581
|
+
aliases: brand.aliases.join(", "),
|
|
1582
|
+
websites: brand.websites.join(", "),
|
|
1583
|
+
packages: brand.packages.map((p) => p.name).join(", ")
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
function register5(program2) {
|
|
1587
|
+
const group = program2.command("brands").description("Manage org brands");
|
|
1588
|
+
group.command("list").description("List brands").addOption(new Option("--kind <kind>", "filter by kind").choices(KINDS)).action(async (opts, cmd) => {
|
|
1589
|
+
const org = resolveOrg(cmd);
|
|
1590
|
+
const items = await fetchAllPages(
|
|
1591
|
+
createClient(),
|
|
1592
|
+
brandsPath(org),
|
|
1593
|
+
{
|
|
1594
|
+
kind: opts.kind?.toUpperCase()
|
|
1595
|
+
}
|
|
1596
|
+
);
|
|
1597
|
+
if (resolveOutput(cmd) === "json") {
|
|
1598
|
+
printJson(items);
|
|
1599
|
+
return;
|
|
1600
|
+
}
|
|
1601
|
+
printItems(items.map(toRow), "table");
|
|
1602
|
+
});
|
|
1603
|
+
group.command("add <name>").description("Add a brand").addOption(
|
|
1604
|
+
new Option("--kind <kind>", "brand kind").choices(KINDS).makeOptionMandatory()
|
|
1605
|
+
).option("--aliases <list>", "comma-separated alias spellings").option("--websites <list>", "comma-separated docs/marketing URLs").option(
|
|
1606
|
+
"--package <name[:ecosystem]>",
|
|
1607
|
+
"package identifier, repeatable (ecosystem: npm|pypi|cargo|go|rubygems|other)",
|
|
1608
|
+
collect
|
|
1609
|
+
).action(
|
|
1610
|
+
async (name, opts, cmd) => {
|
|
1611
|
+
const org = resolveOrg(cmd);
|
|
1612
|
+
const row = await createClient().post(brandsPath(org), {
|
|
1613
|
+
name,
|
|
1614
|
+
kind: opts.kind.toUpperCase(),
|
|
1615
|
+
aliases: opts.aliases === void 0 ? [] : splitList(opts.aliases),
|
|
1616
|
+
websites: opts.websites === void 0 ? [] : splitList(opts.websites),
|
|
1617
|
+
packages: (opts.package ?? []).map(parsePackage)
|
|
1618
|
+
});
|
|
1619
|
+
if (resolveOutput(cmd) === "json") printJson(row);
|
|
1620
|
+
else console.log(`Created brand ${row.id} (${row.name})`);
|
|
1621
|
+
}
|
|
1622
|
+
);
|
|
1623
|
+
group.command("edit <id>").description("Update a brand (only the provided fields change)").option("--name <name>", "new canonical name").addOption(new Option("--kind <kind>", "new kind").choices(KINDS)).option(
|
|
1624
|
+
"--aliases <list>",
|
|
1625
|
+
"replace aliases (comma-separated; pass '' to clear)"
|
|
1626
|
+
).option(
|
|
1627
|
+
"--websites <list>",
|
|
1628
|
+
"replace websites (comma-separated; pass '' to clear)"
|
|
1629
|
+
).option(
|
|
1630
|
+
"--package <name[:ecosystem]>",
|
|
1631
|
+
"replace packages, repeatable",
|
|
1632
|
+
collect
|
|
1633
|
+
).option("--clear-packages", "remove all packages").action(
|
|
1634
|
+
async (id, opts, cmd) => {
|
|
1635
|
+
const patch = {};
|
|
1636
|
+
if (opts.name !== void 0) patch.name = opts.name;
|
|
1637
|
+
if (opts.kind !== void 0) patch.kind = opts.kind.toUpperCase();
|
|
1638
|
+
if (opts.aliases !== void 0) patch.aliases = splitList(opts.aliases);
|
|
1639
|
+
if (opts.websites !== void 0)
|
|
1640
|
+
patch.websites = splitList(opts.websites);
|
|
1641
|
+
if (opts.clearPackages) patch.packages = [];
|
|
1642
|
+
else if (opts.package !== void 0)
|
|
1643
|
+
patch.packages = opts.package.map(parsePackage);
|
|
1644
|
+
if (Object.keys(patch).length === 0) {
|
|
1645
|
+
throw new CliError(
|
|
1646
|
+
"usage",
|
|
1647
|
+
"Nothing to update \u2014 pass at least one of --name/--kind/--aliases/--websites/--package/--clear-packages."
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
const org = resolveOrg(cmd);
|
|
1651
|
+
const row = await createClient().patch(
|
|
1652
|
+
`${brandsPath(org)}/${encodeURIComponent(id)}`,
|
|
1653
|
+
patch
|
|
1654
|
+
);
|
|
1655
|
+
if (resolveOutput(cmd) === "json") printJson(row);
|
|
1656
|
+
else console.log(`Updated brand ${row.id} (${row.name})`);
|
|
1657
|
+
}
|
|
1658
|
+
);
|
|
1659
|
+
group.command("rm <id>").description("Delete a brand").action(async (id, _opts, cmd) => {
|
|
1660
|
+
const org = resolveOrg(cmd);
|
|
1661
|
+
await createClient().delete(
|
|
1662
|
+
`${brandsPath(org)}/${encodeURIComponent(id)}`
|
|
1663
|
+
);
|
|
1664
|
+
if (resolveOutput(cmd) !== "json") console.log(`Deleted brand ${id}`);
|
|
1665
|
+
});
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
// src/commands/config.ts
|
|
1669
|
+
var config_exports = {};
|
|
1670
|
+
__export(config_exports, {
|
|
1671
|
+
register: () => register6
|
|
1672
|
+
});
|
|
1673
|
+
function assertKey(key) {
|
|
1674
|
+
if (CONFIG_KEYS.includes(key)) {
|
|
1675
|
+
return key;
|
|
1676
|
+
}
|
|
1677
|
+
throw new CliError(
|
|
1678
|
+
"usage",
|
|
1679
|
+
`Unknown config key '${key}'. Valid keys: ${CONFIG_KEYS.join(", ")}`
|
|
1680
|
+
);
|
|
1681
|
+
}
|
|
1682
|
+
function register6(program2) {
|
|
1683
|
+
const config = program2.command("config").description("Local client config (~/.config/gauge/config.json)");
|
|
1684
|
+
config.command("get [key]").description("Print the whole config, or a single key's value").action((key) => {
|
|
1685
|
+
const current = readConfig();
|
|
1686
|
+
if (key === void 0) {
|
|
1687
|
+
printJson(current);
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
console.log(current[assertKey(key)] ?? "");
|
|
1691
|
+
});
|
|
1692
|
+
config.command("set <key> <value>").description(`Set a config key (${CONFIG_KEYS.join(", ")})`).action((key, value) => {
|
|
1693
|
+
const configKey = assertKey(key);
|
|
1694
|
+
updateConfig({ [configKey]: value });
|
|
1695
|
+
console.log(`${configKey} = ${value} (${configPath()})`);
|
|
1696
|
+
});
|
|
1697
|
+
config.command("unset <key>").description("Remove a config key").action((key) => {
|
|
1698
|
+
const configKey = assertKey(key);
|
|
1699
|
+
updateConfig({ [configKey]: void 0 });
|
|
1700
|
+
console.log(`${configKey} unset (${configPath()})`);
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// src/commands/keys.ts
|
|
1705
|
+
var keys_exports2 = {};
|
|
1706
|
+
__export(keys_exports2, {
|
|
1707
|
+
register: () => register7
|
|
1708
|
+
});
|
|
1709
|
+
function keysPath(org) {
|
|
1710
|
+
return `/api/v1/orgs/${encodeURIComponent(org)}/provider-keys`;
|
|
1711
|
+
}
|
|
1712
|
+
function providerArg(raw) {
|
|
1713
|
+
const provider = raw.toLowerCase();
|
|
1714
|
+
if (!keys_exports.PROVIDER_TYPES.includes(provider)) {
|
|
1715
|
+
throw new CliError(
|
|
1716
|
+
"usage",
|
|
1717
|
+
`Unknown provider "${raw}" (expected ${keys_exports.PROVIDER_TYPES.join("|")})`
|
|
1718
|
+
);
|
|
1719
|
+
}
|
|
1720
|
+
return provider;
|
|
1721
|
+
}
|
|
1722
|
+
async function readKeyInput(opts) {
|
|
1723
|
+
if (opts.key !== void 0) {
|
|
1724
|
+
process.stderr.write(
|
|
1725
|
+
"warning: --key exposes the secret to shell history and process listings; prefer piping it to stdin or setting GAUGE_PROVIDER_KEY.\n"
|
|
1726
|
+
);
|
|
1727
|
+
const key2 = opts.key.trim();
|
|
1728
|
+
if (key2) return key2;
|
|
1729
|
+
throw new CliError("usage", "Empty --key value");
|
|
1730
|
+
}
|
|
1731
|
+
if (!opts.stdin) {
|
|
1732
|
+
const env = process.env.GAUGE_PROVIDER_KEY?.trim();
|
|
1733
|
+
if (env) return env;
|
|
1734
|
+
}
|
|
1735
|
+
if (process.stdin.isTTY) {
|
|
1736
|
+
process.stderr.write("Paste the API key, press Enter, then Ctrl-D:\n");
|
|
1737
|
+
}
|
|
1738
|
+
process.stdin.setEncoding("utf8");
|
|
1739
|
+
let data = "";
|
|
1740
|
+
for await (const chunk of process.stdin) data += chunk;
|
|
1741
|
+
const key = data.trim();
|
|
1742
|
+
if (!key) {
|
|
1743
|
+
throw new CliError(
|
|
1744
|
+
"usage",
|
|
1745
|
+
"No key provided. Pipe it to stdin (e.g. `gauge keys set anthropic < key.txt`) or set GAUGE_PROVIDER_KEY."
|
|
1746
|
+
);
|
|
1747
|
+
}
|
|
1748
|
+
return key;
|
|
1749
|
+
}
|
|
1750
|
+
function register7(program2) {
|
|
1751
|
+
const group = program2.command("keys").description("Manage BYOK provider keys (write-only; reads show last4)");
|
|
1752
|
+
group.command("set <provider>").description(
|
|
1753
|
+
"Set a provider key (anthropic|openai); reads it from stdin or GAUGE_PROVIDER_KEY"
|
|
1754
|
+
).option("--stdin", "read the key from stdin (ignores GAUGE_PROVIDER_KEY)").option(
|
|
1755
|
+
"--key <key>",
|
|
1756
|
+
"pass the key inline (discouraged \u2014 lands in shell history)"
|
|
1757
|
+
).action(
|
|
1758
|
+
async (provider, opts, cmd) => {
|
|
1759
|
+
const type = providerArg(provider);
|
|
1760
|
+
const org = resolveOrg(cmd);
|
|
1761
|
+
const key = await readKeyInput(opts);
|
|
1762
|
+
const res = await createClient().put(
|
|
1763
|
+
`${keysPath(org)}/${type}`,
|
|
1764
|
+
{ key }
|
|
1765
|
+
);
|
|
1766
|
+
if (resolveOutput(cmd) === "json") printJson(res);
|
|
1767
|
+
else console.log(`Saved ${res.providerType} key (\u2026${res.last4})`);
|
|
1768
|
+
}
|
|
1769
|
+
);
|
|
1770
|
+
group.command("list").description("List configured provider keys").action(async (_opts, cmd) => {
|
|
1771
|
+
const org = resolveOrg(cmd);
|
|
1772
|
+
const res = await createClient().get(
|
|
1773
|
+
keysPath(org)
|
|
1774
|
+
);
|
|
1775
|
+
if (resolveOutput(cmd) === "json") printJson(res.items);
|
|
1776
|
+
else
|
|
1777
|
+
printItems(
|
|
1778
|
+
res.items.map((k) => ({
|
|
1779
|
+
providerType: k.providerType,
|
|
1780
|
+
last4: `\u2026${k.last4}`,
|
|
1781
|
+
createdAt: k.createdAt,
|
|
1782
|
+
updatedAt: k.updatedAt
|
|
1783
|
+
})),
|
|
1784
|
+
"table"
|
|
1785
|
+
);
|
|
1786
|
+
});
|
|
1787
|
+
group.command("rm <provider>").description("Remove a provider key").action(async (provider, _opts, cmd) => {
|
|
1788
|
+
const type = providerArg(provider);
|
|
1789
|
+
const org = resolveOrg(cmd);
|
|
1790
|
+
await createClient().delete(`${keysPath(org)}/${type}`);
|
|
1791
|
+
if (resolveOutput(cmd) !== "json") console.log(`Removed ${type} key`);
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// src/commands/orgs.ts
|
|
1796
|
+
var orgs_exports = {};
|
|
1797
|
+
__export(orgs_exports, {
|
|
1798
|
+
register: () => register8
|
|
1799
|
+
});
|
|
1800
|
+
function isDefault(org, defaultOrg) {
|
|
1801
|
+
return defaultOrg !== void 0 && (org.slug === defaultOrg || org.publicId === defaultOrg);
|
|
1802
|
+
}
|
|
1803
|
+
function register8(program2) {
|
|
1804
|
+
const orgs = program2.command("orgs").description("Organizations");
|
|
1805
|
+
orgs.command("list").description("List orgs you belong to").action(async (_opts, command) => {
|
|
1806
|
+
const output = resolveOutput(command);
|
|
1807
|
+
const me = await createClient().get("/api/v1/me");
|
|
1808
|
+
const { defaultOrg } = readConfig();
|
|
1809
|
+
if (output === "json") {
|
|
1810
|
+
printJson(
|
|
1811
|
+
me.orgs.map((org) => ({
|
|
1812
|
+
...org,
|
|
1813
|
+
isDefault: isDefault(org, defaultOrg)
|
|
1814
|
+
}))
|
|
1815
|
+
);
|
|
1816
|
+
return;
|
|
1817
|
+
}
|
|
1818
|
+
printItems(
|
|
1819
|
+
me.orgs.map((org) => ({
|
|
1820
|
+
slug: org.slug,
|
|
1821
|
+
name: org.name,
|
|
1822
|
+
publicId: org.publicId ?? "",
|
|
1823
|
+
default: isDefault(org, defaultOrg) ? "*" : ""
|
|
1824
|
+
})),
|
|
1825
|
+
"table"
|
|
1826
|
+
);
|
|
1827
|
+
});
|
|
1828
|
+
orgs.command("use <org>").description("Set the default org (slug or public id) in local config").action(async (org, _opts, command) => {
|
|
1829
|
+
const me = await createClient().get("/api/v1/me");
|
|
1830
|
+
const match = me.orgs.find((o) => o.slug === org || o.publicId === org);
|
|
1831
|
+
if (!match && !me.user.isSuperuser) {
|
|
1832
|
+
throw new CliError(
|
|
1833
|
+
"not_found",
|
|
1834
|
+
`You are not a member of "${org}". Run \`gauge orgs list\`.`
|
|
1835
|
+
);
|
|
1836
|
+
}
|
|
1837
|
+
const value = match ? match.slug : org;
|
|
1838
|
+
updateConfig({ defaultOrg: value });
|
|
1839
|
+
if (resolveOutput(command) === "json") {
|
|
1840
|
+
printJson({ defaultOrg: value });
|
|
1841
|
+
return;
|
|
1842
|
+
}
|
|
1843
|
+
console.log(
|
|
1844
|
+
`Default org set to ${value}${match ? ` (${match.name})` : ""}`
|
|
1845
|
+
);
|
|
1846
|
+
if (!match) {
|
|
1847
|
+
console.error(
|
|
1848
|
+
"warning: not in your memberships \u2014 accepted because you are a superuser."
|
|
1849
|
+
);
|
|
1850
|
+
}
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
// src/commands/prompts.ts
|
|
1855
|
+
var prompts_exports2 = {};
|
|
1856
|
+
__export(prompts_exports2, {
|
|
1857
|
+
parseAgents: () => parseAgents,
|
|
1858
|
+
promptsPath: () => promptsPath,
|
|
1859
|
+
readPromptText: () => readPromptText,
|
|
1860
|
+
register: () => register9,
|
|
1861
|
+
resolveOrCreateTopicId: () => resolveOrCreateTopicId
|
|
1862
|
+
});
|
|
1863
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
1864
|
+
function promptsPath(org) {
|
|
1865
|
+
return `/api/v1/orgs/${encodeURIComponent(org)}/prompts`;
|
|
1866
|
+
}
|
|
1867
|
+
var AGENT_ALIASES = {
|
|
1868
|
+
"claude-code": "CLAUDE_CODE",
|
|
1869
|
+
claude_code: "CLAUDE_CODE",
|
|
1870
|
+
claude: "CLAUDE_CODE",
|
|
1871
|
+
codex: "CODEX_CLI",
|
|
1872
|
+
"codex-cli": "CODEX_CLI",
|
|
1873
|
+
codex_cli: "CODEX_CLI",
|
|
1874
|
+
cursor: "CURSOR"
|
|
1875
|
+
};
|
|
1876
|
+
function parseAgents(raw) {
|
|
1877
|
+
const out = [];
|
|
1878
|
+
for (const part of raw.split(",")) {
|
|
1879
|
+
const name = part.trim();
|
|
1880
|
+
if (!name) continue;
|
|
1881
|
+
const agent = AGENT_ALIASES[name.toLowerCase()];
|
|
1882
|
+
if (!agent) {
|
|
1883
|
+
throw new CliError(
|
|
1884
|
+
"usage",
|
|
1885
|
+
`unknown agent "${name}" (expected: claude-code, codex, cursor)`
|
|
1886
|
+
);
|
|
1887
|
+
}
|
|
1888
|
+
out.push(agent);
|
|
1889
|
+
}
|
|
1890
|
+
if (out.length === 0) throw new CliError("usage", "no agents given");
|
|
1891
|
+
return [...new Set(out)];
|
|
1892
|
+
}
|
|
1893
|
+
function readPromptText(opts) {
|
|
1894
|
+
if (opts.text === void 0 === (opts.file === void 0)) {
|
|
1895
|
+
throw new CliError("usage", "pass exactly one of --text or --file");
|
|
1896
|
+
}
|
|
1897
|
+
if (opts.text !== void 0) return opts.text;
|
|
1898
|
+
const text = readFileSync4(
|
|
1899
|
+
opts.file === "-" ? 0 : opts.file,
|
|
1900
|
+
"utf8"
|
|
1901
|
+
).trim();
|
|
1902
|
+
if (!text) throw new CliError("usage", "prompt file is empty");
|
|
1903
|
+
return text;
|
|
1904
|
+
}
|
|
1905
|
+
async function resolveOrCreateTopicId(client, org, name) {
|
|
1906
|
+
const path = `/api/v1/orgs/${encodeURIComponent(org)}/topics`;
|
|
1907
|
+
let cursor;
|
|
1908
|
+
do {
|
|
1909
|
+
const page = await client.get(path, {
|
|
1910
|
+
limit: 200,
|
|
1911
|
+
cursor
|
|
1912
|
+
});
|
|
1913
|
+
const match = page.items.find(
|
|
1914
|
+
(t) => t.id === name || t.name.toLowerCase() === name.toLowerCase()
|
|
1915
|
+
);
|
|
1916
|
+
if (match) return match.id;
|
|
1917
|
+
cursor = page.nextCursor;
|
|
1918
|
+
} while (cursor);
|
|
1919
|
+
const created = await client.post(path, { name });
|
|
1920
|
+
return created.id;
|
|
1921
|
+
}
|
|
1922
|
+
function collect2(value, previous) {
|
|
1923
|
+
return [...previous, value];
|
|
1924
|
+
}
|
|
1925
|
+
async function fetchAllPrompts(client, org, filters) {
|
|
1926
|
+
return fetchAllPages(client, promptsPath(org), filters);
|
|
1927
|
+
}
|
|
1928
|
+
function toRow2(p) {
|
|
1929
|
+
return {
|
|
1930
|
+
id: p.id,
|
|
1931
|
+
text: p.text.length > 60 ? `${p.text.slice(0, 57)}...` : p.text,
|
|
1932
|
+
topic: p.topic?.name ?? "",
|
|
1933
|
+
tags: p.tags.map((t) => t.name).join(", "),
|
|
1934
|
+
schedules: p.scheduleCount,
|
|
1935
|
+
branded: p.isBranded ? "yes" : "",
|
|
1936
|
+
scenario: p.scenario
|
|
1937
|
+
};
|
|
1938
|
+
}
|
|
1939
|
+
var BLOCK_WARNINGS = {
|
|
1940
|
+
insufficient_credits: "warning: prompt created, but the first run was blocked: out of credits",
|
|
1941
|
+
needs_provider_key: "warning: prompt created, but the first run was blocked: no provider key for the selected agents"
|
|
1942
|
+
};
|
|
1943
|
+
var RUN_BLOCK_WARNINGS = {
|
|
1944
|
+
insufficient_credits: "warning: some schedules were skipped or truncated: out of credits",
|
|
1945
|
+
needs_provider_key: "warning: some schedules were skipped: no provider key for the selected agents"
|
|
1946
|
+
};
|
|
1947
|
+
function register9(program2) {
|
|
1948
|
+
const group = program2.command("prompts").description("Manage prompts");
|
|
1949
|
+
group.command("list").description("List prompts").option("--topic <topic>", "filter by topic (id or name)").option("--tag <tag>", "filter by tag (id or name)").option("-q, --query <text>", "text search").action(
|
|
1950
|
+
async (opts, cmd) => {
|
|
1951
|
+
const org = resolveOrg(cmd);
|
|
1952
|
+
const items = await fetchAllPrompts(createClient(), org, {
|
|
1953
|
+
topic: opts.topic,
|
|
1954
|
+
tag: opts.tag,
|
|
1955
|
+
q: opts.query
|
|
1956
|
+
});
|
|
1957
|
+
if (resolveOutput(cmd) === "json") printJson(items);
|
|
1958
|
+
else printItems(items.map(toRow2), "table");
|
|
1959
|
+
}
|
|
1960
|
+
);
|
|
1961
|
+
group.command("get <id>").description("Show a prompt (schedules, topic, tags)").action(async (id, _opts, cmd) => {
|
|
1962
|
+
const org = resolveOrg(cmd);
|
|
1963
|
+
const prompt = await createClient().get(
|
|
1964
|
+
`${promptsPath(org)}/${encodeURIComponent(id)}`
|
|
1965
|
+
);
|
|
1966
|
+
if (resolveOutput(cmd) === "json") {
|
|
1967
|
+
printJson(prompt);
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
console.log(`id: ${prompt.id}`);
|
|
1971
|
+
console.log(`scenario: ${prompt.scenario}`);
|
|
1972
|
+
console.log(`branded: ${prompt.isBranded}`);
|
|
1973
|
+
console.log(`topic: ${prompt.topic?.name ?? "-"}`);
|
|
1974
|
+
console.log(
|
|
1975
|
+
`tags: ${prompt.tags.map((t) => t.name).join(", ") || "-"}`
|
|
1976
|
+
);
|
|
1977
|
+
console.log(`created: ${prompt.createdAt}`);
|
|
1978
|
+
console.log(`text: ${prompt.text}`);
|
|
1979
|
+
if (prompt.schedules.length) {
|
|
1980
|
+
console.log("schedules:");
|
|
1981
|
+
printItems(
|
|
1982
|
+
prompt.schedules.map((s) => ({
|
|
1983
|
+
id: s.id,
|
|
1984
|
+
repo: s.repoUrl ?? "(default scaffold)",
|
|
1985
|
+
ref: s.repoRef ?? "",
|
|
1986
|
+
cadence: s.cadence,
|
|
1987
|
+
days: s.daysPerWeek ?? "",
|
|
1988
|
+
agents: s.agents.join(", ")
|
|
1989
|
+
})),
|
|
1990
|
+
"table"
|
|
1991
|
+
);
|
|
1992
|
+
} else {
|
|
1993
|
+
console.log("schedules: (none)");
|
|
1994
|
+
}
|
|
1995
|
+
});
|
|
1996
|
+
group.command("create").description("Create a prompt").option("--text <text>", "prompt text").option("--file <path>", 'read prompt text from a file ("-" for stdin)').option("--scenario <scenario>", "greenfield | migration", "greenfield").option("--branded", "mark as a branded prompt").option("--topic <name>", "topic (created if new)").option(
|
|
1997
|
+
"--tag <name>",
|
|
1998
|
+
"tag name (repeatable; created if new)",
|
|
1999
|
+
collect2,
|
|
2000
|
+
[]
|
|
2001
|
+
).action(
|
|
2002
|
+
async (opts, cmd) => {
|
|
2003
|
+
const org = resolveOrg(cmd);
|
|
2004
|
+
const scenario = opts.scenario.trim().toUpperCase();
|
|
2005
|
+
if (scenario !== "GREENFIELD" && scenario !== "MIGRATION") {
|
|
2006
|
+
throw new CliError(
|
|
2007
|
+
"usage",
|
|
2008
|
+
`invalid --scenario "${opts.scenario}" (expected: greenfield | migration)`
|
|
2009
|
+
);
|
|
2010
|
+
}
|
|
2011
|
+
const created = await createClient().post(
|
|
2012
|
+
promptsPath(org),
|
|
2013
|
+
{
|
|
2014
|
+
text: readPromptText(opts),
|
|
2015
|
+
scenario,
|
|
2016
|
+
isBranded: opts.branded ?? false,
|
|
2017
|
+
topic: opts.topic,
|
|
2018
|
+
tags: opts.tag
|
|
2019
|
+
}
|
|
2020
|
+
);
|
|
2021
|
+
if (resolveOutput(cmd) === "json") printJson(created);
|
|
2022
|
+
else {
|
|
2023
|
+
console.log(`Created prompt ${created.id}`);
|
|
2024
|
+
console.log(
|
|
2025
|
+
`No schedules yet \u2014 add one with \`gauge schedules add --prompt ${created.id} --agent claude-code\``
|
|
2026
|
+
);
|
|
2027
|
+
}
|
|
2028
|
+
if (created.firstRunBlocked)
|
|
2029
|
+
console.error(BLOCK_WARNINGS[created.firstRunBlocked]);
|
|
2030
|
+
}
|
|
2031
|
+
);
|
|
2032
|
+
group.command("edit <id>").description("Update a prompt").option("--text <text>", "new prompt text").option("--file <path>", 'read new text from a file ("-" for stdin)').option("--branded <bool>", "true | false").option("--topic <name>", "set the topic (created if new)").option("--clear-topic", "clear the topic").option(
|
|
2033
|
+
"--tag <name>",
|
|
2034
|
+
"replace the tag set (repeatable; created if new)",
|
|
2035
|
+
collect2,
|
|
2036
|
+
[]
|
|
2037
|
+
).option("--clear-tags", "remove all tags").action(
|
|
2038
|
+
async (id, opts, cmd) => {
|
|
2039
|
+
const org = resolveOrg(cmd);
|
|
2040
|
+
const client = createClient();
|
|
2041
|
+
const body = {};
|
|
2042
|
+
if (opts.text !== void 0 || opts.file !== void 0)
|
|
2043
|
+
body.text = readPromptText(opts);
|
|
2044
|
+
if (opts.branded !== void 0) {
|
|
2045
|
+
if (opts.branded !== "true" && opts.branded !== "false")
|
|
2046
|
+
throw new CliError("usage", "--branded expects true or false");
|
|
2047
|
+
body.isBranded = opts.branded === "true";
|
|
2048
|
+
}
|
|
2049
|
+
if (opts.topic !== void 0 && opts.clearTopic)
|
|
2050
|
+
throw new CliError("usage", "--topic and --clear-topic conflict");
|
|
2051
|
+
if (opts.clearTopic) body.topicId = null;
|
|
2052
|
+
else if (opts.topic !== void 0)
|
|
2053
|
+
body.topicId = await resolveOrCreateTopicId(client, org, opts.topic);
|
|
2054
|
+
if (opts.tag.length > 0 && opts.clearTags)
|
|
2055
|
+
throw new CliError("usage", "--tag and --clear-tags conflict");
|
|
2056
|
+
if (opts.clearTags) body.tags = [];
|
|
2057
|
+
else if (opts.tag.length > 0) body.tags = opts.tag;
|
|
2058
|
+
if (Object.keys(body).length === 0)
|
|
2059
|
+
throw new CliError("usage", "nothing to change");
|
|
2060
|
+
const prompt = await client.patch(
|
|
2061
|
+
`${promptsPath(org)}/${encodeURIComponent(id)}`,
|
|
2062
|
+
body
|
|
2063
|
+
);
|
|
2064
|
+
if (resolveOutput(cmd) === "json") printJson(prompt);
|
|
2065
|
+
else console.log(`Updated prompt ${prompt.id}`);
|
|
2066
|
+
}
|
|
2067
|
+
);
|
|
2068
|
+
group.command("delete <id>").description("Delete a prompt (cascades to its schedules and runs)").action(async (id, _opts, cmd) => {
|
|
2069
|
+
const org = resolveOrg(cmd);
|
|
2070
|
+
await createClient().delete(
|
|
2071
|
+
`${promptsPath(org)}/${encodeURIComponent(id)}`
|
|
2072
|
+
);
|
|
2073
|
+
if (resolveOutput(cmd) !== "json") console.log(`Deleted prompt ${id}`);
|
|
2074
|
+
});
|
|
2075
|
+
group.command("run <id>").description("Run a prompt now (ORG funding, credit-gated)").option(
|
|
2076
|
+
"--agent <agents>",
|
|
2077
|
+
"comma-separated agents, e.g. claude-code,codex"
|
|
2078
|
+
).option("--schedule <ids>", "comma-separated schedule ids").action(
|
|
2079
|
+
async (id, opts, cmd) => {
|
|
2080
|
+
const org = resolveOrg(cmd);
|
|
2081
|
+
const body = {};
|
|
2082
|
+
if (opts.agent !== void 0) body.agents = parseAgents(opts.agent);
|
|
2083
|
+
if (opts.schedule !== void 0) {
|
|
2084
|
+
const ids = opts.schedule.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2085
|
+
if (ids.length === 0)
|
|
2086
|
+
throw new CliError("usage", "no schedule ids given");
|
|
2087
|
+
body.scheduleIds = ids;
|
|
2088
|
+
}
|
|
2089
|
+
const result = await createClient().post(
|
|
2090
|
+
`${promptsPath(org)}/${encodeURIComponent(id)}/run`,
|
|
2091
|
+
body
|
|
2092
|
+
);
|
|
2093
|
+
if (resolveOutput(cmd) === "json") {
|
|
2094
|
+
printJson(result);
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
if (result.runIds.length === 0) {
|
|
2098
|
+
console.log(
|
|
2099
|
+
"No runs enqueued (the prompt has no matching schedules)"
|
|
2100
|
+
);
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
console.log(
|
|
2104
|
+
`Enqueued ${result.runIds.length} run(s) in ${result.batchIds.length} batch(es)`
|
|
2105
|
+
);
|
|
2106
|
+
for (const runId of result.runIds) console.log(runId);
|
|
2107
|
+
if (result.blocked) console.error(RUN_BLOCK_WARNINGS[result.blocked]);
|
|
2108
|
+
console.error("Tip: `gauge runs watch <id>` streams a run live");
|
|
2109
|
+
}
|
|
2110
|
+
);
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
// src/commands/repos.ts
|
|
2114
|
+
var repos_exports2 = {};
|
|
2115
|
+
__export(repos_exports2, {
|
|
2116
|
+
register: () => register10
|
|
2117
|
+
});
|
|
2118
|
+
function reposPath(org) {
|
|
2119
|
+
return `/api/v1/orgs/${encodeURIComponent(org)}/repos`;
|
|
2120
|
+
}
|
|
2121
|
+
async function fetchAllRepos(client, org) {
|
|
2122
|
+
return fetchAllPages(client, reposPath(org));
|
|
2123
|
+
}
|
|
2124
|
+
function toRow3(repo) {
|
|
2125
|
+
return {
|
|
2126
|
+
name: repo.name,
|
|
2127
|
+
url: repo.url,
|
|
2128
|
+
languages: repo.languages.join(", "),
|
|
2129
|
+
frameworks: repo.frameworks.join(", "),
|
|
2130
|
+
tags: repo.tags.join(", "),
|
|
2131
|
+
size: repo.sizeBucket ?? "",
|
|
2132
|
+
stars: repo.stars,
|
|
2133
|
+
system: repo.isSystem ? "yes" : ""
|
|
2134
|
+
};
|
|
2135
|
+
}
|
|
2136
|
+
function syncWarning(repo) {
|
|
2137
|
+
return `warning: GitHub metadata sync failed; retry with \`gauge repos resync ${repo.url}\``;
|
|
2138
|
+
}
|
|
2139
|
+
function register10(program2) {
|
|
2140
|
+
const repos = program2.command("repos").description("Manage repositories");
|
|
2141
|
+
repos.command("list").description("List repositories (yours + read-only Gauge-provided ones)").action(async (_opts, command) => {
|
|
2142
|
+
const org = resolveOrg(command);
|
|
2143
|
+
const output = resolveOutput(command);
|
|
2144
|
+
const items = await fetchAllRepos(createClient(), org);
|
|
2145
|
+
if (output === "json") {
|
|
2146
|
+
printJson(items);
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2149
|
+
printItems(items.map(toRow3), "table");
|
|
2150
|
+
});
|
|
2151
|
+
repos.command("add <url>").description("Add a repository (pulls languages/size/stars from GitHub)").option(
|
|
2152
|
+
"--ref <ref>",
|
|
2153
|
+
"reserved \u2014 not persisted yet; pin a ref per schedule instead"
|
|
2154
|
+
).action(async (url, opts, command) => {
|
|
2155
|
+
const org = resolveOrg(command);
|
|
2156
|
+
const output = resolveOutput(command);
|
|
2157
|
+
if (opts.ref !== void 0) {
|
|
2158
|
+
console.error(
|
|
2159
|
+
"warning: --ref is not persisted yet \u2014 runs still target the repository's default branch. Pin a ref per schedule with `gauge schedules add --ref` instead."
|
|
2160
|
+
);
|
|
2161
|
+
}
|
|
2162
|
+
const result = await createClient().post(reposPath(org), {
|
|
2163
|
+
url,
|
|
2164
|
+
ref: opts.ref
|
|
2165
|
+
});
|
|
2166
|
+
if (output === "json") {
|
|
2167
|
+
printJson(result);
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
console.log(`Added ${result.repo.name} (${result.repo.url})`);
|
|
2171
|
+
if (!result.synced) console.error(syncWarning(result.repo));
|
|
2172
|
+
});
|
|
2173
|
+
repos.command("resync <url>").description("Re-fetch a repository's GitHub metadata").action(async (url, _opts, command) => {
|
|
2174
|
+
const org = resolveOrg(command);
|
|
2175
|
+
const output = resolveOutput(command);
|
|
2176
|
+
const result = await createClient().post(
|
|
2177
|
+
`${reposPath(org)}/resync`,
|
|
2178
|
+
{ url }
|
|
2179
|
+
);
|
|
2180
|
+
if (output === "json") {
|
|
2181
|
+
printJson(result);
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
if (!result.synced) {
|
|
2185
|
+
throw new CliError(
|
|
2186
|
+
"internal",
|
|
2187
|
+
"GitHub metadata sync failed (is the URL a public GitHub repository?)"
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2190
|
+
console.log(`Re-synced ${result.repo.name} (${result.repo.url})`);
|
|
2191
|
+
});
|
|
2192
|
+
repos.command("rm <url>").description("Remove a repository from the catalog").action(async (url, _opts, command) => {
|
|
2193
|
+
const org = resolveOrg(command);
|
|
2194
|
+
const output = resolveOutput(command);
|
|
2195
|
+
await createClient().delete(reposPath(org), { url });
|
|
2196
|
+
if (output !== "json") console.log(`Removed ${url}`);
|
|
2197
|
+
});
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
// src/commands/runs.ts
|
|
2201
|
+
var runs_exports2 = {};
|
|
2202
|
+
__export(runs_exports2, {
|
|
2203
|
+
register: () => register11
|
|
2204
|
+
});
|
|
2205
|
+
function runPath(id) {
|
|
2206
|
+
return `/api/v1/runs/${encodeURIComponent(id)}`;
|
|
2207
|
+
}
|
|
2208
|
+
function streamPath(id) {
|
|
2209
|
+
return `/api/runs/${encodeURIComponent(id)}/stream`;
|
|
2210
|
+
}
|
|
2211
|
+
function fmtDuration(ms) {
|
|
2212
|
+
if (ms == null) return "";
|
|
2213
|
+
const s = Math.round(ms / 1e3);
|
|
2214
|
+
return s >= 60 ? `${Math.floor(s / 60)}m${s % 60}s` : `${s}s`;
|
|
2215
|
+
}
|
|
2216
|
+
function fmtUsd(v) {
|
|
2217
|
+
return v == null ? "" : `$${v.toFixed(2)}`;
|
|
2218
|
+
}
|
|
2219
|
+
function compact(value, max = 140) {
|
|
2220
|
+
const s = typeof value === "string" ? value : JSON.stringify(value) ?? "null";
|
|
2221
|
+
const one = s.replace(/\s+/g, " ").trim();
|
|
2222
|
+
return one.length > max ? `${one.slice(0, max - 1)}\u2026` : one;
|
|
2223
|
+
}
|
|
2224
|
+
function toRow4(run) {
|
|
2225
|
+
return {
|
|
2226
|
+
id: run.id,
|
|
2227
|
+
status: run.status,
|
|
2228
|
+
agent: run.agent,
|
|
2229
|
+
experiment: run.experiment ?? "",
|
|
2230
|
+
turns: run.turns ?? "",
|
|
2231
|
+
cost: fmtUsd(run.usdCost),
|
|
2232
|
+
duration: fmtDuration(run.durationMs),
|
|
2233
|
+
created: run.createdAt,
|
|
2234
|
+
prompt: compact(run.promptSnippet, 48)
|
|
2235
|
+
};
|
|
2236
|
+
}
|
|
2237
|
+
function detailLines(run) {
|
|
2238
|
+
return [
|
|
2239
|
+
["Id", run.id],
|
|
2240
|
+
["Status", run.status],
|
|
2241
|
+
["Agent", run.agent],
|
|
2242
|
+
["Model", run.model || "(default)"],
|
|
2243
|
+
["Prompt", compact(run.promptText, 120)],
|
|
2244
|
+
["Prompt id", run.promptId],
|
|
2245
|
+
["Batch", run.batchId],
|
|
2246
|
+
["Experiment", run.experiment ?? "\u2014"],
|
|
2247
|
+
["Scenario", run.scenario],
|
|
2248
|
+
["Funding", run.funding],
|
|
2249
|
+
[
|
|
2250
|
+
"Repo",
|
|
2251
|
+
run.repoUrl ? `${run.repoUrl}${run.repoRef ? ` @ ${run.repoRef}` : ""}` : "\u2014"
|
|
2252
|
+
],
|
|
2253
|
+
["Attempt", String(run.attempt)],
|
|
2254
|
+
["Turns", run.turns == null ? "\u2014" : String(run.turns)],
|
|
2255
|
+
["Cost", fmtUsd(run.usdCost) || "\u2014"],
|
|
2256
|
+
["Duration", fmtDuration(run.durationMs) || "\u2014"],
|
|
2257
|
+
["Exit reason", run.exitReason ?? "\u2014"],
|
|
2258
|
+
["Failure reason", run.failureReason ?? "\u2014"],
|
|
2259
|
+
["Cancel requested", run.cancelRequested ? "yes" : "no"],
|
|
2260
|
+
["Diff", run.hasDiff ? `yes (gauge runs diff ${run.id})` : "no"],
|
|
2261
|
+
["Created", run.createdAt],
|
|
2262
|
+
["Started", run.startedAt ?? "\u2014"],
|
|
2263
|
+
["Finished", run.finishedAt ?? "\u2014"]
|
|
2264
|
+
];
|
|
2265
|
+
}
|
|
2266
|
+
function parseEvent(data) {
|
|
2267
|
+
try {
|
|
2268
|
+
const parsed = runEventSchema.safeParse(JSON.parse(data));
|
|
2269
|
+
return parsed.success ? parsed.data : null;
|
|
2270
|
+
} catch {
|
|
2271
|
+
return null;
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
function formatEventLine(e) {
|
|
2275
|
+
const rec = e;
|
|
2276
|
+
const head = `[t${e.turn}#${e.seq}]`;
|
|
2277
|
+
switch (e.type) {
|
|
2278
|
+
case "run_meta": {
|
|
2279
|
+
const agent = rec.agent;
|
|
2280
|
+
const model = agent?.model ? ` model=${agent.model}` : "";
|
|
2281
|
+
return `${head} run_meta agent=${agent?.name ?? "?"}${model}`;
|
|
2282
|
+
}
|
|
2283
|
+
case "agent_message":
|
|
2284
|
+
return `${head} agent: ${compact(rec.text)}`;
|
|
2285
|
+
case "reasoning":
|
|
2286
|
+
return `${head} reasoning: ${compact(rec.text)}`;
|
|
2287
|
+
case "tool_call":
|
|
2288
|
+
return `${head} tool_call ${String(rec.name)}: ${compact(rec.input)}`;
|
|
2289
|
+
case "tool_result":
|
|
2290
|
+
return `${head} tool_result${rec.is_error ? " ERROR" : ""}: ${compact(rec.output, 100)}`;
|
|
2291
|
+
case "run_result":
|
|
2292
|
+
return `${head} run_result ${String(rec.exit_reason)} turns=${String(rec.turns)} cost=${fmtUsd(rec.cost_usd)} duration=${fmtDuration(rec.duration_ms)}`;
|
|
2293
|
+
default: {
|
|
2294
|
+
const {
|
|
2295
|
+
run_id: _runId,
|
|
2296
|
+
seq: _seq,
|
|
2297
|
+
turn: _turn,
|
|
2298
|
+
ts: _ts,
|
|
2299
|
+
type: _type,
|
|
2300
|
+
...payload
|
|
2301
|
+
} = rec;
|
|
2302
|
+
return `${head} ${e.type} ${compact(payload)}`;
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set([
|
|
2307
|
+
"SUCCEEDED",
|
|
2308
|
+
"FAILED",
|
|
2309
|
+
"TIMED_OUT",
|
|
2310
|
+
"CANCELED"
|
|
2311
|
+
]);
|
|
2312
|
+
var STATUS_POLL_MS = 5e3;
|
|
2313
|
+
var TERMINAL_DRAIN_MS = 2e3;
|
|
2314
|
+
async function streamRun(id, json, opts) {
|
|
2315
|
+
const client = createClient();
|
|
2316
|
+
const controller = new AbortController();
|
|
2317
|
+
let timer;
|
|
2318
|
+
let poller;
|
|
2319
|
+
let drainTimer;
|
|
2320
|
+
let gotResult = false;
|
|
2321
|
+
let sawData = false;
|
|
2322
|
+
const arm = (ms) => {
|
|
2323
|
+
if (timer) clearTimeout(timer);
|
|
2324
|
+
if (ms > 0) timer = setTimeout(() => controller.abort(), ms);
|
|
2325
|
+
};
|
|
2326
|
+
arm(opts.idleMs > 0 ? opts.firstEventMs : 0);
|
|
2327
|
+
if (opts.pollStatus) {
|
|
2328
|
+
let inFlight = false;
|
|
2329
|
+
poller = setInterval(() => {
|
|
2330
|
+
if (inFlight || drainTimer) return;
|
|
2331
|
+
inFlight = true;
|
|
2332
|
+
client.get(runPath(id)).then((run) => {
|
|
2333
|
+
if (drainTimer || !TERMINAL_STATUSES.has(run.status)) return;
|
|
2334
|
+
if (poller) clearInterval(poller);
|
|
2335
|
+
drainTimer = setTimeout(() => {
|
|
2336
|
+
if (!gotResult) {
|
|
2337
|
+
console.error(`Run is ${run.status}; no more events.`);
|
|
2338
|
+
}
|
|
2339
|
+
controller.abort();
|
|
2340
|
+
}, TERMINAL_DRAIN_MS);
|
|
2341
|
+
}).catch(() => {
|
|
2342
|
+
}).finally(() => {
|
|
2343
|
+
inFlight = false;
|
|
2344
|
+
});
|
|
2345
|
+
}, STATUS_POLL_MS);
|
|
2346
|
+
}
|
|
2347
|
+
try {
|
|
2348
|
+
await client.sse(
|
|
2349
|
+
streamPath(id),
|
|
2350
|
+
(frame) => {
|
|
2351
|
+
const event = parseEvent(frame.data);
|
|
2352
|
+
if (!event) return;
|
|
2353
|
+
sawData = true;
|
|
2354
|
+
if (json) console.log(frame.data);
|
|
2355
|
+
else console.log(formatEventLine(event));
|
|
2356
|
+
if (event.type === "run_result") {
|
|
2357
|
+
gotResult = true;
|
|
2358
|
+
controller.abort();
|
|
2359
|
+
} else if (opts.stopAtLiveMs !== void 0 && Date.parse(event.ts) >= opts.stopAtLiveMs) {
|
|
2360
|
+
gotResult = true;
|
|
2361
|
+
controller.abort();
|
|
2362
|
+
} else arm(opts.idleMs);
|
|
2363
|
+
},
|
|
2364
|
+
{
|
|
2365
|
+
signal: controller.signal,
|
|
2366
|
+
// Keepalives (`: open`, `: ping`) prove the connection is alive while
|
|
2367
|
+
// subscribe/backfill is still warming up, so they extend the
|
|
2368
|
+
// first-event grace — but never the post-backfill idle window, or
|
|
2369
|
+
// `logs` would follow the heartbeat forever and never exit.
|
|
2370
|
+
onComment: () => {
|
|
2371
|
+
if (!sawData && opts.idleMs > 0) arm(opts.firstEventMs);
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
);
|
|
2375
|
+
} finally {
|
|
2376
|
+
if (timer) clearTimeout(timer);
|
|
2377
|
+
if (poller) clearInterval(poller);
|
|
2378
|
+
if (drainTimer) clearTimeout(drainTimer);
|
|
2379
|
+
}
|
|
2380
|
+
}
|
|
2381
|
+
function register11(program2) {
|
|
2382
|
+
const runs = program2.command("runs").description("Inspect and manage runs");
|
|
2383
|
+
runs.command("list").description("List runs (newest first)").option("--status <status>", "filter by status, e.g. running,queued").option("--batch <id>", "filter by batch").option("--prompt <id>", "filter by prompt").option("--experiment <experiment>", "filter by experiment label").option("--since <window>", "e.g. 24h, 7d, or an ISO date").option("--limit <n>", "page size (1-200)", "50").option("--cursor <cursor>", "resume from a previous page's nextCursor").action(
|
|
2384
|
+
async (opts, command) => {
|
|
2385
|
+
const org = resolveOrg(command);
|
|
2386
|
+
const output = resolveOutput(command);
|
|
2387
|
+
const page = await createClient().get(
|
|
2388
|
+
`/api/v1/orgs/${encodeURIComponent(org)}/runs`,
|
|
2389
|
+
{
|
|
2390
|
+
status: opts.status,
|
|
2391
|
+
batch: opts.batch,
|
|
2392
|
+
prompt: opts.prompt,
|
|
2393
|
+
experiment: opts.experiment,
|
|
2394
|
+
since: opts.since,
|
|
2395
|
+
limit: opts.limit,
|
|
2396
|
+
cursor: opts.cursor
|
|
2397
|
+
}
|
|
2398
|
+
);
|
|
2399
|
+
if (output === "json") {
|
|
2400
|
+
printJson(page);
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
printItems(page.items.map(toRow4), "table");
|
|
2404
|
+
if (page.nextCursor) {
|
|
2405
|
+
console.error(
|
|
2406
|
+
`(more results \u2014 rerun with --cursor ${page.nextCursor})`
|
|
2407
|
+
);
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
);
|
|
2411
|
+
runs.command("get <id>").description("Show a run (status, cost, turns, exit reason)").action(async (id, _opts, command) => {
|
|
2412
|
+
const run = await createClient().get(runPath(id));
|
|
2413
|
+
if (resolveOutput(command) === "json") {
|
|
2414
|
+
printJson(run);
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2417
|
+
const lines = detailLines(run);
|
|
2418
|
+
const width = Math.max(...lines.map(([label]) => label.length));
|
|
2419
|
+
for (const [label, value] of lines) {
|
|
2420
|
+
console.log(`${label.padEnd(width)} ${value}`);
|
|
2421
|
+
}
|
|
2422
|
+
});
|
|
2423
|
+
runs.command("watch <id>").description(
|
|
2424
|
+
"Stream normalized trace events live (SSE; exits when the run finishes)"
|
|
2425
|
+
).action(async (id, _opts, command) => {
|
|
2426
|
+
await streamRun(id, resolveOutput(command) === "json", {
|
|
2427
|
+
idleMs: 0,
|
|
2428
|
+
firstEventMs: 0,
|
|
2429
|
+
pollStatus: true
|
|
2430
|
+
});
|
|
2431
|
+
});
|
|
2432
|
+
runs.command("logs <id>").description("Dump the run's trace events so far, then exit (non-follow)").action(async (id, _opts, command) => {
|
|
2433
|
+
await streamRun(id, resolveOutput(command) === "json", {
|
|
2434
|
+
idleMs: 2e3,
|
|
2435
|
+
firstEventMs: 15e3,
|
|
2436
|
+
stopAtLiveMs: Date.now() - 2e3
|
|
2437
|
+
});
|
|
2438
|
+
});
|
|
2439
|
+
runs.command("diff <id>").description("Print the run's working-tree diff to stdout").action(async (id) => {
|
|
2440
|
+
const diff = await createClient().get(`${runPath(id)}/diff`);
|
|
2441
|
+
process.stdout.write(diff);
|
|
2442
|
+
});
|
|
2443
|
+
runs.command("cancel <id>").description("Request cancellation of a run").action(async (id, _opts, command) => {
|
|
2444
|
+
const result = await createClient().post(
|
|
2445
|
+
`${runPath(id)}/cancel`
|
|
2446
|
+
);
|
|
2447
|
+
if (resolveOutput(command) === "json") {
|
|
2448
|
+
printJson(result);
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
console.log(
|
|
2452
|
+
`Run ${result.id}: ${result.status}${result.cancelRequested ? " (cancel requested)" : ""}`
|
|
2453
|
+
);
|
|
2454
|
+
if (result.note) console.error(result.note);
|
|
2455
|
+
});
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
// src/commands/schedules.ts
|
|
2459
|
+
var schedules_exports2 = {};
|
|
2460
|
+
__export(schedules_exports2, {
|
|
2461
|
+
register: () => register12
|
|
2462
|
+
});
|
|
2463
|
+
var CADENCE_CHOICES = [
|
|
2464
|
+
"none",
|
|
2465
|
+
"daily",
|
|
2466
|
+
"weekly",
|
|
2467
|
+
"monthly",
|
|
2468
|
+
"days-per-week"
|
|
2469
|
+
];
|
|
2470
|
+
function parseCadence(raw) {
|
|
2471
|
+
const upper = raw.trim().toUpperCase().replace(/-/g, "_");
|
|
2472
|
+
if (!["NONE", "DAILY", "WEEKLY", "MONTHLY", "DAYS_PER_WEEK"].includes(upper)) {
|
|
2473
|
+
throw new CliError(
|
|
2474
|
+
"usage",
|
|
2475
|
+
`invalid --cadence "${raw}" (expected: ${CADENCE_CHOICES.join(" | ")})`
|
|
2476
|
+
);
|
|
2477
|
+
}
|
|
2478
|
+
return upper;
|
|
2479
|
+
}
|
|
2480
|
+
function parseDays(raw) {
|
|
2481
|
+
const n = Number(raw);
|
|
2482
|
+
if (!Number.isInteger(n) || n < 1 || n > 7)
|
|
2483
|
+
throw new CliError("usage", "--days-per-week must be an integer 1-7");
|
|
2484
|
+
return n;
|
|
2485
|
+
}
|
|
2486
|
+
function toRow5(s) {
|
|
2487
|
+
return {
|
|
2488
|
+
id: s.id,
|
|
2489
|
+
repo: s.repoUrl ?? "(default scaffold)",
|
|
2490
|
+
ref: s.repoRef ?? "",
|
|
2491
|
+
cadence: s.cadence,
|
|
2492
|
+
days: s.daysPerWeek ?? "",
|
|
2493
|
+
agents: s.agents.join(", "),
|
|
2494
|
+
experiment: s.experiment ?? ""
|
|
2495
|
+
};
|
|
2496
|
+
}
|
|
2497
|
+
function register12(program2) {
|
|
2498
|
+
const group = program2.command("schedules").description("Manage prompt schedules");
|
|
2499
|
+
group.command("list").description("List schedules for a prompt").requiredOption("--prompt <id>", "prompt id").action(async (opts, cmd) => {
|
|
2500
|
+
const org = resolveOrg(cmd);
|
|
2501
|
+
const res = await createClient().get(
|
|
2502
|
+
`${promptsPath(org)}/${encodeURIComponent(opts.prompt)}/schedules`
|
|
2503
|
+
);
|
|
2504
|
+
if (resolveOutput(cmd) === "json") printJson(res.items);
|
|
2505
|
+
else printItems(res.items.map(toRow5), "table");
|
|
2506
|
+
});
|
|
2507
|
+
group.command("add").description(
|
|
2508
|
+
"Add a schedule (a repo target + cadence + agents) to a prompt"
|
|
2509
|
+
).requiredOption("--prompt <id>", "prompt id").requiredOption(
|
|
2510
|
+
"--agent <agents>",
|
|
2511
|
+
"comma-separated agents, e.g. claude-code,codex"
|
|
2512
|
+
).option("--repo <url>", "repository URL (default: the Gauge scaffold)").option("--ref <ref>", "branch, tag, or commit").option("--cadence <cadence>", CADENCE_CHOICES.join(" | "), "none").option("--days-per-week <n>", "1-7 (with --cadence days-per-week)").action(
|
|
2513
|
+
async (opts, cmd) => {
|
|
2514
|
+
const org = resolveOrg(cmd);
|
|
2515
|
+
const body = {
|
|
2516
|
+
agents: parseAgents(opts.agent),
|
|
2517
|
+
cadence: parseCadence(opts.cadence),
|
|
2518
|
+
repoUrl: opts.repo,
|
|
2519
|
+
repoRef: opts.ref
|
|
2520
|
+
};
|
|
2521
|
+
if (opts.daysPerWeek !== void 0)
|
|
2522
|
+
body.daysPerWeek = parseDays(opts.daysPerWeek);
|
|
2523
|
+
const schedule = await createClient().post(
|
|
2524
|
+
`${promptsPath(org)}/${encodeURIComponent(opts.prompt)}/schedules`,
|
|
2525
|
+
body
|
|
2526
|
+
);
|
|
2527
|
+
if (resolveOutput(cmd) === "json") printJson(schedule);
|
|
2528
|
+
else console.log(`Added schedule ${schedule.id}`);
|
|
2529
|
+
}
|
|
2530
|
+
);
|
|
2531
|
+
group.command("edit <id>").description("Update a schedule").option("--agent <agents>", "comma-separated agents").option("--repo <url>", "repository URL").option("--clear-repo", "reset to the default scaffold").option("--ref <ref>", "branch, tag, or commit").option("--clear-ref", "reset to the repo's default branch").option("--cadence <cadence>", CADENCE_CHOICES.join(" | ")).option("--days-per-week <n>", "1-7 (with cadence days-per-week)").action(
|
|
2532
|
+
async (id, opts, cmd) => {
|
|
2533
|
+
const body = {};
|
|
2534
|
+
if (opts.agent !== void 0) body.agents = parseAgents(opts.agent);
|
|
2535
|
+
if (opts.repo !== void 0 && opts.clearRepo)
|
|
2536
|
+
throw new CliError("usage", "--repo and --clear-repo conflict");
|
|
2537
|
+
if (opts.clearRepo) body.repoUrl = null;
|
|
2538
|
+
else if (opts.repo !== void 0) body.repoUrl = opts.repo;
|
|
2539
|
+
if (opts.ref !== void 0 && opts.clearRef)
|
|
2540
|
+
throw new CliError("usage", "--ref and --clear-ref conflict");
|
|
2541
|
+
if (opts.clearRef) body.repoRef = null;
|
|
2542
|
+
else if (opts.ref !== void 0) body.repoRef = opts.ref;
|
|
2543
|
+
if (opts.cadence !== void 0)
|
|
2544
|
+
body.cadence = parseCadence(opts.cadence);
|
|
2545
|
+
if (opts.daysPerWeek !== void 0)
|
|
2546
|
+
body.daysPerWeek = parseDays(opts.daysPerWeek);
|
|
2547
|
+
if (Object.keys(body).length === 0)
|
|
2548
|
+
throw new CliError("usage", "nothing to change");
|
|
2549
|
+
const schedule = await createClient().patch(
|
|
2550
|
+
`/api/v1/schedules/${encodeURIComponent(id)}`,
|
|
2551
|
+
body
|
|
2552
|
+
);
|
|
2553
|
+
if (resolveOutput(cmd) === "json") printJson(schedule);
|
|
2554
|
+
else console.log(`Updated schedule ${schedule.id}`);
|
|
2555
|
+
}
|
|
2556
|
+
);
|
|
2557
|
+
group.command("rm <id>").description("Delete a schedule").action(async (id, _opts, cmd) => {
|
|
2558
|
+
await createClient().delete(
|
|
2559
|
+
`/api/v1/schedules/${encodeURIComponent(id)}`
|
|
2560
|
+
);
|
|
2561
|
+
if (resolveOutput(cmd) !== "json") console.log(`Deleted schedule ${id}`);
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
// src/commands/tags.ts
|
|
2566
|
+
var tags_exports2 = {};
|
|
2567
|
+
__export(tags_exports2, {
|
|
2568
|
+
register: () => register13
|
|
2569
|
+
});
|
|
2570
|
+
function tagsPath(org) {
|
|
2571
|
+
return `/api/v1/orgs/${encodeURIComponent(org)}/tags`;
|
|
2572
|
+
}
|
|
2573
|
+
function register13(program2) {
|
|
2574
|
+
const group = program2.command("tags").description("Manage org tags");
|
|
2575
|
+
group.command("list").description("List tags").action(async (_opts, cmd) => {
|
|
2576
|
+
const org = resolveOrg(cmd);
|
|
2577
|
+
const items = await fetchAllPages(createClient(), tagsPath(org));
|
|
2578
|
+
if (resolveOutput(cmd) === "json") printJson(items);
|
|
2579
|
+
else printItems(items, "table");
|
|
2580
|
+
});
|
|
2581
|
+
group.command("add <name>").description("Add a tag").option("--color <color>", "palette color (e.g. blue, grape; default gray)").action(async (name, opts, cmd) => {
|
|
2582
|
+
const org = resolveOrg(cmd);
|
|
2583
|
+
const tag = await createClient().post(tagsPath(org), {
|
|
2584
|
+
name,
|
|
2585
|
+
color: opts.color
|
|
2586
|
+
});
|
|
2587
|
+
if (resolveOutput(cmd) === "json") printJson(tag);
|
|
2588
|
+
else console.log(`Created tag ${tag.id} (${tag.name}, ${tag.color})`);
|
|
2589
|
+
});
|
|
2590
|
+
group.command("rename <tag> <new-name>").description("Rename a tag (by id or current name)").option("--color <color>", "also change the color").action(
|
|
2591
|
+
async (tag, newName, opts, cmd) => {
|
|
2592
|
+
const org = resolveOrg(cmd);
|
|
2593
|
+
const updated = await createClient().patch(
|
|
2594
|
+
`${tagsPath(org)}/${encodeURIComponent(tag)}`,
|
|
2595
|
+
{ name: newName, color: opts.color }
|
|
2596
|
+
);
|
|
2597
|
+
if (resolveOutput(cmd) === "json") printJson(updated);
|
|
2598
|
+
else
|
|
2599
|
+
console.log(
|
|
2600
|
+
`Renamed tag ${updated.id} to ${updated.name} (${updated.color})`
|
|
2601
|
+
);
|
|
2602
|
+
}
|
|
2603
|
+
);
|
|
2604
|
+
group.command("rm <tag>").description("Delete a tag (by id or name)").action(async (tag, _opts, cmd) => {
|
|
2605
|
+
const org = resolveOrg(cmd);
|
|
2606
|
+
await createClient().delete(
|
|
2607
|
+
`${tagsPath(org)}/${encodeURIComponent(tag)}`
|
|
2608
|
+
);
|
|
2609
|
+
if (resolveOutput(cmd) !== "json") console.log(`Deleted tag ${tag}`);
|
|
2610
|
+
});
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
// src/commands/topics.ts
|
|
2614
|
+
var topics_exports2 = {};
|
|
2615
|
+
__export(topics_exports2, {
|
|
2616
|
+
register: () => register14
|
|
2617
|
+
});
|
|
2618
|
+
function topicsPath(org) {
|
|
2619
|
+
return `/api/v1/orgs/${encodeURIComponent(org)}/topics`;
|
|
2620
|
+
}
|
|
2621
|
+
function register14(program2) {
|
|
2622
|
+
const group = program2.command("topics").description("Manage org topics");
|
|
2623
|
+
group.command("list").description("List topics").action(async (_opts, cmd) => {
|
|
2624
|
+
const org = resolveOrg(cmd);
|
|
2625
|
+
const items = await fetchAllPages(createClient(), topicsPath(org));
|
|
2626
|
+
if (resolveOutput(cmd) === "json") printJson(items);
|
|
2627
|
+
else printItems(items, "table");
|
|
2628
|
+
});
|
|
2629
|
+
group.command("create <name>").alias("add").description("Create a topic").action(async (name, _opts, cmd) => {
|
|
2630
|
+
const org = resolveOrg(cmd);
|
|
2631
|
+
const topic = await createClient().post(topicsPath(org), {
|
|
2632
|
+
name
|
|
2633
|
+
});
|
|
2634
|
+
if (resolveOutput(cmd) === "json") printJson(topic);
|
|
2635
|
+
else console.log(`Created topic ${topic.id} (${topic.name})`);
|
|
2636
|
+
});
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
// src/commands/usage.ts
|
|
2640
|
+
var usage_exports = {};
|
|
2641
|
+
__export(usage_exports, {
|
|
2642
|
+
register: () => register15
|
|
2643
|
+
});
|
|
2644
|
+
function register15(program2) {
|
|
2645
|
+
program2.command("usage").description("List credit ledger entries").option("--since <window>", 'e.g. "30d", "24h", or an ISO date').option("--limit <n>", "page size (1-200)", parseLimit, 50).option("--cursor <cursor>", "resume from a previous page's nextCursor").action(
|
|
2646
|
+
async (opts, cmd) => {
|
|
2647
|
+
const org = resolveOrg(cmd);
|
|
2648
|
+
const res = await createClient().get(
|
|
2649
|
+
`/api/v1/orgs/${encodeURIComponent(org)}/usage`,
|
|
2650
|
+
{ since: opts.since, limit: opts.limit, cursor: opts.cursor }
|
|
2651
|
+
);
|
|
2652
|
+
if (resolveOutput(cmd) === "json") {
|
|
2653
|
+
printJson(res);
|
|
2654
|
+
return;
|
|
2655
|
+
}
|
|
2656
|
+
printItems(
|
|
2657
|
+
res.items.map((e) => ({
|
|
2658
|
+
createdAt: e.createdAt,
|
|
2659
|
+
kind: e.kind,
|
|
2660
|
+
delta: e.delta,
|
|
2661
|
+
runId: e.runId ?? ""
|
|
2662
|
+
})),
|
|
2663
|
+
"table"
|
|
2664
|
+
);
|
|
2665
|
+
if (res.items.length === 0 && !opts.cursor) {
|
|
2666
|
+
try {
|
|
2667
|
+
const status = await createClient().get(
|
|
2668
|
+
`/api/v1/orgs/${encodeURIComponent(org)}/billing`
|
|
2669
|
+
);
|
|
2670
|
+
if (!status.enforced) {
|
|
2671
|
+
process.stderr.write(
|
|
2672
|
+
"Billing is not enforced for this org, so runs don't create ledger entries.\n"
|
|
2673
|
+
);
|
|
2674
|
+
}
|
|
2675
|
+
} catch {
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
if (res.nextCursor) {
|
|
2679
|
+
process.stderr.write(
|
|
2680
|
+
`(more results \u2014 rerun with --cursor ${res.nextCursor})
|
|
2681
|
+
`
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
);
|
|
2686
|
+
}
|
|
2687
|
+
|
|
2688
|
+
// src/index.ts
|
|
2689
|
+
var program = new Command();
|
|
2690
|
+
program.name("gauge").description(
|
|
2691
|
+
"Gauge customer CLI \u2014 prompts, repos, runs & org settings over the Gauge API"
|
|
2692
|
+
).version(CLI_VERSION).option(
|
|
2693
|
+
"--org <org>",
|
|
2694
|
+
"organization slug or public id (default: `gauge config get defaultOrg`)"
|
|
2695
|
+
).addOption(
|
|
2696
|
+
new Option2("-o, --output <format>", "output format").choices([...OUTPUT_FORMATS]).default("table")
|
|
2697
|
+
);
|
|
2698
|
+
for (const group of [
|
|
2699
|
+
auth_exports,
|
|
2700
|
+
orgs_exports,
|
|
2701
|
+
prompts_exports2,
|
|
2702
|
+
schedules_exports2,
|
|
2703
|
+
repos_exports2,
|
|
2704
|
+
runs_exports2,
|
|
2705
|
+
apply_exports,
|
|
2706
|
+
batches_exports,
|
|
2707
|
+
brands_exports2,
|
|
2708
|
+
tags_exports2,
|
|
2709
|
+
topics_exports2,
|
|
2710
|
+
keys_exports2,
|
|
2711
|
+
billing_exports2,
|
|
2712
|
+
usage_exports,
|
|
2713
|
+
config_exports
|
|
2714
|
+
]) {
|
|
2715
|
+
group.register(program);
|
|
2716
|
+
}
|
|
2717
|
+
program.exitOverride();
|
|
2718
|
+
try {
|
|
2719
|
+
await program.parseAsync(process.argv);
|
|
2720
|
+
} catch (error) {
|
|
2721
|
+
if (error instanceof CommanderError) {
|
|
2722
|
+
process.exit(error.exitCode === 0 ? 0 : 2);
|
|
2723
|
+
}
|
|
2724
|
+
if (error instanceof CliError) {
|
|
2725
|
+
process.stderr.write(`error: ${error.message}
|
|
2726
|
+
`);
|
|
2727
|
+
process.exit(error.exitCode);
|
|
2728
|
+
}
|
|
2729
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2730
|
+
process.stderr.write(`error: ${message}
|
|
2731
|
+
`);
|
|
2732
|
+
process.exit(1);
|
|
2733
|
+
}
|