@holokai/neuron-sdk 0.1.1 → 0.3.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/README.md CHANGED
@@ -50,6 +50,43 @@ process.on('SIGTERM', () => neuron.stop({ drain: true, timeoutMs: 30_000 }));
50
50
 
51
51
  ---
52
52
 
53
+ ## Authentication
54
+
55
+ The `auth` callback supplies the bearer token for every POST and SSE open. How you obtain tokens depends on where the neuron runs (full model: `docs/NEURON_AUTH_SPEC.md` in the bigbrain repo):
56
+
57
+ - **Desktop-embedded neurons** run under the signed-in user: exchange the user's Moku JWT for an `aud=bigbrain` token (RFC 8693) and return it from `auth`.
58
+ - **Headless / enterprise neurons** enroll once via Moku's device flow, then mint short-lived tokens from the stored client credential — no static long-lived JWT anywhere.
59
+
60
+ ### Enroll (one-time, headless neurons)
61
+
62
+ ```bash
63
+ npx @holokai/neuron-sdk enroll --moku-url https://moku.example \
64
+ --name warehouse-connector --capabilities org/acme/erp.read,org/acme/erp.write
65
+ ```
66
+
67
+ Prints a verification URL + user code; an org admin approves in Moku. The client credential is written to `~/.holokai/neuron-credentials.json` (`0600`; override with `--credentials-file`). The requested capabilities are display hints for the approval screen — what the neuron may actually advertise is governed by the org's capability catalog. Polling is resilient: transient server errors (409/5xx) and network blips are retried until the device code expires; only a denial, expiry, or protocol error is terminal.
68
+
69
+ Also available programmatically: `enroll(opts)` from `@holokai/neuron-sdk/auth`.
70
+
71
+ ### Steady state
72
+
73
+ ```ts
74
+ import { Neuron } from '@holokai/neuron-sdk';
75
+ import { createClientCredentialsAuth, defaultCredentialPath } from '@holokai/neuron-sdk/auth';
76
+
77
+ const neuron = new Neuron({
78
+ gatewayUrl: 'https://api.example.com',
79
+ neuronId: 'warehouse-connector-001',
80
+ auth: createClientCredentialsAuth(defaultCredentialPath()),
81
+ });
82
+ ```
83
+
84
+ `createClientCredentialsAuth` mints `aud=bigbrain` tokens via the OAuth2 `client_credentials` grant, caches them against the response's `expires_in` (proactive refresh, single-flight), and re-reads the credential file on `invalid_client` so an operator-rotated secret is picked up without a restart. If the credential was revoked in Moku it throws `CredentialRevokedError` — re-enroll to recover.
85
+
86
+ > `@holokai/neuron-sdk/auth` is **Node-only** (it reads the credential file); the root and `./ipc` entries remain browser-clean. Python SDK parity for enrollment/minting is a planned follow-up in `holokai-neuron-sdk` — until then, Python neurons keep using an externally supplied token.
87
+
88
+ ---
89
+
53
90
  ## Core concepts
54
91
 
55
92
  ### Capability
@@ -778,3 +815,83 @@ Heartbeats aren't reaching the gateway, so the reaper hasn't reclaimed them. Che
778
815
  ### Auth 401 on every request
779
816
 
780
817
  The `auth` callback isn't returning a valid token. If using a refresh flow, ensure the callback fetches a fresh token on each call (the SDK caches nothing — it's your callback's job).
