@aifabrix/builder 2.55.0 → 2.56.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 (101) hide show
  1. package/lib/agent-kit/host-approve.js +48 -6
  2. package/lib/agent-kit/init.js +2 -2
  3. package/lib/agent-kit/remote-prompts.js +46 -13
  4. package/lib/agent-kit/run.js +1 -1
  5. package/lib/agent-kit/start.js +310 -0
  6. package/lib/api/pipeline.api.js +32 -3
  7. package/lib/app/deploy.js +13 -9
  8. package/lib/app/offer-create-from-template.js +184 -0
  9. package/lib/cli/setup-app.js +15 -7
  10. package/lib/cli/setup-auth.js +11 -2
  11. package/lib/cli/setup-external-system-help.js +15 -3
  12. package/lib/cli/setup-external-system.js +21 -3
  13. package/lib/commands/agent-kit-help.js +74 -4
  14. package/lib/commands/agent-kit.js +83 -12
  15. package/lib/commands/auth-status-user.js +99 -0
  16. package/lib/commands/auth-status.js +14 -4
  17. package/lib/commands/datasource-load-export-cli.js +7 -2
  18. package/lib/commands/datasource-test-trust-cli.js +5 -1
  19. package/lib/commands/governance-command-action.js +5 -4
  20. package/lib/commands/identity-membership-role-cache.js +6 -1
  21. package/lib/commands/lifecycle-command-action.js +16 -15
  22. package/lib/commands/login-credentials.js +8 -3
  23. package/lib/commands/login-device.js +14 -5
  24. package/lib/commands/login.js +14 -1
  25. package/lib/commands/logout.js +50 -15
  26. package/lib/commands/repair-datasource-auto-rbac.js +4 -9
  27. package/lib/commands/role-assistant.js +24 -12
  28. package/lib/commands/upload-openapi-sync-followup.js +59 -0
  29. package/lib/commands/upload-qualification-client.js +131 -0
  30. package/lib/commands/upload-qualification-followups.js +104 -0
  31. package/lib/commands/upload-qualification-poll.js +143 -0
  32. package/lib/commands/upload.js +37 -156
  33. package/lib/commands/verify-governance-command-action.js +9 -7
  34. package/lib/commands/verify-operations-command-action.js +9 -8
  35. package/lib/commands/verify-trust-command-action.js +6 -5
  36. package/lib/deployment/deploy-poll-ui.js +14 -10
  37. package/lib/deployment/deployer-status.js +4 -4
  38. package/lib/external-system/deploy.js +46 -51
  39. package/lib/external-system/download.js +1 -2
  40. package/lib/generator/helpers.js +14 -46
  41. package/lib/generator/index.js +0 -4
  42. package/lib/integration-definition/apply-static-enterprise.js +5 -2
  43. package/lib/programmatic/builder-api-request.js +8 -4
  44. package/lib/programmatic/capability-registry.js +2 -0
  45. package/lib/programmatic/get-deploy-status.js +45 -0
  46. package/lib/programmatic/get-upload-status.js +45 -0
  47. package/lib/programmatic/http-accepted-response.js +31 -0
  48. package/lib/programmatic/openapi-descriptions.js +14 -4
  49. package/lib/programmatic/request-timeout.js +21 -15
  50. package/lib/programmatic/route-handler-map.js +6 -0
  51. package/lib/programmatic/run-deploy.js +7 -3
  52. package/lib/programmatic/run-upload.js +11 -7
  53. package/lib/role-assistant/knowledge-sync-settings-push.js +172 -0
  54. package/lib/role-assistant/knowledge-sync.js +9 -126
  55. package/lib/role-assistant/test-cases-evaluate.js +12 -1
  56. package/lib/role-assistant/test-cases-evidence.js +44 -5
  57. package/lib/role-assistant/test-cases-lifecycle.js +9 -3
  58. package/lib/role-assistant/test-runner.js +3 -0
  59. package/lib/schema/infra-parameter.schema.json +51 -3
  60. package/lib/schema/infra.parameter.yaml +482 -47
  61. package/lib/schema/type/document-storage.json +1 -1
  62. package/lib/utils/api-required-permissions.js +271 -0
  63. package/lib/utils/api.js +16 -13
  64. package/lib/utils/cli-json-exit.js +41 -0
  65. package/lib/utils/cli-utils.js +26 -7
  66. package/lib/utils/controller-url.js +78 -4
  67. package/lib/utils/deployment-errors.js +15 -0
  68. package/lib/utils/deployment-validation-helpers.js +4 -1
  69. package/lib/utils/device-code.js +31 -10
  70. package/lib/utils/error-formatters/error-parser.js +16 -1
  71. package/lib/utils/error-formatters/permission-errors.js +19 -5
  72. package/lib/utils/keycloak-realm-ready.js +5 -2
  73. package/lib/utils/logger.js +1 -0
  74. package/lib/utils/paths.js +26 -5
  75. package/lib/utils/qualification-poll-ui.js +136 -0
  76. package/lib/utils/secrets-helpers.js +2 -2
  77. package/lib/utils/write-stream-and-wait.js +63 -0
  78. package/package.json +22 -7
  79. package/templates/agent-kit/agent-kit.yaml +1 -1
  80. package/templates/agent-kit/instructions/AGENTKIT.md +25 -4
  81. package/templates/agent-kit/instructions/root.AGENTS.md +1 -1
  82. package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +2 -1
  83. package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +1 -1
  84. package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +2 -1
  85. package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +1 -0
  86. package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +1 -0
  87. package/templates/agent-kit/skills/shared/hosts.md +13 -26
  88. package/templates/agent-kit/skills/shared/login.md +16 -0
  89. package/templates/agent-kit/workspace/README.md +1 -1
  90. package/templates/applications/builder-api/env.template +4 -0
  91. package/templates/applications/dataplane/application.yaml +1 -2
  92. package/templates/applications/dataplane/env.template +5 -5
  93. package/templates/applications/keycloak/Dockerfile +12 -5
  94. package/templates/applications/keycloak/application.yaml +2 -2
  95. package/templates/applications/keycloak/cimd-lenient-parse/AbstractClientIdMetadataDocumentExecutor.java +3 -3
  96. package/templates/applications/keycloak/cimd-lenient-parse/SOURCE.md +6 -6
  97. package/templates/applications/keycloak/env.template +13 -1
  98. package/templates/applications/miso-controller/application.yaml +21 -2
  99. package/templates/applications/miso-controller/env.template +97 -28
  100. package/templates/applications/miso-controller/rbac.yaml +5 -0
  101. package/lib/generator/deploy-manifest-azure-kv.js +0 -81
