@aifabrix/builder 2.57.0 → 2.59.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/codex-app-server.js +44 -0
- package/lib/agent-kit/init.js +4 -17
- package/lib/agent-kit/session-report.js +15 -10
- package/lib/agent-kit/setup-auth.js +131 -0
- package/lib/agent-kit/setup-context.js +118 -0
- package/lib/agent-kit/setup-github.js +70 -0
- package/lib/agent-kit/setup-managed.js +79 -0
- package/lib/agent-kit/setup-process.js +42 -0
- package/lib/agent-kit/setup-session.js +53 -0
- package/lib/agent-kit/setup-state.js +52 -0
- package/lib/agent-kit/setup-workspace.js +113 -0
- package/lib/agent-kit/setup.js +84 -0
- package/lib/agent-kit/start.js +4 -3
- package/lib/agent-kit/tmux-sessions.js +20 -3
- package/lib/api/dev.api.js +1 -1
- package/lib/api/miso-health.api.js +26 -0
- package/lib/api/types/dev.types.js +3 -3
- package/lib/app/deploy.js +0 -102
- package/lib/app/push.js +98 -41
- package/lib/build/standard-docker-build.js +13 -11
- package/lib/cli/index.js +4 -0
- package/lib/cli/setup-app.js +16 -5
- package/lib/cli/setup-dev-path-commands.js +53 -2
- package/lib/cli/setup-dev.js +7 -4
- package/lib/cli/setup-environment.js +17 -4
- package/lib/cli/setup-help.js +86 -0
- package/lib/cli/setup-onboarding.js +37 -0
- package/lib/cli/setup-utility-repair.js +7 -5
- package/lib/commands/agent-kit-help.js +21 -7
- package/lib/commands/agent-kit-sessions.js +126 -0
- package/lib/commands/agent-kit-setup.js +44 -0
- package/lib/commands/agent-kit.js +5 -79
- package/lib/commands/dev-init.js +21 -23
- package/lib/commands/dev-refresh.js +2 -2
- package/lib/commands/dev-registry.js +155 -0
- package/lib/commands/dev-show-display.js +10 -2
- package/lib/commands/governance-verify-external.js +12 -5
- package/lib/commands/repair-datasource.js +9 -9
- package/lib/commands/repair.js +45 -8
- package/lib/commands/setup-image-refresh.js +26 -0
- package/lib/commands/upload-qualification-client.js +56 -11
- package/lib/commands/upload-qualification-poll.js +51 -22
- package/lib/commands/wizard-dataplane.js +16 -52
- package/lib/core/config-attach-extensions.js +4 -1
- package/lib/datasource/datasource-validate-summary.js +8 -5
- package/lib/datasource/sync-defaults.js +53 -0
- package/lib/deployment/installation/azure-infra-stage.js +50 -9
- package/lib/deployment/installation/azure-preflight.js +63 -7
- package/lib/deployment/installation/azure-readiness.js +59 -7
- package/lib/deployment/installation/environment-auth.js +60 -20
- package/lib/deployment/installation/environment-stage.js +9 -5
- package/lib/deployment/installation/infra-catalog.js +57 -5
- package/lib/deployment/installation/infra-manifest.js +22 -2
- package/lib/deployment/installation/infra-projection.js +14 -3
- package/lib/deployment/installation/registry-auth-mode.js +121 -0
- package/lib/deployment/installation/stage-input.js +21 -6
- package/lib/deployment/marketplace-environment.js +26 -1
- package/lib/generator/builders.js +8 -9
- package/lib/generator/index.js +23 -12
- package/lib/integration-definition/apply.js +2 -2
- package/lib/internal/fs-real-sync.js +4 -1
- package/lib/programmatic/bearer-auth.js +3 -62
- package/lib/programmatic/builder-api-startup.js +51 -0
- package/lib/programmatic/builder-miso-runtime.js +167 -0
- package/lib/programmatic/openapi-descriptions.js +3 -3
- package/lib/programmatic/openapi-schema-descriptions-core.js +3 -3
- package/lib/programmatic/openapi-spec.js +2 -1
- package/lib/programmatic/route-handler-map.js +3 -2
- package/lib/schema/environment-deploy-request.schema.json +1 -1
- package/lib/schema/external-datasource.schema.json +20 -5
- package/lib/schema/infra-parameter.schema.json +1 -1
- package/lib/schema/infra.parameter.yaml +2 -1
- package/lib/schema/infrastructure-schema.json +200 -3
- package/lib/schema/installation-environment.schema.json +1 -1
- package/lib/schema/wizard-config.schema.json +1 -1
- 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-paths.js +24 -29
- package/lib/utils/config-registry-preference.js +113 -0
- package/lib/utils/dev-init-ssh-merge.js +5 -4
- package/lib/utils/dev-user-groups.js +2 -1
- package/lib/utils/docker-build.js +53 -19
- package/lib/utils/docker-daemon-tls-ca.js +2 -2
- package/lib/utils/env-map.js +23 -6
- package/lib/utils/error-formatters/validation-errors.js +25 -5
- package/lib/utils/help-builder.js +4 -3
- 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/token-manager.js +29 -36
- package/lib/utils/variable-transformer.js +2 -4
- package/package.json +4 -2
- package/templates/agent-kit/instructions/AGENTKIT.md +39 -2
- package/templates/agent-kit/instructions/root.AGENTS.md +40 -1
- package/templates/agent-kit/skills/shared/hosts.md +23 -5
- package/templates/agent-kit/workspace/README.md +14 -1
- package/templates/applications/builder-api/application.yaml +1 -1
- package/templates/applications/dataplane/application.yaml +1 -1
- package/templates/applications/dataplane/env.template +1 -1
- package/templates/applications/miso-controller/application.yaml +1 -1
- package/templates/applications/miso-controller/env.template +1 -1
- package/templates/external-system/external-datasource.yaml.hbs +7 -6
- package/templates/marketplace/main.json +440 -82
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/** Machine-local onboarding progress, outside the repository and without credentials. */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('../internal/fs-real-sync');
|
|
6
|
+
const { randomUUID } = require('crypto');
|
|
7
|
+
const { assertDirectory } = require('./setup-context');
|
|
8
|
+
|
|
9
|
+
function readState(file) {
|
|
10
|
+
if (!fs.existsSync(file)) return null;
|
|
11
|
+
if (!fs.lstatSync(file).isFile()) throw new Error('Onboarding state must be a regular file.');
|
|
12
|
+
try {
|
|
13
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error('Onboarding state is invalid; preserve the workspace and inspect its state file.');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function acquireWorkspace(context) {
|
|
20
|
+
const dir = path.join(context.root, '.aifabrix-onboarding');
|
|
21
|
+
assertDirectory(dir);
|
|
22
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
23
|
+
const file = path.join(dir, `${context.name}.json`);
|
|
24
|
+
const lock = `${file}.lock`;
|
|
25
|
+
let descriptor;
|
|
26
|
+
try {
|
|
27
|
+
descriptor = fs.openSync(lock, 'wx', 0o600);
|
|
28
|
+
} catch {
|
|
29
|
+
throw new Error(`Workspace setup is locked. If no setup is running, remove the stale lock: ${lock}`);
|
|
30
|
+
}
|
|
31
|
+
fs.closeSync(descriptor);
|
|
32
|
+
const release = () => fs.unlinkSync(lock);
|
|
33
|
+
try {
|
|
34
|
+
const state = readState(file);
|
|
35
|
+
if (state && (state.controller !== context.controller || state.fullName !== context.fullName || state.target !== context.target)) {
|
|
36
|
+
throw new Error('This workspace belongs to a different Controller or repository. Use a different AI Fabrix work directory.');
|
|
37
|
+
}
|
|
38
|
+
return { file, release, state: state || { controller: context.controller, fullName: context.fullName, target: context.target } };
|
|
39
|
+
} catch (error) {
|
|
40
|
+
release();
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function saveProgress(progress, patch) {
|
|
46
|
+
Object.assign(progress.state, patch);
|
|
47
|
+
const temporary = `${progress.file}.${randomUUID()}.tmp`;
|
|
48
|
+
fs.writeFileSync(temporary, `${JSON.stringify(progress.state, null, 2)}\n`, { flag: 'wx', mode: 0o600 });
|
|
49
|
+
fs.renameSync(temporary, progress.file);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { acquireWorkspace, saveProgress };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/** Local workspace preparation and optional GitHub clone/create with safe resumability. */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('../internal/fs-real-sync');
|
|
5
|
+
const { runInit } = require('./init');
|
|
6
|
+
const { runInstall, runUpdate } = require('./run');
|
|
7
|
+
const { resolveGitRoot } = require('./repo-guard');
|
|
8
|
+
const { ensureInitialCommit } = require('./scm/git');
|
|
9
|
+
const { capture, checked } = require('./setup-process');
|
|
10
|
+
const github = require('./setup-github');
|
|
11
|
+
const { saveProgress } = require('./setup-state');
|
|
12
|
+
|
|
13
|
+
function validateExisting(context, progress, options) {
|
|
14
|
+
if (progress.state.initializing || progress.state.cloning) {
|
|
15
|
+
throw new Error('Workspace initialization or clone was interrupted. Inspect the preserved folder and onboarding state before retrying.');
|
|
16
|
+
}
|
|
17
|
+
if (!fs.existsSync(context.target) || fs.readdirSync(context.target).length === 0) return false;
|
|
18
|
+
if (resolveGitRoot(context.target) !== context.target) throw new Error('Workspace must be its own Git repository.');
|
|
19
|
+
if (context.fullName) {
|
|
20
|
+
const origin = github.originIdentity(context.target, options);
|
|
21
|
+
if (origin || !progress.state.initialized) github.assertOrigin(context, options);
|
|
22
|
+
} else if (!progress.state.initialized || github.originIdentity(context.target, options)) {
|
|
23
|
+
throw new Error('Local workspace is not an existing local-only onboarding workspace. No files were changed.');
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function checkKit(result) {
|
|
29
|
+
if (result.conflicts && result.conflicts.length) {
|
|
30
|
+
throw new Error('Agent Kit files have local conflicts. Resolve them with agent-kit status/update before retrying setup.');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function initializeLocal(context, progress, options) {
|
|
35
|
+
// Persist the operation first so an interrupted partial init cannot be adopted on retry.
|
|
36
|
+
saveProgress(progress, { initializing: true });
|
|
37
|
+
runInit({ nameOrPath: context.target, host: context.code, noRemote: true, approveAll: false,
|
|
38
|
+
interactive: false, execFileSync: options.execFileSync });
|
|
39
|
+
saveProgress(progress, { initialized: true, initializing: false });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function finishNewRemote(context, progress, options) {
|
|
43
|
+
const opts = { ...options, cwd: context.target };
|
|
44
|
+
if (!progress.state.initialCommit) {
|
|
45
|
+
ensureInitialCommit(context.target, opts);
|
|
46
|
+
const initialCommit = String(capture('git', ['rev-parse', 'HEAD'], opts)).trim();
|
|
47
|
+
saveProgress(progress, { initialCommit });
|
|
48
|
+
}
|
|
49
|
+
if (!progress.state.remoteCreated) {
|
|
50
|
+
github.createRepository(context.fullName, options);
|
|
51
|
+
saveProgress(progress, { remoteCreated: true });
|
|
52
|
+
}
|
|
53
|
+
if (!github.originIdentity(context.target, options)) {
|
|
54
|
+
checked('git', ['remote', 'add', 'origin', `https://github.com/${context.fullName}.git`],
|
|
55
|
+
'Could not configure the GitHub origin. Local files are preserved.', opts);
|
|
56
|
+
}
|
|
57
|
+
github.pushInitialCommit(context, progress, saveProgress, options);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function cloneWorkspace(context, progress, options) {
|
|
61
|
+
if (progress.state.initialized) throw new Error('Previously initialized workspace is missing. Restore it before retrying.');
|
|
62
|
+
saveProgress(progress, { cloning: true });
|
|
63
|
+
github.cloneRepository(context, options);
|
|
64
|
+
saveProgress(progress, { initialized: true, cloning: false });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function prepareGithub(context, progress, options, existing) {
|
|
68
|
+
if (existing && github.originIdentity(context.target, options)) {
|
|
69
|
+
if (progress.state.remoteCreated && !progress.state.pushed) finishNewRemote(context, progress, options);
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
const remote = github.lookupRepository(context.fullName, options);
|
|
73
|
+
if (!remote) {
|
|
74
|
+
if (progress.state.remoteCreated) {
|
|
75
|
+
throw new Error('The existing remote is no longer accessible. Restore access before retrying.');
|
|
76
|
+
}
|
|
77
|
+
if (existing && !progress.state.initialCommit) {
|
|
78
|
+
throw new Error('Initialization stopped before recording the initial commit. Inspect the preserved workspace before creating its remote.');
|
|
79
|
+
}
|
|
80
|
+
if (!existing) initializeLocal(context, progress, options);
|
|
81
|
+
finishNewRemote(context, progress, options);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (!existing) {
|
|
85
|
+
cloneWorkspace(context, progress, options);
|
|
86
|
+
} else if (!progress.state.remoteCreated) {
|
|
87
|
+
throw new Error('Repository appeared during creation. Inspect the local workspace and remote before connecting them.');
|
|
88
|
+
}
|
|
89
|
+
if (progress.state.remoteCreated && !progress.state.pushed) finishNewRemote(context, progress, options);
|
|
90
|
+
github.assertOrigin(context, options);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function prepareWorkspace(context, progress, options = {}) {
|
|
95
|
+
const existing = validateExisting(context, progress, options);
|
|
96
|
+
let installed = false;
|
|
97
|
+
if (context.fullName) installed = prepareGithub(context, progress, options, existing);
|
|
98
|
+
else if (!existing) {
|
|
99
|
+
if (progress.state.initialized) throw new Error('Previously initialized workspace is missing. Restore it before retrying.');
|
|
100
|
+
initializeLocal(context, progress, options);
|
|
101
|
+
installed = true;
|
|
102
|
+
}
|
|
103
|
+
if (!installed) {
|
|
104
|
+
const install = options.managed ? runUpdate : runInstall;
|
|
105
|
+
const preview = install({ cwd: context.target, host: context.code, approveAll: false, dryRun: true });
|
|
106
|
+
checkKit(preview);
|
|
107
|
+
checkKit(install({ cwd: context.target, host: context.code, approveAll: false }));
|
|
108
|
+
}
|
|
109
|
+
if (!fs.existsSync(context.target)) throw new Error('Workspace preparation did not create its directory.');
|
|
110
|
+
saveProgress(progress, { initialized: true, ready: true });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { validateExisting, prepareWorkspace, checkKit };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/** Portable local-first agent onboarding. */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { randomUUID } = require('crypto');
|
|
5
|
+
const logger = require('../utils/logger');
|
|
6
|
+
const { formatSuccessLine } = require('../utils/cli-layout-chalk');
|
|
7
|
+
const { auditLog } = require('../core/audit-logger');
|
|
8
|
+
const { normalizeSetup, preflight, resolveWork } = require('./setup-context');
|
|
9
|
+
const { acquireWorkspace } = require('./setup-state');
|
|
10
|
+
const { ensureController, ensureGithubLogin, ensureProvider } = require('./setup-auth');
|
|
11
|
+
const { prepareWorkspace, validateExisting } = require('./setup-workspace');
|
|
12
|
+
const { prepareSession, attachSession } = require('./setup-session');
|
|
13
|
+
const { managedSetupOptions } = require('./setup-managed');
|
|
14
|
+
|
|
15
|
+
async function authenticate(context, options, setPhase) {
|
|
16
|
+
setPhase('controller-login');
|
|
17
|
+
logger.log('Connecting to AI Fabrix...');
|
|
18
|
+
await ensureController(context);
|
|
19
|
+
if (context.fullName) {
|
|
20
|
+
setPhase('github-login');
|
|
21
|
+
ensureGithubLogin(options);
|
|
22
|
+
}
|
|
23
|
+
if (context.code === 'cursor') return null;
|
|
24
|
+
setPhase('agent-login');
|
|
25
|
+
return ensureProvider(context, options);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function prepareOnboarding(context, progress, options) {
|
|
29
|
+
const correlationId = randomUUID();
|
|
30
|
+
let provider;
|
|
31
|
+
let session;
|
|
32
|
+
let phase = 'workspace-check';
|
|
33
|
+
try {
|
|
34
|
+
validateExisting(context, progress, options);
|
|
35
|
+
provider = await authenticate(context, options, value => {
|
|
36
|
+
phase = value;
|
|
37
|
+
});
|
|
38
|
+
phase = 'workspace';
|
|
39
|
+
prepareWorkspace(context, progress, options);
|
|
40
|
+
if (!options.managed || context.code === 'claude') {
|
|
41
|
+
phase = 'agent-start';
|
|
42
|
+
session = prepareSession(context, provider, options);
|
|
43
|
+
}
|
|
44
|
+
await auditLog('AUDIT', 'Agent workspace onboarding ready', { correlationId, path: context.target, agent: context.code });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
await auditLog('ERROR', 'Agent workspace onboarding failed', { correlationId, phase });
|
|
47
|
+
throw error;
|
|
48
|
+
} finally {
|
|
49
|
+
progress.release();
|
|
50
|
+
}
|
|
51
|
+
return { session, provider };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function runSetup(options = {}) {
|
|
55
|
+
const input = normalizeSetup(options);
|
|
56
|
+
preflight(input, options);
|
|
57
|
+
const context = await resolveWork(input, options);
|
|
58
|
+
const progress = acquireWorkspace(context);
|
|
59
|
+
const { session, provider } = await prepareOnboarding(context, progress, options);
|
|
60
|
+
logger.log(formatSuccessLine(`Workspace ready: ${context.target}`));
|
|
61
|
+
if (options.managed) {
|
|
62
|
+
const host = options.remoteHost || new URL(context.controller).hostname;
|
|
63
|
+
if (context.code === 'claude') {
|
|
64
|
+
logger.log(`Claude Code running in tmux session ${session}. Attach: tmux attach -t ${session}`);
|
|
65
|
+
logger.log(`Next: Open Claude Code Remote Control on ${host}, or attach to ${session}.`);
|
|
66
|
+
return { path: context.target, code: context.code, session };
|
|
67
|
+
}
|
|
68
|
+
const next = {
|
|
69
|
+
cursor: `In Cursor, connect with Remote SSH to ${host}, then open ${context.target}.`,
|
|
70
|
+
codex: `In Codex, add the SSH project ${context.target} on ${host}.`
|
|
71
|
+
}[context.code];
|
|
72
|
+
logger.log(`Next: ${next} Say "Hi" to begin.`);
|
|
73
|
+
} else {
|
|
74
|
+
attachSession(session, provider, options);
|
|
75
|
+
}
|
|
76
|
+
return { path: context.target, code: context.code, session };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function runManagedOnboarding(options = {}) {
|
|
80
|
+
const managed = await managedSetupOptions(options);
|
|
81
|
+
return runSetup({ ...options, ...managed });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { runSetup, runManagedOnboarding };
|
package/lib/agent-kit/start.js
CHANGED
|
@@ -129,7 +129,7 @@ function buildTmuxSpec(p) {
|
|
|
129
129
|
const remote = p.app === 'claude' && p.remoteControl;
|
|
130
130
|
return {
|
|
131
131
|
bin: 'tmux',
|
|
132
|
-
args: [...mode, spec.bin, ...extra],
|
|
132
|
+
args: [...mode, ...(p.binary ? ['env', p.binary] : [spec.bin]), ...extra],
|
|
133
133
|
session: p.session,
|
|
134
134
|
appBin: spec.bin,
|
|
135
135
|
needTmux: true,
|
|
@@ -287,16 +287,17 @@ function resolveStartContext(options) {
|
|
|
287
287
|
const cwd = resolveGitRoot(options.cwd || process.cwd());
|
|
288
288
|
const session = resolveSessionName(options.name, cwd);
|
|
289
289
|
const remoteControlPrefix = hostUserPrefix(options);
|
|
290
|
-
const remoteControlName = resolveRemoteControlName(remoteControlPrefix, session);
|
|
290
|
+
const remoteControlName = options.remoteControlName || resolveRemoteControlName(remoteControlPrefix, session);
|
|
291
291
|
const remoteControl = options.remoteControl !== false;
|
|
292
292
|
const spec = buildLaunchSpec({
|
|
293
293
|
app, cwd, session, remoteControlPrefix, remoteControlName,
|
|
294
|
-
detach: Boolean(options.detach), remoteControl
|
|
294
|
+
detach: Boolean(options.detach), remoteControl, binary: options.binary
|
|
295
295
|
});
|
|
296
296
|
spec.cwd = cwd;
|
|
297
297
|
const remoteSpec = app === 'codex' && remoteControl
|
|
298
298
|
? buildCodexRemoteSpec(cwd)
|
|
299
299
|
: undefined;
|
|
300
|
+
if (remoteSpec && options.binary) remoteSpec.bin = options.binary;
|
|
300
301
|
return { app, cwd, session, remoteControl, spec, remoteSpec };
|
|
301
302
|
}
|
|
302
303
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
-
const { execFileSync } = require('child_process');
|
|
8
|
+
const { execFileSync, spawnSync } = require('child_process');
|
|
9
9
|
|
|
10
10
|
const LIST_FORMAT = [
|
|
11
11
|
'#{session_name}',
|
|
@@ -66,7 +66,7 @@ function isNoTmuxServer(error) {
|
|
|
66
66
|
.filter(Boolean)
|
|
67
67
|
.map(String)
|
|
68
68
|
.join(' ');
|
|
69
|
-
return /no server running|failed to connect to server/i.test(message);
|
|
69
|
+
return /no server running|failed to connect to server|error connecting to .*\/tmux-\d+\/.*No such file or directory/i.test(message);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/**
|
|
@@ -150,6 +150,22 @@ function runKill(options) {
|
|
|
150
150
|
return { command: 'kill', session, killed: true };
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
function runAttach(options) {
|
|
154
|
+
const exec = options.execFileSync || execFileSync;
|
|
155
|
+
const session = normalizeExactSessionName(options.name);
|
|
156
|
+
requireTmux(exec);
|
|
157
|
+
if (!tmuxSessionExists(session, exec)) {
|
|
158
|
+
throw new Error(`tmux session "${session}" is not running. Run: aifabrix agent-kit list`);
|
|
159
|
+
}
|
|
160
|
+
const insideTmux = Boolean((options.env || process.env).TMUX);
|
|
161
|
+
const args = [insideTmux ? 'switch-client' : 'attach-session', '-t', `=${session}`];
|
|
162
|
+
const result = (options.spawnSync || spawnSync)('tmux', args, { stdio: 'inherit' });
|
|
163
|
+
if (!result || result.error || result.signal || result.status !== 0) {
|
|
164
|
+
throw new Error(`Could not attach to tmux session "${session}".`);
|
|
165
|
+
}
|
|
166
|
+
return { command: 'attach', session, attached: true };
|
|
167
|
+
}
|
|
168
|
+
|
|
153
169
|
module.exports = {
|
|
154
170
|
LIST_FORMAT,
|
|
155
171
|
hasOnPath,
|
|
@@ -159,5 +175,6 @@ module.exports = {
|
|
|
159
175
|
tmuxSessionExists,
|
|
160
176
|
parseSessionLine,
|
|
161
177
|
runList,
|
|
162
|
-
runKill
|
|
178
|
+
runKill,
|
|
179
|
+
runAttach
|
|
163
180
|
};
|
package/lib/api/dev.api.js
CHANGED
|
@@ -198,7 +198,7 @@ async function listUsers(serverUrl, clientCertPem, serverCaPem) {
|
|
|
198
198
|
* @requiresPermission {BuilderServer} Client certificate (X-Client-Cert)
|
|
199
199
|
* @param {string} serverUrl - Builder Server base URL
|
|
200
200
|
* @param {string} clientCertPem - PEM client certificate
|
|
201
|
-
* @param {Object} body - CreateUserDto: developerId, name, email, optional groups (admin | secret-manager | secret-reader | developer | docker)
|
|
201
|
+
* @param {Object} body - CreateUserDto: developerId, name, email, optional groups (admin | secret-manager | secret-reader | developer | docker | integrator)
|
|
202
202
|
* @param {string} [serverCaPem] - Dev root CA PEM
|
|
203
203
|
* @returns {Promise<Object>} UserResponseDto
|
|
204
204
|
*/
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application-token health proof through the public Miso SDK transport.
|
|
3
|
+
* @fileoverview Never reads or returns the application token itself.
|
|
4
|
+
*/
|
|
5
|
+
'use strict';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* POST /api/v1/auth/validate-client-token
|
|
9
|
+
* @requiresPermission {Controller} Client token
|
|
10
|
+
* @param {Object} client Initialized runtime client
|
|
11
|
+
* @param {AbortSignal} signal Probe cancellation
|
|
12
|
+
* @returns {Promise<void>} Resolves only for an authenticated application
|
|
13
|
+
*/
|
|
14
|
+
async function validateMisoApplicationToken(client, signal) {
|
|
15
|
+
const response = await client.requestWithAuthStrategy(
|
|
16
|
+
'POST', '/api/v1/auth/validate-client-token', { methods: ['client-token'] },
|
|
17
|
+
{}, { signal, timeout: 3000, maxRedirects: 0 }
|
|
18
|
+
);
|
|
19
|
+
if (response?.success === false || response?.data?.authenticated !== true) {
|
|
20
|
+
const error = new Error('Miso application authentication failed');
|
|
21
|
+
error.statusCode = 401;
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { validateMisoApplicationToken };
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
* @property {string} aifabrix-secrets - Local filesystem path or https URL for shared secrets (file vs remote API)
|
|
31
31
|
* @property {string} aifabrix-env-config - Env config path
|
|
32
32
|
* @property {string} remote-server - Builder-server base URL
|
|
33
|
-
* @property {string} docker-endpoint - Docker API endpoint
|
|
33
|
+
* @property {string} [docker-endpoint] - Docker API endpoint; omit for SSH-only/integrator users
|
|
34
34
|
* @property {string} [developer-root-domain] - App hostname root (e.g. aifabrix.dev → dev01.aifabrix.dev)
|
|
35
35
|
* @property {boolean} [docker-tls-skip-verify] - When true, Docker uses DOCKER_TLS_VERIFY=0 even if ca.pem exists (client mTLS unchanged)
|
|
36
36
|
* @property {string} sync-ssh-user - SSH user for Mutagen
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
* @property {string} createdAt - ISO 8601
|
|
47
47
|
* @property {boolean} certificateIssued - Whether cert was issued
|
|
48
48
|
* @property {string} [certificateValidNotAfter] - Cert validity end (optional)
|
|
49
|
-
* @property {string[]} groups - Access groups (admin, secret-manager, secret-reader, developer, docker)
|
|
49
|
+
* @property {string[]} groups - Access groups (admin, secret-manager, secret-reader, developer, docker, integrator)
|
|
50
50
|
*/
|
|
51
51
|
|
|
52
52
|
/**
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
* @property {string} developerId - Unique developer ID (numeric string)
|
|
56
56
|
* @property {string} name - Display name
|
|
57
57
|
* @property {string} email - Email
|
|
58
|
-
* @property {string[]} [groups] - Default [developer]; tokens: admin, secret-manager, secret-reader, developer, docker
|
|
58
|
+
* @property {string[]} [groups] - Default [developer]; tokens: admin, secret-manager, secret-reader, developer, docker, integrator
|
|
59
59
|
*/
|
|
60
60
|
|
|
61
61
|
/**
|
package/lib/app/deploy.js
CHANGED
|
@@ -18,7 +18,6 @@ const {
|
|
|
18
18
|
|
|
19
19
|
const fs = require('fs').promises;
|
|
20
20
|
const chalk = require('chalk');
|
|
21
|
-
const pushUtils = require('../deployment/push');
|
|
22
21
|
|
|
23
22
|
const SEP = chalk.gray('────────────────────────────────────────');
|
|
24
23
|
const logger = require('../utils/logger');
|
|
@@ -56,106 +55,6 @@ function validateAppName(appName) {
|
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
/**
|
|
60
|
-
* Validates push prerequisites
|
|
61
|
-
* @async
|
|
62
|
-
* @function validatePushPrerequisites
|
|
63
|
-
* @param {string} appName - Application name
|
|
64
|
-
* @param {string} registry - Registry URL
|
|
65
|
-
* @throws {Error} If prerequisites are not met
|
|
66
|
-
*/
|
|
67
|
-
async function validatePushPrerequisites(appName, registry) {
|
|
68
|
-
if (!pushUtils.validateRegistryURL(registry)) {
|
|
69
|
-
throw new Error(`Invalid registry URL format: ${registry}. Expected format: *.azurecr.io`);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (!await pushUtils.checkLocalImageExists(appName, 'latest')) {
|
|
73
|
-
throw new Error(`Docker image ${appName}:latest not found locally.\nRun 'aifabrix build ${appName}' first`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (!await pushUtils.checkAzureCLIInstalled()) {
|
|
77
|
-
throw new Error('Azure CLI is not installed. Install from: https://docs.microsoft.com/cli/azure/install-azure-cli');
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Executes push operations
|
|
83
|
-
* @async
|
|
84
|
-
* @function executePush
|
|
85
|
-
* @param {string} appName - Application name
|
|
86
|
-
* @param {string} registry - Registry URL
|
|
87
|
-
* @param {string[]} tags - Tags to push
|
|
88
|
-
* @throws {Error} If push fails
|
|
89
|
-
*/
|
|
90
|
-
async function executePush(appName, registry, tags) {
|
|
91
|
-
if (await pushUtils.checkACRAuthentication(registry)) {
|
|
92
|
-
logger.log(formatSuccessLine(`Already authenticated with ${registry}`));
|
|
93
|
-
} else {
|
|
94
|
-
await pushUtils.authenticateACR(registry);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
await Promise.all(tags.map(async(tag) => {
|
|
98
|
-
await pushUtils.tagImage(`${appName}:latest`, `${registry}/${appName}:${tag}`);
|
|
99
|
-
await pushUtils.pushImage(`${registry}/${appName}:${tag}`, registry);
|
|
100
|
-
}));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Verifies push result
|
|
105
|
-
* @function verifyPushResult
|
|
106
|
-
* @param {string[]} tags - Tags that were pushed
|
|
107
|
-
* @param {string} registry - Registry URL
|
|
108
|
-
* @param {string} appName - Application name
|
|
109
|
-
*/
|
|
110
|
-
function verifyPushResult(tags, registry, appName) {
|
|
111
|
-
logger.log(formatSuccessParagraph(`Successfully pushed ${tags.length} tag(s) to ${registry}`));
|
|
112
|
-
logger.log(chalk.gray(`Image: ${registry}/${appName}:*`));
|
|
113
|
-
logger.log(chalk.gray(`Tags: ${tags.join(', ')}`));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Pushes application image to Azure Container Registry
|
|
118
|
-
* @async
|
|
119
|
-
* @function pushApp
|
|
120
|
-
* @param {string} appName - Name of the application
|
|
121
|
-
* @param {Object} options - Push options (registry, tag)
|
|
122
|
-
* @returns {Promise<void>} Resolves when push is complete
|
|
123
|
-
*/
|
|
124
|
-
async function pushApp(appName, options = {}) {
|
|
125
|
-
try {
|
|
126
|
-
validateAppName(appName);
|
|
127
|
-
|
|
128
|
-
const { getBuilderPath, resolveApplicationConfigPath } = require('../utils/paths');
|
|
129
|
-
const { loadConfigFile } = require('../utils/config-format');
|
|
130
|
-
const builderPath = getBuilderPath(appName);
|
|
131
|
-
let config;
|
|
132
|
-
try {
|
|
133
|
-
const configPath = resolveApplicationConfigPath(builderPath);
|
|
134
|
-
config = loadConfigFile(configPath);
|
|
135
|
-
} catch (error) {
|
|
136
|
-
throw new Error(`Failed to load configuration: ${error.message}\nRun 'aifabrix create ${appName}' first`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const registry = options.registry || config.image?.registry;
|
|
140
|
-
if (!registry) {
|
|
141
|
-
throw new Error('Registry URL is required. Provide via --registry flag or configure in application.yaml under image.registry');
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (!/^[^.]+\.azurecr\.io$/.test(registry)) {
|
|
145
|
-
throw new Error(`Invalid ACR URL format: ${registry}. Expected format: *.azurecr.io`);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const tags = options.tag ? options.tag.split(',').map(t => t.trim()) : ['latest'];
|
|
149
|
-
|
|
150
|
-
await validatePushPrerequisites(appName, registry);
|
|
151
|
-
await executePush(appName, registry, tags);
|
|
152
|
-
verifyPushResult(tags, registry, appName);
|
|
153
|
-
|
|
154
|
-
} catch (error) {
|
|
155
|
-
throw new Error(`Failed to push application: ${error.message}`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
58
|
/**
|
|
160
59
|
* Generates and validates deployment manifest
|
|
161
60
|
* @async
|
|
@@ -492,7 +391,6 @@ async function deployApp(appName, options = {}) {
|
|
|
492
391
|
}
|
|
493
392
|
|
|
494
393
|
module.exports = {
|
|
495
|
-
pushApp,
|
|
496
394
|
deployApp,
|
|
497
395
|
validateAppName,
|
|
498
396
|
validateImageIsPullable
|