@aifabrix/builder 2.58.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.
Files changed (84) hide show
  1. package/lib/agent-kit/codex-app-server.js +44 -0
  2. package/lib/agent-kit/init.js +4 -17
  3. package/lib/agent-kit/session-report.js +15 -10
  4. package/lib/agent-kit/setup-auth.js +131 -0
  5. package/lib/agent-kit/setup-context.js +118 -0
  6. package/lib/agent-kit/setup-github.js +70 -0
  7. package/lib/agent-kit/setup-managed.js +79 -0
  8. package/lib/agent-kit/setup-process.js +42 -0
  9. package/lib/agent-kit/setup-session.js +53 -0
  10. package/lib/agent-kit/setup-state.js +52 -0
  11. package/lib/agent-kit/setup-workspace.js +113 -0
  12. package/lib/agent-kit/setup.js +84 -0
  13. package/lib/agent-kit/start.js +4 -3
  14. package/lib/agent-kit/tmux-sessions.js +20 -3
  15. package/lib/api/dev.api.js +1 -1
  16. package/lib/api/miso-health.api.js +26 -0
  17. package/lib/api/types/dev.types.js +3 -3
  18. package/lib/app/deploy.js +0 -102
  19. package/lib/cli/index.js +4 -0
  20. package/lib/cli/setup-dev-path-commands.js +4 -0
  21. package/lib/cli/setup-dev.js +7 -4
  22. package/lib/cli/setup-help.js +86 -0
  23. package/lib/cli/setup-onboarding.js +37 -0
  24. package/lib/cli/setup-utility-repair.js +7 -5
  25. package/lib/commands/agent-kit-help.js +21 -7
  26. package/lib/commands/agent-kit-sessions.js +126 -0
  27. package/lib/commands/agent-kit-setup.js +44 -0
  28. package/lib/commands/agent-kit.js +5 -79
  29. package/lib/commands/dev-init.js +21 -23
  30. package/lib/commands/dev-refresh.js +2 -2
  31. package/lib/commands/governance-verify-external.js +12 -5
  32. package/lib/commands/repair-datasource.js +9 -9
  33. package/lib/commands/repair.js +45 -8
  34. package/lib/commands/upload-qualification-client.js +56 -11
  35. package/lib/commands/upload-qualification-poll.js +51 -22
  36. package/lib/commands/wizard-dataplane.js +16 -52
  37. package/lib/datasource/datasource-validate-summary.js +8 -5
  38. package/lib/datasource/sync-defaults.js +53 -0
  39. package/lib/deployment/installation/azure-infra-stage.js +10 -0
  40. package/lib/deployment/installation/azure-preflight.js +13 -2
  41. package/lib/deployment/installation/azure-readiness.js +24 -2
  42. package/lib/deployment/installation/environment-auth.js +60 -20
  43. package/lib/deployment/installation/environment-stage.js +9 -5
  44. package/lib/deployment/installation/infra-catalog.js +8 -2
  45. package/lib/deployment/installation/infra-manifest.js +5 -0
  46. package/lib/deployment/installation/registry-auth-mode.js +33 -10
  47. package/lib/deployment/marketplace-environment.js +26 -1
  48. package/lib/generator/index.js +23 -12
  49. package/lib/integration-definition/apply.js +2 -2
  50. package/lib/internal/fs-real-sync.js +4 -1
  51. package/lib/programmatic/bearer-auth.js +3 -62
  52. package/lib/programmatic/builder-api-startup.js +51 -0
  53. package/lib/programmatic/builder-miso-runtime.js +167 -0
  54. package/lib/programmatic/openapi-descriptions.js +3 -3
  55. package/lib/programmatic/openapi-schema-descriptions-core.js +3 -3
  56. package/lib/programmatic/openapi-spec.js +2 -1
  57. package/lib/programmatic/route-handler-map.js +3 -2
  58. package/lib/schema/environment-deploy-request.schema.json +1 -1
  59. package/lib/schema/external-datasource.schema.json +20 -5
  60. package/lib/schema/infra-parameter.schema.json +1 -1
  61. package/lib/schema/infra.parameter.yaml +2 -1
  62. package/lib/schema/infrastructure-schema.json +145 -1
  63. package/lib/schema/installation-environment.schema.json +1 -1
  64. package/lib/schema/wizard-config.schema.json +1 -1
  65. package/lib/utils/config-paths.js +24 -29
  66. package/lib/utils/config-registry-preference.js +13 -6
  67. package/lib/utils/dev-init-ssh-merge.js +5 -4
  68. package/lib/utils/dev-user-groups.js +2 -1
  69. package/lib/utils/docker-daemon-tls-ca.js +2 -2
  70. package/lib/utils/error-formatters/validation-errors.js +25 -5
  71. package/lib/utils/help-builder.js +4 -3
  72. package/lib/utils/token-manager.js +29 -36
  73. package/package.json +3 -2
  74. package/templates/agent-kit/instructions/AGENTKIT.md +39 -2
  75. package/templates/agent-kit/instructions/root.AGENTS.md +40 -1
  76. package/templates/agent-kit/skills/shared/hosts.md +23 -5
  77. package/templates/agent-kit/workspace/README.md +14 -1
  78. package/templates/applications/builder-api/application.yaml +1 -1
  79. package/templates/applications/dataplane/application.yaml +1 -1
  80. package/templates/applications/dataplane/env.template +1 -1
  81. package/templates/applications/miso-controller/application.yaml +1 -1
  82. package/templates/applications/miso-controller/env.template +1 -1
  83. package/templates/external-system/external-datasource.yaml.hbs +7 -6
  84. package/templates/marketplace/main.json +438 -80
