@themoltnet/node-red-contrib-core 0.12.3 → 0.12.4

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 (2) hide show
  1. package/dist/nodes/src.js +166 -76
  2. package/package.json +2 -2
package/dist/nodes/src.js CHANGED
@@ -4,6 +4,59 @@ import crypto, { createHash as createHash$1 } from "crypto";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import { join } from "node:path";
6
6
  import { homedir } from "node:os";
7
+ //#region ../sdk/src/errors.ts
8
+ var MoltNetError = class extends Error {
9
+ code;
10
+ statusCode;
11
+ detail;
12
+ /**
13
+ * Populated when the server returned a `VALIDATION_FAILED` problem
14
+ * (status 400) with field-level errors. Empty / undefined for every
15
+ * other problem kind. Proposer scripts surface these to operators so
16
+ * they don't have to re-run with curl to see what was rejected.
17
+ */
18
+ validationErrors;
19
+ constructor(message, options) {
20
+ super(message);
21
+ this.name = "MoltNetError";
22
+ this.code = options.code;
23
+ this.statusCode = options.statusCode;
24
+ this.detail = options.detail;
25
+ this.validationErrors = options.validationErrors;
26
+ }
27
+ };
28
+ var NetworkError = class extends MoltNetError {
29
+ constructor(message, options) {
30
+ super(message, {
31
+ code: "NETWORK_ERROR",
32
+ detail: options?.detail
33
+ });
34
+ this.name = "NetworkError";
35
+ }
36
+ };
37
+ var AuthenticationError = class extends MoltNetError {
38
+ constructor(message, options) {
39
+ super(message, {
40
+ code: "AUTH_FAILED",
41
+ statusCode: options?.statusCode,
42
+ detail: options?.detail
43
+ });
44
+ this.name = "AuthenticationError";
45
+ }
46
+ };
47
+ function problemToError(problem, statusCode) {
48
+ const title = problem.title ?? "Request failed";
49
+ const message = problem.detail ? `${title}: ${problem.detail}` : title;
50
+ const rawErrors = problem.errors;
51
+ const validationErrors = Array.isArray(rawErrors) ? rawErrors.filter((e) => typeof e === "object" && e !== null && typeof e.field === "string" && typeof e.message === "string") : void 0;
52
+ return new MoltNetError(message, {
53
+ code: problem.type ?? problem.code ?? "UNKNOWN",
54
+ statusCode,
55
+ detail: problem.detail,
56
+ validationErrors
57
+ });
58
+ }
59
+ //#endregion
7
60
  //#region ../api-client/src/generated/core/bodySerializer.gen.ts
8
61
  var jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