818
+
819
+ ---
820
+
821
+ ## Releasing (maintainers)
822
+
823
+ `@holokai/neuron-sdk` is published to public npm. Releases are cut from a
824
+ monorepo-scoped git tag, and CI does the publish — there is no npm token on
825
+ anyone's laptop and none stored in the repo.
826
+
827
+ ### Cut a release
828
+
829
+ 1. **Bump the version** in `packages/neuron-sdk/package.json`
830
+ (`version`). Follow semver:
831
+ - Pilot phase, we are in **`0.x`**. Because the protocol is still absorbing
832
+ pilot-developer feedback, a **breaking** change bumps the **minor**
833
+ (`0.2.0` → `0.3.0`); a backward-compatible change or fix bumps the
834
+ **patch** (`0.2.0` → `0.2.1`). This is the standard `0.x` convention and
835
+ is what buys us room to break things on feedback without a major bump.
836
+ - Hold **`1.0.0`** until the public API has survived pilot feedback and we're
837
+ ready to commit to "breaking changes bump the major." Jumping to `1.0.0`
838
+ is a stability promise, not a milestone — don't spend it early.
839
+ 2. **Commit** the bump (reference the Linear ticket) and merge to `main`.
840
+ 3. **Tag and push** from `main`:
841
+ ```sh
842
+ git tag neuron-sdk-v0.2.0 # must equal the package.json version
843
+ git push origin neuron-sdk-v0.2.0
844
+ ```
845
+ The tag is prefixed `neuron-sdk-v` (the Python SDK uses
846
+ `neuron-sdk-python-v`) so the two never collide.
847
+ 4. **`publish-neuron-sdk.yml`** fires on the tag: it asserts the tag version
848
+ matches `package.json`, runs the `prepublish` gate (`build` + `test`), and
849
+ publishes to npm. The `npm` GitHub Environment gates the publish behind a
850
+ **required-reviewer approval** — approve the run under Actions, and on success
851
+ the new version is live at
852
+ [`@holokai/neuron-sdk`](https://www.npmjs.com/package/@holokai/neuron-sdk).
853
+
854
+ > No `--provenance`: bigbrain is a private repo, so npm's provenance
855
+ > attestation (a public transparency-log link back to the source) adds little
856
+ > and has historically errored from private repos. Trusted-publishing auth is
857
+ > unaffected. Add the flag back if bigbrain ever goes public.
858
+
859
+ The tag→`package.json` assertion means a mismatched tag fails **before** the
860
+ registry is touched — npm refuses to re-upload an existing version, so there's
861
+ no half-published state to clean up.
862
+
863
+ ### Publish auth: npm Trusted Publishing (one-time setup)
864
+
865
+ CI authenticates to npm via **OIDC trusted publishing** — GitHub Actions
866
+ exchanges a short-lived OIDC token for publish rights, so no long-lived
867
+ `NPM_TOKEN` exists to leak. This mirrors the Python SDK's PyPI trusted
868
+ publishing. It requires two one-time setups:
869
+
870
+ 1. **On npmjs.com** (a package admin, once): open the
871
+ [`@holokai/neuron-sdk`](https://www.npmjs.com/package/@holokai/neuron-sdk)
872
+ package → **Settings → Trusted Publisher → GitHub Actions**, and register:
873
+ - Organization / repository: `holok-ai/bigbrain`
874
+ - Workflow filename: `publish-neuron-sdk.yml`
875
+ - Environment: `npm`
876
+ 2. **In this repo** (once): create the **`npm`** GitHub Environment
877
+ (Settings → Environments). Add reviewers/branch protection here if you want a
878
+ manual approval gate before each publish.
879
+
880
+ Until the trusted publisher is registered, the `npm publish` step fails with an
881
+ auth error — that's the expected "not set up yet" signal, not a code bug.
882
+
883
+ ### Manual publish (fallback / first bootstrap)
884
+
885
+ The tag flow is the norm. If you ever need to publish by hand (e.g. to bootstrap
886
+ the very first version before trusted publishing is configured):
887
+
888
+ ```sh
889
+ cd packages/neuron-sdk
890
+ pnpm run publish:npm:dry # inspect the tarball + files list
891
+ pnpm run publish:npm # runs the prepublishOnly build+test gate, then publishes
892
+ ```
893
+
894
+ This uses your own `npm login` credentials and skips the CI approval gate —
895
+ prefer the tag flow for anything a developer will consume.
896
+
897
+ <!-- ci: force full build on the self-hosted runner (HOL-2028 verify); safe to remove -->
@@ -0,0 +1,187 @@
1
+ /** Function returning the current bearer JWT. Called on each POST and SSE
2
+ * open; can refresh tokens internally. The SDK retries once on 401, so
3
+ * refreshing on 401 inside the callback is the right place to do it. */
4
+ type AuthCallback = () => Promise<string> | string;
5
+ /** Logger surface — minimal shape so consumers can plug in pino, console,
6
+ * etc. without an interface mismatch. */
7
+ interface NeuronLogger {
8
+ debug(meta: Record<string, unknown>, msg?: string): void;
9
+ info(meta: Record<string, unknown>, msg?: string): void;
10
+ warn(meta: Record<string, unknown>, msg?: string): void;
11
+ error(meta: Record<string, unknown>, msg?: string): void;
12
+ }
13
+
14
+ /**
15
+ * OAuth endpoint set resolved from RFC 8414 discovery (or the fixed
16
+ * fallback paths when discovery is unreachable — see `discovery.ts`).
17
+ */
18
+ interface OAuthEndpoints {
19
+ tokenEndpoint: string;
20
+ deviceAuthorizationEndpoint: string;
21
+ }
22
+ /**
23
+ * The durable artifact on a neuron host: a revocable Moku OAuth client
24
+ * credential (never a bearer JWT). Field names mirror the wire/JSON
25
+ * shape so the file is greppable against Moku's own vocabulary.
26
+ */
27
+ interface StoredCredential {
28
+ client_id: string;
29
+ client_secret: string;
30
+ token_endpoint: string;
31
+ moku_base_url: string;
32
+ }
33
+ /**
34
+ * How the client proves its identity to Moku's token endpoint.
35
+ *
36
+ * v1 ships `client_secret_post` only (matching Moku's discovery doc).
37
+ * The interface exists so `private_key_jwt` (RFC 7523, HOL-2543) can
38
+ * slot in later without touching `createClientCredentialsAuth` callers:
39
+ * a keypair-backed implementation would append a signed
40
+ * `client_assertion` instead of a shared secret.
41
+ */
42
+ interface ClientAuthenticator {
43
+ /** Apply client authentication to the token-endpoint form body. */
44
+ apply(form: URLSearchParams): void | Promise<void>;
45
+ }
46
+ /** RFC 6749 §2.3.1-style shared-secret client auth, in the body. */
47
+ declare function clientSecretPost(clientId: string, clientSecret: string): ClientAuthenticator;
48
+ /** Successful token-endpoint response (RFC 6749 §5.1). On device-flow
49
+ * redemption Moku additionally delivers the client credential — once. */
50
+ interface TokenResponse {
51
+ access_token: string;
52
+ token_type: string;
53
+ expires_in: number;
54
+ scope?: string;
55
+ client_id?: string;
56
+ client_secret?: string;
57
+ }
58
+ /** Token-endpoint error (`{error, error_description}`, RFC 6749 §5.2). */
59
+ declare class OAuthTokenError extends Error {
60
+ readonly error: string;
61
+ readonly status: number;
62
+ constructor(error: string, status: number, description?: string);
63
+ }
64
+ /**
65
+ * Terminal auth failure: the stored credential no longer mints (revoked,
66
+ * deleted, or rotated away underneath us) or does not exist. The neuron
67
+ * must be re-enrolled; retrying against Moku is pointless and the SDK
68
+ * will not loop on it.
69
+ */
70
+ declare class CredentialRevokedError extends Error {
71
+ constructor(message: string);
72
+ }
73
+ /** Enrollment (device flow) failed terminally. */
74
+ declare class EnrollmentError extends Error {
75
+ readonly code: 'expired_token' | 'access_denied' | 'invalid_response' | 'device_request_failed';
76
+ constructor(message: string, code: 'expired_token' | 'access_denied' | 'invalid_response' | 'device_request_failed');
77
+ }
78
+
79
+ interface ClientCredentialsAuthOptions {
80
+ fetch?: typeof fetch;
81
+ logger?: NeuronLogger;
82
+ /**
83
+ * Refresh this long before the token's `expires_in` elapses. The
84
+ * response's `expires_in` is authoritative (Moku default is 15 min,
85
+ * per-audience cap 1h) — never assume a lifetime.
86
+ */
87
+ refreshBufferMs?: number;
88
+ /**
89
+ * Client-auth method factory. Defaults to `client_secret_post` from the
90
+ * stored credential; a `private_key_jwt` implementation (HOL-2543) can
91
+ * be injected here without changing any other consumer code.
92
+ */
93
+ authenticator?: (credential: StoredCredential) => ClientAuthenticator;
94
+ /** Clock override for tests. */
95
+ now?: () => number;
96
+ }
97
+ /**
98
+ * Steady-state auth for enrolled (headless/enterprise) neurons: returns an
99
+ * `auth()` callback for `HttpTransport` that mints short-lived
100
+ * `aud=bigbrain` access tokens from Moku via the OAuth2 `client_credentials`
101
+ * grant, using the credential file written by `enroll`.
102
+ *
103
+ * Behavior:
104
+ * - In-memory cache driven by the response's `expires_in`, with proactive
105
+ * refresh `refreshBufferMs` before expiry.
106
+ * - Single-flight: concurrent callers share one mint request.
107
+ * - On `invalid_client`, the credential file is re-read once before
108
+ * failing — an operator-rotated secret is picked up without a restart.
109
+ * - If the re-read credential still fails (revoked/deleted client), a
110
+ * `CredentialRevokedError` is thrown; the SDK's transport will not
111
+ * retry-loop against Moku on it. Re-enrollment is required.
112
+ */
113
+ declare function createClientCredentialsAuth(credentialPath: string, opts?: ClientCredentialsAuthOptions): AuthCallback;
114
+
115
+ /** `~/.holokai/neuron-credentials.json` unless the caller overrides. */
116
+ declare function defaultCredentialPath(): string;
117
+ /**
118
+ * Read a stored credential. Returns null when the file does not exist
119
+ * (unenrolled); throws on a present-but-malformed file, which is an
120
+ * operator problem the caller must not paper over.
121
+ */
122
+ declare function readCredentialFile(path: string): StoredCredential | null;
123
+ /** Write the credential with owner-only permissions (0700 dir, 0600 file). */
124
+ declare function writeCredentialFile(path: string, credential: StoredCredential): void;
125
+
126
+ /**
127
+ * Resolve OAuth endpoints for a Moku base URL via RFC 8414 discovery,
128
+ * falling back to the fixed default paths when the metadata document is
129
+ * unreachable or malformed.
130
+ *
131
+ * Served endpoint URLs are re-rooted onto `mokuBaseUrl` (path is taken
132
+ * from the document, origin from the caller): the metadata document
133
+ * derives its URLs from the inbound request URL, which behind a
134
+ * TLS-terminating proxy can name an origin the neuron cannot reach.
135
+ * The caller-supplied base URL is the one address known to work.
136
+ */
137
+ declare function discoverEndpoints(mokuBaseUrl: string, opts?: {
138
+ fetch?: typeof fetch;
139
+ logger?: NeuronLogger;
140
+ }): Promise<OAuthEndpoints>;
141
+
142
+ interface EnrollOptions {
143
+ /** Moku base URL — the only required config (endpoints are discovered). */
144
+ mokuBaseUrl: string;
145
+ /** Display name shown to the approving admin. Required by Moku. */
146
+ clientName: string;
147
+ /** Display hint for the approval screen; defaults to `os.hostname()`. */
148
+ hostname?: string;
149
+ /**
150
+ * Capability types this neuron intends to serve — display hints for the
151
+ * approval screen ONLY. Authorization comes from the org's capability
152
+ * catalog, not from this list (requested ≠ granted).
153
+ */
154
+ capabilityTypes?: string[];
155
+ /** Where to persist the credential. Default: `~/.holokai/neuron-credentials.json`. */
156
+ credentialPath?: string;
157
+ fetch?: typeof fetch;
158
+ logger?: NeuronLogger;
159
+ /** Where user-facing lines (verification URI + user code) go. Default: console.log. */
160
+ out?: (line: string) => void;
161
+ /** Sleep override for tests. */
162
+ sleep?: (ms: number) => Promise<void>;
163
+ }
164
+ interface EnrollResult {
165
+ status: 'already-enrolled' | 'enrolled';
166
+ clientId: string;
167
+ credentialPath: string;
168
+ /** Bootstrap token from the redemption response (only when freshly enrolled). */
169
+ accessToken?: string;
170
+ expiresIn?: number;
171
+ }
172
+ /**
173
+ * One-time enrollment of a headless neuron via Moku's RFC 8628 device
174
+ * authorization grant.
175
+ *
176
+ * Flow: start a device authorization → print the verification URI +
177
+ * user code (container-log friendly) → poll the token endpoint until an
178
+ * org ADMIN approves in Moku → persist the client credential delivered
179
+ * in the redemption token response (delivered exactly once).
180
+ *
181
+ * Idempotent: if a credential file already exists and still mints, this
182
+ * is a no-op reporting `already-enrolled`. A revoked existing credential
183
+ * falls through to a fresh enrollment.
184
+ */
185
+ declare function enroll(opts: EnrollOptions): Promise<EnrollResult>;
186
+
187
+ export { type ClientAuthenticator, type ClientCredentialsAuthOptions, CredentialRevokedError, type EnrollOptions, type EnrollResult, EnrollmentError, type OAuthEndpoints, OAuthTokenError, type StoredCredential, type TokenResponse, clientSecretPost, createClientCredentialsAuth, defaultCredentialPath, discoverEndpoints, enroll, readCredentialFile, writeCredentialFile };
package/dist/auth.d.ts ADDED
@@ -0,0 +1,187 @@
1
+ /** Function returning the current bearer JWT. Called on each POST and SSE
2
+ * open; can refresh tokens internally. The SDK retries once on 401, so
3
+ * refreshing on 401 inside the callback is the right place to do it. */
4
+ type AuthCallback = () => Promise<string> | string;
5
+ /** Logger surface — minimal shape so consumers can plug in pino, console,
6
+ * etc. without an interface mismatch. */
7
+ interface NeuronLogger {
8
+ debug(meta: Record<string, unknown>, msg?: string): void;
9
+ info(meta: Record<string, unknown>, msg?: string): void;
10
+ warn(meta: Record<string, unknown>, msg?: string): void;
11
+ error(meta: Record<string, unknown>, msg?: string): void;
12
+ }
13
+
14
+ /**
15
+ * OAuth endpoint set resolved from RFC 8414 discovery (or the fixed
16
+ * fallback paths when discovery is unreachable — see `discovery.ts`).
17
+ */
18
+ interface OAuthEndpoints {
19
+ tokenEndpoint: string;
20
+ deviceAuthorizationEndpoint: string;
21
+ }
22
+ /**
23
+ * The durable artifact on a neuron host: a revocable Moku OAuth client
24
+ * credential (never a bearer JWT). Field names mirror the wire/JSON
25
+ * shape so the file is greppable against Moku's own vocabulary.
26
+ */
27
+ interface StoredCredential {
28
+ client_id: string;
29
+ client_secret: string;
30
+ token_endpoint: string;
31
+ moku_base_url: string;
32
+ }
33
+ /**
34
+ * How the client proves its identity to Moku's token endpoint.
35
+ *
36
+ * v1 ships `client_secret_post` only (matching Moku's discovery doc).
37
+ * The interface exists so `private_key_jwt` (RFC 7523, HOL-2543) can
38
+ * slot in later without touching `createClientCredentialsAuth` callers:
39
+ * a keypair-backed implementation would append a signed
40
+ * `client_assertion` instead of a shared secret.
41
+ */
42
+ interface ClientAuthenticator {
43
+ /** Apply client authentication to the token-endpoint form body. */
44
+ apply(form: URLSearchParams): void | Promise<void>;
45
+ }
46
+ /** RFC 6749 §2.3.1-style shared-secret client auth, in the body. */
47
+ declare function clientSecretPost(clientId: string, clientSecret: string): ClientAuthenticator;
48
+ /** Successful token-endpoint response (RFC 6749 §5.1). On device-flow
49
+ * redemption Moku additionally delivers the client credential — once. */
50
+ interface TokenResponse {
51
+ access_token: string;
52
+ token_type: string;
53
+ expires_in: number;
54
+ scope?: string;
55
+ client_id?: string;
56
+ client_secret?: string;
57
+ }
58
+ /** Token-endpoint error (`{error, error_description}`, RFC 6749 §5.2). */
59
+ declare class OAuthTokenError extends Error {
60
+ readonly error: string;
61
+ readonly status: number;
62
+ constructor(error: string, status: number, description?: string);
63
+ }
64
+ /**
65
+ * Terminal auth failure: the stored credential no longer mints (revoked,
66
+ * deleted, or rotated away underneath us) or does not exist. The neuron
67
+ * must be re-enrolled; retrying against Moku is pointless and the SDK
68
+ * will not loop on it.
69
+ */
70
+ declare class CredentialRevokedError extends Error {
71
+ constructor(message: string);
72
+ }
73
+ /** Enrollment (device flow) failed terminally. */
74
+ declare class EnrollmentError extends Error {
75
+ readonly code: 'expired_token' | 'access_denied' | 'invalid_response' | 'device_request_failed';
76
+ constructor(message: string, code: 'expired_token' | 'access_denied' | 'invalid_response' | 'device_request_failed');
77
+ }
78
+
79
+ interface ClientCredentialsAuthOptions {
80
+ fetch?: typeof fetch;
81
+ logger?: NeuronLogger;
82
+ /**
83
+ * Refresh this long before the token's `expires_in` elapses. The
84
+ * response's `expires_in` is authoritative (Moku default is 15 min,
85
+ * per-audience cap 1h) — never assume a lifetime.
86
+ */
87
+ refreshBufferMs?: number;
88
+ /**
89
+ * Client-auth method factory. Defaults to `client_secret_post` from the
90
+ * stored credential; a `private_key_jwt` implementation (HOL-2543) can
91
+ * be injected here without changing any other consumer code.
92
+ */
93
+ authenticator?: (credential: StoredCredential) => ClientAuthenticator;
94
+ /** Clock override for tests. */
95
+ now?: () => number;
96
+ }
97
+ /**
98
+ * Steady-state auth for enrolled (headless/enterprise) neurons: returns an
99
+ * `auth()` callback for `HttpTransport` that mints short-lived
100
+ * `aud=bigbrain` access tokens from Moku via the OAuth2 `client_credentials`
101
+ * grant, using the credential file written by `enroll`.
102
+ *
103
+ * Behavior:
104
+ * - In-memory cache driven by the response's `expires_in`, with proactive
105
+ * refresh `refreshBufferMs` before expiry.
106
+ * - Single-flight: concurrent callers share one mint request.
107
+ * - On `invalid_client`, the credential file is re-read once before
108
+ * failing — an operator-rotated secret is picked up without a restart.
109
+ * - If the re-read credential still fails (revoked/deleted client), a
110
+ * `CredentialRevokedError` is thrown; the SDK's transport will not
111
+ * retry-loop against Moku on it. Re-enrollment is required.
112
+ */
113
+ declare function createClientCredentialsAuth(credentialPath: string, opts?: ClientCredentialsAuthOptions): AuthCallback;
114
+
115
+ /** `~/.holokai/neuron-credentials.json` unless the caller overrides. */
116
+ declare function defaultCredentialPath(): string;
117
+ /**
118
+ * Read a stored credential. Returns null when the file does not exist
119
+ * (unenrolled); throws on a present-but-malformed file, which is an
120
+ * operator problem the caller must not paper over.
121
+ */
122
+ declare function readCredentialFile(path: string): StoredCredential | null;
123
+ /** Write the credential with owner-only permissions (0700 dir, 0600 file). */
124
+ declare function writeCredentialFile(path: string, credential: StoredCredential): void;
125
+
126
+ /**
127
+ * Resolve OAuth endpoints for a Moku base URL via RFC 8414 discovery,
128
+ * falling back to the fixed default paths when the metadata document is
129
+ * unreachable or malformed.
130
+ *
131
+ * Served endpoint URLs are re-rooted onto `mokuBaseUrl` (path is taken
132
+ * from the document, origin from the caller): the metadata document
133
+ * derives its URLs from the inbound request URL, which behind a
134
+ * TLS-terminating proxy can name an origin the neuron cannot reach.
135
+ * The caller-supplied base URL is the one address known to work.
136
+ */
137
+ declare function discoverEndpoints(mokuBaseUrl: string, opts?: {
138
+ fetch?: typeof fetch;
139
+ logger?: NeuronLogger;
140
+ }): Promise<OAuthEndpoints>;
141
+
142
+ interface EnrollOptions {
143
+ /** Moku base URL — the only required config (endpoints are discovered). */
144
+ mokuBaseUrl: string;
145
+ /** Display name shown to the approving admin. Required by Moku. */
146
+ clientName: string;
147
+ /** Display hint for the approval screen; defaults to `os.hostname()`. */
148
+ hostname?: string;
149
+ /**
150
+ * Capability types this neuron intends to serve — display hints for the
151
+ * approval screen ONLY. Authorization comes from the org's capability
152
+ * catalog, not from this list (requested ≠ granted).
153
+ */
154
+ capabilityTypes?: string[];
155
+ /** Where to persist the credential. Default: `~/.holokai/neuron-credentials.json`. */
156
+ credentialPath?: string;
157
+ fetch?: typeof fetch;
158
+ logger?: NeuronLogger;
159
+ /** Where user-facing lines (verification URI + user code) go. Default: console.log. */
160
+ out?: (line: string) => void;
161
+ /** Sleep override for tests. */
162
+ sleep?: (ms: number) => Promise<void>;
163
+ }
164
+ interface EnrollResult {
165
+ status: 'already-enrolled' | 'enrolled';
166
+ clientId: string;
167
+ credentialPath: string;
168
+ /** Bootstrap token from the redemption response (only when freshly enrolled). */
169
+ accessToken?: string;
170
+ expiresIn?: number;
171
+ }
172
+ /**
173
+ * One-time enrollment of a headless neuron via Moku's RFC 8628 device
174
+ * authorization grant.
175
+ *
176
+ * Flow: start a device authorization → print the verification URI +
177
+ * user code (container-log friendly) → poll the token endpoint until an
178
+ * org ADMIN approves in Moku → persist the client credential delivered
179
+ * in the redemption token response (delivered exactly once).
180
+ *
181
+ * Idempotent: if a credential file already exists and still mints, this
182
+ * is a no-op reporting `already-enrolled`. A revoked existing credential
183
+ * falls through to a fresh enrollment.
184
+ */
185
+ declare function enroll(opts: EnrollOptions): Promise<EnrollResult>;
186
+
187
+ export { type ClientAuthenticator, type ClientCredentialsAuthOptions, CredentialRevokedError, type EnrollOptions, type EnrollResult, EnrollmentError, type OAuthEndpoints, OAuthTokenError, type StoredCredential, type TokenResponse, clientSecretPost, createClientCredentialsAuth, defaultCredentialPath, discoverEndpoints, enroll, readCredentialFile, writeCredentialFile };