@shipeasy/openapi 1.4.0 → 1.6.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.
Files changed (63) hide show
  1. package/dist/index.d.ts +24 -0
  2. package/dist/index.js +1924 -3
  3. package/dist/node-context.d.ts +37 -0
  4. package/dist/node-context.js +56 -0
  5. package/dist/operations/attributes.d.ts +33 -0
  6. package/dist/operations/attributes.js +16 -0
  7. package/dist/operations/cli-adapter.d.ts +35 -0
  8. package/dist/operations/cli-adapter.js +91 -0
  9. package/dist/operations/coerce.d.ts +32 -0
  10. package/dist/operations/coerce.js +53 -0
  11. package/dist/operations/configs.d.ts +28 -0
  12. package/dist/operations/configs.js +119 -0
  13. package/dist/operations/docs.d.ts +37 -0
  14. package/dist/operations/docs.js +39 -0
  15. package/dist/operations/events.d.ts +28 -0
  16. package/dist/operations/events.js +121 -0
  17. package/dist/operations/experiments.d.ts +28 -0
  18. package/dist/operations/experiments.js +280 -0
  19. package/dist/operations/gates.d.ts +28 -0
  20. package/dist/operations/gates.js +132 -0
  21. package/dist/operations/i18n.d.ts +28 -0
  22. package/dist/operations/i18n.js +121 -0
  23. package/dist/operations/index.d.ts +63 -0
  24. package/dist/operations/index.js +1751 -0
  25. package/dist/operations/killswitches.d.ts +28 -0
  26. package/dist/operations/killswitches.js +126 -0
  27. package/dist/operations/mcp-adapter.d.ts +35 -0
  28. package/dist/operations/mcp-adapter.js +94 -0
  29. package/dist/operations/metrics.d.ts +30 -0
  30. package/dist/operations/metrics.js +114 -0
  31. package/dist/operations/ops.d.ts +28 -0
  32. package/dist/operations/ops.js +313 -0
  33. package/dist/operations/projects.d.ts +28 -0
  34. package/dist/operations/projects.js +36 -0
  35. package/dist/operations/sdk-docs.d.ts +28 -0
  36. package/dist/operations/sdk-docs.js +160 -0
  37. package/dist/operations/types.d.ts +138 -0
  38. package/dist/operations/types.js +9 -0
  39. package/dist/operations/universes.d.ts +28 -0
  40. package/dist/operations/universes.js +93 -0
  41. package/dist/operations/util.d.ts +33 -0
  42. package/dist/operations/util.js +9 -0
  43. package/dist/resources/alert-rules.d.ts +3 -1
  44. package/dist/resources/alert-rules.js +11 -2
  45. package/dist/resources/attributes.d.ts +19 -0
  46. package/dist/resources/attributes.js +10 -0
  47. package/dist/resources/events.d.ts +61 -0
  48. package/dist/resources/events.js +42 -0
  49. package/dist/resources/i18n.d.ts +84 -0
  50. package/dist/resources/i18n.js +60 -0
  51. package/dist/resources/index.d.ts +22 -4
  52. package/dist/resources/index.js +184 -3
  53. package/dist/resources/metrics.d.ts +54 -0
  54. package/dist/resources/metrics.js +40 -0
  55. package/dist/resources/ops.d.ts +88 -0
  56. package/dist/resources/ops.js +36 -0
  57. package/dist/resources/projects.d.ts +54 -0
  58. package/dist/resources/projects.js +14 -0
  59. package/dist/schemas/alert-rules.d.ts +23 -1
  60. package/dist/schemas/alert-rules.js +13 -3
  61. package/dist/schemas/index.d.ts +1 -1
  62. package/dist/schemas/index.js +12 -2
  63. package/package.json +10 -1
@@ -1,7 +1,17 @@
1
- import { Transport } from '../transport.js';
2
- import '../schemas/pagination.js';
3
1
  import * as zod_v4_core from 'zod/v4/core';
4
2
  import * as zod from 'zod';
3
+ import { AttributesClient } from './attributes.js';
4
+ export { attributesClient } from './attributes.js';
5
+ import { I18nClient } from './i18n.js';
6
+ export { i18nClient } from './i18n.js';
7
+ import { ProjectsClient } from './projects.js';
8
+ export { projectsClient } from './projects.js';
9
+ import { OpsClient } from './ops.js';
10
+ export { opsClient } from './ops.js';
11
+ import { EventsClient } from './events.js';
12
+ export { eventsClient } from './events.js';
13
+ import { MetricsClient } from './metrics.js';
14
+ export { metricsClient } from './metrics.js';
5
15
  import { AlertRulesClient } from './alert-rules.js';
6
16
  export { alertRulesClient } from './alert-rules.js';
7
17
  import { KillswitchesClient } from './killswitches.js';
