@spectrum-ts/core 9.1.0 → 9.3.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.
@@ -1,4 +1,4 @@
1
- import { At as asReply, Cn as asCustom, Ct as asText, Et as asRichlink, Ft as renameSchema, H as ProviderMessageRecord, Hn as PhotoInput, Jt as asMarkdown, Lt as asRead, Mt as replySchema, Nn as asContact, Sn as reactionSchema, U as ProviderUserRecord, Un as buildPhotoAction, Ut as asPoll, Vn as avatarSchema, Wn as photoActionSchema, Wt as asPollOption, _n as removeMemberSchema, _t as asVoice, bn as asReaction, hn as leaveSpaceSchema, pn as addMemberSchema, q as ManagedStream, r as asAttachment, rn as groupSchema, tn as asGroup } from "./attachment-B4H1Q8xO.js";
1
+ import { At as asReply, Cn as asCustom, Ct as asText, Et as asRichlink, Ft as renameSchema, H as ProviderMessageRecord, Hn as PhotoInput, Jt as asMarkdown, Lt as asRead, Mt as replySchema, Nn as asContact, Sn as reactionSchema, U as ProviderUserRecord, Un as buildPhotoAction, Ut as asPoll, Vn as avatarSchema, Wn as photoActionSchema, Wt as asPollOption, _n as removeMemberSchema, _t as asVoice, bn as asReaction, hn as leaveSpaceSchema, pn as addMemberSchema, q as ManagedStream, r as asAttachment, rn as groupSchema, tn as asGroup } from "./attachment-ChqzKngn.js";
2
2
  import z from "zod";
3
3
  import { FetchSpanOptions, LogAttrs, LogAttrs as LogAttrs$1, LogLevel, PhotonLogger, SanitizeUrlOptions, createLogger, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel } from "@photon-ai/otel";
4
4
  import { Token } from "marked";
@@ -30,6 +30,61 @@ declare const ensureM4a: (buffer: Buffer, mimeType: string) => Promise<{
30
30
  duration?: number;
31
31
  }>;
32
32
  //#endregion
33
+ //#region src/utils/env.d.ts
34
+ /**
35
+ * Build a Spectrum config env-var name from a channel and a key, centralizing
36
+ * the `SPECTRUM_<CHANNEL>_<KEY>` convention so the prefix can't drift or be
37
+ * mistyped per field. Both segments are joined verbatim (callers pass them
38
+ * already upper-snake-cased), e.g. `envFor("TELEGRAM", "BOT_TOKEN")` →
39
+ * `"SPECTRUM_TELEGRAM_BOT_TOKEN"`.
40
+ */
41
+ declare const envFor: (channel: string, key: string) => string;
42
+ /**
43
+ * Wrap a config-field schema so it falls back to an environment variable when
44
+ * the field is omitted. Precedence is **explicit value > env var > the inner
45
+ * schema's own default/required check**:
46
+ *
47
+ * - An explicit value (anything other than `undefined`) is passed straight
48
+ * through — a caller-supplied config always wins over the environment.
49
+ * - When the field is `undefined`, `process.env[envKey]` is substituted. An
50
+ * env var set to the empty string is treated as unset (a common deploy
51
+ * footgun) so it doesn't satisfy a `.min(1)` by accident.
52
+ * - The inner `schema` then validates the resolved value, keeping its exact
53
+ * semantics — regex, `.min(1)`, `.url()`, `.optional()`, `.default(...)`.
54
+ * When both the field and the env var are absent, a required inner schema
55
+ * raises its normal "required" error.
56
+ *
57
+ * The output type is the inner schema's output type, so call sites are
58
+ * unchanged. Because substitution only happens on `undefined`, wrapping a
59
+ * field inside a `z.object` leaves it a plain key on the parsed result — union
60
+ * discriminators like `"accessToken" in config` keep working.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * botToken: fromEnv("SPECTRUM_TELEGRAM_BOT_TOKEN", z.string().regex(BOT_TOKEN_PATTERN)),
65
+ * ```
66
+ */
67
+ declare const fromEnv: <T extends z.ZodType>(envKey: string, schema: T) => z.ZodPreprocess<T>;
68
+ /**
69
+ * Rewrite a platform's config schema so every string-leaf field falls back to
70
+ * `SPECTRUM_<PLATFORM>_<KEY>` when omitted which is the equivalent of
71
+ * hand-wrapping each field with {@link fromEnv}. Called once by
72
+ * `definePlatform`, so adapters declare plain Zod and get env fallback for free.
73
+ *
74
+ * - **object**: each string-leaf field is wrapped with `fromEnv`; non-string
75
+ * fields (records, arrays, nested objects, booleans, numbers) are left as-is.
76
+ * The object is only reconstructed when a field actually changed, and
77
+ * `.strict()` is preserved so an empty strict "cloud" branch is untouched.
78
+ * - **union**: each option is rewritten independently, so an env value only
79
+ * satisfies the branch that declares that field (a direct/cloud union stays
80
+ * correctly discriminated).
81
+ * - anything else is returned unchanged.
82
+ *
83
+ * The output type is identical to the input schema's. Only omitted fields gain
84
+ * an env fallback which means that `z.infer` and downstream contexts are unaffected.
85
+ */
86
+ declare const envAwareConfig: <T extends z.ZodType>(name: string, schema: T) => T;
87
+ //#endregion
33
88
  //#region src/utils/instrumented-fetch.d.ts
