@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
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Offer to materialize a missing app from shipped application templates.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Interactive create-from-template before deploy when the app is not on disk
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const logger = require('../utils/logger');
|
|
12
|
+
const { metadata } = require('../utils/cli-test-layout-chalk');
|
|
13
|
+
const { listAvailableTemplates } = require('../validation/template');
|
|
14
|
+
const { findExistingAppType } = require('../utils/paths');
|
|
15
|
+
const { ensureAppFromTemplate } = require('../commands/up-common');
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param {Object} [options]
|
|
19
|
+
* @returns {boolean}
|
|
20
|
+
*/
|
|
21
|
+
function assumeYesCreate(options = {}) {
|
|
22
|
+
return options.yes === true || options.assumeYes === true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {Object} [deps]
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
function isInteractiveCreate(deps = {}) {
|
|
30
|
+
if (process.env.CI === 'true' || process.env.CI === '1') {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (typeof deps.isTty === 'function') {
|
|
34
|
+
return Boolean(deps.isTty());
|
|
35
|
+
}
|
|
36
|
+
return Boolean(process.stdin && process.stdin.isTTY);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* @param {string[]} templates
|
|
41
|
+
* @returns {string}
|
|
42
|
+
*/
|
|
43
|
+
function formatTemplateNames(templates) {
|
|
44
|
+
return templates.join(', ');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @param {string} appName
|
|
49
|
+
* @param {string[]} templates
|
|
50
|
+
* @returns {Error}
|
|
51
|
+
*/
|
|
52
|
+
function buildMissingAppError(appName, templates) {
|
|
53
|
+
const listed = templates.length > 0
|
|
54
|
+
? `Shipped templates: ${formatTemplateNames(templates)}.`
|
|
55
|
+
: 'No shipped application templates were found.';
|
|
56
|
+
const hint = templates.includes(appName)
|
|
57
|
+
? `Create it with: aifabrix deploy ${appName} --yes`
|
|
58
|
+
: 'Pass a matching template name, or create the app first.';
|
|
59
|
+
return new Error(
|
|
60
|
+
`App '${appName}' was not found under integration/ or builder/. ${listed} ${hint}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* @param {string} appName
|
|
66
|
+
* @param {Function} promptFn
|
|
67
|
+
* @returns {Promise<boolean>}
|
|
68
|
+
*/
|
|
69
|
+
async function confirmCreateMatchingTemplate(appName, promptFn) {
|
|
70
|
+
const ask = promptFn || ((qs) => require('inquirer').prompt(qs));
|
|
71
|
+
const { create } = await ask([{
|
|
72
|
+
type: 'confirm',
|
|
73
|
+
name: 'create',
|
|
74
|
+
message: `App '${appName}' is not available. Create it from the shipped '${appName}' template?`,
|
|
75
|
+
default: true
|
|
76
|
+
}]);
|
|
77
|
+
return Boolean(create);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {string[]} templates
|
|
82
|
+
* @param {Function} promptFn
|
|
83
|
+
* @returns {Promise<string|null>}
|
|
84
|
+
*/
|
|
85
|
+
async function pickShippedTemplate(templates, promptFn) {
|
|
86
|
+
const ask = promptFn || ((qs) => require('inquirer').prompt(qs));
|
|
87
|
+
const { template } = await ask([{
|
|
88
|
+
type: 'list',
|
|
89
|
+
name: 'template',
|
|
90
|
+
message: 'App was not found. Create from which shipped application template?',
|
|
91
|
+
choices: [
|
|
92
|
+
...templates.map((name) => ({ name, value: name })),
|
|
93
|
+
{ name: 'Cancel', value: null }
|
|
94
|
+
]
|
|
95
|
+
}]);
|
|
96
|
+
return template || null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {string} appName
|
|
101
|
+
* @param {Object} [deps]
|
|
102
|
+
* @returns {Promise<string>}
|
|
103
|
+
*/
|
|
104
|
+
async function materializeShippedTemplate(appName, deps = {}) {
|
|
105
|
+
const copy = deps.ensureAppFromTemplate || ensureAppFromTemplate;
|
|
106
|
+
await copy(appName);
|
|
107
|
+
return appName;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @param {string} appName
|
|
112
|
+
* @param {string[]} templates
|
|
113
|
+
* @param {Object} options
|
|
114
|
+
* @param {Object} deps
|
|
115
|
+
* @returns {Promise<string>}
|
|
116
|
+
*/
|
|
117
|
+
async function createMatchingTemplateApp(appName, templates, options, deps) {
|
|
118
|
+
if (assumeYesCreate(options)) {
|
|
119
|
+
return materializeShippedTemplate(appName, deps);
|
|
120
|
+
}
|
|
121
|
+
if (!isInteractiveCreate(deps)) {
|
|
122
|
+
throw buildMissingAppError(appName, templates);
|
|
123
|
+
}
|
|
124
|
+
const ok = await confirmCreateMatchingTemplate(appName, deps.prompt);
|
|
125
|
+
if (!ok) {
|
|
126
|
+
throw buildMissingAppError(appName, templates);
|
|
127
|
+
}
|
|
128
|
+
return materializeShippedTemplate(appName, deps);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @param {string} appName
|
|
133
|
+
* @param {string[]} templates
|
|
134
|
+
* @param {Object} options
|
|
135
|
+
* @param {Object} deps
|
|
136
|
+
* @returns {Promise<string>}
|
|
137
|
+
*/
|
|
138
|
+
async function createPickedTemplateApp(appName, templates, options, deps) {
|
|
139
|
+
if (assumeYesCreate(options) || !isInteractiveCreate(deps) || templates.length === 0) {
|
|
140
|
+
throw buildMissingAppError(appName, templates);
|
|
141
|
+
}
|
|
142
|
+
const chosen = await pickShippedTemplate(templates, deps.prompt);
|
|
143
|
+
if (!chosen) {
|
|
144
|
+
throw buildMissingAppError(appName, templates);
|
|
145
|
+
}
|
|
146
|
+
if (chosen !== appName) {
|
|
147
|
+
logger.log(metadata(
|
|
148
|
+
`Creating '${chosen}' from shipped template (requested '${appName}' was not found).`
|
|
149
|
+
));
|
|
150
|
+
}
|
|
151
|
+
await materializeShippedTemplate(chosen, deps);
|
|
152
|
+
return chosen;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* If the app is missing locally, offer to create it from shipped templates.
|
|
157
|
+
*
|
|
158
|
+
* @async
|
|
159
|
+
* @function offerCreateFromTemplateIfMissing
|
|
160
|
+
* @param {string} appName - Requested app or system key
|
|
161
|
+
* @param {Object} [options] - CLI options (`yes` skips the confirm when the name matches a template)
|
|
162
|
+
* @param {Object} [deps] - Test seams (prompt, isTty, listTemplates, findExisting, ensureAppFromTemplate)
|
|
163
|
+
* @returns {Promise<string>} App key to continue with (may differ if the user picked another template)
|
|
164
|
+
* @throws {Error} When the app is missing and the user declines or cannot be prompted
|
|
165
|
+
*/
|
|
166
|
+
async function offerCreateFromTemplateIfMissing(appName, options = {}, deps = {}) {
|
|
167
|
+
const findExisting = deps.findExisting || findExistingAppType;
|
|
168
|
+
if (findExisting(appName, options)) {
|
|
169
|
+
return appName;
|
|
170
|
+
}
|
|
171
|
+
const listTemplates = deps.listTemplates || listAvailableTemplates;
|
|
172
|
+
const templates = await listTemplates();
|
|
173
|
+
if (templates.includes(appName)) {
|
|
174
|
+
return createMatchingTemplateApp(appName, templates, options, deps);
|
|
175
|
+
}
|
|
176
|
+
return createPickedTemplateApp(appName, templates, options, deps);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = {
|
|
180
|
+
offerCreateFromTemplateIfMissing,
|
|
181
|
+
buildMissingAppError,
|
|
182
|
+
assumeYesCreate,
|
|
183
|
+
isInteractiveCreate
|
|
184
|
+
};
|
package/lib/cli/setup-app.js
CHANGED
|
@@ -39,6 +39,10 @@ Examples:
|
|
|
39
39
|
$ aifabrix deploy myapp
|
|
40
40
|
$ aifabrix deploy myapp --local
|
|
41
41
|
|
|
42
|
+
# Missing local app that matches a shipped template (TTY asks to create)
|
|
43
|
+
$ aifabrix deploy dataplane
|
|
44
|
+
$ aifabrix deploy dataplane --yes
|
|
45
|
+
|
|
42
46
|
# Local — external system (integration/<systemKey>/); manifest regenerated on deploy
|
|
43
47
|
$ aifabrix deploy hubspot-demo
|
|
44
48
|
$ aifabrix deploy hubspot-demo --local
|
|
@@ -69,6 +73,7 @@ Notes:
|
|
|
69
73
|
- Default output is a compact status card; use -v for expanded preflight and readiness detail.
|
|
70
74
|
- Controller deploy always polls to a terminal status (no --poll / --no-poll on this command).
|
|
71
75
|
- --repository-url overrides repo URL for pipeline validate (default: application.yaml repository.repositoryUrl).
|
|
76
|
+
- If the app is missing locally and the name matches a shipped template, TTY asks to create it (use --yes in scripts).
|
|
72
77
|
`;
|
|
73
78
|
|
|
74
79
|
const PUSH_HELP_AFTER = `
|
|
@@ -367,6 +372,7 @@ function setupDeployCommand(program) {
|
|
|
367
372
|
'--subject-email <emails>',
|
|
368
373
|
'Subject email(s) for governance gate (comma-separated; used with --gate governance|full)'
|
|
369
374
|
)
|
|
375
|
+
.option('-y, --yes', 'Create a missing app from a matching shipped template without prompting')
|
|
370
376
|
.action(async(appName, options) => {
|
|
371
377
|
try {
|
|
372
378
|
const probeTimeout =
|
|
@@ -381,8 +387,9 @@ function setupDeployCommand(program) {
|
|
|
381
387
|
};
|
|
382
388
|
const outcome = await app.deployApp(appName, opts);
|
|
383
389
|
if (opts.local && outcome) {
|
|
390
|
+
const targetApp = outcome.appKey || appName;
|
|
384
391
|
if (outcome.usedExternalDeploy) await app.restartApp('dataplane');
|
|
385
|
-
else await app.runApp(
|
|
392
|
+
else await app.runApp(targetApp, opts);
|
|
386
393
|
}
|
|
387
394
|
} catch (error) {
|
|
388
395
|
handleCommandError(error, 'deploy');
|
package/lib/cli/setup-auth.js
CHANGED
|
@@ -17,7 +17,12 @@ const { handleAuthConfig } = require('../commands/auth-config');
|
|
|
17
17
|
const LOGIN_HELP_AFTER = `
|
|
18
18
|
Examples:
|
|
19
19
|
$ aifabrix login
|
|
20
|
+
$ aifabrix login -c https://controller.example.com
|
|
20
21
|
$ aifabrix login -m credentials -a myapp -e dev
|
|
22
|
+
|
|
23
|
+
Notes:
|
|
24
|
+
- Public controller URLs are stored as origin + /miso (extra path segments are ignored).
|
|
25
|
+
- Localhost URLs keep the origin unless the path is already /miso.
|
|
21
26
|
`;
|
|
22
27
|
|
|
23
28
|
const AUTH_HELP_AFTER = `
|
|
@@ -34,12 +39,15 @@ Subcommand: auth status [--validate] for CI/scripts (exit codes 0/1/3).
|
|
|
34
39
|
const LOGOUT_HELP_AFTER = `
|
|
35
40
|
Examples:
|
|
36
41
|
$ aifabrix logout
|
|
42
|
+
$ aifabrix logout --type client
|
|
43
|
+
$ aifabrix logout --type device
|
|
37
44
|
$ aifabrix logout -c http://localhost:3600
|
|
38
45
|
$ aifabrix logout -e dev
|
|
39
46
|
$ aifabrix logout -e dev -a hubspot-e2e
|
|
40
47
|
|
|
41
48
|
Notes:
|
|
42
49
|
- With no flags: clears all device tokens and all client tokens in config.yaml.
|
|
50
|
+
- Use --type client or --type device to clear only that token type.
|
|
43
51
|
- Device tokens are keyed by controller URL (-c).
|
|
44
52
|
- Client tokens are per environment and app (-e and optional -a).
|
|
45
53
|
- Run aifabrix auth status to list stored app tokens and copy-paste logout commands.
|
|
@@ -164,7 +172,7 @@ function setupLoginCommand(program) {
|
|
|
164
172
|
program.command('login')
|
|
165
173
|
.description('Sign in to Miso Controller (device or credentials flow)')
|
|
166
174
|
.addHelpText('after', LOGIN_HELP_AFTER)
|
|
167
|
-
.option('-c, --controller <url>', 'Controller URL (default:
|
|
175
|
+
.option('-c, --controller <url>', 'Controller URL (public hosts use origin + /miso; default: config or localhost by developer ID)')
|
|
168
176
|
.option('-m, --method <method>', 'Authentication method (device|credentials)', 'device')
|
|
169
177
|
.option('-a, --app <app>', 'Application name (required for credentials method, reads from secrets.local.yaml)')
|
|
170
178
|
.option('--client-id <id>', 'Client ID (for credentials method, overrides secrets.local.yaml)')
|
|
@@ -188,7 +196,8 @@ function setupLoginCommand(program) {
|
|
|
188
196
|
|
|
189
197
|
function setupLogoutCommand(program) {
|
|
190
198
|
program.command('logout')
|
|
191
|
-
.description('Clear stored tokens (optional filter by controller/env/app)')
|
|
199
|
+
.description('Clear stored tokens (optional filter by type/controller/env/app)')
|
|
200
|
+
.option('-t, --type <type>', 'Clear only one token type (device|client)')
|
|
192
201
|
.option('-c, --controller <url>', 'Clear device tokens for specific controller')
|
|
193
202
|
.option('-e, --environment <env>', 'Clear client tokens for specific environment')
|
|
194
203
|
.option('-a, --app <app>', 'Clear client tokens for specific app (requires --environment)')
|
|
@@ -5,24 +5,46 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
+
const HOST_OPTION_HELP =
|
|
9
|
+
'Coding hosts to activate: all, cursor, claude, or codex (one name = that host only)';
|
|
10
|
+
|
|
11
|
+
const APPROVE_ALL_OPTION_HELP =
|
|
12
|
+
'Auto-approve tools for all, claude, or codex (omit value = every selected host)';
|
|
13
|
+
|
|
8
14
|
const GROUP_HELP = `
|
|
15
|
+
Coding hosts (--host) are Cursor, Claude Code, and/or Codex in this workspace.
|
|
16
|
+
GitHub remotes are a separate command (agent-kit remote create).
|
|
17
|
+
|
|
9
18
|
Examples:
|
|
10
19
|
$ aifabrix agent-kit init cust-demo --host all --approve-all --yes
|
|
11
|
-
$ aifabrix agent-kit install --host
|
|
20
|
+
$ aifabrix agent-kit install --host claude --approve-all --yes
|
|
21
|
+
$ aifabrix agent-kit install --host all --approve-all claude --yes
|
|
12
22
|
$ aifabrix agent-kit update --host all --approve-all --yes
|
|
23
|
+
$ aifabrix agent-kit remote create --org my-org --yes
|
|
24
|
+
$ aifabrix agent-kit start claude --remote-control --approve-all --yes
|
|
25
|
+
$ aifabrix agent-kit start claude --name aifabrix-docs --remote-control --approve-all --yes
|
|
26
|
+
$ aifabrix agent-kit start codex --approve-all --yes
|
|
13
27
|
$ aifabrix agent-kit status
|
|
14
28
|
$ aifabrix agent-kit validate
|
|
15
29
|
`;
|
|
16
30
|
|
|
17
31
|
const INSTALL_HELP = `
|
|
32
|
+
--host chooses which coding apps this repo supports (default all).
|
|
33
|
+
Use one name to activate only that host. --approve-all [claude|codex] writes
|
|
34
|
+
auto-approve configs for that host's tools only.
|
|
35
|
+
|
|
18
36
|
Examples:
|
|
19
37
|
$ aifabrix agent-kit install
|
|
20
38
|
$ aifabrix agent-kit install --host all --approve-all --yes
|
|
21
39
|
$ aifabrix agent-kit install --host claude --approve-all --yes
|
|
40
|
+
$ aifabrix agent-kit install --host codex --approve-all --yes
|
|
41
|
+
$ aifabrix agent-kit install --host all --approve-all claude --yes
|
|
22
42
|
$ aifabrix agent-kit install --skip-approve-all
|
|
23
43
|
$ aifabrix agent-kit install --dry-run
|
|
24
44
|
|
|
25
|
-
TTY asks
|
|
45
|
+
TTY asks which hosts to support (omit --host) and which tools to auto-approve.
|
|
46
|
+
Silent: --host <name> --approve-all [claude|codex] --yes. Skip: --skip-approve-all.
|
|
47
|
+
Start without installing skills: aifabrix agent-kit start claude
|
|
26
48
|
`;
|
|
27
49
|
|
|
28
50
|
const STATUS_HELP = `
|
|
@@ -36,9 +58,12 @@ Examples:
|
|
|
36
58
|
$ aifabrix agent-kit update
|
|
37
59
|
$ aifabrix agent-kit update --host all --approve-all --yes
|
|
38
60
|
$ aifabrix agent-kit update --host claude --force
|
|
61
|
+
$ aifabrix agent-kit update --host all --approve-all claude --yes
|
|
39
62
|
$ aifabrix agent-kit update --dry-run
|
|
40
63
|
|
|
41
|
-
Omit --host to keep the coding hosts already recorded. Pass --host
|
|
64
|
+
Omit --host to keep the coding hosts already recorded. Pass --host claude or
|
|
65
|
+
--host codex to activate only that host. --approve-all claude writes Claude
|
|
66
|
+
Code tool auto-approve only.
|
|
42
67
|
`;
|
|
43
68
|
|
|
44
69
|
const VALIDATE_HELP = `
|
|
@@ -48,18 +73,39 @@ Examples:
|
|
|
48
73
|
`;
|
|
49
74
|
|
|
50
75
|
const INIT_HELP = `
|
|
76
|
+
--host chooses Cursor, Claude Code, and/or Codex for the new workspace.
|
|
77
|
+
GitHub (origin + push) is --remote / agent-kit remote create — not --host.
|
|
78
|
+
|
|
51
79
|
Examples:
|
|
52
80
|
$ aifabrix agent-kit init cust-demo
|
|
53
81
|
$ aifabrix agent-kit init cust-demo --host all --approve-all --yes
|
|
82
|
+
$ aifabrix agent-kit init cust-demo --host claude --approve-all --yes
|
|
83
|
+
$ aifabrix agent-kit init cust-demo --host all --approve-all claude --yes
|
|
54
84
|
$ aifabrix agent-kit init cust-demo --host all --remote --org my-org --yes
|
|
55
85
|
$ aifabrix agent-kit init ./path/to/cust-demo --dry-run --json
|
|
56
86
|
|
|
57
87
|
Interactive (TTY): omit --host / --yes to be prompted.
|
|
58
88
|
Silent local: --host all --approve-all --yes
|
|
89
|
+
Silent one host: --host claude --approve-all --yes
|
|
59
90
|
Silent remote: --host all --remote --org <owner> --yes
|
|
60
91
|
`;
|
|
61
92
|
|
|
93
|
+
const REMOTE_GROUP_HELP = `
|
|
94
|
+
This is GitHub only: create origin and push this workspace.
|
|
95
|
+
It does not choose Cursor, Claude Code, or Codex.
|
|
96
|
+
|
|
97
|
+
Examples:
|
|
98
|
+
$ aifabrix agent-kit remote create
|
|
99
|
+
$ aifabrix agent-kit remote create --org my-org --yes
|
|
100
|
+
$ aifabrix agent-kit install --host claude --approve-all --yes
|
|
101
|
+
$ aifabrix agent-kit install --host codex --approve-all --yes
|
|
102
|
+
$ aifabrix agent-kit install --host all --approve-all claude --yes
|
|
103
|
+
`;
|
|
104
|
+
|
|
62
105
|
const REMOTE_HELP = `
|
|
106
|
+
Creates a GitHub repository (origin) and pushes this workspace.
|
|
107
|
+
Not coding-host selection — use --host on install/update for Claude or Codex.
|
|
108
|
+
|
|
63
109
|
Examples:
|
|
64
110
|
$ cd cust-demo && aifabrix agent-kit remote create
|
|
65
111
|
$ aifabrix agent-kit remote create --org my-org --yes
|
|
@@ -68,12 +114,36 @@ Examples:
|
|
|
68
114
|
Interactive (TTY): omit flags to be prompted. Silent: --org <owner> --yes
|
|
69
115
|
`;
|
|
70
116
|
|
|
117
|
+
const START_HELP = `
|
|
118
|
+
Does not install Agent Kit skills. Starts claude or codex in tmux in this git
|
|
119
|
+
workspace so closing the terminal does not kill the session. Detach: Ctrl-b then
|
|
120
|
+
d. --approve-all writes that app's local permission config only. --name is the
|
|
121
|
+
tmux session (default: last folder, e.g. aifabrix-docs). Claude also gets
|
|
122
|
+
--name and --remote-control hostname-user-folder, e.g.
|
|
123
|
+
builder01-dev03-aifabrix-docs. Claude --tmux is for worktrees, not this wrapper.
|
|
124
|
+
That is not Claude --cloud (a separate cloud VM). Codex has no phone/web attach.
|
|
125
|
+
|
|
126
|
+
Examples:
|
|
127
|
+
$ aifabrix agent-kit start claude --remote-control --approve-all --yes
|
|
128
|
+
$ aifabrix agent-kit start claude --name aifabrix-docs --remote-control --approve-all --yes
|
|
129
|
+
$ aifabrix agent-kit start claude --name aifabrix-miso --remote-control --approve-all --yes
|
|
130
|
+
$ aifabrix agent-kit start claude --remote-control --detach
|
|
131
|
+
$ aifabrix agent-kit start claude
|
|
132
|
+
$ aifabrix agent-kit start claude --approve-all --yes
|
|
133
|
+
$ aifabrix agent-kit start codex --approve-all --yes
|
|
134
|
+
$ aifabrix agent-kit start claude --dry-run --json
|
|
135
|
+
`;
|
|
136
|
+
|
|
71
137
|
module.exports = {
|
|
138
|
+
HOST_OPTION_HELP,
|
|
139
|
+
APPROVE_ALL_OPTION_HELP,
|
|
72
140
|
GROUP_HELP,
|
|
73
141
|
INSTALL_HELP,
|
|
74
142
|
STATUS_HELP,
|
|
75
143
|
UPDATE_HELP,
|
|
76
144
|
VALIDATE_HELP,
|
|
77
145
|
INIT_HELP,
|
|
78
|
-
|
|
146
|
+
REMOTE_GROUP_HELP,
|
|
147
|
+
REMOTE_HELP,
|
|
148
|
+
START_HELP
|
|
79
149
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `aifabrix agent-kit` command group.
|
|
3
|
-
* @fileoverview Install, init, status, update, validate, remote create
|
|
3
|
+
* @fileoverview Install, init, status, update, validate, remote create, start
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
'use strict';
|
|
@@ -9,6 +9,7 @@ const logger = require('../utils/logger');
|
|
|
9
9
|
const { handleCommandError } = require('../utils/cli-utils');
|
|
10
10
|
const { formatSuccessLine, formatBlockingError, formatWarningLine } = require('../utils/cli-layout-chalk');
|
|
11
11
|
const { runInstall, runUpdate } = require('../agent-kit/run');
|
|
12
|
+
const { runStart } = require('../agent-kit/start');
|
|
12
13
|
const path = require('path');
|
|
13
14
|
const { runInit, runRemoteCreate } = require('../agent-kit/init');
|
|
14
15
|
const { enrichInitHostOptions, enrichInitRemoteOptions, enrichRemoteOptions, enrichApproveAllOptions } = require('../agent-kit/remote-prompts');
|
|
@@ -26,7 +27,11 @@ const {
|
|
|
26
27
|
UPDATE_HELP,
|
|
27
28
|
VALIDATE_HELP,
|
|
28
29
|
INIT_HELP,
|
|
29
|
-
|
|
30
|
+
REMOTE_GROUP_HELP,
|
|
31
|
+
REMOTE_HELP,
|
|
32
|
+
START_HELP,
|
|
33
|
+
HOST_OPTION_HELP,
|
|
34
|
+
APPROVE_ALL_OPTION_HELP
|
|
30
35
|
} = require('./agent-kit-help');
|
|
31
36
|
|
|
32
37
|
/**
|
|
@@ -91,6 +96,34 @@ function emitRemoteReport(json, asJson) {
|
|
|
91
96
|
logger.log(formatSuccessLine(json.dryRun ? 'Dry-run complete' : 'Done'));
|
|
92
97
|
}
|
|
93
98
|
|
|
99
|
+
/**
|
|
100
|
+
* @param {object} json
|
|
101
|
+
* @param {boolean} asJson
|
|
102
|
+
* @returns {void}
|
|
103
|
+
*/
|
|
104
|
+
function emitStartReport(json, asJson) {
|
|
105
|
+
if (asJson) {
|
|
106
|
+
logger.log(JSON.stringify(json, null, 2));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
logger.log(`App ${json.app}`);
|
|
110
|
+
logger.log(`Session ${json.session}`);
|
|
111
|
+
if (json.remoteControlName) {
|
|
112
|
+
logger.log(`Remote ${json.remoteControlName}`);
|
|
113
|
+
}
|
|
114
|
+
logger.log(`Path ${json.path}`);
|
|
115
|
+
logger.log(`Launch ${json.launch.join(' ')}`);
|
|
116
|
+
if (json.executed && json.detach) {
|
|
117
|
+
logger.log(formatSuccessLine('Session started in background'));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (json.executed) {
|
|
121
|
+
logger.log(formatSuccessLine('Session ended'));
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
logger.log(formatSuccessLine(json.dryRun ? 'Dry-run complete' : 'Ready'));
|
|
125
|
+
}
|
|
126
|
+
|
|
94
127
|
/**
|
|
95
128
|
* @param {Function} runner
|
|
96
129
|
* @param {object} options
|
|
@@ -125,6 +158,7 @@ function setupAgentKitCommands(program) {
|
|
|
125
158
|
addUpdateCommand(group);
|
|
126
159
|
addValidateCommand(group);
|
|
127
160
|
addRemoteCommand(group);
|
|
161
|
+
addStartCommand(group);
|
|
128
162
|
}
|
|
129
163
|
|
|
130
164
|
/**
|
|
@@ -134,16 +168,16 @@ function addInitCommand(group) {
|
|
|
134
168
|
group
|
|
135
169
|
.command('init [nameOrPath]')
|
|
136
170
|
.description('Create a customer workspace skeleton, git repo, and Agent Kit')
|
|
137
|
-
.option('--host <target>',
|
|
171
|
+
.option('--host <target>', HOST_OPTION_HELP, 'all')
|
|
138
172
|
.option('--scm <kind>', 'git or none', 'git')
|
|
139
|
-
.option('--remote', 'Create private
|
|
173
|
+
.option('--remote', 'Create private GitHub remote and push (requires auth)')
|
|
140
174
|
.option('--no-remote', 'Force local-only (skip remote prompt on TTY)')
|
|
141
175
|
.option('--org <owner>', 'Hosting org or user (required with --remote when non-TTY)')
|
|
142
176
|
.option('--visibility <mode>', 'private or public', 'private')
|
|
143
177
|
.option('--license <kind>', 'mit or none', 'none')
|
|
144
178
|
.option('--force', 'Allow non-empty target directory')
|
|
145
179
|
.option('--yes', 'Silent: skip TTY confirm (pair with --host and --org); also auto-approves host permissions')
|
|
146
|
-
.option('--approve-all',
|
|
180
|
+
.option('--approve-all [host]', APPROVE_ALL_OPTION_HELP)
|
|
147
181
|
.option('--skip-approve-all', 'Do not write Claude Code or Codex auto-approve configs')
|
|
148
182
|
.option('--dry-run', 'Plan writes without changing files')
|
|
149
183
|
.option('--json', 'Machine-readable output')
|
|
@@ -177,10 +211,13 @@ function addInitCommand(group) {
|
|
|
177
211
|
* @param {import('commander').Command} group
|
|
178
212
|
*/
|
|
179
213
|
function addRemoteCommand(group) {
|
|
180
|
-
const remote = group
|
|
214
|
+
const remote = group
|
|
215
|
+
.command('remote')
|
|
216
|
+
.description('Create a GitHub repository for this workspace and push')
|
|
217
|
+
.addHelpText('after', REMOTE_GROUP_HELP);
|
|
181
218
|
remote
|
|
182
219
|
.command('create')
|
|
183
|
-
.description('Create a private GitHub
|
|
220
|
+
.description('Create a private GitHub repository (origin) and push this workspace')
|
|
184
221
|
.option('--org <owner>', 'Hosting org or user (required when non-TTY)')
|
|
185
222
|
.option('--name <repo>', 'Remote repository name (default: directory basename)')
|
|
186
223
|
.option('--visibility <mode>', 'private or public', 'private')
|
|
@@ -215,8 +252,8 @@ function addInstallCommand(group) {
|
|
|
215
252
|
group
|
|
216
253
|
.command('install')
|
|
217
254
|
.description('Install the Agent Kit into the current git repository')
|
|
218
|
-
.option('--host <target>',
|
|
219
|
-
.option('--approve-all',
|
|
255
|
+
.option('--host <target>', HOST_OPTION_HELP, 'all')
|
|
256
|
+
.option('--approve-all [host]', APPROVE_ALL_OPTION_HELP)
|
|
220
257
|
.option('--skip-approve-all', 'Do not write Claude Code or Codex auto-approve configs')
|
|
221
258
|
.option('--yes', 'Silent: auto-approve host permission configs without asking')
|
|
222
259
|
.option('--dry-run', 'Plan writes without changing files')
|
|
@@ -225,7 +262,8 @@ function addInstallCommand(group) {
|
|
|
225
262
|
.action(async(options) => {
|
|
226
263
|
try {
|
|
227
264
|
const interactive = Boolean(process.stdout.isTTY) && !options.json;
|
|
228
|
-
const
|
|
265
|
+
const withHost = await enrichInitHostOptions(options, { interactive });
|
|
266
|
+
const merged = await enrichApproveAllOptions(withHost, { interactive });
|
|
229
267
|
await runLeaf(runInstall, merged, true);
|
|
230
268
|
} catch (error) {
|
|
231
269
|
handleCommandError(error, 'agent-kit install');
|
|
@@ -287,8 +325,8 @@ function addUpdateCommand(group) {
|
|
|
287
325
|
group
|
|
288
326
|
.command('update')
|
|
289
327
|
.description('Update managed Agent Kit files from this Builder release')
|
|
290
|
-
.option('--host <target>',
|
|
291
|
-
.option('--approve-all',
|
|
328
|
+
.option('--host <target>', `${HOST_OPTION_HELP} (omit to keep recorded hosts)`)
|
|
329
|
+
.option('--approve-all [host]', APPROVE_ALL_OPTION_HELP)
|
|
292
330
|
.option('--skip-approve-all', 'Do not write Claude Code or Codex auto-approve configs')
|
|
293
331
|
.option('--yes', 'Silent: auto-approve host permission configs without asking')
|
|
294
332
|
.option('--dry-run', 'Plan writes without changing files')
|
|
@@ -343,4 +381,37 @@ async function runValidateLeaf(options) {
|
|
|
343
381
|
}
|
|
344
382
|
}
|
|
345
383
|
|
|
384
|
+
/**
|
|
385
|
+
* @param {import('commander').Command} group
|
|
386
|
+
*/
|
|
387
|
+
function addStartCommand(group) {
|
|
388
|
+
group
|
|
389
|
+
.command('start <app>')
|
|
390
|
+
.description('Start Claude Code or Codex in tmux (does not install skills)')
|
|
391
|
+
.option('--approve-all', 'Write auto-approve config for this app only')
|
|
392
|
+
.option('--skip-approve-all', 'Do not write auto-approve configs')
|
|
393
|
+
.option('--yes', 'Same as --approve-all')
|
|
394
|
+
.option('--detach', 'Create the tmux session in the background (no attach)')
|
|
395
|
+
.option(
|
|
396
|
+
'--name <session>',
|
|
397
|
+
'tmux session name (default: last folder of this git workspace)'
|
|
398
|
+
)
|
|
399
|
+
.option(
|
|
400
|
+
'--remote-control',
|
|
401
|
+
'Claude Code only: same session on this PC, claude.ai/code, and the Claude mobile app'
|
|
402
|
+
)
|
|
403
|
+
.option('--dry-run', 'Print the launch command without starting it')
|
|
404
|
+
.option('--json', 'Machine-readable output')
|
|
405
|
+
.addHelpText('after', START_HELP)
|
|
406
|
+
.action(async(app, options) => {
|
|
407
|
+
try {
|
|
408
|
+
const json = runStart({ ...options, app });
|
|
409
|
+
emitStartReport(json, Boolean(options.json));
|
|
410
|
+
} catch (error) {
|
|
411
|
+
handleCommandError(error, 'agent-kit start');
|
|
412
|
+
process.exitCode = 1;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
346
417
|
module.exports = { setupAgentKitCommands };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the signed-in username for `aifabrix auth status`.
|
|
3
|
+
*
|
|
4
|
+
* @fileoverview Unwrap controller auth/user payloads and pick a display username
|
|
5
|
+
* @author AI Fabrix Team
|
|
6
|
+
* @version 2.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const { unwrapControllerData } = require('../identity/identity-response-helpers');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {...*} values
|
|
15
|
+
* @returns {string}
|
|
16
|
+
*/
|
|
17
|
+
function firstNonEmptyString(...values) {
|
|
18
|
+
for (const value of values) {
|
|
19
|
+
if (typeof value === 'string' && value.trim()) {
|
|
20
|
+
return value.trim();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return '';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Decode a JWT payload for display only. Does not verify the signature.
|
|
28
|
+
* @param {string} token
|
|
29
|
+
* @returns {Object|null}
|
|
30
|
+
*/
|
|
31
|
+
function decodeJwtPayload(token) {
|
|
32
|
+
if (!token || typeof token !== 'string') {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const parts = token.split('.');
|
|
36
|
+
if (parts.length < 2) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const b64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
41
|
+
const padded = b64.padEnd(Math.ceil(b64.length / 4) * 4, '=');
|
|
42
|
+
const payload = JSON.parse(Buffer.from(padded, 'base64').toString('utf8'));
|
|
43
|
+
return payload && typeof payload === 'object' ? payload : null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {Object|null|undefined} user
|
|
51
|
+
* @param {string} [token]
|
|
52
|
+
* @returns {string}
|
|
53
|
+
*/
|
|
54
|
+
function resolveDisplayUsername(user, token) {
|
|
55
|
+
const fromUser = firstNonEmptyString(
|
|
56
|
+
user && user.username,
|
|
57
|
+
user && user.preferredUsername,
|
|
58
|
+
user && user.preferred_username,
|
|
59
|
+
user && user.email
|
|
60
|
+
);
|
|
61
|
+
if (fromUser) {
|
|
62
|
+
return fromUser;
|
|
63
|
+
}
|
|
64
|
+
const payload = decodeJwtPayload(token);
|
|
65
|
+
return firstNonEmptyString(
|
|
66
|
+
payload && payload.preferred_username,
|
|
67
|
+
payload && payload.preferredUsername,
|
|
68
|
+
payload && payload.email,
|
|
69
|
+
payload && payload.upn
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @param {Object|null|undefined} response
|
|
75
|
+
* @param {string} [token]
|
|
76
|
+
* @returns {{ authenticated: boolean, user: Object|null, username: string }}
|
|
77
|
+
*/
|
|
78
|
+
function parseAuthUserResponse(response, token) {
|
|
79
|
+
const payload = unwrapControllerData(response);
|
|
80
|
+
if (!payload || typeof payload !== 'object') {
|
|
81
|
+
return { authenticated: false, user: null, username: '' };
|
|
82
|
+
}
|
|
83
|
+
const rawUser = payload.user && typeof payload.user === 'object' ? payload.user : null;
|
|
84
|
+
const username = resolveDisplayUsername(rawUser, token);
|
|
85
|
+
const user = username && (!rawUser || !rawUser.username)
|
|
86
|
+
? { ...(rawUser || {}), username }
|
|
87
|
+
: rawUser;
|
|
88
|
+
return {
|
|
89
|
+
authenticated: payload.authenticated !== false,
|
|
90
|
+
user,
|
|
91
|
+
username
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = {
|
|
96
|
+
decodeJwtPayload,
|
|
97
|
+
resolveDisplayUsername,
|
|
98
|
+
parseAuthUserResponse
|
|
99
|
+
};
|