proteum 2.4.3 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -55
- package/agents/project/AGENTS.md +112 -31
- package/agents/project/CODING_STYLE.md +2 -2
- package/agents/project/app-root/AGENTS.md +1 -3
- package/agents/project/client/AGENTS.md +1 -1
- package/agents/project/client/pages/AGENTS.md +21 -9
- package/agents/project/diagnostics.md +2 -2
- package/agents/project/optimizations.md +1 -1
- package/agents/project/root/AGENTS.md +105 -22
- package/agents/project/server/routes/AGENTS.md +30 -1
- package/agents/project/tests/AGENTS.md +1 -1
- package/cli/commands/doctor.ts +54 -3
- package/cli/commands/runtime.ts +6 -0
- package/cli/commands/worktree.ts +116 -0
- package/cli/compiler/artifacts/controllers.ts +16 -15
- package/cli/compiler/artifacts/discovery.ts +129 -17
- package/cli/compiler/artifacts/routing.ts +0 -5
- package/cli/compiler/artifacts/services.ts +253 -76
- package/cli/compiler/common/controllers.ts +159 -57
- package/cli/compiler/common/generatedRouteModules.ts +457 -363
- package/cli/mcp/router.ts +47 -3
- package/cli/presentation/commands.ts +25 -15
- package/cli/runtime/commands.ts +39 -12
- package/cli/runtime/worktreeBootstrap.ts +608 -0
- package/cli/scaffold/index.ts +28 -18
- package/cli/scaffold/templates.ts +44 -33
- package/cli/utils/agents.ts +14 -1
- package/client/services/router/index.tsx +23 -3
- package/client/services/router/request/api.ts +14 -4
- package/common/dev/contractsDoctor.ts +1 -1
- package/common/dev/mcpPayloads.ts +8 -1
- package/common/env/proteumEnv.ts +14 -2
- package/common/router/contracts.ts +1 -1
- package/common/router/definitions.ts +177 -0
- package/common/router/index.ts +23 -12
- package/common/router/pageData.ts +5 -5
- package/common/router/register.ts +2 -2
- package/common/router/request/api.ts +12 -2
- package/docs/agent-routing.md +5 -2
- package/docs/diagnostics.md +2 -0
- package/docs/mcp.md +6 -3
- package/eslint.js +36 -1
- package/package.json +1 -1
- package/server/app/commands.ts +5 -1
- package/server/app/container/console/http-client-error-context.test.cjs +10 -1
- package/server/app/container/console/index.ts +2 -1
- package/server/app/controller/index.ts +98 -40
- package/server/app/index.ts +92 -1
- package/server/app/service/index.ts +5 -1
- package/server/index.ts +6 -2
- package/server/services/router/index.ts +47 -38
- package/server/services/router/response/index.ts +2 -2
- package/tests/agents-utils.test.cjs +14 -1
- package/tests/cli-mcp-command.test.cjs +84 -0
- package/tests/definition-contracts.test.cjs +453 -0
- package/tests/dev-transpile-watch.test.cjs +37 -28
- package/tests/eslint-rules.test.cjs +39 -1
- package/tests/mcp.test.cjs +90 -0
- package/tests/worktree-bootstrap.test.cjs +206 -0
- package/types/aliases.d.ts +0 -5
- package/types/controller-input.test.ts +23 -17
- package/types/controller-request-context.test.ts +10 -11
- package/cli/commands/migrate.ts +0 -51
- package/cli/migrate/pageContract.ts +0 -516
- package/docs/migrate-from-2.1.3.md +0 -396
- package/scripts/cleanup-generated-controllers.ts +0 -62
- package/scripts/fix-reference-app-typing.ts +0 -490
- package/scripts/format-router-registrations.ts +0 -119
- package/scripts/migrate-explicit-controllers-and-request.ts +0 -423
- package/scripts/refactor-client-app-imports.ts +0 -244
- package/scripts/refactor-client-pages.ts +0 -587
- package/scripts/refactor-server-controllers.ts +0 -471
- package/scripts/refactor-server-runtime-aliases.ts +0 -360
- package/scripts/restore-client-app-import-files.ts +0 -41
package/cli/mcp/router.ts
CHANGED
|
@@ -37,6 +37,11 @@ import {
|
|
|
37
37
|
type TProteumAppRootSummary,
|
|
38
38
|
} from '../utils/appRoots';
|
|
39
39
|
import { inspectDevPort, type TDevPortInspection } from '../runtime/ports';
|
|
40
|
+
import {
|
|
41
|
+
compactWorktreeBootstrapStatus,
|
|
42
|
+
createWorktreeBootstrapMcpBlockResponse,
|
|
43
|
+
getWorktreeBootstrapStatus,
|
|
44
|
+
} from '../runtime/worktreeBootstrap';
|
|
40
45
|
|
|
41
46
|
type TDevMcpClient = {
|
|
42
47
|
callTool: (input: { arguments?: Record<string, unknown>; name: string }) => Promise<CallToolResult>;
|
|
@@ -134,12 +139,17 @@ const createOfflineNextAction = async ({
|
|
|
134
139
|
baseRoot,
|
|
135
140
|
portInspection,
|
|
136
141
|
summary,
|
|
142
|
+
version,
|
|
137
143
|
}: {
|
|
138
144
|
appRoot: string;
|
|
139
145
|
baseRoot?: string;
|
|
140
146
|
portInspection?: TDevPortInspection;
|
|
141
147
|
summary: TProteumAppRootSummary;
|
|
148
|
+
version: string;
|
|
142
149
|
}) => {
|
|
150
|
+
const bootstrapStatus = getWorktreeBootstrapStatus({ appRoot, proteumVersion: version });
|
|
151
|
+
if (bootstrapStatus.blocking) return bootstrapStatus.nextAction;
|
|
152
|
+
|
|
143
153
|
if (!summary.manifest) {
|
|
144
154
|
return {
|
|
145
155
|
label: 'Check Runtime Status',
|
|
@@ -178,10 +188,12 @@ const compactOfflineProject = async ({
|
|
|
178
188
|
appRoot,
|
|
179
189
|
baseRoot,
|
|
180
190
|
matchReason,
|
|
191
|
+
version,
|
|
181
192
|
}: {
|
|
182
193
|
appRoot: string;
|
|
183
194
|
baseRoot?: string;
|
|
184
195
|
matchReason: string;
|
|
196
|
+
version: string;
|
|
185
197
|
}): Promise<TOfflineProject> => {
|
|
186
198
|
const summary = readProteumAppRootSummary(appRoot, baseRoot);
|
|
187
199
|
const portInspection = summary.manifest
|
|
@@ -196,7 +208,7 @@ const compactOfflineProject = async ({
|
|
|
196
208
|
devPort: portInspection,
|
|
197
209
|
live: false,
|
|
198
210
|
matchReason,
|
|
199
|
-
nextAction: await createOfflineNextAction({ appRoot: summary.appRoot, baseRoot, portInspection, summary }),
|
|
211
|
+
nextAction: await createOfflineNextAction({ appRoot: summary.appRoot, baseRoot, portInspection, summary, version }),
|
|
200
212
|
projectId: await resolveProteumProjectId(summary.appRoot),
|
|
201
213
|
state: 'offline',
|
|
202
214
|
};
|
|
@@ -297,7 +309,7 @@ export const createProteumMachineMcpServer = ({ createDevMcpClient, version }: T
|
|
|
297
309
|
if (!matches.has(record.projectId)) matches.set(record.projectId, { project: compactProjectMatch(record, reason), record });
|
|
298
310
|
};
|
|
299
311
|
const addOfflineMatch = async (appRoot: string, reason: string, baseRoot?: string) => {
|
|
300
|
-
const offline = await compactOfflineProject({ appRoot, baseRoot, matchReason: reason });
|
|
312
|
+
const offline = await compactOfflineProject({ appRoot, baseRoot, matchReason: reason, version });
|
|
301
313
|
if (!matches.has(offline.projectId)) matches.set(offline.projectId, { offline, project: offline });
|
|
302
314
|
};
|
|
303
315
|
const normalizedProjectId = typeof projectId === 'string' ? projectId.trim() : '';
|
|
@@ -386,16 +398,38 @@ export const createProteumMachineMcpServer = ({ createDevMcpClient, version }: T
|
|
|
386
398
|
return { error: null, record: inspection.record };
|
|
387
399
|
};
|
|
388
400
|
|
|
401
|
+
const augmentForwardedPayload = (result: CallToolResult, record: TMachineDevSessionRecord) => {
|
|
402
|
+
if (result.content[0]?.type !== 'text') return result;
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
const payload = JSON.parse(result.content[0].text);
|
|
406
|
+
const bootstrapStatus = getWorktreeBootstrapStatus({ appRoot: record.appRoot, proteumVersion: version });
|
|
407
|
+
|
|
408
|
+
return jsonToolResult({
|
|
409
|
+
...payload,
|
|
410
|
+
data: {
|
|
411
|
+
...(payload.data || {}),
|
|
412
|
+
worktreeBootstrap: compactWorktreeBootstrapStatus(bootstrapStatus),
|
|
413
|
+
},
|
|
414
|
+
});
|
|
415
|
+
} catch {
|
|
416
|
+
return result;
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
389
420
|
const forwardTool = async (name: string, input: Record<string, unknown>) => {
|
|
390
421
|
const resolution = await resolveProject(input.projectId);
|
|
391
422
|
if (!resolution.record) return resolution.error;
|
|
392
423
|
|
|
393
424
|
try {
|
|
394
425
|
const client = await getClient(resolution.record);
|
|
395
|
-
|
|
426
|
+
const result = await client.callTool({
|
|
396
427
|
arguments: stripProjectRouting(input),
|
|
397
428
|
name,
|
|
398
429
|
});
|
|
430
|
+
|
|
431
|
+
if (name === 'runtime_status' || name === 'doctor') return augmentForwardedPayload(result, resolution.record);
|
|
432
|
+
return result;
|
|
399
433
|
} catch (error) {
|
|
400
434
|
await closeClient(resolution.record);
|
|
401
435
|
return errorToolResult(`Could not reach Proteum dev MCP for ${resolution.record.projectId}.`, {
|
|
@@ -407,6 +441,9 @@ export const createProteumMachineMcpServer = ({ createDevMcpClient, version }: T
|
|
|
407
441
|
};
|
|
408
442
|
|
|
409
443
|
const createOfflineWorkflowStartResult = (offline: TOfflineProject, input: Record<string, unknown>) => {
|
|
444
|
+
const bootstrapStatus = getWorktreeBootstrapStatus({ appRoot: offline.appRoot, proteumVersion: version });
|
|
445
|
+
if (bootstrapStatus.blocking) return jsonToolResult(createWorktreeBootstrapMcpBlockResponse(bootstrapStatus, offline), true);
|
|
446
|
+
|
|
410
447
|
let manifest: ReturnType<typeof readProteumManifest>;
|
|
411
448
|
try {
|
|
412
449
|
manifest = readProteumManifest(offline.appRoot);
|
|
@@ -458,6 +495,7 @@ export const createProteumMachineMcpServer = ({ createDevMcpClient, version }: T
|
|
|
458
495
|
...payload,
|
|
459
496
|
data: {
|
|
460
497
|
project: offline,
|
|
498
|
+
worktreeBootstrap: compactWorktreeBootstrapStatus(bootstrapStatus),
|
|
461
499
|
...payload.data,
|
|
462
500
|
},
|
|
463
501
|
nextActions: [
|
|
@@ -497,6 +535,11 @@ export const createProteumMachineMcpServer = ({ createDevMcpClient, version }: T
|
|
|
497
535
|
});
|
|
498
536
|
}
|
|
499
537
|
|
|
538
|
+
const bootstrapStatus = getWorktreeBootstrapStatus({ appRoot: record.appRoot, proteumVersion: version });
|
|
539
|
+
if (bootstrapStatus.blocking) {
|
|
540
|
+
return jsonToolResult(createWorktreeBootstrapMcpBlockResponse(bootstrapStatus, compactProject(record)), true);
|
|
541
|
+
}
|
|
542
|
+
|
|
500
543
|
try {
|
|
501
544
|
const client = await getClient(record);
|
|
502
545
|
const result = await client.callTool({
|
|
@@ -525,6 +568,7 @@ export const createProteumMachineMcpServer = ({ createDevMcpClient, version }: T
|
|
|
525
568
|
...payload,
|
|
526
569
|
data: {
|
|
527
570
|
project: compactProject(record),
|
|
571
|
+
worktreeBootstrap: compactWorktreeBootstrapStatus(bootstrapStatus),
|
|
528
572
|
...payload.data,
|
|
529
573
|
},
|
|
530
574
|
...(routedNextActions && routedNextActions.length > 0 ? { nextActions: routedNextActions } : {}),
|
|
@@ -7,7 +7,7 @@ export const proteumCommandNames = [
|
|
|
7
7
|
'init',
|
|
8
8
|
'create',
|
|
9
9
|
'configure',
|
|
10
|
-
'
|
|
10
|
+
'worktree',
|
|
11
11
|
'dev',
|
|
12
12
|
'refresh',
|
|
13
13
|
'build',
|
|
@@ -61,7 +61,7 @@ export const proteumCommandGroups: Array<{ title: string; names: TProteumCommand
|
|
|
61
61
|
{ title: 'Daily workflow', names: ['dev', 'refresh', 'build'] },
|
|
62
62
|
{ title: 'Quality gates', names: ['typecheck', 'lint', 'check', 'e2e'] },
|
|
63
63
|
{ title: 'Manifest and contracts', names: ['connect', 'doctor', 'explain', 'orient', 'diagnose', 'perf', 'db', 'runtime', 'mcp', 'trace', 'command', 'session', 'verify'] },
|
|
64
|
-
{ title: 'Project scaffolding', names: ['init', 'configure', 'create', '
|
|
64
|
+
{ title: 'Project scaffolding', names: ['init', 'configure', 'create', 'worktree'] },
|
|
65
65
|
];
|
|
66
66
|
|
|
67
67
|
export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> = {
|
|
@@ -135,28 +135,38 @@ export const proteumCommands: Record<TProteumCommandName, TProteumCommandDoc> =
|
|
|
135
135
|
],
|
|
136
136
|
status: 'experimental',
|
|
137
137
|
},
|
|
138
|
-
|
|
139
|
-
name: '
|
|
138
|
+
worktree: {
|
|
139
|
+
name: 'worktree',
|
|
140
140
|
category: 'Project scaffolding',
|
|
141
|
-
summary: '
|
|
142
|
-
usage:
|
|
141
|
+
summary: 'Initialize or create Codex worktrees with a Proteum bootstrap marker.',
|
|
142
|
+
usage:
|
|
143
|
+
'proteum worktree <init|create> [target-repo-root] --source <app-root> [--branch <branch>] [--base <ref>] [--refresh] [--skip-deps --reason <text>] [--json]',
|
|
143
144
|
bestFor:
|
|
144
|
-
'
|
|
145
|
+
'Making newly created Codex worktrees explicit and machine-checkable before agents run refresh, runtime, dev, verify, or MCP workflow reads.',
|
|
145
146
|
examples: [
|
|
146
|
-
{ description: 'Rewrite the current app in place', command: 'proteum migrate page-contract' },
|
|
147
147
|
{
|
|
148
|
-
description: '
|
|
149
|
-
command: 'proteum
|
|
148
|
+
description: 'Initialize the current Codex worktree from a source app root',
|
|
149
|
+
command: 'proteum worktree init --source /path/to/main-app',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
description: 'Refresh a stale bootstrap marker',
|
|
153
|
+
command: 'proteum worktree init --source /path/to/main-app --refresh',
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
description: 'Create a new Git worktree and bootstrap the matching Proteum app root',
|
|
157
|
+
command:
|
|
158
|
+
'proteum worktree create /path/to/.codex/worktrees/feature --source /path/to/main-app --branch feature/worktree-bootstrap',
|
|
150
159
|
},
|
|
151
160
|
{
|
|
152
|
-
description: '
|
|
153
|
-
command: 'proteum
|
|
161
|
+
description: 'Record an intentional dependency-install skip',
|
|
162
|
+
command: 'proteum worktree init --source /path/to/main-app --skip-deps --reason "dependencies are shared by the parent workspace"',
|
|
154
163
|
},
|
|
155
164
|
],
|
|
156
165
|
notes: [
|
|
157
|
-
'
|
|
158
|
-
'
|
|
159
|
-
'
|
|
166
|
+
'Bootstrap writes `.proteum/worktree-bootstrap.json` with hashes, step timestamps, dependency status, runtime status, and Proteum version.',
|
|
167
|
+
'When `.env` is missing, `--source` is required and must point to an app root with a readable `.env`.',
|
|
168
|
+
'Guarded commands block inside `/.codex/worktrees/` until bootstrap is complete or explicitly bypassed with `PROTEUM_ALLOW_UNBOOTSTRAPPED_WORKTREE=1`.',
|
|
169
|
+
'`worktree create` preserves the source app root path relative to the source repository root, so monorepo app roots are bootstrapped in the matching target location.',
|
|
160
170
|
],
|
|
161
171
|
status: 'experimental',
|
|
162
172
|
},
|
package/cli/runtime/commands.ts
CHANGED
|
@@ -4,6 +4,7 @@ import path from 'path';
|
|
|
4
4
|
import cli, { type TArgsObject } from '../context';
|
|
5
5
|
import { applyLegacyBooleanArgs, assertNoLegacyArgs } from './argv';
|
|
6
6
|
import { buildUsage, ProteumCommand, runCommandModule } from './command';
|
|
7
|
+
import { createWorktreeBootstrapBlockResponse, getWorktreeBootstrapStatus } from './worktreeBootstrap';
|
|
7
8
|
import { createStartDevCommand, quoteShellPath, resolveProteumAppRootContext } from '../utils/appRoots';
|
|
8
9
|
import { printJson } from '../utils/agentOutput';
|
|
9
10
|
|
|
@@ -68,6 +69,19 @@ const printNonAppRootResponse = ({
|
|
|
68
69
|
|
|
69
70
|
const isCurrentWorkdirProteumAppRoot = () => resolveProteumAppRootContext(String(cli.args.workdir || process.cwd())).isAppRoot;
|
|
70
71
|
|
|
72
|
+
const blockIfWorktreeBootstrapRequired = () => {
|
|
73
|
+
const status = getWorktreeBootstrapStatus({
|
|
74
|
+
appRoot: cli.paths.appRoot,
|
|
75
|
+
proteumVersion: String(cli.packageJson.version || ''),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
if (!status.blocking) return false;
|
|
79
|
+
|
|
80
|
+
printJson(createWorktreeBootstrapBlockResponse(status));
|
|
81
|
+
process.exitCode = 1;
|
|
82
|
+
return true;
|
|
83
|
+
};
|
|
84
|
+
|
|
71
85
|
class InitCommand extends ProteumCommand {
|
|
72
86
|
public static paths = [['init']];
|
|
73
87
|
|
|
@@ -156,26 +170,35 @@ class ConfigureCommand extends ProteumCommand {
|
|
|
156
170
|
}
|
|
157
171
|
}
|
|
158
172
|
|
|
159
|
-
class
|
|
160
|
-
public static paths = [['
|
|
173
|
+
class WorktreeCommand extends ProteumCommand {
|
|
174
|
+
public static paths = [['worktree']];
|
|
161
175
|
|
|
162
|
-
public static usage = buildUsage('
|
|
176
|
+
public static usage = buildUsage('worktree');
|
|
163
177
|
|
|
164
|
-
public
|
|
165
|
-
public
|
|
166
|
-
public
|
|
178
|
+
public source = Option.String('--source', { description: 'Source Proteum app root used for .env copy and worktree creation.' });
|
|
179
|
+
public branch = Option.String('--branch', { description: 'Branch name created by `worktree create`.' });
|
|
180
|
+
public base = Option.String('--base', { description: 'Base ref used by `worktree create`.' });
|
|
181
|
+
public refresh = Option.Boolean('--refresh', false, { description: 'Refresh an existing stale bootstrap marker.' });
|
|
182
|
+
public skipDeps = Option.Boolean('--skip-deps', false, { description: 'Skip dependency install while recording an explicit reason.' });
|
|
183
|
+
public reason = Option.String('--reason', { description: 'Reason required when --skip-deps is used.' });
|
|
184
|
+
public json = Option.Boolean('--json', false, { description: 'Print machine-readable worktree bootstrap output.' });
|
|
167
185
|
public args = Option.Rest();
|
|
168
186
|
|
|
169
187
|
public async execute() {
|
|
170
|
-
const [action = ''] = this.args;
|
|
188
|
+
const [action = '', target = ''] = this.args;
|
|
171
189
|
|
|
172
190
|
this.setCliArgs({
|
|
173
191
|
action,
|
|
174
|
-
|
|
175
|
-
|
|
192
|
+
base: this.base ?? '',
|
|
193
|
+
branch: this.branch ?? '',
|
|
176
194
|
json: this.json,
|
|
195
|
+
reason: this.reason ?? '',
|
|
196
|
+
refresh: this.refresh,
|
|
197
|
+
skipDeps: this.skipDeps,
|
|
198
|
+
source: this.source ?? '',
|
|
199
|
+
target,
|
|
177
200
|
});
|
|
178
|
-
await runCommandModule(() => import('../commands/
|
|
201
|
+
await runCommandModule(() => import('../commands/worktree'));
|
|
179
202
|
}
|
|
180
203
|
}
|
|
181
204
|
|
|
@@ -222,6 +245,7 @@ class DevCommand extends ProteumCommand {
|
|
|
222
245
|
printNonAppRootResponse({ commandName: 'dev', cwd: String(cli.args.workdir || process.cwd()) });
|
|
223
246
|
return 1;
|
|
224
247
|
}
|
|
248
|
+
if (action !== 'stop' && blockIfWorktreeBootstrapRequired()) return 1;
|
|
225
249
|
await runCommandModule(() => import('../commands/dev'));
|
|
226
250
|
}
|
|
227
251
|
}
|
|
@@ -236,6 +260,7 @@ class RefreshCommand extends ProteumCommand {
|
|
|
236
260
|
public async execute() {
|
|
237
261
|
assertNoLegacyArgs('refresh', this.legacyArgs);
|
|
238
262
|
this.setCliArgs();
|
|
263
|
+
if (blockIfWorktreeBootstrapRequired()) return 1;
|
|
239
264
|
await runCommandModule(() => import('../commands/refresh'));
|
|
240
265
|
}
|
|
241
266
|
}
|
|
@@ -776,6 +801,7 @@ class RuntimeCommand extends ProteumCommand {
|
|
|
776
801
|
return 1;
|
|
777
802
|
}
|
|
778
803
|
|
|
804
|
+
if (blockIfWorktreeBootstrapRequired()) return 1;
|
|
779
805
|
await runCommandModule(() => import('../commands/runtime'));
|
|
780
806
|
}
|
|
781
807
|
}
|
|
@@ -869,6 +895,7 @@ class VerifyCommand extends ProteumCommand {
|
|
|
869
895
|
websitePort: this.websitePort ?? '',
|
|
870
896
|
});
|
|
871
897
|
|
|
898
|
+
if (blockIfWorktreeBootstrapRequired()) return 1;
|
|
872
899
|
await runCommandModule(() => import('../commands/verify'));
|
|
873
900
|
}
|
|
874
901
|
}
|
|
@@ -877,7 +904,7 @@ export const registeredCommands = {
|
|
|
877
904
|
init: InitCommand,
|
|
878
905
|
create: CreateCommand,
|
|
879
906
|
configure: ConfigureCommand,
|
|
880
|
-
|
|
907
|
+
worktree: WorktreeCommand,
|
|
881
908
|
dev: DevCommand,
|
|
882
909
|
refresh: RefreshCommand,
|
|
883
910
|
build: BuildCommand,
|
|
@@ -913,7 +940,7 @@ export const createCli = (version: string) => {
|
|
|
913
940
|
clipanion.register(InitCommand);
|
|
914
941
|
clipanion.register(CreateCommand);
|
|
915
942
|
clipanion.register(ConfigureCommand);
|
|
916
|
-
clipanion.register(
|
|
943
|
+
clipanion.register(WorktreeCommand);
|
|
917
944
|
clipanion.register(DevCommand);
|
|
918
945
|
clipanion.register(RefreshCommand);
|
|
919
946
|
clipanion.register(BuildCommand);
|