@@ -14,12 +24,14 @@ import { ExperimentsClient } from './experiments.js';
14
24
  export { experimentsClient, experimentsResource } from './experiments.js';
15
25
  import { GatesClient } from './gates.js';
16
26
  export { gatesClient, gatesResource } from './gates.js';
17
- import '../schemas/gates.js';
18
- import '../schemas/universes.js';
27
+ import { Transport } from '../transport.js';
19
28
  import '../schemas/alert-rules.js';
20
29
  import '../schemas/killswitches.js';
30
+ import '../schemas/pagination.js';
31
+ import '../schemas/universes.js';
21
32
  import '../schemas/configs.js';
22
33
  import '../schemas/experiments.js';
34
+ import '../schemas/gates.js';
23
35
 
24
36
  /**
25
37
  * Aggregate admin client. Each resource lives in its own file under
@@ -36,6 +48,12 @@ declare function createAdminClient(transport: Transport): {
36
48
  universes: UniversesClient;
37
49
  killswitches: KillswitchesClient;
38
50
  alertRules: AlertRulesClient;
51
+ metrics: MetricsClient;
52
+ events: EventsClient;
53
+ ops: OpsClient;
54
+ projects: ProjectsClient;
55
+ i18n: I18nClient;
56
+ attributes: AttributesClient;
39
57
  };
40
58
  type AdminClient = ReturnType<typeof createAdminClient>;
41
59
  /**
@@ -2296,6 +2296,13 @@ var killswitchesResource = {
2296
2296
  import { z as z13 } from "zod";
2297
2297
  var ALERT_COMPARATORS = ["gt", "gte", "lt", "lte"];
2298
2298
  var ALERT_SEVERITIES = ["danger", "warn", "info"];
2299
+ var notificationTargetSchema = z13.object({
2300
+ slackChannel: z13.object({
2301
+ id: z13.string().min(1).describe("Slack channel id (e.g. C0123ABCD)."),
2302
+ name: z13.string().min(1).describe("Slack channel name (without the leading #).")
2303
+ }).nullish().describe("Slack channel to post this rule's alert to. Requires a Slack connector."),
2304
+ email: z13.string().email().nullish().describe("Email address to notify for this rule (overrides the default recipient).")
2305
+ }).strict();
2299
2306
  var alertRuleCreateSchema = z13.object({
2300
2307
  name: z13.string().min(1).max(120).describe("Human label for the rule, shown on the alert and the rules list."),
2301
2308
  metricId: z13.string().min(1).describe("Id of the metric to evaluate."),
@@ -2303,7 +2310,8 @@ var alertRuleCreateSchema = z13.object({
2303
2310
  threshold: z13.number().finite().describe("Threshold the metric value is compared against."),
2304
2311
  windowHours: z13.number().int().min(1).max(720).default(24).describe("Lookback window (hours) the metric is aggregated over. 1\u2013720."),
2305
2312
  severity: z13.enum(ALERT_SEVERITIES).default("warn").describe("Severity of the raised alert."),
2306
- enabled: z13.boolean().default(true).describe("Whether the rule is evaluated by the cron.")
2313
+ enabled: z13.boolean().default(true).describe("Whether the rule is evaluated by the cron."),
2314
+ notify: notificationTargetSchema.nullish().describe("Where to deliver this rule's alert (Slack channel and/or email target).")
2307
2315
  }).strict();
2308
2316
  var alertRuleUpdateSchema = z13.object({
2309
2317
  name: z13.string().min(1).max(120),
@@ -2311,7 +2319,8 @@ var alertRuleUpdateSchema = z13.object({
2311
2319
  threshold: z13.number().finite(),
2312
2320
  windowHours: z13.number().int().min(1).max(720),
2313
2321
  severity: z13.enum(ALERT_SEVERITIES),
2314
- enabled: z13.boolean()
2322
+ enabled: z13.boolean(),
2323
+ notify: notificationTargetSchema.nullish()
2315
2324
  }).partial().strict();
2316
2325
 
2317
2326
  // src/resources/alert-rules.ts
@@ -2346,6 +2355,166 @@ function alertRulesClient(t) {
2346
2355
  };
2347
2356
  }
2348
2357
 
2358
+ // src/resources/metrics.ts
2359
+ var BASE7 = "/api/admin/metrics";
2360
+ function metricsClient(t) {
2361
+ async function list() {
2362
+ return t.request("GET", BASE7);
2363
+ }
2364
+ async function resolve(idOrName) {
2365
+ const all = await list();
2366
+ const byId = all.find((m) => m.id === idOrName);
2367
+ if (byId) return byId;
2368
+ const byPrefix = all.filter((m) => m.id.startsWith(idOrName));
2369
+ if (byPrefix.length === 1) return byPrefix[0];
2370
+ if (byPrefix.length > 1) throw new ApiError(`Metric id prefix '${idOrName}' is ambiguous`, 400);
2371
+ const byName = all.filter((m) => m.name === idOrName);
2372
+ if (byName.length === 1) return byName[0];
2373
+ if (byName.length > 1)
2374
+ throw new ApiError(`Metric name '${idOrName}' is ambiguous \u2014 pass an id`, 400);
2375
+ throw new ApiError(`Metric '${idOrName}' not found`, 404);
2376
+ }
2377
+ return {
2378
+ list,
2379
+ resolve,
2380
+ get: (id) => t.request("GET", `${BASE7}/${id}`),
2381
+ create: (input) => t.request("POST", BASE7, input),
2382
+ delete: (id) => t.request("DELETE", `${BASE7}/${id}`)
2383
+ };
2384
+ }
2385
+
2386
+ // src/resources/events.ts
2387
+ var BASE8 = "/api/admin/events";
2388
+ function eventsClient(t) {
2389
+ async function list() {
2390
+ return t.request("GET", BASE8);
2391
+ }
2392
+ async function resolve(idOrName) {
2393
+ const all = await list();
2394
+ const byId = all.find((e) => e.id === idOrName);
2395
+ if (byId) return byId;
2396
+ const byPrefix = all.filter((e) => e.id.startsWith(idOrName));
2397
+ if (byPrefix.length === 1) return byPrefix[0];
2398
+ if (byPrefix.length > 1) throw new ApiError(`Event id prefix '${idOrName}' is ambiguous`, 400);
2399
+ const byName = all.filter((e) => e.name === idOrName);
2400
+ if (byName.length === 1) return byName[0];
2401
+ if (byName.length > 1)
2402
+ throw new ApiError(`Event name '${idOrName}' is ambiguous \u2014 pass an id`, 400);
2403
+ throw new ApiError(`Event '${idOrName}' not found`, 404);
2404
+ }
2405
+ return {
2406
+ list,
2407
+ resolve,
2408
+ get: (id) => t.request("GET", `${BASE8}/${id}`),
2409
+ create: (input) => t.request("POST", BASE8, input),
2410
+ update: (id, input) => t.request("PATCH", `${BASE8}/${id}`, input),
2411
+ approve: (id, input = {}) => t.request("POST", `${BASE8}/${id}/approve`, input),
2412
+ delete: (id) => t.request("DELETE", `${BASE8}/${id}`)
2413
+ };
2414
+ }
2415
+
2416
+ // src/resources/ops.ts
2417
+ var FEEDBACK = "/api/admin/feedback";
2418
+ var NOTIFY = "/api/admin/notifications";
2419
+ var CREATE_PATH = {
2420
+ bug: "/api/admin/bugs",
2421
+ feature_request: "/api/admin/feature-requests"
2422
+ };
2423
+ function itemPath(handle) {
2424
+ return `${FEEDBACK}/${encodeURIComponent(handle)}`;
2425
+ }
2426
+ function opsClient(t) {
2427
+ return {
2428
+ list: (query = {}) => {
2429
+ const q = {};
2430
+ if (query.type) q.type = query.type;
2431
+ if (query.status) q.status = query.status;
2432
+ if (query.limit !== void 0) q.limit = String(query.limit);
2433
+ return t.request("GET", FEEDBACK, void 0, q);
2434
+ },
2435
+ get: (handle) => t.request("GET", itemPath(handle)),
2436
+ create: ({ type, ...body }) => t.request("POST", CREATE_PATH[type], body),
2437
+ update: (handle, input) => t.request("PATCH", itemPath(handle), input),
2438
+ linkPr: (handle, input) => t.request("POST", `${itemPath(handle)}/link-pr`, input),
2439
+ notify: (input) => t.request("POST", NOTIFY, {
2440
+ title: input.title,
2441
+ summary: input.summary,
2442
+ steps: input.steps ?? [],
2443
+ ...input.href ? { href: input.href } : {},
2444
+ ...input.dedupeKey ? { dedupeKey: input.dedupeKey } : {}
2445
+ }),
2446
+ channels: () => t.request("GET", "/api/admin/slack/channels")
2447
+ };
2448
+ }
2449
+
2450
+ // src/resources/projects.ts
2451
+ var BASE9 = "/api/admin/projects";
2452
+ function projectsClient(t) {
2453
+ return {
2454
+ upsert: (input) => t.request("POST", `${BASE9}/upsert`, {
2455
+ domain: input.domain,
2456
+ ...input.name ? { name: input.name } : {}
2457
+ }),
2458
+ current: () => t.request("GET", `${BASE9}/current`)
2459
+ };
2460
+ }
2461
+
2462
+ // src/resources/i18n.ts
2463
+ var PROFILES = "/api/admin/i18n/profiles";
2464
+ var KEYS = "/api/admin/i18n/keys";
2465
+ var DRAFTS = "/api/admin/i18n/drafts";
2466
+ function i18nClient(t) {
2467
+ async function listProfiles() {
2468
+ return t.request("GET", PROFILES);
2469
+ }
2470
+ async function resolveProfile(name) {
2471
+ const all = await listProfiles();
2472
+ const found = all.find((p) => p.name === name);
2473
+ if (!found)
2474
+ throw new ApiError(
2475
+ `Profile '${name}' not found. Existing: ${all.map((p) => p.name).join(", ") || "(none)"}`,
2476
+ 404
2477
+ );
2478
+ return found;
2479
+ }
2480
+ async function listKeys(query = {}) {
2481
+ const q = {};
2482
+ if (query.profile_id) q.profile_id = query.profile_id;
2483
+ if (query.prefix) q.prefix = query.prefix;
2484
+ if (query.limit !== void 0) q.limit = String(query.limit);
2485
+ const res = await t.request("GET", KEYS, void 0, q);
2486
+ return Array.isArray(res) ? res : res.keys;
2487
+ }
2488
+ function updateKey(id, input) {
2489
+ return t.request("PUT", `${KEYS}/${id}`, input);
2490
+ }
2491
+ return {
2492
+ listProfiles,
2493
+ resolveProfile,
2494
+ listKeys,
2495
+ updateKey,
2496
+ createProfile: (input) => t.request("POST", PROFILES, input),
2497
+ pushKeys: (input) => t.request("POST", KEYS, input),
2498
+ updateKeyByName: async (profileId, key, input) => {
2499
+ const rows = await listKeys({ profile_id: profileId, prefix: key, limit: 500 });
2500
+ const match = rows.find((k) => k.key === key);
2501
+ if (!match)
2502
+ throw new ApiError(`Key '${key}' not found in profile. Add it first with i18n push.`, 404);
2503
+ return updateKey(match.id, input);
2504
+ },
2505
+ listDrafts: () => t.request("GET", DRAFTS),
2506
+ publish: (profileId, input) => t.request("POST", `${PROFILES}/${profileId}/publish`, { chunk: input.chunk ?? "default" })
2507
+ };
2508
+ }
2509
+
2510
+ // src/resources/attributes.ts
2511
+ var BASE10 = "/api/admin/attributes";
2512
+ function attributesClient(t) {
2513
+ return {
2514
+ list: () => t.request("GET", BASE10)
2515
+ };
2516
+ }
2517
+
2349
2518
  // src/resources/index.ts
2350
2519
  function createAdminClient(transport) {
2351
2520
  return {
@@ -2355,7 +2524,13 @@ function createAdminClient(transport) {
2355
2524
  configs: configsClient(transport),
2356
2525
  universes: universesClient(transport),
2357
2526
  killswitches: killswitchesClient(transport),
2358
- alertRules: alertRulesClient(transport)
2527
+ alertRules: alertRulesClient(transport),
2528
+ metrics: metricsClient(transport),
2529
+ events: eventsClient(transport),
2530
+ ops: opsClient(transport),
2531
+ projects: projectsClient(transport),
2532
+ i18n: i18nClient(transport),
2533
+ attributes: attributesClient(transport)
2359
2534
  };
2360
2535
  }
2361
2536
  var RESOURCE_REGISTRY = [
@@ -2368,15 +2543,21 @@ var RESOURCE_REGISTRY = [
2368
2543
  export {
2369
2544
  RESOURCE_REGISTRY,
2370
2545
  alertRulesClient,
2546
+ attributesClient,
2371
2547
  configsClient,
2372
2548
  configsResource,
2373
2549
  createAdminClient,
2550
+ eventsClient,
2374
2551
  experimentsClient,
2375
2552
  experimentsResource,
2376
2553
  gatesClient,
2377
2554
  gatesResource,
2555
+ i18nClient,
2378
2556
  killswitchesClient,
2379
2557
  killswitchesResource,
2558
+ metricsClient,
2559
+ opsClient,
2560
+ projectsClient,
2380
2561
  universesClient,
2381
2562
  universesResource
2382
2563
  };
@@ -0,0 +1,54 @@
1
+ import { Transport } from '../transport.js';
2
+
3
+ /**
4
+ * Metric definitions — the event-backed queries that power tracking dashboards
5
+ * and experiment success/guardrail metrics. A metric pins one event, one
6
+ * aggregation, and (for sum/avg) a value path, expressed as the metric query
7
+ * DSL or its typed IR form.
8
+ *
9
+ * The admin endpoint accepts either `query` (the DSL string) or `query_ir`
10
+ * (the typed IR). The CLI's vendored DSL parser turns `--query` into IR
11
+ * client-side for an early error; the server validates either form, so the
12
+ * registry op can pass `query` straight through and stay worker-safe (no
13
+ * parser bundled into the shared registry).
14
+ */
15
+ interface Metric {
16
+ id: string;
17
+ name: string;
18
+ folder: string | null;
19
+ eventName: string;
20
+ aggregation: string;
21
+ valuePath: string | null;
22
+ queryIr?: unknown;
23
+ [key: string]: unknown;
24
+ }
25
+ interface MetricCreateInput {
26
+ name: string;
27
+ event_name: string;
28
+ folder?: string | null;
29
+ winsorize_pct?: number;
30
+ min_detectable_effect?: number | null;
31
+ /** Metric query DSL string, e.g. `sum(purchase, amount)`. */
32
+ query?: string;
33
+ /** Typed IR form (alternative to `query`). */
34
+ query_ir?: unknown;
35
+ }
36
+ interface MetricsClient {
37
+ /** Every metric in the project (the list endpoint is not paginated). */
38
+ list(): Promise<Metric[]>;
39
+ /** Fetch one metric by its full id. */
40
+ get(id: string): Promise<Metric>;
41
+ /** Resolve by exact id, unique id-prefix, or exact (unique) name. */
42
+ resolve(idOrName: string): Promise<Metric>;
43
+ create(input: MetricCreateInput): Promise<{
44
+ id: string;
45
+ name: string;
46
+ }>;
47
+ /** Soft-delete (the user-facing verb is `archive`). */
48
+ delete(id: string): Promise<{
49
+ ok: true;
50
+ }>;
51
+ }
52
+ declare function metricsClient(t: Transport): MetricsClient;
53
+
54
+ export { type Metric, type MetricCreateInput, type MetricsClient, metricsClient };
@@ -0,0 +1,40 @@
1
+ // src/transport.ts
2
+ var ApiError = class extends Error {
3
+ constructor(message, status) {
4
+ super(message);
5
+ this.status = status;
6
+ this.name = "ApiError";
7
+ }
8
+ status;
9
+ };
10
+
11
+ // src/resources/metrics.ts
12
+ var BASE = "/api/admin/metrics";
13
+ function metricsClient(t) {
14
+ async function list() {
15
+ return t.request("GET", BASE);
16
+ }
17
+ async function resolve(idOrName) {
18
+ const all = await list();
19
+ const byId = all.find((m) => m.id === idOrName);
20
+ if (byId) return byId;
21
+ const byPrefix = all.filter((m) => m.id.startsWith(idOrName));
22
+ if (byPrefix.length === 1) return byPrefix[0];
23
+ if (byPrefix.length > 1) throw new ApiError(`Metric id prefix '${idOrName}' is ambiguous`, 400);
24
+ const byName = all.filter((m) => m.name === idOrName);
25
+ if (byName.length === 1) return byName[0];
26
+ if (byName.length > 1)
27
+ throw new ApiError(`Metric name '${idOrName}' is ambiguous \u2014 pass an id`, 400);
28
+ throw new ApiError(`Metric '${idOrName}' not found`, 404);
29
+ }
30
+ return {
31
+ list,
32
+ resolve,
33
+ get: (id) => t.request("GET", `${BASE}/${id}`),
34
+ create: (input) => t.request("POST", BASE, input),
35
+ delete: (id) => t.request("DELETE", `${BASE}/${id}`)
36
+ };
37
+ }
38
+ export {
39
+ metricsClient
40
+ };
@@ -0,0 +1,88 @@
1
+ import { Transport } from '../transport.js';
2
+
3
+ /**
4
+ * Operational queue — the unified feedback table of bug reports, feature
5
+ * requests, and auto-filed error/alert tickets, over `/api/admin/feedback`.
6
+ * One resource covers list/get/create/update/link-pr plus the `notify`
7
+ * escalation bell (`/api/admin/notifications`) and the Slack channel reader
8
+ * (`/api/admin/slack/channels`) used to resolve alert-rule targets.
9
+ *
10
+ * Item handles are either the per-project number (`7`) or the full id; the
11
+ * admin API resolves either, so callers pass the handle straight through.
12
+ */
13
+ type OpsType = "bug" | "feature_request" | "error" | "alert";
14
+ type OpsStatus = "open" | "triaged" | "in_progress" | "ready_for_qa" | "resolved" | "wont_fix";
15
+ type OpsPriority = "nice_to_have" | "medium" | "high" | "critical";
16
+ interface OpsItem {
17
+ id: string;
18
+ number: number | null;
19
+ type: string;
20
+ title: string;
21
+ status: string;
22
+ priority: string | null;
23
+ sourceRef?: string | null;
24
+ createdAt: string;
25
+ [key: string]: unknown;
26
+ }
27
+ interface OpsListQuery {
28
+ type?: OpsType | "all";
29
+ status?: OpsStatus | "all";
30
+ limit?: number;
31
+ }
32
+ interface OpsCreateInput {
33
+ /** Only `bug` / `feature_request` are user-fileable; error/alert are auto-filed. */
34
+ type: "bug" | "feature_request";
35
+ title: string;
36
+ body?: string;
37
+ priority?: OpsPriority;
38
+ stepsToReproduce?: string;
39
+ pageUrl?: string;
40
+ }
41
+ interface OpsUpdateInput {
42
+ status?: OpsStatus;
43
+ priority?: OpsPriority;
44
+ }
45
+ interface OpsNotifyInput {
46
+ title: string;
47
+ summary: string;
48
+ steps?: string[];
49
+ href?: string;
50
+ dedupeKey?: string;
51
+ }
52
+ interface SlackChannel {
53
+ id: string;
54
+ name: string;
55
+ isPrivate?: boolean;
56
+ }
57
+ interface SlackChannelsResponse {
58
+ connected: boolean;
59
+ channels: SlackChannel[];
60
+ }
61
+ interface OpsClient {
62
+ list(query?: OpsListQuery): Promise<OpsItem[]>;
63
+ get(handle: string): Promise<OpsItem>;
64
+ /** File a bug or feature request. Fires project connectors (GitHub/Slack). */
65
+ create(input: OpsCreateInput): Promise<{
66
+ id: string;
67
+ number?: number | null;
68
+ }>;
69
+ update(handle: string, input: OpsUpdateInput): Promise<{
70
+ id: string;
71
+ }>;
72
+ linkPr(handle: string, input: {
73
+ prNumber: number | null;
74
+ prUrl?: string;
75
+ }): Promise<{
76
+ id: string;
77
+ }>;
78
+ /** Raise a 'needs your attention' bell notification (create-only, idempotent on dedupeKey). */
79
+ notify(input: OpsNotifyInput): Promise<{
80
+ dedupeKey: string;
81
+ dispatched: boolean;
82
+ }>;
83
+ /** The project's Slack channels — used to resolve alert-rule `--slack-channel`. */
84
+ channels(): Promise<SlackChannelsResponse>;
85
+ }
86
+ declare function opsClient(t: Transport): OpsClient;
87
+
88
+ export { type OpsClient, type OpsCreateInput, type OpsItem, type OpsListQuery, type OpsNotifyInput, type OpsPriority, type OpsStatus, type OpsType, type OpsUpdateInput, type SlackChannel, type SlackChannelsResponse, opsClient };
@@ -0,0 +1,36 @@
1
+ // src/resources/ops.ts
2
+ var FEEDBACK = "/api/admin/feedback";
3
+ var NOTIFY = "/api/admin/notifications";
4
+ var CREATE_PATH = {
5
+ bug: "/api/admin/bugs",
6
+ feature_request: "/api/admin/feature-requests"
7
+ };
8
+ function itemPath(handle) {
9
+ return `${FEEDBACK}/${encodeURIComponent(handle)}`;
10
+ }
11
+ function opsClient(t) {
12
+ return {
13
+ list: (query = {}) => {
14
+ const q = {};
15
+ if (query.type) q.type = query.type;
16
+ if (query.status) q.status = query.status;
17
+ if (query.limit !== void 0) q.limit = String(query.limit);
18
+ return t.request("GET", FEEDBACK, void 0, q);
19
+ },
20
+ get: (handle) => t.request("GET", itemPath(handle)),
21
+ create: ({ type, ...body }) => t.request("POST", CREATE_PATH[type], body),
22
+ update: (handle, input) => t.request("PATCH", itemPath(handle), input),
23
+ linkPr: (handle, input) => t.request("POST", `${itemPath(handle)}/link-pr`, input),
24
+ notify: (input) => t.request("POST", NOTIFY, {
25
+ title: input.title,
26
+ summary: input.summary,
27
+ steps: input.steps ?? [],
28
+ ...input.href ? { href: input.href } : {},
29
+ ...input.dedupeKey ? { dedupeKey: input.dedupeKey } : {}
30
+ }),
31
+ channels: () => t.request("GET", "/api/admin/slack/channels")
32
+ };
33
+ }
34
+ export {
35
+ opsClient
36
+ };
@@ -0,0 +1,54 @@
1
+ import { Transport } from '../transport.js';
2
+
3
+ /**
4
+ * Projects — find-or-create by domain (`upsert`) and the auth-resolved current
5
+ * project (`current`). The `.shipeasy` write that `bind`/`upsert --bind`
6
+ * performs is a CONSUMER side-effect layered on top of these calls (it touches
7
+ * the filesystem); it never lives in the resource, so this stays worker-safe.
8
+ */
9
+ interface UpsertResult {
10
+ id: string;
11
+ name: string;
12
+ domain: string | null;
13
+ owner_email: string;
14
+ created: boolean;
15
+ }
16
+ /**
17
+ * The project the caller's auth header resolves to. The server reads the
18
+ * project from the credential — there is no id parameter — so `current` works
19
+ * with nothing but the session, which is what makes a registry-driven `whoami`
20
+ * possible.
21
+ */
22
+ interface CurrentProject {
23
+ id: string;
24
+ name: string;
25
+ domain: string | null;
26
+ ownerEmail: string;
27
+ plan: "free" | "paid";
28
+ status: "active" | "inactive";
29
+ subscriptionStatus: string;
30
+ billingInterval: "monthly" | "annual";
31
+ currentPeriodEnd: string | null;
32
+ trialEndsAt: string | null;
33
+ cancelAtPeriodEnd: number;
34
+ moduleTranslations: boolean | number;
35
+ moduleConfigs: boolean | number;
36
+ moduleGates: boolean | number;
37
+ moduleExperiments: boolean | number;
38
+ moduleFeedback: boolean | number;
39
+ createdAt: string;
40
+ updatedAt: string;
41
+ [key: string]: unknown;
42
+ }
43
+ interface ProjectsClient {
44
+ /** Find-or-create a project by domain (idempotent). */
45
+ upsert(input: {
46
+ domain: string;
47
+ name?: string;
48
+ }): Promise<UpsertResult>;
49
+ /** The project the auth header resolves to (no id needed). */
50
+ current(): Promise<CurrentProject>;
51
+ }
52
+ declare function projectsClient(t: Transport): ProjectsClient;
53
+
54
+ export { type CurrentProject, type ProjectsClient, type UpsertResult, projectsClient };
@@ -0,0 +1,14 @@
1
+ // src/resources/projects.ts
2
+ var BASE = "/api/admin/projects";
3
+ function projectsClient(t) {
4
+ return {
5
+ upsert: (input) => t.request("POST", `${BASE}/upsert`, {
6
+ domain: input.domain,
7
+ ...input.name ? { name: input.name } : {}
8
+ }),
9
+ current: () => t.request("GET", `${BASE}/current`)
10
+ };
11
+ }
12
+ export {
13
+ projectsClient
14
+ };
@@ -2,6 +2,14 @@ import { z } from 'zod';
2
2
 