9
62
  Object.entries({
@@ -716,7 +769,7 @@ var rotateAgentKey = (options) => (options.client ?? client).post({
716
769
  ...options
717
770
  });
718
771
  /**
719
- * Get the authenticated agent identity (requires bearer token).
772
+ * Get the authenticated caller identity and context. Works for both agents (identity plus, under agent-key auth, the credential binding) and humans, via bearer, session, or cookie auth.
720
773
  */
721
774
  var getWhoami = (options) => (options?.client ?? client).get({
722
775
  security: [
@@ -2896,63 +2949,11 @@ function createRateLimitFetch(options) {
2896
2949
  });
2897
2950
  }
2898
2951
  //#endregion
2899
- //#region ../sdk/src/errors.ts
2900
- var MoltNetError = class extends Error {
2901
- code;
2902
- statusCode;
2903
- detail;
2904
- /**
2905
- * Populated when the server returned a `VALIDATION_FAILED` problem
2906
- * (status 400) with field-level errors. Empty / undefined for every
2907
- * other problem kind. Proposer scripts surface these to operators so
2908
- * they don't have to re-run with curl to see what was rejected.
2909
- */
2910
- validationErrors;
2911
- constructor(message, options) {
2912
- super(message);
2913
- this.name = "MoltNetError";
2914
- this.code = options.code;
2915
- this.statusCode = options.statusCode;
2916
- this.detail = options.detail;
2917
- this.validationErrors = options.validationErrors;
2918
- }
2919
- };
2920
- var NetworkError = class extends MoltNetError {
2921
- constructor(message, options) {
2922
- super(message, {
2923
- code: "NETWORK_ERROR",
2924
- detail: options?.detail
2925
- });
2926
- this.name = "NetworkError";
2927
- }
2928
- };
2929
- var AuthenticationError = class extends MoltNetError {
2930
- constructor(message, options) {
2931
- super(message, {
2932
- code: "AUTH_FAILED",
2933
- statusCode: options?.statusCode,
2934
- detail: options?.detail
2935
- });
2936
- this.name = "AuthenticationError";
2937
- }
2938
- };
2939
- function problemToError(problem, statusCode) {
2940
- const title = problem.title ?? "Request failed";
2941
- const message = problem.detail ? `${title}: ${problem.detail}` : title;
2942
- const rawErrors = problem.errors;
2943
- const validationErrors = Array.isArray(rawErrors) ? rawErrors.filter((e) => typeof e === "object" && e !== null && typeof e.field === "string" && typeof e.message === "string") : void 0;
2944
- return new MoltNetError(message, {
2945
- code: problem.type ?? problem.code ?? "UNKNOWN",
2946
- statusCode,
2947
- detail: problem.detail,
2948
- validationErrors
2949
- });
2950
- }
2951
- //#endregion
2952
2952
  //#region ../sdk/src/agent-context.ts
2953
2953
  function unwrapResult(result) {
2954
2954
  if (result.error !== void 0 && result.error !== null) {
2955
2955
  const error = result.error;
2956
+ if (error instanceof MoltNetError) throw error;
2956
2957
  if (isProblemDetails(error)) throw problemToError(error, error.status);
2957
2958
  if (error instanceof Error && result.response === void 0) {
2958
2959
  const networkError = new NetworkError(error.message, { detail: error.cause ? stringifyUnknown(error.cause) : void 0 });
@@ -3075,16 +3076,25 @@ function createAgentKeysNamespace(context) {
3075
3076
  };
3076
3077
  }
3077
3078
  //#endregion
3079
+ //#region ../sdk/src/namespaces/whoami.ts
3080
+ /**
3081
+ * Build a `whoami()` accessor bound to an authenticated context. Returns the
3082
+ * caller's identity and context: `subjectType`, `currentTeamId`, and, for an
3083
+ * agent authenticated via an agent key, its `credentialBinding`.
3084
+ */
3085
+ function createWhoami(context) {
3086
+ const { client, auth } = context;
3087
+ return async () => unwrapResult(await getWhoami({
3088
+ client,
3089
+ auth
3090
+ }));
3091
+ }
3092
+ //#endregion
3078
3093
  //#region ../sdk/src/namespaces/agents.ts
3079
3094
  function createAgentsNamespace(context) {
3080
- const { client, auth } = context;
3095
+ const { client } = context;
3081
3096
  return {
3082
- async whoami() {
3083
- return unwrapResult(await getWhoami({
3084
- client,
3085
- auth
3086
- }));
3087
- },
3097
+ whoami: createWhoami(context),
3088
3098
  async lookup(fingerprint) {
3089
3099
  return unwrapResult(await getAgentProfile({
3090
3100
  client,
@@ -15677,22 +15687,37 @@ function createAgent(options) {
15677
15687
  runtimeSlots: createRuntimeSlotsNamespace(context),
15678
15688
  runtimeSessions: createRuntimeSessionsNamespace(context),
15679
15689
  client,
15680
- getToken: () => tokenManager.getToken()
15690
+ getToken: () => {
15691
+ if (tokenManager) return tokenManager.getToken();
15692
+ if (auth) return auth();
15693
+ return Promise.reject(new MoltNetError("No token source configured", { code: "NO_TOKEN_SOURCE" }));
15694
+ }
15681
15695
  };
15682
15696
  }
15683
15697
  //#endregion
15684
15698
  //#region ../sdk/src/config.ts
15685
15699
  /**
15686
15700
  * Read MoltNet credentials from environment variables.
15687
- * Reads MOLTNET_CLIENT_ID, MOLTNET_CLIENT_SECRET, and MOLTNET_API_URL.
15701
+ * Reads MOLTNET_CLIENT_ID, MOLTNET_CLIENT_SECRET, MOLTNET_API_URL, and
15702
+ * MOLTNET_AGENT_KEY.
15688
15703
  */
15689
15704
  function readEnvCredentials() {
15690
15705
  return {
15691
15706
  clientId: process.env.MOLTNET_CLIENT_ID,
15692
15707
  clientSecret: process.env.MOLTNET_CLIENT_SECRET,
15693
- apiUrl: process.env.MOLTNET_API_URL
15708
+ apiUrl: process.env.MOLTNET_API_URL,
15709
+ agentKey: process.env.MOLTNET_AGENT_KEY
15694
15710
  };
15695
15711
  }
15712
+ /**
15713
+ * Resolve the API base URL from an ordered list of candidates, falling back to
15714
+ * the hosted default, with any trailing slash stripped. Candidates are tried in
15715
+ * order — pass them highest-precedence first (typically explicit option, then
15716
+ * env, then config file) so every caller shares one precedence rule.
15717
+ */
15718
+ function normalizeApiUrl(...candidates) {
15719
+ return (candidates.find((c) => c) ?? "https://api.themolt.net").replace(/\/$/, "");
15720
+ }
15696
15721
  //#endregion
15697
15722
  //#region ../sdk/src/credentials.ts
15698
15723
  function getConfigDir() {
@@ -15756,6 +15781,32 @@ function createRetryFetch(tokenManager, options) {
15756
15781
  return doFetch(init);
15757
15782
  };
15758
15783
  }
15784
+ /**
15785
+ * Create a fetch wrapper for agent-key (static-bearer) authentication.
15786
+ *
15787
+ * A static key cannot be refreshed, so there is no token-invalidation/replay
15788
+ * (that half of {@link createRetryFetch} is intentionally omitted). What remains
15789
+ * is orthogonal to token refresh and still matters for a long-running client:
15790
+ *
15791
+ * - **429**: delegates to `createRateLimitFetch` (Retry-After / backoff), unless
15792
+ * `retry` is `false`.
15793
+ * - **401**: the key was rejected (revoked, expired, or not authorized for the
15794
+ * requested team). Rather than silently returning a bare 401 on every call,
15795
+ * throw an actionable {@link AuthenticationError}. The key value is never
15796
+ * included in the message.
15797
+ */
15798
+ function createAgentKeyFetch(retry) {
15799
+ const rateLimitFetch = retry === false ? fetch : createRateLimitFetch({
15800
+ maxRetries: retry?.maxRateLimitRetries,
15801
+ baseDelayMs: retry?.baseDelayMs,
15802
+ maxDelayMs: retry?.maxDelayMs
15803
+ });
15804
+ return async (input, init) => {
15805
+ const response = await rateLimitFetch(input, init);
15806
+ if (response.status === 401) throw new AuthenticationError("agent key rejected (401): the key is revoked, expired, or not authorized for the requested team — re-provision the key.", { statusCode: 401 });
15807
+ return response;
15808
+ };
15809
+ }
15759
15810
  //#endregion
15760
15811
  //#region ../sdk/src/token.ts
15761
15812
  var TokenManager = class {
@@ -15815,37 +15866,76 @@ var TokenManager = class {
15815
15866
  };
15816
15867
  //#endregion
15817
15868
  //#region ../sdk/src/connect.ts
15818
- var DEFAULT_API_URL = "https://api.themolt.net";
15819
- async function resolveCredentials(options) {
15869
+ async function resolveConnection(options) {
15870
+ const env = readEnvCredentials();
15871
+ const explicitAgentKey = options.agentKey?.trim();
15872
+ if (explicitAgentKey) {
15873
+ const config = await readConfig(options.configDir);
15874
+ return {
15875
+ mode: "agentKey",
15876
+ agentKey: explicitAgentKey,
15877
+ apiUrl: normalizeApiUrl(options.apiUrl, env.apiUrl, config?.endpoints?.api)
15878
+ };
15879
+ }
15820
15880
  if (options.clientId && options.clientSecret) return {
15881
+ mode: "oauth2",
15821
15882
  clientId: options.clientId,
15822
15883
  clientSecret: options.clientSecret,
15823
- apiUrl: (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "")
15884
+ apiUrl: normalizeApiUrl(options.apiUrl, env.apiUrl)
15824
15885
  };
15825
- const env = readEnvCredentials();
15886
+ const envAgentKey = env.agentKey?.trim();
15887
+ if (envAgentKey) {
15888
+ const config = await readConfig(options.configDir);
15889
+ return {
15890
+ mode: "agentKey",
15891
+ agentKey: envAgentKey,
15892
+ apiUrl: normalizeApiUrl(options.apiUrl, env.apiUrl, config?.endpoints?.api)
15893
+ };
15894
+ }
15826
15895
  if (env.clientId && env.clientSecret) return {
15896
+ mode: "oauth2",
15827
15897
  clientId: env.clientId,
15828
15898
  clientSecret: env.clientSecret,
15829
- apiUrl: (env.apiUrl ?? options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "")
15899
+ apiUrl: normalizeApiUrl(options.apiUrl, env.apiUrl)
15830
15900
  };
15831
15901
  const config = await readConfig(options.configDir);
15832
15902
  if (config?.oauth2?.client_id && config?.oauth2?.client_secret) return {
15903
+ mode: "oauth2",
15833
15904
  clientId: config.oauth2.client_id,
15834
15905
  clientSecret: config.oauth2.client_secret,
15835
- apiUrl: (options.apiUrl ?? config.endpoints?.api ?? DEFAULT_API_URL).replace(/\/$/, "")
15906
+ apiUrl: normalizeApiUrl(options.apiUrl, config.endpoints?.api)
15836
15907
  };
15837
- throw new MoltNetError("No credentials found. Provide clientId/clientSecret, set MOLTNET_CLIENT_ID/MOLTNET_CLIENT_SECRET env vars, or run `moltnet register` first.", { code: "NO_CREDENTIALS" });
15908
+ throw new MoltNetError("No credentials found. Provide an agentKey / MOLTNET_AGENT_KEY, clientId/clientSecret, set MOLTNET_CLIENT_ID/MOLTNET_CLIENT_SECRET, or run `moltnet register` first.", { code: "NO_CREDENTIALS" });
15838
15909
  }
15839
15910
  /**
15840
15911
  * Connect to MoltNet and return an authenticated Agent facade.
15841
15912
  *
15842
- * Credential resolution order:
15843
- * 1. Explicit `clientId` / `clientSecret` in options
15844
- * 2. `MOLTNET_CLIENT_ID` / `MOLTNET_CLIENT_SECRET` environment variables
15845
- * 3. Config file (`~/.config/moltnet/moltnet.json`)
15913
+ * Credential resolution, highest precedence first. Explicit in-code options —
15914
+ * of either kind always win over the environment and config file:
15915
+ * 1. Explicit `agentKey` option agent-key mode (static bearer)
15916
+ * 2. Explicit `clientId` / `clientSecret` → OAuth2 client-credentials
15917
+ * 3. `MOLTNET_AGENT_KEY` env → agent-key mode
15918
+ * 4. `MOLTNET_CLIENT_ID` / `MOLTNET_CLIENT_SECRET` env → OAuth2
15919
+ * 5. Config file (`~/.config/moltnet/moltnet.json`) → OAuth2
15920
+ *
15921
+ * In agent-key mode the key is sent directly as a bearer token — no OAuth2
15922
+ * round-trip — and 429 backoff still applies; a rejected key surfaces an
15923
+ * `AuthenticationError`.
15846
15924
  */
15847
15925
  async function connect(options = {}) {
15848
- const creds = await resolveCredentials(options);
15926
+ const resolved = await resolveConnection(options);
15927
+ if (resolved.mode === "agentKey") {
15928
+ const client = createClient({
15929
+ baseUrl: resolved.apiUrl,
15930
+ fetch: createAgentKeyFetch(options.retry)
15931
+ });
15932
+ const auth = () => Promise.resolve(resolved.agentKey);
15933
+ return createAgent({
15934
+ client,
15935
+ auth
15936
+ });
15937
+ }
15938
+ const creds = resolved;
15849
15939
  const autoToken = options.autoToken ?? true;
15850
15940
  const tokenManager = new TokenManager({
15851
15941
  clientId: creds.clientId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@themoltnet/node-red-contrib-core",
3
- "version": "0.12.3",
3
+ "version": "0.12.4",
4
4
  "type": "module",
5
5
  "description": "Node-RED nodes for the MoltNet API",
6
6
  "keywords": [
@@ -46,7 +46,7 @@
46
46
  },
47
47
  "main": "dist/nodes/agent.js",
48
48
  "dependencies": {
49
- "@themoltnet/sdk": "0.123.0"
49
+ "@themoltnet/sdk": "0.124.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^22.19.0",