@azx-pbc/helix-cli 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist/helix.js +525 -157
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,9 +51,15 @@ helix rollback [number] # revert the live p
|
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
`deploy` uploads the bundle as a **preview**; `--promote` flips it live in the
|
|
54
|
-
same step (architecture §5.1). `visibility` is `
|
|
54
|
+
same step (architecture §5.1). `visibility` is `internal | group:<id> | password
|
|
55
55
|
| public`.
|
|
56
56
|
|
|
57
|
+
> **Breaking in 0.2.0:** the `private` visibility mode was renamed to `internal`.
|
|
58
|
+
> It never checked _which_ user signed in, only that someone had. Passing the old
|
|
59
|
+
> value now **errors** rather than mapping to the new one, deliberately: the name
|
|
60
|
+
> is reserved for a future owner-only mode, so a silent alias would come to mean
|
|
61
|
+
> the opposite of what it says. Update scripts to use `internal`.
|
|
62
|
+
|
|
57
63
|
## Authentication (M3)
|
|
58
64
|
|
|
59
65
|
Two paths, in precedence order:
|
package/dist/helix.js
CHANGED
|
@@ -19,17 +19,17 @@ function parseCliArgs(argv) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
// src/client.ts
|
|
22
|
-
import { z as
|
|
22
|
+
import { z as z19 } from "zod";
|
|
23
23
|
|
|
24
24
|
// ../shared/src/visibility.ts
|
|
25
25
|
import { z } from "zod";
|
|
26
26
|
var VisibilitySchema = z.discriminatedUnion("mode", [
|
|
27
|
-
z.object({ mode: z.literal("
|
|
27
|
+
z.object({ mode: z.literal("internal") }),
|
|
28
28
|
z.object({ mode: z.literal("group"), groupId: z.string().min(1) }),
|
|
29
29
|
z.object({ mode: z.literal("password") }),
|
|
30
30
|
z.object({ mode: z.literal("public") })
|
|
31
31
|
]);
|
|
32
|
-
var VISIBILITY_MODES = ["
|
|
32
|
+
var VISIBILITY_MODES = ["internal", "group", "password", "public"];
|
|
33
33
|
var VisibilityModeSchema = z.enum(VISIBILITY_MODES);
|
|
34
34
|
|
|
35
35
|
// ../shared/src/app.ts
|
|
@@ -104,6 +104,26 @@ var FetchCapabilitySchema = z4.object({
|
|
|
104
104
|
/** Per-app daily proxied-request budget; unset ⇒ unbounded (fetch-proxy §7). */
|
|
105
105
|
requestsPerDay: z4.int().positive().optional()
|
|
106
106
|
});
|
|
107
|
+
var SCOPE_SEGMENT = /^[A-Za-z0-9\-._~]+$/;
|
|
108
|
+
function isValidServiceWorkerScope(scope) {
|
|
109
|
+
if (!scope.startsWith("/") || !scope.endsWith("/")) return false;
|
|
110
|
+
const segments = scope.slice(1, -1).split("/");
|
|
111
|
+
const first = segments[0];
|
|
112
|
+
if (first === void 0) return false;
|
|
113
|
+
if (segments.some((s) => s === "" || s === "." || s === ".." || !SCOPE_SEGMENT.test(s))) {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
return !first.startsWith("_");
|
|
117
|
+
}
|
|
118
|
+
var OfflineCapabilitySchema = z4.object({
|
|
119
|
+
/**
|
|
120
|
+
* URL path prefix the worker controls, e.g. `/app/`. Leading and trailing
|
|
121
|
+
* slash required; never root, never a `_`-prefixed platform namespace.
|
|
122
|
+
*/
|
|
123
|
+
scope: z4.string().refine(isValidServiceWorkerScope, {
|
|
124
|
+
message: "scope must be a non-root path prefix with a leading and trailing slash, and must not start with a reserved `_` segment (e.g. `/app/`)"
|
|
125
|
+
})
|
|
126
|
+
});
|
|
107
127
|
var CapabilitiesSchema = z4.object({
|
|
108
128
|
llm: LlmCapabilitySchema.optional(),
|
|
109
129
|
data: DataCapabilitySchema.optional(),
|
|
@@ -112,7 +132,9 @@ var CapabilitiesSchema = z4.object({
|
|
|
112
132
|
/** Extra CSP `connect-src` origins for **direct** browser calls (§4.4). */
|
|
113
133
|
externalOrigins: z4.array(z4.url()).default([]),
|
|
114
134
|
/** Governed outbound HTTP via the fetch-proxy / egress plane (in build, M4.5). */
|
|
115
|
-
fetch: FetchCapabilitySchema.optional()
|
|
135
|
+
fetch: FetchCapabilitySchema.optional(),
|
|
136
|
+
/** Platform-owned, scope-confined service worker for offline cold boot (ADR-0035). */
|
|
137
|
+
offline: OfflineCapabilitySchema.optional()
|
|
116
138
|
});
|
|
117
139
|
var AppManifestSchema = z4.object({
|
|
118
140
|
/** App slug; matches `App.slug`. */
|
|
@@ -126,12 +148,84 @@ import { z as z5 } from "zod";
|
|
|
126
148
|
|
|
127
149
|
// ../shared/src/pricing.ts
|
|
128
150
|
var MODEL_PRICING = {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
"claude-
|
|
133
|
-
|
|
134
|
-
|
|
151
|
+
// Anthropic. NB `structuredOutputs` is deliberately absent on 4-7/4-6/sonnet-4-6:
|
|
152
|
+
// structured outputs are supported on Fable 5, Opus 4.8 and Haiku 4.5 but not on
|
|
153
|
+
// those three, so the flag is opt-in per model rather than per provider.
|
|
154
|
+
"claude-fable-5": {
|
|
155
|
+
inputPerMTok: 10,
|
|
156
|
+
outputPerMTok: 50,
|
|
157
|
+
provider: "anthropic",
|
|
158
|
+
structuredOutputs: true
|
|
159
|
+
},
|
|
160
|
+
"claude-opus-5": {
|
|
161
|
+
inputPerMTok: 5,
|
|
162
|
+
outputPerMTok: 25,
|
|
163
|
+
provider: "anthropic",
|
|
164
|
+
structuredOutputs: true
|
|
165
|
+
},
|
|
166
|
+
// NB list rates. Sonnet 5 has promotional $2/$10 pricing through 2026-08-31; this
|
|
167
|
+
// table drives the **cost gate**, so the list price is the safe number — it
|
|
168
|
+
// over-estimates spend during the promo rather than under-billing once it lapses.
|
|
169
|
+
"claude-sonnet-5": {
|
|
170
|
+
inputPerMTok: 3,
|
|
171
|
+
outputPerMTok: 15,
|
|
172
|
+
provider: "anthropic",
|
|
173
|
+
structuredOutputs: true
|
|
174
|
+
},
|
|
175
|
+
"claude-opus-4-8": {
|
|
176
|
+
inputPerMTok: 5,
|
|
177
|
+
outputPerMTok: 25,
|
|
178
|
+
provider: "anthropic",
|
|
179
|
+
structuredOutputs: true
|
|
180
|
+
},
|
|
181
|
+
"claude-opus-4-7": { inputPerMTok: 5, outputPerMTok: 25, provider: "anthropic" },
|
|
182
|
+
"claude-opus-4-6": { inputPerMTok: 5, outputPerMTok: 25, provider: "anthropic" },
|
|
183
|
+
"claude-sonnet-4-6": { inputPerMTok: 3, outputPerMTok: 15, provider: "anthropic" },
|
|
184
|
+
"claude-haiku-4-5": {
|
|
185
|
+
inputPerMTok: 1,
|
|
186
|
+
outputPerMTok: 5,
|
|
187
|
+
provider: "anthropic",
|
|
188
|
+
structuredOutputs: true
|
|
189
|
+
},
|
|
190
|
+
// OpenAI — VERIFY against current published rates before production billing.
|
|
191
|
+
// Every model here resolves to a snapshot new enough for `response_format`
|
|
192
|
+
// json_schema, so `structuredOutputs` is set across the board.
|
|
193
|
+
"gpt-4o": { inputPerMTok: 2.5, outputPerMTok: 10, provider: "openai", structuredOutputs: true },
|
|
194
|
+
"gpt-4o-mini": {
|
|
195
|
+
inputPerMTok: 0.15,
|
|
196
|
+
outputPerMTok: 0.6,
|
|
197
|
+
provider: "openai",
|
|
198
|
+
structuredOutputs: true
|
|
199
|
+
},
|
|
200
|
+
"gpt-4.1": { inputPerMTok: 2, outputPerMTok: 8, provider: "openai", structuredOutputs: true },
|
|
201
|
+
"gpt-4.1-mini": {
|
|
202
|
+
inputPerMTok: 0.4,
|
|
203
|
+
outputPerMTok: 1.6,
|
|
204
|
+
provider: "openai",
|
|
205
|
+
structuredOutputs: true
|
|
206
|
+
},
|
|
207
|
+
"gpt-4.1-nano": {
|
|
208
|
+
inputPerMTok: 0.1,
|
|
209
|
+
outputPerMTok: 0.4,
|
|
210
|
+
provider: "openai",
|
|
211
|
+
structuredOutputs: true
|
|
212
|
+
},
|
|
213
|
+
o3: {
|
|
214
|
+
inputPerMTok: 2,
|
|
215
|
+
outputPerMTok: 8,
|
|
216
|
+
provider: "openai",
|
|
217
|
+
reasoning: true,
|
|
218
|
+
minCompletionTokens: 25e3,
|
|
219
|
+
structuredOutputs: true
|
|
220
|
+
},
|
|
221
|
+
"o4-mini": {
|
|
222
|
+
inputPerMTok: 1.1,
|
|
223
|
+
outputPerMTok: 4.4,
|
|
224
|
+
provider: "openai",
|
|
225
|
+
reasoning: true,
|
|
226
|
+
minCompletionTokens: 25e3,
|
|
227
|
+
structuredOutputs: true
|
|
228
|
+
}
|
|
135
229
|
};
|
|
136
230
|
|
|
137
231
|
// ../shared/src/approval.ts
|
|
@@ -213,7 +307,7 @@ import { z as z7 } from "zod";
|
|
|
213
307
|
var CreateAppRequestSchema = z7.object({
|
|
214
308
|
slug: AppSchema.shape.slug,
|
|
215
309
|
displayName: AppSchema.shape.displayName,
|
|
216
|
-
visibility: VisibilitySchema.default({ mode: "
|
|
310
|
+
visibility: VisibilitySchema.default({ mode: "internal" }),
|
|
217
311
|
/** Optional per-app capability grant set at create time (architecture §6.3). */
|
|
218
312
|
capabilities: CapabilitiesSchema.optional()
|
|
219
313
|
});
|
|
@@ -373,7 +467,18 @@ var DeploymentConfigResponseSchema = z9.object({
|
|
|
373
467
|
* Display-only — the gateway is the choke point, so the rollup is exact, but
|
|
374
468
|
* nothing enforces this. Absent ⇒ no ceiling shown.
|
|
375
469
|
*/
|
|
376
|
-
platformMonthlyUsdCap: z9.number().positive().optional()
|
|
470
|
+
platformMonthlyUsdCap: z9.number().positive().optional(),
|
|
471
|
+
/**
|
|
472
|
+
* Deploy bundle size caps in megabytes (`DEPLOY_MAX_FILE_MB` /
|
|
473
|
+
* `DEPLOY_MAX_BUNDLE_MB` on the portal) — `deployMaxFileMb` is per file,
|
|
474
|
+
* `deployMaxBundleMb` the whole-archive total. A current portal always sends
|
|
475
|
+
* both; optional only to tolerate one that predates the fields, same as `url`
|
|
476
|
+
* on {@link AppSchema}. Absent means "don't state a number" — a client must not
|
|
477
|
+
* substitute a default, because printing the wrong cap to an agent sends it
|
|
478
|
+
* chasing a rejection it can't see the cause of.
|
|
479
|
+
*/
|
|
480
|
+
deployMaxFileMb: z9.number().positive().optional(),
|
|
481
|
+
deployMaxBundleMb: z9.number().positive().optional()
|
|
377
482
|
});
|
|
378
483
|
|
|
379
484
|
// ../shared/src/scrypt.ts
|
|
@@ -400,12 +505,60 @@ var LlmUsageSchema = z10.object({
|
|
|
400
505
|
cacheReadInputTokens: z10.int().nonnegative().default(0),
|
|
401
506
|
cacheCreationInputTokens: z10.int().nonnegative().default(0)
|
|
402
507
|
});
|
|
508
|
+
var MAX_SCHEMA_CHARS = 32768;
|
|
509
|
+
var MAX_SCHEMA_DEPTH = 12;
|
|
510
|
+
function schemaDepth(value, depth = 1) {
|
|
511
|
+
if (value === null || typeof value !== "object") return depth;
|
|
512
|
+
let deepest = depth;
|
|
513
|
+
for (const child of Object.values(value)) {
|
|
514
|
+
const d = schemaDepth(child, depth + 1);
|
|
515
|
+
if (d > deepest) deepest = d;
|
|
516
|
+
if (deepest > MAX_SCHEMA_DEPTH) return deepest;
|
|
517
|
+
}
|
|
518
|
+
return deepest;
|
|
519
|
+
}
|
|
520
|
+
function withinSchemaBudget(schema) {
|
|
521
|
+
let serialized;
|
|
522
|
+
try {
|
|
523
|
+
serialized = JSON.stringify(schema);
|
|
524
|
+
} catch {
|
|
525
|
+
return false;
|
|
526
|
+
}
|
|
527
|
+
return serialized.length <= MAX_SCHEMA_CHARS && schemaDepth(schema) <= MAX_SCHEMA_DEPTH;
|
|
528
|
+
}
|
|
529
|
+
var LlmResponseFormatSchema = z10.object({
|
|
530
|
+
type: z10.literal("json_schema"),
|
|
531
|
+
/** Schema name. OpenAI requires one; defaulted at translation when omitted. */
|
|
532
|
+
name: z10.string().regex(/^[A-Za-z0-9_-]{1,64}$/, "must be 1-64 chars of [A-Za-z0-9_-]").optional(),
|
|
533
|
+
schema: z10.record(z10.string(), z10.unknown()).refine((s) => s.type === "object", 'schema root must be `{"type":"object"}`').refine(
|
|
534
|
+
withinSchemaBudget,
|
|
535
|
+
`schema must serialize to <= ${MAX_SCHEMA_CHARS} characters and nest <= ${MAX_SCHEMA_DEPTH} levels`
|
|
536
|
+
)
|
|
537
|
+
// NB there is deliberately no `strict` knob. Anthropic's `output_config.format`
|
|
538
|
+
// always enforces and has no best-effort mode, so a `strict:false` could only be
|
|
539
|
+
// honored on one vendor — the same request would then yield schema-violating JSON
|
|
540
|
+
// on `gpt-*` but not `claude-*`, which is exactly the provider leak this seam
|
|
541
|
+
// exists to prevent. The platform always enforces; see ADR-0034.
|
|
542
|
+
});
|
|
403
543
|
var LlmChatRequestSchema = z10.object({
|
|
404
544
|
model: z10.string().min(1),
|
|
405
545
|
messages: z10.array(LlmMessageSchema).min(1),
|
|
406
546
|
/** Optional system prompt; maps to the vendor's system channel. */
|
|
407
547
|
system: z10.string().optional(),
|
|
408
|
-
maxTokens: z10.int().positive().max(128e3).
|
|
548
|
+
maxTokens: z10.int().positive().max(128e3).optional(),
|
|
549
|
+
/** Sampling temperature; forwarded as-is (the vendor validates its own range). */
|
|
550
|
+
temperature: z10.number().optional(),
|
|
551
|
+
/** Nucleus sampling; forwarded as-is. */
|
|
552
|
+
topP: z10.number().optional(),
|
|
553
|
+
/** Stop sequences; normalized to a list at the boundary. */
|
|
554
|
+
stop: z10.array(z10.string()).optional(),
|
|
555
|
+
/**
|
|
556
|
+
* Constrain the completion to a JSON schema (ADR-0034). Refused up front when
|
|
557
|
+
* the requested model can't enforce it (`ModelPrice.structuredOutputs`). The
|
|
558
|
+
* JSON still arrives as ordinary text, so `content` and the SSE `delta` frames
|
|
559
|
+
* are unchanged — callers `JSON.parse` the result.
|
|
560
|
+
*/
|
|
561
|
+
responseFormat: LlmResponseFormatSchema.optional(),
|
|
409
562
|
/** SSE streaming (default) vs a single JSON body. */
|
|
410
563
|
stream: z10.boolean().default(true)
|
|
411
564
|
});
|
|
@@ -425,142 +578,238 @@ var LlmStreamErrorSchema = z10.object({
|
|
|
425
578
|
message: z10.string()
|
|
426
579
|
});
|
|
427
580
|
|
|
428
|
-
// ../shared/src/
|
|
581
|
+
// ../shared/src/llmOpenai.ts
|
|
429
582
|
import { z as z11 } from "zod";
|
|
583
|
+
var OpenAiRoleSchema = z11.enum(["system", "developer", "user", "assistant", "tool"]);
|
|
584
|
+
var OpenAiMessageSchema = z11.object({
|
|
585
|
+
role: OpenAiRoleSchema,
|
|
586
|
+
content: z11.union([z11.string(), z11.array(z11.unknown())]).nullish(),
|
|
587
|
+
/** Present only on assistant tool-call turns — parsed to reject, never honored. */
|
|
588
|
+
tool_calls: z11.array(z11.unknown()).optional(),
|
|
589
|
+
tool_call_id: z11.string().optional(),
|
|
590
|
+
name: z11.string().optional()
|
|
591
|
+
});
|
|
592
|
+
var OpenAiStreamOptionsSchema = z11.object({
|
|
593
|
+
include_usage: z11.boolean().optional()
|
|
594
|
+
});
|
|
595
|
+
var OpenAiResponseFormatSchema = z11.discriminatedUnion("type", [
|
|
596
|
+
z11.object({
|
|
597
|
+
type: z11.literal("json_schema"),
|
|
598
|
+
json_schema: z11.object({
|
|
599
|
+
name: z11.string().optional(),
|
|
600
|
+
schema: z11.record(z11.string(), z11.unknown()),
|
|
601
|
+
strict: z11.boolean().nullish(),
|
|
602
|
+
/**
|
|
603
|
+
* Declared so the codec can reject it rather than zod silently stripping it.
|
|
604
|
+
* It is honorable on the OpenAI path but has no Anthropic equivalent, so
|
|
605
|
+
* forwarding it would make behaviour depend on the backing vendor — the same
|
|
606
|
+
* provider leak that got `json_object` rejected (ADR-0034).
|
|
607
|
+
*/
|
|
608
|
+
description: z11.string().optional()
|
|
609
|
+
})
|
|
610
|
+
}),
|
|
611
|
+
z11.object({ type: z11.literal("json_object") }),
|
|
612
|
+
z11.object({ type: z11.literal("text") })
|
|
613
|
+
]);
|
|
614
|
+
var OpenAiChatCompletionRequestSchema = z11.object({
|
|
615
|
+
model: z11.string().min(1),
|
|
616
|
+
messages: z11.array(OpenAiMessageSchema).min(1),
|
|
617
|
+
max_tokens: z11.int().positive().max(128e3).optional(),
|
|
618
|
+
max_completion_tokens: z11.int().positive().max(128e3).optional(),
|
|
619
|
+
temperature: z11.number().optional(),
|
|
620
|
+
top_p: z11.number().optional(),
|
|
621
|
+
stop: z11.union([z11.string(), z11.array(z11.string())]).optional(),
|
|
622
|
+
stream: z11.boolean().optional(),
|
|
623
|
+
stream_options: OpenAiStreamOptionsSchema.optional(),
|
|
624
|
+
/** Declared so the codec can reject tool use in v1 (not supported). */
|
|
625
|
+
tools: z11.array(z11.unknown()).optional(),
|
|
626
|
+
tool_choice: z11.unknown().optional(),
|
|
627
|
+
/**
|
|
628
|
+
* Structured output (ADR-0034). Parsed, not rejected — see the union above.
|
|
629
|
+
* `.nullish()` because clients and proxies that serialize every field send
|
|
630
|
+
* `"response_format": null` to mean "no structured output", which is a request
|
|
631
|
+
* this surface can serve; refusing it at the envelope would be gratuitous.
|
|
632
|
+
*/
|
|
633
|
+
response_format: OpenAiResponseFormatSchema.nullish(),
|
|
634
|
+
// Behaviour-changing params the platform does not honor in v1. Declared (not
|
|
635
|
+
// stripped) so the codec can reject them with a 400 rather than silently drop
|
|
636
|
+
// them — the same "reject, never silently drop" contract as `tools`.
|
|
637
|
+
n: z11.unknown().optional(),
|
|
638
|
+
seed: z11.unknown().optional(),
|
|
639
|
+
logit_bias: z11.unknown().optional(),
|
|
640
|
+
presence_penalty: z11.unknown().optional(),
|
|
641
|
+
frequency_penalty: z11.unknown().optional(),
|
|
642
|
+
logprobs: z11.unknown().optional(),
|
|
643
|
+
top_logprobs: z11.unknown().optional()
|
|
644
|
+
});
|
|
645
|
+
var OpenAiUsageSchema = z11.object({
|
|
646
|
+
prompt_tokens: z11.int().nonnegative(),
|
|
647
|
+
completion_tokens: z11.int().nonnegative(),
|
|
648
|
+
total_tokens: z11.int().nonnegative()
|
|
649
|
+
});
|
|
650
|
+
var OpenAiChatCompletionResponseSchema = z11.object({
|
|
651
|
+
id: z11.string(),
|
|
652
|
+
object: z11.literal("chat.completion"),
|
|
653
|
+
created: z11.int(),
|
|
654
|
+
model: z11.string(),
|
|
655
|
+
choices: z11.array(
|
|
656
|
+
z11.object({
|
|
657
|
+
index: z11.int(),
|
|
658
|
+
message: z11.object({
|
|
659
|
+
role: z11.literal("assistant"),
|
|
660
|
+
content: z11.string()
|
|
661
|
+
}),
|
|
662
|
+
finish_reason: z11.string()
|
|
663
|
+
})
|
|
664
|
+
),
|
|
665
|
+
usage: OpenAiUsageSchema
|
|
666
|
+
});
|
|
667
|
+
var OpenAiChatCompletionChunkSchema = z11.object({
|
|
668
|
+
id: z11.string(),
|
|
669
|
+
object: z11.literal("chat.completion.chunk"),
|
|
670
|
+
created: z11.int(),
|
|
671
|
+
model: z11.string(),
|
|
672
|
+
choices: z11.array(
|
|
673
|
+
z11.object({
|
|
674
|
+
index: z11.int(),
|
|
675
|
+
delta: z11.object({
|
|
676
|
+
role: z11.literal("assistant").optional(),
|
|
677
|
+
content: z11.string().optional()
|
|
678
|
+
}),
|
|
679
|
+
finish_reason: z11.string().nullable()
|
|
680
|
+
})
|
|
681
|
+
),
|
|
682
|
+
usage: OpenAiUsageSchema.nullish()
|
|
683
|
+
});
|
|
684
|
+
var OpenAiModelSchema = z11.object({
|
|
685
|
+
id: z11.string(),
|
|
686
|
+
object: z11.literal("model"),
|
|
687
|
+
created: z11.int(),
|
|
688
|
+
owned_by: z11.string()
|
|
689
|
+
});
|
|
690
|
+
var OpenAiModelListSchema = z11.object({
|
|
691
|
+
object: z11.literal("list"),
|
|
692
|
+
data: z11.array(OpenAiModelSchema)
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
// ../shared/src/usage.ts
|
|
696
|
+
import { z as z12 } from "zod";
|
|
430
697
|
var GATEWAY_OUTCOMES = ["ok", "error", "refusal", "quota_blocked"];
|
|
431
|
-
var GatewayOutcomeSchema =
|
|
698
|
+
var GatewayOutcomeSchema = z12.enum(GATEWAY_OUTCOMES);
|
|
432
699
|
var USAGE_RANGES = ["24h", "7d", "30d"];
|
|
433
|
-
var UsageRangeSchema =
|
|
700
|
+
var UsageRangeSchema = z12.enum(USAGE_RANGES);
|
|
434
701
|
var PLATFORM_RANGES = ["7d", "30d", "90d"];
|
|
435
|
-
var PlatformRangeSchema =
|
|
436
|
-
var UsageSeriesPointSchema =
|
|
437
|
-
bucket:
|
|
438
|
-
costUsd:
|
|
439
|
-
tokens:
|
|
440
|
-
requests:
|
|
441
|
-
});
|
|
442
|
-
var UsageSummarySchema =
|
|
443
|
-
appId:
|
|
702
|
+
var PlatformRangeSchema = z12.enum(PLATFORM_RANGES);
|
|
703
|
+
var UsageSeriesPointSchema = z12.object({
|
|
704
|
+
bucket: z12.iso.datetime(),
|
|
705
|
+
costUsd: z12.number().nonnegative(),
|
|
706
|
+
tokens: z12.int().nonnegative(),
|
|
707
|
+
requests: z12.int().nonnegative()
|
|
708
|
+
});
|
|
709
|
+
var UsageSummarySchema = z12.object({
|
|
710
|
+
appId: z12.uuid(),
|
|
444
711
|
/** The rolling range these figures cover. */
|
|
445
712
|
range: UsageRangeSchema,
|
|
446
|
-
requests:
|
|
447
|
-
inputTokens:
|
|
448
|
-
outputTokens:
|
|
713
|
+
requests: z12.int().nonnegative(),
|
|
714
|
+
inputTokens: z12.int().nonnegative(),
|
|
715
|
+
outputTokens: z12.int().nonnegative(),
|
|
449
716
|
/** Cache-aware input token totals (0 until prompt caching is enabled). */
|
|
450
|
-
cacheReadInputTokens:
|
|
451
|
-
cacheCreationInputTokens:
|
|
717
|
+
cacheReadInputTokens: z12.int().nonnegative(),
|
|
718
|
+
cacheCreationInputTokens: z12.int().nonnegative(),
|
|
452
719
|
/** Estimated spend in USD over the window at current rates (./pricing.ts). */
|
|
453
|
-
costUsd:
|
|
720
|
+
costUsd: z12.number().nonnegative(),
|
|
454
721
|
/** 95th-percentile upstream latency (ms) over the window; null when no timed calls. */
|
|
455
|
-
latencyP95Ms:
|
|
722
|
+
latencyP95Ms: z12.number().nonnegative().nullable(),
|
|
456
723
|
/** Fraction of calls in the window whose outcome was not `ok` (0..1). */
|
|
457
|
-
errorRate:
|
|
724
|
+
errorRate: z12.number().min(0).max(1),
|
|
458
725
|
/** Count of calls keyed by outcome (`ok` / `error` / `refusal` / `quota_blocked`). */
|
|
459
|
-
byOutcome:
|
|
460
|
-
byModel:
|
|
461
|
-
|
|
462
|
-
model:
|
|
463
|
-
tokens:
|
|
464
|
-
requests:
|
|
726
|
+
byOutcome: z12.record(z12.string(), z12.int().nonnegative()),
|
|
727
|
+
byModel: z12.array(
|
|
728
|
+
z12.object({
|
|
729
|
+
model: z12.string(),
|
|
730
|
+
tokens: z12.int().nonnegative(),
|
|
731
|
+
requests: z12.int().nonnegative(),
|
|
465
732
|
/** Estimated spend in USD for this model over the window. */
|
|
466
|
-
costUsd:
|
|
733
|
+
costUsd: z12.number().nonnegative()
|
|
467
734
|
})
|
|
468
735
|
),
|
|
469
736
|
/** Dense, zero-filled buckets across the range, oldest-first, for the trend chart. */
|
|
470
|
-
series:
|
|
737
|
+
series: z12.array(UsageSeriesPointSchema),
|
|
471
738
|
/**
|
|
472
739
|
* Today-since-midnight totals, independent of `range` — backs the daily-cap
|
|
473
740
|
* gauge (the budget the edge enforces is per calendar day).
|
|
474
741
|
*/
|
|
475
|
-
today:
|
|
476
|
-
tokens:
|
|
477
|
-
costUsd:
|
|
742
|
+
today: z12.object({
|
|
743
|
+
tokens: z12.int().nonnegative(),
|
|
744
|
+
costUsd: z12.number().nonnegative()
|
|
478
745
|
})
|
|
479
746
|
});
|
|
480
|
-
var GatewayCallSchema =
|
|
481
|
-
id:
|
|
482
|
-
appId:
|
|
747
|
+
var GatewayCallSchema = z12.object({
|
|
748
|
+
id: z12.uuid(),
|
|
749
|
+
appId: z12.uuid(),
|
|
483
750
|
/** App slug at read time; null when the app row no longer exists. */
|
|
484
|
-
slug:
|
|
485
|
-
userOid:
|
|
486
|
-
capability:
|
|
487
|
-
model:
|
|
488
|
-
inputTokens:
|
|
489
|
-
outputTokens:
|
|
490
|
-
cacheReadInputTokens:
|
|
491
|
-
cacheCreationInputTokens:
|
|
751
|
+
slug: z12.string().nullable(),
|
|
752
|
+
userOid: z12.string(),
|
|
753
|
+
capability: z12.string(),
|
|
754
|
+
model: z12.string(),
|
|
755
|
+
inputTokens: z12.int().nonnegative(),
|
|
756
|
+
outputTokens: z12.int().nonnegative(),
|
|
757
|
+
cacheReadInputTokens: z12.int().nonnegative(),
|
|
758
|
+
cacheCreationInputTokens: z12.int().nonnegative(),
|
|
492
759
|
/** Estimated spend in USD for this single call at current rates. */
|
|
493
|
-
costUsd:
|
|
760
|
+
costUsd: z12.number().nonnegative(),
|
|
494
761
|
/** Upstream round-trip latency in ms (0 when not measured). */
|
|
495
|
-
durationMs:
|
|
762
|
+
durationMs: z12.int().nonnegative(),
|
|
496
763
|
/** Upstream/egress HTTP status — set for `fetch`; null for streamed `llm`. */
|
|
497
|
-
statusCode:
|
|
764
|
+
statusCode: z12.int().nullable(),
|
|
498
765
|
/** LLM stop reason; null for non-LLM calls. */
|
|
499
|
-
stopReason:
|
|
766
|
+
stopReason: z12.string().nullable(),
|
|
500
767
|
/** Short upstream error string; null on success. */
|
|
501
|
-
errorDetail:
|
|
768
|
+
errorDetail: z12.string().nullable(),
|
|
502
769
|
outcome: GatewayOutcomeSchema,
|
|
503
|
-
createdAt:
|
|
770
|
+
createdAt: z12.iso.datetime()
|
|
504
771
|
});
|
|
505
|
-
var GatewayAuditPageSchema =
|
|
506
|
-
rows:
|
|
772
|
+
var GatewayAuditPageSchema = z12.object({
|
|
773
|
+
rows: z12.array(GatewayCallSchema),
|
|
507
774
|
/** Pass as `?before=` to fetch the next (older) page; absent when exhausted. */
|
|
508
|
-
nextBefore:
|
|
775
|
+
nextBefore: z12.iso.datetime().optional()
|
|
509
776
|
});
|
|
510
|
-
var PlatformUsageSchema =
|
|
777
|
+
var PlatformUsageSchema = z12.object({
|
|
511
778
|
/** The rolling range the series + breakdowns cover. */
|
|
512
779
|
range: PlatformRangeSchema,
|
|
513
780
|
/** Dense, zero-filled daily buckets across the range, oldest-first. */
|
|
514
|
-
series:
|
|
781
|
+
series: z12.array(UsageSeriesPointSchema),
|
|
515
782
|
/** Per-app rollup over the range, busiest-first. */
|
|
516
|
-
byApp:
|
|
517
|
-
|
|
518
|
-
slug:
|
|
519
|
-
tokens:
|
|
520
|
-
requests:
|
|
783
|
+
byApp: z12.array(
|
|
784
|
+
z12.object({
|
|
785
|
+
slug: z12.string().nullable(),
|
|
786
|
+
tokens: z12.int().nonnegative(),
|
|
787
|
+
requests: z12.int().nonnegative(),
|
|
521
788
|
/** Estimated spend in USD for this app over the range. */
|
|
522
|
-
costUsd:
|
|
789
|
+
costUsd: z12.number().nonnegative()
|
|
523
790
|
})
|
|
524
791
|
),
|
|
525
792
|
/** Month-to-date headline KPIs (independent of `range`). */
|
|
526
|
-
totals:
|
|
527
|
-
tokensMTD:
|
|
528
|
-
requestsMTD:
|
|
793
|
+
totals: z12.object({
|
|
794
|
+
tokensMTD: z12.int().nonnegative(),
|
|
795
|
+
requestsMTD: z12.int().nonnegative(),
|
|
529
796
|
/** Estimated month-to-date spend in USD across all apps. */
|
|
530
|
-
costMTD:
|
|
797
|
+
costMTD: z12.number().nonnegative(),
|
|
531
798
|
/** Distinct `userOid`s seen month-to-date. */
|
|
532
|
-
activeUsers:
|
|
799
|
+
activeUsers: z12.int().nonnegative()
|
|
533
800
|
}),
|
|
534
801
|
/** Token + cost share by capability over the range (essentially all `llm` in M4). */
|
|
535
|
-
capabilityMix:
|
|
536
|
-
|
|
537
|
-
capability:
|
|
538
|
-
tokens:
|
|
802
|
+
capabilityMix: z12.array(
|
|
803
|
+
z12.object({
|
|
804
|
+
capability: z12.string(),
|
|
805
|
+
tokens: z12.int().nonnegative(),
|
|
539
806
|
/** Estimated spend in USD for this capability over the range. */
|
|
540
|
-
costUsd:
|
|
807
|
+
costUsd: z12.number().nonnegative()
|
|
541
808
|
})
|
|
542
809
|
)
|
|
543
810
|
});
|
|
544
811
|
|
|
545
812
|
// ../shared/src/data.ts
|
|
546
|
-
import { z as z12 } from "zod";
|
|
547
|
-
var CollectionItemSchema = z12.object({
|
|
548
|
-
id: z12.uuid(),
|
|
549
|
-
collection: z12.string(),
|
|
550
|
-
/** The submitting user, if authenticated; null for anonymous/public visitors. */
|
|
551
|
-
userOid: z12.string().nullable(),
|
|
552
|
-
item: z12.unknown(),
|
|
553
|
-
/** Hashed IP / truncated UA for abuse triage; null if not captured. */
|
|
554
|
-
meta: z12.unknown().nullable(),
|
|
555
|
-
createdAt: z12.iso.datetime()
|
|
556
|
-
});
|
|
557
|
-
var CollectionItemsPageSchema = z12.object({
|
|
558
|
-
rows: z12.array(CollectionItemSchema),
|
|
559
|
-
/** Pass as `?before=` to fetch the next (older) page; absent when exhausted. */
|
|
560
|
-
nextBefore: z12.iso.datetime().optional()
|
|
561
|
-
});
|
|
562
|
-
|
|
563
|
-
// ../shared/src/secrets.ts
|
|
564
813
|
import { z as z14 } from "zod";
|
|
565
814
|
|
|
566
815
|
// ../shared/src/env.ts
|
|
@@ -568,46 +817,160 @@ import { z as z13 } from "zod";
|
|
|
568
817
|
var ENVS = ["prod", "dev"];
|
|
569
818
|
var EnvSchema = z13.enum(ENVS);
|
|
570
819
|
|
|
820
|
+
// ../shared/src/data.ts
|
|
821
|
+
var CollectionItemSchema = z14.object({
|
|
822
|
+
id: z14.uuid(),
|
|
823
|
+
collection: z14.string(),
|
|
824
|
+
/**
|
|
825
|
+
* Which tier collected the row (dev-mode §5). The runtime roles are RLS-pinned
|
|
826
|
+
* to one tier each, but the portal reads across both, so the discriminator has
|
|
827
|
+
* to travel — otherwise a developer's dev-mode test submissions are
|
|
828
|
+
* indistinguishable from real prod leads in the drain.
|
|
829
|
+
*/
|
|
830
|
+
env: EnvSchema,
|
|
831
|
+
/** The submitting user, if authenticated; null for anonymous/public visitors. */
|
|
832
|
+
userOid: z14.string().nullable(),
|
|
833
|
+
item: z14.unknown(),
|
|
834
|
+
/** Hashed IP / truncated UA for abuse triage; null if not captured. */
|
|
835
|
+
meta: z14.unknown().nullable(),
|
|
836
|
+
createdAt: z14.iso.datetime()
|
|
837
|
+
});
|
|
838
|
+
var CollectionItemsPageSchema = z14.object({
|
|
839
|
+
rows: z14.array(CollectionItemSchema),
|
|
840
|
+
/** Pass as `?before=` to fetch the next (older) page; absent when exhausted. */
|
|
841
|
+
nextBefore: z14.iso.datetime().optional()
|
|
842
|
+
});
|
|
843
|
+
var CollectionSummarySchema = z14.object({
|
|
844
|
+
name: z14.string(),
|
|
845
|
+
env: EnvSchema,
|
|
846
|
+
count: z14.int().nonnegative(),
|
|
847
|
+
/** Newest row's timestamp; null only if the group is somehow empty. */
|
|
848
|
+
lastAt: z14.iso.datetime().nullable()
|
|
849
|
+
});
|
|
850
|
+
|
|
851
|
+
// ../shared/src/collectionTable.ts
|
|
852
|
+
var BOM = String.fromCharCode(65279);
|
|
853
|
+
|
|
571
854
|
// ../shared/src/secrets.ts
|
|
572
|
-
|
|
855
|
+
import { z as z15 } from "zod";
|
|
856
|
+
var FORBIDDEN_HEADER_NAMES = /* @__PURE__ */ new Set([
|
|
857
|
+
"host",
|
|
858
|
+
"content-length",
|
|
859
|
+
"transfer-encoding",
|
|
860
|
+
"connection",
|
|
861
|
+
"te",
|
|
862
|
+
"upgrade",
|
|
863
|
+
"expect",
|
|
864
|
+
"trailer"
|
|
865
|
+
]);
|
|
866
|
+
var HEADER_TOKEN = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
|
|
867
|
+
var HEADER_VALUE_SAFE = /^[\t\x20-\x7e]*$/;
|
|
868
|
+
var HEADER_VALUE_WIRE = /^[\t\x20-\x7e\x80-\xff]*$/;
|
|
869
|
+
var headerName = (from) => {
|
|
870
|
+
const base = z15.string().min(1);
|
|
871
|
+
return (from === "request" ? base.max(64).regex(HEADER_TOKEN, "must be an RFC 7230 token") : base).transform((n) => n.trim().toLowerCase()).refine((n) => !FORBIDDEN_HEADER_NAMES.has(n), "reserved header name").refine((n) => !n.startsWith("x-helix-"), "reserved header prefix");
|
|
872
|
+
};
|
|
873
|
+
var headerTemplate = (from) => from === "request" ? z15.string().max(512).regex(HEADER_VALUE_SAFE) : z15.string().regex(HEADER_VALUE_WIRE);
|
|
874
|
+
var injectionRecipe = (from) => z15.discriminatedUnion("kind", [
|
|
573
875
|
/** `Authorization: Bearer <secret>` — the common case. */
|
|
574
|
-
|
|
876
|
+
z15.object({ kind: z15.literal("header-bearer") }),
|
|
575
877
|
/** Arbitrary header; `{}` in `template` is replaced with the secret. */
|
|
576
|
-
|
|
577
|
-
kind:
|
|
578
|
-
name:
|
|
579
|
-
template:
|
|
878
|
+
z15.object({
|
|
879
|
+
kind: z15.literal("header"),
|
|
880
|
+
name: headerName(from),
|
|
881
|
+
template: headerTemplate(from).default("{}")
|
|
580
882
|
}),
|
|
581
883
|
/** Query parameter `?<param>=<secret>`. */
|
|
582
|
-
|
|
884
|
+
z15.object({ kind: z15.literal("query"), param: z15.string().min(1) }),
|
|
885
|
+
/**
|
|
886
|
+
* HMAC over a timestamp. The signed input is the timestamp string **alone** —
|
|
887
|
+
* not the method, path, query, or body — so injection is a pure function of
|
|
888
|
+
* (private key, now) and needs no request context.
|
|
889
|
+
*
|
|
890
|
+
* The canonical form lives in the KIND NAME, deliberately. A scheme that signs
|
|
891
|
+
* method+path+body is a *sibling kind* (`hmac-request`) — a code change with
|
|
892
|
+
* tests, reviewed — never an admin-editable canonical-string template.
|
|
893
|
+
* Canonicalization is where implementations of this family go wrong, and it
|
|
894
|
+
* does not belong in a text box.
|
|
895
|
+
*
|
|
896
|
+
* SHA-256, lowercase-hex, and ISO-8601-with-milliseconds are fixed rather than
|
|
897
|
+
* configurable. Each would carry a default, and `app_secrets.injection` is a
|
|
898
|
+
* schemaless JSON column, so adding a knob later is purely additive with no
|
|
899
|
+
* migration — while fixing them now removes the weak-algorithm and
|
|
900
|
+
* unencodable-digest failure classes outright.
|
|
901
|
+
*
|
|
902
|
+
* The stored value is a JSON blob carrying both halves of the key pair
|
|
903
|
+
* ({@link HmacCredentialSchema}): regenerating the pair changes both, and a
|
|
904
|
+
* blob rotates atomically through the existing rotate route.
|
|
905
|
+
*/
|
|
906
|
+
z15.object({
|
|
907
|
+
kind: z15.literal("hmac-timestamp"),
|
|
908
|
+
/** Header carrying the timestamp that is also the entire signed input. */
|
|
909
|
+
timestampHeader: headerName(from),
|
|
910
|
+
/** Header carrying the rendered credential + signature. */
|
|
911
|
+
authHeader: headerName(from).default("authorization"),
|
|
912
|
+
/**
|
|
913
|
+
* Value written to `authHeader`. `{credential}` and `{signature}` are
|
|
914
|
+
* substituted. Named rather than the `header` kind's bare `{}` because there
|
|
915
|
+
* are two substitutions: the convention is one value ⇒ `{}`, more than one ⇒
|
|
916
|
+
* named placeholders. Positional `{}` here would let a swapped template
|
|
917
|
+
* produce a well-formed header that silently fails to authenticate.
|
|
918
|
+
*
|
|
919
|
+
* Both placeholders are required. This recipe writes exactly two headers,
|
|
920
|
+
* one of them the timestamp, so there is no configuration where the public
|
|
921
|
+
* credential id travels elsewhere — omitting `{credential}` means the
|
|
922
|
+
* upstream can never identify the key and every call 401s.
|
|
923
|
+
*/
|
|
924
|
+
template: headerTemplate(from).refine((t) => t.includes("{signature}"), "template must contain {signature}").refine((t) => t.includes("{credential}"), "template must contain {credential}")
|
|
925
|
+
}).refine(
|
|
926
|
+
(r) => r.timestampHeader !== r.authHeader,
|
|
927
|
+
"timestampHeader and authHeader must differ \u2014 the second write overwrites the first"
|
|
928
|
+
)
|
|
583
929
|
]);
|
|
930
|
+
var InjectionRecipeSchema = injectionRecipe("request");
|
|
931
|
+
var StoredInjectionRecipeSchema = injectionRecipe("stored");
|
|
932
|
+
var HmacCredentialSchema = z15.object({
|
|
933
|
+
credential: z15.string().min(1),
|
|
934
|
+
key: z15.string().min(1)
|
|
935
|
+
});
|
|
584
936
|
var SECRET_SCOPES = ["app", "global", "platform"];
|
|
585
|
-
var SecretScopeSchema =
|
|
586
|
-
var SecretMetadataSchema =
|
|
587
|
-
id:
|
|
588
|
-
name:
|
|
937
|
+
var SecretScopeSchema = z15.enum(SECRET_SCOPES);
|
|
938
|
+
var SecretMetadataSchema = z15.object({
|
|
939
|
+
id: z15.string(),
|
|
940
|
+
name: z15.string().min(1),
|
|
589
941
|
scope: SecretScopeSchema,
|
|
590
942
|
/** Partition tier (dev-mode §6): a dev fetch injects only `dev` connection secrets. */
|
|
591
943
|
env: EnvSchema,
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
944
|
+
/**
|
|
945
|
+
* The **stored** parser, not the strict one — this field describes a row, and
|
|
946
|
+
* the SPA re-parses this schema on every response. Using the strict parser here
|
|
947
|
+
* would let the portal return a name the browser then refuses, moving the read
|
|
948
|
+
* failure from the server to the client, where it presents as a dead page.
|
|
949
|
+
*
|
|
950
|
+
* `null` = the stored recipe is unreadable (it names a reserved header, or is
|
|
951
|
+
* not a recipe at all). The credential still exists and is still deletable; it
|
|
952
|
+
* cannot be rotated, and egress fails its hop closed. Recipes are immutable by
|
|
953
|
+
* design, so recovery is delete-and-recreate.
|
|
954
|
+
*/
|
|
955
|
+
injection: StoredInjectionRecipeSchema.nullable(),
|
|
956
|
+
createdBy: z15.string(),
|
|
957
|
+
createdAt: z15.string(),
|
|
958
|
+
rotatedAt: z15.string().nullable().optional(),
|
|
959
|
+
lastUsedAt: z15.string().nullable().optional(),
|
|
960
|
+
boundApps: z15.array(z15.string()).default([])
|
|
961
|
+
});
|
|
962
|
+
var SecretCreateRequestSchema = z15.object({
|
|
963
|
+
name: z15.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9-]*$/, "lowercase letters, digits, and hyphens"),
|
|
964
|
+
value: z15.string().min(1),
|
|
602
965
|
/** Target tier (dev-mode §6). Defaults `prod`; `dev` configures a dev-tier credential. */
|
|
603
966
|
env: EnvSchema.default("prod"),
|
|
604
967
|
injection: InjectionRecipeSchema.default({ kind: "header-bearer" })
|
|
605
968
|
});
|
|
606
|
-
var SecretRotateRequestSchema =
|
|
607
|
-
var SecretGrantRequestSchema =
|
|
969
|
+
var SecretRotateRequestSchema = z15.object({ value: z15.string().min(1) });
|
|
970
|
+
var SecretGrantRequestSchema = z15.object({ appSlug: z15.string().min(1) });
|
|
608
971
|
|
|
609
972
|
// ../shared/src/devTokens.ts
|
|
610
|
-
import { z as
|
|
973
|
+
import { z as z16 } from "zod";
|
|
611
974
|
function isValidDevOrigin(input) {
|
|
612
975
|
if (input.includes("*")) return false;
|
|
613
976
|
let url;
|
|
@@ -621,47 +984,47 @@ function isValidDevOrigin(input) {
|
|
|
621
984
|
if (url.username !== "" || url.password !== "") return false;
|
|
622
985
|
return input === url.origin || input === `${url.origin}/`;
|
|
623
986
|
}
|
|
624
|
-
var DevOriginSchema =
|
|
987
|
+
var DevOriginSchema = z16.string().refine(
|
|
625
988
|
isValidDevOrigin,
|
|
626
989
|
"must be an exact origin (scheme://host[:port]) \u2014 no path, query, or wildcard"
|
|
627
990
|
).transform((o) => new URL(o).origin);
|
|
628
|
-
var TtlDaysSchema =
|
|
629
|
-
var DevTokenMintRequestSchema =
|
|
630
|
-
origins:
|
|
991
|
+
var TtlDaysSchema = z16.number().int().min(1).max(365).optional();
|
|
992
|
+
var DevTokenMintRequestSchema = z16.object({
|
|
993
|
+
origins: z16.array(DevOriginSchema).min(1).max(20),
|
|
631
994
|
ttlDays: TtlDaysSchema
|
|
632
995
|
});
|
|
633
|
-
var DevTokenRotateRequestSchema =
|
|
996
|
+
var DevTokenRotateRequestSchema = z16.object({
|
|
634
997
|
ttlDays: TtlDaysSchema
|
|
635
998
|
});
|
|
636
|
-
var DevTokenMetadataSchema =
|
|
637
|
-
id:
|
|
638
|
-
developerOid:
|
|
639
|
-
origins:
|
|
640
|
-
expiresAt:
|
|
641
|
-
revokedAt:
|
|
642
|
-
createdAt:
|
|
643
|
-
});
|
|
644
|
-
var DevTokenMintResponseSchema =
|
|
645
|
-
token:
|
|
999
|
+
var DevTokenMetadataSchema = z16.object({
|
|
1000
|
+
id: z16.string(),
|
|
1001
|
+
developerOid: z16.string(),
|
|
1002
|
+
origins: z16.array(z16.string()),
|
|
1003
|
+
expiresAt: z16.string(),
|
|
1004
|
+
revokedAt: z16.string().nullable().optional(),
|
|
1005
|
+
createdAt: z16.string()
|
|
1006
|
+
});
|
|
1007
|
+
var DevTokenMintResponseSchema = z16.object({
|
|
1008
|
+
token: z16.string(),
|
|
646
1009
|
metadata: DevTokenMetadataSchema
|
|
647
1010
|
});
|
|
648
1011
|
|
|
649
1012
|
// ../shared/src/instruction.ts
|
|
650
|
-
import { z as
|
|
1013
|
+
import { z as z17 } from "zod";
|
|
651
1014
|
var INSTRUCTION_CAPABILITIES = ["fetch", "llm"];
|
|
652
|
-
var InstructionCapabilitySchema =
|
|
653
|
-
var AttestedInstructionSchema =
|
|
1015
|
+
var InstructionCapabilitySchema = z17.enum(INSTRUCTION_CAPABILITIES);
|
|
1016
|
+
var AttestedInstructionSchema = z17.object({
|
|
654
1017
|
/** App the call is attributed to (registry app id). */
|
|
655
|
-
appId:
|
|
1018
|
+
appId: z17.string().min(1),
|
|
656
1019
|
/** Authenticated user, or the anonymous sentinel on `public` apps. */
|
|
657
|
-
userOid:
|
|
1020
|
+
userOid: z17.string().min(1),
|
|
658
1021
|
capability: InstructionCapabilitySchema,
|
|
659
1022
|
/** The allowlisted origin the edge authorized (scheme + host + port). */
|
|
660
|
-
origin:
|
|
1023
|
+
origin: z17.url(),
|
|
661
1024
|
/** Connection (secret) name to inject, if this is a secret-backed call. */
|
|
662
|
-
connection:
|
|
1025
|
+
connection: z17.string().min(1).optional(),
|
|
663
1026
|
/** Correlates the edge audit row with the egress call. */
|
|
664
|
-
requestId:
|
|
1027
|
+
requestId: z17.string().min(1),
|
|
665
1028
|
/**
|
|
666
1029
|
* The HTTP method + URL pathname the edge authorized (ADR-0013 step 2, issue #6).
|
|
667
1030
|
* Egress refuses a mismatched verb/resource, so a captured instruction can't be
|
|
@@ -676,8 +1039,8 @@ var AttestedInstructionSchema = z16.object({
|
|
|
676
1039
|
* means "old edge", not tampering — egress asserts ONLY when the claim is
|
|
677
1040
|
* present. Make required once a fleet is reliably past deploy.
|
|
678
1041
|
*/
|
|
679
|
-
method:
|
|
680
|
-
path:
|
|
1042
|
+
method: z17.string().min(1).optional(),
|
|
1043
|
+
path: z17.string().optional(),
|
|
681
1044
|
/**
|
|
682
1045
|
* Environment tier this call is scoped to (dev-mode design §6). Egress resolves
|
|
683
1046
|
* the connection secret within this tier — a `dev` instruction can never reach a
|
|
@@ -691,7 +1054,7 @@ var INSTRUCTION_TTL_SECONDS = 30;
|
|
|
691
1054
|
var INSTRUCTION_BURN_RETENTION_SECONDS = INSTRUCTION_TTL_SECONDS + 15;
|
|
692
1055
|
|
|
693
1056
|
// ../shared/src/fetch.ts
|
|
694
|
-
import { z as
|
|
1057
|
+
import { z as z18 } from "zod";
|
|
695
1058
|
var FETCH_ERROR_CODES = [
|
|
696
1059
|
"forbidden",
|
|
697
1060
|
"rate_limited",
|
|
@@ -701,10 +1064,10 @@ var FETCH_ERROR_CODES = [
|
|
|
701
1064
|
"replay",
|
|
702
1065
|
"upstream_error"
|
|
703
1066
|
];
|
|
704
|
-
var FetchErrorCodeSchema =
|
|
705
|
-
var FetchProxyErrorSchema =
|
|
1067
|
+
var FetchErrorCodeSchema = z18.enum(FETCH_ERROR_CODES);
|
|
1068
|
+
var FetchProxyErrorSchema = z18.object({
|
|
706
1069
|
code: FetchErrorCodeSchema,
|
|
707
|
-
message:
|
|
1070
|
+
message: z18.string()
|
|
708
1071
|
});
|
|
709
1072
|
|
|
710
1073
|
// src/client.ts
|
|
@@ -716,7 +1079,7 @@ var CliError = class extends Error {
|
|
|
716
1079
|
this.code = code;
|
|
717
1080
|
}
|
|
718
1081
|
};
|
|
719
|
-
var VersionListSchema =
|
|
1082
|
+
var VersionListSchema = z19.array(VersionSchema);
|
|
720
1083
|
var PortalClient = class {
|
|
721
1084
|
#baseUrl;
|
|
722
1085
|
#tokenProvider;
|
|
@@ -938,10 +1301,15 @@ function parseVisibility(input) {
|
|
|
938
1301
|
if (!groupId) throw new CliError("group visibility needs an id: group:<id>");
|
|
939
1302
|
return { mode: "group", groupId };
|
|
940
1303
|
}
|
|
941
|
-
if (input === "
|
|
1304
|
+
if (input === "internal" || input === "password" || input === "public") {
|
|
942
1305
|
return { mode: input };
|
|
943
1306
|
}
|
|
944
|
-
|
|
1307
|
+
if (input === "private") {
|
|
1308
|
+
throw new CliError(
|
|
1309
|
+
'visibility "private" was renamed to "internal" (the mode never checked which user signed in, only that someone had). Use --visibility internal; the name "private" is reserved for a future owner-only mode, so it is not accepted as an alias.'
|
|
1310
|
+
);
|
|
1311
|
+
}
|
|
1312
|
+
throw new CliError(`invalid visibility "${input}" (internal | group:<id> | password | public)`);
|
|
945
1313
|
}
|
|
946
1314
|
function printVersion(v) {
|
|
947
1315
|
console.log(` version ${v.number} (${v.status}) \u2014 ${v.id}`);
|
|
@@ -1113,7 +1481,7 @@ Usage:
|
|
|
1113
1481
|
Common flags: --slug <slug> --portal-url <url> --token <token>
|
|
1114
1482
|
Env: HELIX_PORTAL_URL, HELIX_TOKEN (static token \u2014 skips login; CI/scripts).
|
|
1115
1483
|
Config file: helix.json { slug, portalUrl, dir }
|
|
1116
|
-
Visibility:
|
|
1484
|
+
Visibility: internal | group:<id> | password | public
|
|
1117
1485
|
`;
|
|
1118
1486
|
async function main() {
|
|
1119
1487
|
const { values, positionals } = parseCliArgs(process.argv.slice(2));
|