@aifabrix/builder 2.40.0 → 2.41.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 (108) hide show
  1. package/README.md +7 -5
  2. package/integration/hubspot/test.js +1 -1
  3. package/jest.config.manual.js +29 -0
  4. package/lib/api/credential.api.js +40 -0
  5. package/lib/api/dev.api.js +423 -0
  6. package/lib/api/types/credential.types.js +23 -0
  7. package/lib/api/types/dev.types.js +140 -0
  8. package/lib/app/config.js +21 -0
  9. package/lib/app/down.js +2 -1
  10. package/lib/app/index.js +9 -0
  11. package/lib/app/push.js +36 -12
  12. package/lib/app/readme.js +1 -3
  13. package/lib/app/run-env-compose.js +201 -0
  14. package/lib/app/run-helpers.js +121 -118
  15. package/lib/app/run.js +148 -28
  16. package/lib/app/show.js +5 -2
  17. package/lib/build/index.js +11 -3
  18. package/lib/cli/setup-app.js +140 -14
  19. package/lib/cli/setup-auth.js +1 -0
  20. package/lib/cli/setup-dev.js +180 -17
  21. package/lib/cli/setup-environment.js +4 -2
  22. package/lib/cli/setup-external-system.js +71 -21
  23. package/lib/cli/setup-infra.js +29 -2
  24. package/lib/cli/setup-secrets.js +52 -5
  25. package/lib/cli/setup-utility.js +19 -4
  26. package/lib/commands/app-install.js +172 -0
  27. package/lib/commands/app-shell.js +75 -0
  28. package/lib/commands/app-test.js +282 -0
  29. package/lib/commands/app.js +1 -1
  30. package/lib/commands/auth-status.js +36 -3
  31. package/lib/commands/dev-cli-handlers.js +141 -0
  32. package/lib/commands/dev-down.js +114 -0
  33. package/lib/commands/dev-init.js +309 -0
  34. package/lib/commands/secrets-list.js +118 -0
  35. package/lib/commands/secrets-remove.js +97 -0
  36. package/lib/commands/secrets-set.js +30 -17
  37. package/lib/commands/secrets-validate.js +50 -0
  38. package/lib/commands/up-dataplane.js +2 -2
  39. package/lib/commands/up-miso.js +0 -25
  40. package/lib/commands/upload.js +26 -1
  41. package/lib/core/admin-secrets.js +96 -0
  42. package/lib/core/secrets-ensure.js +378 -0
  43. package/lib/core/secrets-env-write.js +157 -0
  44. package/lib/core/secrets.js +147 -81
  45. package/lib/datasource/field-reference-validator.js +91 -0
  46. package/lib/datasource/validate.js +21 -3
  47. package/lib/deployment/environment-config.js +137 -0
  48. package/lib/deployment/environment.js +21 -98
  49. package/lib/deployment/push.js +32 -2
  50. package/lib/external-system/download.js +7 -0
  51. package/lib/external-system/test-auth.js +7 -3
  52. package/lib/external-system/test.js +5 -1
  53. package/lib/generator/index.js +174 -25
  54. package/lib/generator/wizard.js +13 -1
  55. package/lib/infrastructure/helpers.js +103 -20
  56. package/lib/infrastructure/index.js +88 -10
  57. package/lib/infrastructure/services.js +70 -15
  58. package/lib/schema/application-schema.json +24 -3
  59. package/lib/schema/external-system.schema.json +435 -413
  60. package/lib/utils/api.js +3 -3
  61. package/lib/utils/app-register-auth.js +25 -3
  62. package/lib/utils/cli-utils.js +20 -0
  63. package/lib/utils/compose-generator.js +76 -75
  64. package/lib/utils/compose-handlebars-helpers.js +43 -0
  65. package/lib/utils/compose-vector-helper.js +18 -0
  66. package/lib/utils/config-paths.js +127 -2
  67. package/lib/utils/credential-secrets-env.js +267 -0
  68. package/lib/utils/dev-cert-helper.js +122 -0
  69. package/lib/utils/device-code-helpers.js +224 -0
  70. package/lib/utils/device-code.js +37 -336
  71. package/lib/utils/docker-build.js +40 -8
  72. package/lib/utils/env-copy.js +83 -13
  73. package/lib/utils/env-map.js +35 -5
  74. package/lib/utils/env-template.js +6 -5
  75. package/lib/utils/error-formatters/http-status-errors.js +20 -1
  76. package/lib/utils/help-builder.js +15 -2
  77. package/lib/utils/infra-status.js +30 -1
  78. package/lib/utils/local-secrets.js +7 -52
  79. package/lib/utils/mutagen-install.js +195 -0
  80. package/lib/utils/mutagen.js +146 -0
  81. package/lib/utils/paths.js +49 -33
  82. package/lib/utils/port-resolver.js +28 -16
  83. package/lib/utils/remote-dev-auth.js +38 -0
  84. package/lib/utils/remote-docker-env.js +43 -0
  85. package/lib/utils/remote-secrets-loader.js +60 -0
  86. package/lib/utils/secrets-generator.js +94 -6
  87. package/lib/utils/secrets-helpers.js +33 -25
  88. package/lib/utils/secrets-path.js +2 -2
  89. package/lib/utils/secrets-utils.js +52 -1
  90. package/lib/utils/secrets-validation.js +84 -0
  91. package/lib/utils/ssh-key-helper.js +116 -0
  92. package/lib/utils/token-manager-messages.js +90 -0
  93. package/lib/utils/token-manager.js +5 -4
  94. package/lib/utils/variable-transformer.js +3 -3
  95. package/lib/validation/validate.js +1 -1
  96. package/lib/validation/validator.js +65 -0
  97. package/package.json +4 -2
  98. package/scripts/install-local.js +34 -15
  99. package/templates/README.md +0 -1
  100. package/templates/applications/README.md.hbs +4 -4
  101. package/templates/applications/dataplane/application.yaml +5 -4
  102. package/templates/applications/dataplane/env.template +12 -7
  103. package/templates/applications/keycloak/env.template +2 -0
  104. package/templates/applications/miso-controller/application.yaml +1 -0
  105. package/templates/applications/miso-controller/env.template +11 -9
  106. package/templates/external-system/external-system.json.hbs +1 -16
  107. package/templates/python/docker-compose.hbs +49 -23
  108. package/templates/typescript/docker-compose.hbs +48 -22