3
3
  declare const ALERT_COMPARATORS: readonly ["gt", "gte", "lt", "lte"];
4
4
  declare const ALERT_SEVERITIES: readonly ["danger", "warn", "info"];
5
+ declare const notificationTargetSchema: z.ZodObject<{
6
+ slackChannel: z.ZodOptional<z.ZodNullable<z.ZodObject<{
7
+ id: z.ZodString;
8
+ name: z.ZodString;
9
+ }, z.core.$strip>>>;
10
+ email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
11
+ }, z.core.$strict>;
12
+ type NotificationTarget = z.infer<typeof notificationTargetSchema>;
5
13
  declare const alertRuleCreateSchema: z.ZodObject<{
6
14
  name: z.ZodString;
7
15
  metricId: z.ZodString;
@@ -19,6 +27,13 @@ declare const alertRuleCreateSchema: z.ZodObject<{
19
27
  info: "info";
20
28
  }>>;
21
29
  enabled: z.ZodDefault<z.ZodBoolean>;
30
+ notify: z.ZodOptional<z.ZodNullable<z.ZodObject<{
31
+ slackChannel: z.ZodOptional<z.ZodNullable<z.ZodObject<{
32
+ id: z.ZodString;
33
+ name: z.ZodString;
34
+ }, z.core.$strip>>>;
35
+ email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
36
+ }, z.core.$strict>>>;
22
37
  }, z.core.$strict>;
