@everfur/cli 0.1.0 → 0.2.1

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/dist/index.d.ts CHANGED
@@ -32,11 +32,12 @@ interface CredentialSpec {
32
32
  /**
33
33
  * Additional accepted prefixes, beyond `prefix`.
34
34
  *
35
- * A publishable key is `pk_live_` on a production deployment and `pk_test_` on a sandbox one, and the
36
- * CLI cannot tell which host it is pointed at before it makes a call. Hardcoding one of them would make
37
- * the CLI reject exactly the sandbox credentials the platform now issues, which is the opposite of what
38
- * a test-mode tool should do. The SERVER is the authority: it accepts one prefix per deployment and
39
- * refuses the other on the fast-fail path, so accepting both here narrows nothing.
35
+ * The prefix a publishable key must carry is a property of the DEPLOYMENT, not of the tenant: production and
36
+ * staging both take `pk_live_` (a staging sandbox tenant is issued a `pk_live_` key and refuses `pk_test_`), and
37
+ * `pk_test_` is the prefix a deployment configured as a sandbox deployment would take instead; no deployed
38
+ * environment is configured that way today, so no deployed environment accepts it. The CLI cannot tell which
39
+ * deployment it is pointed at before it makes a call, so it accepts both shapes. The SERVER is the authority: each
40
+ * deployment refuses the prefix it does not take on the fast-fail path, so accepting both here narrows nothing.
40
41
  */
41
42
  readonly alsoAccepts?: readonly string[];
42
43
  /** Exact expected length when the prefix is known, or null. */
@@ -63,6 +64,12 @@ interface ResolvedCredential {
63
64
  readonly credential: Credential | null;
64
65
  /** Why the value was rejected. Names the variable and the expected shape, never the value. */
65
66
  readonly reason: string | null;
67
+ /**
68
+ * Which accepted prefix a `present` value starts with (`pk_live_` or `pk_test_`), or null for an
69
+ * opaque credential or one that is not present. A prefix is a public fact about the key, so a
70
+ * caller that needs it reads it here instead of reading the raw value out of the environment.
71
+ */
72
+ readonly matchedPrefix: string | null;
66
73
  }
67
74
  /**
68
75
  * Wrap a value that did NOT come from the environment, for example a session token the CLI just
@@ -87,6 +94,28 @@ declare function withPrefix(credential: Credential, prefix: string): Credential;
87
94
  */
88
95
  declare function resolveCredential(env: Readonly<Record<string, string | undefined>>, spec: CredentialSpec): ResolvedCredential;
89
96
 
97
+ /**
98
+ * WHERE A FETCHED CONTRACT IS KEPT BETWEEN RUNS.
99
+ *
100
+ * The contract is served only to an authenticated partner (`GET /partners/contract`), so every
101
+ * command that needs it would otherwise make a network call per run. The route answers an ETag that
102
+ * IS the contract digest, so a cached copy costs one conditional request and an empty 304 while it is
103
+ * current, and is replaced the moment the platform publishes a new one.
104
+ *
105
+ * A PORT, NOT A MODULE-LEVEL FILESYSTEM, for the same reason `FileSystemPort` is one: commands stay pure
106
+ * functions over injected dependencies and a test never touches a real directory. Only the bin shims and
107
+ * the MCP server construct the node adapter.
108
+ *
109
+ * Writes never throw. A read-only home directory or a full disk degrades to fetching every time, which is
110
+ * slower and still correct; failing a plan because a cache could not be written would not be.
111
+ */
112
+ interface ContractCachePort {
113
+ /** The entry's contents, or null when absent or unreadable. */
114
+ read(name: string): string | null;
115
+ /** Store an entry. Best effort: a failure is swallowed and the next run fetches again. */
116
+ write(name: string, contents: string): void;
117
+ }
118
+
90
119
  /**
91
120
  * The filesystem seam. Detection and contract loading are pure functions over this interface,
92
121
  * which is the whole reason they can be tested against fixtures without a temp directory
@@ -105,6 +134,11 @@ interface FileSystemPort {
105
134
  exists(path: string): boolean;
106
135
  /** True when the path exists and is a directory. */
107
136
  isDirectory(path: string): boolean;
137
+ /**
138
+ * The names of a directory's direct entries, sorted, or null when the path is not a readable directory. Used only
139
+ * by the offline project checks of `verify`, which have to find a secret wherever it was written.
140
+ */
141
+ listDirectory(path: string): readonly string[] | null;
108
142
  }
109
143
 
110
144
  /**
@@ -116,7 +150,8 @@ interface FileSystemPort {
116
150
  * a `CommandOutcome`: it holds the credential headers, and an outcome is a thing the formatter
117
151
  * prints. Failures carry the method, the path, and the request id, never the request object.
118
152
  */
119
- type HttpMethod = 'GET' | 'POST' | 'DELETE';
153
+ /** OPTIONS is sent only by the web checks' CORS preflight, directly through a transport, never retried. */
154
+ type HttpMethod = 'GET' | 'POST' | 'DELETE' | 'OPTIONS';
120
155
  /** Methods that may be retried. A retry of anything else could mint a second credential. */
121
156
  declare const IDEMPOTENT_METHODS: readonly HttpMethod[];
122
157
  interface HttpRequest {
@@ -304,6 +339,11 @@ interface CommandContext {
304
339
  readonly transport: TransportPort;
305
340
  /** Waiting, injected so a poll loop in a test costs nothing. */
306
341
  readonly clock: ClockPort;
342
+ /**
343
+ * Where a contract fetched from `GET /partners/contract` is kept between runs. Absent means nothing is
344
+ * cached and every run that needs the contract fetches it. `src/bin` supplies the one real adapter.
345
+ */
346
+ readonly contractCache?: ContractCachePort;
307
347
  }
308
348
  /**
309
349
  * A positional argument. Declared as data beside the flags so `--help` documents it and the
@@ -334,7 +374,7 @@ interface Command {
334
374
  * The credentials this command can actually use, declared as data for the same reason the
335
375
  * flags are: `--help` renders what the command declares, so a credential a command reads is a
336
376
  * credential its own help names. Absent means the command touches none, which is the truth
337
- * for `plan`, `contract validate`, and `explain-error`.
377
+ * for `contract validate`.
338
378
  *
339
379
  * This exists because per-command help used to render only the flags. A partner told to run
340
380
  * one command had no way to learn from the tool that they must export EVERFUR_PARTNER_KEY;
@@ -438,6 +478,10 @@ declare const fetchTransport: TransportPort;
438
478
  * `API_PREFIX` is appended by this module rather than being part of what the operator types,
439
479
  * because it is a deployment fact of the API and not a choice. A base URL that already ends in
440
480
  * the prefix is accepted as-is, so both spellings work and neither doubles up.
481
+ *
482
+ * HTTPS, EXCEPT ON THIS MACHINE. Every networked command sends a credential to this base (the partner
483
+ * key, the secret key, the docs key), so plain `http:` is accepted only for `localhost`, `127.0.0.1`
484
+ * and `[::1]`, where a local API runs. Anything else must be https.
441
485
  */
442
486
  declare const BASE_URL_ENV_VAR = "EVERFUR_API_BASE_URL";
443
487
  /** `app/main.py` mounts every router used here at `settings.api_prefix`. */
@@ -571,6 +615,26 @@ declare const SCHEMAS: Readonly<{
571
615
  type?: never;
572
616
  })[];
573
617
  };
618
+ embedding: {
619
+ description: string;
620
+ oneOf: ({
621
+ type: string;
622
+ $ref?: never;
623
+ } | {
624
+ $ref: string;
625
+ type?: never;
626
+ })[];
627
+ };
628
+ environment: {
629
+ description: string;
630
+ oneOf: ({
631
+ type: string;
632
+ enum?: never;
633
+ } | {
634
+ enum: string[];
635
+ type?: never;
636
+ })[];
637
+ };
574
638
  };
575
639
  };
576
640
  missingFacts: {
@@ -605,6 +669,16 @@ declare const SCHEMAS: Readonly<{
605
669
  $ref: string;
606
670
  };
607
671
  };
