@ultimat3/cli 20.2.1 → 22.0.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/CLAUDE.md +84 -1582
- package/README.md +40 -2
- package/package.json +31 -31
- package/src/api-registration.ts +124 -0
- package/src/app-artifacts.ts +39 -0
- package/src/app-boundaries.ts +5 -10
- package/src/app-env.ts +2 -2
- package/src/app-load.ts +37 -5
- package/src/app-openapi.ts +27 -0
- package/src/app-permissions.ts +0 -0
- package/src/app-root.ts +1 -1
- package/src/async-pages.ts +32 -0
- package/src/bin.ts +7 -1
- package/src/boundary-findings.ts +36 -0
- package/src/browser-launcher-fake-html.ts +80 -0
- package/src/browser-launcher-fake.ts +165 -0
- package/src/browser-launcher-port.ts +159 -0
- package/src/browser-launcher.ts +26 -133
- package/src/budgets.ts +56 -13
- package/src/build-errors.ts +54 -0
- package/src/cdp-shot-a11y.ts +77 -0
- package/src/cdp-shot-clock.ts +14 -0
- package/src/cdp-shot-driver.ts +150 -0
- package/src/cdp-shot-element.ts +147 -0
- package/src/cdp-shot-errors.ts +62 -0
- package/src/cdp-shot-keys.ts +152 -0
- package/src/cdp-shot-page.ts +230 -0
- package/src/cdp-shot-watch.ts +241 -0
- package/src/cmd-affected-spec.ts +24 -0
- package/src/cmd-affected.ts +3 -19
- package/src/cmd-build-spec.ts +16 -0
- package/src/cmd-build.ts +46 -20
- package/src/cmd-ci-spec.ts +24 -0
- package/src/cmd-ci.ts +17 -21
- package/src/cmd-db-branch.ts +2 -2
- package/src/cmd-db-spec.ts +99 -0
- package/src/cmd-db.ts +15 -94
- package/src/cmd-deploy-helm.ts +136 -0
- package/src/cmd-deploy-spec.ts +40 -0
- package/src/cmd-deploy.ts +117 -42
- package/src/cmd-dev-spec.ts +25 -0
- package/src/cmd-dev.ts +78 -122
- package/src/cmd-docs-spec.ts +16 -0
- package/src/cmd-docs.ts +2 -11
- package/src/cmd-doctor-spec.ts +21 -0
- package/src/cmd-doctor.ts +4 -18
- package/src/cmd-env-spec.ts +18 -0
- package/src/cmd-env.ts +4 -13
- package/src/cmd-errors-spec.ts +23 -0
- package/src/cmd-errors.ts +3 -17
- package/src/cmd-fix-spec.ts +18 -0
- package/src/cmd-fix.ts +4 -13
- package/src/cmd-generate-spec.ts +31 -0
- package/src/cmd-generate.ts +46 -42
- package/src/cmd-i18n-spec.ts +16 -0
- package/src/cmd-i18n.ts +7 -14
- package/src/cmd-jobs-spec.ts +56 -0
- package/src/cmd-jobs.ts +3 -50
- package/src/cmd-manifest-spec.ts +15 -0
- package/src/cmd-manifest.ts +19 -20
- package/src/cmd-mcp-spec.ts +21 -0
- package/src/cmd-mcp.ts +2 -15
- package/src/cmd-new-spec.ts +33 -0
- package/src/cmd-new.ts +38 -33
- package/src/cmd-policy-spec.ts +13 -0
- package/src/cmd-policy.ts +2 -9
- package/src/cmd-pr-spec.ts +39 -0
- package/src/cmd-pr.ts +5 -65
- package/src/cmd-registries-spec.ts +32 -0
- package/src/cmd-registries.ts +4 -25
- package/src/cmd-routes-spec.ts +12 -0
- package/src/cmd-routes.ts +2 -7
- package/src/cmd-secrets-spec.ts +19 -0
- package/src/cmd-secrets.ts +22 -34
- package/src/cmd-shot-island.ts +12 -13
- package/src/cmd-shot-spec.ts +53 -0
- package/src/cmd-shot.ts +11 -60
- package/src/cmd-tasks-spec.ts +21 -0
- package/src/cmd-tasks.ts +2 -16
- package/src/cmd-test-spec.ts +54 -0
- package/src/cmd-test.ts +4 -49
- package/src/cmd-verify-spec.ts +28 -0
- package/src/cmd-verify.ts +3 -23
- package/src/db-seed.ts +4 -2
- package/src/dev-dashboard.ts +4 -4
- package/src/dev-lock.ts +18 -2
- package/src/dev-port.ts +21 -0
- package/src/dev-route-table.ts +119 -0
- package/src/dispatch.ts +12 -5
- package/src/document-styles.ts +1 -1
- package/src/drift.ts +4 -18
- package/src/error-catalog.ts +16 -9
- package/src/error-codes.ts +33 -28
- package/src/error-pages.ts +19 -7
- package/src/error-unthrown.ts +130 -0
- package/src/errors.ts +22 -29
- package/src/favicon.ts +2 -2
- package/src/fix-imports.ts +1 -1
- package/src/fix-scan.ts +2 -9
- package/src/flag-reads.ts +1 -2
- package/src/foreign-text.ts +36 -0
- package/src/framework-schema.ts +5 -6
- package/src/generate-feature.ts +42 -0
- package/src/generate-files.ts +7 -3
- package/src/generate-grants.ts +83 -0
- package/src/generate-kinds.ts +56 -4
- package/src/i18n-index.ts +59 -10
- package/src/icon-assets.ts +1 -1
- package/src/image-prepare.ts +14 -0
- package/src/import-scan.ts +63 -0
- package/src/index.ts +67 -334
- package/src/invocation-flags.ts +26 -0
- package/src/island-bundle.ts +38 -11
- package/src/island-capture.ts +6 -6
- package/src/island-harness-script.ts +6 -2
- package/src/island-realtime.ts +98 -0
- package/src/island-shot.ts +5 -0
- package/src/island-store.ts +131 -0
- package/src/island-verdict.ts +2 -2
- package/src/job-registration.ts +42 -0
- package/src/jobs-driver.ts +2 -2
- package/src/live-routes.ts +82 -42
- package/src/load-findings.ts +51 -0
- package/src/mcp-db-target.ts +1 -1
- package/src/mcp-errors.ts +30 -26
- package/src/mcp-host.ts +27 -12
- package/src/mcp-ui-diff.ts +27 -0
- package/src/mcp-ui-inspect.ts +4 -4
- package/src/mcp-ui-interact.ts +21 -15
- package/src/mcp-ui.ts +19 -15
- package/src/measure-database.ts +73 -0
- package/src/measure-paths.ts +74 -0
- package/src/measure-scope.ts +74 -0
- package/src/messages.ts +1 -3
- package/src/metrics-endpoint.ts +1 -1
- package/src/otlp-export.ts +1 -1
- package/src/output.ts +6 -0
- package/src/page-sync.ts +54 -0
- package/src/permission-grants.ts +86 -0
- package/src/prerender-out.ts +25 -0
- package/src/prerender.ts +121 -102
- package/src/pwa-artifacts.ts +3 -3
- package/src/realtime-browser-probe-fixture.ts +2 -2
- package/src/reexport-manifest.ts +2 -1
- package/src/registry.ts +80 -56
- package/src/role-realtime.ts +36 -0
- package/src/{dev-replicator.ts → role-replicator.ts} +1 -1
- package/src/{dev-roles-fixture.ts → role-start-fixture.ts} +9 -5
- package/src/role-start-types.ts +112 -0
- package/src/{dev-roles.ts → role-start.ts} +42 -115
- package/src/{dev-sync.ts → role-sync.ts} +23 -11
- package/src/root-env.ts +67 -0
- package/src/{dev-assets.ts → runtime-assets.ts} +7 -7
- package/src/{dev-services.ts → runtime-bindings.ts} +43 -17
- package/src/{dev-cache.ts → runtime-cache.ts} +2 -2
- package/src/runtime-jobs.ts +87 -0
- package/src/{dev-live-feed.ts → runtime-live-feed.ts} +20 -5
- package/src/{dev-notify-retention.ts → runtime-notify-retention.ts} +1 -1
- package/src/{dev-purge.ts → runtime-purge.ts} +2 -2
- package/src/{dev-queue.ts → runtime-queue.ts} +4 -4
- package/src/runtime-realtime.ts +55 -0
- package/src/{dev-render.ts → runtime-render.ts} +119 -20
- package/src/{dev-replica.ts → runtime-replica.ts} +2 -2
- package/src/{dev-runtime.ts → runtime-services.ts} +41 -17
- package/src/{dev-storage.ts → runtime-storage.ts} +4 -4
- package/src/scaffold-fixture.ts +28 -6
- package/src/scaffold-typecheck.ts +6 -3
- package/src/schema-drift.ts +7 -1
- package/src/script-csp.ts +5 -2
- package/src/secrets-rotation.ts +59 -0
- package/src/serve-boot.ts +192 -0
- package/src/serve-drain.ts +24 -0
- package/src/serve-entry.ts +6 -0
- package/src/serve-env.ts +116 -0
- package/src/serve-types.ts +55 -0
- package/src/serve.ts +44 -338
- package/src/shot-server.ts +2 -2
- package/src/shot-settle.ts +10 -1
- package/src/shot-theme.ts +3 -3
- package/src/shot-verdict.ts +16 -7
- package/src/signal-shred.ts +27 -0
- package/src/solid-loader.ts +26 -2
- package/src/static-report.ts +8 -1
- package/src/sw-artifacts.ts +13 -3
- package/src/sync-url.ts +31 -0
- package/src/templates/action.ts +30 -16
- package/src/templates/entity.ts +12 -7
- package/src/templates/index.ts +1 -1
- package/src/templates/job.ts +10 -7
- package/src/templates/policy.ts +20 -2
- package/src/templates/resource-create.ts +127 -0
- package/src/templates/resource-form-island.ts +76 -30
- package/src/templates/resource.ts +10 -4
- package/src/templates/route.ts +3 -0
- package/src/templates/scaffold-app.ts +4 -1
- package/src/templates/scaffold-auth.ts +3 -1
- package/src/templates/scaffold-container-compose.ts +184 -0
- package/src/templates/scaffold-container.ts +25 -145
- package/src/templates/scaffold-dashboard-example.ts +2 -2
- package/src/templates/scaffold-db-package.ts +16 -3
- package/src/templates/scaffold-demo-org.ts +41 -0
- package/src/templates/scaffold-entries.ts +1 -1
- package/src/templates/scaffold-env.ts +6 -0
- package/src/templates/scaffold-helm-templates.ts +66 -7
- package/src/templates/scaffold-helm.ts +27 -5
- package/src/templates/scaffold-i18n.ts +15 -10
- package/src/templates/scaffold-repo.ts +13 -10
- package/src/templates/scaffold-roles.ts +38 -10
- package/src/templates/slice-foundation.ts +1 -1
- package/src/templates/wrap.ts +4 -1
- package/src/test-passes.ts +2 -1
- package/src/test-workers.ts +26 -0
- package/src/ts-scan.ts +3 -6
- package/src/tsconfig-references.ts +1 -2
- package/src/verify-checks.ts +41 -39
- package/src/verify-e2e.ts +41 -0
- package/src/verify-run.ts +115 -50
- package/src/verify-step.ts +3 -3
- package/src/verify-tests.ts +22 -30
- package/src/verify-typecheck.ts +28 -0
- package/src/web-binding.ts +2 -2
- package/src/worker-bundle.ts +192 -0
- package/src/workspace-graph.ts +10 -33
- package/src/cdp-browser.ts +0 -100
- package/src/cdp-connection.ts +0 -211
- package/src/cdp-e2e-page.ts +0 -209
- package/src/cdp-errors.ts +0 -56
- package/src/cdp-launch.ts +0 -139
- package/src/e2e-dom-fixture.ts +0 -117
- package/src/e2e-driver.ts +0 -97
- package/src/e2e-errors.ts +0 -103
- package/src/e2e-evaluate.ts +0 -156
- package/src/e2e-locator.ts +0 -86
- package/src/e2e-page.ts +0 -150
- package/src/e2e-selection.ts +0 -182
- package/src/measurement-actor.ts +0 -26
- /package/src/{dev-hooks.ts → runtime-hooks.ts} +0 -0
package/src/cmd-jobs.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import type { JobDriver } from '@ultimat3/jobs';
|
|
8
8
|
import { cancelJob, createNatsDriver, createRedisDriver } from '@ultimat3/jobs';
|
|
9
9
|
import { requireAppRoot } from './app-root';
|
|
10
|
+
import { DRAIN_TARGETS, jobsSpec } from './cmd-jobs-spec';
|
|
10
11
|
import type { CliCommand, CommandContext } from './command';
|
|
11
12
|
import { BadFlagError, JobUnknownError, MissingPositionalError } from './errors';
|
|
12
13
|
import type { DrainOutcome } from './jobs-drain';
|
|
@@ -27,13 +28,7 @@ import { msg } from './messages';
|
|
|
27
28
|
import type { CommandResult } from './output';
|
|
28
29
|
import { flagBool, flagString } from './parse';
|
|
29
30
|
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* The drivers a drain may move work ONTO — every one of them durable, and that is the whole rule.
|
|
34
|
-
* Closed, and read three ways: the flag summary, the refusal, and the `memory` case below.
|
|
35
|
-
*/
|
|
36
|
-
export const DRAIN_TARGETS = ['redis', 'nats'] as const;
|
|
31
|
+
export { DRAIN_TARGETS, JOBS_SUBCOMMANDS } from './cmd-jobs-spec';
|
|
37
32
|
|
|
38
33
|
/**
|
|
39
34
|
* `memory` was on that list until 2026-09 and could not be: `createMemoryDriver()` is a `Map` in
|
|
@@ -252,49 +247,7 @@ async function runDrain(
|
|
|
252
247
|
}
|
|
253
248
|
|
|
254
249
|
export const jobsCommand: CliCommand = {
|
|
255
|
-
spec:
|
|
256
|
-
name: 'jobs',
|
|
257
|
-
summary: 'list, show, retry, cancel and drain the job queue',
|
|
258
|
-
usage:
|
|
259
|
-
'x jobs [ls|show <id>|retry <id>|cancel <id>|drain --to <driver>] [--queue q] [--state s] [--limit n] [--from-step name] [--reason text] [--to driver] [--dry-run] [--json]',
|
|
260
|
-
requiresApp: true,
|
|
261
|
-
subcommands: JOBS_SUBCOMMANDS,
|
|
262
|
-
// The bare `x jobs` lists; it never retries, cancels or drains anything.
|
|
263
|
-
defaultSubcommand: 'ls',
|
|
264
|
-
flags: [
|
|
265
|
-
{ name: 'queue', type: 'string', summary: 'filter by queue name' },
|
|
266
|
-
{ name: 'state', type: 'string', summary: 'filter by job state' },
|
|
267
|
-
{ name: 'limit', type: 'string', summary: 'max rows to return' },
|
|
268
|
-
{ name: 'name', type: 'string', summary: 'filter by job name' },
|
|
269
|
-
// Each of these is read by ONE subcommand — `retryJob`, `cancelJob`, `runDrain` — and says
|
|
270
|
-
// so in its own summary. The scope is what makes the parser refuse it anywhere else instead
|
|
271
|
-
// of accepting it and ignoring it: `x db gen --dry-run` parsed and wrote the migration.
|
|
272
|
-
{
|
|
273
|
-
name: 'from-step',
|
|
274
|
-
type: 'string',
|
|
275
|
-
summary: 'retry: drop this step so it re-executes',
|
|
276
|
-
subcommands: ['retry'],
|
|
277
|
-
},
|
|
278
|
-
{
|
|
279
|
-
name: 'reason',
|
|
280
|
-
type: 'string',
|
|
281
|
-
summary: 'cancel: why, recorded on the job',
|
|
282
|
-
subcommands: ['cancel'],
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
name: 'to',
|
|
286
|
-
type: 'string',
|
|
287
|
-
summary: `drain: target driver — ${DRAIN_TARGETS.join(', ')}`,
|
|
288
|
-
subcommands: ['drain'],
|
|
289
|
-
},
|
|
290
|
-
{
|
|
291
|
-
name: 'dry-run',
|
|
292
|
-
type: 'boolean',
|
|
293
|
-
summary: 'drain: report the plan, move nothing',
|
|
294
|
-
subcommands: ['drain'],
|
|
295
|
-
},
|
|
296
|
-
],
|
|
297
|
-
},
|
|
250
|
+
spec: jobsSpec,
|
|
298
251
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
299
252
|
const root = requireAppRoot('jobs', ctx.cwd).dir;
|
|
300
253
|
const sub = ctx.args.subcommand ?? 'ls';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// `x manifest`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-manifest.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const manifestSpec: CommandSpec = {
|
|
7
|
+
name: 'manifest',
|
|
8
|
+
summary: 'regenerate x.manifest.json and openapi.json from the code',
|
|
9
|
+
usage: 'x manifest [--check] [--json]',
|
|
10
|
+
requiresApp: true,
|
|
11
|
+
flags: [
|
|
12
|
+
{ name: 'check', type: 'boolean', summary: 'fail if the committed files are stale' },
|
|
13
|
+
{ name: 'openapi', type: 'boolean', summary: 'also write openapi.json', default: true },
|
|
14
|
+
],
|
|
15
|
+
};
|
package/src/cmd-manifest.ts
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import type { Manifest } from '@ultimat3/manifest';
|
|
7
7
|
import { assertNoDrift, MANIFEST_FILENAME } from '@ultimat3/manifest';
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
8
|
+
import { writeAppArtifacts } from './app-artifacts';
|
|
9
|
+
import { appManifest } from './app-manifest';
|
|
10
|
+
import { openApiStaleness } from './app-openapi';
|
|
10
11
|
import { requireAppRoot } from './app-root';
|
|
12
|
+
import { manifestSpec } from './cmd-manifest-spec';
|
|
11
13
|
import type { CliCommand, CommandContext } from './command';
|
|
12
14
|
import { msg } from './messages';
|
|
13
15
|
import type { CommandResult, Finding, JsonValue } from './output';
|
|
@@ -41,28 +43,24 @@ async function staleness(root: string, manifest: Manifest): Promise<Finding | un
|
|
|
41
43
|
}
|
|
42
44
|
|
|
43
45
|
export const manifestCommand: CliCommand = {
|
|
44
|
-
spec:
|
|
45
|
-
name: 'manifest',
|
|
46
|
-
summary: 'regenerate x.manifest.json and openapi.json from the code',
|
|
47
|
-
usage: 'x manifest [--check] [--json]',
|
|
48
|
-
requiresApp: true,
|
|
49
|
-
flags: [
|
|
50
|
-
{ name: 'check', type: 'boolean', summary: 'fail if the committed files are stale' },
|
|
51
|
-
{ name: 'openapi', type: 'boolean', summary: 'also write openapi.json', default: true },
|
|
52
|
-
],
|
|
53
|
-
},
|
|
46
|
+
spec: manifestSpec,
|
|
54
47
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
55
48
|
const root = requireAppRoot('manifest', ctx.cwd).dir;
|
|
56
49
|
const { manifest, findings } = await appManifest(root);
|
|
57
50
|
const counts = countsOf(manifest);
|
|
58
51
|
|
|
59
52
|
if (flagBool(ctx.args, 'check')) {
|
|
60
|
-
|
|
53
|
+
// BOTH files the command writes: `--check` compared only `x.manifest.json`, so a stale
|
|
54
|
+
// `openapi.json` — the one the typed client is generated from — read as fresh.
|
|
55
|
+
const stale = [
|
|
56
|
+
...[await staleness(root, manifest)].filter((one) => one !== undefined),
|
|
57
|
+
...(await openApiStaleness(root, manifest)),
|
|
58
|
+
];
|
|
61
59
|
return {
|
|
62
|
-
ok: stale ===
|
|
60
|
+
ok: stale.length === 0 && findings.length === 0,
|
|
63
61
|
command: 'manifest',
|
|
64
|
-
summary: stale ===
|
|
65
|
-
findings:
|
|
62
|
+
summary: stale.length === 0 ? msg('cli.manifest.fresh') : msg('cli.manifest.stale'),
|
|
63
|
+
findings: [...findings, ...stale],
|
|
66
64
|
data: { buildId: manifest.buildId, counts },
|
|
67
65
|
};
|
|
68
66
|
}
|
|
@@ -79,10 +77,11 @@ export const manifestCommand: CliCommand = {
|
|
|
79
77
|
};
|
|
80
78
|
}
|
|
81
79
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
80
|
+
await writeAppArtifacts(root, manifest, {
|
|
81
|
+
openapi: ctx.args.flags.get('openapi') !== false,
|
|
82
|
+
onlyExisting: false,
|
|
83
|
+
});
|
|
84
|
+
const path = join(root, MANIFEST_FILENAME);
|
|
86
85
|
return {
|
|
87
86
|
ok: true,
|
|
88
87
|
command: 'manifest',
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// `x mcp`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-mcp.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const DEFAULT_PORT = 9229;
|
|
7
|
+
|
|
8
|
+
export const mcpSpec: CommandSpec = {
|
|
9
|
+
name: 'mcp',
|
|
10
|
+
summary: 'serve the dev tools: routes, schema, policies, db, queues, logs, tests, verify',
|
|
11
|
+
usage: 'x mcp tools | x mcp serve [--transport stdio|http] [--port 9229] [--json]',
|
|
12
|
+
requiresApp: true,
|
|
13
|
+
subcommands: ['serve', 'tools'],
|
|
14
|
+
// No default, deliberately: `wiki/CLI-Reference.md` already says to write `x mcp serve` rather
|
|
15
|
+
// than a bare `x mcp`, and the parser's old first-element guess made the bare form START A
|
|
16
|
+
// SERVER — the one thing a word typed by mistake must not do. Refusing enforces the guidance.
|
|
17
|
+
flags: [
|
|
18
|
+
{ name: 'transport', type: 'string', summary: 'stdio | http', default: 'stdio' },
|
|
19
|
+
{ name: 'port', type: 'string', summary: 'HTTP port', default: String(DEFAULT_PORT) },
|
|
20
|
+
],
|
|
21
|
+
};
|
package/src/cmd-mcp.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import { markListening, nanoid, timingSafeEqual } from '@ultimat3/core';
|
|
7
7
|
import { mcpHttpRoute, serveStdio } from '@ultimat3/mcp';
|
|
8
8
|
import { requireAppRoot } from './app-root';
|
|
9
|
+
import { DEFAULT_PORT, mcpSpec } from './cmd-mcp-spec';
|
|
9
10
|
import type { CliCommand, CommandContext } from './command';
|
|
10
11
|
import { BadFlagError } from './errors';
|
|
11
12
|
import { intFlagOr, PORT_RANGE } from './flag-number';
|
|
@@ -16,7 +17,6 @@ import { msg } from './messages';
|
|
|
16
17
|
import type { CommandResult } from './output';
|
|
17
18
|
import { flagString } from './parse';
|
|
18
19
|
|
|
19
|
-
const DEFAULT_PORT = 9229;
|
|
20
20
|
const TRANSPORTS = ['stdio', 'http'] as const;
|
|
21
21
|
type Transport = (typeof TRANSPORTS)[number];
|
|
22
22
|
|
|
@@ -156,20 +156,7 @@ const readPort = (ctx: CommandContext): number =>
|
|
|
156
156
|
);
|
|
157
157
|
|
|
158
158
|
export const mcpCommand: CliCommand = {
|
|
159
|
-
spec:
|
|
160
|
-
name: 'mcp',
|
|
161
|
-
summary: 'serve the dev tools: routes, schema, policies, db, queues, logs, tests, verify',
|
|
162
|
-
usage: 'x mcp tools | x mcp serve [--transport stdio|http] [--port 9229] [--json]',
|
|
163
|
-
requiresApp: true,
|
|
164
|
-
subcommands: ['serve', 'tools'],
|
|
165
|
-
// No default, deliberately: `wiki/CLI-Reference.md` already says to write `x mcp serve` rather
|
|
166
|
-
// than a bare `x mcp`, and the parser's old first-element guess made the bare form START A
|
|
167
|
-
// SERVER — the one thing a word typed by mistake must not do. Refusing enforces the guidance.
|
|
168
|
-
flags: [
|
|
169
|
-
{ name: 'transport', type: 'string', summary: 'stdio | http', default: 'stdio' },
|
|
170
|
-
{ name: 'port', type: 'string', summary: 'HTTP port', default: String(DEFAULT_PORT) },
|
|
171
|
-
],
|
|
172
|
-
},
|
|
159
|
+
spec: mcpSpec,
|
|
173
160
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
174
161
|
const root = requireAppRoot('mcp', ctx.cwd).dir;
|
|
175
162
|
const transport = flagString(ctx.args, 'transport') ?? 'stdio';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// `x new`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-new.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const newSpec: CommandSpec = {
|
|
7
|
+
name: 'new',
|
|
8
|
+
summary: 'scaffold a new Ultimate monorepo that already runs',
|
|
9
|
+
// Every flag the table below declares, in the spelling that turns it off where the default is
|
|
10
|
+
// on: the usage line offered `--no-example` while the table listed `--example`, and a reader
|
|
11
|
+
// had to reconcile the two to answer "which one do I get if I type neither".
|
|
12
|
+
usage: 'x new <name> [--dir path] [--no-example] [--no-git] [--dry-run] [--force] [--json]',
|
|
13
|
+
flags: [
|
|
14
|
+
{ name: 'dir', type: 'string', summary: 'parent directory (default: cwd)' },
|
|
15
|
+
{
|
|
16
|
+
// The summary carries the default and the negation because the page has to answer "which
|
|
17
|
+
// one do I get if I type neither": the usage line offered `--no-example`, this table said
|
|
18
|
+
// `--example`, and `default: true` is a field only `--json` renders. 136 files against 109.
|
|
19
|
+
name: 'example',
|
|
20
|
+
type: 'boolean',
|
|
21
|
+
summary: 'include the example feature slice (default: on; --no-example for an empty app/)',
|
|
22
|
+
default: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
name: 'git',
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
summary: 'git init and commit the scaffold (default: on; --no-git for a bare directory)',
|
|
28
|
+
default: true,
|
|
29
|
+
},
|
|
30
|
+
{ name: 'dry-run', type: 'boolean', summary: 'print the file list, write nothing' },
|
|
31
|
+
{ name: 'force', type: 'boolean', summary: 'write into a directory that already exists' },
|
|
32
|
+
],
|
|
33
|
+
};
|
package/src/cmd-new.ts
CHANGED
|
@@ -7,13 +7,16 @@ import { chmod } from 'node:fs/promises';
|
|
|
7
7
|
import { isAbsolute, join, resolve } from 'node:path';
|
|
8
8
|
import { ERROR_DOCS_URL, renderThrowable } from '@ultimat3/core';
|
|
9
9
|
import { dedupe } from './cmd-generate';
|
|
10
|
+
import { newSpec } from './cmd-new-spec';
|
|
10
11
|
import type { CliCommand, CommandContext } from './command';
|
|
11
12
|
import { invocationOf } from './command';
|
|
12
|
-
import { AppNameIsPathError, MissingPositionalError } from './errors';
|
|
13
|
+
import { AppNameEmptyError, AppNameIsPathError, MissingPositionalError } from './errors';
|
|
13
14
|
import type { Runner } from './exec';
|
|
15
|
+
import { reproducedFlags } from './invocation-flags';
|
|
14
16
|
import { msg } from './messages';
|
|
15
17
|
import type { CommandResult } from './output';
|
|
16
18
|
import { flagBool, flagString } from './parse';
|
|
19
|
+
import { quoteArg } from './shell-quote';
|
|
17
20
|
import type { GeneratedFile } from './templates';
|
|
18
21
|
import { appFiles, EXECUTABLE_FILES, names, repoFiles, resourceFiles } from './templates';
|
|
19
22
|
import { loadVersion } from './version-loader';
|
|
@@ -59,6 +62,17 @@ const GIT_STEPS: readonly (readonly string[])[] = [
|
|
|
59
62
|
/** What `--no-git` records, so `data.git` has the same shape whichever way the flag went. */
|
|
60
63
|
const NO_GIT: RepositoryInit = { initialized: false, committed: false, problem: SKIPPED };
|
|
61
64
|
|
|
65
|
+
/** `--force` into a directory that already existed: its files are not ours to commit. */
|
|
66
|
+
const PRE_EXISTING: RepositoryInit = {
|
|
67
|
+
initialized: false,
|
|
68
|
+
committed: false,
|
|
69
|
+
problem: 'the directory already existed, so nothing was committed — its files are yours',
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/** Every flag the caller set, in the spelling that reproduces it (`invocation-flags.ts`). */
|
|
73
|
+
const callerFlags = (ctx: CommandContext): readonly string[] =>
|
|
74
|
+
reproducedFlags(newCommand.spec, ctx.args);
|
|
75
|
+
|
|
62
76
|
/**
|
|
63
77
|
* A scaffold is a REPOSITORY, because three surfaces of this CLI already assume one and answered
|
|
64
78
|
* `not a git repository` in a fresh app: `x affected`, `x ci` and `x pr`. It was four —
|
|
@@ -151,34 +165,7 @@ export function appNamePath(raw: string): { parent: string; base: string } | und
|
|
|
151
165
|
}
|
|
152
166
|
|
|
153
167
|
export const newCommand: CliCommand = {
|
|
154
|
-
spec:
|
|
155
|
-
name: 'new',
|
|
156
|
-
summary: 'scaffold a new Ultimate monorepo that already runs',
|
|
157
|
-
// Every flag the table below declares, in the spelling that turns it off where the default is
|
|
158
|
-
// on: the usage line offered `--no-example` while the table listed `--example`, and a reader
|
|
159
|
-
// had to reconcile the two to answer "which one do I get if I type neither".
|
|
160
|
-
usage: 'x new <name> [--dir path] [--no-example] [--no-git] [--dry-run] [--force] [--json]',
|
|
161
|
-
flags: [
|
|
162
|
-
{ name: 'dir', type: 'string', summary: 'parent directory (default: cwd)' },
|
|
163
|
-
{
|
|
164
|
-
// The summary carries the default and the negation because the page has to answer "which
|
|
165
|
-
// one do I get if I type neither": the usage line offered `--no-example`, this table said
|
|
166
|
-
// `--example`, and `default: true` is a field only `--json` renders. 136 files against 109.
|
|
167
|
-
name: 'example',
|
|
168
|
-
type: 'boolean',
|
|
169
|
-
summary: 'include the example feature slice (default: on; --no-example for an empty app/)',
|
|
170
|
-
default: true,
|
|
171
|
-
},
|
|
172
|
-
{
|
|
173
|
-
name: 'git',
|
|
174
|
-
type: 'boolean',
|
|
175
|
-
summary: 'git init and commit the scaffold (default: on; --no-git for a bare directory)',
|
|
176
|
-
default: true,
|
|
177
|
-
},
|
|
178
|
-
{ name: 'dry-run', type: 'boolean', summary: 'print the file list, write nothing' },
|
|
179
|
-
{ name: 'force', type: 'boolean', summary: 'write into a directory that already exists' },
|
|
180
|
-
],
|
|
181
|
-
},
|
|
168
|
+
spec: newSpec,
|
|
182
169
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
183
170
|
const raw = ctx.args.positionals[0];
|
|
184
171
|
// The class, not a hand-built finding with the same code: `MissingPositionalError` is what
|
|
@@ -202,6 +189,13 @@ export const newCommand: CliCommand = {
|
|
|
202
189
|
throw new AppNameIsPathError({ name: raw, invocation: invocationOf(ctx, 'new'), ...path });
|
|
203
190
|
}
|
|
204
191
|
const app = names(raw);
|
|
192
|
+
if (app.kebab === '') {
|
|
193
|
+
throw new AppNameEmptyError({
|
|
194
|
+
name: raw,
|
|
195
|
+
invocation: invocationOf(ctx, 'new'),
|
|
196
|
+
flags: callerFlags(ctx),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
205
199
|
const target = resolve(parentDir(ctx.cwd, flagString(ctx.args, 'dir')), app.kebab);
|
|
206
200
|
const options: NewAppOptions = { name: raw, example: ctx.args.flags.get('example') !== false };
|
|
207
201
|
|
|
@@ -224,22 +218,33 @@ export const newCommand: CliCommand = {
|
|
|
224
218
|
{
|
|
225
219
|
code: 'X_GENERATE_CONFLICT',
|
|
226
220
|
cause: `${target} already exists`,
|
|
227
|
-
|
|
221
|
+
// The invocation the caller ran, with every flag it set: dropping `--dir` wrote a
|
|
222
|
+
// second app into the cwd, and the literal `x` is not installed under create-ultimate.
|
|
223
|
+
fix: `${[invocationOf(ctx, 'new'), quoteArg(app.kebab), ...callerFlags(ctx), '--force'].join(' ')} # or choose another name`,
|
|
228
224
|
docs: ERROR_DOCS_URL,
|
|
229
225
|
at: target,
|
|
230
226
|
},
|
|
231
227
|
],
|
|
232
228
|
};
|
|
233
229
|
}
|
|
230
|
+
// Read BEFORE writing: `--force` into a directory that already held files must never
|
|
231
|
+
// `git add -A && git commit` them — they are the user's, and `x new` did not write them.
|
|
232
|
+
const existed = existsSync(target);
|
|
234
233
|
const written = await writeNewApp(target, options);
|
|
235
234
|
const git =
|
|
236
|
-
ctx.args.flags.get('git') === false
|
|
235
|
+
ctx.args.flags.get('git') === false
|
|
236
|
+
? NO_GIT
|
|
237
|
+
: existed
|
|
238
|
+
? PRE_EXISTING
|
|
239
|
+
: await initRepository(ctx.runner, target);
|
|
237
240
|
const lines = [msg('cli.new.wrote', { count: written.files.length, dir: target })];
|
|
238
|
-
if (git.problem !== null && git.problem !== SKIPPED) {
|
|
241
|
+
if (git.problem !== null && git.problem !== SKIPPED && git !== PRE_EXISTING) {
|
|
239
242
|
lines.push(msg('cli.new.noRepository', { problem: git.problem }));
|
|
240
243
|
// Raw, unlike the two prose lines around it: this one is an instruction to run verbatim, and
|
|
241
244
|
// a translated command is a broken one (`packages/cli/CLAUDE.md`).
|
|
242
|
-
lines.push(
|
|
245
|
+
lines.push(
|
|
246
|
+
` run: cd ${quoteArg(target)} && git init && git add -A && git commit -m 'x new'`,
|
|
247
|
+
);
|
|
243
248
|
}
|
|
244
249
|
return {
|
|
245
250
|
ok: true,
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// `x policy`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-policy.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const policySpec: CommandSpec = {
|
|
7
|
+
name: 'policy',
|
|
8
|
+
summary: 'which clause decided a permission, and why',
|
|
9
|
+
usage: 'x policy [list|explain <subject>] [--json]',
|
|
10
|
+
requiresApp: true,
|
|
11
|
+
subcommands: ['list', 'explain'],
|
|
12
|
+
defaultSubcommand: 'list',
|
|
13
|
+
};
|
package/src/cmd-policy.ts
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
import { nearestName } from '@ultimat3/core';
|
|
6
6
|
import { loadApp } from './app-load';
|
|
7
7
|
import { requireAppRoot } from './app-root';
|
|
8
|
+
import { policySpec } from './cmd-policy-spec';
|
|
8
9
|
import type { CliCommand, CommandContext } from './command';
|
|
9
10
|
import { DeclarationUnknownError, MissingPositionalError } from './errors';
|
|
10
11
|
import { msg } from './messages';
|
|
11
12
|
import type { CommandResult, Finding, JsonValue } from './output';
|
|
12
|
-
|
|
13
13
|
import type { DeclarationExplanation } from './policy-facts';
|
|
14
14
|
import { explainPolicy, knownPolicySubjects, listPolicy } from './policy-facts';
|
|
15
15
|
import { renderTable } from './table';
|
|
@@ -123,14 +123,7 @@ function runExplain(ctx: CommandContext, findings: readonly Finding[]): CommandR
|
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
export const policyCommand: CliCommand = {
|
|
126
|
-
spec:
|
|
127
|
-
name: 'policy',
|
|
128
|
-
summary: 'which clause decided a permission, and why',
|
|
129
|
-
usage: 'x policy [list|explain <subject>] [--json]',
|
|
130
|
-
requiresApp: true,
|
|
131
|
-
subcommands: ['list', 'explain'],
|
|
132
|
-
defaultSubcommand: 'list',
|
|
133
|
-
},
|
|
126
|
+
spec: policySpec,
|
|
134
127
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
135
128
|
const root = requireAppRoot('policy', ctx.cwd).dir;
|
|
136
129
|
const { findings } = await loadApp(root);
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// `x pr`'s declaration, apart from its body: the parser, `x help` and the `errors` step
|
|
2
|
+
// read it without loading `cmd-pr.ts`, which `registry.ts` imports only when the command runs.
|
|
3
|
+
|
|
4
|
+
import type { CommandSpec } from './parse';
|
|
5
|
+
|
|
6
|
+
export const PR_SUBCOMMANDS = ['review', 'resolve', 'reply'] as const;
|
|
7
|
+
|
|
8
|
+
export const prSpec: CommandSpec = {
|
|
9
|
+
name: 'pr',
|
|
10
|
+
summary: 'inline review threads: list them with their ids, resolve one, reply in one',
|
|
11
|
+
usage:
|
|
12
|
+
'x pr review [--pr <n>] [--repo owner/name] [--all] [--full] | x pr resolve <thread-id> | x pr reply <thread-id> --body "…"',
|
|
13
|
+
subcommands: PR_SUBCOMMANDS,
|
|
14
|
+
flags: [
|
|
15
|
+
{ name: 'repo', type: 'string', summary: 'owner/name; the checkout own remote by default' },
|
|
16
|
+
{ name: 'pr', type: 'string', summary: 'pull request number; this branch own by default' },
|
|
17
|
+
// Scoped to the subcommand each summary already names: `resolve` and `reply` WRITE to
|
|
18
|
+
// somebody else's pull request, and a flag they silently ignore is a flag whose caller
|
|
19
|
+
// believed it did something to a request that cannot be re-run.
|
|
20
|
+
{
|
|
21
|
+
name: 'all',
|
|
22
|
+
type: 'boolean',
|
|
23
|
+
summary: 'review: resolved threads too, not just open ones',
|
|
24
|
+
subcommands: ['review'],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'full',
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
summary: 'review: whole comment bodies, never truncated',
|
|
30
|
+
subcommands: ['review'],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'body',
|
|
34
|
+
type: 'string',
|
|
35
|
+
summary: 'reply: the comment text to post in the thread',
|
|
36
|
+
subcommands: ['reply'],
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
package/src/cmd-pr.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// `reviewDecision` outlives the push that answered it, and a RESOLVED thread is a closed
|
|
6
6
|
// conversation rather than a fixed finding.
|
|
7
7
|
|
|
8
|
+
import { PR_SUBCOMMANDS, prSpec } from './cmd-pr-spec';
|
|
8
9
|
import type { CliCommand, CommandContext } from './command';
|
|
9
10
|
import {
|
|
10
11
|
BadFlagError,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
UnknownCommandError,
|
|
14
15
|
} from './errors';
|
|
15
16
|
import { parseIntFlag } from './flag-number';
|
|
17
|
+
import { commentBlock } from './foreign-text';
|
|
16
18
|
import { PrNotFoundError, resolvePrNumber, resolveRepo } from './gh-target';
|
|
17
19
|
import { msg } from './messages';
|
|
18
20
|
import type { CommandResult, JsonValue } from './output';
|
|
@@ -20,7 +22,7 @@ import { flagBool, flagString } from './parse';
|
|
|
20
22
|
import type { PrReviewReport, PrThread } from './pr-threads';
|
|
21
23
|
import { fetchReviewReport, replyToThread, resolveThread, THREAD_PAGE } from './pr-threads';
|
|
22
24
|
|
|
23
|
-
export
|
|
25
|
+
export { PR_SUBCOMMANDS } from './cmd-pr-spec';
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Every catalog key this command renders, declared. `msg()` answers `⟦key⟧` for a key nobody
|
|
@@ -51,38 +53,7 @@ export const PR_MESSAGE_KEYS = [
|
|
|
51
53
|
export const BODY_LINES = 20;
|
|
52
54
|
|
|
53
55
|
export const prCommand: CliCommand = {
|
|
54
|
-
spec:
|
|
55
|
-
name: 'pr',
|
|
56
|
-
summary: 'inline review threads: list them with their ids, resolve one, reply in one',
|
|
57
|
-
usage:
|
|
58
|
-
'x pr review [--pr <n>] [--repo owner/name] [--all] [--full] | x pr resolve <thread-id> | x pr reply <thread-id> --body "…"',
|
|
59
|
-
subcommands: PR_SUBCOMMANDS,
|
|
60
|
-
flags: [
|
|
61
|
-
{ name: 'repo', type: 'string', summary: 'owner/name; the checkout own remote by default' },
|
|
62
|
-
{ name: 'pr', type: 'string', summary: 'pull request number; this branch own by default' },
|
|
63
|
-
// Scoped to the subcommand each summary already names: `resolve` and `reply` WRITE to
|
|
64
|
-
// somebody else's pull request, and a flag they silently ignore is a flag whose caller
|
|
65
|
-
// believed it did something to a request that cannot be re-run.
|
|
66
|
-
{
|
|
67
|
-
name: 'all',
|
|
68
|
-
type: 'boolean',
|
|
69
|
-
summary: 'review: resolved threads too, not just open ones',
|
|
70
|
-
subcommands: ['review'],
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: 'full',
|
|
74
|
-
type: 'boolean',
|
|
75
|
-
summary: 'review: whole comment bodies, never truncated',
|
|
76
|
-
subcommands: ['review'],
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
name: 'body',
|
|
80
|
-
type: 'string',
|
|
81
|
-
summary: 'reply: the comment text to post in the thread',
|
|
82
|
-
subcommands: ['reply'],
|
|
83
|
-
},
|
|
84
|
-
],
|
|
85
|
-
},
|
|
56
|
+
spec: prSpec,
|
|
86
57
|
async run(ctx: CommandContext): Promise<CommandResult> {
|
|
87
58
|
// No `defaultSubcommand`: `resolve` and `reply` both WRITE to a pull request, so "whatever the
|
|
88
59
|
// caller left out" is not a safe guess for any of the three.
|
|
@@ -201,38 +172,7 @@ function threadLines(thread: PrThread, bodyLines: number): readonly string[] {
|
|
|
201
172
|
return out;
|
|
202
173
|
}
|
|
203
174
|
|
|
204
|
-
|
|
205
|
-
const BLOCK_CLOSE = '</comment>';
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* The fence, as a READER would parse it rather than as this file spells it.
|
|
209
|
-
*
|
|
210
|
-
* Two literal `replaceAll`s were the whole neutralisation, and markup is not spelled one way:
|
|
211
|
-
* `</comment >`, `</COMMENT>` and `< comment id=` all end or open a block for anything reading
|
|
212
|
-
* tags, and none of the three matched. One pattern over `<`, an optional `/`, and whitespace
|
|
213
|
-
* around a case-insensitive `comment` covers every spelling of the delimiter; the escape goes on
|
|
214
|
-
* the `<`, so what the reviewer wrote after it survives byte for byte.
|
|
215
|
-
*/
|
|
216
|
-
const BLOCK_DELIMITER = /<(\s*\/?\s*comment\b)/gi;
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* One comment body, fenced and labelled with the thread id it came from — `@ultimat3/ai`'s
|
|
220
|
-
* `documentBlock` (`rag.ts`), applied to the other place foreign text enters an agent's context.
|
|
221
|
-
* `x pr review` exists because an agent cannot read the GitHub web UI, and a review body is
|
|
222
|
-
* written by anyone who can comment on the pull request: rendered as bare indented text it arrived
|
|
223
|
-
* in that agent's context indistinguishable from the command's own output, which is prompt
|
|
224
|
-
* injection with a shell attached.
|
|
225
|
-
*
|
|
226
|
-
* The fence is neutralised INSIDE the payload rather than deleted, so every word the reviewer
|
|
227
|
-
* wrote still reads, and the label is stripped of the three characters that would end the
|
|
228
|
-
* attribute. Influence only, and deliberately not sold as more: a fence tells a reader this text
|
|
229
|
-
* is data, and it can never stop one that decides otherwise.
|
|
230
|
-
*/
|
|
231
|
-
export function commentBlock(id: string, lines: readonly string[]): readonly string[] {
|
|
232
|
-
const label = id.replaceAll('"', "'").replaceAll('>', ')').replaceAll('<', '(');
|
|
233
|
-
const body = lines.map((line) => line.replace(BLOCK_DELIMITER, '<\\$1'));
|
|
234
|
-
return [`${BLOCK_OPEN}"${label}">`, ...body, BLOCK_CLOSE];
|
|
235
|
-
}
|
|
175
|
+
export { commentBlock };
|
|
236
176
|
|
|
237
177
|
/**
|
|
238
178
|
* A review body is prose written for a browser: the ones in this repo run to six thousand
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// `x actions`, `x queries` and `x entities`' declarations, apart from their body: the parser, `x help`
|
|
2
|
+
// and the `errors` step read them without loading `cmd-registries.ts` and the three registries it
|
|
3
|
+
// projects, which `registry.ts` imports only when one of the commands runs.
|
|
4
|
+
|
|
5
|
+
import type { CommandSpec } from './parse';
|
|
6
|
+
|
|
7
|
+
export const actionsSpec: CommandSpec = {
|
|
8
|
+
name: 'actions',
|
|
9
|
+
summary: 'the action registry: input/output schema, policy, tags, MCP exposure',
|
|
10
|
+
usage: 'x actions [list|describe <name>] [--json]',
|
|
11
|
+
subcommands: ['list', 'describe'],
|
|
12
|
+
defaultSubcommand: 'list',
|
|
13
|
+
requiresApp: true,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const queriesSpec: CommandSpec = {
|
|
17
|
+
name: 'queries',
|
|
18
|
+
summary: 'the query registry: schema, policy, live, cache tags',
|
|
19
|
+
usage: 'x queries [list|describe <name>] [--json]',
|
|
20
|
+
subcommands: ['list', 'describe'],
|
|
21
|
+
defaultSubcommand: 'list',
|
|
22
|
+
requiresApp: true,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const entitiesSpec: CommandSpec = {
|
|
26
|
+
name: 'entities',
|
|
27
|
+
summary: 'the entity registry: columns, invariants, indexes, tenancy',
|
|
28
|
+
usage: 'x entities [list|describe <name>] [--json]',
|
|
29
|
+
subcommands: ['list', 'describe'],
|
|
30
|
+
defaultSubcommand: 'list',
|
|
31
|
+
requiresApp: true,
|
|
32
|
+
};
|
package/src/cmd-registries.ts
CHANGED
|
@@ -13,12 +13,12 @@ import type { AnyQuery, QueryDescriptor } from '@ultimat3/query';
|
|
|
13
13
|
import { describeQueries, getQuery } from '@ultimat3/query';
|
|
14
14
|
import { loadApp } from './app-load';
|
|
15
15
|
import { requireAppRoot } from './app-root';
|
|
16
|
+
import { actionsSpec, entitiesSpec, queriesSpec } from './cmd-registries-spec';
|
|
16
17
|
import type { CliCommand, CommandContext } from './command';
|
|
17
18
|
import { DeclarationUnknownError, MissingPositionalError } from './errors';
|
|
18
19
|
import { msg } from './messages';
|
|
19
20
|
import type { CommandResult, Finding, JsonValue } from './output';
|
|
20
21
|
import type { CommandSpec } from './parse';
|
|
21
|
-
|
|
22
22
|
import { renderTable } from './table';
|
|
23
23
|
|
|
24
24
|
/**
|
|
@@ -53,14 +53,7 @@ interface RegistryKind<D extends { readonly name: string }, Raw extends { descri
|
|
|
53
53
|
const ACTIONS: RegistryKind<ActionDescriptor, AnyAction> = {
|
|
54
54
|
kind: 'actions',
|
|
55
55
|
singular: 'action',
|
|
56
|
-
spec:
|
|
57
|
-
name: 'actions',
|
|
58
|
-
summary: 'the action registry: input/output schema, policy, tags, MCP exposure',
|
|
59
|
-
usage: 'x actions [list|describe <name>] [--json]',
|
|
60
|
-
subcommands: ['list', 'describe'],
|
|
61
|
-
defaultSubcommand: 'list',
|
|
62
|
-
requiresApp: true,
|
|
63
|
-
},
|
|
56
|
+
spec: actionsSpec,
|
|
64
57
|
header: ['name', 'verb', 'resource', 'path', 'capability', 'mcp'],
|
|
65
58
|
list: describeActions,
|
|
66
59
|
find: getAction,
|
|
@@ -71,14 +64,7 @@ const ACTIONS: RegistryKind<ActionDescriptor, AnyAction> = {
|
|
|
71
64
|
const QUERIES: RegistryKind<QueryDescriptor, AnyQuery> = {
|
|
72
65
|
kind: 'queries',
|
|
73
66
|
singular: 'query',
|
|
74
|
-
spec:
|
|
75
|
-
name: 'queries',
|
|
76
|
-
summary: 'the query registry: schema, policy, live, cache tags',
|
|
77
|
-
usage: 'x queries [list|describe <name>] [--json]',
|
|
78
|
-
subcommands: ['list', 'describe'],
|
|
79
|
-
defaultSubcommand: 'list',
|
|
80
|
-
requiresApp: true,
|
|
81
|
-
},
|
|
67
|
+
spec: queriesSpec,
|
|
82
68
|
header: ['name', 'live', 'capability', 'tags', 'ttlMs'],
|
|
83
69
|
list: describeQueries,
|
|
84
70
|
find: getQuery,
|
|
@@ -97,14 +83,7 @@ const QUERIES: RegistryKind<QueryDescriptor, AnyQuery> = {
|
|
|
97
83
|
const ENTITIES: RegistryKind<EntityDescription, RegistryEntry> = {
|
|
98
84
|
kind: 'entities',
|
|
99
85
|
singular: 'entity',
|
|
100
|
-
spec:
|
|
101
|
-
name: 'entities',
|
|
102
|
-
summary: 'the entity registry: columns, invariants, indexes, tenancy',
|
|
103
|
-
usage: 'x entities [list|describe <name>] [--json]',
|
|
104
|
-
subcommands: ['list', 'describe'],
|
|
105
|
-
defaultSubcommand: 'list',
|
|
106
|
-
requiresApp: true,
|
|
107
|
-
},
|
|
86
|
+
spec: entitiesSpec,
|
|
108
87
|
header: ['name', 'table', 'columns', 'invariants', 'indexes', 'orgScoped'],
|
|
109
88
|
list: describeEntities,
|
|
110
89
|
find: getEntity,
|