@apicircle/shared 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1245 @@
1
+ declare function generateId(): string;
2
+
3
+ /**
4
+ * Pure validators shared between core and UI. Each returns a discriminated
5
+ * union so callers can branch cleanly: `.ok ? proceed : showError(reason)`.
6
+ *
7
+ * No throws — failures are explicit values. Validators never mutate input.
8
+ *
9
+ * Used by:
10
+ * - Inline UI feedback (`role="alert"` under inputs, disabled submit)
11
+ * - PreSendPanel + Send-time guards
12
+ * - Test fixtures that want to check shape without fabricating asserts
13
+ */
14
+ type ValidationResult = {
15
+ ok: true;
16
+ } | {
17
+ ok: false;
18
+ reason: string;
19
+ };
20
+ /**
21
+ * Accepts any URL the browser can fetch + the variable-template form
22
+ * `{{NAME}}/path`. Empty + whitespace-only fail. Schemes other than
23
+ * http/https/file/{{...}} fail (mailto:/tel: aren't fetchable from
24
+ * Studio's executor).
25
+ */
26
+ declare function validateUrl(value: string): ValidationResult;
27
+ declare function validateAwsRegion(value: string): ValidationResult;
28
+ /**
29
+ * Mock endpoint path pattern: must start with `/`, no whitespace, no
30
+ * query string (`?` is an error — query matching is a separate concern).
31
+ * Permits Express-style `:param` segments and `*` wildcards.
32
+ */
33
+ declare function validateMockPath(value: string): ValidationResult;
34
+ /**
35
+ * Environment-variable key — the bit between `{{ }}` at resolve time.
36
+ * Allowed: ASCII letters, digits, underscore, hyphen. First character
37
+ * must be a letter or underscore (matches POSIX env-var naming).
38
+ */
39
+ declare function validateEnvVarName(value: string): ValidationResult;
40
+ /**
41
+ * Plan name — same characters allowed as env-var names plus spaces, but
42
+ * must be non-empty after trim. Caller is responsible for uniqueness.
43
+ */
44
+ declare function validatePlanName(value: string): ValidationResult;
45
+ /**
46
+ * GitHub PR title — non-empty after trim, ≤256 chars (GitHub's hard cap).
47
+ */
48
+ declare function validatePRTitle(value: string): ValidationResult;
49
+ /**
50
+ * JSON validity — accepts only object/array roots (string/number/bool/null
51
+ * are technically valid JSON but rarely what users mean for headers/body
52
+ * payloads; we surface a clearer message).
53
+ */
54
+ declare function validateJsonString(value: string, opts?: {
55
+ allowEmpty?: boolean;
56
+ allowRoots?: 'object' | 'array' | 'any';
57
+ }): ValidationResult;
58
+ declare function validateHttpHeaderName(value: string): ValidationResult;
59
+ /**
60
+ * JavaScript-compatible regular-expression body. Lets the user spot
61
+ * unclosed groups / bad character classes at edit time rather than at
62
+ * runtime where the rule silently never matches.
63
+ */
64
+ declare function validateRegex(value: string, flags?: string): ValidationResult;
65
+ declare function validateJsonPath(value: string): ValidationResult;
66
+ /**
67
+ * Non-negative integer duration in milliseconds (or whatever unit the
68
+ * caller documents). 0 is allowed for "no wait"; negative values reject.
69
+ */
70
+ declare function validatePositiveDuration(value: number | string): ValidationResult;
71
+ /**
72
+ * Returns the URL string if `value` is a syntactically valid URL whose scheme
73
+ * is `http:` or `https:` — otherwise `null`. Use this at every site that
74
+ * renders a third-party-supplied URL as `<a href>` or hands one to
75
+ * `window.open` / `shell.openExternal`. The OAuth2 device-flow
76
+ * `verification_uri` comes straight from the IdP and could in principle be
77
+ * `javascript:`, `data:`, `file:`, or a custom OS protocol handler —
78
+ * rendering any of those is an XSS / RCE foot-gun.
79
+ *
80
+ * `null` returns should be rendered as plain text so the user can still see
81
+ * and copy the value, but cannot one-click execute it.
82
+ */
83
+ declare function safeExternalHref(value: unknown): string | null;
84
+
85
+ /**
86
+ * Human-readable byte size. UTF-8 byte count, base-1024 (KiB/MiB).
87
+ * 0 → "0 B", 1023 → "1023 B", 1024 → "1.0 KB", 1_500_000 → "1.4 MB".
88
+ */
89
+ declare function formatBytes(bytes: number): string;
90
+ /** UTF-8 byte length of a string. Falls back to char length if TextEncoder unavailable. */
91
+ declare function utf8ByteLength(s: string): number;
92
+
93
+ type MockResponseBodyType = 'none' | 'json' | 'text' | 'xml' | 'urlencoded' | 'form-data' | 'binary';
94
+ type MockResponseBody = {
95
+ type: 'none';
96
+ content: '';
97
+ } | {
98
+ type: 'json';
99
+ content: string;
100
+ } | {
101
+ type: 'text';
102
+ content: string;
103
+ } | {
104
+ type: 'xml';
105
+ content: string;
106
+ } | {
107
+ type: 'urlencoded';
108
+ content: string;
109
+ } | {
110
+ type: 'form-data';
111
+ content: '';
112
+ formRows: Array<{
113
+ key: string;
114
+ value: string;
115
+ enabled: boolean;
116
+ }>;
117
+ } | {
118
+ type: 'binary';
119
+ content: '';
120
+ /** Attachment ref into Global Assets — same shape as request bodies. */
121
+ attachment?: AttachmentRef;
122
+ };
123
+ interface MockResponseConfig {
124
+ status: number;
125
+ headers: Array<{
126
+ key: string;
127
+ value: string;
128
+ enabled: boolean;
129
+ }>;
130
+ body: MockResponseBody;
131
+ /** Optional artificial latency before responding. */
132
+ delayMs?: number;
133
+ /**
134
+ * Optional response-shape multipliers. At runtime, each multiplier reads a
135
+ * value from the request (a query/path/header param or a JSON-path slice
136
+ * of the request body) and repeats the array element at `targetJsonPath`
137
+ * inside the response body that many times. Used to drive page-size
138
+ * aware mock responses without templating the body manually.
139
+ *
140
+ * Only fires when `body.type === 'json'`; ignored otherwise.
141
+ */
142
+ multipliers?: MockResponseMultiplier[];
143
+ }
144
+ type MockMultiplierSourceKind = 'query' | 'pathParam' | 'header' | 'body-json-path';
145
+ interface MockMultiplierSource {
146
+ kind: MockMultiplierSourceKind;
147
+ /** Query/path/header name, or JSON path into the request body (e.g. "$.page.size"). */
148
+ key: string;
149
+ }
150
+ interface MockResponseMultiplier {
151
+ id: string;
152
+ /** Optional user-facing label. */
153
+ name?: string;
154
+ source: MockMultiplierSource;
155
+ /**
156
+ * JSON path into the *response body* pointing at the array to repeat
157
+ * (e.g. "$.items"). The first element of that array becomes the repeated
158
+ * template — additional elements are discarded.
159
+ */
160
+ targetJsonPath: string;
161
+ /** Used when source is missing or non-numeric. */
162
+ defaultCount: number;
163
+ /** Optional inclusive lower bound on the resolved count. */
164
+ min?: number;
165
+ /** Optional inclusive upper bound on the resolved count. */
166
+ max?: number;
167
+ }
168
+ interface MockParamDef {
169
+ /** Stable id so the editor can reorder rows without losing focus. */
170
+ id: string;
171
+ name: string;
172
+ /** Free-form type hint (e.g. 'string', 'integer', 'uuid'). Documentation only. */
173
+ typeHint?: string;
174
+ required?: boolean;
175
+ description?: string;
176
+ example?: string;
177
+ }
178
+ interface MockRequestSchema {
179
+ /** Declared path params (auto-derived from `{slot}` segments in pathPattern + manual entries). */
180
+ pathParams: MockParamDef[];
181
+ queryParams: MockParamDef[];
182
+ headers: MockParamDef[];
183
+ cookies: MockParamDef[];
184
+ /** Optional documentation for the expected request body shape. */
185
+ body?: {
186
+ description?: string;
187
+ example?: string;
188
+ };
189
+ }
190
+ type MockValidationKind = 'header-required' | 'header-equals' | 'header-matches' | 'query-required' | 'query-equals' | 'query-matches' | 'cookie-required' | 'body-required' | 'content-type-equals';
191
+ interface MockValidationRule {
192
+ id: string;
193
+ kind: MockValidationKind;
194
+ /** Header / query / cookie name being validated (empty for body / content-type). */
195
+ target: string;
196
+ /** Expected literal or regex (when applicable). */
197
+ expected?: string;
198
+ /** Friendly message surfaced into the failResponse body / debugger. */
199
+ message?: string;
200
+ /**
201
+ * Disable without deleting — disabled rules are skipped during request
202
+ * validation but stay in the editor for what-if debugging. Defaults to
203
+ * `true` for newly authored rules.
204
+ */
205
+ enabled: boolean;
206
+ /** Response returned when this rule fails. */
207
+ failResponse: MockResponseConfig;
208
+ }
209
+ type MockConditionScope = 'query' | 'pathParam' | 'header' | 'cookie' | 'body-json-path';
210
+ type MockConditionOp = 'equals' | 'not-equals' | 'matches' | 'gt' | 'lt' | 'gte' | 'lte' | 'present' | 'absent';
211
+ interface MockConditionClause {
212
+ id: string;
213
+ scope: MockConditionScope;
214
+ /** Name of the query/header/cookie/path-param OR a JSON-path for body matches. */
215
+ target: string;
216
+ op: MockConditionOp;
217
+ /** Comparison value (omitted for present/absent ops). */
218
+ value?: string;
219
+ }
220
+ interface MockResponseRule {
221
+ id: string;
222
+ /** User-facing rule label (e.g. "Page 1 — small response"). */
223
+ name: string;
224
+ /** Disable without deleting — useful for what-if testing. */
225
+ enabled: boolean;
226
+ /** AND-combined clauses; rule fires only when every clause matches. */
227
+ when: MockConditionClause[];
228
+ response: MockResponseConfig;
229
+ }
230
+ interface MockEndpoint {
231
+ /** Stable id; survives spec re-parses so per-endpoint overrides keep matching. */
232
+ id: string;
233
+ /** User-friendly label for the sidebar / picker. Defaults to "{METHOD} {pathPattern}". */
234
+ name: string;
235
+ method: HttpMethod;
236
+ /** OpenAPI-style path template, e.g. `/pets/{id}`. Hono routes get derived from this. */
237
+ pathPattern: string;
238
+ description?: string;
239
+ /** Declarative input schema — drives editor UI + runtime docs. */
240
+ requestSchema: MockRequestSchema;
241
+ /** Pre-validation gates evaluated before response rules. */
242
+ requestValidation: MockValidationRule[];
243
+ /** Conditional response rules (first match wins). */
244
+ responseRules: MockResponseRule[];
245
+ /** Fallback response when no rule matches. */
246
+ defaultResponse: MockResponseConfig;
247
+ /** Optional: name of the OpenAPI example chosen when multiple were present. */
248
+ example?: string;
249
+ }
250
+ type MockServerSource = {
251
+ kind: 'openapi';
252
+ spec: string;
253
+ format: 'json' | 'yaml';
254
+ } | {
255
+ kind: 'postman';
256
+ collection: string;
257
+ } | {
258
+ kind: 'insomnia';
259
+ export: string;
260
+ } | {
261
+ kind: 'manual';
262
+ endpoints: MockEndpoint[];
263
+ };
264
+ interface MockServer {
265
+ id: string;
266
+ name: string;
267
+ source: MockServerSource;
268
+ /**
269
+ * Resolved endpoint table — populated when the source is parsed; persisted
270
+ * so the desktop app doesn't re-parse on every start. Empty array for
271
+ * `kind: 'manual'` (the source carries the endpoints) — though in
272
+ * practice we mirror the manual endpoints into both fields so downstream
273
+ * consumers can read either one.
274
+ */
275
+ endpoints: MockEndpoint[];
276
+ /** Default port used when starting; null = pick a free port at start. */
277
+ defaultPort: number | null;
278
+ cors: {
279
+ enabled: boolean;
280
+ origins: string[];
281
+ };
282
+ createdAt: string;
283
+ updatedAt: string;
284
+ }
285
+ /** Lives in WorkspaceLocal — never pushed to git. */
286
+ interface MockRuntime {
287
+ /** Keyed by mockServerId. Absent = not running. */
288
+ active: Record<string, MockRuntimeEntry>;
289
+ }
290
+ interface MockRuntimeEntry {
291
+ port: number;
292
+ /** null in browser-preview mode where there's no OS process. */
293
+ pid: number | null;
294
+ startedAt: string;
295
+ lastError: string | null;
296
+ requestCount: number;
297
+ }
298
+ declare function getAllowedMockResponseBodyTypes(status: number): MockResponseBodyType[];
299
+ /**
300
+ * If `currentBodyType` isn't allowed for `status`, return a safe
301
+ * fallback (`'json'` for status codes that allow bodies, `'none'`
302
+ * otherwise). Returns `null` when the current type is already
303
+ * allowed — caller can early-return.
304
+ */
305
+ declare function coerceMockResponseBodyTypeForStatus(currentBodyType: MockResponseBodyType, status: number): MockResponseBodyType | null;
306
+ declare function makeDefaultMockResponseBody(type: MockResponseBodyType): MockResponseBody;
307
+ declare function makeDefaultMockResponse(): MockResponseConfig;
308
+ declare function makeDefaultRequestSchema(): MockRequestSchema;
309
+
310
+ type ThemeId = 'studio-dark' | 'graphite-dark' | 'midnight-blue' | 'workbench-light' | 'paper-light' | 'high-contrast-dark' | 'high-contrast-light' | 'dracula' | 'nord' | 'tokyo-night' | 'one-dark-pro' | 'monokai-pro' | 'gruvbox-dark' | 'solarized-dark' | 'catppuccin-mocha' | 'catppuccin-macchiato' | 'synthwave-84' | 'cobalt2' | 'rose-pine' | 'ayu-mirage' | 'night-owl' | 'github-dark' | 'material-palenight' | 'solarized-light' | 'github-light' | 'catppuccin-latte' | 'ayu-light' | 'atom-one-light' | 'rose-pine-dawn' | 'tokyo-night-day';
311
+ type FontFamilyId = 'system-mono' | 'jetbrains-mono' | 'fira-code' | 'cascadia-code' | 'ibm-plex-mono' | 'source-code-pro' | 'roboto-mono' | 'space-mono' | 'hack' | 'inconsolata' | 'anonymous-pro' | 'ubuntu-mono' | 'dm-mono' | 'geist-mono' | 'red-hat-mono' | 'azeret-mono' | 'victor-mono' | 'system-sans' | 'inter' | 'roboto' | 'open-sans' | 'lato' | 'source-sans-3' | 'nunito-sans' | 'manrope' | 'dm-sans' | 'geist' | 'plus-jakarta-sans' | 'ibm-plex-sans' | 'work-sans';
312
+ type PanelId = 'workspace' | 'link-workspace' | 'editor' | 'env' | 'execution' | 'history' | 'mocks' | 'mcp' | 'help';
313
+ /**
314
+ * Display name used when seeding a fresh workspace's registry entry on
315
+ * first boot. The name itself is local-only — it never lives in the
316
+ * git-synced doc — so two machines pulling the same workspace.json can
317
+ * each call their local copy whatever they want.
318
+ */
319
+ declare const DEFAULT_WORKSPACE_NAME = "My Workspace";
320
+ interface WorkspaceSynced {
321
+ schemaVersion: 1;
322
+ workspaceId: string;
323
+ collections: {
324
+ tree: FolderNode;
325
+ requests: Record<string, Request>;
326
+ folders: Record<string, Folder>;
327
+ };
328
+ environments: {
329
+ items: Record<string, Environment>;
330
+ activeName: string | null;
331
+ /**
332
+ * Ordered list of envs the resolver layers into request scope. Mixes
333
+ * local and linked-workspace envs — the consumer picks order. See
334
+ * `EnvPriorityRef`.
335
+ */
336
+ priorityOrder: EnvPriorityRef[];
337
+ };
338
+ linkedWorkspaces: Record<string, LinkedWorkspace>;
339
+ linkedOverrides: {
340
+ requests: Record<string, RequestOverride>;
341
+ environmentVars: Record<string, EnvironmentVariableOverride>;
342
+ };
343
+ releases: {
344
+ self: ReleaseHistory | null;
345
+ perLink: Record<string, ReleaseHistory>;
346
+ };
347
+ globalAssets: {
348
+ schemas: Record<string, GlobalSchema>;
349
+ graphql: Record<string, GlobalGraphQL>;
350
+ };
351
+ mockServers: Record<string, MockServer>;
352
+ /**
353
+ * Workspace-wide execution plans. Plan **definitions** travel through
354
+ * Git so collaborators on the same workspace see the same plans;
355
+ * plan **runs** (history) stay in `WorkspaceLocal.history.planRuns`
356
+ * because they're per-device and per-execution.
357
+ *
358
+ * Optional in the type: pre-migration workspaces persisted plans on
359
+ * `WorkspaceLocal.executionPlans` only; the hydration normalizer
360
+ * lifts those into `synced.executionPlans` on first load. The store
361
+ * always writes a populated value (defaulting to `{}`) after
362
+ * migration, so consumers can rely on `synced.executionPlans` being
363
+ * defined post-hydrate.
364
+ */
365
+ executionPlans?: Record<string, ExecutionPlan>;
366
+ secretKeys?: Record<string, SecretKeyMeta>;
367
+ /**
368
+ * Workspace-passphrase crypto state. `null` when no passphrase has been
369
+ * set yet (the workspace either has no secrets, or hasn't been migrated
370
+ * to the passphrase model). Populated by `setupPassphrase` the first
371
+ * time a user creates a passphrase; from then on, decryption requires
372
+ * the same passphrase to be re-entered (in memory only).
373
+ *
374
+ * The actual encrypted secret-value payloads still live in device-local
375
+ * IndexedDB today; migrating those into the synced doc is its own
376
+ * follow-up.
377
+ *
378
+ * `kdf` / `salt` / `iterations` parameterise the PBKDF2 derivation;
379
+ * `verifier` lets us reject a wrong passphrase up front without trying
380
+ * to decrypt every payload. See `passphraseKey.ts` for the algorithm.
381
+ */
382
+ secretCrypto?: SecretCryptoMeta | null;
383
+ meta: {
384
+ createdAt: string;
385
+ updatedAt: string;
386
+ appVersion: string;
387
+ };
388
+ }
389
+ interface FolderNode {
390
+ id: string;
391
+ type: 'root' | 'folder';
392
+ children: Array<{
393
+ kind: 'folder' | 'request';
394
+ id: string;
395
+ }>;
396
+ }
397
+ interface Folder {
398
+ id: string;
399
+ name: string;
400
+ parentId: string | null;
401
+ /**
402
+ * Optional folder-level auth. When a request has `auth.type === 'inherit'`,
403
+ * the runner walks up the folder chain and uses the first explicit
404
+ * (non-`inherit`, non-`none`) auth it finds. Absent here = no folder-level
405
+ * auth at this level (continue walking up).
406
+ */
407
+ auth?: RequestAuth;
408
+ }
409
+ type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
410
+ type BodyType = 'none' | 'json' | 'text' | 'form-data' | 'urlencoded' | 'binary' | 'xml' | 'graphql';
411
+ interface Request {
412
+ id: string;
413
+ name: string;
414
+ folderId: string | null;
415
+ method: HttpMethod;
416
+ url: string;
417
+ headers: Array<{
418
+ key: string;
419
+ value: string;
420
+ enabled: boolean;
421
+ }>;
422
+ query: Array<{
423
+ key: string;
424
+ value: string;
425
+ enabled: boolean;
426
+ }>;
427
+ /**
428
+ * Values for URL path placeholders (`:name` Express-style or `{name}`
429
+ * OpenAPI-style). Keys are expected to match placeholder names found in
430
+ * `url`. Missing keys substitute to empty string at send time. Absent =
431
+ * empty (no path params), so the field is optional in storage.
432
+ */
433
+ pathParams?: Record<string, string>;
434
+ /**
435
+ * Cookies sent with the request. Joined into a single `Cookie` header at
436
+ * send time (existing user-set Cookie header wins). Absent = no cookies.
437
+ */
438
+ cookies?: Array<{
439
+ key: string;
440
+ value: string;
441
+ enabled: boolean;
442
+ }>;
443
+ body: RequestBody;
444
+ auth: RequestAuth;
445
+ contextVars: Array<{
446
+ key: string;
447
+ value: string;
448
+ }>;
449
+ extractions: ContextExtraction[];
450
+ bodySchemaId?: string | null;
451
+ graphqlSchemaId?: string | null;
452
+ assertions: Assertion[];
453
+ createdAt: string;
454
+ updatedAt: string;
455
+ }
456
+ interface ContextExtraction {
457
+ id: string;
458
+ variable: string;
459
+ source: 'body' | 'header' | 'cookie' | 'status';
460
+ /**
461
+ * Source-specific path:
462
+ * - body: JSON path (dot/bracket, e.g. `data.token` or `items[0].id`)
463
+ * - header: header name (case-insensitive)
464
+ * - cookie: cookie name
465
+ * - status: ignored — the HTTP status code is the value
466
+ */
467
+ path: string;
468
+ enabled: boolean;
469
+ }
470
+ interface GlobalSchema {
471
+ id: string;
472
+ name: string;
473
+ description?: string;
474
+ /** JSON Schema document, stored as a string so the user can paste any draft. */
475
+ schema: string;
476
+ createdAt: string;
477
+ updatedAt: string;
478
+ }
479
+ interface GlobalGraphQL {
480
+ id: string;
481
+ name: string;
482
+ description?: string;
483
+ kind: 'sdl' | 'introspection';
484
+ source: string;
485
+ createdAt: string;
486
+ updatedAt: string;
487
+ }
488
+ type RequestAuth = {
489
+ type: 'none';
490
+ } | {
491
+ type: 'inherit';
492
+ } | {
493
+ type: 'bearer';
494
+ token: string;
495
+ } | {
496
+ type: 'basic';
497
+ username: string;
498
+ password: string;
499
+ } | {
500
+ type: 'api-key';
501
+ key: string;
502
+ value: string;
503
+ addTo: 'header' | 'query' | 'cookie';
504
+ } | {
505
+ type: 'custom-header';
506
+ key: string;
507
+ value: string;
508
+ } | OAuth2ClientCredentialsAuth | OAuth2AuthCodeAuth | OAuth2PkceAuth | OAuth2PasswordAuth | OAuth2ImplicitAuth | OAuth2DeviceAuth | AwsSigV4Auth | DigestAuth | NtlmAuth | HawkAuth | JwtBearerAuth;
509
+ interface OAuth2TokenState {
510
+ accessToken: string;
511
+ tokenType: string;
512
+ refreshToken: string;
513
+ /**
514
+ * Epoch milliseconds when the access token expires, or 0 / null when
515
+ * unknown. Stored as number so all comparisons are direct
516
+ * `Date.now() < expiresAt` without round-tripping through Date()
517
+ * parsing on the hot path. Workspace serialization rolls it through
518
+ * JSON unchanged — git-side this is a number, not an ISO string.
519
+ */
520
+ expiresAt: number | null;
521
+ /**
522
+ * Scope the IdP actually granted (may differ from the request's
523
+ * `scope` field if the user/client is missing some). Refresh keeps
524
+ * this; clearing the token resets to ''.
525
+ */
526
+ obtainedScope: string;
527
+ }
528
+ interface OAuth2ClientCredentialsAuth extends OAuth2TokenState {
529
+ type: 'oauth2-client-credentials';
530
+ tokenUrl: string;
531
+ clientId: string;
532
+ clientSecret: string;
533
+ scope: string;
534
+ clientAuthMethod: 'header' | 'body';
535
+ }
536
+ interface OAuth2AuthCodeAuth extends OAuth2TokenState {
537
+ type: 'oauth2-auth-code';
538
+ authUrl: string;
539
+ tokenUrl: string;
540
+ clientId: string;
541
+ clientSecret: string;
542
+ redirectUri: string;
543
+ scope: string;
544
+ state: string;
545
+ }
546
+ interface OAuth2PkceAuth extends OAuth2TokenState {
547
+ type: 'oauth2-pkce';
548
+ authUrl: string;
549
+ tokenUrl: string;
550
+ clientId: string;
551
+ clientSecret: string;
552
+ redirectUri: string;
553
+ scope: string;
554
+ state: string;
555
+ codeVerifier: string;
556
+ codeChallengeMethod: 'S256' | 'plain';
557
+ }
558
+ interface OAuth2PasswordAuth extends OAuth2TokenState {
559
+ type: 'oauth2-password';
560
+ tokenUrl: string;
561
+ clientId: string;
562
+ clientSecret: string;
563
+ username: string;
564
+ password: string;
565
+ scope: string;
566
+ }
567
+ interface OAuth2ImplicitAuth extends Omit<OAuth2TokenState, 'refreshToken'> {
568
+ type: 'oauth2-implicit';
569
+ authUrl: string;
570
+ clientId: string;
571
+ redirectUri: string;
572
+ scope: string;
573
+ }
574
+ interface OAuth2DeviceAuth extends OAuth2TokenState {
575
+ type: 'oauth2-device';
576
+ deviceAuthUrl: string;
577
+ tokenUrl: string;
578
+ clientId: string;
579
+ scope: string;
580
+ deviceCode: string;
581
+ userCode: string;
582
+ verificationUri: string;
583
+ }
584
+ interface AwsSigV4Auth {
585
+ type: 'aws-sigv4';
586
+ accessKeyId: string;
587
+ secretAccessKey: string;
588
+ sessionToken: string;
589
+ region: string;
590
+ service: string;
591
+ addTo: 'header' | 'query';
592
+ }
593
+ interface DigestAuth {
594
+ type: 'digest';
595
+ username: string;
596
+ password: string;
597
+ }
598
+ interface NtlmAuth {
599
+ type: 'ntlm';
600
+ username: string;
601
+ password: string;
602
+ domain: string;
603
+ workstation: string;
604
+ }
605
+ interface HawkAuth {
606
+ type: 'hawk';
607
+ hawkId: string;
608
+ hawkKey: string;
609
+ algorithm: 'sha256' | 'sha1';
610
+ ext: string;
611
+ /**
612
+ * When true, the request body is folded into the Hawk MAC via the
613
+ * payload-hash extension (Hawk spec §3.2.5). Required for servers
614
+ * configured with strict body-binding; leave false for the looser
615
+ * "header-only" form that most public Hawk APIs accept.
616
+ */
617
+ bindPayload?: boolean;
618
+ }
619
+ interface JwtBearerAuth {
620
+ type: 'jwt-bearer';
621
+ algorithm: 'HS256' | 'HS384' | 'HS512' | 'RS256' | 'RS384' | 'RS512' | 'PS256' | 'PS384' | 'PS512' | 'ES256' | 'ES384' | 'ES512' | 'EdDSA';
622
+ secretOrKey: string;
623
+ payload: string;
624
+ jwtHeaders: string;
625
+ token: string;
626
+ }
627
+ interface RequestBody {
628
+ type: BodyType;
629
+ content: string;
630
+ formRows?: FormDataRow[];
631
+ attachment?: AttachmentRef;
632
+ variables?: string;
633
+ }
634
+ type FormDataRow = {
635
+ kind: 'text';
636
+ key: string;
637
+ value: string;
638
+ enabled: boolean;
639
+ } | {
640
+ kind: 'file';
641
+ key: string;
642
+ slotId: string | null;
643
+ filename?: string;
644
+ size?: number;
645
+ mimeType?: string;
646
+ sha256?: string;
647
+ enabled: boolean;
648
+ };
649
+ interface AttachmentRef {
650
+ slotId: string | null;
651
+ filename?: string;
652
+ size?: number;
653
+ mimeType?: string;
654
+ sha256?: string;
655
+ }
656
+ interface Assertion {
657
+ id: string;
658
+ kind: 'status' | 'header' | 'json-path' | 'duration';
659
+ op: 'equals' | 'not-equals' | 'contains' | 'lt' | 'gt' | 'matches';
660
+ target?: string;
661
+ expected: string | number;
662
+ }
663
+ interface Environment {
664
+ name: string;
665
+ variables: EnvironmentVariable[];
666
+ }
667
+ /**
668
+ * Entry in the global / plan-level environment priority order. Both local
669
+ * environments and linked-workspace environments are first-class citizens
670
+ * — the consumer can interleave them in any order and the resolver layers
671
+ * them top-down at request-time. The two `kind`s exist because linked envs
672
+ * need a `linkedWorkspaceId` to resolve against the right snapshot in
673
+ * `WorkspaceLocal.linkedCollections` (and to apply the consumer's per-row
674
+ * overrides from `synced.linkedOverrides.environmentVars`).
675
+ *
676
+ * Stored in `WorkspaceSynced.environments.priorityOrder` and
677
+ * `ExecutionPlan.envPriorityOrder`.
678
+ */
679
+ type EnvPriorityRef = {
680
+ kind: 'local';
681
+ name: string;
682
+ } | {
683
+ kind: 'linked';
684
+ linkedWorkspaceId: string;
685
+ envName: string;
686
+ };
687
+ interface EnvironmentVariable {
688
+ key: string;
689
+ value: string;
690
+ encrypted: boolean;
691
+ secretKeyId?: string;
692
+ }
693
+ interface SecretKeyMeta {
694
+ id: string;
695
+ label: string;
696
+ salt: string;
697
+ createdAt: string;
698
+ }
699
+ /**
700
+ * Workspace-passphrase crypto parameters. Persisted in `WorkspaceSynced.
701
+ * secretCrypto`, written by `setupPassphrase` and read by `unlockSecretCrypto`.
702
+ * Single-version contract for now (`pbkdf2-sha256-v1`); future versions
703
+ * will be additional discriminants on `kdf`.
704
+ *
705
+ * `salt` is base64-encoded 16 random bytes; `verifier` is base64-encoded
706
+ * AES-GCM ciphertext of a fixed sentinel string under the derived key with
707
+ * a zero IV — comparing it constant-time tells a right passphrase from a
708
+ * wrong one before any real decrypt is attempted.
709
+ */
710
+ interface SecretCryptoMeta {
711
+ kdf: 'pbkdf2-sha256-v1';
712
+ salt: string;
713
+ iterations: number;
714
+ verifier: string;
715
+ }
716
+ interface LinkedWorkspace {
717
+ id: string;
718
+ kind: 'private' | 'public';
719
+ name: string;
720
+ description?: string;
721
+ source: {
722
+ provider: 'github';
723
+ repoFullName: string;
724
+ branch: string;
725
+ /**
726
+ * Which GitHub session credentials this link uses for `workspace.json`
727
+ * fetches at link / refresh time.
728
+ *
729
+ * - `'workspace'` — reuse `local.sessions.github.workspace` (the same
730
+ * PAT that pushes/pulls THIS workspace). Convenient when both repos
731
+ * are reachable from a single token.
732
+ * - `'dedicated'` — use a per-link PAT stored at
733
+ * `local.sessions.github.links[linkedWorkspaceId]`. Used when the
734
+ * source repo lives under a different account (different org, a
735
+ * bot user, a teammate's fork) that the workspace session can't
736
+ * read.
737
+ *
738
+ * Public links still pick a mode — even public-repo fetches today route
739
+ * through `GitHubClient.getContents`, which uses an auth header.
740
+ */
741
+ sessionMode: 'workspace' | 'dedicated';
742
+ };
743
+ scope: Array<'collections' | 'environments'>;
744
+ pinnedVersion: string | null;
745
+ updatePolicy: 'manual';
746
+ linkedAt: string;
747
+ requiredSecretKeyIds: string[];
748
+ marketplace?: {
749
+ listedAs: string;
750
+ tags: string[];
751
+ summary: string;
752
+ };
753
+ }
754
+ interface ReleaseHistory {
755
+ versions: ReleaseVersion[];
756
+ currentVersion: string | null;
757
+ }
758
+ interface ReleaseVersion {
759
+ version: string;
760
+ publishedAt: string;
761
+ notes: string;
762
+ workspaceSnapshot: string;
763
+ sha?: string;
764
+ tagName?: string;
765
+ deprecated: boolean;
766
+ yanked: boolean;
767
+ }
768
+ interface WorkspaceLocal {
769
+ schemaVersion: 1;
770
+ workspaceId: string;
771
+ /**
772
+ * @deprecated Plans now live on `WorkspaceSynced.executionPlans` so
773
+ * they round-trip through Git (team-shared). This field is kept for
774
+ * one schema version to support hydration migration only — code
775
+ * should NOT write here. The hydration normalizer
776
+ * `liftLegacyExecutionPlansToSynced` lifts any value found here into
777
+ * `synced.executionPlans` on first load and clears it.
778
+ */
779
+ executionPlans: Record<string, ExecutionPlan>;
780
+ history: {
781
+ requestRuns: RequestRun[];
782
+ planRuns: PlanRun[];
783
+ };
784
+ secretIndex: SecretIndex;
785
+ sessions: {
786
+ github: {
787
+ workspace: GitHubSession | null;
788
+ links: Record<string, GitHubSession>;
789
+ };
790
+ };
791
+ connectedRepo: ConnectedRepo | null;
792
+ workingBranch: WorkingBranch | null;
793
+ /**
794
+ * Blob sha of the scaffold `workspace.json` written by
795
+ * `seedInitialCommit`. Persisted so the next `createWorkingBranch` can
796
+ * recognise its own scaffold on the new branch and suppress the
797
+ * "remote already has content" first-pull prompt — that prompt only
798
+ * makes sense for genuinely pre-populated remote content, not the
799
+ * empty seed we just wrote ourselves. `null` once any other content
800
+ * has overwritten the scaffold.
801
+ */
802
+ seededWorkspaceSha: string | null;
803
+ /**
804
+ * Set by `refreshWorkspace` when it detects that the working branch is
805
+ * functionally over: the PR was merged on GitHub, OR the branch ref was
806
+ * deleted out from under us (typically by GitHub's "delete branch on
807
+ * merge" setting). `workingBranch` is cleared at the same time so the
808
+ * UI flips back to the create-branch form, and this slot drives a
809
+ * one-time banner pointing the user toward starting a new branch.
810
+ * Cleared by `dismissRetiredBranch` once the user acknowledges or
811
+ * creates a new branch.
812
+ */
813
+ retiredBranch: RetiredBranch | null;
814
+ sync: SyncSnapshot;
815
+ linkedCollections: Record<string, LinkedSnapshot>;
816
+ globalContext: Record<string, string>;
817
+ mockRuntime: MockRuntime;
818
+ ui: {
819
+ activeRequestId: string | null;
820
+ sidebarExpandedSections: string[];
821
+ themeId: ThemeId;
822
+ /**
823
+ * Workspace-bound font family. Switching workspaces applies this
824
+ * font; renaming a workspace does not affect it. Default
825
+ * `'system-mono'` matches the seed in `createEmptyWorkspace`.
826
+ */
827
+ fontId: FontFamilyId;
828
+ /**
829
+ * Whole-UI text-size scaling, expressed as a percentage of the
830
+ * browser's default root font-size. The HTML root's `font-size` is
831
+ * set to this percentage at hydrate / switch time, scaling every
832
+ * Tailwind `rem`-based utility plus the Monaco editor's option in
833
+ * `MonacoEditorBase`. Range: `FONT_SIZE_PERCENT_MIN`..`MAX`, snapped
834
+ * to `FONT_SIZE_PERCENT_STEP`. Default `FONT_SIZE_PERCENT_DEFAULT`
835
+ * (100) — matches the browser baseline so first-paint before
836
+ * hydrate doesn't flash a different size.
837
+ */
838
+ fontSizePercent: number;
839
+ };
840
+ /**
841
+ * User-tunable client-side settings. Local-only; never round-trips
842
+ * through Git so each developer can keep their own preferences.
843
+ *
844
+ * - `validateOnSend`: when true, the Editor surfaces a pre-send
845
+ * validation panel (warnings + blockers from
846
+ * `core/preSendValidation`) above the Send button. Default: true.
847
+ */
848
+ settings: WorkspaceLocalSettings;
849
+ /**
850
+ * Pre-destructive snapshot ledger. Auto-captured before every operation
851
+ * that could lose work (push, merge, linked-update apply, yank, deprecate),
852
+ * and on user demand via the History panel. Local-only; never pushed.
853
+ *
854
+ * The ledger acts as a ring buffer: when total `sizeBytes` exceeds
855
+ * `maxBytes`, the oldest snapshots are evicted until the total drops
856
+ * back under cap. Set `maxBytes: Number.POSITIVE_INFINITY` to disable
857
+ * eviction.
858
+ */
859
+ snapshots: WorkspaceSnapshotLedger;
860
+ }
861
+ interface WorkspaceLocalSettings {
862
+ validateOnSend: boolean;
863
+ /**
864
+ * Whether Monaco editors consume mouse-wheel events even when the user
865
+ * isn't intending to scroll the editor (e.g. they're hovering over the
866
+ * editor while scrolling the page). When `false`, wheel events bubble
867
+ * up to the page so long pages remain scrollable past the editor.
868
+ * When `true`, the editor scrolls first and only releases the wheel
869
+ * once it reaches its top/bottom (Monaco's default behavior).
870
+ *
871
+ * Default: `false` (page-scroll friendly).
872
+ */
873
+ monacoConsumesWheel: boolean;
874
+ }
875
+ type WorkspaceSnapshotTrigger = 'manual' | 'pre-push' | 'pre-merge' | 'pre-linked-update' | 'pre-yank' | 'pre-deprecate';
876
+ interface WorkspaceSnapshot {
877
+ /** Stable id; survives ledger updates so restore is idempotent. */
878
+ id: string;
879
+ /** ISO timestamp the snapshot was captured at. */
880
+ createdAt: string;
881
+ /** What triggered the capture — informational, used for the History badge. */
882
+ triggeredBy: WorkspaceSnapshotTrigger;
883
+ /** Optional user-provided note (manual snapshots; the others auto-fill it). */
884
+ note?: string;
885
+ /**
886
+ * Verbatim copy of `WorkspaceSynced` at the moment of capture. Stored
887
+ * inline so restore is a single state replacement — no IPFS, no SHA-only
888
+ * placeholder. Cost: ~the size of `workspace.json`. The ring buffer +
889
+ * cap keep this bounded.
890
+ */
891
+ workspaceSyncedSnapshot: WorkspaceSynced;
892
+ /**
893
+ * Approximate JSON byte length of `workspaceSyncedSnapshot` at capture
894
+ * time. Used for the storage meter + ring-buffer eviction; the exact
895
+ * persisted size after IDB compression may differ.
896
+ */
897
+ sizeBytes: number;
898
+ }
899
+ interface WorkspaceSnapshotLedger {
900
+ entries: WorkspaceSnapshot[];
901
+ /**
902
+ * Cap on total `sizeBytes` across all entries. When exceeded, oldest
903
+ * entries are dropped until the total drops back under cap. Defaults
904
+ * to 50 MB (52,428,800).
905
+ */
906
+ maxBytes: number;
907
+ }
908
+ /**
909
+ * Snapshot of a linked source workspace at a specific ref. Lives only
910
+ * in `WorkspaceLocal.linkedCollections[id]`. Refreshed on demand via
911
+ * the link card's Refresh ledger button (which pulls workspace.json
912
+ * and re-derives this snapshot).
913
+ *
914
+ * `ref` is the pinnedVersion when the link is pinned, otherwise
915
+ * `HEAD@<branch>` to make it obvious which moving target the snapshot
916
+ * is tracking.
917
+ */
918
+ interface LinkedSnapshot {
919
+ pulledAt: string;
920
+ ref: string;
921
+ collections: WorkspaceSynced['collections'];
922
+ environments: WorkspaceSynced['environments'];
923
+ /**
924
+ * The source workspace's secret-key registry, cached so the link card
925
+ * can render slot labels (not just raw ids). Optional — older
926
+ * snapshots from before this field was tracked load with `undefined`,
927
+ * and the card falls back to showing ids until the next refresh.
928
+ */
929
+ secretKeys?: Record<string, SecretKeyMeta>;
930
+ }
931
+ interface SecretIndex {
932
+ entries: Record<string, SecretEntry>;
933
+ }
934
+ interface SecretEntry {
935
+ id: string;
936
+ label: string;
937
+ createdAt: string;
938
+ origin: 'workspace' | 'linked';
939
+ linkedWorkspaceId?: string;
940
+ linkedKeyId?: string;
941
+ usedIn: SecretUsage[];
942
+ }
943
+ interface SecretUsage {
944
+ kind: 'request' | 'environment-var' | 'linked-workspace-input';
945
+ id: string;
946
+ label: string;
947
+ }
948
+ interface GitHubSession {
949
+ accountLogin: string;
950
+ tokenSecretId: string;
951
+ grantedScopes: string[];
952
+ addedAt: string;
953
+ lastVerifiedAt: string | null;
954
+ /**
955
+ * Whether this token can create pull requests, derived from a two-step
956
+ * check: (1) scope inspection (`repo` on classic PATs OR `pull_request`
957
+ * on fine-grained PATs covers PR creation), and (2) if the scope check
958
+ * is inconclusive, a real `GET /repos/:owner/:repo/pulls` probe against
959
+ * the connected repo. The PR-creation warning + Create PR button enable
960
+ * state both read this flag instead of doing string-includes checks
961
+ * against `grantedScopes` — which would false-fire for any classic PAT
962
+ * (classic PATs don't have a separate `pull_request` scope; `repo`
963
+ * already grants full PR powers, and that's what GitHub actually
964
+ * accepts at runtime).
965
+ *
966
+ * - `true` — scope check confirmed OR probe returned 200
967
+ * - `false` — probe returned 403 with missing-scope hint
968
+ * - `null` — not yet probed (no repo connected, or probe pending)
969
+ */
970
+ canCreatePullRequests: boolean | null;
971
+ }
972
+ /**
973
+ * Field-level override for a single linked request. Every field is
974
+ * optional — present ⇒ replaces the source workspace's value, absent ⇒
975
+ * inherits from the snapshot. Stored as a delta (smallest possible
976
+ * patch) so reset = drop the entry.
977
+ *
978
+ * The five identity / lifecycle fields (`id`, `folderId`, `createdAt`,
979
+ * `updatedAt`, plus `bodySchemaId` / `graphqlSchemaId` since those
980
+ * reference the source's globalAssets) are intentionally NOT
981
+ * overridable — keeping them source-pinned avoids stale references and
982
+ * keeps the consumer's tree structure under the source's control.
983
+ */
984
+ type RequestOverridePatch = Partial<Pick<Request, 'name' | 'method' | 'url' | 'headers' | 'query' | 'pathParams' | 'cookies' | 'body' | 'auth' | 'contextVars' | 'extractions' | 'assertions'>>;
985
+ interface RequestOverride {
986
+ linkedWorkspaceId: string;
987
+ itemId: string;
988
+ patch: RequestOverridePatch;
989
+ updatedAt: string;
990
+ }
991
+ /**
992
+ * Per-variable override on a linked workspace's environment. Keyed
993
+ * `${linkedWorkspaceId}:${envName}:${varKey}` in the parent record.
994
+ *
995
+ * Three modes:
996
+ * 1. Replace value: `value` (and optionally `encrypted` / `secretKeyId`) set,
997
+ * `removed` absent. Keeps the source variable but with the consumer's value.
998
+ * 2. Hide source variable: `removed: true`. The source's variable is dropped
999
+ * from the consumer's effective environment.
1000
+ * 3. Inject new variable: the `varKey` does not exist in the source's env;
1001
+ * the override row introduces it for this consumer only.
1002
+ */
1003
+ interface EnvironmentVariableOverride {
1004
+ linkedWorkspaceId: string;
1005
+ envName: string;
1006
+ varKey: string;
1007
+ value?: string;
1008
+ encrypted?: boolean;
1009
+ secretKeyId?: string;
1010
+ removed?: boolean;
1011
+ updatedAt: string;
1012
+ }
1013
+ interface ExecutionPlan {
1014
+ id: string;
1015
+ name: string;
1016
+ /**
1017
+ * Steps run sequentially in this order. `enabled: false` skips the step
1018
+ * entirely at run time — useful for keeping a step in the plan while
1019
+ * temporarily routing around it. Defaults to `true` when missing on
1020
+ * older persisted plans (pre-`enabled` plans that haven't been touched
1021
+ * since the field landed).
1022
+ */
1023
+ steps: Array<{
1024
+ requestId: string;
1025
+ linkedWorkspaceId?: string;
1026
+ enabled?: boolean;
1027
+ }>;
1028
+ /**
1029
+ * Plan-scoped overlay for the workspace's env priority order. Empty
1030
+ * means "inherit the workspace order"; non-empty replaces it for runs
1031
+ * of this plan. Mixes local + linked envs the same way the workspace
1032
+ * order does — see `EnvPriorityRef`.
1033
+ */
1034
+ envPriorityOrder: EnvPriorityRef[];
1035
+ /**
1036
+ * Plan-level variables sit between context vars and the env priority
1037
+ * list in the resolver chain — they let a plan override an env value
1038
+ * without mutating the env. Keys are case-sensitive; later entries
1039
+ * silently win on duplicate keys (consistent with env vars).
1040
+ */
1041
+ variables?: Array<{
1042
+ key: string;
1043
+ value: string;
1044
+ }>;
1045
+ /**
1046
+ * When `true`, runPlan halts the loop the first time a step's
1047
+ * assertions don't all pass. Only consulted when the run is launched
1048
+ * `withAssertions` — `Run` (without assertions) never short-circuits.
1049
+ * Defaults to `false` (continue past failed assertions).
1050
+ */
1051
+ stopOnAssertionFailure?: boolean;
1052
+ createdAt: string;
1053
+ updatedAt: string;
1054
+ }
1055
+ /**
1056
+ * Captured wire detail for a request run, written when the run completes.
1057
+ * Stored on `WorkspaceLocal.history` (capped, IDB-only). Body fields are
1058
+ * truncated past `RUN_BODY_PREVIEW_LIMIT` so a hundred history rows can't
1059
+ * blow up the IDB record.
1060
+ */
1061
+ interface RequestRun {
1062
+ id: string;
1063
+ requestId: string;
1064
+ startedAt: string;
1065
+ durationMs: number;
1066
+ status: number | null;
1067
+ /** Empty string for network errors (status === null). */
1068
+ statusText: string;
1069
+ ok: boolean;
1070
+ error?: string;
1071
+ /** Final URL after path-param substitution + query composition. */
1072
+ url: string;
1073
+ method: string;
1074
+ /** Final headers actually sent on the wire (post-auth). */
1075
+ requestHeaders: Record<string, string>;
1076
+ /**
1077
+ * Best-effort string preview of the request body. `null` for binary/form
1078
+ * bodies (where the body isn't a string) or no body. Truncated past
1079
+ * `RUN_BODY_PREVIEW_LIMIT` bytes.
1080
+ */
1081
+ requestBodyPreview: string | null;
1082
+ /** Headers received from the server. */
1083
+ responseHeaders: Record<string, string>;
1084
+ /** Truncated string preview of the response body. */
1085
+ responseBodyPreview: string;
1086
+ responseBodyKind: 'json' | 'text' | 'binary' | 'empty';
1087
+ responseTruncated: boolean;
1088
+ /**
1089
+ * Verdicts captured at run time. Snapshots the assertion definition so the
1090
+ * History detail view can render kind/op/target/expected even when the
1091
+ * source request has since been edited or deleted.
1092
+ */
1093
+ assertions: Array<{
1094
+ assertionId: string;
1095
+ kind: Assertion['kind'];
1096
+ op: Assertion['op'];
1097
+ target?: string;
1098
+ expected: string | number;
1099
+ passed: boolean;
1100
+ detail?: string;
1101
+ }>;
1102
+ }
1103
+ /** Soft cap for body previews stored on a RequestRun (each side). */
1104
+ declare const RUN_BODY_PREVIEW_LIMIT: number;
1105
+ /**
1106
+ * UI text-size scaling bounds. `fontSizePercent` on `WorkspaceLocal.ui`
1107
+ * is clamped to `[MIN, MAX]` and snapped to `STEP`. Below 80% the
1108
+ * smallest chrome (10–11px bracketed Tailwind sizes) becomes unreadable;
1109
+ * above 150% layout pressure mounts in narrow panels.
1110
+ */
1111
+ declare const FONT_SIZE_PERCENT_MIN = 80;
1112
+ declare const FONT_SIZE_PERCENT_MAX = 150;
1113
+ declare const FONT_SIZE_PERCENT_STEP = 10;
1114
+ declare const FONT_SIZE_PERCENT_DEFAULT = 100;
1115
+ interface PlanRun {
1116
+ id: string;
1117
+ planId: string;
1118
+ startedAt: string;
1119
+ durationMs: number;
1120
+ withAssertions: boolean;
1121
+ steps: Array<{
1122
+ requestRunId: string;
1123
+ passed: boolean;
1124
+ }>;
1125
+ }
1126
+ interface ConnectedRepo {
1127
+ fullName: string;
1128
+ owner: string;
1129
+ name: string;
1130
+ defaultBranch: string;
1131
+ visibility: 'public' | 'private' | 'internal';
1132
+ isPrivate: boolean;
1133
+ pushable: boolean;
1134
+ connectedAt: string;
1135
+ }
1136
+ interface WorkingBranch {
1137
+ /** Branch name on GitHub, e.g. `apicircle/payments-a3f9c2`. */
1138
+ name: string;
1139
+ /** Base branch (typically the repo's default — `main` / `master`). */
1140
+ baseBranch: string;
1141
+ /** `owner/name` on GitHub. */
1142
+ repoFullName: string;
1143
+ /** Owner login, stored redundantly so call sites don't have to re-split. */
1144
+ repoOwner: string;
1145
+ /** Repo name, same idea. */
1146
+ repoName: string;
1147
+ /** Commit SHA on this branch's HEAD at creation (= base SHA initially). */
1148
+ headSha: string;
1149
+ createdAt: string;
1150
+ lastPushedSha: string | null;
1151
+ diffSummary: {
1152
+ ahead: number;
1153
+ behind: number;
1154
+ staleAt: string;
1155
+ } | null;
1156
+ openPrUrl: string | null;
1157
+ }
1158
+ /**
1159
+ * A working branch that's been retired — either the PR was merged or the
1160
+ * branch was deleted on GitHub (or both). Persisted on `local.retiredBranch`
1161
+ * so the create-branch form can surface a "this branch is done — create a
1162
+ * new one" banner pointing back at the closed PR.
1163
+ *
1164
+ * Reasons:
1165
+ * - `pr-merged` — PR was merged. Branch may still exist on GitHub
1166
+ * (no auto-delete) or may be gone; either way it's
1167
+ * functionally retired.
1168
+ * - `branch-deleted` — Branch ref returns 404. PR (if any) was not
1169
+ * merged — most likely a deliberate delete or a
1170
+ * closed-without-merge cleanup.
1171
+ */
1172
+ interface RetiredBranch {
1173
+ /** Branch name that was retired. */
1174
+ branchName: string;
1175
+ /** Why the branch is retired. */
1176
+ reason: 'pr-merged' | 'branch-deleted';
1177
+ /** ISO timestamp when retirement was detected. */
1178
+ retiredAt: string;
1179
+ /** PR HTML URL if one was opened (kept across retirement so the banner can link it). */
1180
+ prUrl: string | null;
1181
+ /** PR number if known — useful for the banner copy ("PR #42 was merged"). */
1182
+ prNumber: number | null;
1183
+ }
1184
+ interface SyncSnapshot {
1185
+ lastPulledSnapshot: WorkspaceSynced | null;
1186
+ lastPulledSha: string | null;
1187
+ lastPulledAt: string | null;
1188
+ dirtyKeys: string[];
1189
+ }
1190
+
1191
+ /**
1192
+ * Stable string key for an `EnvPriorityRef`. Used by the resolver as the
1193
+ * lookup key into the flattened `environments` map (which mixes local and
1194
+ * linked envs under composite keys), and by React lists as the row id.
1195
+ *
1196
+ * - local: `local:<envName>`
1197
+ * - linked: `linked:<linkedWorkspaceId>:<envName>`
1198
+ *
1199
+ * The `local:` prefix is intentional even for local envs — having a uniform
1200
+ * shape avoids ambiguity (a local env named `linked:abc:dev` would collide
1201
+ * with a linked env without the prefix). Treat keys as opaque; round-trip
1202
+ * through `parseEnvPriorityKey` rather than parsing inline.
1203
+ */
1204
+ declare function envPriorityKey(ref: EnvPriorityRef): string;
1205
+ /**
1206
+ * Inverse of `envPriorityKey`. Returns null for unknown shapes — callers
1207
+ * use that to skip stale priority entries (e.g. a linked env that was
1208
+ * unlinked between pulls).
1209
+ */
1210
+ declare function parseEnvPriorityKey(key: string): EnvPriorityRef | null;
1211
+ /**
1212
+ * Equality on EnvPriorityRef. Used for "is this env in the priority
1213
+ * list?" toggles and for diffing in tests.
1214
+ */
1215
+ declare function envPriorityRefEqual(a: EnvPriorityRef, b: EnvPriorityRef): boolean;
1216
+ /**
1217
+ * Display name for an env priority entry. Used by sidebar + plan editor.
1218
+ * Linked entries get a "via {linkName}" suffix at render-time — we keep
1219
+ * just the env name here so the caller can format with workspace context.
1220
+ */
1221
+ declare function envPriorityDisplayName(ref: EnvPriorityRef): string;
1222
+
1223
+ type RequestAuthType = RequestAuth['type'];
1224
+ declare function defaultAuthFor<T extends RequestAuthType>(type: T): Extract<RequestAuth, {
1225
+ type: T;
1226
+ }>;
1227
+ /** Best-effort upgrade of an unknown value into a valid RequestAuth. */
1228
+ declare function normalizeAuth(input: unknown): RequestAuth;
1229
+ declare const REQUEST_AUTH_TYPES: ReadonlyArray<RequestAuthType>;
1230
+
1231
+ /**
1232
+ * Every MCP tool the server exposes. Namespaced by capability area so AI
1233
+ * clients can group them in their UI. Keep in sync with the registry in
1234
+ * `packages/mcp-server/src/tools/registry.ts`.
1235
+ */
1236
+ type McpToolName = 'import.curl' | 'import.openapi' | 'import.postman' | 'import.insomnia' | 'import.har' | 'generate.code' | 'workspace.read' | 'workspace.write' | 'request.create' | 'request.read' | 'request.update' | 'request.delete' | 'folder.create' | 'folder.read' | 'folder.update' | 'folder.delete' | 'environment.create' | 'environment.read' | 'environment.update' | 'environment.delete' | 'environment.set_active' | 'environment.set_priority' | 'environment.export' | 'environment.import' | 'plan.create' | 'plan.run' | 'plan.read' | 'plan.update' | 'plan.delete' | 'plan.add_step' | 'plan.remove_step' | 'plan.reorder_steps' | 'plan.set_variables' | 'assertion.create' | 'assertion.read' | 'assertion.update' | 'assertion.delete' | 'history.list_runs' | 'history.get_run' | 'history.delete_run' | 'history.purge_by_age' | 'codebase.extract_collection' | 'prompt.create_environment' | 'prompt.create_assertion' | 'prompt.create_plan' | 'prompt.create_request' | 'prompt.update_request' | 'prompt.create_folder_tree' | 'prompt.add_plan_steps' | 'prompt.set_plan_variables' | 'prompt.create_mock_server' | 'prompt.add_mock_endpoint' | 'prompt.set_endpoint_validation_rules' | 'prompt.set_endpoint_response_rules' | 'prompt.set_endpoint_multipliers' | 'mock.create_from_openapi' | 'mock.create_from_postman' | 'mock.create_from_insomnia' | 'mock.create_manual' | 'mock.list' | 'mock.list_endpoints' | 'mock.start' | 'mock.stop' | 'mock.delete' | 'mock.add_endpoint' | 'mock.update_endpoint' | 'mock.delete_endpoint' | 'mock.set_validation_rules' | 'mock.set_response_rules' | 'mock.set_multipliers' | 'mock.import_postman_mock_collection';
1237
+ interface McpError {
1238
+ code: 'invalid_input' | 'not_found' | 'conflict' | 'unsupported' | 'internal';
1239
+ message: string;
1240
+ details?: unknown;
1241
+ }
1242
+ /** Helper: full enumeration of tool names — useful for the docs / config UIs. */
1243
+ declare const MCP_TOOL_NAMES: ReadonlyArray<McpToolName>;
1244
+
1245
+ export { type Assertion, type AttachmentRef, type AwsSigV4Auth, type BodyType, type ConnectedRepo, type ContextExtraction, DEFAULT_WORKSPACE_NAME, type DigestAuth, type EnvPriorityRef, type Environment, type EnvironmentVariable, type EnvironmentVariableOverride, type ExecutionPlan, FONT_SIZE_PERCENT_DEFAULT, FONT_SIZE_PERCENT_MAX, FONT_SIZE_PERCENT_MIN, FONT_SIZE_PERCENT_STEP, type Folder, type FolderNode, type FontFamilyId, type FormDataRow, type GitHubSession, type GlobalGraphQL, type GlobalSchema, type HawkAuth, type HttpMethod, type JwtBearerAuth, type LinkedSnapshot, type LinkedWorkspace, MCP_TOOL_NAMES, type McpError, type McpToolName, type MockConditionClause, type MockConditionOp, type MockConditionScope, type MockEndpoint, type MockMultiplierSource, type MockMultiplierSourceKind, type MockParamDef, type MockRequestSchema, type MockResponseBody, type MockResponseBodyType, type MockResponseConfig, type MockResponseMultiplier, type MockResponseRule, type MockRuntime, type MockRuntimeEntry, type MockServer, type MockServerSource, type MockValidationKind, type MockValidationRule, type NtlmAuth, type OAuth2AuthCodeAuth, type OAuth2ClientCredentialsAuth, type OAuth2DeviceAuth, type OAuth2ImplicitAuth, type OAuth2PasswordAuth, type OAuth2PkceAuth, type OAuth2TokenState, type PanelId, type PlanRun, REQUEST_AUTH_TYPES, RUN_BODY_PREVIEW_LIMIT, type ReleaseHistory, type ReleaseVersion, type Request, type RequestAuth, type RequestAuthType, type RequestBody, type RequestOverride, type RequestOverridePatch, type RequestRun, type RetiredBranch, type SecretCryptoMeta, type SecretEntry, type SecretIndex, type SecretKeyMeta, type SecretUsage, type SyncSnapshot, type ThemeId, type ValidationResult, type WorkingBranch, type WorkspaceLocal, type WorkspaceSnapshot, type WorkspaceSnapshotLedger, type WorkspaceSnapshotTrigger, type WorkspaceSynced, coerceMockResponseBodyTypeForStatus, defaultAuthFor, envPriorityDisplayName, envPriorityKey, envPriorityRefEqual, formatBytes, generateId, getAllowedMockResponseBodyTypes, makeDefaultMockResponse, makeDefaultMockResponseBody, makeDefaultRequestSchema, normalizeAuth, parseEnvPriorityKey, safeExternalHref, utf8ByteLength, validateAwsRegion, validateEnvVarName, validateHttpHeaderName, validateJsonPath, validateJsonString, validateMockPath, validatePRTitle, validatePlanName, validatePositiveDuration, validateRegex, validateUrl };