@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.
- package/lib/agent-kit/host-approve.js +48 -6
- package/lib/agent-kit/init.js +2 -2
- package/lib/agent-kit/remote-prompts.js +46 -13
- package/lib/agent-kit/run.js +1 -1
- package/lib/agent-kit/start.js +310 -0
- package/lib/api/pipeline.api.js +32 -3
- package/lib/app/deploy.js +13 -9
- package/lib/app/offer-create-from-template.js +184 -0
- package/lib/cli/setup-app.js +15 -7
- package/lib/cli/setup-auth.js +11 -2
- package/lib/cli/setup-external-system-help.js +15 -3
- package/lib/cli/setup-external-system.js +21 -3
- package/lib/commands/agent-kit-help.js +74 -4
- package/lib/commands/agent-kit.js +83 -12
- package/lib/commands/auth-status-user.js +99 -0
- package/lib/commands/auth-status.js +14 -4
- package/lib/commands/datasource-load-export-cli.js +7 -2
- package/lib/commands/datasource-test-trust-cli.js +5 -1
- package/lib/commands/governance-command-action.js +5 -4
- package/lib/commands/identity-membership-role-cache.js +6 -1
- package/lib/commands/lifecycle-command-action.js +16 -15
- package/lib/commands/login-credentials.js +8 -3
- package/lib/commands/login-device.js +14 -5
- package/lib/commands/login.js +14 -1
- package/lib/commands/logout.js +50 -15
- package/lib/commands/repair-datasource-auto-rbac.js +4 -9
- package/lib/commands/role-assistant.js +24 -12
- package/lib/commands/upload-openapi-sync-followup.js +59 -0
- package/lib/commands/upload-qualification-client.js +131 -0
- package/lib/commands/upload-qualification-followups.js +104 -0
- package/lib/commands/upload-qualification-poll.js +143 -0
- package/lib/commands/upload.js +37 -156
- package/lib/commands/verify-governance-command-action.js +9 -7
- package/lib/commands/verify-operations-command-action.js +9 -8
- package/lib/commands/verify-trust-command-action.js +6 -5
- package/lib/deployment/deploy-poll-ui.js +14 -10
- package/lib/deployment/deployer-status.js +4 -4
- package/lib/external-system/deploy.js +46 -51
- package/lib/external-system/download.js +1 -2
- package/lib/generator/helpers.js +14 -46
- package/lib/generator/index.js +0 -4
- package/lib/integration-definition/apply-static-enterprise.js +5 -2
- package/lib/programmatic/builder-api-request.js +8 -4
- package/lib/programmatic/capability-registry.js +2 -0
- package/lib/programmatic/get-deploy-status.js +45 -0
- package/lib/programmatic/get-upload-status.js +45 -0
- package/lib/programmatic/http-accepted-response.js +31 -0
- package/lib/programmatic/openapi-descriptions.js +14 -4
- package/lib/programmatic/request-timeout.js +21 -15
- package/lib/programmatic/route-handler-map.js +6 -0
- package/lib/programmatic/run-deploy.js +7 -3
- package/lib/programmatic/run-upload.js +11 -7
- package/lib/role-assistant/knowledge-sync-settings-push.js +172 -0
- package/lib/role-assistant/knowledge-sync.js +9 -126
- package/lib/role-assistant/test-cases-evaluate.js +12 -1
- package/lib/role-assistant/test-cases-evidence.js +44 -5
- package/lib/role-assistant/test-cases-lifecycle.js +9 -3
- package/lib/role-assistant/test-runner.js +3 -0
- package/lib/schema/infra-parameter.schema.json +51 -3
- package/lib/schema/infra.parameter.yaml +482 -47
- package/lib/schema/type/document-storage.json +1 -1
- package/lib/utils/api-required-permissions.js +271 -0
- package/lib/utils/api.js +16 -13
- package/lib/utils/cli-json-exit.js +41 -0
- package/lib/utils/cli-utils.js +26 -7
- package/lib/utils/controller-url.js +78 -4
- package/lib/utils/deployment-errors.js +15 -0
- package/lib/utils/deployment-validation-helpers.js +4 -1
- package/lib/utils/device-code.js +31 -10
- package/lib/utils/error-formatters/error-parser.js +16 -1
- package/lib/utils/error-formatters/permission-errors.js +19 -5
- package/lib/utils/keycloak-realm-ready.js +5 -2
- package/lib/utils/logger.js +1 -0
- package/lib/utils/paths.js +26 -5
- package/lib/utils/qualification-poll-ui.js +136 -0
- package/lib/utils/secrets-helpers.js +2 -2
- package/lib/utils/write-stream-and-wait.js +63 -0
- package/package.json +22 -7
- package/templates/agent-kit/agent-kit.yaml +1 -1
- package/templates/agent-kit/instructions/AGENTKIT.md +25 -4
- package/templates/agent-kit/instructions/root.AGENTS.md +1 -1
- package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +2 -1
- package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +1 -1
- package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +2 -1
- package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +1 -0
- package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +1 -0
- package/templates/agent-kit/skills/shared/hosts.md +13 -26
- package/templates/agent-kit/skills/shared/login.md +16 -0
- package/templates/agent-kit/workspace/README.md +1 -1
- package/templates/applications/builder-api/env.template +4 -0
- package/templates/applications/dataplane/application.yaml +1 -2
- package/templates/applications/dataplane/env.template +5 -5
- package/templates/applications/keycloak/Dockerfile +12 -5
- package/templates/applications/keycloak/application.yaml +2 -2
- package/templates/applications/keycloak/cimd-lenient-parse/AbstractClientIdMetadataDocumentExecutor.java +3 -3
- package/templates/applications/keycloak/cimd-lenient-parse/SOURCE.md +6 -6
- package/templates/applications/keycloak/env.template +13 -1
- package/templates/applications/miso-controller/application.yaml +21 -2
- package/templates/applications/miso-controller/env.template +97 -28
- package/templates/applications/miso-controller/rbac.yaml +5 -0
- 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
|
|
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
|
-
|
|
115
|
-
addPermissionList(lines,
|
|
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
|
-
|
|
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}` : ''}). ` +
|
package/lib/utils/logger.js
CHANGED
package/lib/utils/paths.js
CHANGED
|
@@ -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
|
|
975
|
-
if (
|
|
976
|
-
|
|
977
|
-
|
|
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)
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
124
|
+
"qs": "^6.16.0",
|
|
125
|
+
"smol-toml": "^1.8.0"
|
|
111
126
|
}
|
|
112
127
|
},
|
|
113
128
|
"dependencies": {
|
|
114
|
-
"@aifabrix/miso-client": "
|
|
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.
|
|
139
|
+
"js-yaml": "^4.3.2",
|
|
125
140
|
"ora": "^5.4.1",
|
|
126
141
|
"yaml": "^2.9.0"
|
|
127
142
|
},
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
|