@agentxm/registry-auth 0.28.4-bootstrap.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/LICENSE +110 -0
  2. package/dist/src/auth-client.d.ts +199 -0
  3. package/dist/src/auth-client.js +638 -0
  4. package/dist/src/auth-middleware.d.ts +28 -0
  5. package/dist/src/auth-middleware.js +105 -0
  6. package/dist/src/credential-store.d.ts +77 -0
  7. package/dist/src/credential-store.js +443 -0
  8. package/dist/src/device-login.d.ts +114 -0
  9. package/dist/src/device-login.js +300 -0
  10. package/dist/src/errors.d.ts +141 -0
  11. package/dist/src/errors.js +84 -0
  12. package/dist/src/guard.d.ts +24 -0
  13. package/dist/src/guard.js +62 -0
  14. package/dist/src/index.d.ts +36 -0
  15. package/dist/src/index.js +31 -0
  16. package/dist/src/internal/environment.d.ts +23 -0
  17. package/dist/src/internal/environment.js +45 -0
  18. package/dist/src/live.d.ts +13 -0
  19. package/dist/src/live.js +13 -0
  20. package/dist/src/login-interaction.d.ts +41 -0
  21. package/dist/src/login-interaction.js +108 -0
  22. package/dist/src/login-output.d.ts +22 -0
  23. package/dist/src/login-output.js +32 -0
  24. package/dist/src/login-presenter.d.ts +95 -0
  25. package/dist/src/login-presenter.js +58 -0
  26. package/dist/src/login-strategy.d.ts +21 -0
  27. package/dist/src/login-strategy.js +25 -0
  28. package/dist/src/loopback-login.d.ts +19 -0
  29. package/dist/src/loopback-login.js +91 -0
  30. package/dist/src/loopback-server.d.ts +40 -0
  31. package/dist/src/loopback-server.js +168 -0
  32. package/dist/src/oauth-contract.d.ts +7 -0
  33. package/dist/src/oauth-contract.js +2 -0
  34. package/dist/src/pending-device-login-store.d.ts +37 -0
  35. package/dist/src/pending-device-login-store.js +122 -0
  36. package/dist/src/publish-authorization.d.ts +11 -0
  37. package/dist/src/publish-authorization.js +79 -0
  38. package/dist/src/schema.d.ts +78 -0
  39. package/dist/src/schema.js +55 -0
  40. package/dist/src/testing.d.ts +14 -0
  41. package/dist/src/testing.js +14 -0
  42. package/dist/src/token-resolution.d.ts +79 -0
  43. package/dist/src/token-resolution.js +190 -0
  44. package/package.json +62 -0
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Shared device-code login flow for auth commands and guards.
3
+ *
4
+ * @experimental This API is unstable and may change without notice.
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import * as Schema from "effect/Schema";
8
+ import * as ServiceMap from "effect/Context";
9
+ import * as Layer from "effect/Layer";
10
+ import { DeviceAuthorizationPending, RegistryAuthFailed } from "./errors.js";
11
+ import { AuthClient } from "./auth-client.js";
12
+ import { CredentialStore } from "./credential-store.js";
13
+ import { AuthLoginPresenter } from "./login-presenter.js";
14
+ import { PendingDeviceLoginStore } from "./pending-device-login-store.js";
15
+ export interface DeviceLoginInteractionService {
16
+ readonly openBrowser: (url: string) => Effect.Effect<boolean>;
17
+ readonly copyToClipboard: (text: string) => Effect.Effect<boolean>;
18
+ }
19
+ declare const DeviceLoginInteraction_base: ServiceMap.ServiceClass<DeviceLoginInteraction, "@agentxm/registry-auth/device-login/DeviceLoginInteraction", DeviceLoginInteractionService>;
20
+ export declare class DeviceLoginInteraction extends DeviceLoginInteraction_base {
21
+ }
22
+ export interface DeviceLoginInteractionTestState {
23
+ readonly openBrowserCalls: Array<string>;
24
+ readonly copyToClipboardCalls: Array<string>;
25
+ }
26
+ export declare const DeviceLoginInteractionTest: (overrides?: {
27
+ readonly openBrowser?: (url: string) => Effect.Effect<boolean>;
28
+ readonly copyToClipboard?: (text: string) => Effect.Effect<boolean>;
29
+ }) => {
30
+ layer: Layer.Layer<DeviceLoginInteraction, never, never>;
31
+ state: DeviceLoginInteractionTestState;
32
+ };
33
+ export interface RunDeviceLoginOptions {
34
+ readonly emitPendingResult?: boolean;
35
+ readonly openBrowser?: boolean;
36
+ readonly restart?: boolean;
37
+ readonly scopes?: ReadonlyArray<string>;
38
+ }
39
+ export interface ResumeDeviceLoginOptions {
40
+ readonly timeoutSeconds?: number;
41
+ }
42
+ export declare const DeviceLoginPendingResultSchema: Schema.Struct<{
43
+ readonly status: Schema.Literal<"pending-human">;
44
+ readonly blockedOn: Schema.Literal<"human">;
45
+ readonly retryable: Schema.Literal<true>;
46
+ readonly flow: Schema.Literals<readonly ["started", "re-emitted"]>;
47
+ readonly registryHost: Schema.String;
48
+ readonly verificationUri: Schema.String;
49
+ readonly verificationUriComplete: Schema.String;
50
+ readonly requestedScopes: Schema.$Array<Schema.String>;
51
+ readonly userCode: Schema.String;
52
+ readonly expiresAt: Schema.String;
53
+ readonly interval: Schema.Number;
54
+ readonly resume: Schema.String;
55
+ readonly action: Schema.Struct<{
56
+ readonly kind: Schema.Literal<"open-url">;
57
+ readonly url: Schema.String;
58
+ readonly fallbackUrl: Schema.String;
59
+ readonly code: Schema.String;
60
+ readonly expiresAt: Schema.String;
61
+ readonly resume: Schema.String;
62
+ }>;
63
+ }>;
64
+ export type DeviceLoginPendingResult = typeof DeviceLoginPendingResultSchema.Type;
65
+ export declare const DeviceLoginPendingDocumentSchema: Schema.Struct<{
66
+ readonly result: Schema.Struct<{
67
+ readonly status: Schema.Literal<"pending-human">;
68
+ readonly blockedOn: Schema.Literal<"human">;
69
+ readonly retryable: Schema.Literal<true>;
70
+ readonly flow: Schema.Literals<readonly ["started", "re-emitted"]>;
71
+ readonly registryHost: Schema.String;
72
+ readonly verificationUri: Schema.String;
73
+ readonly verificationUriComplete: Schema.String;
74
+ readonly requestedScopes: Schema.$Array<Schema.String>;
75
+ readonly userCode: Schema.String;
76
+ readonly expiresAt: Schema.String;
77
+ readonly interval: Schema.Number;
78
+ readonly resume: Schema.String;
79
+ readonly action: Schema.Struct<{
80
+ readonly kind: Schema.Literal<"open-url">;
81
+ readonly url: Schema.String;
82
+ readonly fallbackUrl: Schema.String;
83
+ readonly code: Schema.String;
84
+ readonly expiresAt: Schema.String;
85
+ readonly resume: Schema.String;
86
+ }>;
87
+ }>;
88
+ }>;
89
+ export declare const initiateDeviceLogin: (registryUrl: string, options?: RunDeviceLoginOptions) => Effect.Effect<{
90
+ readonly status: "pending-human";
91
+ readonly blockedOn: "human";
92
+ readonly retryable: true;
93
+ readonly flow: "started" | "re-emitted";
94
+ readonly registryHost: string;
95
+ readonly verificationUri: string;
96
+ readonly verificationUriComplete: string;
97
+ readonly requestedScopes: readonly string[];
98
+ readonly userCode: string;
99
+ readonly expiresAt: string;
100
+ readonly interval: number;
101
+ readonly resume: string;
102
+ readonly action: {
103
+ readonly kind: "open-url";
104
+ readonly url: string;
105
+ readonly fallbackUrl: string;
106
+ readonly code: string;
107
+ readonly expiresAt: string;
108
+ readonly resume: string;
109
+ };
110
+ }, import("./errors.js").AuthError, AuthClient | CredentialStore | AuthLoginPresenter | PendingDeviceLoginStore | DeviceLoginInteraction>;
111
+ export declare const resumeDeviceLogin: (registryUrl: string, options?: ResumeDeviceLoginOptions) => Effect.Effect<undefined, RegistryAuthFailed | import("./errors.js").AuthLoginRequired | import("./errors.js").AuthTokenPolicyRequired | DeviceAuthorizationPending | import("./errors.js").StepUpRequired | import("@agentxm/registry-client").RegistryClientFailure | import("./errors.js").AuthExchangeFailed, AuthClient | CredentialStore | AuthLoginPresenter | PendingDeviceLoginStore>;
112
+ export declare const runDeviceLogin: (registryUrl: string, options?: RunDeviceLoginOptions) => Effect.Effect<void, RegistryAuthFailed | import("./errors.js").AuthLoginRequired | import("./errors.js").AuthTokenPolicyRequired | import("./errors.js").DeviceLoginDenied | import("./errors.js").DeviceLoginCodeExpired | DeviceAuthorizationPending | import("./errors.js").StepUpRequired | import("@agentxm/registry-client").RegistryProblem | import("@agentxm/registry-client").RegistryRequestFailed | import("@agentxm/registry-client").RegistryOperationFailed | import("./errors.js").AuthExchangeFailed, AuthClient | CredentialStore | AuthLoginPresenter | PendingDeviceLoginStore | DeviceLoginInteraction>;
113
+ export {};
114
+ //# sourceMappingURL=device-login.d.ts.map
@@ -0,0 +1,300 @@
1
+ /**
2
+ * Shared device-code login flow for auth commands and guards.
3
+ *
4
+ * @experimental This API is unstable and may change without notice.
5
+ */
6
+ import * as Effect from "effect/Effect";
7
+ import * as DateTime from "effect/DateTime";
8
+ import * as Duration from "effect/Duration";
9
+ import * as Option from "effect/Option";
10
+ import * as Schema from "effect/Schema";
11
+ import * as ServiceMap from "effect/Context";
12
+ import * as Layer from "effect/Layer";
13
+ import { normalizeHandle } from "@agentxm/extension-model/unstable/extensions/handle";
14
+ import { DeviceAuthorizationPending, RegistryAuthFailed } from "./errors.js";
15
+ import { AuthClient, normalizeRequestedLoginScopes } from "./auth-client.js";
16
+ import { CredentialStore, makePersistedCredentialsUnsupportedError } from "./credential-store.js";
17
+ import { emitLoginSuccess } from "./login-output.js";
18
+ import { AuthLoginPresenter } from "./login-presenter.js";
19
+ import { PendingDeviceLoginStore } from "./pending-device-login-store.js";
20
+ export class DeviceLoginInteraction extends ServiceMap.Service()("@agentxm/registry-auth/device-login/DeviceLoginInteraction") {
21
+ }
22
+ export const DeviceLoginInteractionTest = (overrides) => {
23
+ const state = {
24
+ openBrowserCalls: [],
25
+ copyToClipboardCalls: [],
26
+ };
27
+ const layer = Layer.succeed(DeviceLoginInteraction, {
28
+ openBrowser: (url) => Effect.gen(function* () {
29
+ state.openBrowserCalls.push(url);
30
+ return yield* overrides?.openBrowser?.(url) ?? Effect.succeed(false);
31
+ }),
32
+ copyToClipboard: (text) => Effect.gen(function* () {
33
+ state.copyToClipboardCalls.push(text);
34
+ return yield* overrides?.copyToClipboard?.(text) ?? Effect.succeed(false);
35
+ }),
36
+ });
37
+ return { layer, state };
38
+ };
39
+ // -----------------------------------------------------------------------------
40
+ // Device login orchestration
41
+ // -----------------------------------------------------------------------------
42
+ const UNKNOWN_HANDLE = normalizeHandle("@unknown");
43
+ const persistLoginCredentials = (registryUrl, token) => Effect.gen(function* () {
44
+ const authClient = yield* AuthClient;
45
+ const credStore = yield* CredentialStore;
46
+ const meResult = yield* authClient
47
+ .getMe(token.access_token)
48
+ .pipe(Effect.retry({ times: 1 }), Effect.option);
49
+ const handle = Option.match(meResult, {
50
+ onNone: () => UNKNOWN_HANDLE,
51
+ onSome: (me) => me.userHandle,
52
+ });
53
+ yield* credStore.save(registryUrl, handle, {
54
+ access_token: token.access_token,
55
+ refresh_token: token.refresh_token,
56
+ expires_at: token.expires_at,
57
+ });
58
+ return Option.map(meResult, (me) => me.userHandle);
59
+ });
60
+ const DeviceLoginActionSchema = Schema.Struct({
61
+ kind: Schema.Literal("open-url"),
62
+ url: Schema.String,
63
+ fallbackUrl: Schema.String,
64
+ code: Schema.String,
65
+ expiresAt: Schema.String,
66
+ resume: Schema.String,
67
+ });
68
+ export const DeviceLoginPendingResultSchema = Schema.Struct({
69
+ status: Schema.Literal("pending-human"),
70
+ blockedOn: Schema.Literal("human"),
71
+ retryable: Schema.Literal(true),
72
+ flow: Schema.Literals(["started", "re-emitted"]),
73
+ registryHost: Schema.String,
74
+ verificationUri: Schema.String,
75
+ verificationUriComplete: Schema.String,
76
+ requestedScopes: Schema.Array(Schema.String),
77
+ userCode: Schema.String,
78
+ expiresAt: Schema.String,
79
+ interval: Schema.Number,
80
+ resume: Schema.String,
81
+ action: DeviceLoginActionSchema,
82
+ });
83
+ export const DeviceLoginPendingDocumentSchema = Schema.Struct({
84
+ result: DeviceLoginPendingResultSchema,
85
+ });
86
+ const presentDeviceFlow = (verificationUri, verificationUriComplete, userCode, expiresInSeconds, options) => Effect.gen(function* () {
87
+ const presenter = yield* AuthLoginPresenter;
88
+ const interaction = yield* DeviceLoginInteraction;
89
+ const shouldOpenBrowser = options.openBrowser ?? true;
90
+ const copiedToClipboard = yield* interaction.copyToClipboard(userCode);
91
+ const browserOpened = shouldOpenBrowser
92
+ ? yield* interaction.openBrowser(verificationUriComplete)
93
+ : false;
94
+ yield* presenter.presentDeviceFlow({
95
+ verificationUri,
96
+ verificationUriComplete,
97
+ userCode,
98
+ expiresInSeconds,
99
+ browserOpened,
100
+ copiedToClipboard,
101
+ });
102
+ });
103
+ const makePendingResult = (pending, flow = "started") => {
104
+ const registryHost = new URL(pending.registryUrl).host;
105
+ const expiresAt = DateTime.formatIso(pending.expiresAt);
106
+ const resume = "axm login --wait --json";
107
+ return {
108
+ status: "pending-human",
109
+ blockedOn: "human",
110
+ retryable: true,
111
+ flow,
112
+ registryHost,
113
+ verificationUri: pending.verificationUri,
114
+ verificationUriComplete: pending.verificationUriComplete,
115
+ requestedScopes: pending.requestedScopes,
116
+ userCode: pending.userCode,
117
+ expiresAt,
118
+ interval: pending.interval,
119
+ resume,
120
+ action: {
121
+ kind: "open-url",
122
+ url: pending.verificationUriComplete,
123
+ fallbackUrl: pending.verificationUri,
124
+ code: pending.userCode,
125
+ expiresAt,
126
+ resume,
127
+ },
128
+ };
129
+ };
130
+ const emitPendingDeviceLogin = (pending, options, flow = "started") => Effect.gen(function* () {
131
+ const presenter = yield* AuthLoginPresenter;
132
+ const result = makePendingResult(pending, flow);
133
+ // Machine mode consumes the pending document; browser/clipboard side
134
+ // effects and human presentation must not run afterwards.
135
+ if (yield* presenter.tryEmitPendingDeviceLogin(result)) {
136
+ return result;
137
+ }
138
+ yield* presentDeviceFlow(pending.verificationUri, pending.verificationUriComplete, pending.userCode, Math.max(0, Math.ceil((DateTime.toEpochMillis(pending.expiresAt) -
139
+ DateTime.toEpochMillis(yield* DateTime.now)) /
140
+ 1000)), options);
141
+ yield* presenter.notePendingApproval(result);
142
+ return result;
143
+ });
144
+ export const initiateDeviceLogin = (registryUrl, options = {}) => Effect.gen(function* () {
145
+ const authClient = yield* AuthClient;
146
+ const credStore = yield* CredentialStore;
147
+ const pendingStore = yield* PendingDeviceLoginStore;
148
+ const presenter = yield* AuthLoginPresenter;
149
+ const registryHost = new URL(registryUrl).host;
150
+ const requestedScopes = normalizeRequestedLoginScopes(options.scopes);
151
+ if (!credStore.allowsPersistedCredentials) {
152
+ return yield* makePersistedCredentialsUnsupportedError();
153
+ }
154
+ const existing = yield* pendingStore.load();
155
+ if (Option.isSome(existing)) {
156
+ const expired = yield* DateTime.isPast(existing.value.expiresAt);
157
+ if (expired || options.restart === true) {
158
+ yield* pendingStore.clear();
159
+ }
160
+ else if (existing.value.registryUrl === registryUrl &&
161
+ existing.value.requestedScopes.length === requestedScopes.length &&
162
+ existing.value.requestedScopes.every((scope, index) => scope === requestedScopes[index])) {
163
+ return yield* emitPendingDeviceLogin(existing.value, options, "re-emitted");
164
+ }
165
+ else {
166
+ return yield* new RegistryAuthFailed({
167
+ category: "conflict",
168
+ detail: existing.value.registryUrl === registryUrl
169
+ ? `A device sign-in for ${registryHost} is already pending with a different scope set.`
170
+ : `A device sign-in for ${new URL(existing.value.registryUrl).host} is already pending.`,
171
+ suggestions: [
172
+ {
173
+ description: "Finish the pending sign-in before starting another.",
174
+ cmd: "axm login --wait --json",
175
+ },
176
+ {
177
+ description: "Replace the pending sign-in intentionally.",
178
+ cmd: "axm login --device-code --restart --json",
179
+ },
180
+ ],
181
+ });
182
+ }
183
+ }
184
+ const deviceFlow = yield* presenter.withProgress({ _tag: "StartingDeviceAuthorization", registryHost }, () => authClient.initiateDeviceFlow({ scopes: requestedScopes }));
185
+ const pending = {
186
+ version: 2,
187
+ registryUrl,
188
+ deviceCode: deviceFlow.device_code,
189
+ userCode: deviceFlow.user_code,
190
+ verificationUri: deviceFlow.verification_uri,
191
+ verificationUriComplete: deviceFlow.verification_uri_complete,
192
+ requestedScopes,
193
+ interval: deviceFlow.interval,
194
+ expiresAt: DateTime.add(yield* DateTime.now, { seconds: deviceFlow.expires_in }),
195
+ };
196
+ yield* pendingStore.save(pending);
197
+ if (options.emitPendingResult === false) {
198
+ yield* presentDeviceFlow(pending.verificationUri, pending.verificationUriComplete, pending.userCode, deviceFlow.expires_in, options);
199
+ return makePendingResult(pending);
200
+ }
201
+ return yield* emitPendingDeviceLogin(pending, options);
202
+ });
203
+ const pendingLoginNotFound = () => new RegistryAuthFailed({
204
+ category: "not_found",
205
+ detail: "No pending device sign-in was found.",
206
+ suggestions: [
207
+ {
208
+ description: "Start a device sign-in first.",
209
+ cmd: "axm login --device-code --json",
210
+ },
211
+ ],
212
+ });
213
+ export const resumeDeviceLogin = (registryUrl, options = {}) => Effect.gen(function* () {
214
+ const authClient = yield* AuthClient;
215
+ const credStore = yield* CredentialStore;
216
+ const pendingStore = yield* PendingDeviceLoginStore;
217
+ const presenter = yield* AuthLoginPresenter;
218
+ const registryHost = new URL(registryUrl).host;
219
+ if (!credStore.allowsPersistedCredentials) {
220
+ return yield* makePersistedCredentialsUnsupportedError();
221
+ }
222
+ const loaded = yield* pendingStore.load();
223
+ if (Option.isNone(loaded))
224
+ return yield* pendingLoginNotFound();
225
+ const pending = loaded.value;
226
+ if (pending.registryUrl !== registryUrl) {
227
+ return yield* new RegistryAuthFailed({
228
+ category: "conflict",
229
+ detail: `The pending sign-in belongs to ${new URL(pending.registryUrl).host}, not ${registryHost}.`,
230
+ suggestions: [
231
+ {
232
+ description: "Resume with the registry that started the sign-in.",
233
+ cmd: "axm login --wait --registry <url> --json",
234
+ },
235
+ ],
236
+ });
237
+ }
238
+ if (yield* DateTime.isPast(pending.expiresAt)) {
239
+ yield* pendingStore.clear();
240
+ return yield* new RegistryAuthFailed({
241
+ category: "auth_expired",
242
+ detail: "The pending device sign-in expired. No credentials were changed.",
243
+ suggestions: [
244
+ {
245
+ description: "Request a new device sign-in code.",
246
+ cmd: "axm login --device-code --json",
247
+ },
248
+ ],
249
+ });
250
+ }
251
+ const polling = authClient.pollDeviceToken(pending.deviceCode, pending.interval);
252
+ const boundedPolling = options.timeoutSeconds === undefined
253
+ ? polling
254
+ : polling.pipe(Effect.timeoutOrElse({
255
+ duration: Duration.seconds(options.timeoutSeconds),
256
+ orElse: () => {
257
+ const timeoutSeconds = options.timeoutSeconds ?? 0;
258
+ const action = makePendingResult(pending).action;
259
+ return Effect.fail(new DeviceAuthorizationPending({
260
+ timeoutSeconds,
261
+ verificationUri: action.fallbackUrl,
262
+ verificationUriComplete: action.url,
263
+ userCode: action.code,
264
+ expiresAt: action.expiresAt,
265
+ resume: action.resume,
266
+ }));
267
+ },
268
+ }));
269
+ const token = yield* presenter
270
+ .withProgress({ _tag: "WaitingForDeviceAuthorization", registryHost }, () => boundedPolling)
271
+ .pipe(Effect.catchTag("DeviceLoginCodeExpired", (error) => pendingStore.clear().pipe(Effect.flatMap(() => Effect.fail(new RegistryAuthFailed({
272
+ category: "auth_expired",
273
+ detail: "The pending device sign-in expired. No credentials were changed.",
274
+ suggestions: [
275
+ {
276
+ description: "Request a new device sign-in code.",
277
+ cmd: "axm login --device-code --json",
278
+ },
279
+ ],
280
+ cause: error,
281
+ }))))), Effect.catchTag("DeviceLoginDenied", (error) => pendingStore.clear().pipe(Effect.flatMap(() => Effect.fail(new RegistryAuthFailed({
282
+ category: "auth_denied",
283
+ detail: "Device sign-in was denied or cancelled. No credentials were changed.",
284
+ suggestions: [
285
+ {
286
+ description: "Start a new device sign-in when ready.",
287
+ cmd: "axm login --device-code --json",
288
+ },
289
+ ],
290
+ cause: error,
291
+ }))))));
292
+ yield* pendingStore.clear();
293
+ const handle = yield* presenter.withProgress({ _tag: "SavingCredentials", registryHost }, () => persistLoginCredentials(registryUrl, token));
294
+ yield* emitLoginSuccess(registryUrl, handle);
295
+ });
296
+ export const runDeviceLogin = (registryUrl, options = {}) => Effect.gen(function* () {
297
+ yield* initiateDeviceLogin(registryUrl, { ...options, emitPendingResult: false });
298
+ yield* resumeDeviceLogin(registryUrl);
299
+ });
300
+ //# sourceMappingURL=device-login.js.map
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Typed failures for the registry-auth feature. The producer owns the
3
+ * category choice and user-facing wording; the application boundary converts
4
+ * the carried fields into its error envelope verbatim. Failures that carry a
5
+ * registry transport failure keep it intact so the boundary can restore the
6
+ * exact evidence, metadata, and semantics the former in-place conversion
7
+ * produced.
8
+ *
9
+ * @experimental This API is unstable and may change without notice.
10
+ */
11
+ import { type RegistryClientFailure } from "@agentxm/registry-client";
12
+ import type { SuggestedAction } from "@agentxm/registry-protocol/unstable/suggested-action";
13
+ /** Every category a registry-auth failure can carry. Identical strings to the CLI error codes. */
14
+ export declare const REGISTRY_AUTH_ERROR_CATEGORIES: readonly ["auth", "auth_denied", "auth_expired", "conflict", "internal", "not_found", "validation"];
15
+ export type RegistryAuthErrorCategory = (typeof REGISTRY_AUTH_ERROR_CATEGORIES)[number];
16
+ declare const RegistryAuthFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
17
+ readonly _tag: "RegistryAuthFailed";
18
+ } & Readonly<A>;
19
+ /**
20
+ * An auth policy step could not proceed. The carried fields mirror the
21
+ * application error envelope's inputs 1:1: `category` selects the code,
22
+ * `recover`/`cmd` fold into the leading suggested action, and `detail`,
23
+ * `suggestions`, and `cause` carry over verbatim.
24
+ */
25
+ export declare class RegistryAuthFailed extends RegistryAuthFailed_base<{
26
+ readonly category: RegistryAuthErrorCategory;
27
+ readonly detail: string;
28
+ readonly recover?: string;
29
+ readonly cmd?: string;
30
+ readonly suggestions?: ReadonlyArray<SuggestedAction>;
31
+ readonly cause?: unknown;
32
+ }> {
33
+ }
34
+ declare const AuthLoginRequired_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
35
+ readonly _tag: "AuthLoginRequired";
36
+ } & Readonly<A>;
37
+ /**
38
+ * Sign-in is required and a person must approve it. The boundary renders the
39
+ * fixed device-flow and token-creation guidance; the producer chooses only
40
+ * the leading message.
41
+ */
42
+ export declare class AuthLoginRequired extends AuthLoginRequired_base<{
43
+ readonly message: string;
44
+ readonly cause?: unknown;
45
+ }> {
46
+ }
47
+ export declare const authLoginRequired: (message?: string, cause?: unknown) => AuthLoginRequired;
48
+ declare const AuthTokenPolicyRequired_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
49
+ readonly _tag: "AuthTokenPolicyRequired";
50
+ } & Readonly<A>;
51
+ /**
52
+ * Persisted credentials are unavailable by policy (for example in CI), so an
53
+ * ambient token is the only accepted authentication. The boundary renders the
54
+ * fixed AXM_TOKEN_FILE / token-creation guidance.
55
+ */
56
+ export declare class AuthTokenPolicyRequired extends AuthTokenPolicyRequired_base<{
57
+ readonly cause?: unknown;
58
+ }> {
59
+ }
60
+ declare const DeviceLoginDenied_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
61
+ readonly _tag: "DeviceLoginDenied";
62
+ } & Readonly<A>;
63
+ /** The device authorization was denied or cancelled by the person approving it. */
64
+ export declare class DeviceLoginDenied extends DeviceLoginDenied_base {
65
+ }
66
+ declare const DeviceLoginCodeExpired_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
67
+ readonly _tag: "DeviceLoginCodeExpired";
68
+ } & Readonly<A>;
69
+ /** The device authorization code expired before the person approved it. */
70
+ export declare class DeviceLoginCodeExpired extends DeviceLoginCodeExpired_base {
71
+ }
72
+ declare const DeviceAuthorizationPending_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
73
+ readonly _tag: "DeviceAuthorizationPending";
74
+ } & Readonly<A>;
75
+ /**
76
+ * A bounded wait for device approval elapsed while the pending flow is still
77
+ * valid. Carries every fact the boundary needs to render the pending-human
78
+ * envelope: status, blocked-on semantics, the open-url action with fallback
79
+ * and one-time code, and the resume command.
80
+ */
81
+ export declare class DeviceAuthorizationPending extends DeviceAuthorizationPending_base<{
82
+ readonly timeoutSeconds: number;
83
+ readonly verificationUri: string;
84
+ readonly verificationUriComplete: string;
85
+ readonly userCode: string;
86
+ /** ISO timestamp at which the pending device authorization expires. */
87
+ readonly expiresAt: string;
88
+ /** Exact command that resumes the pending sign-in. */
89
+ readonly resume: string;
90
+ }> {
91
+ }
92
+ /** One step-up verification request as the registry described it on the wire. */
93
+ export interface StepUpRequest {
94
+ readonly requestId: string;
95
+ readonly verificationUrl: string;
96
+ readonly statusUrl: string;
97
+ readonly expiresAt: string;
98
+ readonly intervalSeconds: number;
99
+ readonly maxAgeSeconds?: number;
100
+ readonly action: string;
101
+ readonly target: string;
102
+ }
103
+ declare const StepUpRequired_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
104
+ readonly _tag: "StepUpRequired";
105
+ } & Readonly<A>;
106
+ /**
107
+ * The registry demands step-up verification by a person before the operation
108
+ * can proceed. Carries the parsed step-up request and the underlying
109
+ * transport failure so the boundary reproduces the exact metadata and cause
110
+ * evidence.
111
+ */
112
+ export declare class StepUpRequired extends StepUpRequired_base<{
113
+ readonly stepUp: StepUpRequest;
114
+ readonly failure: RegistryClientFailure;
115
+ }> {
116
+ }
117
+ declare const AuthExchangeFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
118
+ readonly _tag: "AuthExchangeFailed";
119
+ } & Readonly<A>;
120
+ /**
121
+ * A token-exchange endpoint failed and the flow assigns it auth semantics:
122
+ * the boundary overlays the carried detail and suggestions onto the mapped
123
+ * transport failure exactly as the former in-place conversion did.
124
+ */
125
+ export declare class AuthExchangeFailed extends AuthExchangeFailed_base<{
126
+ readonly detail: string;
127
+ readonly suggestions?: ReadonlyArray<SuggestedAction>;
128
+ readonly failure: RegistryClientFailure;
129
+ }> {
130
+ }
131
+ /** Every typed failure the registry-auth feature constructs. */
132
+ export type RegistryAuthFailure = RegistryAuthFailed | AuthLoginRequired | AuthTokenPolicyRequired | DeviceLoginDenied | DeviceLoginCodeExpired | DeviceAuthorizationPending | StepUpRequired | AuthExchangeFailed;
133
+ export declare const isRegistryAuthFailure: (error: unknown) => error is RegistryAuthFailure;
134
+ /**
135
+ * Every failure a registry-auth use case can surface: the feature's own typed
136
+ * failures plus registry transport failures propagated unwrapped.
137
+ */
138
+ export type AuthError = RegistryAuthFailure | RegistryClientFailure;
139
+ export declare const isAuthError: (error: unknown) => error is AuthError;
140
+ export {};
141
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Typed failures for the registry-auth feature. The producer owns the
3
+ * category choice and user-facing wording; the application boundary converts
4
+ * the carried fields into its error envelope verbatim. Failures that carry a
5
+ * registry transport failure keep it intact so the boundary can restore the
6
+ * exact evidence, metadata, and semantics the former in-place conversion
7
+ * produced.
8
+ *
9
+ * @experimental This API is unstable and may change without notice.
10
+ */
11
+ import * as Data from "effect/Data";
12
+ import { isRegistryClientFailure } from "@agentxm/registry-client";
13
+ /** Every category a registry-auth failure can carry. Identical strings to the CLI error codes. */
14
+ export const REGISTRY_AUTH_ERROR_CATEGORIES = [
15
+ "auth",
16
+ "auth_denied",
17
+ "auth_expired",
18
+ "conflict",
19
+ "internal",
20
+ "not_found",
21
+ "validation",
22
+ ];
23
+ /**
24
+ * An auth policy step could not proceed. The carried fields mirror the
25
+ * application error envelope's inputs 1:1: `category` selects the code,
26
+ * `recover`/`cmd` fold into the leading suggested action, and `detail`,
27
+ * `suggestions`, and `cause` carry over verbatim.
28
+ */
29
+ export class RegistryAuthFailed extends Data.TaggedError("RegistryAuthFailed") {
30
+ }
31
+ /**
32
+ * Sign-in is required and a person must approve it. The boundary renders the
33
+ * fixed device-flow and token-creation guidance; the producer chooses only
34
+ * the leading message.
35
+ */
36
+ export class AuthLoginRequired extends Data.TaggedError("AuthLoginRequired") {
37
+ }
38
+ export const authLoginRequired = (message = "Authentication required", cause) => new AuthLoginRequired({ message, ...(cause === undefined ? {} : { cause }) });
39
+ /**
40
+ * Persisted credentials are unavailable by policy (for example in CI), so an
41
+ * ambient token is the only accepted authentication. The boundary renders the
42
+ * fixed AXM_TOKEN_FILE / token-creation guidance.
43
+ */
44
+ export class AuthTokenPolicyRequired extends Data.TaggedError("AuthTokenPolicyRequired") {
45
+ }
46
+ /** The device authorization was denied or cancelled by the person approving it. */
47
+ export class DeviceLoginDenied extends Data.TaggedError("DeviceLoginDenied") {
48
+ }
49
+ /** The device authorization code expired before the person approved it. */
50
+ export class DeviceLoginCodeExpired extends Data.TaggedError("DeviceLoginCodeExpired") {
51
+ }
52
+ /**
53
+ * A bounded wait for device approval elapsed while the pending flow is still
54
+ * valid. Carries every fact the boundary needs to render the pending-human
55
+ * envelope: status, blocked-on semantics, the open-url action with fallback
56
+ * and one-time code, and the resume command.
57
+ */
58
+ export class DeviceAuthorizationPending extends Data.TaggedError("DeviceAuthorizationPending") {
59
+ }
60
+ /**
61
+ * The registry demands step-up verification by a person before the operation
62
+ * can proceed. Carries the parsed step-up request and the underlying
63
+ * transport failure so the boundary reproduces the exact metadata and cause
64
+ * evidence.
65
+ */
66
+ export class StepUpRequired extends Data.TaggedError("StepUpRequired") {
67
+ }
68
+ /**
69
+ * A token-exchange endpoint failed and the flow assigns it auth semantics:
70
+ * the boundary overlays the carried detail and suggestions onto the mapped
71
+ * transport failure exactly as the former in-place conversion did.
72
+ */
73
+ export class AuthExchangeFailed extends Data.TaggedError("AuthExchangeFailed") {
74
+ }
75
+ export const isRegistryAuthFailure = (error) => error instanceof RegistryAuthFailed ||
76
+ error instanceof AuthLoginRequired ||
77
+ error instanceof AuthTokenPolicyRequired ||
78
+ error instanceof DeviceLoginDenied ||
79
+ error instanceof DeviceLoginCodeExpired ||
80
+ error instanceof DeviceAuthorizationPending ||
81
+ error instanceof StepUpRequired ||
82
+ error instanceof AuthExchangeFailed;
83
+ export const isAuthError = (error) => isRegistryAuthFailure(error) || isRegistryClientFailure(error);
84
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Auth guard combinator for commands that require authentication.
3
+ *
4
+ * Wraps an Effect with a pre-check for authentication. If no token is
5
+ * resolvable, fails immediately with an actionable error message directing
6
+ * the user to `axm login`.
7
+ *
8
+ * @experimental This API is unstable and may change without notice.
9
+ */
10
+ import * as Effect from "effect/Effect";
11
+ import { RegistryUrl } from "@agentxm/registry-client";
12
+ import { type AuthError } from "./errors.js";
13
+ import { CredentialStore } from "./credential-store.js";
14
+ /**
15
+ * Wraps an Effect with an auth guard.
16
+ *
17
+ * - If the target registry is local, runs the inner effect directly.
18
+ * - If a token is already resolvable, runs the inner effect directly.
19
+ * - If no token: fails with `AUTH_LOGIN_REQUIRED`.
20
+ */
21
+ export declare const withAuthGuard: <A, E, R>(effect: Effect.Effect<A, E | AuthError, R>, options?: {
22
+ registryUrl?: string;
23
+ }) => Effect.Effect<A, AuthError | E, RegistryUrl | CredentialStore | R>;
24
+ //# sourceMappingURL=guard.d.ts.map