@@ -177,6 +177,21 @@ function handleGenericError(errorData, statusCode) {
177
177
  return createErrorResult('generic', errorData.message || errorData.error || 'Unknown error', formatGenericError(errorData, statusCode), errorData);
178
178
  }
179
179
 
180
+ function unwrapNestedErrorData(errorData) {
181
+ if (!errorData || !errorData.data || typeof errorData.data !== 'object') {
182
+ return errorData;
183
+ }
184
+ const nested = errorData.data;
185
+ return {
186
+ ...nested,
187
+ cliPermissionService: errorData.cliPermissionService || nested.cliPermissionService,
188
+ requiredPermissions: errorData.requiredPermissions || nested.requiredPermissions,
189
+ method: errorData.method || nested.method,
190
+ url: nested.instance || nested.url || errorData.url,
191
+ instance: nested.instance || errorData.instance
192
+ };
193
+ }
194
+
180
195
  /**
181
196
  * Parses error response and determines error type
182
197
  * @param {string|Object} errorResponse - Error response (string or parsed JSON)
@@ -200,7 +215,7 @@ function parseErrorResponse(errorResponse, statusCode, isNetworkError) {
200
215
 
201
216
  // Handle nested response structure (some APIs wrap errors in data field)
202
217
  if (errorData.data && typeof errorData.data === 'object') {
203
- errorData = errorData.data;
218
+ errorData = unwrapNestedErrorData(errorData);
204
219
  }
205
220
 
206
221
  // Handle network errors
@@ -42,6 +42,9 @@ function extractRequiredPermissions(errorData) {
42
42
  if (errorData.requiredPermissions && Array.isArray(errorData.requiredPermissions)) {
43
43
  return errorData.requiredPermissions;
44
44
  }
45
+ if (typeof errorData.requiredPermission === 'string' && errorData.requiredPermission.trim()) {
46
+ return [errorData.requiredPermission.trim()];
47
+ }
45
48
  if (errorData.required && errorData.required.permissions && Array.isArray(errorData.required.permissions)) {
46
49
  return errorData.required.permissions;
47
50
  }
@@ -97,6 +100,19 @@ function getPermissionDetailLines(errorData) {
97
100
  return lines;
98
101
  }
99
102
 
103
+ function appendPermissionAssignHint(lines, errorData) {
104
+ const service = errorData.cliPermissionService;
105
+ if (service) {
106
+ lines.push(chalk.gray(`Required on: ${service}`));
107
+ }
108
+ lines.push(chalk.gray(
109
+ service
110
+ ? `Assign this permission on the ${service} for this environment (roles and groups).`
111
+ : 'Assign the required permission on the Controller or Dataplane for this environment (roles and groups).'
112
+ ));
113
+ lines.push('');
114
+ }
115
+
100
116
  /**
101
117
  * Formats permission error with missing and required permissions
102
118
  * @param {Object} errorData - Error response data
@@ -111,11 +127,9 @@ function formatPermissionError(errorData) {
111
127
  lines.push('');
112
128
  }
113
129
 
114
- const missingPerms = extractMissingPermissions(errorData);
115
- addPermissionList(lines, missingPerms, 'Missing permissions');
116
-
117
- const requiredPerms = extractRequiredPermissions(errorData);
118
- addPermissionList(lines, requiredPerms, 'Required permissions');
130
+ addPermissionList(lines, extractMissingPermissions(errorData), 'Missing permissions');
131
+ addPermissionList(lines, extractRequiredPermissions(errorData), 'Required permissions');
132
+ appendPermissionAssignHint(lines, errorData);
119
133
 
120
134
  const requestUrl = errorData.instance || errorData.url;
121
135
  const method = errorData.method || 'POST';
@@ -82,15 +82,18 @@ async function waitForKeycloakRealmReady(opts = {}) {
82
82
  const directUrl = directBase ? buildRealmWellKnownUrl(directBase) : null;
83
83
  const deadline = Date.now() + timeoutSeconds * 1000;
84
84
 
85
- while (Date.now() < deadline) {
85
+ do {
86
86
  if (await probeRealmWellKnown(url)) {
87
87
  return;
88
88
  }
89
89
  if (directUrl && (await probeRealmWellKnown(directUrl))) {
90
90
  return;
91
91
  }
92
+ if (Date.now() >= deadline) {
93
+ break;
94
+ }
92
95
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
93
- }
96
+ } while (Date.now() < deadline);
94
97
 
95
98
  throw new Error(
96
99
  `Keycloak realm "aifabrix" was not ready within ${timeoutSeconds}s (checked ${url}${directUrl ? ` and ${directUrl}` : ''}). ` +
@@ -83,4 +83,5 @@ const logger = {
83
83
  };
84
84
 
85
85
  module.exports = logger;
86
+ module.exports.sanitizeValue = sanitizeValue;
86
87
 
@@ -957,6 +957,24 @@ async function ensureShippedBuilderAppTemplate(appName) {
957
957
  return true;
958
958
  }
959
959
 
960
+ /**
961
+ * Finds an existing local app workspace without copying shipped templates.
962
+ *
963
+ * @param {string} appName - Application name
964
+ * @param {Object} [options] - Detection options (reserved; options.type is ignored)
965
+ * @returns {{isExternal: boolean, appPath: string, appType: string, baseDir?: string}|null}
966
+ */
967
+ function findExistingAppType(appName, options = {}) {
968
+ if (!appName || typeof appName !== 'string') {
969
+ return null;
970
+ }
971
+ const externalResult = checkExternalWorkspaceFolder(appName, options);
972
+ if (externalResult) {
973
+ return externalResult;
974
+ }
975
+ return checkBuilderFolder(appName);
976
+ }
977
+
960
978
  /**
961
979
  * Detects if an app is external type by checking application config.
962
980
  * Resolution order: integration/ first, then builder/; if neither exists, throws.
@@ -971,13 +989,15 @@ async function detectAppType(appName, options = {}) {
971
989
  if (!appName || typeof appName !== 'string') {
972
990
  throw new Error('App name is required and must be a string');
973
991
  }
974
- const externalResult = checkExternalWorkspaceFolder(appName, options);
975
- if (externalResult) return externalResult;
976
- const builderResult = checkBuilderFolder(appName);
977
- if (builderResult) return builderResult;
992
+ const existing = findExistingAppType(appName, options);
993
+ if (existing) {
994
+ return existing;
995
+ }
978
996
  await ensureShippedBuilderAppTemplate(appName);
979
997
  const builderAfterTemplate = checkBuilderFolder(appName);
980
- if (builderAfterTemplate) return builderAfterTemplate;
998
+ if (builderAfterTemplate) {
999
+ return builderAfterTemplate;
1000
+ }
981
1001
  throw new Error(
982
1002
  `App '${appName}' not found in integration/${appName}, enterprise-knowledge/${appName}, role-assistant/${appName}, or builder/${appName}`
983
1003
  );
@@ -1121,6 +1141,7 @@ module.exports = {
1121
1141
  resolveApplicationConfigPath,
1122
1142
  resolveRbacPath,
1123
1143
  ensureShippedBuilderAppTemplate,
1144
+ findExistingAppType,
1124
1145
  detectAppType,
1125
1146
  getResolveAppPath,
1126
1147
  resolveIntegrationAppKeyFromCwd,
@@ -0,0 +1,136 @@
1
+ /**
2
+ * @fileoverview TTY ora spinner + throttled non-TTY lines for qualification job polling.
3
+ * Mirrors host-ops-poll-ui.js's presentation pattern (plan 191.0 task 1).
4
+ * @author AI Fabrix Team
5
+ * @version 1.0.0
6
+ */
7
+
8
+ 'use strict';
9
+
10
+ const chalk = require('chalk');
11
+ const logger = require('./logger');
12
+
13
+ const SILENT_POLL_HANDLERS = {
14
+ onPollProgress: () => {},
15
+ onTransientError: () => {},
16
+ finish: () => {}
17
+ };
18
+
19
+ const NON_TTY_THROTTLE_MS = 5000;
20
+
21
+ const HUMAN_STEP_LABELS = Object.freeze({
22
+ stageCandidate: 'Uploading configuration',
23
+ gateOperations: 'Checking operations',
24
+ gateTrust: 'Checking trust',
25
+ gateGovernance: 'Checking governance',
26
+ sealCertificate: 'Finalizing upload'
27
+ });
28
+
29
+ function shouldUseQualificationPollSpinner() {
30
+ return Boolean(process && process.stdout && process.stdout.isTTY);
31
+ }
32
+
33
+ /**
34
+ * @param {Object} job - PublishJobStatusResponse-shaped data ({ state, steps })
35
+ * @returns {string}
36
+ */
37
+ function currentQualificationStepLabel(job, options = {}) {
38
+ if (!job || !Array.isArray(job.steps) || job.steps.length === 0) {
39
+ return 'Preparing upload';
40
+ }
41
+ const running = job.steps.find((step) => step.status === 'running');
42
+ if (running) return qualificationStepLabel(running, 'running', options);
43
+ const failed = job.steps.find((step) => step.status === 'failed');
44
+ if (failed) return qualificationStepLabel(failed, 'failed', options);
45
+ const pending = job.steps.find((step) => step.status === 'pending');
46
+ if (pending) return qualificationStepLabel(pending, 'pending', options);
47
+ return 'Upload complete';
48
+ }
49
+
50
+ function qualificationStepLabel(step, _status, _options) {
51
+ return HUMAN_STEP_LABELS[step.key] || 'Processing upload';
52
+ }
53
+
54
+ /**
55
+ * @param {string} jobId
56
+ * @param {Object|null} job
57
+ * @param {number} elapsedMs
58
+ * @param {string} [transientNote]
59
+ * @returns {string}
60
+ */
61
+ function buildQualificationPollLine(jobId, job, elapsedMs, transientNote, options = {}) {
62
+ const seconds = Math.max(0, Math.round(elapsedMs / 1000));
63
+ const note = transientNote ? ` — ${transientNote}` : '';
64
+ if (options.verbose === true) {
65
+ return `Upload (job ${jobId}): ${currentQualificationStepLabel(job, options)} — ${seconds}s elapsed${note}`;
66
+ }
67
+ return `${currentQualificationStepLabel(job, options)} — ${seconds}s elapsed${note}`;
68
+ }
69
+
70
+ function createSpinnerQualificationPollHandlers(jobId, startedAt, options) {
71
+ const ora = require('ora');
72
+ const spinner = ora({
73
+ text: buildQualificationPollLine(jobId, null, 0, undefined, options),
74
+ spinner: 'dots'
75
+ }).start();
76
+ return {
77
+ onPollProgress: (job) => {
78
+ spinner.text = buildQualificationPollLine(jobId, job, Date.now() - startedAt, undefined, options);
79
+ },
80
+ onTransientError: (status, elapsedMs) => {
81
+ spinner.text = buildQualificationPollLine(
82
+ jobId,
83
+ null,
84
+ elapsedMs,
85
+ 'service temporarily unavailable, retrying',
86
+ options
87
+ );
88
+ },
89
+ finish: () => spinner.stop()
90
+ };
91
+ }
92
+
93
+ function createNonTtyQualificationPollHandlers(jobId, startedAt, options) {
94
+ let lastLogAt = 0;
95
+ return {
96
+ onPollProgress: (job) => {
97
+ const now = Date.now();
98
+ if (now - lastLogAt < NON_TTY_THROTTLE_MS) {
99
+ return;
100
+ }
101
+ lastLogAt = now;
102
+ logger.log(chalk.gray(` ${buildQualificationPollLine(jobId, job, now - startedAt, undefined, options)}`));
103
+ },
104
+ onTransientError: (status, elapsedMs) => {
105
+ logger.log(
106
+ chalk.gray(
107
+ ` ${buildQualificationPollLine(jobId, null, elapsedMs, 'service temporarily unavailable, retrying', options)}`
108
+ )
109
+ );
110
+ },
111
+ finish: () => {}
112
+ };
113
+ }
114
+
115
+ /**
116
+ * @param {string} jobId
117
+ * @param {{ startedAt?: number, silent?: boolean, verbose?: boolean }} [options]
118
+ * @returns {{ onPollProgress: Function, onTransientError: Function, finish: () => void }}
119
+ */
120
+ function createQualificationPollHandlers(jobId, options = {}) {
121
+ if (options.silent === true) {
122
+ return SILENT_POLL_HANDLERS;
123
+ }
124
+ const startedAt = options.startedAt || Date.now();
125
+ if (shouldUseQualificationPollSpinner()) {
126
+ return createSpinnerQualificationPollHandlers(jobId, startedAt, options);
127
+ }
128
+ return createNonTtyQualificationPollHandlers(jobId, startedAt, options);
129
+ }
130
+
131
+ module.exports = {
132
+ currentQualificationStepLabel,
133
+ buildQualificationPollLine,
134
+ createQualificationPollHandlers,
135
+ shouldUseQualificationPollSpinner
136
+ };
@@ -47,7 +47,8 @@ const { resolveBashKvFromProcessEnv } = require('./secrets-bash-kv');
47
47
  const EMPTY_ALLOWED_KV_FALLBACK = new Set([
48
48
  'redis-passwordKeyVault',
49
49
  'azure-openaiapi-urlKeyVault',
50
- 'secrets-azureOpenaiApiKeyVault'
50
+ 'secrets-azureOpenaiApiKeyVault',
51
+ 'miso-controller-secrets-apiKeyVault'
51
52
  ]);