34
89
  /**
35
90
  * Build a fetch that traces spectrum's OWN outbound HTTP: each request through
@@ -123,4 +178,4 @@ declare const resumableOrderedStream: <TLive, TMissed, TOutput>(options: Resumab
123
178
  */
124
179
  declare function errorAttrs(error: unknown, prefix?: string): LogAttrs$1;
125
180
  //#endregion
126
- export { type CloseableAsyncIterable, type LogAttrs, type LogLevel, type PhotoInput, type PhotonLogger, type ProviderMessageRecord, type ProviderUserRecord, type ResumableStreamItem, type SanitizeUrlOptions, addMemberSchema, asAttachment, asContact, asCustom, asGroup, asMarkdown, asPoll, asPollOption, asReaction, asRead, asReply, asRichlink, asText, asVoice, avatarSchema, buildPhotoAction, createLogger, ensureM4a, errorAttrs, groupSchema, leaveSpaceSchema, messageEffectSchema, photoActionSchema, reactionSchema, removeMemberSchema, renameSchema, renderInlineTokens, replySchema, resumableOrderedStream, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel, tracedFetch };
181
+ export { type CloseableAsyncIterable, type LogAttrs, type LogLevel, type PhotoInput, type PhotonLogger, type ProviderMessageRecord, type ProviderUserRecord, type ResumableStreamItem, type SanitizeUrlOptions, addMemberSchema, asAttachment, asContact, asCustom, asGroup, asMarkdown, asPoll, asPollOption, asReaction, asRead, asReply, asRichlink, asText, asVoice, avatarSchema, buildPhotoAction, createLogger, ensureM4a, envAwareConfig, envFor, errorAttrs, fromEnv, groupSchema, leaveSpaceSchema, messageEffectSchema, photoActionSchema, reactionSchema, removeMemberSchema, renameSchema, renderInlineTokens, replySchema, resumableOrderedStream, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel, tracedFetch };
package/dist/authoring.js CHANGED
@@ -1,4 +1,4 @@
1
- import { A as addMemberSchema, B as groupSchema, E as asPollOption, F as asMarkdown, H as asText, L as markdownSchema, M as leaveSpaceSchema, P as removeMemberSchema, R as asGroup, S as asReaction, T as asPoll, W as textSchema, Y as asContact, _ as replySchema, at as attachmentSchema, b as asRead, d as asVoice, et as avatarSchema, h as asReply, i as stream, l as renderInlineTokens, nt as photoActionSchema, o as errorAttrs, p as asRichlink, q as asCustom, rt as asAttachment, st as tracedFetch, tt as buildPhotoAction, w as reactionSchema, y as renameSchema } from "./stream-DxI5ZiE2.js";
1
+ import { B as markdownSchema, C as asRead, D as reactionSchema, F as leaveSpaceSchema, G as asText, L as removeMemberSchema, N as addMemberSchema, O as asPoll, Q as asContact, R as asMarkdown, S as renameSchema, T as asReaction, U as groupSchema, V as asGroup, X as asCustom, at as photoActionSchema, b as replySchema, ct as attachmentSchema, d as envAwareConfig, f as envFor, g as asRichlink, i as stream, it as buildPhotoAction, k as asPollOption, l as renderInlineTokens, m as asVoice, o as errorAttrs, ot as asAttachment, p as fromEnv, q as textSchema, rt as avatarSchema, ut as tracedFetch, v as asReply } from "./stream-DAfZ5DFR.js";
2
2
  import z from "zod";
3
3
  import { createLogger, createLogger as createLogger$1, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel } from "@photon-ai/otel";
4
4
  import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
@@ -422,4 +422,4 @@ const resumableOrderedStream = (options) => stream((emit, end) => {
422
422
  };
423
423
  });
424
424
  //#endregion