@@ -0,0 +1,37 @@
1
+ /** Top-level managed Agent Kit onboarding command. */
2
+ 'use strict';
3
+
4
+ const logger = require('../utils/logger');
5
+ const { formatBlockingError } = require('../utils/cli-layout-chalk');
6
+ const { maskSensitiveData } = require('../utils/log-redaction');
7
+ const { runManagedOnboarding } = require('../agent-kit/setup');
8
+
9
+ function setupOnboardingCommand(program) {
10
+ program.command('onboarding')
11
+ .description('Prepare your provisioned Agent Kit workspace for the selected coding platform')
12
+ .option('--coding <platform>', 'Use claude, codex, or cursor for this run')
13
+ .option('--name <folder>', 'Create or reuse this workspace folder under the managed work root')
14
+ .option('--repo <owner/name>', 'Reuse or clone this GitHub repository, or create it privately')
15
+ .addHelpText('after', '\nRun on your managed workspace host after connecting by SSH.\n' +
16
+ 'Reads ~/.aifabrix/config.yaml; --coding overrides its codingPlatform for this run only.\n' +
17
+ 'For portable onboarding use aifabrix agent-kit setup <controllerUrl>.\n\n' +
18
+ 'Examples:\n' +
19
+ ' $ af onboarding\n' +
20
+ ' $ af onboarding --coding claude\n' +
21
+ ' $ af onboarding --coding claude --name sales-workspace\n' +
22
+ ' $ af onboarding --coding claude --name finance-workspace\n' +
23
+ ' $ af onboarding --repo example-org/example-workspace\n' +
24
+ ' $ af onboarding --repo my-org/new-repo\n' +
25
+ ' $ af onboarding --coding codex\n' +
26
+ ' $ af onboarding --coding cursor\n')
27
+ .action(async(options) => {
28
+ try {
29
+ await runManagedOnboarding(options);
30
+ } catch (error) {
31
+ logger.error(formatBlockingError(maskSensitiveData(error.message)));
32
+ process.exitCode = 1;
33
+ }
34
+ });
35
+ }
36
+
37
+ module.exports = { setupOnboardingCommand };
@@ -99,7 +99,9 @@ function logRepairDryRunOutcome(options, result, changedFiles) {
99
99
  */
100
100
  function logRepairSuccessOutcome(result, changedFiles) {
101
101
  if (result.updated) {
102
- logger.log(formatSuccessParagraph('Repaired external integration config.'));
102
+ logger.log(formatSuccessParagraph(result.applicationReadme
103
+ ? 'Regenerated README.md from application configuration.'
104
+ : 'Repaired external integration config.'));
103
105
  logRepairChangedFiles(changedFiles, false);
104
106
  logRepairNextActions(changedFiles, result.warnings);
105
107
  return;
@@ -166,7 +168,7 @@ async function handleRepairCommand(appName, options) {
166
168
  */
167
169
  function setupRepairCommand(program) {
168
170
  program.command('repair <systemKey>')
169
- .description('Fix external integration drift (files, RBAC, manifest, …)')
171
+ .description('Fix external integration drift or regenerate application documentation (--doc)')
170
172
  .option('--all', 'Run all repair actions (api, doc, expose, rbac, sync, test)')
171
173
  .option(
172
174
  '--api',
@@ -176,10 +178,10 @@ function setupRepairCommand(program) {
176
178
  '--auth <method>',
177
179
  'Set authentication method (oauth2, aad, apikey, bearerToken, basic, queryParam, oidc, hmac, none); updates system file and env.template'
178
180
  )
179
- .option('--doc', 'Regenerate README.md from deployment manifest')
181
+ .option('--doc', 'Regenerate README.md from application config or external integration deployment manifest')
180
182
  .option('--expose', 'Set exposed.schema on each datasource from all fieldMappings.attributes keys (metadata.<key>); removes deprecated exposed.attributes if present')
181
183
  .option('--rbac', 'Ensure RBAC permissions per datasource and add default Admin/Reader roles if none exist; warns when capabilities lack openapi.operations (auto-syncs operations)')
182
- .option('--sync', 'Add default sync section to datasources that lack it')
184
+ .option('--sync', 'Add enabled monthly UTC sync defaults when the sync section is absent')
183
185
  .option(
184
186
  '--auth-webhook',
185
187
  'Ensure authentications[] slot usage=webhookInbound (454 webhook inbound auth)'
@@ -197,7 +199,7 @@ function setupRepairCommand(program) {
197
199
  '--rbac-preset <preset>',
198
200
  'RBAC preset for facade APIs (e.g. platform — dataplane role catalog)'
199
201
  )
200
- .option('--no-backup', 'Skip timestamped copies under integration/<systemKey>/backup/')
202
+ .option('--no-backup', 'Skip timestamped copies under the application folder backup/')
201
203
  .option('--dry-run', 'Report changes only; do not write')
202
204
  .addHelpText('after', REPAIR_HELP_AFTER)
203
205
  .action(async(appName, options) => {
@@ -16,6 +16,7 @@ Coding hosts (--host) are Cursor, Claude Code, and/or Codex in this workspace.
16
16
  GitHub remotes are a separate command (agent-kit remote create).
17
17
 
18
18
  Examples:
19
+ $ aifabrix agent-kit setup https://controller.example.com
19
20
  $ aifabrix agent-kit init cust-demo --host all --approve-all --yes
20
21
  $ aifabrix agent-kit install --host claude --approve-all --yes
21
22
  $ aifabrix agent-kit install --host all --approve-all claude --yes
@@ -94,10 +95,12 @@ Silent remote: --host all --remote --org <owner> --yes
94
95
  const REMOTE_GROUP_HELP = `
95
96
  This is GitHub only: create origin and push this workspace.
96
97
  It does not choose Cursor, Claude Code, or Codex.
98
+ For an existing GitHub repository on a managed host, use af onboarding --repo owner/name.
97
99
 
98
100
  Examples:
99
- $ aifabrix agent-kit remote create
100
- $ aifabrix agent-kit remote create --org my-org --yes
101
+ $ cd ~/agent-kit-workspace && af agent-kit remote create --org my-org --name my-repo --yes
102
+ $ af onboarding --repo my-org/existing-repo
103
+ $ af onboarding --repo my-org/new-repo
101
104
  $ aifabrix agent-kit install --host claude --approve-all --yes
102
105
  $ aifabrix agent-kit install --host codex --approve-all --yes
103
106
  $ aifabrix agent-kit install --host all --approve-all claude --yes
@@ -105,12 +108,16 @@ Examples:
105
108
 
106
109
  const REMOTE_HELP = `
107
110
  Creates a GitHub repository (origin) and pushes this workspace.
111
+ Run inside the local workspace. --name sets the new GitHub repository name;
112
+ omit it to use the workspace folder name. This command does not clone an
113
+ existing GitHub repository.
108
114
  Not coding-host selection — use --host on install/update for Claude or Codex.
109
115
 
110
116
  Examples:
111
- $ cd cust-demo && aifabrix agent-kit remote create
112
- $ aifabrix agent-kit remote create --org my-org --yes
113
- $ aifabrix agent-kit remote create --org my-org --dry-run --json
117
+ $ cd ~/agent-kit-workspace && af agent-kit remote create --org my-org --name my-repo --yes
118
+ $ af agent-kit remote create --org my-org --name my-repo --dry-run --json
119
+ $ af onboarding --repo my-org/existing-repo
120
+ $ af onboarding --repo my-org/new-repo
114
121
 
115
122
  Interactive (TTY): omit flags to be prompted. Silent: --org <owner> --yes
116
123
  `;
@@ -134,7 +141,10 @@ Examples:
134
141
  `;
135
142
 
136
143
  const LIST_HELP = `
137
- Lists every active tmux session visible to the current Unix user.
144
+ Lists active tmux sessions and direct Codex app servers owned by the current
145
+ Unix user. On a terminal, choose a tmux session to attach or Close. Codex app
146
+ servers are shown as information and cannot be selected from this menu.
147
+ --json and non-interactive output print the report without a prompt.
138
148
 
139
149
  Examples:
140
150
  $ aifabrix agent-kit list
@@ -142,9 +152,13 @@ Examples:
142
152
  `;
143
153
 
144
154
  const KILL_HELP = `
145
- Stops exactly one tmux session. Use agent-kit list to find its name.
155
+ Stops exactly one tmux session by name. Omit the name in an interactive
156
+ terminal to choose a tmux session or Codex app server. Stopping a Codex app
157
+ server requires confirmation and may disconnect an active desktop chat.
158
+ Pass an exact tmux name for scripts or --json.
146
159
 
147
160
  Examples:
161
+ $ aifabrix agent-kit kill
148
162
  $ aifabrix agent-kit kill aifabrix-miso
149
163
  $ aifabrix agent-kit kill aifabrix-miso --json
150
164
  `;
@@ -0,0 +1,126 @@
1
+ /** CLI actions for listing, attaching, and stopping Agent Kit sessions. */
2
+ 'use strict';
3
+
4
+ const inquirer = require('inquirer');
5
+ const logger = require('../utils/logger');
6
+ const { handleCommandError } = require('../utils/cli-utils');
7
+ const { formatSuccessLine } = require('../utils/cli-layout-chalk');
8
+ const { runList, runKill, runAttach } = require('../agent-kit/tmux-sessions');
9
+ const { listCodexAppServers, stopCodexAppServer } = require('../agent-kit/codex-app-server');
10
+ const { formatSessionList, formatMissingKillName } = require('../agent-kit/session-report');
11
+ const { LIST_HELP, KILL_HELP } = require('./agent-kit-help');
12
+
13
+ function emitListReport(report, asJson) {
14
+ logger.log(asJson ? JSON.stringify(report, null, 2) : formatSessionList(report));
15
+ }
16
+
17
+ function emitKillReport(report, asJson) {
18
+ logger.log(asJson ? JSON.stringify(report, null, 2)
19
+ : formatSuccessLine(report.kind === 'codex-app-server'
20
+ ? `Sent stop signal to Codex app server PID ${report.pid}`
21
+ : `Stopped tmux session ${report.session}`));
22
+ }
23
+
24
+ function listSessions(options = {}) {
25
+ const report = (options.runList || runList)(options);
26
+ const codexServers = (options.listCodexAppServers || listCodexAppServers)(options);
27
+ return { ...report, codexServers, serverCount: codexServers.length };
28
+ }
29
+
30
+ async function runListLeaf(options = {}) {
31
+ try {
32
+ const report = listSessions(options);
33
+ emitListReport(report, Boolean(options.json));
34
+ const tty = options.isTTY === undefined
35
+ ? Boolean(process.stdin.isTTY && process.stdout.isTTY) : options.isTTY;
36
+ if (!tty || options.json || report.sessions.length === 0) return;
37
+ const choices = report.sessions.map(session => ({
38
+ name: `${session.name} (${session.attached ? 'attached' : 'detached'})`, value: session.name
39
+ }));
40
+ choices.push({ name: 'Close', value: null });
41
+ const answer = await (options.prompt || inquirer.prompt)([{
42
+ type: 'list', name: 'name', message: 'Attach to a tmux session:', choices
43
+ }]);
44
+ if (answer.name) (options.runAttach || runAttach)({ ...options, name: answer.name });
45
+ } catch (error) {
46
+ handleCommandError(error, 'agent-kit list');
47
+ process.exitCode = 1;
48
+ }
49
+ }
50
+
51
+ async function selectKillSession(options = {}) {
52
+ const tty = options.isTTY === undefined
53
+ ? Boolean(process.stdin.isTTY && process.stdout.isTTY) : options.isTTY;
54
+ if (!tty || options.json) return { state: 'missing' };
55
+ const report = listSessions(options);
56
+ emitListReport(report, false);
57
+ if (report.sessions.length + report.codexServers.length === 0) return { state: 'empty' };
58
+ const choices = report.sessions.map(session => ({
59
+ name: `tmux: ${session.name} (${session.attached ? 'attached' : 'detached'})`,
60
+ value: { kind: 'tmux', name: session.name }
61
+ }));
62
+ report.codexServers.forEach(server => choices.push({
63
+ name: `Codex app server (PID ${server.pid})`,
64
+ value: { kind: 'codex-app-server', ...server }
65
+ }));
66
+ choices.push({ name: 'Cancel', value: null });
67
+ const answer = await (options.prompt || inquirer.prompt)([{
68
+ type: 'list', name: 'selection', message: 'Select one session to stop:', choices
69
+ }]);
70
+ if (!answer.selection) return { state: 'cancelled' };
71
+ if (answer.selection.kind === 'codex-app-server') {
72
+ const confirm = await (options.prompt || inquirer.prompt)([{
73
+ type: 'confirm', name: 'stop', default: false,
74
+ message: `Stop Codex app server PID ${answer.selection.pid}? This may disconnect this chat; the client may reconnect.`
75
+ }]);
76
+ if (!confirm.stop) return { state: 'cancelled' };
77
+ }
78
+ return { state: 'selected', ...answer.selection };
79
+ }
80
+
81
+ function addListCommand(group) {
82
+ group.command('list')
83
+ .description('List sessions and choose a tmux session to attach')
84
+ .option('--json', 'Machine-readable output')
85
+ .addHelpText('after', LIST_HELP)
86
+ .action(runListLeaf);
87
+ }
88
+
89
+ function addKillCommand(group) {
90
+ group.command('kill [name]')
91
+ .description('Stop a tmux session by name, or choose a session interactively')
92
+ .option('--json', 'Machine-readable output')
93
+ .addHelpText('after', KILL_HELP)
94
+ .action(runKillLeaf);
95
+ }
96
+
97
+ async function runKillLeaf(name, options = {}) {
98
+ try {
99
+ const selection = name ? { state: 'selected', kind: 'tmux', name } : await selectKillSession(options);
100
+ if (selection.state === 'missing') {
101
+ const report = formatMissingKillName(Boolean(options.json));
102
+ logger.error(report.stderr);
103
+ if (report.stdout) logger.log(report.stdout);
104
+ process.exitCode = 1;
105
+ return;
106
+ }
107
+ if (selection.state === 'empty' || selection.state === 'cancelled') {
108
+ logger.log('No session stopped.');
109
+ return;
110
+ }
111
+ const report = selection.kind === 'codex-app-server'
112
+ ? (options.stopCodexAppServer || stopCodexAppServer)(selection, options)
113
+ : (options.runKill || runKill)({ ...options, name: selection.name });
114
+ emitKillReport(report, Boolean(options.json));
115
+ } catch (error) {
116
+ handleCommandError(error, 'agent-kit kill');
117
+ process.exitCode = 1;
118
+ }
119
+ }
120
+
121
+ function addSessionCommands(group) {
122
+ addListCommand(group);
123
+ addKillCommand(group);
124
+ }
125
+
126
+ module.exports = { addSessionCommands, listSessions, runListLeaf, selectKillSession, runKillLeaf };
@@ -0,0 +1,44 @@
1
+ /** Minimal setup command: controller required, local Codex workspace by default. */
2
+ 'use strict';
3
+
4
+ const logger = require('../utils/logger');
5
+ const { formatBlockingError } = require('../utils/cli-layout-chalk');
6
+ const { maskSensitiveData } = require('../utils/log-redaction');
7
+ const { runSetup } = require('../agent-kit/setup');
8
+
9
+ const SETUP_HELP = `
10
+ Start onboarding with only a Controller URL. Defaults to Codex and a local Git
11
+ workspace under the AI Fabrix work directory; GitHub is not required.
12
+ If the work directory is unset, setup asks once and saves it.
13
+
14
+ Examples:
15
+ $ aifabrix agent-kit setup https://controller.example.com
16
+ $ aifabrix agent-kit setup https://controller.example.com --code claude
17
+ $ aifabrix agent-kit setup https://controller.example.com --repo example-org/example-workspace
18
+
19
+ With --repo owner/name, setup reuses a matching local checkout, clones an accessible
20
+ remote, or creates a private repository and pushes. Unresolved access stops safely.
21
+ Without --repo, files stay local and no GitHub login or push occurs.
22
+ Folder name is the repository name, or workspace without --repo. Change environment with
23
+ aifabrix auth --set-environment <environment>.
24
+ Requires an interactive Linux/macOS terminal, git and tmux (gh only for GitHub).
25
+ Installing a missing Claude CLI through npm requires Node.js 22+.
26
+ `;
27
+
28
+ function addSetupCommand(group) {
29
+ group.command('setup <controllerUrl>')
30
+ .description('Onboard a local Codex workspace; optionally use Claude or GitHub')
31
+ .option('--repo <owner/name>', 'GitHub repository to reuse, clone, or create privately')
32
+ .option('--code <agent>', 'Coding agent: codex or claude', 'codex')
33
+ .addHelpText('after', SETUP_HELP)
34
+ .action(async(controllerUrl, options) => {
35
+ try {
36
+ await runSetup({ ...options, controllerUrl });
37
+ } catch (error) {
38
+ logger.error(formatBlockingError(maskSensitiveData(error.message)));
39
+ process.exitCode = 1;
40
+ }
41
+ });
42
+ }
43
+
44
+ module.exports = { addSetupCommand, SETUP_HELP };
@@ -1,5 +1,4 @@
1
1
  /** `aifabrix agent-kit` command group. */
2
-
3
2
  'use strict';
4
3
 
5
4
  const logger = require('../utils/logger');
@@ -11,8 +10,8 @@ const {
11
10
  } = require('../utils/cli-layout-chalk');
12
11
  const { runInstall, runUpdate } = require('../agent-kit/run');
13
12
  const { runStart } = require('../agent-kit/start');
14
- const { runList, runKill } = require('../agent-kit/tmux-sessions');
15
- const { formatSessionList, formatMissingKillName } = require('../agent-kit/session-report');
13
+ const { addSetupCommand } = require('./agent-kit-setup');
14
+ const { addSessionCommands } = require('./agent-kit-sessions');
16
15
  const path = require('path');
17
16
  const { runInit, runRemoteCreate } = require('../agent-kit/init');
18
17
  const { enrichInitHostOptions, enrichInitRemoteOptions, enrichRemoteOptions, enrichApproveAllOptions } = require('../agent-kit/remote-prompts');
@@ -33,8 +32,6 @@ const {
33
32
  REMOTE_GROUP_HELP,
34
33
  REMOTE_HELP,
35
34
  START_HELP,
36
- LIST_HELP,
37
- KILL_HELP,
38
35
  HOST_OPTION_HELP,
39
36
  APPROVE_ALL_OPTION_HELP
40
37
  } = require('./agent-kit-help');
@@ -131,32 +128,6 @@ function emitStartReport(json, asJson) {
131
128
  logger.log(formatSuccessLine(json.dryRun ? 'Dry-run complete' : 'Ready'));
132
129
  }
133
130
 
134
- /**
135
- * @param {object} json
136
- * @param {boolean} asJson
137
- * @returns {void}
138
- */
139
- function emitListReport(json, asJson) {
140
- if (asJson) {
141
- logger.log(JSON.stringify(json, null, 2));
142
- return;
143
- }
144
- logger.log(formatSessionList(json));
145
- }
146
-
147
- /**
148
- * @param {object} json
149
- * @param {boolean} asJson
150
- * @returns {void}
151
- */
152
- function emitKillReport(json, asJson) {
153
- if (asJson) {
154
- logger.log(JSON.stringify(json, null, 2));
155
- return;
156
- }
157
- logger.log(formatSuccessLine(`Stopped tmux session ${json.session}`));
158
- }
159
-
160
131
  /**
161
132
  * @param {Function} runner
162
133
  * @param {object} options
@@ -185,6 +156,7 @@ function setupAgentKitCommands(program) {
185
156
  .description('Install, init, and update the AI Fabrix Agent Kit in a customer workspace')
186
157
  .addHelpText('after', GROUP_HELP);
187
158
 
159
+ addSetupCommand(group);
188
160
  addInitCommand(group);
189
161
  addInstallCommand(group);
190
162
  addStatusCommand(group);
@@ -192,8 +164,7 @@ function setupAgentKitCommands(program) {
192
164
  addValidateCommand(group);
193
165
  addRemoteCommand(group);
194
166
  addStartCommand(group);
195
- addListCommand(group);
196
- addKillCommand(group);
167
+ addSessionCommands(group);
197
168
  }
198
169
 
199
170
  /**
@@ -254,7 +225,7 @@ function addRemoteCommand(group) {
254
225
  .command('create')
255
226
  .description('Create a private GitHub repository (origin) and push this workspace')
256
227
  .option('--org <owner>', 'Hosting org or user (required when non-TTY)')
257
- .option('--name <repo>', 'Remote repository name (default: directory basename)')
228
+ .option('--name <repo>', 'New GitHub repository name (default: workspace folder name)')
258
229
  .option('--visibility <mode>', 'private or public', 'private')
259
230
  .option('--yes', 'Silent: skip TTY confirmation (pair with --org)')
260
231
  .option('--dry-run', 'Plan without creating the remote')
@@ -451,49 +422,4 @@ function addStartCommand(group) {
451
422
  });
452
423
  }
453
424
 
454
- /**
455
- * @param {import('commander').Command} group
456
- */
457
- function addListCommand(group) {
458
- group
459
- .command('list')
460
- .description('List active tmux sessions for the current user')
461
- .option('--json', 'Machine-readable output')
462
- .addHelpText('after', LIST_HELP)
463
- .action(async(options) => {
464
- try {
465
- emitListReport(runList(options), Boolean(options.json));
466
- } catch (error) {
467
- handleCommandError(error, 'agent-kit list');
468
- process.exitCode = 1;
469
- }
470
- });
471
- }
472
-
473
- /**
474
- * @param {import('commander').Command} group
475
- */
476
- function addKillCommand(group) {
477
- group
478
- .command('kill [name]')
479
- .description('Stop one tmux session by exact name')
480
- .option('--json', 'Machine-readable output')
481
- .addHelpText('after', KILL_HELP)
482
- .action(async(name, options) => {
483
- if (!name) {
484
- const report = formatMissingKillName(Boolean(options.json));
485
- logger.error(report.stderr);
486
- if (report.stdout) logger.log(report.stdout);
487
- process.exitCode = 1;
488
- return;
489
- }
490
- try {
491
- emitKillReport(runKill({ ...options, name }), Boolean(options.json));
492
- } catch (error) {
493
- handleCommandError(error, 'agent-kit kill');
494
- process.exitCode = 1;
495
- }
496
- });
497
- }
498
-
499
425
  module.exports = { setupAgentKitCommands };
@@ -29,13 +29,13 @@ const {
29
29
  isLinuxCaSudoRequiredError
30
30
  } = require('../utils/dev-ca-install');
31
31
  const { runOptionalHostsSetup } = require('../utils/dev-hosts-helper');
32
- const { mergeDevSshConfigAfterInit } = require('../utils/dev-init-ssh-merge');
32
+ const { mergeDevSshConfigAfterInit, hostnameFromBaseUrl } = require('../utils/dev-init-ssh-merge');
33
33
  const { resolveInitOptions } = require('../utils/dev-init-resolve');
34
34
  const {
35
35
  isHealthHttpServerError,
36
36
  formatEnsureServerTrustedFailure
37
37
  } = require('../utils/dev-init-health-messages');
38
- const { getBadRequestHint, logCertTroubleshootingHint } = require('../utils/dev-init-cert-hints');
38
+ const { getBadRequestHint } = require('../utils/dev-init-cert-hints');
39
39
  const { augmentAndSaveCaPemForRemoteDocker } = require('../utils/dev-ca-docker-augment');
40
40
 
41
41
  /**
@@ -249,7 +249,7 @@ async function _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, config
249
249
  } catch (err) {
250
250
  const msg = err.status === 400 ? getBadRequestHint() : (err.message || String(err));
251
251
  logger.log(chalk.yellow(' ⚠ Could not register SSH key: ' + msg));
252
- logCertTroubleshootingHint(configDir, devId, baseUrl);
252
+ throw new Error(`Onboarding incomplete for developer ${devId}: SSH key registration failed. Ask the server administrator to check role access, then retry dev init with a new PIN.`);
253
253
  }
254
254
  }
255
255
 
@@ -262,24 +262,23 @@ async function _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, config
262
262
  * @param {string} [serverCaPem] - Dev root CA for Node TLS
263
263
  */
264
264
  async function applySettingsFromServer(baseUrl, devId, issueResponse, keyPem, serverCaPem) {
265
- const configDir = getConfigDirForPaths();
266
- if (issueResponse.settings && typeof issueResponse.settings === 'object') {
267
- await config.mergeRemoteSettings(issueResponse.settings);
268
- logger.log(chalk.green(' ') + formatSuccessLine('Config updated from server (issue-cert response)'));
269
- return;
270
- }
271
- logger.log(chalk.gray(' Fetching settings...'));
272
265
  try {
273
- if (keyPem && typeof keyPem === 'string') {
274
- logger.log(chalk.gray(' Using client certificate for TLS'));
266
+ let settings = issueResponse.settings;
267
+ if (!settings) {
268
+ logger.log(chalk.gray(' Fetching settings...'));
269
+ settings = await devApi.getSettings(baseUrl, issueResponse.certificate, keyPem, serverCaPem);
275
270
  }
276
- const settings = await devApi.getSettings(baseUrl, issueResponse.certificate, keyPem, serverCaPem);
277
- await config.mergeRemoteSettings(settings);
271
+ if (!settings || typeof settings !== 'object' || Array.isArray(settings)) throw new Error('Server settings are missing or invalid.');
272
+ const fresh = { ...settings, 'developer-id': devId, 'remote-server': baseUrl,
273
+ 'sync-ssh-user': settings['sync-ssh-user'] || `dev${devId}`,
274
+ 'sync-ssh-host': settings['sync-ssh-host'] || hostnameFromBaseUrl(baseUrl) };
275
+ await config.mergeRemoteSettings(fresh);
278
276
  logger.log(chalk.green(' ') + formatSuccessLine('Config updated from server'));
277
+ return fresh;
279
278
  } catch (err) {
280
279
  const msg = err.status === 400 ? getBadRequestHint() : (err.message || String(err));
281
- logger.log(chalk.yellow(' ⚠ Could not fetch settings (server may not support cert yet): ' + msg));
282
- logCertTroubleshootingHint(configDir, devId, baseUrl);
280
+ logger.log(chalk.yellow(' ⚠ Could not fetch settings: ' + msg));
281
+ throw new Error(`Onboarding incomplete for developer ${devId}: current server settings could not be applied. Saved settings from another developer will not be used. Ask the server administrator to check role access, then retry dev init with a new PIN.`);
283
282
  }
284
283
  }
285
284
 
@@ -317,7 +316,7 @@ async function resolveServerCaPemForTls(baseUrl, options) {
317
316
  * @param {{ hostAlias: string|null, syncUser: string, syncHost: string|null }} p
318
317
  */
319
318
  function logOnboardingFinished({ hostAlias, syncUser, syncHost }) {
320
- logger.log(formatSuccessParagraph('Onboarding complete. You can use remote Docker and Mutagen sync.'));
319
+ logger.log(formatSuccessParagraph('Certificate, server settings and SSH key registration complete. Access depends on your server permissions.'));
321
320
  if (hostAlias) {
322
321
  logger.log(
323
322
  chalk.gray(' You can also open an SSH session on the builder with ') +
@@ -367,14 +366,13 @@ async function runDevInit(options) {
367
366
  );
368
367
  await saveCertAndConfig(configDir, devId, issueResponse.certificate, keyPem, caPem);
369
368
 
370
- await applySettingsFromServer(baseUrl, devId, issueResponse, keyPem, serverCaPemForTls || undefined);
371
- await augmentAndSaveCaPemForRemoteDocker(configDir, devId, caPem, {
372
- settings: issueResponse.settings,
373
- remoteServerUrl: baseUrl,
369
+ const settings = await applySettingsFromServer(baseUrl, devId, issueResponse, keyPem, serverCaPemForTls || undefined);
370
+ await _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, configDir, devId, serverCaPemForTls || undefined);
371
+ if (settings['docker-endpoint']) await augmentAndSaveCaPemForRemoteDocker(configDir, devId, caPem, {
372
+ settings,
374
373
  warn: (msg) => logger.log(chalk.yellow(` ⚠ ${msg}`))
375
374
  });
376
- await _runSshKeyRegistrationStep(baseUrl, issueResponse, keyPem, configDir, devId, serverCaPemForTls || undefined);
377
- const sshInfo = await mergeDevSshConfigAfterInit(baseUrl, devId);
375
+ const sshInfo = await mergeDevSshConfigAfterInit(baseUrl, devId, settings);
378
376
  logOnboardingFinished(sshInfo);
379
377
  }
380
378
 
@@ -82,9 +82,9 @@ async function runCertificateRefresh(auth) {
82
82
  const issueResponse = await requestCertificate(auth.serverUrl, devId, pin, csrPem, serverCaPem);
83
83
  const caPem = mergeCaPemBlocks(serverCaPem, issueResponse.caCertificate, issueResponse.ca);
84
84
  await saveCertAndConfig(configDir, devId, issueResponse.certificate, keyPem, caPem);
85
- await applySettingsFromServer(auth.serverUrl, devId, issueResponse, keyPem, serverCaPem);
85
+ const settings = await applySettingsFromServer(auth.serverUrl, devId, issueResponse, keyPem, serverCaPem);
86
86
  await augmentAndSaveCaPemForRemoteDocker(configDir, devId, caPem, {
87
- settings: issueResponse.settings,
87
+ settings,
88
88
  remoteServerUrl: auth.serverUrl,
89
89
  warn: (msg) => logger.log(chalk.yellow(` ⚠ ${msg}`))
90
90
  });
@@ -10,7 +10,7 @@ const {
10
10
  aggregateCoverage,
11
11
  buildPackOverrideResult
12
12
  } = require('./verify-governance-result-builders');
13
- const { VERDICT, verdictFromPassed } = require('../lifecycle/product-model');
13
+ const { VERDICT } = require('../lifecycle/product-model');
14
14
 
15
15
  /**
16
16
  * @async
@@ -43,14 +43,21 @@ async function runGovernanceVerifyForExternalSystem(systemKey, options = {}) {
43
43
  };
44
44
  }
45
45
 
46
- const allVerified = datasourceRows.every(r => r.verdict === VERDICT.VERIFIED);
46
+ const allNotApplicable = datasourceRows.every(
47
+ r => r.verdict === VERDICT.NOT_APPLICABLE
48
+ );
49
+ const allClosed = datasourceRows.every(
50
+ r => r.verdict === VERDICT.VERIFIED || r.verdict === VERDICT.NOT_APPLICABLE
51
+ );
47
52
  const coverage = aggregateCoverage(datasourceRows);
48
53
  return {
49
54
  systemKey: resolvedKey,
50
55
  command: 'governance verify',
51
- verdict: verdictFromPassed(allVerified),
52
- policyCoveragePercent: coverage.policy ?? (allVerified ? 100 : 0),
53
- dimensionCoveragePercent: coverage.dimension ?? (allVerified ? 100 : 0),
56
+ verdict: allNotApplicable
57
+ ? VERDICT.NOT_APPLICABLE
58
+ : (allClosed ? VERDICT.VERIFIED : VERDICT.FAILED),
59
+ policyCoveragePercent: coverage.policy ?? (allClosed ? 100 : 0),
60
+ dimensionCoveragePercent: coverage.dimension ?? (allClosed ? 100 : 0),
54
61
  enforcementScenarios: sumEnforcementScenarios(datasourceRows),
55
62
  datasourceRows
56
63
  };
@@ -16,11 +16,12 @@ const {
16
16
  repairNoneLlmToServiceContract,
17
17
  stripDeprecatedServiceCategory
18
18
  } = require('./repair-datasource-service-contract');
19
+ const {
20
+ DEFAULT_DATASOURCE_SYNC,
21
+ createDefaultDatasourceSync
22
+ } = require('../datasource/sync-defaults');
19
23
 
20
- const DEFAULT_SYNC = {
21
- mode: 'pull',
22
- batchSize: 500
23
- };
24
+ const DEFAULT_SYNC = DEFAULT_DATASOURCE_SYNC;
24
25
 
25
26
  const MINIMAL_METADATA_SCHEMA = {
26
27
  type: 'object',
@@ -348,7 +349,7 @@ function repairExposeFromAttributes(parsed, changes) {
348
349
  }
349
350
 
350
351
  /**
351
- * Adds default sync section if missing or empty. Not applied for entityType 'none'.
352
+ * Adds a default sync section only when absent. Not applied for entityType 'none'.
352
353
  *
353
354
  * @param {Object} parsed - Parsed datasource (mutated)
354
355
  * @param {string[]} changes - Change log
@@ -356,10 +357,9 @@ function repairExposeFromAttributes(parsed, changes) {
356
357
  */
357
358
  function repairSyncSection(parsed, changes) {
358
359
  if (isNoneEntityType(parsed?.entityType)) return false;
359
- const sync = parsed.sync;
360
- if (sync && typeof sync === 'object' && Object.keys(sync).length > 0) return false;
361
- parsed.sync = { ...DEFAULT_SYNC };
362
- changes.push('Added default sync section (mode: pull, batchSize: 500)');
360
+ if (Object.prototype.hasOwnProperty.call(parsed, 'sync')) return false;
361
+ parsed.sync = createDefaultDatasourceSync();
362
+ changes.push('Added default sync section (enabled: true, mode: pull, schedule: @monthly UTC, batchSize: 500)');
363
363
  return true;
364
364
  }
365
365