@aifabrix/builder 2.55.2 → 2.57.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/session-report.js +76 -0
- package/lib/agent-kit/start.js +124 -57
- package/lib/agent-kit/tmux-sessions.js +163 -0
- package/lib/api/environments.api.js +38 -0
- package/lib/api/marketplace-bootstrap.api.js +31 -0
- package/lib/api/pipeline.api.js +32 -3
- package/lib/app/deploy.js +6 -5
- package/lib/cli/setup-app.js +7 -6
- package/lib/cli/setup-environment.js +76 -19
- 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 +32 -15
- package/lib/commands/agent-kit.js +90 -8
- package/lib/commands/repair-datasource-auto-rbac.js +4 -9
- 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/deployment/deploy-poll-ui.js +14 -10
- package/lib/deployment/deployer-status.js +4 -4
- package/lib/deployment/installation/azure-infra-stage.js +160 -0
- package/lib/deployment/installation/azure-preflight.js +102 -0
- package/lib/deployment/installation/azure-readiness.js +71 -0
- package/lib/deployment/installation/docker-register.js +112 -0
- package/lib/deployment/installation/environment-auth.js +76 -0
- package/lib/deployment/installation/environment-manifest.js +165 -0
- package/lib/deployment/installation/environment-stage.js +191 -0
- package/lib/deployment/installation/index.js +39 -0
- package/lib/deployment/installation/infra-catalog.js +165 -0
- package/lib/deployment/installation/infra-manifest.js +161 -0
- package/lib/deployment/installation/infra-projection.js +86 -0
- package/lib/deployment/installation/public-ip.js +65 -0
- package/lib/deployment/installation/stage-input.js +169 -0
- package/lib/deployment/installation/stage-output.js +53 -0
- package/lib/deployment/installation/workspace-env.js +76 -0
- package/lib/deployment/installation/workspace-files.js +178 -0
- package/lib/deployment/installation/workspace-schema.js +40 -0
- package/lib/deployment/installation/workspace-secrets.js +66 -0
- package/lib/deployment/installation/workspace.js +128 -0
- package/lib/deployment/marketplace-environment.js +305 -0
- package/lib/external-system/deploy.js +46 -51
- package/lib/generator/builders.js +4 -0
- package/lib/generator/helpers.js +55 -99
- package/lib/generator/index.js +0 -4
- package/lib/generator/split-variables.js +11 -5
- package/lib/internal/fs-real-sync.js +29 -0
- 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/package-validate.js +4 -0
- package/lib/role-assistant/test-runner-clarification-resubmit.js +149 -0
- package/lib/role-assistant/test-runner.js +14 -17
- package/lib/schema/application-schema.json +20 -2
- package/lib/schema/infra-parameter.schema.json +7 -0
- package/lib/schema/infra.parameter.yaml +32 -43
- package/lib/schema/infrastructure-schema.json +1480 -399
- package/lib/schema/installation-environment.schema.json +601 -0
- package/lib/utils/cli-utils.js +3 -2
- package/lib/utils/qualification-poll-ui.js +136 -0
- package/lib/utils/variable-transformer.js +3 -0
- package/lib/validation/required-variables.js +41 -0
- package/lib/validation/validator.js +8 -5
- package/package.json +7 -2
- package/templates/README.md +4 -0
- package/templates/agent-kit/instructions/AGENTKIT.md +6 -6
- package/templates/agent-kit/skills/shared/hosts.md +10 -10
- package/templates/applications/builder-api/env.template +4 -0
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +1 -1
- 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 +3 -2
- package/templates/applications/miso-controller/env.template +28 -22
- package/templates/marketplace/createUiDefinition.json +77 -0
- package/templates/marketplace/install.parameters.example.json +9 -0
- package/templates/marketplace/main.json +4087 -0
- package/lib/generator/deploy-manifest-azure-kv.js +0 -81
|
@@ -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
|
+
};
|
|
@@ -301,6 +301,9 @@ function transformSimpleOptionalFields(variables, transformed) {
|
|
|
301
301
|
if (variables.externalIntegration) {
|
|
302
302
|
transformed.externalIntegration = variables.externalIntegration;
|
|
303
303
|
}
|
|
304
|
+
if (variables.required !== undefined) {
|
|
305
|
+
transformed.required = variables.required;
|
|
306
|
+
}
|
|
304
307
|
}
|
|
305
308
|
|
|
306
309
|
/**
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Semantic checks for the application top-level `required` variable list:
|
|
3
|
+
* every name must be declared by the manifest configuration and have a resolvable source.
|
|
4
|
+
* Shape (array, unique, name pattern) is enforced by application-schema.json.
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
function hasResolvableValue(item) {
|
|
12
|
+
if (item.location === 'keyvault') return typeof item.value === 'string' && item.value !== '';
|
|
13
|
+
return (typeof item.value === 'string' && item.value !== '') || Boolean(item.portalInput);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Validates top-level `required` names against the deployment manifest configuration.
|
|
18
|
+
* @param {Object} manifest - Deployment manifest (application schema shape)
|
|
19
|
+
* @returns {string[]} Error messages (empty when valid or when `required` is absent)
|
|
20
|
+
*/
|
|
21
|
+
function validateRequiredVariables(manifest) {
|
|
22
|
+
const required = manifest && manifest.required;
|
|
23
|
+
if (!Array.isArray(required) || required.length === 0) return [];
|
|
24
|
+
const byName = new Map();
|
|
25
|
+
for (const item of Array.isArray(manifest.configuration) ? manifest.configuration : []) {
|
|
26
|
+
if (item && typeof item.name === 'string') byName.set(item.name, item);
|
|
27
|
+
}
|
|
28
|
+
const errors = [];
|
|
29
|
+
for (const name of required) {
|
|
30
|
+
if (typeof name !== 'string') continue;
|
|
31
|
+
const item = byName.get(name);
|
|
32
|
+
if (!item) {
|
|
33
|
+
errors.push(`required: ${name} is not declared in the application configuration (env.template)`);
|
|
34
|
+
} else if (!hasResolvableValue(item)) {
|
|
35
|
+
errors.push(`required: ${name} has no value or portal input, so it cannot resolve for the target`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return errors;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
module.exports = { validateRequiredVariables };
|
|
@@ -24,6 +24,7 @@ const { loadConfigFile } = require('../utils/config-format');
|
|
|
24
24
|
const { validateAuthKvCoverage, validateAuthSecurityPathConsistency } = require('./env-template-auth');
|
|
25
25
|
const { validateKvReferencesInLines } = require('./env-template-kv');
|
|
26
26
|
|
|
27
|
+
const { validateRequiredVariables } = require('./required-variables');
|
|
27
28
|
/**
|
|
28
29
|
* Validates application config file against application schema
|
|
29
30
|
* Provides detailed error messages for configuration issues
|
|
@@ -366,13 +367,15 @@ function validateDeploymentJson(deployment) {
|
|
|
366
367
|
ajv.addSchema(externalDataSourceSchemaCopy, externalDataSourceSchema.$id);
|
|
367
368
|
const validate = ajv.compile(applicationSchema);
|
|
368
369
|
const valid = validate(deployment);
|
|
370
|
+
const schemaErrors = valid ? [] : formatValidationErrors(validate.errors, {
|
|
371
|
+
deploymentManifest: deployment,
|
|
372
|
+
rootData: deployment
|
|
373
|
+
});
|
|
374
|
+
const requiredErrors = validateRequiredVariables(deployment);
|
|
369
375
|
|
|
370
376
|
return {
|
|
371
|
-
valid,
|
|
372
|
-
errors:
|
|
373
|
-
deploymentManifest: deployment,
|
|
374
|
-
rootData: deployment
|
|
375
|
-
})
|
|
377
|
+
valid: valid && requiredErrors.length === 0,
|
|
378
|
+
errors: [...schemaErrors, ...requiredErrors]
|
|
376
379
|
};
|
|
377
380
|
}
|
|
378
381
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aifabrix/builder",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.57.0",
|
|
4
4
|
"description": "AI Fabrix Builder — CLI and developer scripts (pnpm + af)",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,8 +21,13 @@
|
|
|
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",
|
|
27
|
+
"agent-skills:sync": "node scripts/pnpm/agent-skills-sync.mjs",
|
|
28
|
+
"agent-skills:check": "node scripts/pnpm/agent-skills-sync.mjs --check",
|
|
29
|
+
"agent-skills:sync:codex": "node scripts/pnpm/agent-skills-sync.mjs --codex-home",
|
|
30
|
+
"agent-skills:test": "node --test scripts/pnpm/agent-skills-sync.test.mjs",
|
|
26
31
|
"check": "npm run lint && npm run test",
|
|
27
32
|
"lint": "eslint . --ext .js",
|
|
28
33
|
"lint:fix": "eslint . --ext .js --fix",
|
|
@@ -125,7 +130,7 @@
|
|
|
125
130
|
}
|
|
126
131
|
},
|
|
127
132
|
"dependencies": {
|
|
128
|
-
"@aifabrix/miso-client": "
|
|
133
|
+
"@aifabrix/miso-client": "4.22.4",
|
|
129
134
|
"adm-zip": "^0.6.0",
|
|
130
135
|
"ajv": "^8.20.0",
|
|
131
136
|
"ajv-formats": "^3.0.1",
|
package/templates/README.md
CHANGED
|
@@ -22,12 +22,16 @@ Application templates are folder-based and located under `templates/applications
|
|
|
22
22
|
| `aifabrix-builder/builder/builder-api/` | `templates/applications/builder-api/` |
|
|
23
23
|
| `aifabrix-miso/builder/miso-controller/` (`env.template`, `rbac.yaml`, `application.yaml`) | `templates/applications/miso-controller/` |
|
|
24
24
|
| `aifabrix-miso/builder/keycloak/` (`env.template`, `application.yaml`, `Dockerfile`, `cimd-lenient-parse/`) | `templates/applications/keycloak/` |
|
|
25
|
+
| `aifabrix-miso/infrastructure/marketplace/` (`main.json`, `createUiDefinition.json`) | `templates/marketplace/` |
|
|
25
26
|
| `aifabrix-dataplane/builder/dataplane/` (`env.template`, `rbac.yaml`, `application.yaml`) | `templates/applications/dataplane/` |
|
|
26
27
|
|
|
27
28
|
**Schema master (dataplane):** Also copies dataplane `app/schemas/json/` core integration schemas + `type/*` and `configs/deployment-rules.yaml` into `lib/schema/` (same contract as dataplane `make sync-json`). Do not hand-edit those mirrors — edit dataplane SSOT, then sync. Builder-owned schemas in `lib/schema/` (wizard, infra, etc.) are left alone.
|
|
28
29
|
|
|
29
30
|
Overrides: `AIFABRIX_MISO_SRC`, `AIFABRIX_DATAPLANE_SRC` / `AIFABRIX_DATAPLANE_ROOT` (repo roots). Install with `aifabrix up-platform` / `up-builder-api` — do not hand-edit the template copies as primary.
|
|
30
31
|
|
|
32
|
+
The Marketplace copies are compiled/source artifacts for a future Azure install command. `main.parameters.json` is intentionally excluded because installation secrets and customer-specific values must come from the installer's secure parameter file.
|
|
33
|
+
The two-stage CLI procedure and a blank parameter-file example are in [docs/marketplace-env-deploy.md](../docs/marketplace-env-deploy.md).
|
|
34
|
+
|
|
31
35
|
**Template Validation:**
|
|
32
36
|
- Template folder must exist in `templates/applications/<name>/`
|
|
33
37
|
- Template folder must contain at least one file
|
|
@@ -57,13 +57,13 @@ curl -fsSL https://claude.ai/install.sh | bash
|
|
|
57
57
|
claude auth login
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
**Run in tmux** from this workspace so closing the terminal does not kill Claude.
|
|
60
|
+
**Run in tmux** from this workspace so closing the terminal does not kill Claude. The command enables Remote Control and this workspace's Claude auto-approval by default. 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
61
|
|
|
62
62
|
```bash
|
|
63
|
-
aifabrix agent-kit start claude
|
|
63
|
+
aifabrix agent-kit start claude
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
tmux session name defaults to this folder. Remote Control name is `hostname-user-folder` (for example `builder01-dev03-aifabrix-docs`). Override
|
|
66
|
+
tmux session name defaults to this folder. Remote Control name is `hostname-user-folder` (for example `builder01-dev03-aifabrix-docs`). Override with `--name`; opt out with `--no-remote-control` and/or `--skip-approve-all`. Detach: `Ctrl-b` then `d`. Reattach: `tmux attach -t <folder>`. Use `aifabrix agent-kit list` and `agent-kit kill <folder>` to manage sessions. 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`.
|
|
67
67
|
{{/if}}
|
|
68
68
|
{{#if codex}}
|
|
69
69
|
|
|
@@ -76,13 +76,13 @@ npm install -g @openai/codex
|
|
|
76
76
|
codex login
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
**Run in tmux** from this workspace so the session stays if you close the terminal:
|
|
79
|
+
**Run in tmux** from this workspace so the session stays if you close the terminal. The command starts native Codex Remote Control and writes this workspace's Codex auto-approval by default:
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
|
-
aifabrix agent-kit start codex
|
|
82
|
+
aifabrix agent-kit start codex
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
Detach: `Ctrl-b` then `d`. Reattach: `tmux attach -t <folder
|
|
85
|
+
Detach: `Ctrl-b` then `d`. Reattach: `tmux attach -t <folder>`. A duplicate `start` fails instead of attaching. Default session name is this folder. Use `--no-remote-control` and/or `--skip-approve-all` to opt out. Skills: `$aifabrix-plan` (and the other `$aifabrix-*` names). Codex reads `AGENTS.md`. Full notes: `.agents/skills/shared/hosts.md`.
|
|
86
86
|
{{/if}}
|
|
87
87
|
|
|
88
88
|
## Auth for delivery CLI
|
|
@@ -6,14 +6,14 @@ Install and sign-in steps are in `AGENTKIT.md`. This file is how to **run** each
|
|
|
6
6
|
|
|
7
7
|
Do not start Claude or Codex in your home directory. `cd` into this git workspace first.
|
|
8
8
|
|
|
9
|
-
## Auto-approve
|
|
9
|
+
## Auto-approve
|
|
10
10
|
|
|
11
11
|
`aifabrix agent-kit install` can write gitignored host configs so Claude Code and Codex do not prompt for every tool:
|
|
12
12
|
|
|
13
13
|
- Claude Code: `.claude/settings.local.json` (`permissions.defaultMode` = `bypassPermissions`, `allow` = `Bash(aifabrix *)` and `Read(~/**)`)
|
|
14
14
|
- Codex: `.codex/config.toml` (`approval_policy = "never"`, `sandbox_mode = "workspace-write"`)
|
|
15
15
|
|
|
16
|
-
Those files stay in **this workspace**. Claude may **read** files under the developer home (`Read(~/**)`) and run the delivery CLI (`Bash(aifabrix *)`). They do not add sibling folders as extra workspaces, do not grant Write outside this repo, and do not allow every shell command.
|
|
16
|
+
Those files stay in **this workspace**. Claude may **read** files under the developer home (`Read(~/**)`) and run the delivery CLI (`Bash(aifabrix *)`). They do not add sibling folders as extra workspaces, do not grant Write outside this repo, and do not allow every shell command. `agent-kit start claude|codex` writes only the selected host's file by default; `--skip-approve-all` opts out. Install/update retain their explicit approval prompts and flags. `--force` overwrites existing local files. Builder never writes `~/.claude` or `~/.codex`.
|
|
17
17
|
|
|
18
18
|
## Cursor
|
|
19
19
|
|
|
@@ -23,13 +23,13 @@ Those files stay in **this workspace**. Claude may **read** files under the deve
|
|
|
23
23
|
|
|
24
24
|
## Claude Code
|
|
25
25
|
|
|
26
|
-
**Local or SSH — tmux.** Closing the terminal must not kill Claude. Agent Kit starts Claude **inside tmux
|
|
26
|
+
**Local or SSH — tmux.** Closing the terminal must not kill Claude. Agent Kit starts Claude **inside tmux**, with Remote Control and workspace auto-approval enabled by default. Remote Control is the **same** session on this PC, [claude.ai/code](https://claude.ai/code), and the Claude mobile app (**Code**). Do not use `claude --cloud` for this — that starts a different machine that clones GitHub. Claude `--tmux` is only for `--worktree` sessions, not this wrapper.
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
|
-
aifabrix agent-kit start claude
|
|
29
|
+
aifabrix agent-kit start claude
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
tmux session name is this folder (for example `aifabrix-docs`). Remote Control name is `hostname-user-folder` (for example `builder01-dev03-aifabrix-docs`). `--name` overrides the folder. Several workspaces can run at once. Detach without killing Claude: `Ctrl-b` then `d`. Reattach: `tmux attach -t aifabrix-docs
|
|
32
|
+
tmux session name is this folder (for example `aifabrix-docs`). Remote Control name is `hostname-user-folder` (for example `builder01-dev03-aifabrix-docs`). `--name` overrides the folder; `--no-remote-control` and `--skip-approve-all` opt out. Several workspaces can run at once. Detach without killing Claude: `Ctrl-b` then `d`. Reattach: `tmux attach -t aifabrix-docs`. A duplicate `start` fails instead of attaching. Then `/aifabrix-plan` (or another `/aifabrix-*` skill). First run in a project asks you to trust the folder; accept only this workspace.
|
|
33
33
|
|
|
34
34
|
**Phone and PC.** Sign in with the same Claude Pro/Max/Team account (`claude auth login` — not an API key). After Remote Control starts, open **Code** in the Claude mobile app and/or [claude.ai/code](https://claude.ai/code) on the PC. In an already-running session, `/remote-control` or `/mobile`.
|
|
35
35
|
|
|
@@ -37,13 +37,13 @@ Closing the terminal (or SSH) does not stop a tmux session. Stopping tmux or the
|
|
|
37
37
|
|
|
38
38
|
## Codex
|
|
39
39
|
|
|
40
|
-
**Local or SSH — tmux
|
|
40
|
+
**Local or SSH — tmux and Remote Control.** tmux keeps Codex running if you close the terminal or disconnect SSH. Agent Kit also starts Codex's native remote-control daemon and workspace auto-approval by default.
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
aifabrix agent-kit start codex
|
|
43
|
+
aifabrix agent-kit start codex
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
Default `--name` is the last folder. `--no-remote-control` keeps the launch local; `--skip-approve-all` leaves Codex permissions unchanged. If that session already exists, `start` fails with list/kill guidance. Detach: `Ctrl-b` then `d`. Reattach with `tmux attach -t aifabrix-docs`. Then `$aifabrix-plan` (and the other `$aifabrix-*` skills). Codex reads `AGENTS.md`. Killing tmux does not stop the shared daemon; use `codex remote-control stop` when required.
|
|
47
47
|
|
|
48
48
|
**Foreground only (no tmux).**
|
|
49
49
|
|
|
@@ -58,8 +58,8 @@ codex
|
|
|
58
58
|
| --- | --- |
|
|
59
59
|
| New named session | `tmux new -s <name> -c /path/to/this-workspace` |
|
|
60
60
|
| Detach (leave it running) | `Ctrl-b`, then `d` |
|
|
61
|
-
| List sessions | `
|
|
61
|
+
| List sessions | `aifabrix agent-kit list` |
|
|
62
62
|
| Attach | `tmux attach -t <name>` |
|
|
63
|
-
| Kill session | `
|
|
63
|
+
| Kill session | `aifabrix agent-kit kill <name>` |
|
|
64
64
|
|
|
65
65
|
Use a different `--name` per workspace so several Claude or Codex sessions can run on the same host. Claude and Codex in the **same** folder also need two names.
|
|
@@ -37,6 +37,10 @@ DATAPLANE_URL=url://dataplane-internal
|
|
|
37
37
|
# Public: Slack/Teams/ChatGPT callbacks, packages, and portal remaining-step URLs
|
|
38
38
|
DATAPLANE_PUBLIC_URL=url://dataplane-public
|
|
39
39
|
|
|
40
|
+
# Self URLs: internal service calls and public links generated by Builder API
|
|
41
|
+
BUILDER_API_URL=url://internal
|
|
42
|
+
BUILDER_API_PUBLIC_URL=url://public
|
|
43
|
+
|
|
40
44
|
DEFAULT_ENVIRONMENT=dev
|
|
41
45
|
|
|
42
46
|
# Browser Origin allowlist for portal calls (CORS). url:// expands at resolve.
|
|
@@ -5,7 +5,7 @@ app:
|
|
|
5
5
|
description: "AI Fabrix Dataplane is a secure, in-tenant integration and automation layer that supplies governed, normalized, and explainable enterprise data to AI agents. Using CIP as a declarative standard, it enforces RBAC and ABAC, executes integrations, and exposes trusted data via MCP and OpenAPI."
|
|
6
6
|
type: webapp
|
|
7
7
|
language: python # Explicitly specify Python language
|
|
8
|
-
version: 2.0.
|
|
8
|
+
version: 2.0.70
|
|
9
9
|
# Image Configuration
|
|
10
10
|
# Set tag to match your build (example: aifabrix build dataplane -t 1.0.0).
|
|
11
11
|
# Registry is required so the controller can pull the image (avoids docker-not-found on the controller host).
|
|
@@ -44,7 +44,7 @@ API_KEY_BYPASS_ROLES=full-access
|
|
|
44
44
|
|
|
45
45
|
# API Configuration
|
|
46
46
|
API_V1_STR=/api/v1
|
|
47
|
-
VERSION=2.0.
|
|
47
|
+
VERSION=2.0.70
|
|
48
48
|
# Minimum supported aifabrix-builder CLI semver (advertised on /health and /api/v1/health; empty = no gate)
|
|
49
49
|
# MIN_BUILDER_CLI_VERSION=2.45.0
|
|
50
50
|
MIN_BUILDER_CLI_VERSION=2.45.5
|
|
@@ -1,29 +1,34 @@
|
|
|
1
1
|
# Keycloak Identity and Access Management with Custom Themes
|
|
2
|
-
# Official 26.7.
|
|
2
|
+
# Official 26.7.3 distro + experimental CIMD. Does not compile Keycloak from source.
|
|
3
3
|
# One class is recompiled against this image's jars:
|
|
4
4
|
# - PR 51235: CIMD parse ignores unknown JSON fields (ChatGPT client.json)
|
|
5
5
|
# - #50362: strip jwt-bearer (and other confidential-only grants) on public CIMD
|
|
6
6
|
# 26.7 advertises token_endpoint_auth_methods_supported "none" for Claude CIMD.
|
|
7
7
|
|
|
8
|
-
FROM quay.io/keycloak/keycloak:26.7.
|
|
8
|
+
FROM quay.io/keycloak/keycloak:26.7.3 AS keycloak-base
|
|
9
9
|
|
|
10
10
|
FROM eclipse-temurin:21-jdk-jammy AS cimd-patch
|
|
11
11
|
COPY --from=keycloak-base /opt/keycloak/lib/lib/main /opt/kc-jars/main
|
|
12
12
|
COPY --from=keycloak-base /opt/keycloak/lib/lib/boot /opt/kc-jars/boot
|
|
13
13
|
COPY cimd-lenient-parse/AbstractClientIdMetadataDocumentExecutor.java \
|
|
14
14
|
/tmp/src/org/keycloak/protocol/oauth2/cimd/clientpolicy/executor/AbstractClientIdMetadataDocumentExecutor.java
|
|
15
|
+
COPY mori-email-sender/com /tmp/src/com
|
|
16
|
+
COPY mori-email-sender/META-INF /tmp/mori-provider/META-INF
|
|
15
17
|
RUN mkdir -p /tmp/classes \
|
|
16
18
|
&& find /opt/kc-jars -name '*.jar' | paste -sd: - > /tmp/cp.txt \
|
|
17
19
|
&& { \
|
|
18
20
|
echo --release; echo 21; echo -cp; cat /tmp/cp.txt; echo -d; echo /tmp/classes; \
|
|
19
21
|
echo /tmp/src/org/keycloak/protocol/oauth2/cimd/clientpolicy/executor/AbstractClientIdMetadataDocumentExecutor.java; \
|
|
22
|
+
find /tmp/src/com -name '*.java'; \
|
|
20
23
|
} > /tmp/javac.args \
|
|
21
24
|
&& javac @/tmp/javac.args \
|
|
22
25
|
&& SERVICES_JAR=$(ls /opt/kc-jars/main/org.keycloak.keycloak-services-*.jar) \
|
|
23
26
|
&& cp "$SERVICES_JAR" /tmp/keycloak-services-patched.jar \
|
|
24
|
-
&& jar uf /tmp/keycloak-services-patched.jar -C /tmp/classes
|
|
27
|
+
&& jar uf /tmp/keycloak-services-patched.jar -C /tmp/classes org/keycloak \
|
|
28
|
+
&& cp -r /tmp/classes/com /tmp/mori-provider/ \
|
|
29
|
+
&& jar cf /tmp/mori-email-sender.jar -C /tmp/mori-provider .
|
|
25
30
|
|
|
26
|
-
FROM quay.io/keycloak/keycloak:26.7.
|
|
31
|
+
FROM quay.io/keycloak/keycloak:26.7.3
|
|
27
32
|
|
|
28
33
|
# Set working directory
|
|
29
34
|
WORKDIR /opt/keycloak
|
|
@@ -31,6 +36,7 @@ WORKDIR /opt/keycloak
|
|
|
31
36
|
# Copy custom themes from share directory (only if themes/ folder exists)
|
|
32
37
|
USER root
|
|
33
38
|
COPY --from=cimd-patch /tmp/keycloak-services-patched.jar /tmp/keycloak-services-patched.jar
|
|
39
|
+
COPY --from=cimd-patch /tmp/mori-email-sender.jar /opt/keycloak/providers/mori-email-sender.jar
|
|
34
40
|
RUN SERVICES_JAR=$(ls /opt/keycloak/lib/lib/main/org.keycloak.keycloak-services-*.jar) \
|
|
35
41
|
&& cp /tmp/keycloak-services-patched.jar "$SERVICES_JAR" \
|
|
36
42
|
&& rm /tmp/keycloak-services-patched.jar
|
|
@@ -50,7 +56,8 @@ RUN if [ -d "/tmp/build-context/themes" ] && [ "$(ls -A /tmp/build-context/theme
|
|
|
50
56
|
# Build Keycloak with health checks enabled (similar to Keycloak 24.0)
|
|
51
57
|
# This enables the /health, /health/live, /health/ready, and /health/started endpoints
|
|
52
58
|
# Expose health endpoints on main HTTP port (like 24.0) instead of management port
|
|
53
|
-
RUN /opt/keycloak/bin/kc.sh build --health-enabled=true --http-management-health-enabled=false
|
|
59
|
+
RUN /opt/keycloak/bin/kc.sh build --health-enabled=true --http-management-health-enabled=false \
|
|
60
|
+
--features=cimd --spi-email-sender--provider=mori
|
|
54
61
|
|
|
55
62
|
# Switch back to keycloak user
|
|
56
63
|
USER keycloak
|
|
@@ -4,12 +4,12 @@ app:
|
|
|
4
4
|
displayName: 'AI Fabrix Keycloak'
|
|
5
5
|
description: 'Identity and Access Management'
|
|
6
6
|
type: webapp
|
|
7
|
-
version: '26.7.
|
|
7
|
+
version: '26.7.3-aifabrix.1'
|
|
8
8
|
|
|
9
9
|
# Image Configuration
|
|
10
10
|
image:
|
|
11
11
|
name: aifabrix/keycloak
|
|
12
|
-
tag: '
|
|
12
|
+
tag: '26.7.3-aifabrix.1'
|
|
13
13
|
registry: aifabrixacr.azurecr.io
|
|
14
14
|
registryMode: external
|
|
15
15
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
package org.keycloak.protocol.oauth2.cimd.clientpolicy.executor;
|
|
2
2
|
|
|
3
|
-
// AI Fabrix Keycloak image patch on 26.7.
|
|
3
|
+
// AI Fabrix Keycloak image patch on 26.7.3 (not a Keycloak source build):
|
|
4
4
|
// 1) PR 51235 — ignore unknown CIMD JSON fields (ChatGPT client.json).
|
|
5
5
|
// 2) keycloak#50362 / PR 50411 idea — drop confidential-only grants (jwt-bearer)
|
|
6
6
|
// from public CIMD metadata so Claude hosted Connect can register.
|
|
@@ -848,7 +848,7 @@ public abstract class AbstractClientIdMetadataDocumentExecutor<CONFIG extends Ab
|
|
|
848
848
|
|
|
849
849
|
/**
|
|
850
850
|
* Grant types Keycloak's DescriptionConverter refuses on a public client.
|
|
851
|
-
* Same set as Keycloak PR 50411 (not in 26.7.
|
|
851
|
+
* Same set as Keycloak PR 50411 (not in 26.7.3).
|
|
852
852
|
*/
|
|
853
853
|
protected static final Set<String> CONFIDENTIAL_ONLY_GRANT_TYPES = new LinkedHashSet<>(Arrays.asList(
|
|
854
854
|
OAuth2Constants.CLIENT_CREDENTIALS,
|
|
@@ -873,7 +873,7 @@ public abstract class AbstractClientIdMetadataDocumentExecutor<CONFIG extends Ab
|
|
|
873
873
|
|
|
874
874
|
/**
|
|
875
875
|
* RFC 7591 §3.2.1 lets the AS replace requested grant_types. Claude CIMD is public
|
|
876
|
-
* (`none`) plus jwt-bearer; Keycloak 26.7.
|
|
876
|
+
* (`none`) plus jwt-bearer; Keycloak 26.7.3 rejects that combination (#50362).
|
|
877
877
|
*/
|
|
878
878
|
protected void stripConfidentialOnlyGrantsForPublicClient(OIDCClientRepresentation oidcClient) {
|
|
879
879
|
if (!"none".equals(oidcClient.getTokenEndpointAuthMethod())) {
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
# CIMD image patch (Keycloak 26.7.
|
|
1
|
+
# CIMD image patch (Keycloak 26.7.3)
|
|
2
2
|
|
|
3
|
-
This directory is **not** a Keycloak source build. `aifabrix build keycloak` still starts from `quay.io/keycloak/keycloak:26.7.
|
|
3
|
+
This directory is **not** a Keycloak source build. `aifabrix build keycloak` still starts from `quay.io/keycloak/keycloak:26.7.3`. A Docker stage recompiles this one class against that image’s jars and injects it into `org.keycloak.keycloak-services-*.jar` before `kc.sh build`.
|
|
4
4
|
|
|
5
|
-
Upstream file: `services/.../AbstractClientIdMetadataDocumentExecutor.java` @ tag `26.7.
|
|
5
|
+
Upstream file: `services/.../AbstractClientIdMetadataDocumentExecutor.java` @ tag `26.7.3` (Apache 2.0).
|
|
6
6
|
|
|
7
7
|
## Lenient parse (ChatGPT)
|
|
8
8
|
|
|
9
9
|
ChatGPT’s CIMD `client.json` includes extra fields (for example `token_endpoint_auth_methods_supported`) that Keycloak 26.7.x rejects because CIMD parse uses Jackson `FAIL_ON_UNKNOWN_PROPERTIES`.
|
|
10
10
|
|
|
11
|
-
- [keycloak/keycloak#51235](https://github.com/keycloak/keycloak/pull/51235) (open; not in 26.7.
|
|
11
|
+
- [keycloak/keycloak#51235](https://github.com/keycloak/keycloak/pull/51235) (open; not in 26.7.3)
|
|
12
12
|
|
|
13
13
|
## Public client + jwt-bearer (Claude hosted)
|
|
14
14
|
|
|
15
|
-
Claude’s CIMD is a public client (`token_endpoint_auth_method: none`) that also lists `urn:ietf:params:oauth:grant-type:jwt-bearer`. Keycloak 26.7.
|
|
15
|
+
Claude’s CIMD is a public client (`token_endpoint_auth_method: none`) that also lists `urn:ietf:params:oauth:grant-type:jwt-bearer`. Keycloak 26.7.3 then fails authorize with `JWT authorization grant cannot be enabled in a public client` ([#50362](https://github.com/keycloak/keycloak/issues/50362)). Official [PR 50411](https://github.com/keycloak/keycloak/pull/50411) is still a draft and defaults the filter **off**. This image always drops confidential-only grants on public CIMD metadata (RFC 7591 §3.2.1), which is enough for hosted Claude Connect (authorization_code). It does **not** implement public-client JWT Authorization Grant / EMA.
|
|
16
16
|
|
|
17
|
-
26.7.
|
|
17
|
+
26.7.3 already advertises `token_endpoint_auth_methods_supported` `"none"` when `--features=cimd`.
|
|
18
18
|
|
|
19
19
|
Drop this patched class when the consumed `quay.io` tag includes both behaviors.
|
|
@@ -55,7 +55,7 @@ KC_HEALTH_ENABLED=true
|
|
|
55
55
|
KC_HTTP_MANAGEMENT_HEALTH_ENABLED=false
|
|
56
56
|
# Single-instance: use local cache so /health/ready passes (avoids Infinispan cluster check)
|
|
57
57
|
KC_CACHE=local
|
|
58
|
-
# Runtime features. Include cimd so Keycloak 26.7.
|
|
58
|
+
# Runtime features. Include cimd so Keycloak 26.7.3 advertises Client ID Metadata
|
|
59
59
|
# Documents. preview alone triggers a start-time rebuild that drops cimd.
|
|
60
60
|
KC_FEATURES=preview,cimd
|
|
61
61
|
|
|
@@ -67,6 +67,18 @@ KC_FEATURES=preview,cimd
|
|
|
67
67
|
MISO_CLIENTID=kv://keycloak-client-idKeyVault
|
|
68
68
|
MISO_CLIENTSECRET=kv://keycloak-client-secretKeyVault
|
|
69
69
|
|
|
70
|
+
# =============================================================================
|
|
71
|
+
# MANAGED ACTION EMAIL (Keycloak -> Mori REST)
|
|
72
|
+
# =============================================================================
|
|
73
|
+
# The custom EmailSenderProvider relays Keycloak-rendered signed action emails to
|
|
74
|
+
# Mori. It never receives ACS credentials and never sends the action URL to Miso.
|
|
75
|
+
# Installation must supply the Mori environment returned during registration.
|
|
76
|
+
MORI_BASE_URL=kv://mori-controller-web-server-url
|
|
77
|
+
MORI_ENVIRONMENT_ID=kv://mori-controller-environment-idKeyVault
|
|
78
|
+
MORI_API_KEY=kv://mori-controller-api-keyKeyVault
|
|
79
|
+
MORI_NOTIFICATION_TIMEOUT_MS=10000
|
|
80
|
+
MORI_NOTIFICATION_MAX_ATTEMPTS=2
|
|
81
|
+
|
|
70
82
|
# Connects to postgres service in Docker network (postgres) or localhost (local)
|
|
71
83
|
|
|
72
84
|
KC_DB=postgres
|
|
@@ -4,7 +4,7 @@ app:
|
|
|
4
4
|
displayName: 'Miso Controller'
|
|
5
5
|
description: 'Miso is the AI Fabrix in-tenant controller and portal layer for securely operating enterprise AI apps inside a customer’s Azure tenant. It provides Entra ID SSO, RBAC, audit logs, environment/app configuration via schemas, and safe secret handling via Key Vault references—ensuring governance, traceability, and predictable UX across portal, SDK, and CLI.'
|
|
6
6
|
type: webapp
|
|
7
|
-
version: '2.0.
|
|
7
|
+
version: '2.0.81'
|
|
8
8
|
# Image Configuration
|
|
9
9
|
# Set tag to match your build (e.g. aifabrix build miso-controller -t 1.0.0 then tag: 1.0.0)
|
|
10
10
|
# Registry is required so the controller can pull the image (avoids "docker: not found" on the controller host).
|
|
@@ -58,7 +58,8 @@ build:
|
|
|
58
58
|
dockerfile: builder/miso-controller/Dockerfile # Dockerfile name (relative to project root)
|
|
59
59
|
envOutputPath: ../../packages/miso-controller/.env # Copy .env to repo root for local dev (relative to builder/) (if null, no .env file is copied) (if empty, .env file is copied to repo root)
|
|
60
60
|
language: typescript # Runtime language for template selection (typescript or python)
|
|
61
|
-
|
|
61
|
+
# Runtime image has npm/npx, not pnpm. Keep the existing dev:watch:public script.
|
|
62
|
+
reloadStart: npm --prefix packages/miso-controller run dev:watch:public
|
|
62
63
|
|
|
63
64
|
# Repository Configuration (pipeline validate: REPOSITORY_URL_MISMATCH)
|
|
64
65
|
repository:
|