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

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 +448 -79
  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: [
@@ -799,6 +852,145 @@ var getCryptoIdentity = (options) => (options?.client ?? client).get({
799
852
  url: "/crypto/identity",
800
853
  ...options
801
854
  });
855
+ var listSigningCredentials = (options) => (options.client ?? client).get({
856
+ security: [
857
+ {
858
+ scheme: "bearer",
859
+ type: "http"
860
+ },
861
+ {
862
+ name: "X-Moltnet-Session-Token",
863
+ type: "apiKey"
864
+ },
865
+ {
866
+ in: "cookie",
867
+ name: "ory_kratos_session",
868
+ type: "apiKey"
869
+ }
870
+ ],
871
+ url: "/crypto/signing-credentials",
872
+ ...options
873
+ });
874
+ var beginSigningCredentialRegistration = (options) => (options.client ?? client).post({
875
+ security: [{
876
+ name: "X-Moltnet-Session-Token",
877
+ type: "apiKey"
878
+ }, {
879
+ in: "cookie",
880
+ name: "ory_kratos_session",
881
+ type: "apiKey"
882
+ }],
883
+ url: "/crypto/signing-credentials/registrations",
884
+ ...options,
885
+ headers: {
886
+ "Content-Type": "application/json",
887
+ ...options.headers
888
+ }
889
+ });
890
+ var completeSigningCredentialRegistration = (options) => (options.client ?? client).post({
891
+ security: [{
892
+ name: "X-Moltnet-Session-Token",
893
+ type: "apiKey"
894
+ }, {
895
+ in: "cookie",
896
+ name: "ory_kratos_session",
897
+ type: "apiKey"
898
+ }],
899
+ url: "/crypto/signing-credentials/registrations/{id}/complete",
900
+ ...options,
901
+ headers: {
902
+ "Content-Type": "application/json",
903
+ ...options.headers
904
+ }
905
+ });
906
+ var getSigningCredential = (options) => (options.client ?? client).get({
907
+ security: [
908
+ {
909
+ scheme: "bearer",
910
+ type: "http"
911
+ },
912
+ {
913
+ name: "X-Moltnet-Session-Token",
914
+ type: "apiKey"
915
+ },
916
+ {
917
+ in: "cookie",
918
+ name: "ory_kratos_session",
919
+ type: "apiKey"
920
+ }
921
+ ],
922
+ url: "/crypto/signing-credentials/{id}",
923
+ ...options
924
+ });
925
+ var approveSigningCredential = (options) => (options.client ?? client).post({
926
+ security: [
927
+ {
928
+ scheme: "bearer",
929
+ type: "http"
930
+ },
931
+ {
932
+ name: "X-Moltnet-Session-Token",
933
+ type: "apiKey"
934
+ },
935
+ {
936
+ in: "cookie",
937
+ name: "ory_kratos_session",
938
+ type: "apiKey"
939
+ }
940
+ ],
941
+ url: "/crypto/signing-credentials/{id}/approve",
942
+ ...options,
943
+ headers: {
944
+ "Content-Type": "application/json",
945
+ ...options.headers
946
+ }
947
+ });
948
+ var revokeSigningCredential = (options) => (options.client ?? client).post({
949
+ security: [
950
+ {
951
+ scheme: "bearer",
952
+ type: "http"
953
+ },
954
+ {
955
+ name: "X-Moltnet-Session-Token",
956
+ type: "apiKey"
957
+ },
958
+ {
959
+ in: "cookie",
960
+ name: "ory_kratos_session",
961
+ type: "apiKey"
962
+ }
963
+ ],
964
+ url: "/crypto/signing-credentials/{id}/revoke",
965
+ ...options,
966
+ headers: {
967
+ "Content-Type": "application/json",
968
+ ...options.headers
969
+ }
970
+ });
971
+ var suspendSigningCredential = (options) => (options.client ?? client).post({
972
+ security: [
973
+ {
974
+ scheme: "bearer",
975
+ type: "http"
976
+ },
977
+ {
978
+ name: "X-Moltnet-Session-Token",
979
+ type: "apiKey"
980
+ },
981
+ {
982
+ in: "cookie",
983
+ name: "ory_kratos_session",
984
+ type: "apiKey"
985
+ }
986
+ ],
987
+ url: "/crypto/signing-credentials/{id}/suspend",
988
+ ...options,
989
+ headers: {
990
+ "Content-Type": "application/json",
991
+ ...options.headers
992
+ }
993
+ });
802
994
  /**
803
995
  * List signing requests for the authenticated agent.
804
996
  */
@@ -869,6 +1061,54 @@ var getSigningRequest = (options) => (options.client ?? client).get({
869
1061
  url: "/crypto/signing-requests/{id}",
870
1062
  ...options
871
1063
  });
1064
+ var claimSigningRequest = (options) => (options.client ?? client).post({
1065
+ security: [{
1066
+ name: "X-Moltnet-Session-Token",
1067
+ type: "apiKey"
1068
+ }, {
1069
+ in: "cookie",
1070
+ name: "ory_kratos_session",
1071
+ type: "apiKey"
1072
+ }],
1073
+ url: "/crypto/signing-requests/{id}/claim",
1074
+ ...options,
1075
+ headers: {
1076
+ "Content-Type": "application/json",
1077
+ ...options.headers
1078
+ }
1079
+ });
1080
+ var completeSigningRequest = (options) => (options.client ?? client).post({
1081
+ security: [{
1082
+ name: "X-Moltnet-Session-Token",
1083
+ type: "apiKey"
1084
+ }, {
1085
+ in: "cookie",
1086
+ name: "ory_kratos_session",
1087
+ type: "apiKey"
1088
+ }],
1089
+ url: "/crypto/signing-requests/{id}/complete",
1090
+ ...options,
1091
+ headers: {
1092
+ "Content-Type": "application/json",
1093
+ ...options.headers
1094
+ }
1095
+ });
1096
+ var rejectSigningRequest = (options) => (options.client ?? client).post({
1097
+ security: [{
1098
+ name: "X-Moltnet-Session-Token",
1099
+ type: "apiKey"
1100
+ }, {
1101
+ in: "cookie",
1102
+ name: "ory_kratos_session",
1103
+ type: "apiKey"
1104
+ }],
1105
+ url: "/crypto/signing-requests/{id}/reject",
1106
+ ...options,
1107
+ headers: {
1108
+ "Content-Type": "application/json",
1109
+ ...options.headers
1110
+ }
1111
+ });
872
1112
  /**
873
1113
  * Submit a signature for a signing request. The DBOS workflow verifies the signature and updates the request status.
874
1114
  */
@@ -2896,63 +3136,11 @@ function createRateLimitFetch(options) {
2896
3136
  });