52
53
 
53
54
  /**
@@ -486,4 +487,3 @@ module.exports = {
486
487
  rewriteInfraEndpoints,
487
488
  getEnvHosts
488
489
  };
489
-
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Flush a writable stream chunk without hanging on Jest write mocks.
3
+ *
4
+ * @fileoverview Shared stdout/stderr write + wait for CLI exits.
5
+ * @author AI Fabrix Team
6
+ * @version 1.0.0
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ /**
12
+ * @param {unknown} writeFn - Stream write implementation
13
+ * @returns {boolean}
14
+ */
15
+ function isJestMockWrite(writeFn) {
16
+ return typeof jest !== 'undefined'
17
+ && typeof jest.isMockFunction === 'function'
18
+ && jest.isMockFunction(writeFn);
19
+ }
20
+
21
+ /**
22
+ * Write `chunk` and resolve after the stream flushes.
23
+ *
24
+ * Piped stdout/stderr on Linux is asynchronous; exiting before the write
25
+ * callback can truncate output. Jest spies that ignore the callback, and
26
+ * native writes inside a Jest worker, must not hang the test.
27
+ *
28
+ * @param {NodeJS.WritableStream} stream - stdout or stderr
29
+ * @param {string} chunk - Bytes to write
30
+ * @returns {Promise<void>}
31
+ */
32
+ function writeStreamAndWait(stream, chunk) {
33
+ return new Promise(resolve => {
34
+ let settled = false;
35
+ const finish = () => {
36
+ if (settled) {
37
+ return;
38
+ }
39
+ settled = true;
40
+ resolve();
41
+ };
42
+ if (!stream || typeof stream.write !== 'function') {
43
+ finish();
44
+ return;
45
+ }
46
+ const writeFn = stream.write;
47
+ try {
48
+ writeFn.call(stream, chunk, finish);
49
+ } catch {
50
+ finish();
51
+ return;
52
+ }
53
+ if (isJestMockWrite(writeFn) && writeFn.length < 2) {
54
+ finish();
55
+ return;
56
+ }
57
+ if (!isJestMockWrite(writeFn) && process.env.JEST_WORKER_ID) {
58
+ setImmediate(finish);
59
+ }
60
+ });
61
+ }
62
+
63
+ module.exports = { writeStreamAndWait };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aifabrix/builder",
3
- "version": "2.55.0",
3
+ "version": "2.56.0",
4
4
  "description": "AI Fabrix Builder — CLI and developer scripts (pnpm + af)",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -21,6 +21,7 @@