672
+ platformBlock: {
673
+ description: string;
674
+ oneOf: ({
675
+ type: string;
676
+ $ref?: never;
677
+ } | {
678
+ $ref: string;
679
+ type?: never;
680
+ })[];
681
+ };
608
682
  decisionSpace: {
609
683
  description: string;
610
684
  oneOf: ({
@@ -746,6 +820,159 @@ declare const SCHEMAS: Readonly<{
746
820
  $ref: string;
747
821
  };
748
822
  };
823
+ webEmbedding: {
824
+ description: string;
825
+ oneOf: ({
826
+ type: string;
827
+ $ref?: never;
828
+ } | {
829
+ $ref: string;
830
+ type?: never;
831
+ })[];
832
+ };
833
+ webFramework: {
834
+ description: string;
835
+ oneOf: ({
836
+ type: string;
837
+ enum?: never;
838
+ } | {
839
+ enum: string[];
840
+ type?: never;
841
+ })[];
842
+ };
843
+ theme: {
844
+ description: string;
845
+ oneOf: ({
846
+ type: string;
847
+ $ref?: never;
848
+ } | {
849
+ $ref: string;
850
+ type?: never;
851
+ })[];
852
+ };
853
+ };
854
+ };
855
+ ThemeToken: {
856
+ enum: string[];
857
+ };
858
+ ThemeDetection: {
859
+ type: string;
860
+ required: string[];
861
+ properties: {
862
+ source: {
863
+ enum: string[];
864
+ };
865
+ confidence: {
866
+ enum: string[];
867
+ };
868
+ theme: {
869
+ description: string;
870
+ type: string;
871
+ properties: {
872
+ primaryColor: {
873
+ type: string;
874
+ pattern: string;
875
+ };
876
+ accentColor: {
877
+ type: string;
878
+ pattern: string;
879
+ };
880
+ primaryColorDark: {
881
+ type: string;
882
+ pattern: string;
883
+ };
884
+ accentColorDark: {
885
+ type: string;
886
+ pattern: string;
887
+ };
888
+ brightness: {
889
+ enum: string[];
890
+ };
891
+ surfaceStyle: {
892
+ enum: string[];
893
+ };
894
+ radiusScale: {
895
+ enum: string[];
896
+ };
897
+ fontFamily: {
898
+ type: string;
899
+ minLength: number;
900
+ };
901
+ fontRegular: {
902
+ type: string;
903
+ minLength: number;
904
+ description: string;
905
+ };
906
+ fontMedium: {
907
+ type: string;
908
+ minLength: number;
909
+ description: string;
910
+ };
911
+ fontBold: {
912
+ type: string;
913
+ minLength: number;
914
+ description: string;
915
+ };
916
+ };
917
+ additionalProperties: boolean;
918
+ };
919
+ evidence: {
920
+ type: string;
921
+ items: {
922
+ type: string;
923
+ required: string[];
924
+ properties: {
925
+ file: {
926
+ type: string;
927
+ minLength: number;
928
+ };
929
+ line: {
930
+ type: string;
931
+ minimum: number;
932
+ };
933
+ key: {
934
+ type: string;
935
+ minLength: number;
936
+ };
937
+ value: {
938
+ type: string;
939
+ };
940
+ token: {
941
+ oneOf: ({
942
+ type: string;
943
+ $ref?: never;
944
+ } | {
945
+ $ref: string;
946
+ type?: never;
947
+ })[];
948
+ };
949
+ reason: {
950
+ type: string;
951
+ minLength: number;
952
+ };
953
+ };
954
+ };
955
+ };
956
+ scanned: {
957
+ type: string;
958
+ items: {
959
+ type: string;
960
+ minLength: number;
961
+ };
962
+ };
963
+ missing: {
964
+ type: string;
965
+ items: {
966
+ type: string;
967
+ minLength: number;
968
+ };
969
+ };
970
+ inferred: {
971
+ type: string;
972
+ items: {
973
+ $ref: string;
974
+ };
975
+ };
749
976
  };
750
977
  };
751
978
  Dependency: {
@@ -802,6 +1029,28 @@ declare const SCHEMAS: Readonly<{
802
1029
  minLength: number;
803
1030
  };
804
1031
  };
1032
+ blockedBy: {
1033
+ description: string;
1034
+ enum: string[];
1035
+ };
1036
+ };
1037
+ };
1038
+ PlatformBlock: {
1039
+ type: string;
1040
+ required: string[];
1041
+ properties: {
1042
+ reason: {
1043
+ type: string;
1044
+ minLength: number;
1045
+ };
1046
+ unblockOwner: {
1047
+ type: string;
1048
+ minLength: number;
1049
+ };
1050
+ requestPath: {
1051
+ type: string;
1052
+ pattern: string;
1053
+ };
805
1054
  };
806
1055
  };
807
1056
  PlanPrinciple: {
@@ -1117,6 +1366,18 @@ declare const SCHEMAS: Readonly<{
1117
1366
  $ref: string;
1118
1367
  };
1119
1368
  };
1369
+ switches: {
1370
+ description: string;
1371
+ oneOf: ({
1372
+ type: string;
1373
+ items?: never;
1374
+ } | {
1375
+ type: string;
1376
+ items: {
1377
+ $ref: string;
1378
+ };
1379
+ })[];
1380
+ };
1120
1381
  };
1121
1382
  $defs: {
1122
1383
  GeneratedBy: {
@@ -1187,6 +1448,54 @@ declare const SCHEMAS: Readonly<{
1187
1448
  };
1188
1449
  };
1189
1450
  };
1451
+ SwitchDiagnosis: {
1452
+ type: string;
1453
+ required: string[];
1454
+ properties: {
1455
+ name: {
1456
+ description: string;
1457
+ type: string;
1458
+ minLength: number;
1459
+ };
1460
+ enabled: {
1461
+ description: string;
1462
+ oneOf: {
1463
+ type: string;
1464
+ }[];
1465
+ };
1466
+ kind: {
1467
+ description: string;
1468
+ enum: string[];
1469
+ };
1470
+ gates: {
1471
+ description: string;
1472
+ type: string;
1473
+ items: {
1474
+ type: string;
1475
+ minLength: number;
1476
+ };
1477
+ };
1478
+ tenantTerm: {
1479
+ description: string;
1480
+ oneOf: ({
1481
+ type: string;
1482
+ minLength?: never;
1483
+ } | {
1484
+ type: string;
1485
+ minLength: number;
1486
+ })[];
1487
+ };
1488
+ reason: {
1489
+ description: string;
1490
+ type: string;
1491
+ };
1492
+ nextAction: {
1493
+ description: string;
1494
+ type: string;
1495
+ minLength: number;
1496
+ };
1497
+ };
1498
+ };
1190
1499
  };
1191
1500
  };
1192
1501
  sandboxCreate: {
@@ -1370,6 +1679,11 @@ declare const SCHEMAS: Readonly<{
1370
1679
  type: string;
1371
1680
  minimum: number;
1372
1681
  };
1682
+ gated: {
1683
+ description: string;
1684
+ type: string;
1685
+ minimum: number;
1686
+ };
1373
1687
  };
1374
1688
  };