425
- export { addMemberSchema, asAttachment, asContact, asCustom, asGroup, asMarkdown, asPoll, asPollOption, asReaction, asRead, asReply, asRichlink, asText, asVoice, avatarSchema, buildPhotoAction, createLogger, ensureM4a, errorAttrs, groupSchema, leaveSpaceSchema, messageEffectSchema, photoActionSchema, reactionSchema, removeMemberSchema, renameSchema, renderInlineTokens, replySchema, resumableOrderedStream, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel, tracedFetch };
425
+ export { addMemberSchema, asAttachment, asContact, asCustom, asGroup, asMarkdown, asPoll, asPollOption, asReaction, asRead, asReply, asRichlink, asText, asVoice, avatarSchema, buildPhotoAction, createLogger, ensureM4a, envAwareConfig, envFor, errorAttrs, fromEnv, groupSchema, leaveSpaceSchema, messageEffectSchema, photoActionSchema, reactionSchema, removeMemberSchema, renameSchema, renderInlineTokens, replySchema, resumableOrderedStream, sanitizeEmail, sanitizeErrorMessage, sanitizePhone, sanitizeUrl, setLogLevel, tracedFetch };
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as DedicatedTokenData, $t as TextStreamOptions, A as isFusorEvent, An as ContactName, B as PlatformUser, Bn as avatar, Bt as PollChoice, C as FusorVerify, D as WebhookRawResult, Dn as ContactDetails, Dt as richlink, E as WebhookRawRequest, En as ContactAddress, F as PlatformInstance, Fn as AgentSender, G as SpaceNamespace, Gn as App, Gt as option, Ht as PollOption, I as PlatformMessage, In as User, It as Read, J as broadcast, Jn as app, K as Broadcaster, Kn as AppLayout, Kt as poll, L as PlatformProviderConfig, Ln as Avatar, M as EventProducer, Mn as ContactPhone, N as Platform, Nt as Rename, O as FusorEvent, On as ContactEmail, Ot as resolveContents, P as PlatformDef, Pn as contact, Pt as rename, Q as CloudPlatform, Qt as StreamTextSource, R as PlatformRuntime, Rn as AvatarData, Rt as read, S as FusorRespond, St as typing, T as WebhookHandler, Tn as Contact, Tt as Richlink, V as ProviderMessage, Vt as PollChoiceInput, W as SchemaMessage, X as stream, Xt as DeltaExtractor, Y as mergeStreams, Yn as appLayoutSchema, Yt as markdown, Z as Store, Zt as StreamText, _ as FusorClient, a as Content, an as edit, at as ProjectProfile, b as FusorMessagesReturn, bt as unsend, c as fromVCard, cn as AddMember, ct as SlackTokenData, d as UnsupportedKind, dn as RemoveMember, dt as SubscriptionStatus, en as Group, et as FusorTokenData, f as Spectrum, fn as addMember, ft as TokenData, g as isFusorClient, gn as removeMember, gt as Voice, h as fusor, ht as EmojiKey, i as attachment, in as Edit, it as ProjectData, j as AnyPlatformDef, jn as ContactOrg, jt as reply, k as fusorEvent, kn as ContactInput, kt as Reply, l as toVCard, ln as LeaveSpace, lt as SpectrumCloudError, m as definePlatform, mn as leaveSpace, mt as Emoji, n as AttachmentInput, nn as group, nt as PlatformStatus, o as ContentBuilder, on as Message, ot as SharedTokenData, p as SpectrumInstance, pt as cloud, q as ManagedStream, qn as AppUrl, qt as Markdown, rt as PlatformsData, s as ContentInput, sn as Space, st as SlackTeamMeta, t as Attachment, tt as ImessageInfoData, u as UnsupportedError, un as MemberInput, ut as SubscriptionData, v as FusorMessages, vn as Reaction, vt as voice, w as FusorVerifyRequest, wn as custom, wt as text, x as FusorReply, xn as reaction, xt as Typing, y as FusorMessagesCtx, yn as ReactionBuilder, yt as Unsend, z as PlatformSpace, zn as AvatarInput, zt as Poll } from "./attachment-B4H1Q8xO.js";
2
- export { type AddMember, type AgentSender, type AnyPlatformDef, type App, type AppLayout, type AppUrl, type Attachment, type AttachmentInput, type Avatar, type AvatarData, type AvatarInput, type Broadcaster, type CloudPlatform, type Contact, type ContactAddress, type ContactDetails, type ContactEmail, type ContactInput, type ContactName, type ContactOrg, type ContactPhone, type Content, type ContentBuilder, type ContentInput, type DedicatedTokenData, type DeltaExtractor, type Edit, Emoji, type EmojiKey, type EventProducer, type FusorClient, type FusorEvent, type FusorMessages, type FusorMessagesCtx, type FusorMessagesReturn, type FusorReply, type FusorRespond, type FusorTokenData, type FusorVerify, type FusorVerifyRequest, type Group, type ImessageInfoData, type LeaveSpace, type ManagedStream, type Markdown, type MemberInput, type Message, type Platform, type PlatformDef, type PlatformInstance, type PlatformMessage, type PlatformProviderConfig, type PlatformRuntime, type PlatformSpace, type PlatformStatus, type PlatformUser, type PlatformsData, type Poll, type PollChoice, type PollChoiceInput, type PollOption, type ProjectData, type ProjectProfile, type ProviderMessage, type Reaction, type ReactionBuilder, type Read, type RemoveMember, type Rename, type Reply, type Richlink, type SchemaMessage, type SharedTokenData, type SlackTeamMeta, type SlackTokenData, type Space, type SpaceNamespace, Spectrum, SpectrumCloudError, type SpectrumInstance, type Store, type StreamText, type StreamTextSource, type SubscriptionData, type SubscriptionStatus, type TextStreamOptions, type TokenData, type Typing, type Unsend, UnsupportedError, type UnsupportedKind, type User, type Voice, type WebhookHandler, type WebhookRawRequest, type WebhookRawResult, addMember, app, appLayoutSchema, attachment, avatar, broadcast, cloud, contact, custom, definePlatform, edit, fromVCard, fusor, fusorEvent, group, isFusorClient, isFusorEvent, leaveSpace, markdown, mergeStreams, option, poll, reaction, read, removeMember, rename, reply, resolveContents, richlink, stream, text, toVCard, typing, unsend, voice };
1
+ import { $ as DedicatedTokenData, $t as TextStreamOptions, A as isFusorEvent, An as ContactName, B as PlatformUser, Bn as avatar, Bt as PollChoice, C as FusorVerify, D as WebhookRawResult, Dn as ContactDetails, Dt as richlink, E as WebhookRawRequest, En as ContactAddress, F as PlatformInstance, Fn as AgentSender, G as SpaceNamespace, Gn as App, Gt as option, Ht as PollOption, I as PlatformMessage, In as User, It as Read, J as broadcast, Jn as AppUrl, K as Broadcaster, Kn as AppLayout, Kt as poll, L as PlatformProviderConfig, Ln as Avatar, M as EventProducer, Mn as ContactPhone, N as Platform, Nt as Rename, O as FusorEvent, On as ContactEmail, Ot as resolveContents, P as PlatformDef, Pn as contact, Pt as rename, Q as CloudPlatform, Qt as StreamTextSource, R as PlatformRuntime, Rn as AvatarData, Rt as read, S as FusorRespond, St as typing, T as WebhookHandler, Tn as Contact, Tt as Richlink, V as ProviderMessage, Vt as PollChoiceInput, W as SchemaMessage, X as stream, Xn as appLayoutSchema, Xt as DeltaExtractor, Y as mergeStreams, Yn as app, Yt as markdown, Z as Store, Zt as StreamText, _ as FusorClient, a as Content, an as edit, at as ProjectProfile, b as FusorMessagesReturn, bt as unsend, c as fromVCard, cn as AddMember, ct as SlackTokenData, d as UnsupportedKind, dn as RemoveMember, dt as SubscriptionStatus, en as Group, et as FusorTokenData, f as Spectrum, fn as addMember, ft as TokenData, g as isFusorClient, gn as removeMember, gt as Voice, h as fusor, ht as EmojiKey, i as attachment, in as Edit, it as ProjectData, j as AnyPlatformDef, jn as ContactOrg, jt as reply, k as fusorEvent, kn as ContactInput, kt as Reply, l as toVCard, ln as LeaveSpace, lt as SpectrumCloudError, m as definePlatform, mn as leaveSpace, mt as Emoji, n as AttachmentInput, nn as group, nt as PlatformStatus, o as ContentBuilder, on as Message, ot as SharedTokenData, p as SpectrumInstance, pt as cloud, q as ManagedStream, qn as AppOptions, qt as Markdown, rt as PlatformsData, s as ContentInput, sn as Space, st as SlackTeamMeta, t as Attachment, tt as ImessageInfoData, u as UnsupportedError, un as MemberInput, ut as SubscriptionData, v as FusorMessages, vn as Reaction, vt as voice, w as FusorVerifyRequest, wn as custom, wt as text, x as FusorReply, xn as reaction, xt as Typing, y as FusorMessagesCtx, yn as ReactionBuilder, yt as Unsend, z as PlatformSpace, zn as AvatarInput, zt as Poll } from "./attachment-ChqzKngn.js";
2
+ export { type AddMember, type AgentSender, type AnyPlatformDef, type App, type AppLayout, type AppOptions, type AppUrl, type Attachment, type AttachmentInput, type Avatar, type AvatarData, type AvatarInput, type Broadcaster, type CloudPlatform, type Contact, type ContactAddress, type ContactDetails, type ContactEmail, type ContactInput, type ContactName, type ContactOrg, type ContactPhone, type Content, type ContentBuilder, type ContentInput, type DedicatedTokenData, type DeltaExtractor, type Edit, Emoji, type EmojiKey, type EventProducer, type FusorClient, type FusorEvent, type FusorMessages, type FusorMessagesCtx, type FusorMessagesReturn, type FusorReply, type FusorRespond, type FusorTokenData, type FusorVerify, type FusorVerifyRequest, type Group, type ImessageInfoData, type LeaveSpace, type ManagedStream, type Markdown, type MemberInput, type Message, type Platform, type PlatformDef, type PlatformInstance, type PlatformMessage, type PlatformProviderConfig, type PlatformRuntime, type PlatformSpace, type PlatformStatus, type PlatformUser, type PlatformsData, type Poll, type PollChoice, type PollChoiceInput, type PollOption, type ProjectData, type ProjectProfile, type ProviderMessage, type Reaction, type ReactionBuilder, type Read, type RemoveMember, type Rename, type Reply, type Richlink, type SchemaMessage, type SharedTokenData, type SlackTeamMeta, type SlackTokenData, type Space, type SpaceNamespace, Spectrum, SpectrumCloudError, type SpectrumInstance, type Store, type StreamText, type StreamTextSource, type SubscriptionData, type SubscriptionStatus, type TextStreamOptions, type TokenData, type Typing, type Unsend, UnsupportedError, type UnsupportedKind, type User, type Voice, type WebhookHandler, type WebhookRawRequest, type WebhookRawResult, addMember, app, appLayoutSchema, attachment, avatar, broadcast, cloud, contact, custom, definePlatform, edit, fromVCard, fusor, fusorEvent, group, isFusorClient, isFusorEvent, leaveSpace, markdown, mergeStreams, option, poll, reaction, read, removeMember, rename, reply, resolveContents, richlink, stream, text, toVCard, typing, unsend, voice };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { $ as avatar, A as addMemberSchema, C as reaction, D as option, F as asMarkdown, G as StreamConsumedError, H as asText, I as markdown, J as custom, K as drainStreamText, M as leaveSpaceSchema, N as removeMember, O as poll, P as removeMemberSchema, Q as toVCard, U as text, V as resolveContents, X as contact, Y as asContact, Z as fromVCard, a as contentAttrs, c as markdownToPlainText, et as avatarSchema, f as voice, g as reply, i as stream, it as attachment, j as leaveSpace, k as addMember, m as richlink, n as createAsyncQueue, o as errorAttrs, ot as fetchUrlBytes, q as asCustom, r as mergeStreams, rt as asAttachment, s as senderAttrs, st as tracedFetch, t as broadcast, u as classifyIdentifier, v as rename, x as read, y as renameSchema, z as group } from "./stream-DxI5ZiE2.js";
1
+ import { $ as contact, A as option, E as reaction, F as leaveSpaceSchema, G as asText, H as group, I as removeMember, J as StreamConsumedError, K as text, L as removeMemberSchema, M as addMember, N as addMemberSchema, P as leaveSpace, Q as asContact, R as asMarkdown, S as renameSchema, W as resolveContents, X as asCustom, Y as drainStreamText, Z as custom, _ as richlink, a as contentAttrs, c as markdownToPlainText, d as envAwareConfig, et as fromVCard, f as envFor, h as voice, i as stream, j as poll, lt as fetchUrlBytes, n as createAsyncQueue, nt as avatar, o as errorAttrs, ot as asAttachment, r as mergeStreams, rt as avatarSchema, s as senderAttrs, st as attachment, t as broadcast, tt as toVCard, u as classifyIdentifier, ut as tracedFetch, w as read, x as rename, y as reply, z as markdown } from "./stream-DAfZ5DFR.js";
2
2
  import z from "zod";