21
21
  "dev-reload": "node scripts/pnpm/dev-reload.mjs",
22
22
  "dev-build": "node scripts/pnpm/af.mjs dev-build",
23
23
  "dev-promote": "node scripts/pnpm/af.mjs dev-promote",
24
+ "dev-deploy": "node scripts/pnpm/dev-deploy.mjs",
24
25
  "af": "node scripts/pnpm/af.mjs",
25
26
  "check-quiet": "node scripts/pnpm/check-quiet.mjs",
26
27
  "check": "npm run lint && npm run test",
@@ -86,32 +87,46 @@
86
87
  "overrides": {
87
88
  "@babel/core": "^7.29.7",
88
89
  "@babel/plugin-transform-modules-systemjs": "^7.29.7",
89
- "fast-uri": "^4.1.0",
90
+ "baseline-browser-mapping": "^2.11.21",
91
+ "brace-expansion@5": "5.0.9",
92
+ "browserslist": "^4.28.9",
93
+ "fast-uri": "^4.1.4",
90
94
  "flatted": "^3.4.2",
91
95
  "follow-redirects": "^1.16.0",
92
96
  "form-data": "^4.0.6",
97
+ "js-yaml@3": "3.15.2",
98
+ "js-yaml@5": "5.4.1",
93
99
  "lodash": "^4.18.1",
94
100
  "minimatch": "^10.2.5",
101
+ "multer": "^2.3.0",
95
102
  "picomatch": "^4.0.5",
96
- "qs": "^6.15.3"
103
+ "qs": "^6.16.0",
104
+ "smol-toml": "^1.8.0"
97
105
  },