package/README.md CHANGED
@@ -16,7 +16,7 @@ Install the AI Fabrix platform and test it locally. Then add external integratio
16
16
  - **Full lifecycle in your version control:** Configuration, apps, and integrations live in your own VCS (GitHub, GitLab, Azure DevOps).
17
17
  - **One tool from day one:** Single CLI for local infra, app and integration creation, build, run, and deploy—same workflow for apps and integrations.
18
18
  - **Consistency and production readiness:** Schema-driven; deploy apps and integrations to the same controller/dataplane; production-ready secrets with `kv://` and Azure Key Vault.
19
- - **Application development:** Use **[miso-client](https://github.com/esystemsdev/aifabrix-miso-client)** for TypeScript and Python to talk to the dataplane and controller (see [templates/applications/dataplane/README.md](templates/applications/dataplane/README.md) and the repo for usage).
19
+ - **Application development:** Use **[miso-client](https://github.com/esystemsdev/aifabrix-miso-client)** (TypeScript and Python) to talk to the dataplane and controller. The repo includes both TypeScript and Python SDKs; see [templates/applications/dataplane/README.md](templates/applications/dataplane/README.md) and the repo for usage.
20
20
 
21
21
  ---
22
22
 
@@ -34,7 +34,7 @@ Install the AI Fabrix platform and test it locally. Then add external integratio
34
34
  npm install -g @aifabrix/builder
35
35
  ```
36
36
 
37
- **Alias:** You can use `aifx` instead of `aifabrix` in any command.
37
+ **Alias:** You can use `af` instead of `aifabrix` in any command.
38
38
 
39
39
  ---
40
40
 
@@ -48,6 +48,8 @@ Get the platform running locally so you can try it.
48
48
  aifabrix up-infra
49
49
  ```
50
50
 
51
+ First-time run creates required infra secrets automatically. Use `aifabrix up-infra --adminPwd <password>` to set a custom admin password for Postgres, pgAdmin, and Redis Commander.
52
+
51
53
  2. **Start the platform** (Keycloak, Miso Controller, Dataplane) from community images:
52
54
 
53
55
  ```bash
@@ -60,12 +62,12 @@ Get the platform running locally so you can try it.
60
62
 
61
63
  - **OpenAI:** set your API key:
62
64
  ```bash
63
- aifabrix secrets set secrets-openaiApiKeyVault <your-openai-secret-key>
65
+ aifabrix secret set secrets-openaiApiKeyVault <your-openai-secret-key>
64
66
  ```
65
67
  - **Azure OpenAI:** set endpoint and API key:
66
68
  ```bash
67
- aifabrix secrets set azure-openaiapi-urlKeyVault <your-azure-openai-endpoint-url>
68
- aifabrix secrets set secrets-azureOpenaiApiKeyVault <your-azure-openai-secret-key>
69
+ aifabrix secret set azure-openaiapi-urlKeyVault <your-azure-openai-endpoint-url>
70
+ aifabrix secret set secrets-azureOpenaiApiKeyVault <your-azure-openai-secret-key>
69
71
  ```
70
72
 
71
73
  Secrets are stored in `~/.aifabrix/secrets.local.yaml` or the file from `aifabrix-secrets` in your config (e.g. `builder/secrets.local.yaml`).
@@ -281,7 +281,7 @@ async function loadEnvFile(envPath, options) {
281
281
  /**
282
282
  * Load test config (controller, environment, dataplane, openapi file).
283
283
  * Reads integration/hubspot/.env; missing CONTROLLER_URL/ENVIRONMENT fall back to
284
- * the same resolution as the CLI (aifx auth status) so tests use the same controller.
284
+ * the same resolution as the CLI (af auth status) so tests use the same controller.
285
285
  * @async
286
286
  * @function loadTestConfigFromEnv
287
287
  * @returns {Promise<Object>} Context with controllerUrl, environment, dataplaneUrl, openapiFile
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Jest Configuration for Manual Tests (real API calls)
3
+ * Runs only tests/manual; requires user to be logged in (validated before run).
4
+ *
5
+ * @fileoverview Jest config for manual tests that call real Controller/Dataplane APIs
6
+ * @author AI Fabrix Team
7
+ * @version 2.0.0
8
+ */
9
+
10
+ const baseProject = require('./jest.config').projects[0];
11
+
12
+ module.exports = {
13
+ projects: [
14
+ {
15
+ ...baseProject,
16
+ displayName: 'manual',
17
+ testMatch: [
18
+ '**/tests/manual/**/*.test.js'
19
+ ],
20
+ testPathIgnorePatterns: [
21
+ '/node_modules/',
22
+ '\\\\node_modules\\\\'
23
+ ],
24
+ setupFilesAfterEnv: ['<rootDir>/tests/manual/setup.js'],
25
+ testTimeout: 60000,
26
+ maxWorkers: 1
27
+ }
28
+ ]
29
+ };
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @fileoverview Credential API functions (Dataplane secret store)
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ const { ApiClient } = require('./index');
8
+
9
+ const CREDENTIAL_SECRET_ENDPOINT = '/api/v1/credential/secret';
10
+
11
+ /**
12
+ * Store credential secrets in the dataplane secret store.
13
+ * Values are encrypted at rest by the dataplane; send plain values only (no kv:// as value).
14
+ *
15
+ * POST /api/v1/credential/secret
16
+ * @requiresPermission {Dataplane} credential:create
17
+ * @async
18
+ * @function storeCredentialSecrets
19
+ * @param {string} dataplaneUrl - Dataplane base URL
20
+ * @param {Object} authConfig - Authentication configuration (Bearer token required)
21
+ * @param {Array<{ key: string, value: string }>} items - Secret items (key = kv path, value = plain)
22
+ * @returns {Promise<{ stored?: number, success?: boolean, error?: string }>} Secret store response
23
+ * @throws {Error} If request fails (non-2xx) and caller may handle 403/401 as warning
24
+ */
25
+ async function storeCredentialSecrets(dataplaneUrl, authConfig, items) {
26
+ if (!dataplaneUrl || typeof dataplaneUrl !== 'string') {
27
+ throw new Error('dataplaneUrl is required and must be a string');
28
+ }
29
+ if (!items || !Array.isArray(items) || items.length === 0) {
30
+ return { stored: 0 };
31
+ }
32
+ const client = new ApiClient(dataplaneUrl, authConfig);
33
+ return await client.post(CREDENTIAL_SECRET_ENDPOINT, {
34
+ body: items
35
+ });
36
+ }
37
+
38
+ module.exports = {
39
+ storeCredentialSecrets
40
+ };
@@ -0,0 +1,423 @@
1
+ /**
2
+ * @fileoverview Builder Server (dev) API – issue-cert, settings, users, SSH keys, secrets.
3
+ * First call: issue-cert is public (no client cert). All other routes require client certificate:
4
+ * when clientKeyPem is provided, requests use mTLS (TLS client cert); otherwise X-Client-Cert header only.
5
+ * @author AI Fabrix Team
6
+ * @version 2.0.0
7
+ */
8
+
9
+ const https = require('https');
10
+ const { makeApiCall } = require('../utils/api');
11
+
12
+ const DEFAULT_TIMEOUT_MS = 15000;
13
+
14
+ /**
15
+ * Encode PEM for use in X-Client-Cert header. HTTP header values must not contain newlines;
16
+ * we send certPem as base64: Buffer.from(pem, 'utf8').toString('base64').
17
+ * Server should decode with Buffer.from(headerVal, 'base64').toString('utf8').
18
+ * @param {string} clientCertPem - PEM-encoded client certificate
19
+ * @returns {string} Base64-encoded PEM for header
20
+ */
21
+ function encodeCertForHeader(clientCertPem) {
22
+ if (!clientCertPem || typeof clientCertPem !== 'string') return '';
23
+ return Buffer.from(clientCertPem, 'utf8').toString('base64');
24
+ }
25
+
26
+ /**
27
+ * Normalize base URL (no trailing slash)
28
+ * @param {string} serverUrl - Base URL of Builder Server
29
+ * @returns {string} Normalized URL
30
+ */
31
+ function normalizeBaseUrl(serverUrl) {
32
+ if (!serverUrl || typeof serverUrl !== 'string') {
33
+ throw new Error('remote-server URL is required and must be a string');
34
+ }
35
+ return serverUrl.trim().replace(/\/+$/, '');
36
+ }
37
+
38
+ /**
39
+ * Build full URL for an endpoint path
40
+ * @param {string} baseUrl - Normalized base URL
41
+ * @param {string} path - Path (e.g. /api/dev/issue-cert)
42
+ * @returns {string} Full URL
43
+ */
44
+ function buildUrl(baseUrl, path) {
45
+ const p = path.startsWith('/') ? path : `/${path}`;
46
+ return `${baseUrl}${p}`;
47
+ }
48
+
49
+ /**
50
+ * Make request to Builder Server. Throws on !success with message from response or error.
51
+ * @param {string} url - Full URL
52
+ * @param {Object} options - Fetch options (method, headers, body)
53
+ * @returns {Promise<Object>} result.data when success
54
+ */
55
+ async function request(url, options = {}) {
56
+ const fetchOptions = {
57
+ method: options.method || 'GET',
58
+ headers: { 'Content-Type': 'application/json', ...options.headers },
59
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT_MS)
60
+ };
61
+ if (options.body !== undefined) {
62
+ fetchOptions.body = typeof options.body === 'string' ? options.body : JSON.stringify(options.body);
63
+ }
64
+ const result = await makeApiCall(url, fetchOptions);
65
+ if (!result.success) {
66
+ const msg = result.formattedError || result.error || result.message || `Request failed (${result.status})`;
67
+ const err = new Error(msg);
68
+ err.status = result.status;
69
+ err.errorData = result.errorData;
70
+ throw err;
71
+ }
72
+ return result.data;
73
+ }
74
+
75
+ /**
76
+ * Build request options and TLS agent for mTLS request.
77
+ * @param {string} url - Full URL
78
+ * @param {Object} options - { method, headers, body }
79
+ * @param {string} certPem - PEM client certificate
80
+ * @param {string} keyPem - PEM client key
81
+ * @returns {{ urlObj: URL, method: string, headers: Object, body: string|undefined, agent: https.Agent }}
82
+ */
83
+ function buildMtlsRequestOptions(url, options, certPem, keyPem) {
84
+ const urlObj = new URL(url);
85
+ const method = (options.method || 'GET').toUpperCase();
86
+ const headers = { 'Content-Type': 'application/json', ...options.headers };
87
+ let body = options.body;
88
+ if (body !== undefined && typeof body !== 'string') {
89
+ body = JSON.stringify(body);
90
+ }
91
+ if (body) {
92
+ headers['Content-Length'] = Buffer.byteLength(body, 'utf8');
93
+ }
94
+ const tlsOptions = { cert: certPem, key: keyPem, rejectUnauthorized: true };
95
+ const agent = new https.Agent(tlsOptions);
96
+ return { urlObj, method, headers, body, agent, tlsOptions };
97
+ }
98
+
99
+ /**
100
+ * Handle mTLS response: collect body, parse JSON, resolve or reject.
101
+ * @param {import('http').IncomingMessage} res - HTTP response
102
+ * @param {Function} resolve - Promise resolve
103
+ * @param {Function} reject - Promise reject
104
+ */
105
+ function handleMtlsResponse(res, resolve, reject) {
106
+ const chunks = [];
107
+ res.on('data', (c) => chunks.push(c));
108
+ res.on('end', () => {
109
+ const raw = Buffer.concat(chunks).toString('utf8');
110
+ let data;
111
+ try {
112
+ data = raw ? JSON.parse(raw) : {};
113
+ } catch {
114
+ data = raw;
115
+ }
116
+ if (res.statusCode < 200 || res.statusCode >= 300) {
117
+ const msg = (data && (data.message || data.error)) || res.statusMessage || `Request failed (${res.statusCode})`;
118
+ const err = new Error(msg);
119
+ err.status = res.statusCode;
120
+ err.errorData = data;
121
+ reject(err);
122
+ } else {
123
+ resolve(data);
124
+ }
125
+ });
126
+ }
127
+
128
+ /**
129
+ * Make request with mTLS (TLS client certificate). Uses Node https module so the client cert
130
+ * is presented in the TLS handshake. Also sends X-Client-Cert header for backends that read it.
131
+ * @param {string} url - Full URL (https only)
132
+ * @param {Object} options - { method, headers, body }
133
+ * @param {string} certPem - PEM-encoded client certificate
134
+ * @param {string} keyPem - PEM-encoded client private key
135
+ * @returns {Promise<Object>} response data when success
136
+ */
137
+ function requestWithCertImpl(url, options, certPem, keyPem) {
138
+ return new Promise((resolve, reject) => {
139
+ const urlObj = new URL(url);
140
+ if (urlObj.protocol !== 'https:') {
141
+ reject(new Error('mTLS request requires https URL'));
142
+ return;
143
+ }
144
+ const { method, headers, body, agent, tlsOptions } = buildMtlsRequestOptions(url, options, certPem, keyPem);
145
+ const req = https.request(
146
+ {
147
+ hostname: urlObj.hostname,
148
+ port: urlObj.port || 443,
149
+ path: urlObj.pathname + urlObj.search,
150
+ method,
151
+ headers,
152
+ agent,
153
+ ...tlsOptions
154
+ },
155
+ (res) => handleMtlsResponse(res, resolve, reject)
156
+ );
157
+ req.on('error', reject);
158
+ req.setTimeout(DEFAULT_TIMEOUT_MS, () => {
159
+ req.destroy();
160
+ reject(new Error(`Request timed out after ${DEFAULT_TIMEOUT_MS}ms`));
161
+ });
162
+ if (body) {
163
+ req.write(body, 'utf8');
164
+ }
165
+ req.end();
166
+ });
167
+ }
168
+
169
+ /**
170
+ * Issue developer certificate (public; no client cert). POST /api/dev/issue-cert
171
+ * @requiresPermission {BuilderServer} Public (no auth required)
172
+ * @param {string} serverUrl - Builder Server base URL
173
+ * @param {Object} body - IssueCertDto: developerId, pin, csr
174
+ * @returns {Promise<Object>} IssueCertResponseDto: certificate, validDays, validNotAfter
175
+ */
176
+ async function issueCert(serverUrl, body) {
177
+ const base = normalizeBaseUrl(serverUrl);
178
+ return request(buildUrl(base, '/api/dev/issue-cert'), { method: 'POST', body });
179
+ }
180
+
181
+ /**
182
+ * Get health. GET /health (public)
183
+ * @requiresPermission {BuilderServer} Public (no auth required)
184
+ * @param {string} serverUrl - Builder Server base URL
185
+ * @returns {Promise<Object>} HealthResponseDto: status, checks (dataDir, encryptionKey, ca, users, tokens)
186
+ */
187
+ async function getHealth(serverUrl) {
188
+ const base = normalizeBaseUrl(serverUrl);
189
+ return request(buildUrl(base, '/health'));
190
+ }
191
+
192
+ /**
193
+ * Get developer settings (cert-authenticated). GET /api/dev/settings
194
+ * When clientKeyPem is provided, uses mTLS (TLS client cert); otherwise X-Client-Cert header only.
195
+ * @requiresPermission {BuilderServer} Client certificate (X-Client-Cert or mTLS)
196
+ * @param {string} serverUrl - Builder Server base URL
197
+ * @param {string} clientCertPem - PEM-encoded client certificate
198
+ * @param {string} [clientKeyPem] - PEM-encoded client private key (enables mTLS when provided)
199
+ * @returns {Promise<Object>} SettingsResponseDto
200
+ */
201
+ async function getSettings(serverUrl, clientCertPem, clientKeyPem) {
202
+ if (!clientCertPem || typeof clientCertPem !== 'string') {
203
+ throw new Error('Client certificate PEM is required for getSettings');
204
+ }
205
+ const base = normalizeBaseUrl(serverUrl);
206
+ const url = buildUrl(base, '/api/dev/settings');
207
+ const reqOptions = { method: 'GET', headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) } };
208
+ if (clientKeyPem && typeof clientKeyPem === 'string') {
209
+ return requestWithCertImpl(url, reqOptions, clientCertPem, clientKeyPem);
210
+ }
211
+ return request(url, reqOptions);
212
+ }
213
+
214
+ /**
215
+ * List developers. GET /api/dev/users
216
+ * @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
217
+ * @param {string} serverUrl - Builder Server base URL
218
+ * @param {string} clientCertPem - PEM client certificate
219
+ * @returns {Promise<Object[]>} Array of UserResponseDto (empty when none)
220
+ */
221
+ async function listUsers(serverUrl, clientCertPem) {
222
+ const base = normalizeBaseUrl(serverUrl);
223
+ const data = await request(buildUrl(base, '/api/dev/users'), {
224
+ method: 'GET',
225
+ headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
226
+ });
227
+ return Array.isArray(data) ? data : [];
228
+ }
229
+
230
+ /**
231
+ * Create developer. POST /api/dev/users
232
+ * @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
233
+ * @param {string} serverUrl - Builder Server base URL
234
+ * @param {string} clientCertPem - PEM client certificate
235
+ * @param {Object} body - CreateUserDto: developerId, name, email, optional groups
236
+ * @returns {Promise<Object>} UserResponseDto
237
+ */
238
+ async function createUser(serverUrl, clientCertPem, body) {
239
+ const base = normalizeBaseUrl(serverUrl);
240
+ return request(buildUrl(base, '/api/dev/users'), {
241
+ method: 'POST',
242
+ headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) },
243
+ body
244
+ });
245
+ }
246
+
247
+ /**
248
+ * Update developer. PATCH /api/dev/users/:id
249
+ * @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
250
+ * @param {string} serverUrl - Builder Server base URL
251
+ * @param {string} clientCertPem - PEM client certificate
252
+ * @param {string} id - Developer ID
253
+ * @param {Object} body - UpdateUserDto: at least one of name, email, groups
254
+ * @returns {Promise<Object>} UserResponseDto
255
+ */
256
+ async function updateUser(serverUrl, clientCertPem, id, body) {
257
+ const base = normalizeBaseUrl(serverUrl);
258
+ return request(buildUrl(base, `/api/dev/users/${encodeURIComponent(id)}`), {
259
+ method: 'PATCH',
260
+ headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) },
261
+ body
262
+ });
263
+ }
264
+
265
+ /**
266
+ * Delete developer. DELETE /api/dev/users/:id
267
+ * @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
268
+ * @param {string} serverUrl - Builder Server base URL
269
+ * @param {string} clientCertPem - PEM client certificate
270
+ * @param {string} id - Developer ID
271
+ * @returns {Promise<Object>} DeletedResponseDto
272
+ */
273
+ async function deleteUser(serverUrl, clientCertPem, id) {
274
+ const base = normalizeBaseUrl(serverUrl);
275
+ return request(buildUrl(base, `/api/dev/users/${encodeURIComponent(id)}`), {
276
+ method: 'DELETE',
277
+ headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
278
+ });
279
+ }
280
+
281
+ /**
282
+ * Create or regenerate one-time PIN. POST /api/dev/users/:id/pin
283
+ * @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
284
+ * @param {string} serverUrl - Builder Server base URL
285
+ * @param {string} clientCertPem - PEM client certificate
286
+ * @param {string} id - Developer ID
287
+ * @returns {Promise<Object>} CreatePinResponseDto: pin, expiresAt
288
+ */
289
+ async function createPin(serverUrl, clientCertPem, id) {
290
+ const base = normalizeBaseUrl(serverUrl);
291
+ return request(buildUrl(base, `/api/dev/users/${encodeURIComponent(id)}/pin`), {
292
+ method: 'POST',
293
+ headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
294
+ });
295
+ }
296
+
297
+ /**
298
+ * List SSH keys for developer. GET /api/dev/users/:id/ssh-keys
299
+ * @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
300
+ * @param {string} serverUrl - Builder Server base URL
301
+ * @param {string} clientCertPem - PEM client certificate
302
+ * @param {string} id - Developer ID
303
+ * @returns {Promise<Object[]>} Array of SshKeyItemDto
304
+ */
305
+ async function listSshKeys(serverUrl, clientCertPem, id) {
306
+ const base = normalizeBaseUrl(serverUrl);
307
+ const data = await request(buildUrl(base, `/api/dev/users/${encodeURIComponent(id)}/ssh-keys`), {
308
+ method: 'GET',
309
+ headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
310
+ });
311
+ return Array.isArray(data) ? data : [];
312
+ }
313
+
314
+ /**
315
+ * Add SSH public key for developer. POST /api/dev/users/:id/ssh-keys
316
+ * When clientKeyPem is provided, uses mTLS (TLS client cert); otherwise X-Client-Cert header only.
317
+ * @requiresPermission {BuilderServer} Client certificate (X-Client-Cert or mTLS)
318
+ * @param {string} serverUrl - Builder Server base URL
319
+ * @param {string} clientCertPem - PEM client certificate
320
+ * @param {string} id - Developer ID
321
+ * @param {Object} body - AddSshKeyDto: publicKey, optional label
322
+ * @param {string} [clientKeyPem] - PEM-encoded client private key (enables mTLS when provided)
323
+ * @returns {Promise<Object>} SshKeyItemDto
324
+ */
325
+ async function addSshKey(serverUrl, clientCertPem, id, body, clientKeyPem) {
326
+ const base = normalizeBaseUrl(serverUrl);
327
+ const url = buildUrl(base, `/api/dev/users/${encodeURIComponent(id)}/ssh-keys`);
328
+ const reqOptions = {
329
+ method: 'POST',
330
+ headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) },
331
+ body
332
+ };
333
+ if (clientKeyPem && typeof clientKeyPem === 'string') {
334
+ return requestWithCertImpl(url, reqOptions, clientCertPem, clientKeyPem);
335
+ }
336
+ return request(url, reqOptions);
337
+ }
338
+
339
+ /**
340
+ * Remove SSH key by fingerprint. DELETE /api/dev/users/:id/ssh-keys/:fingerprint
341
+ * @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
342
+ * @param {string} serverUrl - Builder Server base URL
343
+ * @param {string} clientCertPem - PEM client certificate
344
+ * @param {string} id - Developer ID
345
+ * @param {string} fingerprint - Key fingerprint
346
+ * @returns {Promise<Object>} DeletedResponseDto
347
+ */
348
+ async function removeSshKey(serverUrl, clientCertPem, id, fingerprint) {
349
+ const base = normalizeBaseUrl(serverUrl);
350
+ return request(buildUrl(base, `/api/dev/users/${encodeURIComponent(id)}/ssh-keys/${encodeURIComponent(fingerprint)}`), {
351
+ method: 'DELETE',
352
+ headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
353
+ });
354
+ }
355
+
356
+ /**
357
+ * List secrets. GET /api/dev/secrets
358
+ * @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
359
+ * @param {string} serverUrl - Builder Server base URL
360
+ * @param {string} clientCertPem - PEM client certificate
361
+ * @returns {Promise<Object[]>} Array of SecretItemDto: name, value
362
+ */
363
+ async function listSecrets(serverUrl, clientCertPem) {
364
+ const base = normalizeBaseUrl(serverUrl);
365
+ const data = await request(buildUrl(base, '/api/dev/secrets'), {
366
+ method: 'GET',
367
+ headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
368
+ });
369
+ return Array.isArray(data) ? data : [];
370
+ }
371
+
372
+ /**
373
+ * Add or update secret. POST /api/dev/secrets
374
+ * @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
375
+ * @param {string} serverUrl - Builder Server base URL
376
+ * @param {string} clientCertPem - PEM client certificate
377
+ * @param {Object} body - AddSecretDto: key, value
378
+ * @returns {Promise<Object>} AddSecretResponseDto
379
+ */
380
+ async function addSecret(serverUrl, clientCertPem, body) {
381
+ const base = normalizeBaseUrl(serverUrl);
382
+ return request(buildUrl(base, '/api/dev/secrets'), {
383
+ method: 'POST',
384
+ headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) },
385
+ body
386
+ });
387
+ }
388
+
389
+ /**
390
+ * Delete secret by key. DELETE /api/dev/secrets/:key
391
+ * @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
392
+ * @param {string} serverUrl - Builder Server base URL
393
+ * @param {string} clientCertPem - PEM client certificate
394
+ * @param {string} key - Secret key
395
+ * @returns {Promise<Object>} DeleteSecretResponseDto
396
+ */
397
+ async function deleteSecret(serverUrl, clientCertPem, key) {
398
+ const base = normalizeBaseUrl(serverUrl);
399
+ return request(buildUrl(base, `/api/dev/secrets/${encodeURIComponent(key)}`), {
400
+ method: 'DELETE',
401
+ headers: { 'X-Client-Cert': encodeCertForHeader(clientCertPem) }
402
+ });
403
+ }
404
+
405
+ module.exports = {
406
+ issueCert,
407
+ getHealth,
408
+ getSettings,
409
+ listUsers,
410
+ createUser,
411
+ updateUser,
412
+ deleteUser,
413
+ createPin,
414
+ listSshKeys,
415
+ addSshKey,
416
+ removeSshKey,
417
+ listSecrets,
418
+ addSecret,
419
+ deleteSecret,
420
+ normalizeBaseUrl,
421
+ buildUrl,
422
+ encodeCertForHeader
423
+ };
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @fileoverview Credential API type definitions (Dataplane secret store)
3
+ * @author AI Fabrix Team
4
+ * @version 2.0.0
5
+ */
6
+
7
+ /**
8
+ * Single secret item for Dataplane credential secret store.
9
+ * Key is the kv:// path; value must be plain (resolved), never a kv:// reference.
10
+ * @typedef {Object} SecretStoreItem
11
+ * @property {string} key - kv:// path (e.g. kv://secrets/client-secret)
12
+ * @property {string} value - Plain secret value (encrypted at rest by dataplane)
13
+ */
14
+
15
+ /**
16
+ * Response from POST /api/v1/credential/secret (Dataplane).
17
+ * @typedef {Object} SecretStoreResponse
18
+ * @property {number} [stored] - Number of secrets stored
19
+ * @property {boolean} [success] - Request success flag
20
+ * @property {string} [error] - Error message when success is false
21
+ */
22
+
23
+ module.exports = {};