1375
1689
  steps: {
@@ -1388,6 +1702,7 @@ declare const SCHEMAS: Readonly<{
1388
1702
  minLength: number;
1389
1703
  };
1390
1704
  result: {
1705
+ description: string;
1391
1706
  enum: string[];
1392
1707
  };
1393
1708
  reason: {
@@ -1616,7 +1931,14 @@ interface ErrorRegistryEntry {
1616
1931
  readonly note: string;
1617
1932
  }
1618
1933
  interface ErrorRegistry {
1934
+ /**
1935
+ * The row for a code. A code can ride both envelopes as two rows (`unavailable`: the 503 body and the SSE frame a
1936
+ * partner chat turn ends with); this answers the HTTP row then, and `lookupAll` answers both.
1937
+ */
1619
1938
  lookup(code: string): ErrorRegistryEntry | null;
1939
+ /** Every row for a code, one per envelope it is registered on. Empty for an unknown code. */
1940
+ lookupAll(code: string): readonly ErrorRegistryEntry[];
1941
+ /** The distinct codes, sorted. */
1620
1942
  readonly codes: readonly string[];
1621
1943
  }
1622
1944
  /**
@@ -1660,6 +1982,8 @@ interface ApiSuccess<T> {
1660
1982
  /** The server's id, or null when the response carried none. */
1661
1983
  readonly requestId: string | null;
1662
1984
  readonly attempts: number;
1985
+ /** The response headers, names lowercased by the adapter. A check on what a browser could read needs them. */
1986
+ readonly headers: Readonly<Record<string, string>>;
1663
1987
  }
1664
1988
  type ApiFailure = {
1665
1989
  readonly ok: false;
@@ -1728,6 +2052,11 @@ interface ApiClientOptions {
1728
2052
  readonly maxAttempts?: number;
1729
2053
  /** Delay between attempts, in milliseconds. */
1730
2054
  readonly retryDelayMs?: number;
2055
+ /**
2056
+ * The `x-everfur-sdk-platform` every /widget/v1 call sends (`sdkClientHeaders`). Default `cli`; a caller that read
2057
+ * the project passes `sdkPlatformForProjectType` of it. A header the request itself names always wins.
2058
+ */
2059
+ readonly sdkPlatform?: string;
1731
2060
  }
1732
2061
  interface RequestSpec {
1733
2062
  readonly method: HttpMethod;
@@ -1739,11 +2068,18 @@ interface RequestSpec {
1739
2068
  readonly body?: JsonValue;
1740
2069
  /** Statuses that are a successful outcome for this call, in addition to 2xx. */
1741
2070
  readonly acceptStatuses?: readonly number[];
2071
+ /**
2072
+ * `text` hands back the accepted body as the raw string instead of parsing it, for a route that
2073
+ * answers `text/event-stream`. A failure body is still read for its wire code either way.
2074
+ */
2075
+ readonly responseType?: 'json' | 'text';
1742
2076
  }
1743
2077
  interface ApiClient {
1744
2078
  request(spec: RequestSpec): Promise<ApiResult<JsonValue>>;
1745
2079
  /** The resolved base URL, for reporting. Never a credential. */
1746
2080
  readonly baseUrl: string;
2081
+ /** The absolute URL `request` would call for `path`, for a probe that goes straight to a transport. */
2082
+ urlFor(path: string): string;
1747
2083
  }
1748
2084
  declare function clampTimeout(value: number): number;
1749
2085
  /** True when the value is a shape `RequestIDMiddleware` will reuse rather than replace. */
@@ -1783,6 +2119,38 @@ declare function failureDetails(failure: ApiFailure): readonly string[];
1783
2119
 
1784
2120
  declare function failureOutcome(failure: ApiFailure, extraDetails?: readonly string[]): CommandOutcome;
1785
2121
 
2122
+ /**
2123
+ * WHY A CAPABILITY IS UNAVAILABLE, and what to do about it.
2124
+ *
2125
+ * The nine codes are the platform's own `DiagnosticsReasonCode`, which exists because an empty
2126
+ * `capabilities:{}` verdict has five byte-identical causes and the render payload deliberately
2127
+ * carries no "why". The server sends the code and its own safe copy; this table adds the part
2128
+ * the server cannot know, which is what the PARTNER should do next.
2129
+ *
2130
+ * The distinction that makes this table worth having: three of these are somebody else's job
2131
+ * (`scope_not_provisioned`, `not_in_grant`, `runtime_gate_off`), one is a commercial
2132
+ * conversation (`outside_ceiling`), one is deliberate configuration (`capability_denied`), one
2133
+ * is transient (`resolver_unavailable`), and one is a bug to report (`gate_unknown`). A single
2134
+ * "the feature is unavailable" message collapses all seven into one wrong next step.
2135
+ *
2136
+ * An UNKNOWN code is not an error. The enum grows when the platform grows a new decision point,
2137
+ * and a CLI that rejected the answer would be useless exactly when something new broke. It
2138
+ * renders the code verbatim with an upgrade instruction.
2139
+ */
2140
+ declare const REASON_CODES: readonly ["granted", "scope_not_provisioned", "capability_denied", "outside_ceiling", "not_in_grant", "runtime_gate_off", "gate_unknown", "platform_invariant", "resolver_unavailable"];
2141
+ type ReasonCode = (typeof REASON_CODES)[number];
2142
+ declare function isKnownReasonCode(value: string): value is ReasonCode;
2143
+ /** The next action for a code, including one this CLI has never heard of. */
2144
+ declare function nextActionFor$1(code: string): string;
2145
+ /**
2146
+ * What a partner switch is. A ROUTE GATE opens or closes partner routes (off, they answer 404). A POSTURE changes how
2147
+ * routes behave and is often off by design: `partner.min_version_gate_enabled` is inert off, and
2148
+ * `partner.sessions_required` or `partner.transcript_consent_required` armed would refuse callers. UNKNOWN is an entry
2149
+ * that neither says which it is nor names what it gates.
2150
+ */
2151
+ declare const SWITCH_KINDS: readonly ["route_gate", "posture", "unknown"];
2152
+ type SwitchKind = (typeof SWITCH_KINDS)[number];
2153
+
1786
2154
  /**
1787
2155
  * Narrow `GET /widget/v1/diagnostics` into the CLI's own shape.
1788
2156
  *
@@ -1814,6 +2182,35 @@ interface ScopeLevelDiagnosis {
1814
2182
  readonly kind: string;
1815
2183
  readonly present: boolean;
1816
2184
  }
2185
+ /**
2186
+ * One partner switch that governs a partner-facing route, as the diagnostics block reports it.
2187
+ *
2188
+ * The capability list alone cannot say why a granted surface's routes 404: five switches have a capability gate
2189
+ * (records, records clinic, Televet, records push, server pets), and the rest (notifications, webhooks, records depth,
2190
+ * the Televet sub-features, follow-ups, branding and more) had none, so a partner whose switch was off was told
2191
+ * everything was granted. The CLI renders EVERY switch the backend sends, keyed by its name, in the order sent:
2192
+ * there is no list of known switches here to fall behind the platform.
2193
+ */
2194
+ interface SwitchDiagnosis {
2195
+ /** The switch's name as the backend sends it, e.g. `partner.notifications_enabled`. */
2196
+ readonly name: string;
2197
+ /** On, off, or null when the entry carried no state this CLI can read. */
2198
+ readonly enabled: boolean | null;
2199
+ /**
2200
+ * A route gate (off, its routes 404), a posture (changes behaviour, often off by design), or unknown. The backend's
2201
+ * `kind` when it sends one; otherwise a switch that names what it gates is a route gate, and one that names nothing
2202
+ * is unknown. Only a route gate is ever told to ask Everfur to switch it on.
2203
+ */
2204
+ readonly kind: SwitchKind;
2205
+ /** What it governs, as the backend names it: routes, capabilities or a description. */
2206
+ readonly gates: readonly string[];
2207
+ /** The tenant-level feature term the switch pairs with, when it has one. */
2208
+ readonly tenantTerm: string | null;
2209
+ /** The server's own copy for the switch, or ''. */
2210
+ readonly reason: string;
2211
+ /** CLI-authored. What the partner should do; see `reasons.ts`. */
2212
+ readonly nextAction: string;
2213
+ }
1817
2214
  interface Diagnosis {
1818
2215
  readonly requestId: string | null;
1819
2216
  readonly evaluatedAt: string;
@@ -1836,6 +2233,11 @@ interface Diagnosis {
1836
2233
  readonly levels: readonly ScopeLevelDiagnosis[];
1837
2234
  };
1838
2235
  readonly capabilities: readonly CapabilityDiagnosis[];
2236
+ /**
2237
+ * The partner switches, or null when the response carried no switches block (a deployment older than it). An
2238
+ * empty array is a deployment that reports switches and has none to report.
2239
+ */
2240
+ readonly switches: readonly SwitchDiagnosis[] | null;
1839
2241
  }
1840
2242
  type DiagnosisParse = {
1841
2243
  readonly ok: true;
@@ -1868,30 +2270,6 @@ interface DiagnosisRenderInput {
1868
2270
  }
1869
2271
  declare function renderDiagnosis(input: DiagnosisRenderInput): string;
1870
2272
 
1871
- /**
1872
- * WHY A CAPABILITY IS UNAVAILABLE, and what to do about it.
1873
- *
1874
- * The nine codes are the platform's own `DiagnosticsReasonCode`, which exists because an empty
1875
- * `capabilities:{}` verdict has five byte-identical causes and the render payload deliberately
1876
- * carries no "why". The server sends the code and its own safe copy; this table adds the part
1877
- * the server cannot know, which is what the PARTNER should do next.
1878
- *
1879
- * The distinction that makes this table worth having: three of these are somebody else's job
1880
- * (`scope_not_provisioned`, `not_in_grant`, `runtime_gate_off`), one is a commercial
1881
- * conversation (`outside_ceiling`), one is deliberate configuration (`capability_denied`), one
1882
- * is transient (`resolver_unavailable`), and one is a bug to report (`gate_unknown`). A single
1883
- * "the feature is unavailable" message collapses all seven into one wrong next step.
1884
- *
1885
- * An UNKNOWN code is not an error. The enum grows when the platform grows a new decision point,
1886
- * and a CLI that rejected the answer would be useless exactly when something new broke. It
1887
- * renders the code verbatim with an upgrade instruction.
1888
- */
1889
- declare const REASON_CODES: readonly ["granted", "scope_not_provisioned", "capability_denied", "outside_ceiling", "not_in_grant", "runtime_gate_off", "gate_unknown", "platform_invariant", "resolver_unavailable"];
1890
- type ReasonCode = (typeof REASON_CODES)[number];
1891
- declare function isKnownReasonCode(value: string): value is ReasonCode;
1892
- /** The next action for a code, including one this CLI has never heard of. */
1893
- declare function nextActionFor$1(code: string): string;
1894
-
1895
2273
  /**
1896
2274
  * THE SECRET FENCE. This module turns an admin mint response into the only shape this CLI is
1897
2275
  * allowed to hand back, and it is written so that a raw secret CANNOT travel through it.
@@ -1974,13 +2352,207 @@ declare function buildSandboxDocument(input: SandboxDocumentInput): JsonObject;
1974
2352
  /** The human projection. */
1975
2353
  declare function renderSandboxCreate(creation: SandboxCreation, revocation: SandboxRevocation, requestId: string | null): string;
1976
2354
 
2355
+ /**
2356
+ * THEME DETECTION: what the partner's project already says about its own look, read statically, mapped to the
2357
+ * SDK's `theme` prop only where the mapping is unambiguous, and graded so the planner knows whether to emit it.
2358
+ *
2359
+ * Owner decision, 18 September 2026: the MCP-built app must match the partner's existing theme automatically
2360
+ * when it can, and refuse to guess when it cannot. Four sources are read, in confidence order:
2361
+ *
2362
+ * (a) a Tailwind v4 `@theme` / `@theme inline` block in the project's CSS (what the everfur-frontend console
2363
+ * itself uses), with `var()` references resolved one level through `:root` and the dark block;
2364
+ * (b) a Tailwind v3 `tailwind.config.{js,ts,cjs,mjs}`: `theme.extend.colors`, `fontFamily`, `borderRadius`,
2365
+ * read by a tolerant static parser (theme-js.ts), `hsl(var(--x))` resolved through the project CSS;
2366
+ * (c) root CSS custom properties: `--primary`, `--brand`, `--color-primary`, `--background`, `--foreground`,
2367
+ * `--radius`, `--font-sans` and their common spellings, in a stylesheet or a static page's `<style>`;
2368
+ * (d) React Native: a react-navigation `Theme` object (`colors: { primary, background, card, text, border }`)
2369
+ * or a theme or colours file with obvious keys, identifiers resolved one level to `const` strings; and, from
2370
+ * the same theme files, the exact `fontFamily: '<Name>'` literals, mapped to the SDK's per-weight `fonts`
2371
+ * map by the weight word in each name (`Azeret-Regular`, `Inter_500Medium`, `Roboto Bold`), or the one
2372
+ * family the files name when none carries a weight. Never a family the files do not name.
2373
+ *
2374
+ * STATIC PARSING ONLY. Nothing here requires, evaluates or executes a partner's file; a value that is computed
2375
+ * (an identifier, a call, a template literal, a `calc()`) is dropped with that reason, and a config that runs
2376
+ * code at load time (a Node built-in module, `eval`) is refused whole. `tests/unit/theme-detect.test.ts` pins
2377
+ * that at the source.
2378
+ *
2379
+ * THE MAPPING is to the SDK's `EverfurThemeInput` (everfur-sdk-rn src/client/theme/input.ts), and only where
2380
+ * it is unambiguous: primary or brand to `primaryColor`; accent or secondary to `accentColor`, unless it reads
2381
+ * as a surface tint (under 1.5:1 against the background, the shadcn `--accent` hover wash); the background to
2382
+ * `brightness` (pinned when the project declares no dark variant) and, for a light ground close to one of the
2383
+ * SDK's three compiled surfaces, `surfaceStyle`; a base radius to `radiusScale` by size; the first family of a
2384
+ * font stack to `fontFamily` when it is a plain family name (the web, where the page loads the weights itself); a
2385
+ * React Native theme file's `fontFamily` literals to the registered faces `fontRegular`, `fontMedium` and
2386
+ * `fontBold` (written as the SDK's `fonts` map, names only: the app registers the faces, the SDK bundles no font
2387
+ * and synthesizes no weight on a named face). The foreground is read as evidence only: the SDK
2388
+ * computes its text colours from the ground (WCAG), and the prop has no text token outside slots. Every colour
2389
+ * is written as `#RRGGBB`, the form the SDK parses; `hsl()` and `oklch()` are converted (theme-color.ts).
2390
+ *
2391
+ * THE CONFIDENCE GATE. `high` when a primary and a background both come from one source with literal values;
2392
+ * `medium` when only some tokens were read; `low` otherwise. A computed colour the mapping wanted drops the
2393
+ * grade one level; a config that runs code at load time, or builds a theme value with a template literal, is
2394
+ * `low` whatever it declares. At `low` the theme is empty: nothing read from a source this tool could not fully
2395
+ * trust is written into a partner's app.
2396
+ */
2397
+ type ThemeSource = 'tailwind-v4' | 'tailwind-v3' | 'css-variables' | 'react-navigation' | 'rn-colors' | 'none';
2398
+ type ThemeConfidence = 'high' | 'medium' | 'low';
2399
+ /**
2400
+ * The subset of the SDK's `EverfurThemeInput` a plan writes. Every value is a string, so the emitted literal
2401
+ * type-checks against the prop on every SDK release that publishes it. Mirrors input.ts; extend both together.
2402
+ */
2403
+ interface DetectedTheme {
2404
+ /** `#RRGGBB`. */
2405
+ readonly primaryColor?: string;
2406
+ readonly accentColor?: string;
2407
+ /** The fills on the dark palette, when the project declares a dark variant (or is dark only). */
2408
+ readonly primaryColorDark?: string;
2409
+ readonly accentColorDark?: string;
2410
+ /** Pinned only when the project declares no dark variant: the surface then matches the page whatever the device says. */
2411
+ readonly brightness?: 'light' | 'dark';
2412
+ /** The compiled SDK surface nearest a light background, when one is within tolerance. */
2413
+ readonly surfaceStyle?: 'white' | 'cream' | 'subtle';
2414
+ readonly radiusScale?: 'sharp' | 'default' | 'round';
2415
+ /** One plain family name, never a stack. */
2416
+ readonly fontFamily?: string;
2417
+ /**
2418
+ * The registered face per weight (the SDK's `fonts` map, `regular`, `medium` and `bold`): one family NAME each,
2419
+ * exactly as a React Native theme file declares it in a `fontFamily` literal. The app registers the faces itself.
2420
+ */
2421
+ readonly fontRegular?: string;
2422
+ readonly fontMedium?: string;
2423
+ readonly fontBold?: string;
2424
+ }
2425
+ type ThemeToken = keyof DetectedTheme;
2426
+ /** The order the tokens are written in generated code. */
2427
+ declare const THEME_TOKEN_ORDER: readonly ThemeToken[];
2428
+ interface ThemeEvidence {
2429
+ /** The file, relative to the project root, or the label of a pasted source. */
2430
+ readonly file: string;
2431
+ /** 1-based line of the declaration the value was read from (the literal's line after a one-level resolution). */
2432
+ readonly line: number;
2433
+ /** The declaration read, with its resolution chain: `--color-primary -> --primary`, `Colors.light.tint -> tintColorLight`. */
2434
+ readonly key: string;
2435
+ /** The value as written. */
2436
+ readonly value: string;
2437
+ /** The theme field it became, or null when it was read and dropped (or read as evidence only). */
2438
+ readonly token: ThemeToken | null;
2439
+ /** How it mapped, or why it did not. */
2440
+ readonly reason: string;
2441
+ }
2442
+ /** Where one logo URL was read from. */
2443
+ interface ThemeLogoEvidence {
2444
+ readonly file: string;
2445
+ /** 1-based line of the declaration in the file as written. */
2446
+ readonly line: number;
2447
+ /** The declaration read: a custom property name, or the img tag's attribute. */
2448
+ readonly key: string;
2449
+ readonly value: string;
2450
+ }
2451
+ /**
2452
+ * THE LOGO (MCP-11). The SDK's theme takes `logoUrl` and `logoUrlDark` and the chat header shows them, and the
2453
+ * planner's theme never carried either, so every generated integration rendered Everfur's header on a partner's own
2454
+ * colours with no partner mark on it.
2455
+ *
2456
+ * https ONLY. The SDK drops a relative path (the frame and the app resolve it against the wrong origin) and plain
2457
+ * http (a mixed-content load the browser blocks), so a value that is not an absolute https URL is not read at all
2458
+ * rather than written and silently dropped at runtime.
2459
+ */
2460
+ interface ThemeLogo {
2461
+ readonly light: string;
2462
+ readonly dark: string | null;
2463
+ readonly evidence: readonly ThemeLogoEvidence[];
2464
+ }
2465
+ interface ThemeDetection {
2466
+ readonly source: ThemeSource;
2467
+ readonly confidence: ThemeConfidence;
2468
+ /** Empty at `low`. */
2469
+ readonly theme: DetectedTheme;
2470
+ readonly evidence: readonly ThemeEvidence[];
2471
+ /** The files read, relative to the root, so a refusal says where it looked. */
2472
+ readonly scanned: readonly string[];
2473
+ /** The facts a high-confidence match needs and this project did not give, in the refusal's words. Empty at `high`. */
2474
+ readonly missing: readonly string[];
2475
+ /** The fields whose value is a nearest match rather than a literal; generated code marks them `REPLACE: verify`. */
2476
+ readonly inferred: readonly ThemeToken[];
2477
+ /** The logo the project declares, https only; null when it declares none this detector can use. */
2478
+ readonly logo?: ThemeLogo | null;
2479
+ }
2480
+ /**
2481
+ * Detect the project's theme from disk, through the filesystem port. Every project gets every reader (a React
2482
+ * Native app may carry a NativeWind config, a web project a colours module); `native` only puts the React
2483
+ * Native readers first, so a tie in confidence goes to the source that fits the shape.
2484
+ */
2485
+ declare function detectTheme(fs: FileSystemPort, root: string, options?: {
2486
+ readonly native?: boolean;
2487
+ }): ThemeDetection;
2488
+ /**
2489
+ * Detect a theme from pasted text (the MCP `theme_source` argument): a stylesheet, a Tailwind config, or a
2490
+ * React Native theme file. The stylesheet readers and the Tailwind reader run over the text; the React Native
2491
+ * readers run only when it is not a Tailwind config (a config's `theme.extend.colors` would read as a colours
2492
+ * object otherwise). The best reading wins, and `label` names the text in the evidence.
2493
+ */
2494
+ declare function detectThemeFromText(text: string, label: string): ThemeDetection;
2495
+
2496
+ /**
2497
+ * THE PARTNER'S `theme` PROP, as `everfur plan` writes it for a project: the `EVERFUR_THEME` constant a generated
2498
+ * provider passes as `theme={EVERFUR_THEME}` (codegen.ts themeConstant), from the same theme detection the plan reads
2499
+ * (the project on disk, or a pasted `theme_source`). The SDK merges it over the tenant's console branding per field,
2500
+ * so `verify --surfaces theme` judges that merge rather than the console branding alone.
2501
+ *
2502
+ * The object holds exactly the fields the plan writes: every detected token by its own name (they are the SDK's
2503
+ * `EverfurThemeInput` field names), with the three registered faces written as the `fonts` map. A detection the plan
2504
+ * would not write a theme for (low confidence, or nothing read) yields null: the plan passes no prop then.
2505
+ */
2506
+ interface ThemeProp {
2507
+ /** The prop, in the SDK's `EverfurThemeInput` field names. */
2508
+ readonly input: Readonly<Record<string, unknown>>;
2509
+ /** Where it was read, for the report: the detection source and the file:line of each field. */
2510
+ readonly source: string;
2511
+ }
2512
+
2513
+ /**
2514
+ * THE SURFACE CHECKS, run when `--surfaces` names them: each asks whether one Everfur surface works for THIS tenant
2515
+ * with the session `verify` just minted, from the same wire a partner's app uses.
2516
+ *
2517
+ * chat the diagnostics reason for chat.message.create and chat.conversation.read, then GET
2518
+ * /widget/v1/conversations on the bearer.
2519
+ * records the diagnostics reason for records.record.read, then GET /widget/v1/records/requests on the bearer.
2520
+ * televet SANDBOX TENANTS ONLY (verdict livemode false): POST /widget/v1/televet/handoffs on the bearer. A live
2521
+ * tenant is never sent a write. The handoff token rides the URL fragment; it is stripped before anything
2522
+ * is kept, so only the origin, the path and the expiry reach the report.
2523
+ * theme the branding the verdict's render_hints carry, rendered by the SDK's own resolver (theme-render.ts, over
2524
+ * the SDK code mirrored into src/registry/sdk): which fields are overridden and which the tenant locked,
2525
+ * whether the SDK drops the whole branding for a schema_version it does not read, whether it would drop a
2526
+ * logo or font URL, whether text on the brand fill it paints reaches WCAG 4.5:1 on each palette it shows
2527
+ * (light unless a brightness says otherwise, both for auto), and which configured slot colours its guard
2528
+ * repaints. The inks it pushes until they read (brand text, links) and the dark variants it derives are
2529
+ * reported, not failed. With a project (`--project-root`, `project_root`) or a pasted `theme_source`, the
2530
+ * branding is judged MERGED with the `theme` prop `everfur plan` writes from that theme, as the SDK merges
2531
+ * them: the prop wins per field unless the tenant locked it.
2532
+ *
2533
+ * Every step authenticates with the identity verify used: the minted session bearer, or, for
2534
+ * `--identity anonymous`, the publishable key and the derived device session id (Records and Televet take a
2535
+ * signed session only, so an anonymous run skips both and reads chat alone).
2536
+ *
2537
+ * A surface Everfur has not switched on for the tenant is GATED, not FAIL: a platform setting is not a broken
2538
+ * integration, and reporting it as one would send a partner debugging code that is fine.
2539
+ */
2540
+ declare const SURFACES: readonly ["chat", "records", "televet", "theme"];
2541
+ type Surface = (typeof SURFACES)[number];
2542
+
1977
2543
  /**
1978
2544
  * THE WIRE EXERCISE. Three steps, run in order, each of which reports what happened rather than
1979
2545
  * what the process is about to exit with.
1980
2546
  *
1981
- * mint-session POST /widget/v1/sessions with the sk + pk pair.
2547
+ * mint-session the session to verify with: EVERFUR_SESSION_TOKEN when set (a short-lived session the
2548
+ * partner backend minted, the only source the MCP tool reads), else, from the local CLI
2549
+ * only, POST /widget/v1/sessions with the sk + pk pair. On the Everfur-hosted MCP the
2550
+ * session was minted by the Everfur API for the signed-in sandbox tenant before this ran
2551
+ * (`sessionSource: 'hosted'`), and a live tenant is GATED here rather than skipped.
1982
2552
  * poll-entitlements GET /widget/v1/entitlements with the minted bearer.
1983
- * assert-verdict the verdict carries at least one capability.
2553
+ * assert-verdict the verdict carries at least one capability, and it is ENABLED. A listed capability
2554
+ * whose surface is switched off is reported as unavailable with its reason code, never
2555
+ * as granted: see `VerdictCapability`.
1984
2556
  *
1985
2557
  * Three properties this file exists to guarantee:
1986
2558
  *
@@ -1996,8 +2568,24 @@ declare function renderSandboxCreate(creation: SandboxCreation, revocation: Sand
1996
2568
  * The mint is a POST and gets exactly one attempt. The poll is a GET, so repeating it is safe,
1997
2569
  * and it repeats on purpose: an entitlement scope provisioned seconds ago propagates on a
1998
2570
  * revision bump, and a single read would report a stale empty verdict as a failure.
2571
+ *
2572
+ * `identityMode: 'anonymous'` verifies the path an anonymous integration actually uses, which has no
2573
+ * backend to mint a session: the publishable key plus a device session id derived from the user ref
2574
+ * (`x-everfur-session-id`), exactly what the SDK sends without a user. It runs the entitlement read and
2575
+ * the assertion on that identity, then reads the tenant's session policy and lists the identity's conversations.
2576
+ * `sessions_required` refuses only an UNSIGNED caller that claims an identity (a publishable key plus
2577
+ * `x-everfur-user-ref`); signed callers and pure-anonymous callers stay exempt (EFBackend widget_dependencies.py,
2578
+ * "Session enforcement"), so the policy is reported, never failed, on the anonymous path.
2579
+ */
2580
+ /** The identities a verification can exercise, in the plan's own vocabulary. `partner-user-ref` is the default. */
2581
+ declare const VERIFY_IDENTITY_MODES: readonly ["partner-user-ref", "anonymous"];
2582
+ type VerifyIdentityMode = (typeof VERIFY_IDENTITY_MODES)[number];
2583
+ /**
2584
+ * GATED is the fourth answer, added with the surface checks: the step ran and found a surface Everfur has switched
2585
+ * off for the tenant. It is neither a failure of the integration (FAIL) nor something the operator can supply to make
2586
+ * the step run (SKIP), so it moves the status in neither direction.
1999
2587
  */
2000
- type StepResult = 'PASS' | 'FAIL' | 'SKIP';
2588
+ type StepResult = 'PASS' | 'FAIL' | 'SKIP' | 'GATED';
2001
2589
  interface VerifyStep {
2002
2590
  /** Stable within a schemaVersion. Callers may key on it. */
2003
2591
  readonly id: string;
@@ -2008,6 +2596,51 @@ interface VerifyStep {
2008
2596
  readonly requestId: string | null;
2009
2597
  readonly details: readonly string[];
2010
2598
  }
2599
+ /**
2600
+ * What the Everfur-hosted MCP did about a session before `runVerification` ran.
2601
+ *
2602
+ * The hosted server holds no key of the partner's, so it asks the Everfur API to mint a verify session for
2603
+ * the signed-in tenant (`POST /mcp/verify-sessions`, sandbox tenants only). The outcome is data here, not a
2604
+ * credential: a minted session travels in the env map like a backend-minted one, and this record carries
2605
+ * only what the step report may say about it.
2606
+ */
2607
+ type HostedSessionOutcome = {
2608
+ readonly kind: 'minted';
2609
+ readonly userRef: string;
2610
+ readonly expiresAt: string;
2611
+ readonly capabilities: readonly string[];
2612
+ readonly requestId: string | null;
2613
+ } | {
2614
+ readonly kind: 'gated';
2615
+ readonly reason: string;
2616
+ readonly requestId: string | null;
2617
+ } | {
2618
+ readonly kind: 'failed';
2619
+ readonly reason: string;
2620
+ readonly requestId: string | null;
2621
+ readonly details: readonly string[];
2622
+ /**
2623
+ * The operator-owned facts of the failed mint call, when there was one: how it failed (`http`, `network`,
2624
+ * `timeout`), the status and the wire code. Never the wire text. Lets a caller tell a transient failure (a 429
2625
+ * VerifyMintRateLimited, a 503 VerifyMintUnavailable, a timeout) from a permanent one. Absent when no call was made.
2626
+ */
2627
+ readonly failure?: {
2628
+ readonly kind: string;
2629
+ readonly status: number | null;
2630
+ readonly code: string | null;
2631
+ };
2632
+ };
2633
+ /** What a hosted mint hands back: the outcome for the report, and the session itself when one was minted. */
2634
+ interface HostedMintResult {
2635
+ readonly outcome: HostedSessionOutcome;
2636
+ /** The minted widget session, wrapped as an ephemeral credential the moment it is read here. Null unless minted. */
2637
+ readonly sessionToken: string | null;
2638
+ }
2639
+ /**
2640
+ * The hosted server's mint, called by this step once verification has decided to run, so a call refused for
2641
+ * its arguments never mints, and the session never travels through the env map.
2642
+ */
2643
+ type HostedMint = () => Promise<HostedMintResult>;
2011
2644
  interface VerifyOptions {
2012
2645
  readonly client: ApiClient;
2013
2646
  readonly clock: ClockPort;
@@ -2016,6 +2649,47 @@ interface VerifyOptions {
2016
2649
  /** How many times to read the verdict before concluding it is empty. */
2017
2650
  readonly attempts: number;
2018
2651
  readonly pollDelayMs: number;
2652
+ /**
2653
+ * The page origin a web integration embeds from. When set, the web checks (`web.ts`) run after the wire
2654
+ * exercise: the embed fence, the CORS exposure, the session policy, the loader. Null skips them.
2655
+ */
2656
+ readonly origin?: string | null;
2657
+ /** The transport for the loader probe, which leaves the API host. Null skips that one check. */
2658
+ readonly transport?: TransportPort | null;
2659
+ readonly timeoutMs?: number;
2660
+ /** The surfaces to exercise with the minted session (`surfaces.ts`). Empty or absent runs none. */
2661
+ readonly surfaces?: readonly Surface[];
2662
+ /** The project to run the offline checks over (`project-checks.ts`), with the port to read it. Null runs none. */
2663
+ readonly project?: {
2664
+ readonly fs: FileSystemPort;
2665
+ readonly root: string;
2666
+ } | null;
2667
+ /**
2668
+ * Whether the key pair may mint when EVERFUR_SESSION_TOKEN is absent. True for the local CLI (the fallback); false
2669
+ * for the MCP tool, whose documented environment never holds the secret key, so it never reads one.
2670
+ */
2671
+ readonly mintWithSecretKey?: boolean;
2672
+ /** The loader to probe for the web checks, overriding the one derived from the API host. Null derives it. */
2673
+ readonly loaderUrl?: string | null;
2674
+ /**
2675
+ * Where the session comes from. `env` (the default): EVERFUR_SESSION_TOKEN, else the local key-pair mint when
2676
+ * `mintWithSecretKey` allows it. `hosted`: this step asks the Everfur API for one on the signed-in tenant's
2677
+ * behalf through `hostedMint`, and reports how that went. The hosted wording names no variable, because the
2678
+ * partner exported none.
2679
+ */
2680
+ readonly sessionSource?: 'env' | 'hosted';
2681
+ readonly hostedMint?: HostedMint | null;
2682
+ /**
2683
+ * Which identity to verify with. `partner-user-ref` (the default): a signed session, from `sessionSource` as
2684
+ * above. `anonymous`: the publishable key and a derived device session id; nothing is minted, on either server.
2685
+ */
2686
+ readonly identityMode?: VerifyIdentityMode;
2687
+ /**
2688
+ * The `theme` prop the theme check merges over the tenant's branding, as the SDK does. Absent: the prop `everfur plan`
2689
+ * writes from the project's own theme when `project` is given (none otherwise). Null: no prop. The MCP tool passes
2690
+ * the prop read from a pasted `theme_source`, which wins over the project on disk, as it does for the plan.
2691
+ */
2692
+ readonly themeProp?: ThemeProp | null;
2019
2693
  }
2020
2694
  interface Verification {
2021
2695
  readonly steps: readonly VerifyStep[];
@@ -2072,7 +2746,26 @@ declare function createMemoryFileSystem(files: Readonly<Record<string, string>>)
2072
2746
  * disk. Anything it cannot establish lives in `src/commands/plan/facts.ts` as a supplied fact,
2073
2747
  * and the two sets are deliberately disjoint.
2074
2748
  */
2075
- type ProjectType = 'expo-managed' | 'bare-react-native' | 'flutter' | 'web' | 'unknown';
2749
+ type ProjectType = 'expo-managed' | 'bare-react-native' | 'flutter' | 'web' | 'shopify-storefront' | 'unknown';
2750
+ /**
2751
+ * How a web project embeds the Everfur surface, read from the project: `in-page` when package.json
2752
+ * declares react-dom (the SDK's React components render in the host's own tree), `frame` otherwise (the
2753
+ * loader puts the surface in an iframe on Everfur's origin, and nothing is installed). The platform tree
2754
+ * asks this as its `web-embedding` question; `--embedding` overrides the detected answer. Null for every
2755
+ * other project shape.
2756
+ */
2757
+ type WebEmbedding = 'in-page' | 'frame';
2758
+ /**
2759
+ * The web framework facet of a `web` project (see `web-framework.ts`). It spells the generated files; the
2760
+ * contract's platform tree still branches on the project type, which this never replaces.
2761
+ */
2762
+ type WebFramework = 'next-app-router' | 'next-pages-router' | 'vite-react' | 'remix' | 'static-html' | 'other-web';
2763
+ interface WebFrameworkDetection {
2764
+ readonly framework: WebFramework;
2765
+ /** `src/` when the framework's source root is `src/` (Next.js with src/app, a Vite app), else empty. */
2766
+ readonly sourceDir: '' | 'src/';
2767
+ readonly evidence: readonly string[];
2768
+ }
2076
2769
  type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'not-applicable' | 'undetermined';
2077
2770
  type DependencySource = 'package.json' | 'pubspec.yaml';
2078
2771
  type DependencyScope = 'dependencies' | 'devDependencies' | 'peerDependencies' | 'sdk';
@@ -2097,6 +2790,47 @@ interface ProjectDetection {
2097
2790
  readonly packageManager: PackageManager;
2098
2791
  readonly packageManagerEvidence: readonly string[];
2099
2792
  readonly dependencies: readonly DetectedDependency[];
2793
+ /** The detected embedding of a `web` project; null for every other shape. Its evidence is in `projectTypeEvidence`. */
2794
+ readonly webEmbedding: WebEmbedding | null;
2795
+ /**
2796
+ * The detected framework of a `web` project; null (or absent, for a detection built by hand) for every
2797
+ * other shape.
2798
+ */
2799
+ readonly webFramework?: WebFrameworkDetection | null;
2800
+ /**
2801
+ * What the project already says about its own look (theme-detect.ts), for the shapes the plan writes a provider
2802
+ * for (web, Expo, bare React Native); null for every other shape, and absent for a detection built by hand.
2803
+ */
2804
+ readonly theme?: ThemeDetection | null;
2805
+ /**
2806
+ * Expo Router, for a React Native project that uses it (expo-router.ts); null when it does not, and absent for a
2807
+ * detection built by hand. It decides where the plan tells the app to mount its Everfur module: Expo Router never
2808
+ * renders App.tsx, and every file under its routes directory is a route.
2809
+ */
2810
+ readonly expoRouter?: ExpoRouterDetection | null;
2811
+ /**
2812
+ * The URL schemes a React Native project declares and the application ids beside them (app-schemes.ts), so the
2813
+ * visit-return step can say whether one of them is a destination Everfur would register (MCP-14). Null for every
2814
+ * other shape, and absent for a detection built by hand.
2815
+ */
2816
+ readonly appSchemes?: AppSchemeDetection | null;
2817
+ }
2818
+ /** What a React Native project declares about its own URL schemes. */
2819
+ interface AppSchemeDetection {
2820
+ /** Every declared scheme, in the order app.json, iOS, Android. */
2821
+ readonly schemes: readonly string[];
2822
+ /** The application ids declared beside them (the iOS bundle identifier, the Android package). */
2823
+ readonly appIds: readonly string[];
2824
+ /** The first declared scheme in reverse-DNS form, which is the only form Everfur registers; null when there is none. */
2825
+ readonly reverseDns: string | null;
2826
+ /** The file and key each scheme was read from. */
2827
+ readonly evidence: readonly string[];
2828
+ }
2829
+ /** An Expo Router project: its routes directory, and the files that said so. */
2830
+ interface ExpoRouterDetection {
2831
+ /** `app`, or `src/app` when the routes live there (Expo Router reads `src/app` when `app` is absent). */
2832
+ readonly appDir: 'app' | 'src/app';
2833
+ readonly evidence: readonly string[];
2100
2834
  }
2101
2835
 
2102
2836
  declare function detectProject(fs: FileSystemPort, root: string): ProjectDetection;
@@ -2119,6 +2853,23 @@ interface TerminalOutcome {
2119
2853
  readonly capabilityRefs: readonly string[];
2120
2854
  /** The tree that must be answered next, or null when this answer stands alone. */
2121
2855
  readonly thenAskTreeId: string | null;
2856
+ /**
2857
+ * Present for a `human_ui` outcome (owner decision 2026-09-17): the plan may emit the UI a person taps, and no
2858
+ * agent may perform what it does. Why, and where to ask. Absent (or null) for an ordinary terminal. A
2859
+ * `partner_backend` outcome carries it too, for what no agent does with a delivery.
2860
+ */
2861
+ readonly agentBlocked?: AgentBlock | null;
2862
+ /**
2863
+ * True for a `partner_backend` outcome (DECISIONS.md entry 21): the plan writes code the partner's BACKEND runs to
2864
+ * receive from Everfur (the webhook receiver), not a surface a device mounts. Absent for every other terminal.
2865
+ */
2866
+ readonly partnerBackend?: true;
2867
+ }
2868
+ /** The agent refusal a `human_ui` outcome carries beside its terminal facts. */
2869
+ interface AgentBlock {
2870
+ readonly reason: string;
2871
+ readonly unblockOwner: string;
2872
+ readonly requestPath: string;
2122
2873
  }
2123
2874
  interface BlockedOutcome {
2124
2875
  readonly kind: 'blocked';
@@ -2233,7 +2984,7 @@ interface PlanCode {
2233
2984
  /** Suggested path, relative to the project root. */
2234
2985
  readonly path: string;
2235
2986
  /** For syntax highlighting and for an agent deciding how to write it. */
2236
- readonly language: 'tsx' | 'ts' | 'dart';
2987
+ readonly language: 'tsx' | 'ts' | 'dart' | 'html' | 'liquid' | 'dotenv';
2237
2988
  readonly contents: string;
2238
2989
  }
2239
2990
 
@@ -2270,6 +3021,11 @@ declare const PLAN_DOCUMENT_KIND = "everfur.plan";
2270
3021
  type Capability = string;
2271
3022
  /** An identity mode id: one option of the identity-mode tree's entry question. */
2272
3023
  type IdentityMode = string;
3024
+ /**
3025
+ * A web embedding id: one option of the platform tree's `web-embedding` question. Detected from the
3026
+ * project (react-dom present means in-page, otherwise the frame) and overridable by `--embedding`.
3027
+ */
3028
+ type Embedding = string;
2273
3029
  /** A backend language id: one option of the session-mint-language tree's entry question. */
2274
3030
  type SessionMintLanguage = string;
2275
3031
  type PlanStatus = 'ready' | 'underspecified' | 'blocked';
@@ -2282,7 +3038,7 @@ type PlanStatus = 'ready' | 'underspecified' | 'blocked';
2282
3038
  * reachable. It is a REFUSAL rather than a broken-artifact failure because nothing was loaded
2283
3039
  * and nothing on disk is wrong; the operator simply has not said where the contract is.
2284
3040
  */
2285
- declare const MISSING_FACT_NAMES: readonly ["decisionTrees", "projectType", "packageManager", "capabilities", "identityMode", "sessionMintLanguage"];
3041
+ declare const MISSING_FACT_NAMES: readonly ["decisionTrees", "projectType", "packageManager", "embedding", "capabilities", "identityMode", "sessionMintLanguage", "environment"];
2286
3042
  type MissingFactName = (typeof MISSING_FACT_NAMES)[number];
2287
3043
  interface MissingFact {
2288
3044
  readonly fact: MissingFactName;
@@ -2331,8 +3087,18 @@ interface BlockedCapability {
2331
3087
  readonly unblockOwner: string;
2332
3088
  /** Site-relative path on the partner documentation site. Resolved at compile time. */
2333
3089
  readonly requestPath: string;
2334
- /** The capability pack keys the block derives from. */
3090
+ /** The capability pack keys the block derives from. Empty when the block is the platform's. */
2335
3091
  readonly capabilityKeys: readonly string[];
3092
+ /**
3093
+ * What refused it. `capability` (the default when absent, for documents written before this field
3094
+ * existed) is a BLOCKED terminal in the capabilities tree: the surface itself cannot be built.
3095
+ * `platform` is the host project shape: the surface is buildable, but not for this project, because
3096
+ * the SDK publishes no entry point for it or the contract blocks the shape outright. An agent that
3097
+ * reads the two the same way tells a developer to ask support about chat when the answer is "not
3098
+ * from this project shape". (Next.js, the App Router and the Pages Router alike, IS plannable: the
3099
+ * framework is detected from the project and gets its own route convention.)
3100
+ */
3101
+ readonly blockedBy?: 'capability' | 'platform';
2336
3102
  }
2337
3103
  /**
2338
3104
  * Guidance that is true regardless of what was answered, carried in the document rather than
@@ -2364,6 +3130,22 @@ interface PlanDetection {
2364
3130
  readonly source: string;
2365
3131
  readonly scope: string;
2366
3132
  }[];
3133
+ /**
3134
+ * The detected embedding of a `web` project (`in-page` or `frame`), null for other shapes. Additive to
3135
+ * schema v1: absent on documents written before the web shapes existed.
3136
+ */
3137
+ readonly webEmbedding?: string | null;
3138
+ /**
3139
+ * The detected framework of a `web` project (next-app-router, next-pages-router, vite-react, remix,
3140
+ * static-html, other-web), null for other shapes. Additive to schema v1.
3141
+ */
3142
+ readonly webFramework?: string | null;
3143
+ /**
3144
+ * What the project says about its own look, read statically (detect/theme-detect.ts): the source, the confidence,
3145
+ * the SDK theme fields it mapped, and the file:line evidence for each. Null for a shape the plan writes no
3146
+ * provider for, or for a detection built by hand. Additive to schema v1.
3147
+ */
3148
+ readonly theme?: ThemeDetection | null;
2367
3149
  }
2368
3150
  interface PlanDocument {
2369
3151
  readonly schemaVersion: typeof PLAN_SCHEMA_VERSION;
@@ -2380,6 +3162,13 @@ interface PlanDocument {
2380
3162
  readonly capabilities: readonly Capability[] | null;
2381
3163
  readonly identityMode: IdentityMode | null;
2382
3164
  readonly sessionMintLanguage: SessionMintLanguage | null;
3165
+ /** `--embedding`, when given; null means the detected embedding decided. Additive to schema v1. */
3166
+ readonly embedding?: Embedding | null;
3167
+ /**
3168
+ * `--environment` (test, staging or production); null means it was not given. Required for a plan that writes
3169
+ * configuration, and never defaulted. Additive to schema v1.
3170
+ */
3171
+ readonly environment?: string | null;
2383
3172
  };
2384
3173
  /** Empty when `status` is `ready`. Non-empty exactly when it is `underspecified`. */
2385
3174
  readonly missingFacts: readonly MissingFact[];
@@ -2387,12 +3176,31 @@ interface PlanDocument {
2387
3176
  readonly steps: readonly PlanStep[];
2388
3177
  /** Non-empty exactly when a selected capability cannot be built today. */
2389
3178
  readonly blocked: readonly BlockedCapability[];
3179
+ /**
3180
+ * Non-null when the host project SHAPE refuses every plan, whatever was asked for: the contract blocks
3181
+ * the shape, or the SDK publishes no entry point for it. Carried at the top level, beside the
3182
+ * per-capability blocks, because it is a fact about the project rather than about any capability, and
3183
+ * a reader branching on `blocked[].blockedBy` alone would miss it on a document whose only asked-for
3184
+ * capabilities were also blocked by the registry. Additive to schema v1 (optional; absent on documents
3185
+ * written before it existed, which means "no platform block").
3186
+ */
3187
+ readonly platformBlock?: PlatformBlock | null;
2390
3188
  /** The principles of every tree this plan consulted. */
2391
3189
  readonly principles: readonly PlanPrinciple[];
2392
3190
  readonly warnings: readonly PlanWarning[];
2393
3191
  /** Null only on the refusal that says no decision space was found. */
2394
3192
  readonly decisionSpace: DecisionSpaceProvenance | null;
2395
3193
  }
3194
+ /** Why a whole project shape is refused, independent of which capabilities were asked for. */
3195
+ interface PlatformBlock {
3196
+ readonly reason: string;
3197
+ readonly unblockOwner: string;
3198
+ /** Site-relative path on the partner documentation site. */
3199
+ readonly requestPath: string;
3200
+ }
3201
+
3202
+ declare const ENVIRONMENTS: readonly ["test", "staging", "production"];
3203
+ type Environment = (typeof ENVIRONMENTS)[number];
2396
3204
 
2397
3205
  /**
2398
3206
  * The planner. A pure function from (what was detected, what was supplied, what the contract
@@ -2427,9 +3235,27 @@ interface PlanInput {
2427
3235
  readonly capabilities: readonly Capability[] | null;
2428
3236
  readonly identityMode: IdentityMode | null;
2429
3237
  readonly sessionMintLanguage: SessionMintLanguage | null;
3238
+ /** `--embedding`, overriding the detected web embedding; absent or null means detection decides. */
3239
+ readonly embedding?: Embedding | null;
3240
+ /**
3241
+ * `--environment`: the Everfur deployment the generated configuration targets. Absent or null is a missing fact
3242
+ * for a plan that writes configuration; it is never defaulted.
3243
+ */
3244
+ readonly environment?: Environment | null;
2430
3245
  /** The decision space from the contract projection, or null when none was found. */
2431
3246
  readonly space: DecisionSpace | null;
2432
3247
  readonly cliVersion: string;
3248
+ /**
3249
+ * True when `capabilities` came from the `all` sentinel (every capability the contract can plan). The plan then says
3250
+ * so, and says that `verify` is the fail-closed check of what the tenant actually has: `plan` holds no credential.
3251
+ */
3252
+ readonly allCapabilities?: boolean;
3253
+ /**
3254
+ * A theme detection that replaces the one on `detection`: a remote MCP that cannot read the project received the
3255
+ * partner's Tailwind config or CSS as `theme_source` and read it with `detectThemeFromText`. Absent means the
3256
+ * detection's own (which is absent too for a detection built by hand, and then no theme step is planned).
3257
+ */
3258
+ readonly theme?: ThemeDetection | null;
2433
3259
  }
2434
3260
  /**
2435
3261
  * Compute the missing facts in a fixed order: the contract first, because everything else is
@@ -2474,17 +3300,25 @@ declare function renderPlan(document: PlanDocument): string;
2474
3300
  interface SuppliedFactsResult {
2475
3301
  readonly ok: boolean;
2476
3302
  readonly capabilities: readonly Capability[] | null;
3303
+ /** True when `--capabilities all` expanded to every plannable capability, so the plan can say so. */
3304
+ readonly allCapabilities: boolean;
2477
3305
  readonly identityMode: IdentityMode | null;
2478
3306
  readonly sessionMintLanguage: SessionMintLanguage | null;
3307
+ readonly embedding: Embedding | null;
2479
3308
  readonly errors: readonly string[];
2480
3309
  }
2481
3310
  /**
2482
3311
  * Parse `--capabilities chat,records`. Order is normalised to sorted order and duplicates
2483
3312
  * collapse, so two equivalent invocations produce a byte-identical plan document.
3313
+ *
3314
+ * `all` is the one token the contract does not publish: it expands to every capability the loaded space can plan
3315
+ * (a BLOCKED option is left out, since asking for it is asking for a refusal). `all` is reported so the plan can say
3316
+ * what it meant, and that `verify` is the fail-closed check of what the tenant actually has.
2484
3317
  */
2485
3318
  declare function parseCapabilities(raw: string | null, space: DecisionSpace | null): {
2486
3319
  readonly value: readonly Capability[] | null;
2487
3320
  readonly errors: readonly string[];
3321
+ readonly all: boolean;
2488
3322
  };
2489
3323
  declare function parseIdentityMode(raw: string | null, space: DecisionSpace | null): {
2490
3324
  readonly value: IdentityMode | null;
@@ -2496,7 +3330,7 @@ declare function parseSessionMintLanguage(raw: string | null, space: DecisionSpa
2496
3330
  readonly errors: readonly string[];
2497
3331
  };
2498
3332
  /** Parse every supplied fact, accumulating all errors rather than stopping at the first. */
2499
- declare function parseSuppliedFacts(capabilitiesRaw: string | null, identityRaw: string | null, backendRaw: string | null, space: DecisionSpace | null): SuppliedFactsResult;
3333
+ declare function parseSuppliedFacts(capabilitiesRaw: string | null, identityRaw: string | null, backendRaw: string | null, space: DecisionSpace | null, embeddingRaw?: string | null): SuppliedFactsResult;
2500
3334
 
2501
3335
  /**
2502
3336
  * The CLI version, stamped into every emitted document under `generatedBy.version`.
@@ -2506,6 +3340,6 @@ declare function parseSuppliedFacts(capabilitiesRaw: string | null, identityRaw:
2506
3340
  * install, and a bundled artifact. `tests/unit/version.test.ts` asserts this constant equals
2507
3341
  * the package.json version, so the two cannot drift.
2508
3342
  */
2509
- declare const CLI_VERSION = "0.1.0";
3343
+ declare const CLI_VERSION = "0.2.1";
2510
3344
 
2511
- export { ADMIN_TOKEN, API_PREFIX, type ApiClient, type ApiFailure, type ApiResult, type ApiSuccess, BASE_URL_ENV_VAR, type BlockedCapability, type BlockedOutcome, CLI_VERSION, COMMANDS, CREDENTIAL_SPECS, type Capability, type CapabilityDiagnosis, type ClockPort, type Command, type CommandContext, type CommandOutcome, type ContractValidationOutcome, type Credential, type CredentialSpec, DEFAULT_TIMEOUT_MS, type DecisionSpace, type DecisionSpaceProvenance, type DecisionTree, type DetectedDependency, type Diagnosis, EMPTY_REGISTRY, ERRORS_DOCUMENT_KEY, type ErrorRegistry, type ErrorRegistryEntry, ExitCode, type FileSystemPort, type FormatOptions, type FormattedOutput, type HttpMethod, type HttpRequest, type HttpResponse, IDEMPOTENT_METHODS, type IdentityMode, MISSING_FACT_NAMES, type MissingFact, type MissingFactName, type Outcome, PARTNER_KEY, PARTNER_SECRET_KEY, PLAN_DOCUMENT_KIND, PLAN_SCHEMA_VERSION, type PackageManager, type PlanDocument, type PlanInput, type PlanPrinciple, type PlanStep, type PlanWarning, type PositionalSpec, type ProjectDetection, type ProjectType, REASON_CODES, REDACTED, type ReasonCode, type RegistryLoad, type RequestSpec, type ResolvedCredential, type ResolvedEndpoint, type RunOptions, SCHEMAS, SESSION_TOKEN, type SandboxCreateStatus, type SandboxCreation, type SchemaValidation, type SchemaViolation, type SessionMintLanguage, type StepResult, type TerminalOutcome, TransportError, type TransportPort, type TreeOption, type TreeQuestion, type Verification, type VerifyStep, type WalkResult, apiUrl, buildDecisionSpace, buildDiagnosisDocument, buildExplanationDocument, buildSandboxDocument, buildVerificationDocument, clampTimeout, createApiClient, createMemoryFileSystem, createRecordingClock, describeFailure, detectProject, digestOfProjection, digestsEqual, editDistance, entryFromRow, ephemeralCredential, errorCodeOf, failureDetails, failureOutcome, fetchTransport, formatOutcome, isCorrelatableRequestId, isIdempotent, isKnownReasonCode, loadDecisionSpace, loadErrorRegistry, matchCommand, missingFactsFor, nextActionFor as nextActionForErrorCode, nextActionFor$1 as nextActionForReason, nextActionsFor, nodeFileSystem, parseCapabilities, parseDiagnostics, parseIdentityMode, parseSessionMintLanguage, parseSuppliedFacts, planIntegration, projectSandboxCreate, renderDiagnosis, renderExplanation, renderPlan, renderSandboxCreate, renderVerification, resolveContractPath, resolveCredential, resolveEndpoint, runCli, runVerification, suggestCodes, systemClock, validateContractFile, validateContractPublicEnvelope, validateDiagnosisDocument, validateErrorExplanationDocument, validatePlanDocument, validateSandboxCreateDocument, validateVerificationDocument, verifyDigest, walkSingle, walkTree, withPrefix };
3345
+ export { ADMIN_TOKEN, API_PREFIX, type ApiClient, type ApiFailure, type ApiResult, type ApiSuccess, BASE_URL_ENV_VAR, type BlockedCapability, type BlockedOutcome, CLI_VERSION, COMMANDS, CREDENTIAL_SPECS, type Capability, type CapabilityDiagnosis, type ClockPort, type Command, type CommandContext, type CommandOutcome, type ContractValidationOutcome, type Credential, type CredentialSpec, DEFAULT_TIMEOUT_MS, type DecisionSpace, type DecisionSpaceProvenance, type DecisionTree, type DetectedDependency, type DetectedTheme, type Diagnosis, EMPTY_REGISTRY, ERRORS_DOCUMENT_KEY, type ErrorRegistry, type ErrorRegistryEntry, ExitCode, type FileSystemPort, type FormatOptions, type FormattedOutput, type HttpMethod, type HttpRequest, type HttpResponse, IDEMPOTENT_METHODS, type IdentityMode, MISSING_FACT_NAMES, type MissingFact, type MissingFactName, type Outcome, PARTNER_KEY, PARTNER_SECRET_KEY, PLAN_DOCUMENT_KIND, PLAN_SCHEMA_VERSION, type PackageManager, type PlanDocument, type PlanInput, type PlanPrinciple, type PlanStep, type PlanWarning, type PositionalSpec, type ProjectDetection, type ProjectType, REASON_CODES, REDACTED, type ReasonCode, type RegistryLoad, type RequestSpec, type ResolvedCredential, type ResolvedEndpoint, type RunOptions, SCHEMAS, SESSION_TOKEN, type SandboxCreateStatus, type SandboxCreation, type SchemaValidation, type SchemaViolation, type SessionMintLanguage, type StepResult, THEME_TOKEN_ORDER, type TerminalOutcome, type ThemeConfidence, type ThemeDetection, type ThemeEvidence, type ThemeSource, type ThemeToken, TransportError, type TransportPort, type TreeOption, type TreeQuestion, type Verification, type VerifyStep, type WalkResult, apiUrl, buildDecisionSpace, buildDiagnosisDocument, buildExplanationDocument, buildSandboxDocument, buildVerificationDocument, clampTimeout, createApiClient, createMemoryFileSystem, createRecordingClock, describeFailure, detectProject, detectTheme, detectThemeFromText, digestOfProjection, digestsEqual, editDistance, entryFromRow, ephemeralCredential, errorCodeOf, failureDetails, failureOutcome, fetchTransport, formatOutcome, isCorrelatableRequestId, isIdempotent, isKnownReasonCode, loadDecisionSpace, loadErrorRegistry, matchCommand, missingFactsFor, nextActionFor as nextActionForErrorCode, nextActionFor$1 as nextActionForReason, nextActionsFor, nodeFileSystem, parseCapabilities, parseDiagnostics, parseIdentityMode, parseSessionMintLanguage, parseSuppliedFacts, planIntegration, projectSandboxCreate, renderDiagnosis, renderExplanation, renderPlan, renderSandboxCreate, renderVerification, resolveContractPath, resolveCredential, resolveEndpoint, runCli, runVerification, suggestCodes, systemClock, validateContractFile, validateContractPublicEnvelope, validateDiagnosisDocument, validateErrorExplanationDocument, validatePlanDocument, validateSandboxCreateDocument, validateVerificationDocument, verifyDigest, walkSingle, walkTree, withPrefix };