98
106
  "pnpm": {
99
107
  "overrides": {
100
108
  "@bcoe/v8-coverage": "^1.0.2",
101
109
  "@babel/core": "^7.29.7",
102
110
  "@babel/plugin-transform-modules-systemjs": "^7.29.7",
103
- "fast-uri": "^4.1.0",
111
+ "baseline-browser-mapping": "^2.11.21",
112
+ "brace-expansion@5": "5.0.9",
113
+ "browserslist": "^4.28.9",
114
+ "fast-uri": "^4.1.4",
104
115
  "flatted": "^3.4.2",
105
116
  "follow-redirects": "^1.16.0",
106
117
  "form-data": "^4.0.6",
118
+ "js-yaml@3": "3.15.2",
119
+ "js-yaml@5": "5.4.1",
107
120
  "lodash": "^4.18.1",
108
121
  "minimatch": "^10.2.5",
122
+ "multer": "^2.3.0",
109
123
  "picomatch": "^4.0.5",
110
- "qs": "^6.15.3"
124
+ "qs": "^6.16.0",
125
+ "smol-toml": "^1.8.0"
111
126
  }
112
127
  },
113
128
  "dependencies": {
114
- "@aifabrix/miso-client": "^4.19.0",
129
+ "@aifabrix/miso-client": "4.22.4",
115
130
  "adm-zip": "^0.6.0",
116
131
  "ajv": "^8.20.0",
117
132
  "ajv-formats": "^3.0.1",
@@ -121,7 +136,7 @@
121
136
  "handlebars": "^4.7.9",
122
137
  "inquirer": "^8.2.5",
123
138
  "inquirer-autocomplete-prompt": "^2.0.0",
124
- "js-yaml": "^4.3.0",
139
+ "js-yaml": "^4.3.2",
125
140
  "ora": "^5.4.1",
126
141
  "yaml": "^2.9.0"
127
142
  },