23
38
  declare const alertRuleUpdateSchema: z.ZodObject<{
24
39
  name: z.ZodOptional<z.ZodString>;
@@ -36,8 +51,15 @@ declare const alertRuleUpdateSchema: z.ZodObject<{
36
51
  info: "info";
37
52
  }>>;
38
53
  enabled: z.ZodOptional<z.ZodBoolean>;
54
+ notify: z.ZodOptional<z.ZodOptional<z.ZodNullable<z.ZodObject<{
55
+ slackChannel: z.ZodOptional<z.ZodNullable<z.ZodObject<{
56
+ id: z.ZodString;
57
+ name: z.ZodString;
58
+ }, z.core.$strip>>>;
59
+ email: z.ZodOptional<z.ZodNullable<z.ZodString>>;
60
+ }, z.core.$strict>>>>;
39
61
  }, z.core.$strict>;
40
62
  type AlertRuleCreate = z.infer<typeof alertRuleCreateSchema>;
41
63
  type AlertRuleUpdate = z.infer<typeof alertRuleUpdateSchema>;
42
64
 
43
- export { ALERT_COMPARATORS, ALERT_SEVERITIES, type AlertRuleCreate, type AlertRuleUpdate, alertRuleCreateSchema, alertRuleUpdateSchema };
65
+ export { ALERT_COMPARATORS, ALERT_SEVERITIES, type AlertRuleCreate, type AlertRuleUpdate, type NotificationTarget, alertRuleCreateSchema, alertRuleUpdateSchema, notificationTargetSchema };
@@ -2,6 +2,13 @@
2
2
  import { z } from "zod";
