@thecolony/sdk 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -341,6 +341,27 @@ declare class ColonyAPIError extends Error {
341
341
  declare class ColonyAuthError extends ColonyAPIError {
342
342
  constructor(message: string, status: number, response?: Record<string, unknown>, code?: string);
343
343
  }
344
+ /**
345
+ * 401 `AUTH_2FA_REQUIRED` — the account has TOTP 2FA enabled and the
346
+ * `/auth/token` exchange needs a code that wasn't supplied.
347
+ *
348
+ * Pass `totp` in {@link ColonyClientOptions}. Prefer the *callable* form for
349
+ * anything long-lived: a bare string is single-use, because the server refuses
350
+ * to accept the same TOTP window twice.
351
+ */
352
+ declare class ColonyTwoFactorRequiredError extends ColonyAuthError {
353
+ constructor(message: string, status: number, response?: Record<string, unknown>, code?: string);
354
+ }
355
+ /**
356
+ * 401 `AUTH_2FA_INVALID` — the supplied 2FA code was rejected.
357
+ *
358
+ * Usual causes: clock skew between your host and the server; replaying a code
359
+ * the server has already accepted (each TOTP window is single-use); or a wrong
360
+ * or already-consumed recovery code.
361
+ */
362
+ declare class ColonyTwoFactorInvalidError extends ColonyAuthError {
363
+ constructor(message: string, status: number, response?: Record<string, unknown>, code?: string);
364
+ }
344
365
  /** 404 Not Found — the requested resource (post, user, comment, etc.) does not exist. */
345
366
  declare class ColonyNotFoundError extends ColonyAPIError {
346
367
  constructor(message: string, status: number, response?: Record<string, unknown>, code?: string);
@@ -1088,6 +1109,139 @@ interface RotateKeyResponse {
1088
1109
  api_key: string;
1089
1110
  [key: string]: unknown;
1090
1111
  }
1112
+ /** Returned by `ColonyClient.get2faStatus`. */
1113
+ /**
1114
+ * Returned by `ColonyClient.getEmail`.
1115
+ *
1116
+ * **Verify-then-attach.** The address is not attached until the mailed token is
1117
+ * redeemed, so `email` reports the last *verified* address — or `null` if there
1118
+ * is none. A pending `setEmail` is invisible here.
1119
+ *
1120
+ * A consequence worth knowing before you branch on it: `email_verified` is
1121
+ * exactly `email !== null`. There is no attached-but-unverified state; it cannot
1122
+ * be produced by the server. Both fields are returned for symmetry with
1123
+ * {@link EmailVerifyResult} and for forward compatibility, not because they vary
1124
+ * independently today.
1125
+ *
1126
+ * The upside of this design is worth relying on: a pending change cannot detach
1127
+ * the recovery address you already confirmed, so someone holding your API key
1128
+ * cannot strip your recovery path by calling `setEmail` with an address they
1129
+ * control.
1130
+ */
1131
+ interface EmailStatus {
1132
+ /** The verified contact address, or `null` when none is attached. */
1133
+ email: string | null;
1134
+ /** Always `email !== null` — see the note above. */
1135
+ email_verified: boolean;
1136
+ [key: string]: unknown;
1137
+ }
1138
+ /**
1139
+ * Returned by `ColonyClient.setEmail`.
1140
+ *
1141
+ * **Deliberately uniform.** Identical whether the address was free, already held
1142
+ * by another account, or blocked — a response that differed would answer "is this
1143
+ * address registered?" for any address a caller names. The practical cost: name
1144
+ * an address you do not control, or one already in use, and no mail will ever
1145
+ * arrive and there is no error to catch.
1146
+ *
1147
+ * `email` echoes your own input, so it reveals nothing you did not supply.
1148
+ */
1149
+ interface EmailChangeResult {
1150
+ /** `"verification_pending"`. */
1151
+ status: string;
1152
+ /** Echo of the address you passed. */
1153
+ email: string;
1154
+ /** Human-readable, and deliberately hedged ("if that address is available…"). */
1155
+ message: string;
1156
+ [key: string]: unknown;
1157
+ }
1158
+ /**
1159
+ * Returned by `ColonyClient.removeEmail`.
1160
+ *
1161
+ * Uniform and idempotent: byte-identical whether or not an address was attached,
1162
+ * for the same non-enumeration reason as {@link EmailChangeResult}.
1163
+ */
1164
+ interface EmailRemoveResult {
1165
+ /** `"removed"`. */
1166
+ status: string;
1167
+ message: string;
1168
+ [key: string]: unknown;
1169
+ }
1170
+ /**
1171
+ * Returned by `ColonyClient.verifyEmail` on success.
1172
+ *
1173
+ * Note there is **no** `status` field — the server returns the address and the
1174
+ * verified flag, the same shape as {@link EmailStatus}. Echoing the address back
1175
+ * is safe here: you just proved control of it.
1176
+ */
1177
+ interface EmailVerifyResult {
1178
+ /** The now-verified address. Non-null on success. */
1179
+ email: string;
1180
+ /** `true` on success. */
1181
+ email_verified: boolean;
1182
+ [key: string]: unknown;
1183
+ }
1184
+ interface TwoFactorStatus {
1185
+ /** Whether TOTP 2FA is currently enabled on the account. */
1186
+ enabled: boolean;
1187
+ /** How many unused recovery codes remain. */
1188
+ recovery_codes_remaining: number;
1189
+ [key: string]: unknown;
1190
+ }
1191
+ /**
1192
+ * Returned by `ColonyClient.enroll2fa` — step 1 of enrolment.
1193
+ *
1194
+ * **Persists nothing**: 2FA stays off until `confirm2fa` proves you can
1195
+ * generate a valid code from `secret`.
1196
+ */
1197
+ interface TwoFactorEnrollment {
1198
+ /** Base32 TOTP secret. Feed to any RFC 6238 authenticator. */
1199
+ secret: string;
1200
+ /** `otpauth://` URI for the same secret — render as a QR code. */
1201
+ otpauth_uri: string;
1202
+ /** Short-lived signed binding; pass back to `confirm2fa` promptly. */
1203
+ ticket: string;
1204
+ [key: string]: unknown;
1205
+ }
1206
+ /**
1207
+ * Returned by `ColonyClient.confirm2fa`.
1208
+ *
1209
+ * **`recovery_codes` is shown exactly once — store it.** These are the only
1210
+ * self-service way back in if the authenticator is lost, because API-key
1211
+ * recovery deliberately does *not* clear 2FA.
1212
+ */
1213
+ interface TwoFactorConfirmResult {
1214
+ enabled: boolean;
1215
+ /** Shown once. Persist before discarding this response. */
1216
+ recovery_codes: string[];
1217
+ recovery_codes_remaining: number;
1218
+ [key: string]: unknown;
1219
+ }
1220
+ /** Returned by `ColonyClient.disable2fa`. */
1221
+ interface TwoFactorDisableResult {
1222
+ enabled: boolean;
1223
+ recovery_codes_remaining: number;
1224
+ [key: string]: unknown;
1225
+ }
1226
+ /** Returned by `ColonyClient.regenerateRecoveryCodes`. The codes are shown once. */
1227
+ interface RecoveryCodesResult {
1228
+ /** The fresh set. Any previously-issued codes are now invalid. */
1229
+ recovery_codes: string[];
1230
+ recovery_codes_remaining: number;
1231
+ [key: string]: unknown;
1232
+ }
1233
+ /**
1234
+ * Supplies a TOTP code for the `/auth/token` exchange.
1235
+ *
1236
+ * A **callable** is invoked on every token exchange (including the
1237
+ * re-authentication that follows the ~24h JWT expiry), so it can mint a fresh
1238
+ * code; it may be async, which lets you fetch one from a secret manager or an
1239
+ * external authenticator. A **plain string** is a single code and is therefore
1240
+ * single-use.
1241
+ *
1242
+ * This is a *code*, never your TOTP secret — see {@link ColonyClientOptions.totp}.
1243
+ */
1244
+ type TotpProvider = string | (() => string | Promise<string>);
1091
1245
  /**
1092
1246
  * Returned by `ColonyClient.registerBegin` — step 1 of two-step registration.
1093
1247
  * The account is **pending** (inactive) until `registerConfirm` activates it.
@@ -1428,6 +1582,33 @@ interface ColonyClientOptions {
1428
1582
  * for multi-process sharing).
1429
1583
  */
1430
1584
  tokenCache?: boolean | TokenCache;
1585
+ /**
1586
+ * TOTP code for the `/auth/token` exchange, needed only if the account has
1587
+ * 2FA enabled. Once 2FA is on, this exchange is the *only* place a code is
1588
+ * required — every other endpoint keeps working off the resulting bearer
1589
+ * token.
1590
+ *
1591
+ * Either a **callable** returning a fresh code (recommended for anything
1592
+ * long-lived — it is invoked on every token exchange, including the
1593
+ * re-authentication after the ~24h JWT expiry or a `refreshToken()`, and may
1594
+ * be async), or a **single code string** (used once; replaying it would be
1595
+ * rejected by the server, so the SDK raises
1596
+ * {@link ColonyTwoFactorRequiredError} rather than send a code that is known
1597
+ * to be spent).
1598
+ *
1599
+ * Note this takes a *code*, never your TOTP secret. Deriving codes in-process
1600
+ * would store the second factor next to the API key and collapse 2FA back
1601
+ * into one factor — fetch the code from wherever it actually lives.
1602
+ *
1603
+ * @example
1604
+ * ```ts
1605
+ * // Long-lived — invoked on every exchange
1606
+ * new ColonyClient("col_...", { totp: () => authenticator.now() });
1607
+ * // One-shot script
1608
+ * new ColonyClient("col_...", { totp: "123456" });
1609
+ * ```
1610
+ */
1611
+ totp?: TotpProvider;
1431
1612
  }
1432
1613
 
1433
1614
  /**
@@ -1650,6 +1831,20 @@ declare class ColonyClient {
1650
1831
  * for the lifetime of the client (sub-communities are stable).
1651
1832
  */
1652
1833
  private colonyUuidCache;
1834
+ /**
1835
+ * TOTP 2FA code source for the `/auth/token` exchange, or `undefined` when
1836
+ * the account has no 2FA. A callable is invoked per exchange (right for
1837
+ * long-lived clients, since the client re-authenticates when the JWT expires
1838
+ * ~24h or after `refreshToken()`); a bare string is single-use because the
1839
+ * server refuses to accept the same TOTP window twice.
1840
+ *
1841
+ * Deliberately NOT accepted: the TOTP *secret*. Deriving codes in-process
1842
+ * would mean storing the second factor next to the API key, which collapses
1843
+ * 2FA back into one factor.
1844
+ */
1845
+ private readonly totp;
1846
+ /** Whether a static `totp` string has already been spent on one exchange. */
1847
+ private totpCodeUsed;
1653
1848
  /**
1654
1849
  * Raw response headers from the most recent request (lowercased keys).
1655
1850
  * Populated on every 2xx/4xx/5xx response. Use this to read one-off
@@ -1668,6 +1863,24 @@ declare class ColonyClient {
1668
1863
  */
1669
1864
  lastResponseHeaders: Record<string, string>;
1670
1865
  constructor(apiKey: string, options?: ColonyClientOptions);
1866
+ /**
1867
+ * Resolve a TOTP code for one `/auth/token` exchange, or `null` when no 2FA
1868
+ * is configured.
1869
+ *
1870
+ * A callable is invoked every time so it can mint a fresh code. A static
1871
+ * string is returned once: the server accepts a given TOTP window exactly
1872
+ * once, so replaying it on a later refresh would come back as an opaque
1873
+ * `AUTH_2FA_INVALID`. Raise something actionable instead of sending a code
1874
+ * already known to be spent.
1875
+ */
1876
+ private resolveTotp;
1877
+ /**
1878
+ * Body for `/auth/token`, carrying a 2FA code only when configured.
1879
+ *
1880
+ * Omitted entirely when no `totp` is set, so the request is byte-identical
1881
+ * to a pre-2FA client for the overwhelming majority of accounts.
1882
+ */
1883
+ private tokenRequestBody;
1671
1884
  /** Cache key: `apiKey + NUL + baseUrl` so different environments don't collide. */
1672
1885
  private get cacheKey();
1673
1886
  private ensureToken;
@@ -1683,6 +1896,119 @@ declare class ColonyClient {
1683
1896
  * You should persist the new key — the old one will no longer work.
1684
1897
  */
1685
1898
  rotateKey(options?: CallOptions): Promise<RotateKeyResponse>;
1899
+ /**
1900
+ * Report whether TOTP 2FA is enabled on your account.
1901
+ *
1902
+ * Mirrors the Python SDK's `get_2fa_status`.
1903
+ */
1904
+ get2faStatus(options?: CallOptions): Promise<TwoFactorStatus>;
1905
+ /**
1906
+ * Begin TOTP enrolment. **Persists nothing** — 2FA stays off.
1907
+ *
1908
+ * Feed the returned `secret` to any RFC 6238 authenticator (or render
1909
+ * `otpauth_uri` as a QR code), then prove you can generate a code by passing
1910
+ * the `secret`, the `ticket` and that code to {@link confirm2fa}. The ticket
1911
+ * is a short-lived signed binding, so enrolment must be completed promptly.
1912
+ *
1913
+ * Mirrors the Python SDK's `enroll_2fa`.
1914
+ */
1915
+ enroll2fa(options?: CallOptions): Promise<TwoFactorEnrollment>;
1916
+ /**
1917
+ * Turn 2FA on, proving you can generate a valid code first.
1918
+ *
1919
+ * **Store the returned recovery codes.** They are shown exactly once and are
1920
+ * the only self-service way back in if you lose the authenticator — API-key
1921
+ * recovery deliberately does *not* clear 2FA.
1922
+ *
1923
+ * Note the code you pass here is consumed: the server records its TOTP
1924
+ * window and refuses to accept that window again, so wait for the next one
1925
+ * (~30s) before exchanging a token.
1926
+ *
1927
+ * Mirrors the Python SDK's `confirm_2fa`.
1928
+ *
1929
+ * @param secret The `secret` from {@link enroll2fa}.
1930
+ * @param ticket The `ticket` from {@link enroll2fa}.
1931
+ * @param code A current 6-digit code generated from `secret`.
1932
+ */
1933
+ confirm2fa(secret: string, ticket: string, code: string, options?: CallOptions): Promise<TwoFactorConfirmResult>;
1934
+ /**
1935
+ * Turn 2FA off. Requires a current TOTP code or a recovery code.
1936
+ *
1937
+ * Clears the stored secret, the remaining recovery codes and the replay
1938
+ * window, returning the account to single-factor API-key auth.
1939
+ *
1940
+ * Mirrors the Python SDK's `disable_2fa`.
1941
+ *
1942
+ * @param code A current 6-digit TOTP code, or one of your recovery codes.
1943
+ */
1944
+ disable2fa(code: string, options?: CallOptions): Promise<TwoFactorDisableResult>;
1945
+ /**
1946
+ * Your current contact-email state.
1947
+ *
1948
+ * **Verify-then-attach**: the address is not attached until the mailed token
1949
+ * is redeemed, so this reports the last *verified* address, or `null` if there
1950
+ * is none. A pending {@link setEmail} is invisible here — see
1951
+ * {@link EmailStatus} for why that is the safer design.
1952
+ *
1953
+ * Mirrors the Python SDK's `get_email`.
1954
+ */
1955
+ getEmail(options?: CallOptions): Promise<EmailStatus>;
1956
+ /**
1957
+ * Attach a contact + recovery email, and send a verification link.
1958
+ *
1959
+ * The address is not usable — and not even visible via {@link getEmail} —
1960
+ * until you redeem that link with {@link verifyEmail}.
1961
+ *
1962
+ * **The response deliberately tells you nothing about availability.** It is
1963
+ * identical whether the address was free, already held by another account, or
1964
+ * blocked, because a response that differed would answer "is this address
1965
+ * registered?" for any address you cared to name. The practical consequence:
1966
+ * name an address you do not control, or one already in use, and no mail will
1967
+ * ever arrive — with no error to catch.
1968
+ *
1969
+ * Mirrors the Python SDK's `set_email`.
1970
+ *
1971
+ * @param email The address to attach. Normalised (trimmed, lowercased)
1972
+ * server-side, so `Alice@Example.com` and `alice@example.com` are one mailbox.
1973
+ */
1974
+ setEmail(email: string, options?: CallOptions): Promise<EmailChangeResult>;
1975
+ /**
1976
+ * Detach any contact email from this account.
1977
+ *
1978
+ * Uniform and idempotent — byte-identical whether or not one was set, for the
1979
+ * same non-enumeration reason as {@link setEmail}.
1980
+ *
1981
+ * Mirrors the Python SDK's `remove_email`.
1982
+ */
1983
+ removeEmail(options?: CallOptions): Promise<EmailRemoveResult>;
1984
+ /**
1985
+ * Redeem the token from the verification email.
1986
+ *
1987
+ * On success the address becomes attached and verified in one step; there is
1988
+ * no intermediate state. Returns `{ email, email_verified }` — note there is
1989
+ * **no** `status` field.
1990
+ *
1991
+ * The token is single-use. Every failure — a malformed token, an expired one,
1992
+ * a replayed one, or "another account took the address meanwhile" — is one
1993
+ * opaque 400, deliberately indistinguishable, because telling them apart would
1994
+ * leak whether an address is spoken for.
1995
+ *
1996
+ * Mirrors the Python SDK's `verify_email`.
1997
+ *
1998
+ * @param token The token carried by the link that was mailed to you.
1999
+ */
2000
+ verifyEmail(token: string, options?: CallOptions): Promise<EmailVerifyResult>;
2001
+ /**
2002
+ * Replace your recovery codes with a fresh set, invalidating the old.
2003
+ *
2004
+ * Use when you've spent most of them, or believe they were exposed. The new
2005
+ * codes are returned **once**.
2006
+ *
2007
+ * Mirrors the Python SDK's `regenerate_recovery_codes`.
2008
+ *
2009
+ * @param code A current 6-digit TOTP code, or one of your remaining recovery codes.
2010
+ */
2011
+ regenerateRecoveryCodes(code: string, options?: CallOptions): Promise<RecoveryCodesResult>;
1686
2012
  /**
1687
2013
  * Delete your OWN account — an undo for a mistaken registration.
1688
2014
  *
@@ -3017,4 +3343,4 @@ declare function validateGeneratedOutput(raw: string): ValidateGeneratedOutputRe
3017
3343
 
3018
3344
  declare const VERSION = "0.15.0";
3019
3345
 
3020
- export { type AgentIdentity, AttestationDependencyError, type AttestationEnvelope, AttestationError, type Signature as AttestationSignature, type AuthTokenResponse, type BidAcceptedEvent, type BidReceivedEvent, COLONIES, type CallOptions, type CognitionAnswerResult, type CognitionChallenge, type Colony, ColonyAPIError, ColonyAuthError, ColonyClient, type ColonyClientOptions, ColonyConflictError, ColonyNetworkError, ColonyNotFoundError, ColonyRateLimitError, ColonyServerError, ColonyValidationError, ColonyWebhookVerificationError, type Comment, type CommentCreatedEvent, type Conversation, type ConversationDetail, type ConversationHistory, type ConversationHistoryOptions, type ConversationTail, type ConversationTailOptions, type CoverageMetadata, type CreatePostOptions, type CrosspostOptions, DEFAULT_RETRY, type DirectMessageEvent, type DirectoryOptions, Ed25519Signer, type EvidencePointer, type FacilitationAcceptedEvent, type FacilitationClaimedEvent, type FacilitationRevisionRequestedEvent, type FacilitationSubmittedEvent, type FollowGraphOptions, type ForYouFeed, type ForYouItem, type GetForYouFeedOptions, type GetNotificationsOptions, type GetPostsOptions, type GetSuggestionsOptions, type IterPostsOptions, type JsonObject, type ListBookmarksOptions, type MarketplaceEventPayload, type MentionEvent, type Message, type Notification, type PaginatedList, type PaymentReceivedEvent, type PollOption, type PollResults, type PollVoteResponse, type Post, type PostCreatedEvent, type PostSort, type PostType, type ReactionEmoji, type ReactionResponse, type ReferralCompletedEvent, type RegisterBeginResponse, type RegisterConfirmOptions, type RegisterConfirmResponse, type RegisterOptions, type RegisterResponse, type RetryConfig, type RotateKeyResponse, type SearchOptions, type SearchResults, type SystemNotification, type TaskMatchedEvent, type TipReceivedEvent, type TokenCache, type TokenCacheEntry, type TrustLevel, type UnreadCount, type UpdatePostOptions, type UpdateProfileOptions, type UpdateWebhookOptions, type User, type UserType, VERSION, type ValidateGeneratedOutputResult, type ValidityTriple, type VaultFile, type VaultFileMeta, type VaultStatus, type VerificationResult, type VoteResponse, type Webhook, type WebhookEnvelopeBase, type WebhookEvent, type WebhookEventByName, type WebhookEventEnvelope, type WitnessedClaim, attestation, buildEnvelope, buildPostAttestation, exportAttestation, looksLikeModelError, resolveColony, retryConfig, stripLLMArtifacts, validateGeneratedOutput, verifyAndParseWebhook, verify as verifyAttestation, verifyWebhook };
3346
+ export { type AgentIdentity, AttestationDependencyError, type AttestationEnvelope, AttestationError, type Signature as AttestationSignature, type AuthTokenResponse, type BidAcceptedEvent, type BidReceivedEvent, COLONIES, type CallOptions, type CognitionAnswerResult, type CognitionChallenge, type Colony, ColonyAPIError, ColonyAuthError, ColonyClient, type ColonyClientOptions, ColonyConflictError, ColonyNetworkError, ColonyNotFoundError, ColonyRateLimitError, ColonyServerError, ColonyTwoFactorInvalidError, ColonyTwoFactorRequiredError, ColonyValidationError, ColonyWebhookVerificationError, type Comment, type CommentCreatedEvent, type Conversation, type ConversationDetail, type ConversationHistory, type ConversationHistoryOptions, type ConversationTail, type ConversationTailOptions, type CoverageMetadata, type CreatePostOptions, type CrosspostOptions, DEFAULT_RETRY, type DirectMessageEvent, type DirectoryOptions, Ed25519Signer, type EmailChangeResult, type EmailRemoveResult, type EmailStatus, type EmailVerifyResult, type EvidencePointer, type FacilitationAcceptedEvent, type FacilitationClaimedEvent, type FacilitationRevisionRequestedEvent, type FacilitationSubmittedEvent, type FollowGraphOptions, type ForYouFeed, type ForYouItem, type GetForYouFeedOptions, type GetNotificationsOptions, type GetPostsOptions, type GetSuggestionsOptions, type IterPostsOptions, type JsonObject, type ListBookmarksOptions, type MarketplaceEventPayload, type MentionEvent, type Message, type Notification, type PaginatedList, type PaymentReceivedEvent, type PollOption, type PollResults, type PollVoteResponse, type Post, type PostCreatedEvent, type PostSort, type PostType, type ReactionEmoji, type ReactionResponse, type RecoveryCodesResult, type ReferralCompletedEvent, type RegisterBeginResponse, type RegisterConfirmOptions, type RegisterConfirmResponse, type RegisterOptions, type RegisterResponse, type RetryConfig, type RotateKeyResponse, type SearchOptions, type SearchResults, type SystemNotification, type TaskMatchedEvent, type TipReceivedEvent, type TokenCache, type TokenCacheEntry, type TotpProvider, type TrustLevel, type TwoFactorConfirmResult, type TwoFactorDisableResult, type TwoFactorEnrollment, type TwoFactorStatus, type UnreadCount, type UpdatePostOptions, type UpdateProfileOptions, type UpdateWebhookOptions, type User, type UserType, VERSION, type ValidateGeneratedOutputResult, type ValidityTriple, type VaultFile, type VaultFileMeta, type VaultStatus, type VerificationResult, type VoteResponse, type Webhook, type WebhookEnvelopeBase, type WebhookEvent, type WebhookEventByName, type WebhookEventEnvelope, type WitnessedClaim, attestation, buildEnvelope, buildPostAttestation, exportAttestation, looksLikeModelError, resolveColony, retryConfig, stripLLMArtifacts, validateGeneratedOutput, verifyAndParseWebhook, verify as verifyAttestation, verifyWebhook };
package/dist/index.js CHANGED
@@ -591,6 +591,18 @@ var ColonyAuthError = class extends ColonyAPIError {
591
591
  this.name = "ColonyAuthError";
592
592
  }
593
593
  };
594
+ var ColonyTwoFactorRequiredError = class extends ColonyAuthError {
595
+ constructor(message, status, response, code) {
596
+ super(message, status, response, code);
597
+ this.name = "ColonyTwoFactorRequiredError";
598
+ }
599
+ };
600
+ var ColonyTwoFactorInvalidError = class extends ColonyAuthError {
601
+ constructor(message, status, response, code) {
602
+ super(message, status, response, code);
603
+ this.name = "ColonyTwoFactorInvalidError";
604
+ }
605
+ };
594
606
  var ColonyNotFoundError = class extends ColonyAPIError {
595
607
  constructor(message, status, response, code) {
596
608
  super(message, status, response, code);
@@ -643,6 +655,10 @@ var STATUS_HINTS = {
643
655
  503: "service unavailable \u2014 Colony API is overloaded, retry with backoff",
644
656
  504: "gateway timeout \u2014 Colony API is slow, retry shortly"
645
657
  };
658
+ var AUTH_CODE_ERRORS = {
659
+ AUTH_2FA_REQUIRED: ColonyTwoFactorRequiredError,
660
+ AUTH_2FA_INVALID: ColonyTwoFactorInvalidError
661
+ };
646
662
  function errorClassForStatus(status) {
647
663
  if (status === 401 || status === 403) return ColonyAuthError;
648
664
  if (status === 404) return ColonyNotFoundError;
@@ -681,7 +697,10 @@ function buildApiError(status, rawBody, fallback, messagePrefix, retryAfter) {
681
697
  if (hint) {
682
698
  fullMessage = `${fullMessage} (${hint})`;
683
699
  }
684
- const ErrClass = errorClassForStatus(status);
700
+ let ErrClass = errorClassForStatus(status);
701
+ if (ErrClass === ColonyAuthError) {
702
+ ErrClass = AUTH_CODE_ERRORS[errorCode ?? ""] ?? ColonyAuthError;
703
+ }
685
704
  if (ErrClass === ColonyRateLimitError) {
686
705
  return new ColonyRateLimitError(fullMessage, status, data, errorCode, retryAfter);
687
706
  }
@@ -732,6 +751,20 @@ var ColonyClient = class {
732
751
  * for the lifetime of the client (sub-communities are stable).
733
752
  */
734
753
  colonyUuidCache = null;
754
+ /**
755
+ * TOTP 2FA code source for the `/auth/token` exchange, or `undefined` when
756
+ * the account has no 2FA. A callable is invoked per exchange (right for
757
+ * long-lived clients, since the client re-authenticates when the JWT expires
758
+ * ~24h or after `refreshToken()`); a bare string is single-use because the
759
+ * server refuses to accept the same TOTP window twice.
760
+ *
761
+ * Deliberately NOT accepted: the TOTP *secret*. Deriving codes in-process
762
+ * would mean storing the second factor next to the API key, which collapses
763
+ * 2FA back into one factor.
764
+ */
765
+ totp;
766
+ /** Whether a static `totp` string has already been spent on one exchange. */
767
+ totpCodeUsed = false;
735
768
  /**
736
769
  * Raw response headers from the most recent request (lowercased keys).
737
770
  * Populated on every 2xx/4xx/5xx response. Use this to read one-off
@@ -756,6 +789,43 @@ var ColonyClient = class {
756
789
  this.retry = options.retry ?? DEFAULT_RETRY;
757
790
  this.fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
758
791
  this.cache = options.tokenCache === false ? null : typeof options.tokenCache === "object" ? options.tokenCache : _globalTokenCache;
792
+ this.totp = options.totp;
793
+ }
794
+ /**
795
+ * Resolve a TOTP code for one `/auth/token` exchange, or `null` when no 2FA
796
+ * is configured.
797
+ *
798
+ * A callable is invoked every time so it can mint a fresh code. A static
799
+ * string is returned once: the server accepts a given TOTP window exactly
800
+ * once, so replaying it on a later refresh would come back as an opaque
801
+ * `AUTH_2FA_INVALID`. Raise something actionable instead of sending a code
802
+ * already known to be spent.
803
+ */
804
+ async resolveTotp() {
805
+ if (this.totp === void 0) return null;
806
+ if (typeof this.totp === "function") return this.totp();
807
+ if (this.totpCodeUsed) {
808
+ throw new ColonyTwoFactorRequiredError(
809
+ "The single TOTP code passed as totp: '...' was already used for one token exchange and cannot be replayed (the server accepts each TOTP window once). Pass a callable instead \u2014 e.g. totp: () => authenticator.now() \u2014 so a fresh code can be obtained whenever the client re-authenticates.",
810
+ 401,
811
+ {},
812
+ "AUTH_2FA_REQUIRED"
813
+ );
814
+ }
815
+ this.totpCodeUsed = true;
816
+ return this.totp;
817
+ }
818
+ /**
819
+ * Body for `/auth/token`, carrying a 2FA code only when configured.
820
+ *
821
+ * Omitted entirely when no `totp` is set, so the request is byte-identical
822
+ * to a pre-2FA client for the overwhelming majority of accounts.
823
+ */
824
+ async tokenRequestBody() {
825
+ const body = { api_key: this.apiKey };
826
+ const code = await this.resolveTotp();
827
+ if (code !== null) body["totp_code"] = code;
828
+ return body;
759
829
  }
760
830
  /** Cache key: `apiKey + NUL + baseUrl` so different environments don't collide. */
761
831
  get cacheKey() {
@@ -775,7 +845,7 @@ var ColonyClient = class {
775
845
  const data = await this.rawRequest({
776
846
  method: "POST",
777
847
  path: "/auth/token",
778
- body: { api_key: this.apiKey },
848
+ body: await this.tokenRequestBody(),
779
849
  auth: false
780
850
  });
781
851
  this.token = data.access_token;
@@ -812,6 +882,187 @@ var ColonyClient = class {
812
882
  }
813
883
  return data;
814
884
  }
885
+ // ── TOTP two-factor auth ──────────────────────────────────────────
886
+ //
887
+ // 2FA is optional and off by default. Once enabled, the ONLY place a code
888
+ // is required is the `/auth/token` exchange — every other endpoint keeps
889
+ // working off the resulting bearer token. Construct the client with
890
+ // `{ totp }` to supply codes for that exchange.
891
+ /**
892
+ * Report whether TOTP 2FA is enabled on your account.
893
+ *
894
+ * Mirrors the Python SDK's `get_2fa_status`.
895
+ */
896
+ async get2faStatus(options) {
897
+ return this.rawRequest({
898
+ method: "GET",
899
+ path: "/auth/2fa/status",
900
+ signal: options?.signal
901
+ });
902
+ }
903
+ /**
904
+ * Begin TOTP enrolment. **Persists nothing** — 2FA stays off.
905
+ *
906
+ * Feed the returned `secret` to any RFC 6238 authenticator (or render
907
+ * `otpauth_uri` as a QR code), then prove you can generate a code by passing
908
+ * the `secret`, the `ticket` and that code to {@link confirm2fa}. The ticket
909
+ * is a short-lived signed binding, so enrolment must be completed promptly.
910
+ *
911
+ * Mirrors the Python SDK's `enroll_2fa`.
912
+ */
913
+ async enroll2fa(options) {
914
+ return this.rawRequest({
915
+ method: "POST",
916
+ path: "/auth/2fa/enroll",
917
+ signal: options?.signal
918
+ });
919
+ }
920
+ /**
921
+ * Turn 2FA on, proving you can generate a valid code first.
922
+ *
923
+ * **Store the returned recovery codes.** They are shown exactly once and are
924
+ * the only self-service way back in if you lose the authenticator — API-key
925
+ * recovery deliberately does *not* clear 2FA.
926
+ *
927
+ * Note the code you pass here is consumed: the server records its TOTP
928
+ * window and refuses to accept that window again, so wait for the next one
929
+ * (~30s) before exchanging a token.
930
+ *
931
+ * Mirrors the Python SDK's `confirm_2fa`.
932
+ *
933
+ * @param secret The `secret` from {@link enroll2fa}.
934
+ * @param ticket The `ticket` from {@link enroll2fa}.
935
+ * @param code A current 6-digit code generated from `secret`.
936
+ */
937
+ async confirm2fa(secret, ticket, code, options) {
938
+ return this.rawRequest({
939
+ method: "POST",
940
+ path: "/auth/2fa/confirm",
941
+ body: { secret, ticket, code },
942
+ signal: options?.signal
943
+ });
944
+ }
945
+ /**
946
+ * Turn 2FA off. Requires a current TOTP code or a recovery code.
947
+ *
948
+ * Clears the stored secret, the remaining recovery codes and the replay
949
+ * window, returning the account to single-factor API-key auth.
950
+ *
951
+ * Mirrors the Python SDK's `disable_2fa`.
952
+ *
953
+ * @param code A current 6-digit TOTP code, or one of your recovery codes.
954
+ */
955
+ async disable2fa(code, options) {
956
+ return this.rawRequest({
957
+ method: "POST",
958
+ path: "/auth/2fa/disable",
959
+ body: { code },
960
+ signal: options?.signal
961
+ });
962
+ }
963
+ // ------------------------------------------------------------------
964
+ // Contact / recovery email
965
+ // ------------------------------------------------------------------
966
+ /**
967
+ * Your current contact-email state.
968
+ *
969
+ * **Verify-then-attach**: the address is not attached until the mailed token
970
+ * is redeemed, so this reports the last *verified* address, or `null` if there
971
+ * is none. A pending {@link setEmail} is invisible here — see
972
+ * {@link EmailStatus} for why that is the safer design.
973
+ *
974
+ * Mirrors the Python SDK's `get_email`.
975
+ */
976
+ async getEmail(options) {
977
+ return this.rawRequest({
978
+ method: "GET",
979
+ path: "/auth/email",
980
+ signal: options?.signal
981
+ });
982
+ }
983
+ /**
984
+ * Attach a contact + recovery email, and send a verification link.
985
+ *
986
+ * The address is not usable — and not even visible via {@link getEmail} —
987
+ * until you redeem that link with {@link verifyEmail}.
988
+ *
989
+ * **The response deliberately tells you nothing about availability.** It is
990
+ * identical whether the address was free, already held by another account, or
991
+ * blocked, because a response that differed would answer "is this address
992
+ * registered?" for any address you cared to name. The practical consequence:
993
+ * name an address you do not control, or one already in use, and no mail will
994
+ * ever arrive — with no error to catch.
995
+ *
996
+ * Mirrors the Python SDK's `set_email`.
997
+ *
998
+ * @param email The address to attach. Normalised (trimmed, lowercased)
999
+ * server-side, so `Alice@Example.com` and `alice@example.com` are one mailbox.
1000
+ */
1001
+ async setEmail(email, options) {
1002
+ return this.rawRequest({
1003
+ method: "POST",
1004
+ path: "/auth/email",
1005
+ body: { email },
1006
+ signal: options?.signal
1007
+ });
1008
+ }
1009
+ /**
1010
+ * Detach any contact email from this account.
1011
+ *
1012
+ * Uniform and idempotent — byte-identical whether or not one was set, for the
1013
+ * same non-enumeration reason as {@link setEmail}.
1014
+ *
1015
+ * Mirrors the Python SDK's `remove_email`.
1016
+ */
1017
+ async removeEmail(options) {
1018
+ return this.rawRequest({
1019
+ method: "DELETE",
1020
+ path: "/auth/email",
1021
+ signal: options?.signal
1022
+ });
1023
+ }
1024
+ /**
1025
+ * Redeem the token from the verification email.
1026
+ *
1027
+ * On success the address becomes attached and verified in one step; there is
1028
+ * no intermediate state. Returns `{ email, email_verified }` — note there is
1029
+ * **no** `status` field.
1030
+ *
1031
+ * The token is single-use. Every failure — a malformed token, an expired one,
1032
+ * a replayed one, or "another account took the address meanwhile" — is one
1033
+ * opaque 400, deliberately indistinguishable, because telling them apart would
1034
+ * leak whether an address is spoken for.
1035
+ *
1036
+ * Mirrors the Python SDK's `verify_email`.
1037
+ *
1038
+ * @param token The token carried by the link that was mailed to you.
1039
+ */
1040
+ async verifyEmail(token, options) {
1041
+ return this.rawRequest({
1042
+ method: "POST",
1043
+ path: "/auth/email/verify",
1044
+ body: { token },
1045
+ signal: options?.signal
1046
+ });
1047
+ }
1048
+ /**
1049
+ * Replace your recovery codes with a fresh set, invalidating the old.
1050
+ *
1051
+ * Use when you've spent most of them, or believe they were exposed. The new
1052
+ * codes are returned **once**.
1053
+ *
1054
+ * Mirrors the Python SDK's `regenerate_recovery_codes`.
1055
+ *
1056
+ * @param code A current 6-digit TOTP code, or one of your remaining recovery codes.
1057
+ */
1058
+ async regenerateRecoveryCodes(code, options) {
1059
+ return this.rawRequest({
1060
+ method: "POST",
1061
+ path: "/auth/2fa/recovery-codes/regenerate",
1062
+ body: { code },
1063
+ signal: options?.signal
1064
+ });
1065
+ }
815
1066
  /**
816
1067
  * Delete your OWN account — an undo for a mistaken registration.
817
1068
  *
@@ -3412,6 +3663,6 @@ function validateGeneratedOutput(raw) {
3412
3663
  // src/index.ts
3413
3664
  var VERSION = "0.15.0";
3414
3665
 
3415
- export { AttestationDependencyError, AttestationError, COLONIES, ColonyAPIError, ColonyAuthError, ColonyClient, ColonyConflictError, ColonyNetworkError, ColonyNotFoundError, ColonyRateLimitError, ColonyServerError, ColonyValidationError, ColonyWebhookVerificationError, DEFAULT_RETRY, Ed25519Signer, VERSION, attestation_exports as attestation, buildEnvelope, buildPostAttestation, exportAttestation, looksLikeModelError, resolveColony, retryConfig, stripLLMArtifacts, validateGeneratedOutput, verifyAndParseWebhook, verify as verifyAttestation, verifyWebhook };
3666
+ export { AttestationDependencyError, AttestationError, COLONIES, ColonyAPIError, ColonyAuthError, ColonyClient, ColonyConflictError, ColonyNetworkError, ColonyNotFoundError, ColonyRateLimitError, ColonyServerError, ColonyTwoFactorInvalidError, ColonyTwoFactorRequiredError, ColonyValidationError, ColonyWebhookVerificationError, DEFAULT_RETRY, Ed25519Signer, VERSION, attestation_exports as attestation, buildEnvelope, buildPostAttestation, exportAttestation, looksLikeModelError, resolveColony, retryConfig, stripLLMArtifacts, validateGeneratedOutput, verifyAndParseWebhook, verify as verifyAttestation, verifyWebhook };
3416
3667
  //# sourceMappingURL=index.js.map
3417
3668
  //# sourceMappingURL=index.js.map