3
3
  import ogs from "open-graph-scraper";
4
4
  import { createLogger, setLogLevel, setupOtel, withSpan } from "@photon-ai/otel";
@@ -89,7 +89,8 @@ const layoutAccessor = z.function({
89
89
  const appSchema = z.object({
90
90
  type: z.literal("app"),
91
91
  url: urlAccessor,
92
- layout: layoutAccessor
92
+ layout: layoutAccessor,
93
+ live: z.boolean().optional()
93
94
  });
94
95
  const memoize = (factory) => {
95
96
  let cached;
@@ -136,7 +137,7 @@ const buildLayout = (metadata, url, image) => {
136
137
  * across `url()` / `layout()`; fetch / parse failures resolve to a host-only
137
138
  * caption (no throw, no retry).
138
139
  */
139
- const asApp = (url) => {
140
+ const asApp = (url, options = {}) => {
140
141
  const getUrl = resolveUrl(url);
141
142
  const getMetadata = memoize(async () => fetchLinkMetadata(await getUrl()));
142
143
  const getLayout = memoize(async () => {
@@ -154,11 +155,18 @@ const asApp = (url) => {
154
155
  return appSchema.parse({
155
156
  type: "app",
156
157
  url: getUrl,
157
- layout: getLayout
158
+ layout: getLayout,
159
+ ...options
158
160
  });
159
161
  };
160
- function app(url) {
161
- return { build: async () => asApp(url) };
162
+ /**
163
+ * Construct an app card from a URL.
164
+ *
165
+ * Pass `{ live: true }` to ask supported platforms to render the installed
166
+ * app extension's live UI. Other platforms retain their normal URL fallback.
167
+ */
168
+ function app(url, options = {}) {
169
+ return { build: async () => asApp(url, options) };
162
170
  }
163
171
  //#endregion
164
172
  //#region src/content/edit.ts
@@ -2990,7 +2998,8 @@ function definePlatform(name, rawDef) {
2990
2998
  const def = rawDef;
2991
2999
  const fullDef = {
2992
3000
  ...def,
2993
- name
3001
+ name,
3002
+ config: envAwareConfig(name, def.config)
2994
3003
  };
2995
3004
  const platformCache = /* @__PURE__ */ new WeakMap();
2996
3005
  const narrowSpectrum = (spectrum) => {
@@ -3042,7 +3051,7 @@ function definePlatform(name, rawDef) {
3042
3051
  }
3043
3052
  //#endregion
3044
3053
  //#region src/build-env.ts
3045
- const SPECTRUM_SDK_VERSION = "9.1.0";
3054
+ const SPECTRUM_SDK_VERSION = "9.3.1";
3046
3055
  //#endregion
3047
3056
  //#region src/utils/provider-packages.ts
3048
3057
  const OFFICIAL_PROVIDER_PACKAGES = {
@@ -4082,12 +4091,27 @@ function bootstrapTelemetry(opts) {
4082
4091
  function applyLogLevel(level) {
4083
4092
  if (level) setLogLevel(level);
4084
4093
  }
4094
+ function envValue(key) {
4095
+ const value = process.env[key];
4096
+ return value === "" ? void 0 : value;
4097
+ }
4098
+ function resolveProjectCredentials(options) {
4099
+ return {
4100
+ projectId: options.projectId ?? envValue(envFor("PROJECT", "ID")),
4101
+ projectSecret: options.projectSecret ?? envValue(envFor("PROJECT", "SECRET"))
4102
+ };
4103
+ }
4085
4104
  async function Spectrum(options) {
4086
- spectrumConfigSchema.parse(options);
4087
- const { projectId, projectSecret, providers, options: runtimeOptions, telemetry, webhookSecret } = options;
4105
+ const { projectId, projectSecret } = resolveProjectCredentials(options);
4106
+ spectrumConfigSchema.parse({
4107
+ ...options,
4108
+ projectId,
4109
+ projectSecret
4110
+ });
4111
+ const { providers, options: runtimeOptions, telemetry, webhookSecret } = options;
4088
4112
  const flattenGroups = runtimeOptions?.flattenGroups ?? false;
4089
4113
  applyLogLevel(runtimeOptions?.logLevel);
4090
- const resolvedWebhookSecret = webhookSecret ?? process.env.SPECTRUM_WEBHOOK_SECRET;
4114
+ const resolvedWebhookSecret = webhookSecret ?? process.env[envFor("WEBHOOK", "SECRET")];
4091
4115
  const otelHandle = telemetry ? bootstrapTelemetry({
4092
4116
  projectId,
4093
4117
  projectSecret
@@ -1158,6 +1158,128 @@ function voice(input, options) {
1158
1158
  } };
1159
1159
  }
1160
1160
  //#endregion
1161
+ //#region src/utils/env.ts
1162
+ /**
1163
+ * Build a Spectrum config env-var name from a channel and a key, centralizing
1164
+ * the `SPECTRUM_<CHANNEL>_<KEY>` convention so the prefix can't drift or be
1165
+ * mistyped per field. Both segments are joined verbatim (callers pass them
1166
+ * already upper-snake-cased), e.g. `envFor("TELEGRAM", "BOT_TOKEN")` →
1167
+ * `"SPECTRUM_TELEGRAM_BOT_TOKEN"`.
1168
+ */
1169
+ const envFor = (channel, key) => `SPECTRUM_${channel}_${key}`;
1170
+ /**
1171
+ * Wrap a config-field schema so it falls back to an environment variable when
1172
+ * the field is omitted. Precedence is **explicit value > env var > the inner
1173
+ * schema's own default/required check**:
1174
+ *
1175
+ * - An explicit value (anything other than `undefined`) is passed straight
1176
+ * through — a caller-supplied config always wins over the environment.
1177
+ * - When the field is `undefined`, `process.env[envKey]` is substituted. An
1178
+ * env var set to the empty string is treated as unset (a common deploy
1179
+ * footgun) so it doesn't satisfy a `.min(1)` by accident.
1180
+ * - The inner `schema` then validates the resolved value, keeping its exact
1181
+ * semantics — regex, `.min(1)`, `.url()`, `.optional()`, `.default(...)`.
1182
+ * When both the field and the env var are absent, a required inner schema
1183
+ * raises its normal "required" error.
1184
+ *
1185
+ * The output type is the inner schema's output type, so call sites are
1186
+ * unchanged. Because substitution only happens on `undefined`, wrapping a
1187
+ * field inside a `z.object` leaves it a plain key on the parsed result — union
1188
+ * discriminators like `"accessToken" in config` keep working.
1189
+ *
1190
+ * @example
1191
+ * ```ts
1192
+ * botToken: fromEnv("SPECTRUM_TELEGRAM_BOT_TOKEN", z.string().regex(BOT_TOKEN_PATTERN)),
1193
+ * ```
1194
+ */
1195
+ const fromEnv = (envKey, schema) => z.preprocess((value) => {
1196
+ if (value !== void 0) return value;
1197
+ const envValue = process.env[envKey];
1198
+ return envValue === "" ? void 0 : envValue;
1199
+ }, schema);
1200
+ /**
1201
+ * Normalize a platform id into the env-var prefix segment: upper-case, with any
1202
+ * run of non-alphanumeric characters collapsed to a single `_`. This is a
1203
+ * whole-name transform, NOT camelCase splitting, so it reproduces the prefixes
1204
+ * the adapters used by hand — `"telegram"` → `TELEGRAM`,
1205
+ * `"WhatsApp Business"` → `WHATSAPP_BUSINESS`, `"Slack"` → `SLACK`.
1206
+ */
1207
+ const NON_ALPHANUMERIC_RUN = /[^A-Z0-9]+/;
1208
+ const normalizePlatformName = (name) => name.toUpperCase().split(NON_ALPHANUMERIC_RUN).filter(Boolean).join("_");
1209
+ const CAMEL_BOUNDARY = /([a-z0-9])([A-Z])/g;
1210
+ /** Convert a camelCase config field name into its UPPER_SNAKE env-var suffix. */
1211
+ const toEnvKey = (field) => field.replace(CAMEL_BOUNDARY, "$1_$2").toUpperCase();
1212
+ const INNER_TYPE_WRAPPERS = new Set([
1213
+ "optional",
1214
+ "nullable",
1215
+ "default",
1216
+ "readonly",
1217
+ "catch",
1218
+ "nonoptional",
1219
+ "prefault"
1220
+ ]);
1221
+ const asInternal = (schema) => schema;
1222
+ const unwrapSchema = (schema) => {
1223
+ let current = asInternal(schema);
1224
+ for (let depth = 0; depth < 20; depth++) {
1225
+ const { type } = current.def;
1226
+ if (INNER_TYPE_WRAPPERS.has(type) && current.def.innerType) {
1227
+ current = current.def.innerType;
1228
+ continue;
1229
+ }
1230
+ if (type === "pipe" && (current.def.out || current.def.in)) {
1231
+ current = current.def.out ?? current.def.in;
1232
+ continue;
1233
+ }
1234
+ break;
1235
+ }
1236
+ return current;
1237
+ };
1238
+ const isStringLeaf = (schema) => unwrapSchema(schema).def.type === "string";
1239
+ const isStrictObject = (def) => def.catchall?.def.type === "never";
1240
+ /**
1241
+ * Rewrite a platform's config schema so every string-leaf field falls back to
1242
+ * `SPECTRUM_<PLATFORM>_<KEY>` when omitted which is the equivalent of
1243
+ * hand-wrapping each field with {@link fromEnv}. Called once by
1244
+ * `definePlatform`, so adapters declare plain Zod and get env fallback for free.
1245
+ *
1246
+ * - **object**: each string-leaf field is wrapped with `fromEnv`; non-string
1247
+ * fields (records, arrays, nested objects, booleans, numbers) are left as-is.
1248
+ * The object is only reconstructed when a field actually changed, and
1249
+ * `.strict()` is preserved so an empty strict "cloud" branch is untouched.
1250
+ * - **union**: each option is rewritten independently, so an env value only
1251
+ * satisfies the branch that declares that field (a direct/cloud union stays
1252
+ * correctly discriminated).
1253
+ * - anything else is returned unchanged.
1254
+ *
1255
+ * The output type is identical to the input schema's. Only omitted fields gain
1256
+ * an env fallback which means that `z.infer` and downstream contexts are unaffected.
1257
+ */
1258
+ const envAwareConfig = (name, schema) => {
1259
+ return rewrite(`SPECTRUM_${normalizePlatformName(name)}`, schema);
1260
+ };
1261
+ const rewrite = (prefix, schema) => {
1262
+ const { def } = asInternal(schema);
1263
+ if (def.type === "object" && def.shape) return rewriteObject(prefix, def, schema);
1264
+ if (def.type === "union" && def.options) {
1265
+ const options = def.options.map((option) => rewrite(prefix, option));
1266
+ return z.union(options);
1267
+ }
1268
+ return schema;
1269
+ };
1270
+ const rewriteObject = (prefix, def, original) => {
1271
+ const shape = def.shape;
1272
+ const nextShape = {};
1273
+ let changed = false;
1274
+ for (const [key, field] of Object.entries(shape)) if (isStringLeaf(field)) {
1275
+ nextShape[key] = fromEnv(`${prefix}_${toEnvKey(key)}`, field);
1276
+ changed = true;
1277
+ } else nextShape[key] = field;
1278
+ if (!changed) return original;
1279
+ const rebuilt = z.object(nextShape);
1280
+ return isStrictObject(def) ? rebuilt.strict() : rebuilt;
1281
+ };
1282
+ //#endregion
1161
1283
  //#region src/utils/identifier.ts
1162
1284
  const PHONE_LIKE = /^\+?[\d\s()\-.]{7,}$/;
1163
1285
  const EMAIL_LIKE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -1576,4 +1698,4 @@ function broadcast(source) {
1576
1698
  };
1577
1699
  }
1578
1700
  //#endregion
1579
- export { avatar as $, addMemberSchema as A, groupSchema as B, reaction as C, option as D, asPollOption as E, asMarkdown as F, StreamConsumedError as G, asText as H, markdown as I, custom as J, drainStreamText as K, markdownSchema as L, leaveSpaceSchema as M, removeMember as N, poll as O, removeMemberSchema as P, toVCard as Q, asGroup as R, asReaction as S, asPoll as T, text as U, resolveContents as V, textSchema as W, contact as X, asContact as Y, fromVCard as Z, replySchema as _, contentAttrs as a, attachmentSchema as at, asRead as b, markdownToPlainText as c, asVoice as d, avatarSchema as et, voice as f, reply as g, asReply as h, stream as i, attachment as it, leaveSpace as j, addMember as k, renderInlineTokens as l, richlink as m, createAsyncQueue as n, photoActionSchema as nt, errorAttrs as o, fetchUrlBytes as ot, asRichlink as p, asCustom as q, mergeStreams as r, asAttachment as rt, senderAttrs as s, tracedFetch as st, broadcast as t, buildPhotoAction as tt, classifyIdentifier as u, rename as v, reactionSchema as w, read as x, renameSchema as y, group as z };
1701
+ export { contact as $, option as A, markdownSchema as B, asRead as C, reactionSchema as D, reaction as E, leaveSpaceSchema as F, asText as G, group as H, removeMember as I, StreamConsumedError as J, text as K, removeMemberSchema as L, addMember as M, addMemberSchema as N, asPoll as O, leaveSpace as P, asContact as Q, asMarkdown as R, renameSchema as S, asReaction as T, groupSchema as U, asGroup as V, resolveContents as W, asCustom as X, drainStreamText as Y, custom as Z, richlink as _, contentAttrs as a, photoActionSchema as at, replySchema as b, markdownToPlainText as c, attachmentSchema as ct, envAwareConfig as d, fromVCard as et, envFor as f, asRichlink as g, voice as h, stream as i, buildPhotoAction as it, poll as j, asPollOption as k, renderInlineTokens as l, fetchUrlBytes as lt, asVoice as m, createAsyncQueue as n, avatar as nt, errorAttrs as o, asAttachment as ot, fromEnv as p, textSchema as q, mergeStreams as r, avatarSchema as rt, senderAttrs as s, attachment as st, broadcast as t, toVCard as tt, classifyIdentifier as u, tracedFetch as ut, asReply as v, read as w, rename as x, reply as y, markdown as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spectrum-ts/core",
3
- "version": "9.1.0",
3
+ "version": "9.3.1",
4
4
  "description": "The spectrum-ts runtime — Spectrum, content builders, fusor, and the provider authoring API.",
5
5
  "repository": {
6
6
  "type": "git",