@aifabrix/builder 2.55.0 → 2.55.2
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/host-approve.js +48 -6
- package/lib/agent-kit/init.js +2 -2
- package/lib/agent-kit/remote-prompts.js +46 -13
- package/lib/agent-kit/run.js +1 -1
- package/lib/agent-kit/start.js +310 -0
- package/lib/app/deploy.js +7 -4
- package/lib/app/offer-create-from-template.js +184 -0
- package/lib/cli/setup-app.js +8 -1
- package/lib/cli/setup-auth.js +11 -2
- package/lib/commands/agent-kit-help.js +74 -4
- package/lib/commands/agent-kit.js +83 -12
- package/lib/commands/auth-status-user.js +99 -0
- package/lib/commands/auth-status.js +14 -4
- package/lib/commands/datasource-load-export-cli.js +7 -2
- package/lib/commands/datasource-test-trust-cli.js +5 -1
- package/lib/commands/governance-command-action.js +5 -4
- package/lib/commands/identity-membership-role-cache.js +6 -1
- package/lib/commands/lifecycle-command-action.js +16 -15
- package/lib/commands/login-credentials.js +8 -3
- package/lib/commands/login-device.js +14 -5
- package/lib/commands/login.js +14 -1
- package/lib/commands/logout.js +50 -15
- package/lib/commands/role-assistant.js +24 -12
- package/lib/commands/verify-governance-command-action.js +9 -7
- package/lib/commands/verify-operations-command-action.js +9 -8
- package/lib/commands/verify-trust-command-action.js +6 -5
- package/lib/external-system/download.js +1 -2
- package/lib/generator/helpers.js +12 -1
- package/lib/integration-definition/apply-static-enterprise.js +5 -2
- package/lib/role-assistant/knowledge-sync-settings-push.js +172 -0
- package/lib/role-assistant/knowledge-sync.js +9 -126
- package/lib/role-assistant/test-cases-evaluate.js +12 -1
- package/lib/role-assistant/test-cases-evidence.js +44 -5
- package/lib/role-assistant/test-cases-lifecycle.js +9 -3
- package/lib/role-assistant/test-runner.js +3 -0
- package/lib/schema/infra-parameter.schema.json +51 -3
- package/lib/schema/infra.parameter.yaml +476 -40
- package/lib/schema/type/document-storage.json +1 -1
- package/lib/utils/api-required-permissions.js +271 -0
- package/lib/utils/api.js +16 -13
- package/lib/utils/cli-json-exit.js +41 -0
- package/lib/utils/cli-utils.js +23 -5
- package/lib/utils/controller-url.js +78 -4
- package/lib/utils/deployment-errors.js +15 -0
- package/lib/utils/deployment-validation-helpers.js +4 -1
- package/lib/utils/device-code.js +31 -10
- package/lib/utils/error-formatters/error-parser.js +16 -1
- package/lib/utils/error-formatters/permission-errors.js +19 -5
- package/lib/utils/keycloak-realm-ready.js +5 -2
- package/lib/utils/logger.js +1 -0
- package/lib/utils/paths.js +26 -5
- package/lib/utils/secrets-helpers.js +2 -2
- package/lib/utils/write-stream-and-wait.js +63 -0
- package/package.json +20 -6
- package/templates/agent-kit/agent-kit.yaml +1 -1
- package/templates/agent-kit/instructions/AGENTKIT.md +25 -4
- package/templates/agent-kit/instructions/root.AGENTS.md +1 -1
- package/templates/agent-kit/skills/aifabrix-connected-system/SKILL.md +2 -1
- package/templates/agent-kit/skills/aifabrix-connected-system/references/delivery-gates.md +1 -1
- package/templates/agent-kit/skills/aifabrix-plan/SKILL.md +2 -1
- package/templates/agent-kit/skills/aifabrix-prove/SKILL.md +1 -0
- package/templates/agent-kit/skills/aifabrix-role-assistant/SKILL.md +1 -0
- package/templates/agent-kit/skills/shared/hosts.md +13 -26
- package/templates/agent-kit/skills/shared/login.md +16 -0
- package/templates/agent-kit/workspace/README.md +1 -1
- package/templates/applications/dataplane/application.yaml +1 -2
- package/templates/applications/dataplane/env.template +5 -5
- package/templates/applications/miso-controller/application.yaml +21 -2
- package/templates/applications/miso-controller/env.template +89 -17
- package/templates/applications/miso-controller/rbac.yaml +5 -0
|
@@ -10,9 +10,50 @@ const fs = require('../internal/fs-real-sync');
|
|
|
10
10
|
|
|
11
11
|
const CLAUDE_REL = '.claude/settings.local.json';
|
|
12
12
|
const CODEX_REL = '.codex/config.toml';
|
|
13
|
+
const APPROVE_HOSTS = ['claude', 'codex'];
|
|
13
14
|
const IGNORE_COMMENT = '# Host auto-approve (machine-local; aifabrix agent-kit)';
|
|
14
15
|
const IGNORE_LINES = [CLAUDE_REL, CODEX_REL];
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* --approve-all [host]: true/'all' = every selected host; claude|codex = one tool.
|
|
19
|
+
* @param {unknown} value
|
|
20
|
+
* @returns {{ requested: boolean, target: string|null }}
|
|
21
|
+
*/
|
|
22
|
+
function parseApproveAll(value) {
|
|
23
|
+
if (value === undefined || value === false || value === '') {
|
|
24
|
+
return { requested: false, target: null };
|
|
25
|
+
}
|
|
26
|
+
if (value === true || value === 'all') {
|
|
27
|
+
return { requested: true, target: 'all' };
|
|
28
|
+
}
|
|
29
|
+
const target = String(value);
|
|
30
|
+
if (APPROVE_HOSTS.includes(target)) {
|
|
31
|
+
return { requested: true, target };
|
|
32
|
+
}
|
|
33
|
+
throw new Error('Invalid --approve-all. Use all, claude, or codex.');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Subset of managed hosts that receive auto-approve files.
|
|
38
|
+
* @param {string[]} [managedHosts]
|
|
39
|
+
* @param {unknown} approveAll
|
|
40
|
+
* @returns {string[]}
|
|
41
|
+
*/
|
|
42
|
+
function resolveApproveHosts(managedHosts, approveAll) {
|
|
43
|
+
const parsed = parseApproveAll(approveAll);
|
|
44
|
+
if (!parsed.requested) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
const eligible = (managedHosts || []).filter((h) => APPROVE_HOSTS.includes(h));
|
|
48
|
+
if (parsed.target === 'all') {
|
|
49
|
+
return eligible;
|
|
50
|
+
}
|
|
51
|
+
if (!eligible.includes(parsed.target)) {
|
|
52
|
+
throw new Error(`--approve-all ${parsed.target} requires --host ${parsed.target} or --host all.`);
|
|
53
|
+
}
|
|
54
|
+
return [parsed.target];
|
|
55
|
+
}
|
|
56
|
+
|
|
16
57
|
/**
|
|
17
58
|
* This workspace for writes. Read(~/**) is home-anchored. Bash(aifabrix *)
|
|
18
59
|
* allows the delivery CLI without granting every shell command.
|
|
@@ -101,15 +142,13 @@ function listApproveWrites(options) {
|
|
|
101
142
|
* @returns {{ requested: boolean, files: { path: string, action: string }[] }}
|
|
102
143
|
*/
|
|
103
144
|
function writeApproveAllFiles(options) {
|
|
104
|
-
|
|
145
|
+
const parsed = parseApproveAll(options.approveAll);
|
|
146
|
+
if (!parsed.requested) {
|
|
105
147
|
return { requested: false, files: [] };
|
|
106
148
|
}
|
|
149
|
+
const hosts = resolveApproveHosts(options.hosts, options.approveAll);
|
|
107
150
|
const fsApi = options.fs || fs;
|
|
108
|
-
const ctx = {
|
|
109
|
-
repoRoot: options.repoRoot,
|
|
110
|
-
hosts: options.hosts || [],
|
|
111
|
-
fsApi
|
|
112
|
-
};
|
|
151
|
+
const ctx = { repoRoot: options.repoRoot, hosts, fsApi };
|
|
113
152
|
const dryRun = Boolean(options.dryRun);
|
|
114
153
|
const force = Boolean(options.force);
|
|
115
154
|
const files = listApproveWrites(ctx).map((row) =>
|
|
@@ -124,6 +163,9 @@ function writeApproveAllFiles(options) {
|
|
|
124
163
|
module.exports = {
|
|
125
164
|
CLAUDE_REL,
|
|
126
165
|
CODEX_REL,
|
|
166
|
+
APPROVE_HOSTS,
|
|
167
|
+
parseApproveAll,
|
|
168
|
+
resolveApproveHosts,
|
|
127
169
|
buildClaudeSettings,
|
|
128
170
|
buildCodexConfig,
|
|
129
171
|
writeApproveAllFiles
|
package/lib/agent-kit/init.js
CHANGED
|
@@ -152,7 +152,7 @@ function runInit(options) {
|
|
|
152
152
|
dryRun,
|
|
153
153
|
targetAbs,
|
|
154
154
|
host: options.host || 'all',
|
|
155
|
-
approveAll:
|
|
155
|
+
approveAll: options.approveAll,
|
|
156
156
|
force: Boolean(options.force),
|
|
157
157
|
gitResult,
|
|
158
158
|
phases
|
|
@@ -195,7 +195,7 @@ function installKitPhase(p) {
|
|
|
195
195
|
const kit = runInstall({
|
|
196
196
|
cwd: p.targetAbs,
|
|
197
197
|
host: p.host,
|
|
198
|
-
approveAll:
|
|
198
|
+
approveAll: p.approveAll,
|
|
199
199
|
force: Boolean(p.force)
|
|
200
200
|
});
|
|
201
201
|
p.phases.push({ phase: 'agent-kit', state: kit.state });
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
const { execFileSync } = require('child_process');
|
|
9
9
|
const { ALL_HOSTS, HOST_LABELS, normalizeHosts } = require('./hosts');
|
|
10
|
+
const { APPROVE_HOSTS, parseApproveAll } = require('./host-approve');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* @param {{ execFileSync?: Function }} [options]
|
|
@@ -198,18 +199,57 @@ async function enrichInitRemoteOptions(options, deps = {}) {
|
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
/**
|
|
201
|
-
*
|
|
202
|
+
* @param {object} inquirer
|
|
203
|
+
* @param {string[]} eligible
|
|
204
|
+
* @returns {Promise<true|false|string>}
|
|
205
|
+
*/
|
|
206
|
+
async function askApproveAllChoice(inquirer, eligible) {
|
|
207
|
+
if (eligible.length === 1) {
|
|
208
|
+
const host = eligible[0];
|
|
209
|
+
const { approveAll } = await inquirer.prompt([
|
|
210
|
+
{
|
|
211
|
+
type: 'confirm',
|
|
212
|
+
name: 'approveAll',
|
|
213
|
+
message: `Auto-approve ${HOST_LABELS[host] || host} tool permissions in this workspace?`,
|
|
214
|
+
default: true
|
|
215
|
+
}
|
|
216
|
+
]);
|
|
217
|
+
return Boolean(approveAll);
|
|
218
|
+
}
|
|
219
|
+
const { approveAll } = await inquirer.prompt([
|
|
220
|
+
{
|
|
221
|
+
type: 'list',
|
|
222
|
+
name: 'approveAll',
|
|
223
|
+
message: 'Which coding host should auto-approve tools in this workspace?',
|
|
224
|
+
default: 'all',
|
|
225
|
+
choices: [
|
|
226
|
+
{ name: 'Both Claude Code and Codex', value: 'all' },
|
|
227
|
+
{ name: 'Claude Code only', value: 'claude' },
|
|
228
|
+
{ name: 'Codex only', value: 'codex' },
|
|
229
|
+
{ name: 'Skip (the coding app will ask each time)', value: 'skip' }
|
|
230
|
+
]
|
|
231
|
+
}
|
|
232
|
+
]);
|
|
233
|
+
return approveAll === 'skip' ? false : approveAll;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* TTY asks; --approve-all [host] / --yes / non-TTY auto-approve; --skip-approve-all skips.
|
|
202
238
|
* @param {object} options
|
|
203
239
|
* @param {{ interactive?: boolean, inquirer?: object, ask?: boolean }} [deps]
|
|
204
240
|
* @returns {Promise<object>}
|
|
205
241
|
*/
|
|
206
242
|
async function enrichApproveAllOptions(options, deps = {}) {
|
|
207
243
|
const hosts = normalizeHosts(options.host);
|
|
208
|
-
const
|
|
209
|
-
if (!
|
|
244
|
+
const eligible = hosts.filter((h) => APPROVE_HOSTS.includes(h));
|
|
245
|
+
if (!eligible.length || options.skipApproveAll) {
|
|
210
246
|
return { ...options, approveAll: false };
|
|
211
247
|
}
|
|
212
|
-
if (options.approveAll
|
|
248
|
+
if (options.approveAll) {
|
|
249
|
+
parseApproveAll(options.approveAll);
|
|
250
|
+
return options;
|
|
251
|
+
}
|
|
252
|
+
if (options.yes) {
|
|
213
253
|
return { ...options, approveAll: true };
|
|
214
254
|
}
|
|
215
255
|
if (deps.ask === false) {
|
|
@@ -219,15 +259,8 @@ async function enrichApproveAllOptions(options, deps = {}) {
|
|
|
219
259
|
return { ...options, approveAll: true };
|
|
220
260
|
}
|
|
221
261
|
const inquirer = deps.inquirer || require('inquirer');
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
type: 'confirm',
|
|
225
|
-
name: 'approveAll',
|
|
226
|
-
message: 'Auto-approve all Claude Code and Codex tool permissions in this workspace?',
|
|
227
|
-
default: true
|
|
228
|
-
}
|
|
229
|
-
]);
|
|
230
|
-
return { ...options, approveAll: Boolean(approveAll) };
|
|
262
|
+
const approveAll = await askApproveAllChoice(inquirer, eligible);
|
|
263
|
+
return { ...options, approveAll };
|
|
231
264
|
}
|
|
232
265
|
|
|
233
266
|
module.exports = {
|
package/lib/agent-kit/run.js
CHANGED
|
@@ -46,7 +46,7 @@ function runManagedCommand(options, mode) {
|
|
|
46
46
|
const approvals = writeApproveAllFiles({
|
|
47
47
|
repoRoot,
|
|
48
48
|
hosts,
|
|
49
|
-
approveAll:
|
|
49
|
+
approveAll: options.approveAll,
|
|
50
50
|
dryRun: Boolean(options.dryRun),
|
|
51
51
|
force: Boolean(options.force)
|
|
52
52
|
});
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Start Claude Code or Codex in a named tmux session.
|
|
3
|
+
* @fileoverview Does not install Agent Kit skills
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { execFileSync, spawnSync } = require('child_process');
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const { resolveGitRoot } = require('./repo-guard');
|
|
13
|
+
const { writeApproveAllFiles } = require('./host-approve');
|
|
14
|
+
|
|
15
|
+
const START_APPS = {
|
|
16
|
+
claude: { bin: 'claude' },
|
|
17
|
+
codex: { bin: 'codex' }
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {unknown} value
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
function normalizeStartApp(value) {
|
|
25
|
+
const app = String(value || '').trim();
|
|
26
|
+
if (!START_APPS[app]) {
|
|
27
|
+
throw new Error('Invalid app. Use claude or codex.');
|
|
28
|
+
}
|
|
29
|
+
return app;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Session names may not contain colons or periods.
|
|
34
|
+
* @param {unknown} value
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
function sanitizeSessionName(value) {
|
|
38
|
+
const name = String(value || '')
|
|
39
|
+
.trim()
|
|
40
|
+
.replace(/[.:\s]+/g, '-')
|
|
41
|
+
.replace(/-+/g, '-')
|
|
42
|
+
.replace(/^-|-$/g, '');
|
|
43
|
+
if (!name) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
'Invalid --name. Use the last folder of this git workspace, for example aifabrix-docs.'
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return name;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Default display name is the last folder of the git workspace.
|
|
53
|
+
* @param {unknown} explicit
|
|
54
|
+
* @param {string} cwd
|
|
55
|
+
* @returns {string}
|
|
56
|
+
*/
|
|
57
|
+
function resolveSessionName(explicit, cwd) {
|
|
58
|
+
const raw = explicit === undefined || explicit === null || explicit === ''
|
|
59
|
+
? path.basename(cwd)
|
|
60
|
+
: explicit;
|
|
61
|
+
return sanitizeSessionName(raw);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {object} options
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
function currentUsername(options) {
|
|
69
|
+
if (options && options.username) {
|
|
70
|
+
return String(options.username);
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return os.userInfo().username;
|
|
74
|
+
} catch {
|
|
75
|
+
return process.env.USER || process.env.LOGNAME || 'user';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Hostname + login, e.g. builder01-dev03.
|
|
81
|
+
* @param {object} [options]
|
|
82
|
+
* @returns {string}
|
|
83
|
+
*/
|
|
84
|
+
function hostUserPrefix(options) {
|
|
85
|
+
const hostRaw = options && options.hostname ? options.hostname : os.hostname();
|
|
86
|
+
const host = String(hostRaw).split('.')[0];
|
|
87
|
+
return sanitizeSessionName(`${host}-${currentUsername(options || {})}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Remote Control session: builder01-dev03-aifabrix-docs.
|
|
92
|
+
* @param {string} prefix
|
|
93
|
+
* @param {string} session
|
|
94
|
+
* @returns {string}
|
|
95
|
+
*/
|
|
96
|
+
function resolveRemoteControlName(prefix, session) {
|
|
97
|
+
return sanitizeSessionName(`${prefix}-${session}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {string} bin
|
|
102
|
+
* @param {Function} exec
|
|
103
|
+
* @returns {boolean}
|
|
104
|
+
*/
|
|
105
|
+
function hasOnPath(bin, exec) {
|
|
106
|
+
try {
|
|
107
|
+
exec('which', [bin], {
|
|
108
|
+
encoding: 'utf8',
|
|
109
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
110
|
+
});
|
|
111
|
+
return true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @param {object} options
|
|
119
|
+
* @returns {void}
|
|
120
|
+
*/
|
|
121
|
+
function validateStartOptions(options) {
|
|
122
|
+
const app = String(options.app || '').trim();
|
|
123
|
+
if (options.remoteControl && app !== 'claude') {
|
|
124
|
+
throw new Error('--remote-control is only valid with claude.');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Claude argv after the binary (Remote Control names, not Claude --tmux).
|
|
130
|
+
* @param {object} p
|
|
131
|
+
* @returns {string[]}
|
|
132
|
+
*/
|
|
133
|
+
function claudeInnerArgs(p) {
|
|
134
|
+
const args = ['--name', p.session];
|
|
135
|
+
if (!p.remoteControl) {
|
|
136
|
+
return args;
|
|
137
|
+
}
|
|
138
|
+
return args.concat([
|
|
139
|
+
'--remote-control',
|
|
140
|
+
p.remoteControlName,
|
|
141
|
+
'--remote-control-session-name-prefix',
|
|
142
|
+
p.remoteControlPrefix
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @param {object} p
|
|
148
|
+
* @returns {object}
|
|
149
|
+
*/
|
|
150
|
+
function buildTmuxSpec(p) {
|
|
151
|
+
const spec = START_APPS[p.app];
|
|
152
|
+
const extra = p.app === 'claude' ? claudeInnerArgs(p) : [];
|
|
153
|
+
const mode = p.detach
|
|
154
|
+
? ['new-session', '-d', '-s', p.session, '-c', p.cwd]
|
|
155
|
+
: ['new-session', '-A', '-s', p.session, '-c', p.cwd];
|
|
156
|
+
const remote = p.app === 'claude' && p.remoteControl;
|
|
157
|
+
return {
|
|
158
|
+
bin: 'tmux',
|
|
159
|
+
args: [...mode, spec.bin, ...extra],
|
|
160
|
+
session: p.session,
|
|
161
|
+
appBin: spec.bin,
|
|
162
|
+
needTmux: true,
|
|
163
|
+
remoteControlName: remote ? p.remoteControlName : undefined,
|
|
164
|
+
remoteControlPrefix: remote ? p.remoteControlPrefix : undefined
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param {object} p
|
|
170
|
+
* @returns {object}
|
|
171
|
+
*/
|
|
172
|
+
function buildLaunchSpec(p) {
|
|
173
|
+
return buildTmuxSpec(p);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {{ appBin: string, needTmux?: boolean }} spec
|
|
178
|
+
* @param {Function} exec
|
|
179
|
+
* @returns {void}
|
|
180
|
+
*/
|
|
181
|
+
function requireBinaries(spec, exec) {
|
|
182
|
+
if (spec.needTmux && !hasOnPath('tmux', exec)) {
|
|
183
|
+
throw new Error('tmux is not on PATH. Install tmux, then retry.');
|
|
184
|
+
}
|
|
185
|
+
if (hasOnPath(spec.appBin, exec)) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (spec.appBin === 'claude') {
|
|
189
|
+
throw new Error(
|
|
190
|
+
'claude is not on PATH. Install with: curl -fsSL https://claude.ai/install.sh | bash'
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
throw new Error('codex is not on PATH. Install with: npm install -g @openai/codex');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {object} options
|
|
198
|
+
* @returns {boolean}
|
|
199
|
+
*/
|
|
200
|
+
function shouldExecute(options) {
|
|
201
|
+
if (options.dryRun || options.json) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
if (options.detach) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (!process.stdout.isTTY) {
|
|
208
|
+
throw new Error('agent-kit start needs a TTY to attach. Pass --detach or --dry-run.');
|
|
209
|
+
}
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* @param {object} options
|
|
215
|
+
* @param {{ bin: string, args: string[], cwd: string }} spec
|
|
216
|
+
* @returns {unknown}
|
|
217
|
+
*/
|
|
218
|
+
function spawnLaunch(options, spec) {
|
|
219
|
+
const spawn = options.spawnSync || spawnSync;
|
|
220
|
+
const result = spawn(spec.bin, spec.args, { stdio: 'inherit', cwd: spec.cwd });
|
|
221
|
+
const status = result && typeof result.status === 'number' ? result.status : 0;
|
|
222
|
+
if (status !== 0) {
|
|
223
|
+
throw new Error(`${spec.bin} exited with status ${status}`);
|
|
224
|
+
}
|
|
225
|
+
return status;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* @param {object} spec
|
|
230
|
+
* @param {object} extra
|
|
231
|
+
* @returns {object}
|
|
232
|
+
*/
|
|
233
|
+
function toStartResult(spec, extra) {
|
|
234
|
+
const launch = [spec.bin, ...spec.args];
|
|
235
|
+
return {
|
|
236
|
+
command: 'start',
|
|
237
|
+
app: extra.app,
|
|
238
|
+
session: spec.session,
|
|
239
|
+
remoteControlName: spec.remoteControlName,
|
|
240
|
+
remoteControlPrefix: spec.remoteControlPrefix,
|
|
241
|
+
path: extra.cwd,
|
|
242
|
+
launch,
|
|
243
|
+
tmux: spec.needTmux ? launch : undefined,
|
|
244
|
+
approvals: extra.approvals,
|
|
245
|
+
dryRun: extra.dryRun,
|
|
246
|
+
detach: extra.detach,
|
|
247
|
+
executed: extra.executed
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param {object} options
|
|
253
|
+
* @returns {object}
|
|
254
|
+
*/
|
|
255
|
+
function runStart(options) {
|
|
256
|
+
validateStartOptions(options);
|
|
257
|
+
const app = normalizeStartApp(options.app);
|
|
258
|
+
const cwd = resolveGitRoot(options.cwd || process.cwd());
|
|
259
|
+
const session = resolveSessionName(options.name, cwd);
|
|
260
|
+
const remoteControlPrefix = hostUserPrefix(options);
|
|
261
|
+
const remoteControlName = resolveRemoteControlName(remoteControlPrefix, session);
|
|
262
|
+
const exec = options.execFileSync || execFileSync;
|
|
263
|
+
const spec = buildLaunchSpec({
|
|
264
|
+
app,
|
|
265
|
+
cwd,
|
|
266
|
+
session,
|
|
267
|
+
remoteControlPrefix,
|
|
268
|
+
remoteControlName,
|
|
269
|
+
detach: Boolean(options.detach),
|
|
270
|
+
remoteControl: Boolean(options.remoteControl)
|
|
271
|
+
});
|
|
272
|
+
spec.cwd = cwd;
|
|
273
|
+
const approveAll = !options.skipApproveAll && Boolean(options.approveAll || options.yes);
|
|
274
|
+
const approvals = writeApproveAllFiles({
|
|
275
|
+
repoRoot: cwd,
|
|
276
|
+
hosts: [app],
|
|
277
|
+
approveAll,
|
|
278
|
+
dryRun: Boolean(options.dryRun)
|
|
279
|
+
});
|
|
280
|
+
if (!options.dryRun) {
|
|
281
|
+
requireBinaries(spec, exec);
|
|
282
|
+
}
|
|
283
|
+
const execute = shouldExecute(options);
|
|
284
|
+
if (execute && !options.json) {
|
|
285
|
+
logger.log(`Starting ${app} session ${spec.session}`);
|
|
286
|
+
}
|
|
287
|
+
if (execute) {
|
|
288
|
+
spawnLaunch(options, spec);
|
|
289
|
+
}
|
|
290
|
+
return toStartResult(spec, {
|
|
291
|
+
app,
|
|
292
|
+
cwd,
|
|
293
|
+
approvals,
|
|
294
|
+
dryRun: Boolean(options.dryRun),
|
|
295
|
+
detach: Boolean(options.detach),
|
|
296
|
+
executed: execute
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
module.exports = {
|
|
301
|
+
START_APPS,
|
|
302
|
+
normalizeStartApp,
|
|
303
|
+
sanitizeSessionName,
|
|
304
|
+
resolveSessionName,
|
|
305
|
+
hostUserPrefix,
|
|
306
|
+
resolveRemoteControlName,
|
|
307
|
+
claudeInnerArgs,
|
|
308
|
+
buildTmuxSpec,
|
|
309
|
+
runStart
|
|
310
|
+
};
|
package/lib/app/deploy.js
CHANGED
|
@@ -23,6 +23,7 @@ const pushUtils = require('../deployment/push');
|
|
|
23
23
|
const SEP = chalk.gray('────────────────────────────────────────');
|
|
24
24
|
const logger = require('../utils/logger');
|
|
25
25
|
const { detectAppType, getBuilderPath, getIntegrationPath } = require('../utils/paths');
|
|
26
|
+
const { offerCreateFromTemplateIfMissing } = require('./offer-create-from-template');
|
|
26
27
|
const { checkApplicationExists } = require('../utils/app-existence');
|
|
27
28
|
const { loadDeploymentConfig } = require('./deploy-config');
|
|
28
29
|
const { logOfflinePathWhenType } = require('../utils/cli-utils');
|
|
@@ -462,7 +463,7 @@ async function executeDeployPipeline(appName, options) {
|
|
|
462
463
|
* @param {boolean} [options.poll] - Poll for deployment status
|
|
463
464
|
* @param {number} [options.pollInterval] - Polling interval in milliseconds
|
|
464
465
|
* @param {number} [options.pollMaxAttempts] - Max polling attempts
|
|
465
|
-
* @returns {Promise<{ result: Object, usedExternalDeploy: boolean }>} Deployment result and whether external deploy was used
|
|
466
|
+
* @returns {Promise<{ result: Object, usedExternalDeploy: boolean, appKey: string }>} Deployment result and whether external deploy was used
|
|
466
467
|
* @throws {Error} If deployment fails
|
|
467
468
|
*
|
|
468
469
|
* Controller and environment come from config.yaml (set via aifabrix login or aifabrix auth config).
|
|
@@ -473,16 +474,18 @@ async function executeDeployPipeline(appName, options) {
|
|
|
473
474
|
async function deployApp(appName, options = {}) {
|
|
474
475
|
let controllerUrl = null;
|
|
475
476
|
let usedExternalDeploy = false;
|
|
477
|
+
let resolvedName = appName;
|
|
476
478
|
|
|
477
479
|
try {
|
|
478
480
|
assertDeployNotProtectionScope(appName);
|
|
479
481
|
validateAppName(appName);
|
|
480
|
-
|
|
482
|
+
resolvedName = await offerCreateFromTemplateIfMissing(appName, options);
|
|
483
|
+
const pipeline = await executeDeployPipeline(resolvedName, options);
|
|
481
484
|
controllerUrl = pipeline.controllerUrl ?? null;
|
|
482
485
|
usedExternalDeploy = pipeline.usedExternalDeploy;
|
|
483
|
-
return { result: pipeline.result, usedExternalDeploy };
|
|
486
|
+
return { result: pipeline.result, usedExternalDeploy, appKey: resolvedName };
|
|
484
487
|
} catch (error) {
|
|
485
|
-
await handleDeploymentError(error,
|
|
488
|
+
await handleDeploymentError(error, resolvedName, controllerUrl, usedExternalDeploy);
|
|
486
489
|
}
|
|
487
490
|
}
|
|
488
491
|
|