2897
3137
  }
2898
3138
  //#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
3139
  //#region ../sdk/src/agent-context.ts
2953
3140
  function unwrapResult(result) {
2954
3141
  if (result.error !== void 0 && result.error !== null) {
2955
3142
  const error = result.error;
3143
+ if (error instanceof MoltNetError) throw error;
2956
3144
  if (isProblemDetails(error)) throw problemToError(error, error.status);
2957
3145
  if (error instanceof Error && result.response === void 0) {
2958
3146
  const networkError = new NetworkError(error.message, { detail: error.cause ? stringifyUnknown(error.cause) : void 0 });
@@ -3075,16 +3263,25 @@ function createAgentKeysNamespace(context) {
3075
3263
  };
3076
3264
  }
3077
3265
  //#endregion
3266
+ //#region ../sdk/src/namespaces/whoami.ts
3267
+ /**
3268
+ * Build a `whoami()` accessor bound to an authenticated context. Returns the
3269
+ * caller's identity and context: `subjectType`, `currentTeamId`, and, for an
3270
+ * agent authenticated via an agent key, its `credentialBinding`.
3271
+ */
3272
+ function createWhoami(context) {
3273
+ const { client, auth } = context;
3274
+ return async () => unwrapResult(await getWhoami({
3275
+ client,
3276
+ auth
3277
+ }));
3278
+ }
3279
+ //#endregion
3078
3280
  //#region ../sdk/src/namespaces/agents.ts
3079
3281
  function createAgentsNamespace(context) {
3080
- const { client, auth } = context;
3282
+ const { client } = context;
3081
3283
  return {
3082
- async whoami() {
3083
- return unwrapResult(await getWhoami({
3084
- client,
3085
- auth
3086
- }));
3087
- },
3284
+ whoami: createWhoami(context),
3088
3285
  async lookup(fingerprint) {
3089
3286
  return unwrapResult(await getAgentProfile({
3090
3287
  client,
@@ -3113,7 +3310,7 @@ function createAuthNamespace(context) {
3113
3310
  }
3114
3311
  //#endregion
3115
3312
  //#region ../sdk/src/namespaces/crypto.ts
3116
- function createCryptoNamespace(context, signingRequests) {
3313
+ function createCryptoNamespace(context, signingRequests, signingCredentials) {
3117
3314
  const { client, auth } = context;
3118
3315
  return {
3119
3316
  async identity() {
@@ -3128,7 +3325,8 @@ function createCryptoNamespace(context, signingRequests) {
3128
3325
  body
3129
3326
  }));
3130
3327
  },
3131
- signingRequests
3328
+ signingRequests,
3329
+ signingCredentials
3132
3330
  };
3133
3331
  }
3134
3332
  //#endregion
@@ -5420,6 +5618,70 @@ function createRuntimeSlotsNamespace(context) {
5420
5618
  };
5421
5619
  }
5422
5620
  //#endregion
5621
+ //#region ../sdk/src/namespaces/signing-credentials.ts
5622
+ function createSigningCredentialsNamespace(context) {
5623
+ const { client, auth } = context;
5624
+ return {
5625
+ async list(query, options) {
5626
+ return unwrapResult(await listSigningCredentials({
5627
+ client,
5628
+ auth,
5629
+ headers: requiredTeamHeaders(options),
5630
+ query
5631
+ }));
5632
+ },
5633
+ async get(id, options) {
5634
+ return unwrapResult(await getSigningCredential({
5635
+ client,
5636
+ auth,
5637
+ headers: requiredTeamHeaders(options),
5638
+ path: { id }
5639
+ }));
5640
+ },
5641
+ async startRegistration(body, options) {
5642
+ return unwrapResult(await beginSigningCredentialRegistration({
5643
+ client,
5644
+ auth,
5645
+ headers: requiredTeamHeaders(options),
5646
+ body
5647
+ }));
5648
+ },
5649
+ async completeRegistration(id, body, options) {
5650
+ return unwrapResult(await completeSigningCredentialRegistration({
5651
+ client,
5652
+ auth,
5653
+ headers: requiredTeamHeaders(options),
5654
+ path: { id },
5655
+ body
5656
+ }));
5657
+ },
5658
+ async approve(id, options) {
5659
+ return unwrapResult(await approveSigningCredential({
5660
+ client,
5661
+ auth,
5662
+ headers: requiredTeamHeaders(options),
5663
+ path: { id }
5664
+ }));
5665
+ },
5666
+ async suspend(id, options) {
5667
+ return unwrapResult(await suspendSigningCredential({
5668
+ client,
5669
+ auth,
5670
+ headers: requiredTeamHeaders(options),
5671
+ path: { id }
5672
+ }));
5673
+ },
5674
+ async revoke(id, options) {
5675
+ return unwrapResult(await revokeSigningCredential({
5676
+ client,
5677
+ auth,
5678
+ headers: requiredTeamHeaders(options),
5679
+ path: { id }
5680
+ }));
5681
+ }
5682
+ };
5683
+ }
5684
+ //#endregion
5423
5685
  //#region ../sdk/src/namespaces/signing-requests.ts
5424
5686
  function createSigningRequestsNamespace(context) {
5425
5687
  const { client, auth } = context;
@@ -5452,6 +5714,33 @@ function createSigningRequestsNamespace(context) {
5452
5714
  path: { id },
5453
5715
  body
5454
5716
  }));
5717
+ },
5718
+ async claim(id, body, options) {
5719
+ return unwrapResult(await claimSigningRequest({
5720
+ client,
5721
+ auth,
5722
+ headers: requiredTeamHeaders(options),
5723
+ path: { id },
5724
+ body
5725
+ }));
5726
+ },
5727
+ async complete(id, body, options) {
5728
+ return unwrapResult(await completeSigningRequest({
5729
+ client,
5730
+ auth,
5731
+ headers: requiredTeamHeaders(options),
5732
+ path: { id },
5733
+ body
5734
+ }));
5735
+ },
5736
+ async reject(id, body, options) {
5737
+ return unwrapResult(await rejectSigningRequest({
5738
+ client,
5739
+ auth,
5740
+ headers: requiredTeamHeaders(options),
5741
+ path: { id },
5742
+ body
5743
+ }));
5455
5744
  }
5456
5745
  };
5457
5746
  }
@@ -15664,7 +15953,7 @@ function createAgent(options) {
15664
15953
  packs: createPacksNamespace(context),
15665
15954
  entries: createEntriesNamespace(context),
15666
15955
  agents: createAgentsNamespace(context),
15667
- crypto: createCryptoNamespace(context, createSigningRequestsNamespace(context)),
15956
+ crypto: createCryptoNamespace(context, createSigningRequestsNamespace(context), createSigningCredentialsNamespace(context)),
15668
15957
  vouch: createVouchNamespace(context),
15669
15958
  auth: createAuthNamespace(context),
15670
15959
  recovery: createRecoveryNamespace(context),
@@ -15677,22 +15966,37 @@ function createAgent(options) {
15677
15966
  runtimeSlots: createRuntimeSlotsNamespace(context),
15678
15967
  runtimeSessions: createRuntimeSessionsNamespace(context),
15679
15968
  client,
15680
- getToken: () => tokenManager.getToken()
15969
+ getToken: () => {
15970
+ if (tokenManager) return tokenManager.getToken();
15971
+ if (auth) return auth();
15972
+ return Promise.reject(new MoltNetError("No token source configured", { code: "NO_TOKEN_SOURCE" }));
15973
+ }
15681
15974
  };
15682
15975
  }
15683
15976
  //#endregion
15684
15977
  //#region ../sdk/src/config.ts
15685
15978
  /**
15686
15979
  * Read MoltNet credentials from environment variables.
15687
- * Reads MOLTNET_CLIENT_ID, MOLTNET_CLIENT_SECRET, and MOLTNET_API_URL.
15980
+ * Reads MOLTNET_CLIENT_ID, MOLTNET_CLIENT_SECRET, MOLTNET_API_URL, and
15981
+ * MOLTNET_AGENT_KEY.
15688
15982
  */
15689
15983
  function readEnvCredentials() {
15690
15984
  return {
15691
15985
  clientId: process.env.MOLTNET_CLIENT_ID,
15692
15986
  clientSecret: process.env.MOLTNET_CLIENT_SECRET,
15693
- apiUrl: process.env.MOLTNET_API_URL
15987
+ apiUrl: process.env.MOLTNET_API_URL,
15988
+ agentKey: process.env.MOLTNET_AGENT_KEY
15694
15989
  };
15695
15990
  }
15991
+ /**
15992
+ * Resolve the API base URL from an ordered list of candidates, falling back to
15993
+ * the hosted default, with any trailing slash stripped. Candidates are tried in
15994
+ * order — pass them highest-precedence first (typically explicit option, then
15995
+ * env, then config file) so every caller shares one precedence rule.
15996
+ */
15997
+ function normalizeApiUrl(...candidates) {
15998
+ return (candidates.find((c) => c) ?? "https://api.themolt.net").replace(/\/$/, "");
15999
+ }
15696
16000
  //#endregion
15697
16001
  //#region ../sdk/src/credentials.ts
15698
16002
  function getConfigDir() {
@@ -15756,6 +16060,32 @@ function createRetryFetch(tokenManager, options) {
15756
16060
  return doFetch(init);
15757
16061
  };
15758
16062
  }
16063
+ /**
16064
+ * Create a fetch wrapper for agent-key (static-bearer) authentication.
16065
+ *
16066
+ * A static key cannot be refreshed, so there is no token-invalidation/replay
16067
+ * (that half of {@link createRetryFetch} is intentionally omitted). What remains
16068
+ * is orthogonal to token refresh and still matters for a long-running client:
16069
+ *
16070
+ * - **429**: delegates to `createRateLimitFetch` (Retry-After / backoff), unless
16071
+ * `retry` is `false`.
16072
+ * - **401**: the key was rejected (revoked, expired, or not authorized for the
16073
+ * requested team). Rather than silently returning a bare 401 on every call,
16074
+ * throw an actionable {@link AuthenticationError}. The key value is never
16075
+ * included in the message.
16076
+ */
16077
+ function createAgentKeyFetch(retry) {
16078
+ const rateLimitFetch = retry === false ? fetch : createRateLimitFetch({
16079
+ maxRetries: retry?.maxRateLimitRetries,
16080
+ baseDelayMs: retry?.baseDelayMs,
16081
+ maxDelayMs: retry?.maxDelayMs
16082
+ });
16083
+ return async (input, init) => {
16084
+ const response = await rateLimitFetch(input, init);
16085
+ 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 });
16086
+ return response;
16087
+ };
16088
+ }
15759
16089
  //#endregion