@@ -1,5 +1,5 @@
1
1
  schemaVersion: "1"
2
- kitVersion: "1.1.1"
2
+ kitVersion: "1.1.2"
3
3
  minimumBuilderVersion: "2.54.2"
4
4
  skills:
5
5
  - aifabrix-plan
@@ -34,7 +34,7 @@ New workspace instead of an existing repo: `aifabrix agent-kit init <workspaceNa
34
34
  Install the coding app, then sign in. How to **run** it locally or over SSH is in `.agents/skills/shared/hosts.md` (linked from every Agent Kit skill).
35
35
  {{#if autoApprove}}
36
36
 
37
- On TTY, install **asks** to auto-approve tool permissions **in this workspace**. Claude Code also allows `Bash(aifabrix *)` (delivery CLI) and `Read(~/**)` (home Read). Silent (`--yes` / non-TTY) writes them; `--skip-approve-all` skips.
37
+ On TTY, install **asks** which hosts to support (omit `--host`) and which tools to auto-approve. `--approve-all claude` or `--approve-all codex` writes only that host's config. Claude Code also allows `Bash(aifabrix *)` (delivery CLI) and `Read(~/**)` (home Read). Silent (`--yes` / non-TTY) writes them; `--skip-approve-all` skips.
38
38
  {{#if claude}}
39
39
  - Claude Code writes `.claude/settings.local.json` (gitignored).
40
40
  {{/if}}
@@ -57,22 +57,43 @@ curl -fsSL https://claude.ai/install.sh | bash
57
57
  claude auth login
58
58
  ```
59
59
 
60
- Skills: same `/aifabrix-*` names. First `claude` must run inside this workspace, not your home directory.
60
+ **Run in tmux** from this workspace so closing the terminal does not kill Claude. `--remote-control` is the **same** session on this PC, [claude.ai/code](https://claude.ai/code), and the Claude mobile app (Code). That is not `claude --cloud` (a separate cloud VM). Claude `--tmux` is for worktrees only.
61
+
62
+ ```bash
63
+ aifabrix agent-kit start claude --remote-control --approve-all --yes
64
+ ```
65
+
66
+ tmux session name defaults to this folder. Remote Control name is `hostname-user-folder` (for example `builder01-dev03-aifabrix-docs`). Override the folder with `--name`. Detach: `Ctrl-b` then `d`. Reattach: `tmux attach -t <folder>`. Several workspaces can run at once. Same account on phone and PC (`claude auth login`, Pro/Max/Team — not an API key). On the phone: Claude app → **Code**. Skills: `/aifabrix-plan` (and the other `/aifabrix-*` names). First `claude` must run inside this workspace, not your home directory. Full notes: `.agents/skills/shared/hosts.md`.
61
67
  {{/if}}
62
68
  {{#if codex}}
63
69
 
64
70
  ### Codex
65
71
 
66
- Install the Codex CLI, then sign in with your Codex account. Skills: `$aifabrix-plan` (and the other `$aifabrix-*` names). Codex reads `AGENTS.md`.
72
+ Install the Codex CLI (`@openai/codex`, not the unrelated `codex` package), then sign in with ChatGPT (Plus, Pro, Business, Edu, or Enterprise):
73
+
74
+ ```bash
75
+ npm install -g @openai/codex
76
+ codex login
77
+ ```
78
+
79
+ **Run in tmux** from this workspace so the session stays if you close the terminal:
80
+
81
+ ```bash
82
+ aifabrix agent-kit start codex --approve-all --yes
83
+ ```
84
+
85
+ Detach: `Ctrl-b` then `d`. Reattach: `tmux attach -t <folder>`, or `aifabrix agent-kit start codex` from this workspace. Default session name is this folder. Skills: `$aifabrix-plan` (and the other `$aifabrix-*` names). Codex reads `AGENTS.md`. Full notes: `.agents/skills/shared/hosts.md`.
67
86
  {{/if}}
68
87
 
69
88
  ## Auth for delivery CLI
70
89
 
90
+ Do not tell the user to open a terminal for Builder login. Follow `.agents/skills/shared/login.md`.
91
+
71
92
  ```bash
72
93
  aifabrix auth status
73
94
  ```
74
95
 
75
- If still unauthenticated, run `aifabrix login` (the agent cannot log in for you).
96
+ If that is not authenticated, ask for the Miso Controller URL, run device login, and paste the printed authenticate URL into chat.
76
97
 
77
98
  ## How to start
78
99
 
@@ -28,7 +28,7 @@ Do not collapse these into a generic success boolean. Do not start the next life
28
28
  ## Coding hosts
29
29
 
30
30
  Cursor, Claude Code, and Codex — local machine or SSH (tmux, Claude Remote Control): `.agents/skills/shared/hosts.md`.
31
- Install and sign-in: `AGENTKIT.md`.
31
+ Install and sign-in: `AGENTKIT.md`. Builder CLI login from chat: `.agents/skills/shared/login.md`.
32
32
 
33
33
  ## Docs
34
34
 
@@ -24,6 +24,7 @@ Stop at the phase gate. Do not run the next phase without `next-yes`
24
24
 
25
25
  Read current CLI help before composing commands. You own orchestration, not CLI implementation.
26
26
  Host runtimes (local vs SSH): [hosts.md](../shared/hosts.md).
27
+ CLI login from chat: [login.md](../shared/login.md).
27
28
 
28
29
  ## Always
29
30
 
@@ -31,7 +32,7 @@ Host runtimes (local vs SSH): [hosts.md](../shared/hosts.md).
31
32
  aifabrix auth status
32
33
  ```
33
34
 
34
- Refresh first. User runs `aifabrix login` only if still unauthenticated. Re-run on 401, then retry once.
35
+ Refresh first. If still unauthenticated, follow [login.md](../shared/login.md). Re-run on 401, then retry once.
35
36
 
36
37
  System key: argument → active plan → sole `integration/` folder → ask once. Never invent. Reject `ra-` / `ek-` ([scripts/reserved-system-key.js](scripts/reserved-system-key.js)).
37
38
 
@@ -41,7 +41,7 @@ Never mark live E2E pass when it did not run.
41
41
 
42
42
  ## Identity and protection order
43
43
 
44
- 1. `aifabrix auth status`
44
+ 1. `aifabrix auth status` — if unauthenticated, [login.md](../../shared/login.md)
45
45
  2. Catalog roles + fixtures + `identity apply` / `identity sync` / `role set-groups`
46
46
  3. Load partitioned fixtures
47
47
  4. `aifabrix protection upload <sourceStorageDs> --sync` (storage DS, not the view)
@@ -15,6 +15,7 @@ Output: one delivery plan from [plan-template.md](references/plan-template.md).
15
15
  Score completeness with [challenge-checklist.md](references/challenge-checklist.md).
16
16
  Next-step interaction: [interaction.md](references/interaction.md).
17
17
  Host runtimes (local vs SSH): [hosts.md](../shared/hosts.md).
18
+ CLI login from chat: [login.md](../shared/login.md).
18
19
 
19
20
  ## Sequence
20
21
 
@@ -44,7 +45,7 @@ aifabrix auth status
44
45
  aifabrix role list --page-size 1000
45
46
  ```
46
47
 
47
- If still unauthenticated after refresh, draft the plan; catalog-role rows stay MISSING until the user runs `aifabrix login`.
48
+ If unauthenticated after refresh, follow [login.md](../shared/login.md) before listing catalog roles.
48
49
 
49
50
  ## Trigger examples
50
51
 
@@ -19,6 +19,7 @@ Evidence rules: [evidence-lifecycle.md](references/evidence-lifecycle.md).
19
19
  Demo shape: [demo-template.md](references/demo-template.md).
20
20
  Next-step: [interaction.md](../aifabrix-plan/references/interaction.md).
21
21
  Host runtimes (local vs SSH): [hosts.md](../shared/hosts.md).
22
+ CLI login from chat: [login.md](../shared/login.md).
22
23
 
23
24
  ## Keys
24
25
 
@@ -10,6 +10,7 @@ description: >
10
10
 
11
11
  See [package-boundary.md](references/package-boundary.md) and nested `role-assistant/AGENTS.md`.
12
12
  Host runtimes (local vs SSH): [hosts.md](../shared/hosts.md).
13
+ CLI login from chat: [login.md](../shared/login.md).
13
14
 
14
15
  ## Preconditions
15
16