3
3
  var ALERT_COMPARATORS = ["gt", "gte", "lt", "lte"];
4
4
  var ALERT_SEVERITIES = ["danger", "warn", "info"];
5
+ var notificationTargetSchema = z.object({
6
+ slackChannel: z.object({
7
+ id: z.string().min(1).describe("Slack channel id (e.g. C0123ABCD)."),
8
+ name: z.string().min(1).describe("Slack channel name (without the leading #).")
9
+ }).nullish().describe("Slack channel to post this rule's alert to. Requires a Slack connector."),
10
+ email: z.string().email().nullish().describe("Email address to notify for this rule (overrides the default recipient).")
11
+ }).strict();
5
12
  var alertRuleCreateSchema = z.object({
6
13
  name: z.string().min(1).max(120).describe("Human label for the rule, shown on the alert and the rules list."),
7
14
  metricId: z.string().min(1).describe("Id of the metric to evaluate."),
@@ -9,7 +16,8 @@ var alertRuleCreateSchema = z.object({
9
16
  threshold: z.number().finite().describe("Threshold the metric value is compared against."),
10
17
  windowHours: z.number().int().min(1).max(720).default(24).describe("Lookback window (hours) the metric is aggregated over. 1\u2013720."),
11
18
  severity: z.enum(ALERT_SEVERITIES).default("warn").describe("Severity of the raised alert."),
12
- enabled: z.boolean().default(true).describe("Whether the rule is evaluated by the cron.")
19
+ enabled: z.boolean().default(true).describe("Whether the rule is evaluated by the cron."),
20
+ notify: notificationTargetSchema.nullish().describe("Where to deliver this rule's alert (Slack channel and/or email target).")
13
21
  }).strict();
14
22
  var alertRuleUpdateSchema = z.object({
15
23
  name: z.string().min(1).max(120),
@@ -17,11 +25,13 @@ var alertRuleUpdateSchema = z.object({
17
25
  threshold: z.number().finite(),
18
26
  windowHours: z.number().int().min(1).max(720),
19
27
  severity: z.enum(ALERT_SEVERITIES),
20
- enabled: z.boolean()
28
+ enabled: z.boolean(),
29
+ notify: notificationTargetSchema.nullish()
21
30
  }).partial().strict();
22
31
  export {
23
32
  ALERT_COMPARATORS,
24
33
  ALERT_SEVERITIES,
25
34
  alertRuleCreateSchema,
26
- alertRuleUpdateSchema
35
+ alertRuleUpdateSchema,
36
+ notificationTargetSchema
27
37
  };
@@ -7,5 +7,5 @@ export { GateCreateInput, GateUpdateInput, StackedGateEntryInput, gateCreateResp
7
7
  export { KillswitchCreateInput, KillswitchSwitchSetInput, KillswitchSwitchUnsetInput, KillswitchUpdateInput, KillswitchValue, KillswitchValueSetInput, SWITCH_KEY_RE, killswitchCreateResponseSchema, killswitchCreateSchema, killswitchDeleteResponseSchema, killswitchResponseSchema, killswitchSwitchSetResponseSchema, killswitchSwitchSetSchema, killswitchSwitchUnsetResponseSchema, killswitchSwitchUnsetSchema, killswitchUpdateResponseSchema, killswitchUpdateSchema, killswitchValueSchema, killswitchValueSetSchema, switchKeySchema } from './killswitches.js';
8
8
  export { UniverseCreateInput, UniverseUpdateInput, holdoutRangeSchema, universeCreateResponseSchema, universeCreateSchema, universeDeleteResponseSchema, universeNameSchema, universeResponseSchema, universeUpdateResponseSchema, universeUpdateSchema } from './universes.js';
9
9
  export { ExperimentCreateInput, ExperimentInlineMetric, ExperimentUpdateInput, bucketBySchema, experimentCreateResponseSchema, experimentCreateSchema, experimentDeleteResponseSchema, experimentGroupSchema, experimentInlineMetricSchema, experimentMetricsResponseSchema, experimentMetricsUpdateSchema, experimentNameSchema, experimentReanalyzeResponseSchema, experimentResponseSchema, experimentResultsResponseSchema, experimentStatusResponseSchema, experimentStatusUpdateSchema, experimentTimeseriesResponseSchema, experimentUpdateResponseSchema, experimentUpdateSchema, paramSchemaSchema } from './experiments.js';
10
- export { ALERT_COMPARATORS, ALERT_SEVERITIES, AlertRuleCreate, AlertRuleUpdate, alertRuleCreateSchema, alertRuleUpdateSchema } from './alert-rules.js';
10
+ export { ALERT_COMPARATORS, ALERT_SEVERITIES, AlertRuleCreate, AlertRuleUpdate, NotificationTarget, alertRuleCreateSchema, alertRuleUpdateSchema, notificationTargetSchema } from './alert-rules.js';
11
11
  import 'zod';