15760
16090
  //#region ../sdk/src/token.ts
15761
16091
  var TokenManager = class {
@@ -15815,37 +16145,76 @@ var TokenManager = class {
15815
16145
  };
15816
16146
  //#endregion
15817
16147
  //#region ../sdk/src/connect.ts
15818
- var DEFAULT_API_URL = "https://api.themolt.net";
15819
- async function resolveCredentials(options) {
16148
+ async function resolveConnection(options) {
16149
+ const env = readEnvCredentials();
16150
+ const explicitAgentKey = options.agentKey?.trim();
16151
+ if (explicitAgentKey) {
16152
+ const config = await readConfig(options.configDir);
16153
+ return {
16154
+ mode: "agentKey",
16155
+ agentKey: explicitAgentKey,
16156
+ apiUrl: normalizeApiUrl(options.apiUrl, env.apiUrl, config?.endpoints?.api)
16157
+ };
16158
+ }
15820
16159
  if (options.clientId && options.clientSecret) return {
16160
+ mode: "oauth2",
15821
16161
  clientId: options.clientId,
15822
16162
  clientSecret: options.clientSecret,
15823
- apiUrl: (options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "")
16163
+ apiUrl: normalizeApiUrl(options.apiUrl, env.apiUrl)
15824
16164
  };
15825
- const env = readEnvCredentials();
16165
+ const envAgentKey = env.agentKey?.trim();
16166
+ if (envAgentKey) {
16167
+ const config = await readConfig(options.configDir);
16168
+ return {
16169
+ mode: "agentKey",
16170
+ agentKey: envAgentKey,
16171
+ apiUrl: normalizeApiUrl(options.apiUrl, env.apiUrl, config?.endpoints?.api)
16172
+ };
16173
+ }
15826
16174
  if (env.clientId && env.clientSecret) return {
16175
+ mode: "oauth2",
15827
16176
  clientId: env.clientId,
15828
16177
  clientSecret: env.clientSecret,
15829
- apiUrl: (env.apiUrl ?? options.apiUrl ?? DEFAULT_API_URL).replace(/\/$/, "")
16178
+ apiUrl: normalizeApiUrl(options.apiUrl, env.apiUrl)
15830
16179
  };
15831
16180
  const config = await readConfig(options.configDir);
15832
16181
  if (config?.oauth2?.client_id && config?.oauth2?.client_secret) return {
16182
+ mode: "oauth2",
15833
16183
  clientId: config.oauth2.client_id,
15834
16184
  clientSecret: config.oauth2.client_secret,
15835
- apiUrl: (options.apiUrl ?? config.endpoints?.api ?? DEFAULT_API_URL).replace(/\/$/, "")
16185
+ apiUrl: normalizeApiUrl(options.apiUrl, config.endpoints?.api)
15836
16186
  };
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" });
16187
+ 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
16188
  }
15839
16189
  /**
15840
16190
  * Connect to MoltNet and return an authenticated Agent facade.
15841
16191
  *
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`)
16192
+ * Credential resolution, highest precedence first. Explicit in-code options —
16193
+ * of either kind always win over the environment and config file:
16194
+ * 1. Explicit `agentKey` option agent-key mode (static bearer)
16195
+ * 2. Explicit `clientId` / `clientSecret` → OAuth2 client-credentials
16196
+ * 3. `MOLTNET_AGENT_KEY` env → agent-key mode
16197
+ * 4. `MOLTNET_CLIENT_ID` / `MOLTNET_CLIENT_SECRET` env → OAuth2
16198
+ * 5. Config file (`~/.config/moltnet/moltnet.json`) → OAuth2
16199
+ *
16200
+ * In agent-key mode the key is sent directly as a bearer token — no OAuth2
16201
+ * round-trip — and 429 backoff still applies; a rejected key surfaces an
16202
+ * `AuthenticationError`.
15846
16203
  */
15847
16204
  async function connect(options = {}) {
15848
- const creds = await resolveCredentials(options);
16205
+ const resolved = await resolveConnection(options);
16206
+ if (resolved.mode === "agentKey") {
16207
+ const client = createClient({
16208
+ baseUrl: resolved.apiUrl,
16209
+ fetch: createAgentKeyFetch(options.retry)
16210
+ });
16211
+ const auth = () => Promise.resolve(resolved.agentKey);
16212
+ return createAgent({
16213
+ client,
16214
+ auth
16215
+ });
16216
+ }
16217
+ const creds = resolved;
15849
16218
  const autoToken = options.autoToken ?? true;
15850
16219
  const tokenManager = new TokenManager({
15851
16220
  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.5",
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.125.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^22.19.0",