@aifabrix/builder 2.56.0 → 2.58.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/app/push.js +98 -41
- package/lib/build/standard-docker-build.js +13 -11
- package/lib/cli/setup-app.js +16 -5
- package/lib/cli/setup-dev-path-commands.js +49 -2
- package/lib/cli/setup-environment.js +89 -19
- package/lib/commands/agent-kit-help.js +32 -15
- package/lib/commands/agent-kit.js +90 -8
- package/lib/commands/dev-registry.js +155 -0
- package/lib/commands/dev-show-display.js +10 -2
- package/lib/commands/setup-image-refresh.js +26 -0
- package/lib/core/config-attach-extensions.js +4 -1
- package/lib/deployment/installation/azure-infra-stage.js +191 -0
- package/lib/deployment/installation/azure-preflight.js +147 -0
- package/lib/deployment/installation/azure-readiness.js +101 -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 +211 -0
- package/lib/deployment/installation/infra-manifest.js +176 -0
- package/lib/deployment/installation/infra-projection.js +97 -0
- package/lib/deployment/installation/public-ip.js +65 -0
- package/lib/deployment/installation/registry-auth-mode.js +98 -0
- package/lib/deployment/installation/stage-input.js +184 -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/generator/builders.js +12 -9
- package/lib/generator/helpers.js +55 -56
- package/lib/generator/split-variables.js +11 -5
- package/lib/internal/fs-real-sync.js +29 -0
- 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 +26 -36
- package/lib/schema/infrastructure-schema.json +1531 -397
- package/lib/schema/installation-environment.schema.json +601 -0
- package/lib/utils/app-register-config.js +2 -4
- package/lib/utils/build-resolve-image.js +2 -21
- package/lib/utils/cli-utils.js +8 -1
- package/lib/utils/config-registry-preference.js +106 -0
- package/lib/utils/docker-build.js +53 -19
- package/lib/utils/env-map.js +23 -6
- package/lib/utils/image-tags.js +88 -0
- package/lib/utils/promote-dev-docker-image.js +6 -0
- package/lib/utils/registry-credentials.js +236 -0
- package/lib/utils/resolve-docker-image-ref.js +78 -9
- package/lib/utils/secrets-helpers.js +9 -1
- package/lib/utils/variable-transformer.js +5 -4
- package/lib/validation/required-variables.js +41 -0
- package/lib/validation/validator.js +8 -5
- package/package.json +6 -1
- 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/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/miso-controller/application.yaml +3 -2
- package/templates/applications/miso-controller/env.template +15 -6
- package/templates/marketplace/createUiDefinition.json +77 -0
- package/templates/marketplace/install.parameters.example.json +9 -0
- package/templates/marketplace/main.json +4087 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolve Docker repository path and tag from application config and optional CLI overrides.
|
|
3
|
-
* Precedence: --image (full ref), --registry CLI,
|
|
3
|
+
* Precedence: --image (full ref), --registry CLI, installation `registry` in config.yaml,
|
|
4
|
+
* image.registry in manifest, else unqualified name.
|
|
4
5
|
*
|
|
5
6
|
* For refs like localhost:5000/repo without an explicit tag, prefer --image with :tag (parse ambiguity).
|
|
6
7
|
*
|
|
@@ -12,6 +13,7 @@
|
|
|
12
13
|
'use strict';
|
|
13
14
|
|
|
14
15
|
const { parseImageOverride } = require('./parse-image-ref');
|
|
16
|
+
const { readInstallationRegistrySync } = require('./config-registry-preference');
|
|
15
17
|
const DOCKER_REPOSITORY_RE = /^[a-z0-9][a-z0-9._/-]*$/i;
|
|
16
18
|
const DOCKER_TAG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
17
19
|
|
|
@@ -92,6 +94,73 @@ function normalizeDockerRegistryPrefix(registry) {
|
|
|
92
94
|
return t.slice(0, end);
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Remove a registry host prefix from a Docker repository path.
|
|
99
|
+
* The first segment is a host when it contains `.` or `:` or equals `localhost`.
|
|
100
|
+
* @param {string} repository
|
|
101
|
+
* @returns {string}
|
|
102
|
+
*/
|
|
103
|
+
function stripRegistryHostFromRepository(repository) {
|
|
104
|
+
if (!repository || typeof repository !== 'string') {
|
|
105
|
+
return repository;
|
|
106
|
+
}
|
|
107
|
+
const parts = repository.split('/');
|
|
108
|
+
if (parts.length <= 1) {
|
|
109
|
+
return repository;
|
|
110
|
+
}
|
|
111
|
+
const host = parts[0];
|
|
112
|
+
if (host.includes('.') || host.includes(':') || host === 'localhost') {
|
|
113
|
+
return parts.slice(1).join('/');
|
|
114
|
+
}
|
|
115
|
+
return repository;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Effective registry for an application image.
|
|
120
|
+
* Precedence: `options.registry` (CLI) > installation `registry` > manifest `image.registry`.
|
|
121
|
+
* @param {Object} [appConfig] - Application manifest
|
|
122
|
+
* @param {Object} [options] - `registry` (CLI) and optional `installationRegistry` (preloaded; '' = none)
|
|
123
|
+
* @returns {{ registry: string, source: 'cli'|'installation'|'manifest'|'none' }}
|
|
124
|
+
*/
|
|
125
|
+
function resolveEffectiveRegistry(appConfig, options = {}) {
|
|
126
|
+
const opts = options || {};
|
|
127
|
+
const cli = normalizeDockerRegistryPrefix(opts.registry);
|
|
128
|
+
if (cli) return { registry: cli, source: 'cli' };
|
|
129
|
+
const installation = opts.installationRegistry !== undefined
|
|
130
|
+
? normalizeDockerRegistryPrefix(opts.installationRegistry)
|
|
131
|
+
: readInstallationRegistrySync();
|
|
132
|
+
if (installation) return { registry: installation, source: 'installation' };
|
|
133
|
+
const manifest = normalizeDockerRegistryPrefix(appConfig?.image?.registry ?? '');
|
|
134
|
+
if (manifest) return { registry: manifest, source: 'manifest' };
|
|
135
|
+
return { registry: '', source: 'none' };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Prefix a repository with a registry exactly once (replacing any existing host).
|
|
140
|
+
* @param {string} registry - Registry prefix ('' keeps the repository unchanged)
|
|
141
|
+
* @param {string} repository - Repository path, possibly already qualified
|
|
142
|
+
* @returns {string}
|
|
143
|
+
*/
|
|
144
|
+
function qualifyRepository(registry, repository) {
|
|
145
|
+
const prefix = normalizeDockerRegistryPrefix(registry);
|
|
146
|
+
if (!prefix) return repository;
|
|
147
|
+
return `${prefix}/${stripRegistryHostFromRepository(repository)}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Deployment/registration image reference (`registry/name:tag`) using the shared precedence.
|
|
152
|
+
* @param {Object} variables - Application manifest
|
|
153
|
+
* @param {string} fallbackName - Name when the manifest has no image name or app key
|
|
154
|
+
* @param {Object} [options] - Same options as {@link resolveEffectiveRegistry}
|
|
155
|
+
* @returns {string}
|
|
156
|
+
*/
|
|
157
|
+
function buildManifestImageReference(variables, fallbackName, options = {}) {
|
|
158
|
+
const imageName = variables?.image?.name || variables?.app?.key || fallbackName;
|
|
159
|
+
const tag = variables?.image?.tag || 'latest';
|
|
160
|
+
const { registry } = resolveEffectiveRegistry(variables, options);
|
|
161
|
+
return `${qualifyRepository(registry, imageName)}:${tag}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
95
164
|
/**
|
|
96
165
|
* Effective image repository (may include registry prefix) and tag for Docker.
|
|
97
166
|
* @param {string} appName - Application name
|
|
@@ -118,13 +187,8 @@ function resolveDockerImageRef(appName, appConfig, runOptions = {}) {
|
|
|
118
187
|
|
|
119
188
|
const baseRepo = assertSafeRepository(getRepositoryPathFromConfig(appConfig, appName));
|
|
120
189
|
const imageTag = assertSafeTag(tagFromOpts || imageTagFromConfig(appConfig));
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
normalizeDockerRegistryPrefix(appConfig?.image?.registry ?? '');
|
|
124
|
-
if (prefix) {
|
|
125
|
-
return { imageName: assertSafeRepository(`${prefix}/${baseRepo}`), imageTag };
|
|
126
|
-
}
|
|
127
|
-
return { imageName: baseRepo, imageTag };
|
|
190
|
+
const { registry } = resolveEffectiveRegistry(appConfig, opts);
|
|
191
|
+
return { imageName: assertSafeRepository(qualifyRepository(registry, baseRepo)), imageTag };
|
|
128
192
|
}
|
|
129
193
|
|
|
130
194
|
/**
|
|
@@ -137,7 +201,7 @@ function resolveDockerImageRef(appName, appConfig, runOptions = {}) {
|
|
|
137
201
|
function resolveComposeImageOverrideString(appName, appConfig, options = {}) {
|
|
138
202
|
if (options.image) return options.image;
|
|
139
203
|
if (options.imageOverride) return options.imageOverride;
|
|
140
|
-
const runOpts = { registry: options.registry, image: undefined };
|
|
204
|
+
const runOpts = { registry: options.registry, installationRegistry: options.installationRegistry, image: undefined };
|
|
141
205
|
if (options.tag) {
|
|
142
206
|
const { imageName } = resolveDockerImageRef(appName, appConfig, runOpts);
|
|
143
207
|
return `${imageName}:${options.tag}`;
|
|
@@ -154,6 +218,11 @@ function resolveComposeImageOverrideString(appName, appConfig, options = {}) {
|
|
|
154
218
|
module.exports = {
|
|
155
219
|
resolveDockerImageRef,
|
|
156
220
|
resolveComposeImageOverrideString,
|
|
221
|
+
resolveEffectiveRegistry,
|
|
222
|
+
qualifyRepository,
|
|
223
|
+
buildManifestImageReference,
|
|
224
|
+
stripRegistryHostFromRepository,
|
|
225
|
+
assertSafeTag,
|
|
157
226
|
normalizeDockerRegistryPrefix,
|
|
158
227
|
getRepositoryPathFromConfig,
|
|
159
228
|
imageTagFromConfig
|
|
@@ -29,7 +29,15 @@ const { collectUniqueKvPathStrings, isCommentOrEmptyLine, activeEnvLineForKvScan
|
|
|
29
29
|
* @returns {string} Interpolated content
|
|
30
30
|
*/
|
|
31
31
|
function interpolateEnvVars(content, envVars) {
|
|
32
|
-
|
|
32
|
+
const hasOwn = (key) => Object.prototype.hasOwnProperty.call(envVars, key);
|
|
33
|
+
const interpolated = content.replace(
|
|
34
|
+
/\$\{DEV_USERNAME\}\.\$\{REMOTE_HOST\}/g,
|
|
35
|
+
(match) => {
|
|
36
|
+
if (!hasOwn('DEV_USERNAME') || !hasOwn('REMOTE_HOST')) return match;
|
|
37
|
+
return [envVars.DEV_USERNAME, envVars.REMOTE_HOST].filter(Boolean).join('.');
|
|
38
|
+
}
|
|
39
|
+
);
|
|
40
|
+
return interpolated.replace(/\$\{([A-Z_]+)\}/g, (match, envVar) => {
|
|
33
41
|
return envVars[envVar] || match;
|
|
34
42
|
});
|
|
35
43
|
}
|
|
@@ -126,10 +126,8 @@ function transformFlatStructure(variables, appName) {
|
|
|
126
126
|
* @returns {string} Image reference string
|
|
127
127
|
*/
|
|
128
128
|
function buildImageReference(variables, appName) {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
const tag = variables.image?.tag || 'latest';
|
|
132
|
-
return registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
129
|
+
const { buildManifestImageReference } = require('./resolve-docker-image-ref');
|
|
130
|
+
return buildManifestImageReference(variables, appName);
|
|
133
131
|
}
|
|
134
132
|
|
|
135
133
|
/**
|
|
@@ -301,6 +299,9 @@ function transformSimpleOptionalFields(variables, transformed) {
|
|
|
301
299
|
if (variables.externalIntegration) {
|
|
302
300
|
transformed.externalIntegration = variables.externalIntegration;
|
|
303
301
|
}
|
|
302
|
+
if (variables.required !== undefined) {
|
|
303
|
+
transformed.required = variables.required;
|
|
304
|
+
}
|
|
304
305
|
}
|
|
305
306
|
|
|
306
307
|
/**
|
|
@@ -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.58.0",
|
|
4
4
|
"description": "AI Fabrix Builder — CLI and developer scripts (pnpm + af)",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
"dev-deploy": "node scripts/pnpm/dev-deploy.mjs",
|
|
25
25
|
"af": "node scripts/pnpm/af.mjs",
|
|
26
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",
|
|
31
|
+
"dev-deploy:test": "node --test scripts/pnpm/dev-deploy.test.mjs",
|
|
27
32
|
"check": "npm run lint && npm run test",
|
|
28
33
|
"lint": "eslint . --ext .js",
|
|
29
34
|
"lint:fix": "eslint . --ext .js --fix",
|
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.
|
|
@@ -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
|
|
@@ -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:
|
|
@@ -213,12 +213,6 @@ MISO_ENVIRONMENT=miso
|
|
|
213
213
|
MISO_CLIENTID=kv://miso-controller-client-idKeyVault
|
|
214
214
|
MISO_CLIENTSECRET=kv://miso-controller-client-secretKeyVault
|
|
215
215
|
|
|
216
|
-
# Evaluation mode (optional .env override of DB controller.configuration.evaluation):
|
|
217
|
-
# When true (default if DB omits flag), infra deploy may coerce :envKey to `miso` — e2e poll on `dev` can 404.
|
|
218
|
-
# Set false locally to force path envKey to match deploy + GET .../deployments/:id.
|
|
219
|
-
# Unset = use DB only.
|
|
220
|
-
CONTROLLER_EVALUATION=
|
|
221
|
-
|
|
222
216
|
# =============================================================================
|
|
223
217
|
# ENTRA ID ONBOARDING (vendor multi-tenant app — admin consent) — [always]
|
|
224
218
|
# =============================================================================
|
|
@@ -361,6 +355,21 @@ API_KEY=kv://miso-controller-secrets-apiKeyVault
|
|
|
361
355
|
# LICENSE_VALIDATION_DISABLED=true switch; do not use it in deployed installations.
|
|
362
356
|
LICENSE_JWT=kv://miso-controller-jwt-licenseKeyVault
|
|
363
357
|
|
|
358
|
+
# Signed licenses require a Mori issuer key trusted independently of the JWT.
|
|
359
|
+
# Provision the SPKI PEM public key and exact issuer during installation, before
|
|
360
|
+
# pasting LICENSE_JWT or using Update license. An embedded JWT publicKey/jwk or Mori URL
|
|
361
|
+
# is not a trust source. For disconnected installs, deliver the key through the
|
|
362
|
+
# authenticated installation package or another operator-verified channel.
|
|
363
|
+
# Azure resolves this from Key Vault using the app's managed identity.
|
|
364
|
+
# Builder Docker resolves the same kv:// catalog key from the installer's local
|
|
365
|
+
# secrets store before starting the container. Provision a trusted Mori public key
|
|
366
|
+
# there first; the empty catalog default is only for unlicensed Community mode.
|
|
367
|
+
MORI_LICENSE_PUBLIC_KEY=kv://mori-license-public-keyKeyVault
|
|
368
|
+
# Plain docker run can instead leave MORI_LICENSE_PUBLIC_KEY unset and use a
|
|
369
|
+
# trusted, read-only PEM mount at this path.
|
|
370
|
+
# MORI_LICENSE_PUBLIC_KEY_FILE=/run/secrets/mori-license-public-key.pem
|
|
371
|
+
MORI_LICENSE_ISSUER=mori-controller
|
|
372
|
+
|
|
364
373
|
# =============================================================================
|
|
365
374
|
# ENTRA ID ONBOARDING (delegated Graph via Mori)
|
|
366
375
|
# =============================================================================
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#",
|
|
3
|
+
"handler": "Microsoft.Azure.CreateUIDef",
|
|
4
|
+
"version": "0.1.2-preview",
|
|
5
|
+
"parameters": {
|
|
6
|
+
"config": {
|
|
7
|
+
"basics": {
|
|
8
|
+
"description": "Choose subscription, resource group, and region above. Under **Application resources Resource group name**, enter your install id (for example `aifabrixpoc02`). That managed resource group name is the naming prefix: resources become `{rg}-miso-asp`, `{rg}-miso-miso`, and so on. Set the Azure **Application name** to the same id when the portal asks. Enter the initial platform administrator password and choose the license below. Container images are pulled with the web apps' managed identities; no registry credential is needed."
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"basics": [
|
|
12
|
+
{
|
|
13
|
+
"name": "adminPassword",
|
|
14
|
+
"type": "Microsoft.Common.PasswordBox",
|
|
15
|
+
"label": "Administrator password",
|
|
16
|
+
"toolTip": "Password for the initial AI Fabrix platform administrator (user admin). PostgreSQL, Keycloak, and encryption secrets are generated automatically and stored in Key Vault — do not reuse this password for infrastructure.",
|
|
17
|
+
"constraints": {
|
|
18
|
+
"required": true,
|
|
19
|
+
"regex": "^.{8,128}$",
|
|
20
|
+
"validationMessage": "Password must be 8–128 characters."
|
|
21
|
+
},
|
|
22
|
+
"options": {
|
|
23
|
+
"hideConfirmation": false
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "licenseChoice",
|
|
28
|
+
"type": "Microsoft.Common.DropDown",
|
|
29
|
+
"label": "License",
|
|
30
|
+
"toolTip": "License AI Fabrix requests during tenant activation. Community is permanent. Standard or Enterprise requested by name is a 30-day evaluation. For a purchased plan, choose 'Purchased Marketplace subscription' and enter its subscription ID; the edition is then resolved from the purchased plan.",
|
|
31
|
+
"defaultValue": "Community",
|
|
32
|
+
"constraints": {
|
|
33
|
+
"required": true,
|
|
34
|
+
"allowedValues": [
|
|
35
|
+
{
|
|
36
|
+
"label": "Community",
|
|
37
|
+
"value": "COMMUNITY"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"label": "Standard (30-day evaluation)",
|
|
41
|
+
"value": "STANDARD"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"label": "Enterprise (30-day evaluation)",
|
|
45
|
+
"value": "ENTERPRISE"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"label": "Purchased Marketplace subscription",
|
|
49
|
+
"value": "SUBSCRIPTION_ID"
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"name": "licenseSubscriptionKey",
|
|
56
|
+
"type": "Microsoft.Common.TextBox",
|
|
57
|
+
"label": "Marketplace subscription ID",
|
|
58
|
+
"toolTip": "The ID (GUID) of the purchased AI Fabrix Azure Marketplace subscription.",
|
|
59
|
+
"defaultValue": "",
|
|
60
|
+
"visible": "[equals(basics('licenseChoice'), 'SUBSCRIPTION_ID')]",
|
|
61
|
+
"constraints": {
|
|
62
|
+
"required": "[equals(basics('licenseChoice'), 'SUBSCRIPTION_ID')]",
|
|
63
|
+
"regex": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
|
|
64
|
+
"validationMessage": "Enter the Marketplace subscription ID as a GUID (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)."
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"steps": [],
|
|
69
|
+
"outputs": {
|
|
70
|
+
"location": "[location()]",
|
|
71
|
+
"deploymentEnvironment": "evaluation",
|
|
72
|
+
"adminPassword": "[basics('adminPassword')]",
|
|
73
|
+
"licenseEdition": "[if(equals(basics('licenseChoice'), 'SUBSCRIPTION_ID'), basics('licenseSubscriptionKey'), basics('licenseChoice'))]",
|
|
74
|
+
"acrResourceId": "__MARKETPLACE_ACR_RESOURCE_ID__"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
|
|
3
|
+
"contentVersion": "1.0.0.0",
|
|
4
|
+
"parameters": {
|
|
5
|
+
"adminPassword": { "value": "" },
|
|
6
|
+
"marketplaceSubscriptionId": { "value": "" },
|
|
7
|
+
"location": { "value": "swedencentral" }
|
|
8
|
+
}
|
|
9
|
+
}
|