@aifabrix/builder 2.52.0 → 2.52.1
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/jest.isolated-projects.js +25 -2
- package/jest.projects.js +4 -1
- package/lib/api/datasources-extended.api.js +8 -8
- package/lib/api/keycloak.api.js +92 -0
- package/lib/api/token-providers.api.js +77 -0
- package/lib/api/types/keycloak.types.js +23 -0
- package/lib/api/types/token-providers.types.js +65 -0
- package/lib/app/down.js +16 -1
- package/lib/channels/add-channel.js +126 -19
- package/lib/channels/approval-guides/chatgpt.js +1 -1
- package/lib/channels/approval-guides/microsoft-copilot.js +16 -12
- package/lib/channels/channel-add-ensure.js +128 -0
- package/lib/channels/channel-artifact-generator.js +11 -1
- package/lib/channels/channel-branding.js +94 -0
- package/lib/channels/channel-credential.js +8 -0
- package/lib/channels/channel-discovery.js +330 -0
- package/lib/channels/channel-external-system.js +1 -1
- package/lib/channels/channel-runtime-resolver.js +103 -14
- package/lib/channels/channel-status-display.js +7 -1
- package/lib/channels/microsoft-copilot-channel-add.js +138 -0
- package/lib/channels/microsoft-copilot-entra-sso-form.js +180 -0
- package/lib/channels/microsoft-copilot-entra-sso.js +264 -0
- package/lib/channels/microsoft-copilot-icons.js +74 -0
- package/lib/channels/microsoft-copilot-limits.js +84 -0
- package/lib/channels/microsoft-copilot-package.js +130 -49
- package/lib/channels/microsoft-copilot-validate.js +197 -0
- package/lib/cli/index.js +2 -0
- package/lib/cli/infra-guided.js +13 -6
- package/lib/cli/setup-infra.js +31 -4
- package/lib/commands/channel-add-setup.js +28 -9
- package/lib/commands/channel.js +14 -10
- package/lib/commands/integration-definition.js +158 -0
- package/lib/commands/repair-auth-apply.js +19 -5
- package/lib/commands/repair-datasource-sync-operations.js +4 -1
- package/lib/commands/role-assistant-test-output.js +200 -0
- package/lib/commands/role-assistant.js +125 -166
- package/lib/datasource/capability/capability-resolve.js +15 -5
- package/lib/datasource/capability/copy-operations.js +14 -4
- package/lib/infrastructure/index.js +19 -4
- package/lib/integration-definition/apply.js +478 -0
- package/lib/integration-definition/canonical-json.js +51 -0
- package/lib/integration-definition/emit-suite-bid.js +94 -0
- package/lib/integration-definition/enrich-crm-field-mappings.js +240 -0
- package/lib/integration-definition/enrich-openapi-operations.js +434 -0
- package/lib/integration-definition/from-discovery.js +339 -0
- package/lib/integration-definition/hash.js +37 -0
- package/lib/integration-definition/index.js +37 -0
- package/lib/integration-definition/openapi-enrichment/hubspot-crm-companies.json +205 -0
- package/lib/integration-definition/resource-type-catalog.js +59 -0
- package/lib/integration-definition/system-kind-enum.js +40 -0
- package/lib/integration-definition/validate.js +148 -0
- package/lib/programmatic/builder-api-request.js +5 -1
- package/lib/programmatic/builder-help-governance.js +3 -2
- package/lib/programmatic/capability-registry.js +19 -0
- package/lib/programmatic/openapi-component-schemas.js +6 -1
- package/lib/programmatic/openapi-descriptions.js +40 -0
- package/lib/programmatic/openapi-envelope-examples.js +269 -0
- package/lib/programmatic/openapi-operation-bindings.js +45 -0
- package/lib/programmatic/openapi-request-schemas.js +14 -0
- package/lib/programmatic/openapi-result-schemas-overflow.js +101 -0
- package/lib/programmatic/openapi-result-schemas.js +46 -99
- package/lib/programmatic/openapi-schema-descriptions-core.js +7 -4
- package/lib/programmatic/openapi-schema-descriptions-help.js +1 -1
- package/lib/programmatic/openapi-schema-descriptions-operations.js +24 -0
- package/lib/programmatic/openapi-schema-helpers.js +9 -9
- package/lib/programmatic/openapi-spec.js +11 -1
- package/lib/programmatic/orchestration-openapi-meta.js +10 -0
- package/lib/programmatic/route-handler-map.js +29 -1
- package/lib/programmatic/run-channel-package.js +236 -0
- package/lib/programmatic/run-integration-definition.js +69 -0
- package/lib/role-assistant/knowledge-sync-logging.js +58 -0
- package/lib/role-assistant/knowledge-sync.js +88 -93
- package/lib/role-assistant/test-cases-bid.js +155 -0
- package/lib/role-assistant/test-cases-evaluate.js +9 -1
- package/lib/role-assistant/test-cases-honesty.js +24 -10
- package/lib/role-assistant/test-cases.js +28 -0
- package/lib/role-assistant/test-runner-bid-apply.js +122 -0
- package/lib/role-assistant/test-runner-bid-document.js +278 -0
- package/lib/role-assistant/test-runner-workhub-answers.js +245 -20
- package/lib/role-assistant/test-runner-workhub.js +100 -18
- package/lib/role-assistant/test-suite.js +119 -0
- package/lib/schema/application-schema.json +56 -6
- package/lib/schema/external-datasource.schema.json +103 -17
- package/lib/schema/external-system.schema.json +54 -3
- package/lib/schema/integration-definition.schema.json +195 -0
- package/lib/utils/authentication-shape.js +6 -0
- package/lib/utils/docker-build.js +3 -3
- package/lib/utils/help-builder.js +1 -0
- package/lib/utils/promote-dev-docker-image.js +97 -18
- package/lib/utils/transient-developer-id.js +28 -0
- package/package.json +1 -1
- package/scripts/pnpm/af.mjs +24 -9
- package/scripts/pnpm/help.mjs +1 -1
- package/templates/applications/dataplane/application.yaml +3 -2
- package/templates/applications/dataplane/env.template +7 -6
- package/templates/applications/miso-controller/application.yaml +23 -9
- package/templates/applications/miso-controller/env.template +8 -2
- package/templates/channels/README.md +11 -1
- package/templates/channels/brand/color-192.png +0 -0
- package/templates/channels/brand/dark/128.png +0 -0
- package/templates/channels/brand/dark/192.png +0 -0
- package/templates/channels/brand/dark/256.png +0 -0
- package/templates/channels/brand/dark/32.png +0 -0
- package/templates/channels/brand/dark/512.png +0 -0
- package/templates/channels/brand/dark/64.png +0 -0
- package/templates/channels/brand/light/128.png +0 -0
- package/templates/channels/brand/light/192.png +0 -0
- package/templates/channels/brand/light/256.png +0 -0
- package/templates/channels/brand/light/32.png +0 -0
- package/templates/channels/brand/light/512.png +0 -0
- package/templates/channels/brand/light/64.png +0 -0
- package/templates/channels/brand/mono-white/128.png +0 -0
- package/templates/channels/brand/mono-white/192.png +0 -0
- package/templates/channels/brand/mono-white/256.png +0 -0
- package/templates/channels/brand/mono-white/32.png +0 -0
- package/templates/channels/brand/mono-white/512.png +0 -0
- package/templates/channels/brand/mono-white/64.png +0 -0
- package/templates/channels/brand/outline-32.png +0 -0
- package/templates/channels/chatgpt/install.template.md.hbs +6 -4
- package/templates/channels/chatgpt/integration/README.md +4 -5
- package/templates/channels/chatgpt/integration/chatgpt-system.json +1 -16
- package/templates/channels/microsoft-copilot/ai-plugin.template.json.hbs +10 -16
- package/templates/channels/microsoft-copilot/declarative-agent.template.json.hbs +5 -3
- package/templates/channels/microsoft-copilot/entra-sso-registration.template.md.hbs +27 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +39 -20
- package/templates/channels/microsoft-copilot/integration/README.md +2 -3
- package/templates/channels/microsoft-copilot/integration/microsoft-copilot-system.json +1 -22
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +7 -7
- package/templates/channels/microsoft-copilot/oauth-plugin-vault-required.template.md.hbs +11 -0
- package/templates/channels/slack/integration/README.md +2 -4
- package/templates/channels/teams-bot/install.template.md.hbs +4 -0
- package/templates/channels/teams-bot/integration/README.md +3 -5
- package/templates/channels/web-embed/integration/web-embed-system.json +1 -16
- /package/.cursor/plans/{Done → Archive}/157.1-security-quality-violations-by-type.csv +0 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate Microsoft Copilot package source files before ZIP.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Schema-oriented checks for Teams + declarative agent + AI plugin manifests
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
TEAMS_NAME_SHORT_MAX,
|
|
13
|
+
TEAMS_NAME_FULL_MAX,
|
|
14
|
+
TEAMS_DESCRIPTION_SHORT_MAX,
|
|
15
|
+
TEAMS_DESCRIPTION_FULL_MAX,
|
|
16
|
+
PLUGIN_NAME_FOR_HUMAN_MAX,
|
|
17
|
+
PLUGIN_DESCRIPTION_FOR_HUMAN_MAX
|
|
18
|
+
} = require('./microsoft-copilot-limits');
|
|
19
|
+
const { readPngDimensions } = require('./microsoft-copilot-icons');
|
|
20
|
+
|
|
21
|
+
const ALLOWED_AUTH_TYPES = new Set(['None', 'OAuthPluginVault', 'ApiKeyPluginVault']);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {unknown} value
|
|
25
|
+
* @param {string} label
|
|
26
|
+
* @param {number} max
|
|
27
|
+
* @param {string[]} errors
|
|
28
|
+
*/
|
|
29
|
+
function assertMaxLength(value, label, max, errors) {
|
|
30
|
+
if (typeof value !== 'string') {
|
|
31
|
+
errors.push(`${label} must be a string`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (value.length > max) {
|
|
35
|
+
errors.push(`${label} exceeds ${max} characters (got ${value.length})`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {Record<string, string>} sourceFiles
|
|
41
|
+
* @returns {{ manifest: Object|null, agent: Object|null, plugin: Object|null, errors: string[] }}
|
|
42
|
+
*/
|
|
43
|
+
function parsePackageJsonFiles(sourceFiles) {
|
|
44
|
+
const errors = [];
|
|
45
|
+
const parsed = { manifest: null, agent: null, plugin: null };
|
|
46
|
+
try {
|
|
47
|
+
parsed.manifest = JSON.parse(sourceFiles['manifest.json'] || '');
|
|
48
|
+
} catch {
|
|
49
|
+
errors.push('manifest.json is not valid JSON');
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
parsed.agent = JSON.parse(sourceFiles['declarativeAgent.json'] || '');
|
|
53
|
+
} catch {
|
|
54
|
+
errors.push('declarativeAgent.json is not valid JSON');
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
parsed.plugin = JSON.parse(sourceFiles['ai-plugin.json'] || '');
|
|
58
|
+
} catch {
|
|
59
|
+
errors.push('ai-plugin.json is not valid JSON');
|
|
60
|
+
}
|
|
61
|
+
return { ...parsed, errors };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {Object|null} manifest
|
|
66
|
+
* @param {string[]} errors
|
|
67
|
+
*/
|
|
68
|
+
function validateTeamsManifest(manifest, errors) {
|
|
69
|
+
if (!manifest) return;
|
|
70
|
+
assertMaxLength(manifest?.name?.short, 'manifest.name.short', TEAMS_NAME_SHORT_MAX, errors);
|
|
71
|
+
assertMaxLength(manifest?.name?.full, 'manifest.name.full', TEAMS_NAME_FULL_MAX, errors);
|
|
72
|
+
assertMaxLength(
|
|
73
|
+
manifest?.description?.short,
|
|
74
|
+
'manifest.description.short',
|
|
75
|
+
TEAMS_DESCRIPTION_SHORT_MAX,
|
|
76
|
+
errors
|
|
77
|
+
);
|
|
78
|
+
assertMaxLength(
|
|
79
|
+
manifest?.description?.full,
|
|
80
|
+
'manifest.description.full',
|
|
81
|
+
TEAMS_DESCRIPTION_FULL_MAX,
|
|
82
|
+
errors
|
|
83
|
+
);
|
|
84
|
+
if (!manifest.copilotAgents?.declarativeAgents?.length) {
|
|
85
|
+
errors.push('manifest.copilotAgents.declarativeAgents is required');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {Object|null} agent
|
|
91
|
+
* @param {string[]} errors
|
|
92
|
+
*/
|
|
93
|
+
function validateDeclarativeAgent(agent, errors) {
|
|
94
|
+
if (!agent) return;
|
|
95
|
+
if (agent.version !== 'v1.4') {
|
|
96
|
+
errors.push('declarativeAgent.version must be "v1.4"');
|
|
97
|
+
}
|
|
98
|
+
if (!agent.id || !agent.name || !agent.description || !agent.instructions) {
|
|
99
|
+
errors.push('declarativeAgent requires id, name, description, and instructions');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {Object|null} plugin
|
|
105
|
+
* @param {string[]} errors
|
|
106
|
+
*/
|
|
107
|
+
function validateAiPlugin(plugin, errors) {
|
|
108
|
+
if (!plugin) return;
|
|
109
|
+
if (plugin.schema_version !== 'v2.4') {
|
|
110
|
+
errors.push('ai-plugin.schema_version must be "v2.4"');
|
|
111
|
+
}
|
|
112
|
+
assertMaxLength(plugin.name_for_human, 'ai-plugin.name_for_human', PLUGIN_NAME_FOR_HUMAN_MAX, errors);
|
|
113
|
+
assertMaxLength(
|
|
114
|
+
plugin.description_for_human,
|
|
115
|
+
'ai-plugin.description_for_human',
|
|
116
|
+
PLUGIN_DESCRIPTION_FOR_HUMAN_MAX,
|
|
117
|
+
errors
|
|
118
|
+
);
|
|
119
|
+
if (!plugin.namespace || !/^[A-Za-z0-9-]+$/.test(plugin.namespace)) {
|
|
120
|
+
errors.push('ai-plugin.namespace is required (alphanumeric and hyphen only)');
|
|
121
|
+
}
|
|
122
|
+
validateAiPluginRuntime(plugin, errors);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {Object} plugin
|
|
127
|
+
* @param {string[]} errors
|
|
128
|
+
*/
|
|
129
|
+
function validateAiPluginRuntime(plugin, errors) {
|
|
130
|
+
const runtime = Array.isArray(plugin.runtimes) ? plugin.runtimes[0] : null;
|
|
131
|
+
if (!runtime || runtime.type !== 'RemoteMCPServer') {
|
|
132
|
+
errors.push('ai-plugin.runtimes[0].type must be RemoteMCPServer');
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
if (!ALLOWED_AUTH_TYPES.has(runtime.auth?.type)) {
|
|
136
|
+
errors.push(
|
|
137
|
+
`ai-plugin.runtimes[0].auth.type must be one of ${[...ALLOWED_AUTH_TYPES].join(', ')}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
if (runtime.auth?.type === 'OAuthPluginVault') {
|
|
141
|
+
const ref = runtime.auth.reference_id;
|
|
142
|
+
if (typeof ref !== 'string' || !ref.trim()) {
|
|
143
|
+
errors.push(
|
|
144
|
+
'OAuthPluginVault.reference_id is required — pass --oauth-plugin-vault-reference-id ' +
|
|
145
|
+
'with the Teams Developer Portal Entra SSO auth-config id'
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (runtime.auth?.type === 'None') {
|
|
150
|
+
errors.push(
|
|
151
|
+
'ai-plugin.runtimes[0].auth.type None is not allowed; use OAuthPluginVault (Microsoft token / Entra)'
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
const desc = runtime.spec?.mcp_tool_description;
|
|
155
|
+
if (desc && (desc.mode || desc.layerBToolNames)) {
|
|
156
|
+
errors.push(
|
|
157
|
+
'ai-plugin mcp_tool_description must not use mode/layerBToolNames; omit for dynamic discovery or use tools/file'
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* @param {{ color: Buffer, outline: Buffer }} icons
|
|
164
|
+
* @param {string[]} errors
|
|
165
|
+
*/
|
|
166
|
+
function validatePackageIcons(icons, errors) {
|
|
167
|
+
const colorDims = readPngDimensions(icons.color);
|
|
168
|
+
const outlineDims = readPngDimensions(icons.outline);
|
|
169
|
+
if (!colorDims || colorDims.width !== 192 || colorDims.height !== 192) {
|
|
170
|
+
errors.push('color.png must be a 192x192 PNG');
|
|
171
|
+
}
|
|
172
|
+
if (!outlineDims || outlineDims.width !== 32 || outlineDims.height !== 32) {
|
|
173
|
+
errors.push('outline.png must be a 32x32 PNG');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @param {Record<string, string>} sourceFiles
|
|
179
|
+
* @param {{ color: Buffer, outline: Buffer }} icons
|
|
180
|
+
* @throws {Error}
|
|
181
|
+
*/
|
|
182
|
+
function assertValidMicrosoftCopilotPackage(sourceFiles, icons) {
|
|
183
|
+
const parsed = parsePackageJsonFiles(sourceFiles);
|
|
184
|
+
const errors = [...parsed.errors];
|
|
185
|
+
validateTeamsManifest(parsed.manifest, errors);
|
|
186
|
+
validateDeclarativeAgent(parsed.agent, errors);
|
|
187
|
+
validateAiPlugin(parsed.plugin, errors);
|
|
188
|
+
validatePackageIcons(icons, errors);
|
|
189
|
+
if (errors.length) {
|
|
190
|
+
throw new Error(`Microsoft Copilot package validation failed:\n- ${errors.join('\n- ')}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = {
|
|
195
|
+
assertValidMicrosoftCopilotPackage,
|
|
196
|
+
ALLOWED_AUTH_TYPES
|
|
197
|
+
};
|
package/lib/cli/index.js
CHANGED
|
@@ -67,6 +67,8 @@ function setupCoreCliCommands(program) {
|
|
|
67
67
|
setupChannelCommands(program);
|
|
68
68
|
setupDatasourceCommands(program);
|
|
69
69
|
setupManifestCommands(program);
|
|
70
|
+
const { setupIntegrationDefinitionCommands } = require('../commands/integration-definition');
|
|
71
|
+
setupIntegrationDefinitionCommands(program);
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
/**
|
package/lib/cli/infra-guided.js
CHANGED
|
@@ -331,23 +331,30 @@ async function runGuidedDownInfra(appName, options, infraMod, appLib) {
|
|
|
331
331
|
logger.log(SEPARATOR);
|
|
332
332
|
logger.log('');
|
|
333
333
|
|
|
334
|
+
const targetDeveloperId = options && options.developerId;
|
|
334
335
|
const spin = startSpinner('Stopping infrastructure...');
|
|
335
336
|
await withMutedLogger(async() => {
|
|
336
337
|
if (typeof appName === 'string' && appName.trim().length > 0) {
|
|
337
|
-
await appLib.downApp(appName, {
|
|
338
|
+
await appLib.downApp(appName, {
|
|
339
|
+
volumes: !!options.volumes,
|
|
340
|
+
developerId: targetDeveloperId
|
|
341
|
+
});
|
|
338
342
|
return;
|
|
339
343
|
}
|
|
340
|
-
if (options.volumes) await infraMod.stopInfraWithVolumes();
|
|
341
|
-
else await infraMod.stopInfra();
|
|
344
|
+
if (options.volumes) await infraMod.stopInfraWithVolumes(targetDeveloperId);
|
|
345
|
+
else await infraMod.stopInfra(targetDeveloperId);
|
|
342
346
|
});
|
|
343
347
|
stopSpinnerSuccess(spin, 'Infrastructure stopped');
|
|
344
348
|
|
|
345
|
-
const developerId =
|
|
346
|
-
|
|
349
|
+
const developerId = String(
|
|
350
|
+
targetDeveloperId !== undefined && targetDeveloperId !== null && targetDeveloperId !== ''
|
|
351
|
+
? targetDeveloperId
|
|
352
|
+
: await config.getDeveloperId()
|
|
353
|
+
);
|
|
347
354
|
const cfg = await config.getConfig();
|
|
348
355
|
|
|
349
356
|
logFooterStart('Stopped');
|
|
350
|
-
logSection('Developer', `dev${
|
|
357
|
+
logSection('Developer', `dev${developerId.padStart(2, '0')} (id: ${developerId})`);
|
|
351
358
|
logStoppedServices(cfg);
|
|
352
359
|
if (options.volumes) {
|
|
353
360
|
logger.log(chalk.yellow('⚠ Volumes removed: all local data deleted'));
|
package/lib/cli/setup-infra.js
CHANGED
|
@@ -31,6 +31,9 @@ const {
|
|
|
31
31
|
getRestartableInfraServiceNames,
|
|
32
32
|
buildRestartInfraHelpLines
|
|
33
33
|
} = require('../constants/infra-compose-service-names');
|
|
34
|
+
const {
|
|
35
|
+
resolveTransientDeveloperIdFromOptions
|
|
36
|
+
} = require('../utils/transient-developer-id');
|
|
34
37
|
|
|
35
38
|
const {
|
|
36
39
|
computeEffectiveInfraOptionalFlags,
|
|
@@ -97,6 +100,18 @@ async function applyDeveloperIdFromUpInfra(options) {
|
|
|
97
100
|
return id;
|
|
98
101
|
}
|
|
99
102
|
|
|
103
|
+
const DOWN_INFRA_HELP_AFTER = `
|
|
104
|
+
Examples:
|
|
105
|
+
$ aifabrix down-infra
|
|
106
|
+
$ aifabrix down-infra --volumes
|
|
107
|
+
$ aifabrix down-infra --developer-id 02
|
|
108
|
+
$ aifabrix down-infra -d 02 --volumes
|
|
109
|
+
$ aifabrix down-infra myapp --developer-id 02
|
|
110
|
+
|
|
111
|
+
--developer-id / -d target another developer's stack for this command only.
|
|
112
|
+
They do not change developer-id in config.
|
|
113
|
+
`;
|
|
114
|
+
|
|
100
115
|
/**
|
|
101
116
|
* @param {Object} options
|
|
102
117
|
* @param {Object} cfg - Mutable config from getConfig()
|
|
@@ -302,20 +317,32 @@ function setupUpBuilderApiCommand(program) {
|
|
|
302
317
|
function setupDownInfraCommand(program) {
|
|
303
318
|
program.command('down-infra [service|app]')
|
|
304
319
|
.description('Stop all infra, or stop one app; use -v to remove volumes')
|
|
320
|
+
.addHelpText('after', DOWN_INFRA_HELP_AFTER)
|
|
305
321
|
.option('-v, --volumes', 'Remove volumes (deletes all data)')
|
|
306
322
|
.option('--verbose', 'Show full orchestration output (default is guided summary)')
|
|
323
|
+
.option(
|
|
324
|
+
'-d, --developer <id>',
|
|
325
|
+
'Target developer ID for this stop only (does not change config)'
|
|
326
|
+
)
|
|
327
|
+
.option(
|
|
328
|
+
'--developer-id <id>',
|
|
329
|
+
'Same as --developer; target another developer without changing config'
|
|
330
|
+
)
|
|
307
331
|
.action(async(appName, options) => {
|
|
308
332
|
try {
|
|
333
|
+
const developerId = resolveTransientDeveloperIdFromOptions(options);
|
|
334
|
+
const opts = { ...options, developerId };
|
|
309
335
|
if (!options.verbose) {
|
|
310
|
-
await runGuidedDownInfra(appName,
|
|
336
|
+
await runGuidedDownInfra(appName, opts, infra, appLib);
|
|
311
337
|
return;
|
|
312
338
|
}
|
|
313
339
|
|
|
314
340
|
if (typeof appName === 'string' && appName.trim().length > 0) {
|
|
315
|
-
await appLib.downApp(appName, { volumes: !!options.volumes });
|
|
341
|
+
await appLib.downApp(appName, { volumes: !!options.volumes, developerId });
|
|
342
|
+
} else if (options.volumes) {
|
|
343
|
+
await infra.stopInfraWithVolumes(developerId);
|
|
316
344
|
} else {
|
|
317
|
-
|
|
318
|
-
else await infra.stopInfra();
|
|
345
|
+
await infra.stopInfra(developerId);
|
|
319
346
|
}
|
|
320
347
|
} catch (error) {
|
|
321
348
|
handleCommandError(error, 'down-infra');
|
|
@@ -18,26 +18,36 @@ const { addChannelCommand } = require('../channels/add-channel');
|
|
|
18
18
|
const CHANNEL_ADD_HELP_AFTER = `
|
|
19
19
|
${formatSupportedChannelTargetsHelp()}
|
|
20
20
|
|
|
21
|
+
One command covers scaffold → upload → install:
|
|
22
|
+
$ aifabrix channel add slack --name "Elsa" --probe
|
|
23
|
+
|
|
21
24
|
Examples:
|
|
22
|
-
# After upload/deploy of the communication external system
|
|
23
25
|
$ aifabrix channel add slack --name "Elsa"
|
|
24
26
|
$ aifabrix channel add teams-bot --name "Elsa" --description "Enterprise AI assistant for employees"
|
|
25
27
|
$ aifabrix channel add chatgpt --name "Your Assistant"
|
|
26
|
-
$ aifabrix channel add microsoft-copilot --name "Your Assistant"
|
|
28
|
+
$ aifabrix channel add microsoft-copilot --name "Your Assistant" \\
|
|
29
|
+
--oauth-plugin-vault-reference-id "<Teams-Entra-SSO-auth-config-id>"
|
|
27
30
|
|
|
28
31
|
# Slack slash-command prefix (registers /{prefix}-help in manifest)
|
|
29
32
|
$ aifabrix channel add slack --name "Elsa" --command-prefix af
|
|
30
33
|
|
|
31
|
-
# Generate artifacts only; no dataplane patch
|
|
34
|
+
# Generate artifacts only; no dataplane patch / upload
|
|
32
35
|
$ aifabrix channel add slack --name "Elsa" --dry-run
|
|
33
36
|
|
|
34
|
-
#
|
|
37
|
+
# Re-upload communication system + regenerate channel config (does not rotate secrets)
|
|
35
38
|
$ aifabrix channel add slack --name "Elsa" --force
|
|
36
39
|
|
|
37
40
|
# Validate callbacks and assistant catalog after install
|
|
38
41
|
$ aifabrix channel add slack --name "Elsa" --probe
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
Behavior:
|
|
44
|
+
- Scaffolds integration/<target>/ when missing (does not overwrite existing files)
|
|
45
|
+
- Uploads when the system is not on the dataplane; with --force uploads every time
|
|
46
|
+
- Then configures the channel install (artifacts, credentials, admin guide)
|
|
47
|
+
- microsoft-copilot uses Entra SSO (trustedIssuer); pass --oauth-plugin-vault-reference-id
|
|
48
|
+
from Teams Developer Portal. Does not create Fabrix oauth2 channel credentials.
|
|
49
|
+
|
|
50
|
+
Requires: aifabrix login
|
|
41
51
|
`;
|
|
42
52
|
|
|
43
53
|
/**
|
|
@@ -64,6 +74,7 @@ async function runChannelAddAction(channelTarget, options, meta = {}) {
|
|
|
64
74
|
icon: options.icon,
|
|
65
75
|
template: options.template,
|
|
66
76
|
commandPrefix: options.commandPrefix,
|
|
77
|
+
oauthPluginVaultReferenceId: options.oauthPluginVaultReferenceId,
|
|
67
78
|
dryRun: options.dryRun === true,
|
|
68
79
|
force: options.force === true,
|
|
69
80
|
probe: options.probe === true
|
|
@@ -77,7 +88,7 @@ async function runChannelAddAction(channelTarget, options, meta = {}) {
|
|
|
77
88
|
*/
|
|
78
89
|
function registerChannelAddCommand(parent, meta = {}) {
|
|
79
90
|
const description =
|
|
80
|
-
'
|
|
91
|
+
'Scaffold (if needed), upload (if needed/--force), then configure channel install';
|
|
81
92
|
const commandName = meta.commandName || 'add <channelTarget>';
|
|
82
93
|
const cmd = meta.hidden
|
|
83
94
|
? parent.command(commandName, { hidden: true })
|
|
@@ -88,12 +99,19 @@ function registerChannelAddCommand(parent, meta = {}) {
|
|
|
88
99
|
.option('-d, --description <text>', 'Channel description (default generated)')
|
|
89
100
|
.option('--icon <path>', 'Path to channel icon asset')
|
|
90
101
|
.option('--template <name>', 'Template override (default from channel target)')
|
|
91
|
-
.option('--dry-run', 'Generate artifacts only; do not patch
|
|
92
|
-
.option(
|
|
102
|
+
.option('--dry-run', 'Generate artifacts only; do not upload, patch, or set credentials')
|
|
103
|
+
.option(
|
|
104
|
+
'--force',
|
|
105
|
+
'Re-upload communication system and regenerate channel config (does not rotate secrets)'
|
|
106
|
+
)
|
|
93
107
|
.option(
|
|
94
108
|
'--command-prefix <key>',
|
|
95
109
|
'Slack slash alias prefix (default af) — registers /{prefix}-help etc. in manifest'
|
|
96
110
|
)
|
|
111
|
+
.option(
|
|
112
|
+
'--oauth-plugin-vault-reference-id <id>',
|
|
113
|
+
'Teams Developer Portal Entra SSO OAuth auth-config id (microsoft-copilot)'
|
|
114
|
+
)
|
|
97
115
|
.option('--probe', 'After install, validate callbacks and assistant catalog')
|
|
98
116
|
.addHelpText('after', CHANNEL_ADD_HELP_AFTER)
|
|
99
117
|
.action(async(channelTarget, options) => {
|
|
@@ -101,7 +119,8 @@ function registerChannelAddCommand(parent, meta = {}) {
|
|
|
101
119
|
await runChannelAddAction(channelTarget, options, meta);
|
|
102
120
|
} catch (error) {
|
|
103
121
|
if (!error.message?.includes('Authentication required')) {
|
|
104
|
-
|
|
122
|
+
const detail = error.message || String(error);
|
|
123
|
+
logger.error(formatBlockingError(`Channel install failed:\n${detail}`));
|
|
105
124
|
}
|
|
106
125
|
process.exit(1);
|
|
107
126
|
}
|
package/lib/commands/channel.js
CHANGED
|
@@ -25,15 +25,20 @@ const { registerChannelAddCommand } = require('./channel-add-setup');
|
|
|
25
25
|
|
|
26
26
|
const CHANNEL_HELP_AFTER = `
|
|
27
27
|
Subcommands:
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
add <target> One command: scaffold (if needed) → upload (if needed/--force) → install
|
|
29
|
+
scaffold <target> Optional: create integration/<target>/ files only (local)
|
|
30
30
|
|
|
31
31
|
${formatSupportedChannelTargetsHelp()}
|
|
32
32
|
|
|
33
|
-
Typical flow
|
|
33
|
+
Typical flow:
|
|
34
|
+
aifabrix channel add slack --name "Elsa" --probe
|
|
35
|
+
|
|
36
|
+
# Force re-upload of the communication system, then reinstall channel
|
|
37
|
+
aifabrix channel add slack --name "Elsa" --force --probe
|
|
38
|
+
|
|
39
|
+
Optional low-level steps (usually unnecessary):
|
|
34
40
|
aifabrix channel scaffold slack
|
|
35
41
|
aifabrix upload slack --force
|
|
36
|
-
aifabrix channel add slack --name "Elsa" --probe
|
|
37
42
|
|
|
38
43
|
Controller pipeline apps only: aifabrix app register · aifabrix app list
|
|
39
44
|
|
|
@@ -43,10 +48,10 @@ Requires: aifabrix login (for add)
|
|
|
43
48
|
const CHANNEL_SCAFFOLD_HELP_AFTER = `
|
|
44
49
|
${formatSupportedChannelTargetsHelp()}
|
|
45
50
|
|
|
46
|
-
|
|
47
|
-
aifabrix channel scaffold slack
|
|
48
|
-
aifabrix upload slack --force
|
|
51
|
+
Prefer one command (scaffold + upload + install):
|
|
49
52
|
aifabrix channel add slack --name "Your Assistant" --probe
|
|
53
|
+
|
|
54
|
+
scaffold alone only writes local files under integration/<target>/.
|
|
50
55
|
`;
|
|
51
56
|
|
|
52
57
|
/**
|
|
@@ -56,7 +61,7 @@ function setupChannelCommands(program) {
|
|
|
56
61
|
const channel = program
|
|
57
62
|
.command('channel')
|
|
58
63
|
.description(
|
|
59
|
-
'Assistant channels (
|
|
64
|
+
'Assistant channels — one-command add (scaffold → upload → install), plus optional scaffold'
|
|
60
65
|
)
|
|
61
66
|
.addHelpText('after', CHANNEL_HELP_AFTER);
|
|
62
67
|
|
|
@@ -86,8 +91,7 @@ function setupChannelCommands(program) {
|
|
|
86
91
|
logger.log(metadata(file));
|
|
87
92
|
}
|
|
88
93
|
logger.log('');
|
|
89
|
-
logger.log(chalk.white('Next
|
|
90
|
-
logger.log(chalk.gray(` aifabrix upload ${channelTarget} --force`));
|
|
94
|
+
logger.log(chalk.white('Next step (recommended):'));
|
|
91
95
|
logger.log(
|
|
92
96
|
chalk.gray(` aifabrix channel add ${channelTarget} --name "Your Assistant" --probe`)
|
|
93
97
|
);
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI: integration-definition validate | apply
|
|
3
|
+
* @fileoverview BT → Builder BID commands (no section PATCH orchestration)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
/* eslint-disable no-console */
|
|
9
|
+
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const {
|
|
14
|
+
applyIntegrationDefinition,
|
|
15
|
+
validateIntegrationDefinition,
|
|
16
|
+
withDefinitionHash
|
|
17
|
+
} = require('../integration-definition');
|
|
18
|
+
const { handleCommandError } = require('../utils/cli-utils');
|
|
19
|
+
const {
|
|
20
|
+
formatSuccessLine,
|
|
21
|
+
formatBlockingError,
|
|
22
|
+
failureGlyph
|
|
23
|
+
} = require('../utils/cli-layout-chalk');
|
|
24
|
+
|
|
25
|
+
const PARENT_HELP_AFTER = `
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
$ aifabrix integration-definition validate ./bid.json
|
|
29
|
+
$ aifabrix integration-definition apply ./bid.json --dry-run
|
|
30
|
+
|
|
31
|
+
BID is the minimal business create language (systemKind, resourceType, propertyKeys).
|
|
32
|
+
Apply materializes integration/<systemKey>/ package files (EDS/system).
|
|
33
|
+
AI catalog: GET /help?topic=resourceTypeCatalog on builder-api.
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
const VALIDATE_HELP_AFTER = `
|
|
37
|
+
|
|
38
|
+
Examples:
|
|
39
|
+
$ aifabrix integration-definition validate ./bid.json
|
|
40
|
+
$ aifabrix integration-definition validate ./bid.json --fix-hash
|
|
41
|
+
|
|
42
|
+
Checks JSON Schema, definitionHash, product rules, and active resourceType catalog keys.
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
const APPLY_HELP_AFTER = `
|
|
46
|
+
|
|
47
|
+
Examples:
|
|
48
|
+
$ aifabrix integration-definition apply ./bid.json
|
|
49
|
+
$ aifabrix integration-definition apply ./bid.json --dry-run
|
|
50
|
+
$ aifabrix integration-definition apply ./bid.json --force
|
|
51
|
+
$ aifabrix integration-definition apply ./bid.json --output-dir /tmp/out
|
|
52
|
+
`;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {string} filePath
|
|
56
|
+
* @returns {Record<string, unknown>}
|
|
57
|
+
*/
|
|
58
|
+
function loadBidFile(filePath) {
|
|
59
|
+
const abs = path.resolve(filePath);
|
|
60
|
+
if (!fs.existsSync(abs)) {
|
|
61
|
+
throw new Error(`BID file not found: ${abs}`);
|
|
62
|
+
}
|
|
63
|
+
return JSON.parse(fs.readFileSync(abs, 'utf8'));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {string} file
|
|
68
|
+
* @param {{ fixHash?: boolean }} options
|
|
69
|
+
* @returns {Promise<void>}
|
|
70
|
+
*/
|
|
71
|
+
async function runValidateCommand(file, options) {
|
|
72
|
+
let bid = loadBidFile(file);
|
|
73
|
+
if (options.fixHash) {
|
|
74
|
+
bid = withDefinitionHash(bid);
|
|
75
|
+
fs.writeFileSync(path.resolve(file), `${JSON.stringify(bid, null, 2)}\n`, 'utf8');
|
|
76
|
+
console.log(formatSuccessLine(`Wrote definitionHash to ${file}`));
|
|
77
|
+
}
|
|
78
|
+
const result = validateIntegrationDefinition(bid);
|
|
79
|
+
if (!result.valid) {
|
|
80
|
+
console.error(formatBlockingError('BID invalid'));
|
|
81
|
+
for (const err of result.errors) {
|
|
82
|
+
console.error(chalk.red(` ${failureGlyph()} ${err}`));
|
|
83
|
+
}
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
console.log(formatSuccessLine('BID valid'));
|
|
87
|
+
console.log(` definitionId: ${result.bid.definitionId}`);
|
|
88
|
+
console.log(` definitionHash: ${result.bid.definitionHash}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* @param {string} file
|
|
93
|
+
* @param {{ outputDir?: string, dryRun?: boolean, force?: boolean }} options
|
|
94
|
+
* @returns {Promise<void>}
|
|
95
|
+
*/
|
|
96
|
+
async function runApplyCommand(file, options) {
|
|
97
|
+
const bid = loadBidFile(file);
|
|
98
|
+
const out = await applyIntegrationDefinition(bid, {
|
|
99
|
+
outputDir: options.outputDir,
|
|
100
|
+
dryRun: Boolean(options.dryRun),
|
|
101
|
+
force: Boolean(options.force)
|
|
102
|
+
});
|
|
103
|
+
console.log(formatSuccessLine(`BID apply ${out.status}`));
|
|
104
|
+
console.log(` systemKey: ${out.systemKey}`);
|
|
105
|
+
console.log(` outputDir: ${out.outputDir}`);
|
|
106
|
+
console.log(` definitionHash: ${out.definitionHash}`);
|
|
107
|
+
if (out.files) {
|
|
108
|
+
console.log(` files: ${out.files.join(', ')}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* @param {import('commander').Command} program
|
|
114
|
+
*/
|
|
115
|
+
function setupIntegrationDefinitionCommands(program) {
|
|
116
|
+
const cmd = program
|
|
117
|
+
.command('integration-definition')
|
|
118
|
+
.description(
|
|
119
|
+
'Validate or apply a Builder Integration Definition (BID) — minimal business create language for integration packages'
|
|
120
|
+
)
|
|
121
|
+
.addHelpText('after', PARENT_HELP_AFTER);
|
|
122
|
+
|
|
123
|
+
cmd
|
|
124
|
+
.command('validate <file>')
|
|
125
|
+
.description(
|
|
126
|
+
'Validate BID JSON (schema, definitionHash, product rules, resourceType catalog)'
|
|
127
|
+
)
|
|
128
|
+
.option('--fix-hash', 'Rewrite file with computed definitionHash')
|
|
129
|
+
.addHelpText('after', VALIDATE_HELP_AFTER)
|
|
130
|
+
.action(async(file, options) => {
|
|
131
|
+
try {
|
|
132
|
+
await runValidateCommand(file, options);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
handleCommandError(error, 'integration-definition validate');
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
cmd
|
|
139
|
+
.command('apply <file>')
|
|
140
|
+
.description(
|
|
141
|
+
'Idempotently apply BID to integration/<systemKey>/ (Builder-owned EDS/system writes)'
|
|
142
|
+
)
|
|
143
|
+
.option('--output-dir <dir>', 'Override output directory')
|
|
144
|
+
.option('--force', 'Rewrite package even when definitionHash matches stamp')
|
|
145
|
+
.option('--dry-run', 'Show planned files without writing')
|
|
146
|
+
.addHelpText('after', APPLY_HELP_AFTER)
|
|
147
|
+
.action(async(file, options) => {
|
|
148
|
+
try {
|
|
149
|
+
await runApplyCommand(file, options);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
handleCommandError(error, 'integration-definition apply');
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
module.exports = {
|
|
157
|
+
setupIntegrationDefinitionCommands
|
|
158
|
+
};
|
|
@@ -28,6 +28,7 @@ const ALLOWED_REPAIR_AUTH = [
|
|
|
28
28
|
'oidc',
|
|
29
29
|
'hmac',
|
|
30
30
|
'none',
|
|
31
|
+
'system',
|
|
31
32
|
'bearerforward'
|
|
32
33
|
];
|
|
33
34
|
|
|
@@ -57,8 +58,12 @@ function isAllowedRepairAuth(authOption) {
|
|
|
57
58
|
*/
|
|
58
59
|
function buildAuthenticationForRepair(systemKey, authOption) {
|
|
59
60
|
const normalized = normalizeRepairAuthOption(authOption);
|
|
61
|
+
if (normalized === 'system') {
|
|
62
|
+
return buildSystemAuthentication();
|
|
63
|
+
}
|
|
60
64
|
if (normalized === 'bearerforward') {
|
|
61
|
-
|
|
65
|
+
// Legacy alias: prefer authType system for platform facades.
|
|
66
|
+
return buildSystemAuthentication();
|
|
62
67
|
}
|
|
63
68
|
const schemaMethod =
|
|
64
69
|
normalized === 'queryparam'
|
|
@@ -70,11 +75,19 @@ function buildAuthenticationForRepair(systemKey, authOption) {
|
|
|
70
75
|
}
|
|
71
76
|
|
|
72
77
|
/**
|
|
73
|
-
*
|
|
74
|
-
* @returns {{ authType: '
|
|
78
|
+
* Platform-managed downstream authentication (no workspace kv secret).
|
|
79
|
+
* @returns {{ authType: 'system' }}
|
|
80
|
+
*/
|
|
81
|
+
function buildSystemAuthentication() {
|
|
82
|
+
return { authType: 'system' };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @deprecated Use buildSystemAuthentication — bearerForward is not a public authType.
|
|
87
|
+
* @returns {{ authType: 'system' }}
|
|
75
88
|
*/
|
|
76
89
|
function buildBearerForwardAuthentication() {
|
|
77
|
-
return
|
|
90
|
+
return buildSystemAuthentication();
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
/**
|
|
@@ -99,7 +112,7 @@ function mergePreservedAuthVariables(mergedVariables, existingVars) {
|
|
|
99
112
|
function repairAuthChangeLabel(authOption) {
|
|
100
113
|
const normalized = normalizeRepairAuthOption(authOption);
|
|
101
114
|
if (normalized === 'bearertoken' || normalized === 'bearerkey') return 'bearerToken';
|
|
102
|
-
if (normalized === 'bearerforward') return '
|
|
115
|
+
if (normalized === 'bearerforward') return 'system';
|
|
103
116
|
return normalized;
|
|
104
117
|
}
|
|
105
118
|
|
|
@@ -157,6 +170,7 @@ module.exports = {
|
|
|
157
170
|
normalizeRepairAuthOption,
|
|
158
171
|
isAllowedRepairAuth,
|
|
159
172
|
buildAuthenticationForRepair,
|
|
173
|
+
buildSystemAuthentication,
|
|
160
174
|
buildBearerForwardAuthentication,
|
|
161
175
|
mergePreservedAuthVariables,
|
|
162
176
|
repairAuthChangeLabel,
|