@aifabrix/builder 2.46.0 → 2.47.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/.npmrc.token +1 -0
- package/.temp/jest-protection-convert/run-IFN2nv/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-IvmKLO/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-convert/run-SdvblF/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-b51TsW/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-fiLmb1/.protection/hubspot-companies.json +1 -0
- package/.temp/jest-protection-convert/run-qYGHMU/.protection/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-Mq5re8/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-f3u4YL/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-iQtd1O/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-mdmz5M/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-qbKYxQ/hubspot-companies.yaml +17 -0
- package/.temp/jest-protection-fixtures/hubspot-yzlJy6/hubspot-companies.yaml +17 -0
- package/.temp/validation/summary.txt +5 -0
- package/jest.isolated-projects.js +18 -2
- package/jest.projects.js +24 -0
- package/lib/api/dataplane-health.api.js +19 -7
- package/lib/api/lifecycle.api.js +22 -1
- package/lib/api/resource-types.api.js +107 -0
- package/lib/api/runtime-configuration.api.js +72 -0
- package/lib/api/types/resource-types.types.js +45 -0
- package/lib/api/user-groups.api.js +9 -9
- package/lib/api/users.api.js +8 -8
- package/lib/api/wizard.api.js +10 -2
- package/lib/app/down.js +58 -26
- package/lib/app/index.js +2 -2
- package/lib/app/run-resolve-image.js +2 -1
- package/lib/app/run-stop-containers.js +113 -0
- package/lib/app/run.js +1 -31
- package/lib/app/show-display.js +35 -0
- package/lib/app/show.js +32 -1
- package/lib/build/index.js +17 -5
- package/lib/build/miso-controller-npm-sdk-guard.js +25 -0
- package/lib/channels/add-channel.js +348 -0
- package/lib/channels/channel-artifact-generator.js +141 -0
- package/lib/channels/channel-audit.js +101 -0
- package/lib/channels/channel-auth-context.js +37 -0
- package/lib/channels/channel-credential.js +200 -0
- package/lib/channels/channel-display-name.js +64 -0
- package/lib/channels/channel-external-system.js +106 -0
- package/lib/channels/channel-integration-generator.js +274 -0
- package/lib/channels/channel-probe.js +89 -0
- package/lib/channels/channel-runtime-resolver.js +152 -0
- package/lib/channels/channel-status-display.js +141 -0
- package/lib/channels/channel-targets.js +115 -0
- package/lib/cli/index.js +16 -4
- package/lib/cli/setup-app.js +54 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +2 -2
- package/lib/cli/setup-infra-up-platform-action.js +2 -1
- package/lib/cli/setup-platform.js +1 -1
- package/lib/cli/setup-utility-help-after.js +4 -1
- package/lib/cli/setup-utility-resolve.js +8 -5
- package/lib/commands/app-logs.js +54 -26
- package/lib/commands/app.js +41 -0
- package/lib/commands/auth-status-dataplane-version.js +97 -61
- package/lib/commands/auth-status.js +2 -7
- package/lib/commands/channel.js +58 -0
- package/lib/commands/datasource.js +1 -1
- package/lib/commands/identity-apply-sync.js +49 -8
- package/lib/commands/identity-group-cli.js +66 -26
- package/lib/commands/identity-membership-role-cache.js +58 -37
- package/lib/commands/identity-shared.js +22 -1
- package/lib/commands/identity-user-cli.js +80 -34
- package/lib/commands/repair-auth-apply.js +30 -4
- package/lib/commands/repair-auth-config.js +8 -13
- package/lib/commands/repair-datasource-com-v2.js +15 -0
- package/lib/commands/repair-datasource-service-contract.js +206 -0
- package/lib/commands/repair-datasource.js +53 -18
- package/lib/commands/repair-env-template.js +12 -12
- package/lib/commands/repair-openapi-sync.js +26 -4
- package/lib/commands/repair-steps.js +28 -13
- package/lib/commands/repair-system-alignment.js +7 -4
- package/lib/commands/resource-type.js +371 -0
- package/lib/commands/setup-modes.js +1 -1
- package/lib/commands/setup-prompts-platform-mode.js +24 -21
- package/lib/commands/test-trust-external.js +39 -90
- package/lib/commands/up-common.js +33 -1
- package/lib/commands/up-dataplane.js +8 -4
- package/lib/commands/upload.js +24 -3
- package/lib/commands/verify-trust-command-action.js +4 -1
- package/lib/commands/verify-trust-external.js +12 -20
- package/lib/core/config-setup-platform-mode.js +3 -3
- package/lib/core/setup-platform-mode.js +41 -6
- package/lib/datasource/capability/capability-diff-slice.js +2 -2
- package/lib/datasource/capability/capability-manifest.js +254 -0
- package/lib/datasource/capability/capability-resolve.js +3 -4
- package/lib/datasource/capability/copy-operations.js +5 -3
- package/lib/datasource/capability/remove-operations.js +2 -1
- package/lib/datasource/capability/run-capability-copy.js +32 -4
- package/lib/datasource/capability/run-capability-diff.js +3 -3
- package/lib/datasource/capability/run-capability-dimension.js +2 -4
- package/lib/datasource/capability/run-capability-edit.js +4 -7
- package/lib/datasource/capability/run-capability-relate.js +2 -4
- package/lib/datasource/capability/run-capability-remove.js +2 -4
- package/lib/datasource/capability/validate-capability-slice.js +2 -1
- package/lib/datasource/datasource-identifier.js +2 -1
- package/lib/datasource/datasource-validate-display.js +1 -1
- package/lib/datasource/field-reference-validator.js +24 -3
- package/lib/datasource/orphan-profiles-validator.js +98 -0
- package/lib/datasource/validate.js +41 -23
- package/lib/external-system/download-helpers.js +87 -0
- package/lib/external-system/download.js +61 -122
- package/lib/external-system/generator.js +1 -59
- package/lib/external-system/test.js +10 -15
- package/lib/generator/builders.js +11 -1
- package/lib/generator/external-controller-manifest.js +5 -2
- package/lib/generator/split.js +3 -1
- package/lib/generator/wizard.js +1 -28
- package/lib/identity/fk-reference.js +65 -0
- package/lib/identity/identity-apply-core.js +13 -58
- package/lib/identity/identity-apply-phases.js +5 -1
- package/lib/identity/identity-display.js +243 -0
- package/lib/identity/identity-response-helpers.js +52 -0
- package/lib/identity/identity-sync-errors.js +62 -0
- package/lib/identity/resolve-user-id.js +129 -0
- package/lib/identity/username-default.js +31 -0
- package/lib/lifecycle/certification-progress.js +2 -2
- package/lib/lifecycle/report-display-trust.js +70 -0
- package/lib/lifecycle/report-display.js +14 -13
- package/lib/resolvers/datasource-resolver.js +2 -3
- package/lib/resolvers/resource-type-file.js +159 -0
- package/lib/schema/application-schema.json +16 -0
- package/lib/schema/deployment-rules.yaml +2 -1
- package/lib/schema/external-datasource.schema.json +372 -58
- package/lib/schema/external-system.schema.json +331 -2
- package/lib/schema/type/document-storage.json +9 -5
- package/lib/utils/app-run-containers.js +25 -2
- package/lib/utils/authentication-shape.js +224 -0
- package/lib/utils/build-copy.js +36 -5
- package/lib/utils/build-resolve-image.js +66 -12
- package/lib/utils/celery-companion-apps.js +53 -0
- package/lib/utils/cli-utils.js +4 -0
- package/lib/utils/compose-celery-worker.js +23 -0
- package/lib/utils/compose-generate-docker-compose.js +9 -1
- package/lib/utils/configuration-env-resolver.js +36 -0
- package/lib/utils/credential-secrets-env-kv.js +216 -0
- package/lib/utils/credential-secrets-env.js +52 -262
- package/lib/utils/dataplane-resolver.js +4 -115
- package/lib/utils/declarative-url-ports.js +24 -1
- package/lib/utils/docker-build.js +1 -4
- package/lib/utils/external-env-template.js +10 -3
- package/lib/utils/external-system-validators.js +8 -2
- package/lib/utils/help-builder.js +2 -0
- package/lib/utils/paths.js +25 -0
- package/lib/utils/platform-controller-url.js +2 -1
- package/lib/utils/platform-kv-ref.js +57 -0
- package/lib/utils/runtime-configuration-download.js +189 -0
- package/lib/utils/runtime-configuration-values.js +119 -0
- package/lib/utils/schema-loader.js +19 -4
- package/lib/utils/worker-sidecars.js +142 -0
- package/lib/validation/external-manifest-validator.js +2 -0
- package/lib/validation/runtime-configuration-rules.js +98 -0
- package/lib/validation/validate-display-dimensions.js +154 -0
- package/lib/validation/validate-display-sections.js +276 -0
- package/lib/validation/validate-display-steps.js +67 -0
- package/lib/validation/validate-display.js +24 -402
- package/package.json +1 -1
- package/templates/applications/dataplane/application.yaml +5 -0
- package/templates/applications/dataplane/env.template +26 -13
- package/templates/applications/dataplane/rbac.yaml +108 -1
- package/templates/applications/miso-controller/application.yaml +2 -2
- package/templates/applications/miso-controller/env.template +37 -122
- package/templates/applications/miso-controller/rbac.yaml +28 -13
- package/templates/channels/chatgpt/install.template.md.hbs +49 -0
- package/templates/channels/chatgpt/manifest.template.json.hbs +9 -0
- package/templates/channels/microsoft-copilot/install.template.md.hbs +49 -0
- package/templates/channels/microsoft-copilot/manifest.template.json.hbs +8 -0
- package/templates/channels/slack/install.template.md.hbs +49 -0
- package/templates/channels/slack/manifest.template.json.hbs +66 -0
- package/templates/channels/teams-bot/install.template.md.hbs +49 -0
- package/templates/channels/teams-bot/manifest.template.json.hbs +13 -0
- package/templates/channels/web-embed/install.template.md.hbs +49 -0
- package/templates/channels/web-embed/manifest.template.json.hbs +8 -0
- package/templates/python/docker-compose.hbs +56 -0
- package/templates/typescript/docker-compose.hbs +52 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel install probe checks (plan 152).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Callback, assistant catalog, and inbound verification probes
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { ApiClient } = require('../api/index');
|
|
12
|
+
const { fetchDataplaneGeneralHealth } = require('../api/dataplane-health.api');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @async
|
|
16
|
+
* @param {string} url
|
|
17
|
+
* @returns {Promise<boolean>}
|
|
18
|
+
*/
|
|
19
|
+
async function isUrlReachable(url) {
|
|
20
|
+
if (!url) return false;
|
|
21
|
+
const client = new ApiClient(url.split('/api/')[0] || url);
|
|
22
|
+
try {
|
|
23
|
+
const pathPart = url.includes('/api/') ? url.substring(url.indexOf('/api/')) : '/health';
|
|
24
|
+
const res = await client.get(pathPart, { timeoutMs: 8000 });
|
|
25
|
+
return res && res.success !== false;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @async
|
|
33
|
+
* @param {string} dataplaneUrl
|
|
34
|
+
* @param {Object} authConfig
|
|
35
|
+
* @returns {Promise<boolean>}
|
|
36
|
+
*/
|
|
37
|
+
async function isAssistantCatalogReachable(dataplaneUrl, authConfig) {
|
|
38
|
+
const client = require('../api/index').createDataplaneApiClient(dataplaneUrl, authConfig);
|
|
39
|
+
try {
|
|
40
|
+
const res = await client.get('/api/v1/workers/available', { timeoutMs: 10000 });
|
|
41
|
+
return res && res.success !== false;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {Object|null} channelExt
|
|
49
|
+
* @returns {Object}
|
|
50
|
+
*/
|
|
51
|
+
function evaluateInboundVerification(channelExt) {
|
|
52
|
+
const credentialKey = channelExt?.credentialKey || '';
|
|
53
|
+
return {
|
|
54
|
+
slackSigningSecretConfigured: credentialKey.includes('slack'),
|
|
55
|
+
teamsBotOidcValidationConfigured: credentialKey.includes('teams-bot'),
|
|
56
|
+
issuerAudienceConfigured: Boolean(channelExt?.type),
|
|
57
|
+
replayProtectionConfigured: Boolean(channelExt?.status === 'installed' || channelExt?.status === 'validated')
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @async
|
|
63
|
+
* @param {Object} ctx
|
|
64
|
+
* @param {Object|null} channelExt
|
|
65
|
+
* @returns {Promise<Object>}
|
|
66
|
+
*/
|
|
67
|
+
async function runChannelProbe(ctx, channelExt) {
|
|
68
|
+
const health = await fetchDataplaneGeneralHealth(ctx.dataplaneUrl, { timeoutMs: 8000 });
|
|
69
|
+
const catalogOk = await isAssistantCatalogReachable(ctx.dataplaneUrl, ctx.authConfig);
|
|
70
|
+
const callbackOk = health?.status === 'healthy' || health?.status === 'ok';
|
|
71
|
+
|
|
72
|
+
const inbound = evaluateInboundVerification(channelExt);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
dataplaneHealth: health?.status || 'unknown',
|
|
76
|
+
callbackReachable: callbackOk,
|
|
77
|
+
assistantCatalogReachable: catalogOk,
|
|
78
|
+
credentialValid: Boolean(channelExt?.credentialKey),
|
|
79
|
+
inboundVerification: inbound,
|
|
80
|
+
passed: callbackOk && catalogOk && Boolean(channelExt?.credentialKey)
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = {
|
|
85
|
+
isUrlReachable,
|
|
86
|
+
isAssistantCatalogReachable,
|
|
87
|
+
evaluateInboundVerification,
|
|
88
|
+
runChannelProbe
|
|
89
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve runtime URLs for channel templates (no hardcoded env URLs).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Channel runtime value resolution (plan 152)
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { getChannelTarget } = require('./channel-targets');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Normalize dataplane base URL (no trailing slash).
|
|
15
|
+
* @param {string} dataplaneBaseUrl
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
function normalizeBaseUrl(dataplaneBaseUrl) {
|
|
19
|
+
if (!dataplaneBaseUrl || typeof dataplaneBaseUrl !== 'string') {
|
|
20
|
+
throw new Error('dataplaneBaseUrl is required');
|
|
21
|
+
}
|
|
22
|
+
return dataplaneBaseUrl.trim().replace(/\/+$/, '');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {string} base
|
|
27
|
+
* @param {string} path
|
|
28
|
+
* @returns {string}
|
|
29
|
+
*/
|
|
30
|
+
function joinUrl(base, path) {
|
|
31
|
+
return `${normalizeBaseUrl(base)}${path.startsWith('/') ? path : `/${path}`}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Dataplane external-system route prefix for webhook URLs.
|
|
36
|
+
* @param {string} routeSystemKey
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
function externalSystemsPrefix(routeSystemKey) {
|
|
40
|
+
return `/api/v1/external/systems/${routeSystemKey}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Relative endpoint paths per channel target (under external/systems/{routeSystemKey}).
|
|
45
|
+
* @param {string} channelTarget
|
|
46
|
+
* @returns {Record<string, string>}
|
|
47
|
+
*/
|
|
48
|
+
function getEndpointPathSuffixes(channelTarget) {
|
|
49
|
+
const maps = {
|
|
50
|
+
slack: {
|
|
51
|
+
eventsUrl: '/events',
|
|
52
|
+
interactionUrl: '/interactive',
|
|
53
|
+
oauthCallbackUrl: '/oauth/callback',
|
|
54
|
+
commandUrl: '/commands'
|
|
55
|
+
},
|
|
56
|
+
'teams-bot': {
|
|
57
|
+
messagesUrl: '/messages',
|
|
58
|
+
notificationsUrl: '/notifications',
|
|
59
|
+
botConfigUrl: '/bot-config'
|
|
60
|
+
},
|
|
61
|
+
'microsoft-copilot': {
|
|
62
|
+
openapiUrl: '/openapi.yaml',
|
|
63
|
+
actionsBaseUrl: '/actions'
|
|
64
|
+
},
|
|
65
|
+
chatgpt: {
|
|
66
|
+
openapiUrl: '/openapi.yaml',
|
|
67
|
+
mcpUrl: '/mcp',
|
|
68
|
+
actionsBaseUrl: '/actions'
|
|
69
|
+
},
|
|
70
|
+
'web-embed': {
|
|
71
|
+
configUrl: '/config'
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return maps[channelTarget] || {};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Legacy / non-external-system paths (Copilot/ChatGPT until dataplane routes exist).
|
|
79
|
+
* @param {string} channelTarget
|
|
80
|
+
* @returns {Record<string, string>}
|
|
81
|
+
*/
|
|
82
|
+
function getLegacyApiPaths(channelTarget) {
|
|
83
|
+
const legacy = {
|
|
84
|
+
'microsoft-copilot': {
|
|
85
|
+
openapiUrl: '/api/v1/copilot/openapi.yaml',
|
|
86
|
+
actionsBaseUrl: '/api/v1/copilot/actions'
|
|
87
|
+
},
|
|
88
|
+
chatgpt: {
|
|
89
|
+
openapiUrl: '/api/v1/chatgpt/openapi.yaml',
|
|
90
|
+
mcpUrl: '/api/v1/chatgpt/mcp',
|
|
91
|
+
actionsBaseUrl: '/api/v1/chatgpt/actions'
|
|
92
|
+
},
|
|
93
|
+
'web-embed': {
|
|
94
|
+
configUrl: '/api/v1/web-embed/config'
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return legacy[channelTarget] || {};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {Object} params
|
|
102
|
+
* @param {string} params.dataplaneBaseUrl
|
|
103
|
+
* @param {string} params.channelTarget
|
|
104
|
+
* @param {string} params.appName
|
|
105
|
+
* @param {string} [params.baseName]
|
|
106
|
+
* @param {string} [params.environment]
|
|
107
|
+
* @param {string} params.appDescription
|
|
108
|
+
* @param {string} [params.icon]
|
|
109
|
+
* @returns {Object}
|
|
110
|
+
*/
|
|
111
|
+
function resolveChannelRuntimeValues(params) {
|
|
112
|
+
const target = getChannelTarget(params.channelTarget);
|
|
113
|
+
const base = normalizeBaseUrl(params.dataplaneBaseUrl);
|
|
114
|
+
const routeKey = target.routeSystemKey || target.key;
|
|
115
|
+
const systemPrefix = externalSystemsPrefix(routeKey);
|
|
116
|
+
const suffixes = getEndpointPathSuffixes(params.channelTarget);
|
|
117
|
+
const legacy = getLegacyApiPaths(params.channelTarget);
|
|
118
|
+
|
|
119
|
+
const resolved = {
|
|
120
|
+
dataplaneBaseUrl: base,
|
|
121
|
+
channelTarget: params.channelTarget,
|
|
122
|
+
routeSystemKey: routeKey,
|
|
123
|
+
appName: params.appName,
|
|
124
|
+
baseName: params.baseName || params.appName,
|
|
125
|
+
environment: params.environment || 'dev',
|
|
126
|
+
appDescription: params.appDescription,
|
|
127
|
+
icon: params.icon || '',
|
|
128
|
+
credentialKey: target.credentialKey,
|
|
129
|
+
assistantCatalogUrl: joinUrl(base, '/api/v1/workers/available'),
|
|
130
|
+
scopes: []
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
for (const [key, suffix] of Object.entries(suffixes)) {
|
|
134
|
+
resolved[key] = joinUrl(base, `${systemPrefix}${suffix}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
for (const [key, legacyPath] of Object.entries(legacy)) {
|
|
138
|
+
if (!resolved[key]) {
|
|
139
|
+
resolved[key] = joinUrl(base, legacyPath);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return resolved;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
normalizeBaseUrl,
|
|
148
|
+
joinUrl,
|
|
149
|
+
externalSystemsPrefix,
|
|
150
|
+
getEndpointPathSuffixes,
|
|
151
|
+
resolveChannelRuntimeValues
|
|
152
|
+
};
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TTY output for app add-channel (plan 152).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Channel install status display — customer language: Assistant
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const chalk = require('chalk');
|
|
12
|
+
const logger = require('../utils/logger');
|
|
13
|
+
const {
|
|
14
|
+
formatSuccessLine,
|
|
15
|
+
formatWarningLine,
|
|
16
|
+
formatBlockingError,
|
|
17
|
+
sectionTitle,
|
|
18
|
+
metadata
|
|
19
|
+
} = require('../utils/cli-test-layout-chalk');
|
|
20
|
+
|
|
21
|
+
/** @returns {string} */
|
|
22
|
+
function separatorLine() {
|
|
23
|
+
const gray = chalk.gray;
|
|
24
|
+
return typeof gray === 'function'
|
|
25
|
+
? gray('────────────────────────────────────────')
|
|
26
|
+
: '────────────────────────────────────────';
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {Object} params
|
|
31
|
+
* @param {string} params.channelTarget
|
|
32
|
+
* @param {string} params.name
|
|
33
|
+
* @param {Object|null} params.channelExt
|
|
34
|
+
* @param {string} [params.approvalUrl]
|
|
35
|
+
*/
|
|
36
|
+
function displayExistingChannelStatus(params) {
|
|
37
|
+
logger.log(sectionTitle(`${params.channelTarget} channel: ${params.name}`));
|
|
38
|
+
logger.log('');
|
|
39
|
+
logger.log(metadata(`Status: ${params.channelExt?.status || 'unknown'}`));
|
|
40
|
+
if (params.channelExt?.baseName && params.channelExt.baseName !== params.name) {
|
|
41
|
+
logger.log(metadata(`Base name: ${params.channelExt.baseName}`));
|
|
42
|
+
}
|
|
43
|
+
logger.log(metadata(`External System: ${params.channelTarget}`));
|
|
44
|
+
logger.log(metadata(`Credential: ${params.channelExt?.credentialKey || '—'}`));
|
|
45
|
+
if (params.channelExt?.capabilities?.length) {
|
|
46
|
+
logger.log(metadata('Capabilities:'));
|
|
47
|
+
for (const cap of params.channelExt.capabilities) {
|
|
48
|
+
logger.log(` - ${cap}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
logger.log('');
|
|
52
|
+
logger.log(formatWarningLine('Channel install already configured. Use --force to regenerate artifacts.'));
|
|
53
|
+
if (params.approvalUrl) {
|
|
54
|
+
logger.log(metadata(`Approval URL: ${params.approvalUrl}`));
|
|
55
|
+
}
|
|
56
|
+
logger.log(separatorLine());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* @param {Object} result
|
|
61
|
+
*/
|
|
62
|
+
function displayChannelCoreFields(result) {
|
|
63
|
+
logger.log(sectionTitle(`${result.channelTarget} channel: ${result.name}`));
|
|
64
|
+
logger.log('');
|
|
65
|
+
logger.log(metadata(`Status: ${result.statusLabel}`));
|
|
66
|
+
if (result.baseName && result.baseName !== result.name) {
|
|
67
|
+
logger.log(metadata(`Base name: ${result.baseName}`));
|
|
68
|
+
}
|
|
69
|
+
logger.log(metadata(`External System: ${result.channelTarget}`));
|
|
70
|
+
logger.log(metadata(`Credential: ${result.credentialKey}`));
|
|
71
|
+
if (result.capabilities?.length) {
|
|
72
|
+
logger.log(metadata('Capabilities:'));
|
|
73
|
+
for (const cap of result.capabilities) {
|
|
74
|
+
logger.log(` - ${cap}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (result.artifactPath) {
|
|
78
|
+
logger.log(metadata(`Artifact: ${result.artifactPath}`));
|
|
79
|
+
}
|
|
80
|
+
if (result.installGuidePath) {
|
|
81
|
+
logger.log(metadata(`Install guide: ${result.installGuidePath}`));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {Object|null} validation
|
|
87
|
+
*/
|
|
88
|
+
function displayValidationSection(validation) {
|
|
89
|
+
if (!validation) return;
|
|
90
|
+
logger.log('');
|
|
91
|
+
logger.log(metadata('Validation:'));
|
|
92
|
+
if (validation.callbackReachable) {
|
|
93
|
+
logger.log(formatSuccessLine('Callback reachable'));
|
|
94
|
+
}
|
|
95
|
+
if (validation.credentialValid) {
|
|
96
|
+
logger.log(formatSuccessLine('Credential valid'));
|
|
97
|
+
}
|
|
98
|
+
if (validation.assistantCatalogReachable) {
|
|
99
|
+
logger.log(formatSuccessLine('Assistant catalog reachable'));
|
|
100
|
+
}
|
|
101
|
+
if (validation.inboundVerification) {
|
|
102
|
+
logger.log(formatSuccessLine('Inbound verification configured'));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {Object} result
|
|
108
|
+
*/
|
|
109
|
+
function displayApprovalSection(result) {
|
|
110
|
+
if (!result.approvalUrl) return;
|
|
111
|
+
logger.log('');
|
|
112
|
+
logger.log(formatWarningLine('Approval required'));
|
|
113
|
+
logger.log(metadata('Open this URL as platform admin:'));
|
|
114
|
+
logger.log(chalk.cyan(result.approvalUrl));
|
|
115
|
+
logger.log('');
|
|
116
|
+
logger.log(metadata('After approval, run:'));
|
|
117
|
+
logger.log(chalk.white(` aifabrix app add-channel ${result.channelTarget} --name "${result.name}"`));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* @param {Object} result
|
|
122
|
+
*/
|
|
123
|
+
function displayChannelInstallResult(result) {
|
|
124
|
+
displayChannelCoreFields(result);
|
|
125
|
+
displayValidationSection(result.validation);
|
|
126
|
+
displayApprovalSection(result);
|
|
127
|
+
logger.log(separatorLine());
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {string} message
|
|
132
|
+
*/
|
|
133
|
+
function displayChannelError(message) {
|
|
134
|
+
logger.error(formatBlockingError(message));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = {
|
|
138
|
+
displayExistingChannelStatus,
|
|
139
|
+
displayChannelInstallResult,
|
|
140
|
+
displayChannelError
|
|
141
|
+
};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Channel target registry for assistant communication channels (plan 152).
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Channel install targets — templates, capabilities, approval semantics
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
/** @typedef {'teams-bot'|'slack'|'microsoft-copilot'|'chatgpt'|'web-embed'} ChannelTargetKey */
|
|
12
|
+
|
|
13
|
+
const DEFAULT_CAPABILITIES = ['chat', 'notify'];
|
|
14
|
+
|
|
15
|
+
const CHANNEL_TARGETS = {
|
|
16
|
+
'teams-bot': {
|
|
17
|
+
key: 'teams-bot',
|
|
18
|
+
channelType: 'teamsBot',
|
|
19
|
+
templateDir: 'teams-bot',
|
|
20
|
+
artifactFileName: 'channel-teams-bot.json',
|
|
21
|
+
artifactFormat: 'json',
|
|
22
|
+
credentialKey: 'teams-bot',
|
|
23
|
+
routeSystemKey: 'teams',
|
|
24
|
+
capabilities: DEFAULT_CAPABILITIES,
|
|
25
|
+
approvalRequired: true,
|
|
26
|
+
experimental: false
|
|
27
|
+
},
|
|
28
|
+
slack: {
|
|
29
|
+
key: 'slack',
|
|
30
|
+
channelType: 'slack',
|
|
31
|
+
templateDir: 'slack',
|
|
32
|
+
artifactFileName: 'channel-slack.json',
|
|
33
|
+
artifactFormat: 'json',
|
|
34
|
+
credentialKey: 'slack-app',
|
|
35
|
+
routeSystemKey: 'slack',
|
|
36
|
+
capabilities: DEFAULT_CAPABILITIES,
|
|
37
|
+
approvalRequired: true,
|
|
38
|
+
experimental: false
|
|
39
|
+
},
|
|
40
|
+
'microsoft-copilot': {
|
|
41
|
+
key: 'microsoft-copilot',
|
|
42
|
+
channelType: 'microsoftCopilot',
|
|
43
|
+
templateDir: 'microsoft-copilot',
|
|
44
|
+
artifactFileName: 'channel-microsoft-copilot.json',
|
|
45
|
+
artifactFormat: 'json',
|
|
46
|
+
credentialKey: 'microsoft-copilot-channel',
|
|
47
|
+
capabilities: [...DEFAULT_CAPABILITIES, 'enterpriseActions'],
|
|
48
|
+
approvalRequired: true,
|
|
49
|
+
experimental: false
|
|
50
|
+
},
|
|
51
|
+
chatgpt: {
|
|
52
|
+
key: 'chatgpt',
|
|
53
|
+
channelType: 'chatgpt',
|
|
54
|
+
templateDir: 'chatgpt',
|
|
55
|
+
artifactFileName: 'channel-chatgpt.json',
|
|
56
|
+
artifactFormat: 'json',
|
|
57
|
+
credentialKey: 'chatgpt-channel',
|
|
58
|
+
capabilities: ['chat', 'enterpriseActions'],
|
|
59
|
+
approvalRequired: true,
|
|
60
|
+
experimental: false
|
|
61
|
+
},
|
|
62
|
+
'web-embed': {
|
|
63
|
+
key: 'web-embed',
|
|
64
|
+
channelType: 'webEmbed',
|
|
65
|
+
templateDir: 'web-embed',
|
|
66
|
+
artifactFileName: 'channel-web-embed.json',
|
|
67
|
+
artifactFormat: 'json',
|
|
68
|
+
credentialKey: 'web-embed-channel',
|
|
69
|
+
capabilities: DEFAULT_CAPABILITIES,
|
|
70
|
+
approvalRequired: false,
|
|
71
|
+
experimental: true
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const CHANNEL_TARGET_KEYS = Object.keys(CHANNEL_TARGETS);
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @param {string} channelTarget
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
function isChannelTargetKey(channelTarget) {
|
|
82
|
+
return typeof channelTarget === 'string' && CHANNEL_TARGET_KEYS.includes(channelTarget);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @param {string} channelTarget
|
|
87
|
+
* @returns {typeof CHANNEL_TARGETS[string]}
|
|
88
|
+
*/
|
|
89
|
+
function getChannelTarget(channelTarget) {
|
|
90
|
+
if (!isChannelTargetKey(channelTarget)) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`Unknown channel target "${channelTarget}". Expected one of: ${CHANNEL_TARGET_KEYS.join(', ')}`
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return CHANNEL_TARGETS[channelTarget];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Default customer-facing description when --description is omitted.
|
|
100
|
+
* @returns {string}
|
|
101
|
+
*/
|
|
102
|
+
function getDefaultChannelDescription() {
|
|
103
|
+
return (
|
|
104
|
+
'Enterprise AI assistant for employees. Provides access to approved assistants, ' +
|
|
105
|
+
'enterprise knowledge, business workflows, and governed actions through AI Fabrix.'
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
CHANNEL_TARGETS,
|
|
111
|
+
CHANNEL_TARGET_KEYS,
|
|
112
|
+
isChannelTargetKey,
|
|
113
|
+
getChannelTarget,
|
|
114
|
+
getDefaultChannelDescription
|
|
115
|
+
};
|
package/lib/cli/index.js
CHANGED
|
@@ -22,6 +22,7 @@ const { setupExternalSystemCommands } = require('./setup-external-system');
|
|
|
22
22
|
const { setupAppCommands: setupAppManagementCommands } = require('../commands/app');
|
|
23
23
|
const { setupDatasourceCommands } = require('../commands/datasource');
|
|
24
24
|
const { setupDimensionCommands } = require('../commands/dimension');
|
|
25
|
+
const { setupResourceTypeCommands } = require('../commands/resource-type');
|
|
25
26
|
const { setupIdentityCommands } = require('../commands/identity');
|
|
26
27
|
const { setupProtectionCommands } = require('../commands/protection');
|
|
27
28
|
const { setupCredentialDeploymentCommands } = require('./setup-credential-deployment');
|
|
@@ -29,6 +30,18 @@ const { setupIntegrationClientCommands } = require('./setup-integration-client')
|
|
|
29
30
|
const { setupPlatformCommands } = require('./setup-platform');
|
|
30
31
|
const { setupCertificationCommands } = require('./setup-certification-commands');
|
|
31
32
|
const { setupGovernanceCommands } = require('../commands/governance');
|
|
33
|
+
const { setupChannelCommands } = require('../commands/channel');
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Dev, secrets, parameters, and platform commands (keeps setupCommands under statement limit).
|
|
37
|
+
* @param {import('commander').Command} program
|
|
38
|
+
*/
|
|
39
|
+
function setupDevAndPlatformCommands(program) {
|
|
40
|
+
setupDevCommands(program);
|
|
41
|
+
setupSecretsCommands(program);
|
|
42
|
+
setupParametersCommands(program);
|
|
43
|
+
setupPlatformCommands(program);
|
|
44
|
+
}
|
|
32
45
|
|
|
33
46
|
/**
|
|
34
47
|
* Sets up all CLI commands on the Commander program instance
|
|
@@ -42,18 +55,17 @@ function setupCommands(program) {
|
|
|
42
55
|
setupGovernanceCommands(program);
|
|
43
56
|
setupEnvironmentCommands(program);
|
|
44
57
|
setupAppManagementCommands(program);
|
|
58
|
+
setupChannelCommands(program);
|
|
45
59
|
setupDatasourceCommands(program);
|
|
46
60
|
setupDimensionCommands(program);
|
|
61
|
+
setupResourceTypeCommands(program);
|
|
47
62
|
setupIdentityCommands(program);
|
|
48
63
|
setupProtectionCommands(program);
|
|
49
64
|
setupUtilityCommands(program);
|
|
50
65
|
setupCredentialDeploymentCommands(program);
|
|
51
66
|
setupIntegrationClientCommands(program);
|
|
52
67
|
setupExternalSystemCommands(program);
|
|
53
|
-
|
|
54
|
-
setupSecretsCommands(program);
|
|
55
|
-
setupParametersCommands(program);
|
|
56
|
-
setupPlatformCommands(program);
|
|
68
|
+
setupDevAndPlatformCommands(program);
|
|
57
69
|
}
|
|
58
70
|
|
|
59
71
|
module.exports = {
|
package/lib/cli/setup-app.js
CHANGED
|
@@ -112,6 +112,49 @@ function normalizeExternalOptions(options) {
|
|
|
112
112
|
return normalized;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Whether normalized options include all external CLI fields for headless create.
|
|
117
|
+
* @param {Object} normalized - Normalized external create options
|
|
118
|
+
* @returns {boolean}
|
|
119
|
+
*/
|
|
120
|
+
function hasFullExternalCliOptions(normalized) {
|
|
121
|
+
return Boolean(
|
|
122
|
+
normalized.systemKey &&
|
|
123
|
+
normalized.systemDisplayName &&
|
|
124
|
+
normalized.systemDescription &&
|
|
125
|
+
normalized.systemType &&
|
|
126
|
+
normalized.authType &&
|
|
127
|
+
normalized.entityType &&
|
|
128
|
+
normalized.datasourceCount
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Map positional systemKey and apply headless defaults for external create.
|
|
134
|
+
* @param {string} appName - Positional appKey or systemKey from CLI
|
|
135
|
+
* @param {Object} options - Raw CLI options
|
|
136
|
+
* @returns {Object} Normalized options ready for createApp
|
|
137
|
+
*/
|
|
138
|
+
function prepareExternalCreateOptions(appName, options) {
|
|
139
|
+
const normalized = normalizeExternalOptions(options);
|
|
140
|
+
const isExternalType = options.type === 'external' || !options.type;
|
|
141
|
+
|
|
142
|
+
if (isExternalType && appName) {
|
|
143
|
+
normalized.systemKey = appName;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (isExternalType && hasFullExternalCliOptions(normalized)) {
|
|
147
|
+
if (!Object.prototype.hasOwnProperty.call(normalized, 'github')) {
|
|
148
|
+
normalized.github = false;
|
|
149
|
+
}
|
|
150
|
+
if (!Object.prototype.hasOwnProperty.call(normalized, 'controller')) {
|
|
151
|
+
normalized.controller = false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return normalized;
|
|
156
|
+
}
|
|
157
|
+
|
|
115
158
|
/**
|
|
116
159
|
* Validate required options for non-interactive external creation
|
|
117
160
|
* @param {Object} normalizedOptions - Normalized options
|
|
@@ -149,7 +192,7 @@ async function handleCreateCommand(appName, options) {
|
|
|
149
192
|
}
|
|
150
193
|
|
|
151
194
|
const wizardOptions = { app: appName, ...options };
|
|
152
|
-
const normalizedOptions =
|
|
195
|
+
const normalizedOptions = prepareExternalCreateOptions(appName, options);
|
|
153
196
|
|
|
154
197
|
const isExternalType = options.type === 'external' || !options.type;
|
|
155
198
|
const isNonInteractive = process.stdin && process.stdin.isTTY === false;
|
|
@@ -279,7 +322,10 @@ function setupBuildRunLogsDownCommands(program) {
|
|
|
279
322
|
.option('-f, --force-template', 'Force rebuild from template')
|
|
280
323
|
.option('--no-cache', 'Full Docker rebuild (disable layer cache); use after Dockerfile or context fixes')
|
|
281
324
|
.option('-t, --tag <tag>', 'Image tag (default: latest). Set image.tag in application.yaml to match for deploy.')
|
|
282
|
-
.option(
|
|
325
|
+
.option(
|
|
326
|
+
'--base',
|
|
327
|
+
'When manifest omits image: also tag the base repository after a developer-scoped build (ignored when application.yaml defines image)'
|
|
328
|
+
)
|
|
283
329
|
.action(async(appName, options) => {
|
|
284
330
|
try {
|
|
285
331
|
const imageTag = await app.buildApp(appName, options);
|
|
@@ -437,4 +483,9 @@ function setupAppCommands(program) {
|
|
|
437
483
|
setupPushDeployDockerfileCommands(program);
|
|
438
484
|
}
|
|
439
485
|
|
|
440
|
-
module.exports = {
|
|
486
|
+
module.exports = {
|
|
487
|
+
setupAppCommands,
|
|
488
|
+
normalizeExternalOptions,
|
|
489
|
+
prepareExternalCreateOptions,
|
|
490
|
+
validateNonInteractiveExternalOptions
|
|
491
|
+
};
|
|
@@ -15,7 +15,7 @@ const { handleCommandError, isAuthenticationError } = require('../utils/cli-util
|
|
|
15
15
|
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
16
16
|
const { handleLogin } = require('../commands/login');
|
|
17
17
|
const { handleUpDataplane } = require('../commands/up-dataplane');
|
|
18
|
-
const { cleanBuilderAppDirs } = require('../commands/up-common');
|
|
18
|
+
const { cleanBuilderAppDirs, DATAPLANE_BUILDER_APP_KEYS } = require('../commands/up-common');
|
|
19
19
|
const { runGuidedUpDataplane } = require('./infra-guided');
|
|
20
20
|
const { recordInfraInstallationCommand } = require('./installation-log-command');
|
|
21
21
|
|
|
@@ -26,7 +26,7 @@ const { recordInfraInstallationCommand } = require('./installation-log-command')
|
|
|
26
26
|
async function runUpDataplaneWork(options) {
|
|
27
27
|
let cleanedAppKeys = [];
|
|
28
28
|
if (options.force) {
|
|
29
|
-
const c = await cleanBuilderAppDirs([
|
|
29
|
+
const c = await cleanBuilderAppDirs([...DATAPLANE_BUILDER_APP_KEYS]);
|
|
30
30
|
cleanedAppKeys = Array.isArray(c) ? c : [];
|
|
31
31
|
}
|
|
32
32
|
if (!options.verbose) {
|
|
@@ -19,6 +19,7 @@ const { handleUpDataplane } = require('../commands/up-dataplane');
|
|
|
19
19
|
const {
|
|
20
20
|
applyUpPlatformForceConfig,
|
|
21
21
|
cleanBuilderAppDirs,
|
|
22
|
+
PLATFORM_BUILDER_CLEAN_APP_KEYS,
|
|
22
23
|
prepareUrlsLocalRegistryForUpPlatform
|
|
23
24
|
} = require('../commands/up-common');
|
|
24
25
|
const { runGuidedUpPlatform } = require('./infra-guided');
|
|
@@ -35,7 +36,7 @@ async function runUpPlatformWork(options) {
|
|
|
35
36
|
let platformForceCleanSummary = null;
|
|
36
37
|
if (options.force) {
|
|
37
38
|
const forceSummary = await applyUpPlatformForceConfig({ silent: !options.verbose });
|
|
38
|
-
const cleanedApps = await cleanBuilderAppDirs(
|
|
39
|
+
const cleanedApps = await cleanBuilderAppDirs(PLATFORM_BUILDER_CLEAN_APP_KEYS, {
|
|
39
40
|
silent: !options.verbose
|
|
40
41
|
});
|
|
41
42
|
cleanedAppKeys = Array.isArray(cleanedApps) ? cleanedApps : [];
|
|
@@ -54,7 +54,7 @@ function registerSetup(program) {
|
|
|
54
54
|
.command('setup')
|
|
55
55
|
.description('Install or refresh the full AI Fabrix platform (infra + miso + dataplane)')
|
|
56
56
|
.option('-d, --developer <id>', 'Pin developer ID before fresh install (fresh path only; ignored when infra is already up)')
|
|
57
|
-
.option('--platform <mode>', 'Platform mode: single (
|
|
57
|
+
.option('--platform <mode>', 'Platform mode: single, single-traefik (Traefik + TLS), or full (multi-env; advanced)')
|
|
58
58
|
.option('--installation <profile>', 'Installation profile: dev (one password) or pro (autogen/manual)')
|
|
59
59
|
.option('--pro-password-mode <mode>', 'Pro profile only: autogen or manual')
|
|
60
60
|
.option('--admin-email <email>', 'Admin email (non-interactive fresh install)')
|
|
@@ -46,8 +46,11 @@ Examples:
|
|
|
46
46
|
$ aifabrix resolve hubspot-demo
|
|
47
47
|
External integration: resolves integration/<systemKey>/env.template to .env using local host ports (no Docker rewrite).
|
|
48
48
|
|
|
49
|
+
$ aifabrix resolve dataplane --force
|
|
50
|
+
Seed missing secret keys and replace the full .env from env.template (no merge with an existing file).
|
|
51
|
+
|
|
49
52
|
$ aifabrix resolve dataplane --fresh
|
|
50
|
-
|
|
53
|
+
Replace the full .env only (no merge; does not seed missing secret keys). Same .env output as --force when secrets are already set.
|
|
51
54
|
|
|
52
55
|
$ aifabrix secret set hubspot-demo/token "<your-token>"
|
|
53
56
|
Store a secret when resolve reports a missing kv:// reference.
|