anhedral 0.10.1 → 0.11.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 (89) hide show
  1. package/README.md +77 -38
  2. package/assets/{anhedral-cli-init-technical.svg → anhedral-stack-technical.svg} +6 -6
  3. package/assets/{anhedral-cli-init.svg → anhedral-stack.svg} +7 -7
  4. package/dist/bin.js +271 -154
  5. package/dist/capabilities.d.ts +3 -3
  6. package/dist/capability-contract.d.ts +47 -0
  7. package/dist/capability-contract.js +136 -0
  8. package/dist/cli-arguments.d.ts +16 -0
  9. package/dist/cli-arguments.js +115 -0
  10. package/dist/cli-output.d.ts +8 -0
  11. package/dist/cli-output.js +211 -0
  12. package/dist/cloudflare.d.ts +7 -2
  13. package/dist/cloudflare.js +166 -36
  14. package/dist/command-schema.d.ts +21 -0
  15. package/dist/command-schema.js +374 -0
  16. package/dist/delivery.d.ts +2 -2
  17. package/dist/delivery.js +21 -4
  18. package/dist/index.d.ts +23 -1
  19. package/dist/index.js +15 -1
  20. package/dist/inspector-model.d.ts +115 -0
  21. package/dist/inspector-model.js +1 -0
  22. package/dist/lifecycle-model.d.ts +15 -0
  23. package/dist/lifecycle-model.js +20 -0
  24. package/dist/lifecycle.d.ts +12 -0
  25. package/dist/lifecycle.js +92 -0
  26. package/dist/mobile-delivery.d.ts +100 -0
  27. package/dist/mobile-delivery.js +319 -0
  28. package/dist/platforms/mobile.js +8 -5
  29. package/dist/project-inspector.d.ts +4 -0
  30. package/dist/project-inspector.js +646 -0
  31. package/dist/project-state.d.ts +17 -0
  32. package/dist/project-state.js +239 -11
  33. package/dist/provider-auth.d.ts +41 -0
  34. package/dist/provider-auth.js +210 -0
  35. package/dist/providers/adapters.d.ts +17 -0
  36. package/dist/providers/adapters.js +269 -0
  37. package/dist/providers/api.d.ts +28 -0
  38. package/dist/providers/api.js +252 -0
  39. package/dist/providers/config.d.ts +15 -0
  40. package/dist/providers/config.js +184 -0
  41. package/dist/providers/definitions.d.ts +318 -0
  42. package/dist/providers/definitions.js +127 -0
  43. package/dist/providers/provision.d.ts +24 -0
  44. package/dist/providers/provision.js +76 -0
  45. package/dist/providers/receipts.d.ts +20 -0
  46. package/dist/providers/receipts.js +66 -0
  47. package/dist/providers/recovery-verification.d.ts +7 -0
  48. package/dist/providers/recovery-verification.js +112 -0
  49. package/dist/providers/recovery.d.ts +30 -0
  50. package/dist/providers/recovery.js +34 -0
  51. package/dist/providers/runtime.d.ts +10 -0
  52. package/dist/providers/runtime.js +231 -0
  53. package/dist/providers/schema.d.ts +28 -0
  54. package/dist/providers/schema.js +49 -0
  55. package/dist/providers/state.d.ts +56 -0
  56. package/dist/providers/state.js +329 -0
  57. package/dist/provision.d.ts +1 -1
  58. package/dist/provision.js +61 -3
  59. package/dist/registry/add.d.ts +6 -0
  60. package/dist/registry/add.js +29 -1
  61. package/dist/registry/commands.d.ts +3 -2
  62. package/dist/registry/commands.js +53 -14
  63. package/dist/registry/options.js +3 -2
  64. package/dist/registry/preview.d.ts +11 -0
  65. package/dist/registry/preview.js +91 -0
  66. package/dist/registry/usage.d.ts +10 -0
  67. package/dist/registry/usage.js +22 -37
  68. package/dist/scaffold/auth.js +3 -19
  69. package/dist/scaffold/workspace.js +11 -0
  70. package/dist/ship.d.ts +320 -0
  71. package/dist/ship.js +211 -0
  72. package/dist/stack-registry.d.ts +2 -2
  73. package/dist/stack-registry.js +7 -1
  74. package/dist/standard.d.ts +2 -1
  75. package/dist/standard.js +11 -11
  76. package/dist/vercel.d.ts +3 -3
  77. package/dist/vercel.js +27 -1
  78. package/dist/version.d.ts +1 -1
  79. package/dist/version.js +1 -1
  80. package/docs/application-stack-standard.md +51 -80
  81. package/docs/cli-design.md +67 -0
  82. package/docs/delivery-workflow.md +29 -15
  83. package/docs/plugin-ui.md +61 -0
  84. package/docs/provider-configuration.md +117 -0
  85. package/package.json +16 -12
  86. package/dist/managed-hosting.d.ts +0 -49
  87. package/dist/managed-hosting.js +0 -373
  88. package/docs/managed-hosting-architecture.md +0 -87
  89. package/docs/sponsorships.md +0 -25
@@ -0,0 +1,47 @@
1
+ import type { StackSelection, StandardProduct } from './registry/types.js';
2
+ export type CapabilitySupport = 'implemented' | 'scaffold-only' | 'unsupported' | 'not-applicable';
3
+ export type CapabilityEvidence = 'local-contract' | 'unverified';
4
+ export interface CapabilityStage {
5
+ support: CapabilitySupport;
6
+ evidence: CapabilityEvidence;
7
+ scope: string;
8
+ }
9
+ export interface ProductCapability {
10
+ id: StandardProduct;
11
+ description: string;
12
+ targets: readonly string[];
13
+ registryDependencies: readonly string[];
14
+ selection: StackSelection;
15
+ scaffold: CapabilityStage;
16
+ provisioning: CapabilityStage;
17
+ delivery: CapabilityStage;
18
+ limits: readonly string[];
19
+ }
20
+ /** Advertises implemented scope and the evidence level separately. This catalog is
21
+ * pure metadata; consulting it never authenticates, provisions, or verifies a live account. */
22
+ export declare const CAPABILITY_CONTRACT: {
23
+ schemaVersion: number;
24
+ cliVersion: string;
25
+ mutation: boolean;
26
+ liveAcceptance: "unverified";
27
+ products: ProductCapability[];
28
+ providers: {
29
+ id: string;
30
+ managementCredential: "RESEND_API_KEY" | "STRIPE_SECRET_KEY" | "NEON_API_KEY" | "CLERK_PLATFORM_API_KEY" | "RC_API_KEY" | "SENTRY_AUTH_TOKEN" | "POSTHOG_PERSONAL_API_KEY" | "OPENAI_ADMIN_KEY";
31
+ resources: readonly ["project", "database connection"] | readonly ["application", "environment keys"] | readonly ["domain", "sending key"] | readonly ["account verification", "webhook"] | readonly ["project", "application", "SDK keys"] | readonly ["project", "DSN"] | readonly ["project", "ingestion configuration"] | readonly ["project", "service account", "runtime key"];
32
+ provisioning: CapabilityStage;
33
+ liveAcceptance: "unverified";
34
+ ownerSteps: readonly ["Choose an organization and region; approve any provider billing requirements."] | readonly ["Obtain Platform API access; configure production domains, DNS, and OAuth credentials."] | readonly ["Publish domain verification DNS records and wait for verification."] | readonly ["Complete account activation; define products, prices, and purchase behavior."] | readonly ["Configure store credentials or Stripe, products, offerings, and entitlements; verify purchases."] | readonly ["Choose organization and team access; configure source maps and application instrumentation."] | readonly ["Choose organization and region; define analytics events and consent behavior."] | readonly ["Obtain organization administration access; configure billing and application usage limits."];
35
+ }[];
36
+ hosts: {
37
+ id: string;
38
+ delivery: CapabilityStage;
39
+ liveAcceptance: "unverified";
40
+ }[];
41
+ plugin: {
42
+ id: string;
43
+ capability: CapabilityStage;
44
+ }[];
45
+ limits: string[];
46
+ };
47
+ export declare function formatCapabilityContract(): string;
@@ -0,0 +1,136 @@
1
+ import { STANDARD_PRODUCTS, getStackItem } from './stack-registry.js';
2
+ import { PROVIDER_DEFINITIONS } from './providers/definitions.js';
3
+ import { GENERATOR_VERSION } from './version.js';
4
+ const stage = (support, scope, evidence = 'local-contract') => ({ support, evidence, scope });
5
+ const none = (scope) => stage('not-applicable', scope, 'unverified');
6
+ const cloudflareResources = ['d1', 'r2', 'kv', 'queues', 'basin'];
7
+ const deploymentBindings = ['realtime', 'cron', 'workflows', 'workers-ai'];
8
+ function integration(id) {
9
+ const provider = id === 'ai-sdk' ? 'openai' : id;
10
+ return Object.hasOwn(PROVIDER_DEFINITIONS, provider)
11
+ ? provider
12
+ : undefined;
13
+ }
14
+ function productCapability(id) {
15
+ const item = getStackItem(id);
16
+ const provider = integration(id);
17
+ let provisioning = none('No separately provisioned resource.');
18
+ let delivery = none('Delivered as part of the selected application.');
19
+ const limits = [
20
+ 'Generated primitives require application feature development and acceptance checks.',
21
+ ];
22
+ if (provider) {
23
+ provisioning = stage('implemented', `Selected ${PROVIDER_DEFINITIONS[provider].resources.join(', ')} through provider APIs.`);
24
+ limits.push(...PROVIDER_DEFINITIONS[provider].ownerSteps, 'Live account permissions and provider behavior require authorized acceptance.');
25
+ }
26
+ if (cloudflareResources.includes(id)) {
27
+ provisioning = stage('implemented', id === 'basin'
28
+ ? 'Private Cloudflare stream and Worker binding.'
29
+ : 'Selected Cloudflare resource and checked Worker binding.');
30
+ limits.push('Account eligibility, quotas, and live runtime behavior require authorized acceptance.');
31
+ if (id === 'basin')
32
+ limits.push('Pipeline transforms and sinks are outside automated provisioning.');
33
+ }
34
+ if (deploymentBindings.includes(id)) {
35
+ provisioning = stage('implemented', 'Worker deployment configures the selected binding or trigger.');
36
+ limits.push('Configured bindings and triggers do not establish application behavior or execution success.');
37
+ }
38
+ if (['vinext', 'next', 'hono'].includes(id))
39
+ delivery = stage('implemented', id === 'next'
40
+ ? 'Selected Cloudflare or Vercel deployment with provider identity and source checks.'
41
+ : 'Cloudflare Worker deployment with provider identity and source checks.');
42
+ if (id === 'expo') {
43
+ delivery = stage('implemented', 'EAS build request, build receipt verification, and artifact link.');
44
+ limits.push('No store submission; signing, enrollment, device testing, and store distribution remain developer responsibilities.');
45
+ }
46
+ if (id === 'electron' || id === 'wxt') {
47
+ delivery = stage('scaffold-only', 'Generated application and local build tooling; automated distribution is unavailable.');
48
+ limits.push(id === 'electron'
49
+ ? 'Signing, notarization, installers, updates, and distribution require a separate delivery workflow.'
50
+ : 'Extension signing, store submission, review, and distribution require a separate delivery workflow.');
51
+ }
52
+ if (id === 'better-auth')
53
+ limits.push('Configure private authentication secret and public origin; apply and verify database migrations and authorization rules.');
54
+ return {
55
+ id,
56
+ description: item.description,
57
+ targets: item.targets,
58
+ registryDependencies: item.registryDependencies,
59
+ selection: item.selection,
60
+ scaffold: stage('implemented', 'Bundled source recipe and stack composition.'),
61
+ provisioning,
62
+ delivery,
63
+ limits,
64
+ };
65
+ }
66
+ /** Advertises implemented scope and the evidence level separately. This catalog is
67
+ * pure metadata; consulting it never authenticates, provisions, or verifies a live account. */
68
+ export const CAPABILITY_CONTRACT = {
69
+ schemaVersion: 1,
70
+ cliVersion: GENERATOR_VERSION,
71
+ mutation: false,
72
+ liveAcceptance: 'unverified',
73
+ products: STANDARD_PRODUCTS.map(productCapability),
74
+ providers: Object.entries(PROVIDER_DEFINITIONS).map(([id, definition]) => ({
75
+ id,
76
+ managementCredential: definition.credential,
77
+ resources: definition.resources,
78
+ provisioning: stage('implemented', 'Selected resources through provider APIs; fixture-tested local contracts.'),
79
+ liveAcceptance: 'unverified',
80
+ ownerSteps: definition.ownerSteps,
81
+ })),
82
+ hosts: [
83
+ {
84
+ id: 'cloudflare',
85
+ delivery: stage('implemented', 'Provision selected resources, configure bindings/secrets, deploy Workers, inspect provider identity and source receipts.'),
86
+ liveAcceptance: 'unverified',
87
+ },
88
+ {
89
+ id: 'vercel',
90
+ delivery: stage('implemented', 'Configure the selected project environment, deploy the web app, inspect provider identity and source receipts.'),
91
+ liveAcceptance: 'unverified',
92
+ },
93
+ ],
94
+ plugin: [
95
+ {
96
+ id: 'project-inspection',
97
+ capability: stage('implemented', 'Offline versioned stack, setup prerequisites and target-specific recorded delivery evidence, shared by CLI and optional UI.'),
98
+ },
99
+ {
100
+ id: 'project-catalog',
101
+ capability: stage('implemented', 'Local projects, configuration, and recorded delivery receipts.'),
102
+ },
103
+ {
104
+ id: 'native-host-installation',
105
+ capability: stage('implemented', 'Packaged plugin and host integration.', 'unverified'),
106
+ },
107
+ ...['live-analytics', 'live-database-reader', 'schedule-execution', 'live-provider-health'].map((id) => ({
108
+ id,
109
+ capability: stage('unsupported', 'Configuration and recorded state only; no live reader or execution evidence.', 'unverified'),
110
+ })),
111
+ ],
112
+ limits: [
113
+ 'Local contract tests are separate from live provider acceptance and completed application verification.',
114
+ 'Account selection, enrollment, billing approval, DNS ownership, and signing can require owner action.',
115
+ 'Mixed hosting is sequential; no atomic cross-provider rollback or cached phase skipping is promised.',
116
+ 'The CLI scaffolds supported primitives; customer application features remain development work.',
117
+ ],
118
+ };
119
+ export function formatCapabilityContract() {
120
+ return [
121
+ `Anhedral ${CAPABILITY_CONTRACT.cliVersion} capabilities`,
122
+ 'Evidence: local contracts; live provider acceptance remains unverified.',
123
+ '',
124
+ 'Product Provisioning Delivery',
125
+ ...CAPABILITY_CONTRACT.products.map((product) => `${product.id.padEnd(20)}${product.provisioning.support.padEnd(20)}${product.delivery.support}`),
126
+ '',
127
+ 'Providers: ' + CAPABILITY_CONTRACT.providers.map((provider) => provider.id).join(', '),
128
+ 'Expo delivery returns EAS artifacts; it does not submit to stores.',
129
+ 'Electron and WXT distribution: scaffold only.',
130
+ 'Plugin live analytics, database readers, schedule execution, and provider health: unavailable.',
131
+ '',
132
+ ...CAPABILITY_CONTRACT.limits,
133
+ '',
134
+ 'Use --json for scope, owner steps, and evidence for every capability.',
135
+ ].join('\n');
136
+ }
@@ -0,0 +1,16 @@
1
+ export declare class CliArgumentError extends Error {
2
+ readonly nextAction: string;
3
+ readonly code: string;
4
+ constructor(message: string, nextAction: string, code?: string);
5
+ }
6
+ /** Bound typo matching so arbitrary CLI input cannot create quadratic work. */
7
+ export declare function suggest(input: string, choices: readonly string[]): string | undefined;
8
+ export declare function globalArguments(input: readonly string[]): {
9
+ args: string[];
10
+ cwd: string | undefined;
11
+ quiet: boolean;
12
+ verbose: boolean;
13
+ json: boolean;
14
+ };
15
+ export declare function validateCommand(command: string | undefined, args: readonly string[]): void;
16
+ export declare function inlineArguments(command: string | undefined, args: readonly string[]): string[];
@@ -0,0 +1,115 @@
1
+ import { COMMANDS, GLOBAL_OPTIONS, commandDefinition } from './command-schema.js';
2
+ export class CliArgumentError extends Error {
3
+ nextAction;
4
+ code;
5
+ constructor(message, nextAction, code = 'INVALID_ARGUMENT') {
6
+ super(message);
7
+ this.nextAction = nextAction;
8
+ this.code = code;
9
+ }
10
+ }
11
+ /** Bound typo matching so arbitrary CLI input cannot create quadratic work. */
12
+ export function suggest(input, choices) {
13
+ if (!input || input.length > 64)
14
+ return undefined;
15
+ const distance = (a, b) => {
16
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
17
+ for (let row = 1; row <= a.length; row++) {
18
+ const current = [row];
19
+ for (let column = 1; column <= b.length; column++)
20
+ current[column] = Math.min(current[column - 1] + 1, previous[column] + 1, previous[column - 1] + (a[row - 1] === b[column - 1] ? 0 : 1));
21
+ previous = current;
22
+ }
23
+ return previous[b.length];
24
+ };
25
+ const matches = choices
26
+ .map((choice) => ({ choice, distance: distance(input, choice) }))
27
+ .sort((a, b) => a.distance - b.distance || a.choice.localeCompare(b.choice));
28
+ const best = matches[0];
29
+ if (best &&
30
+ best.distance <= (input.length > 5 ? 2 : 1) &&
31
+ (!matches[1] || best.distance < matches[1].distance))
32
+ return best.choice;
33
+ return undefined;
34
+ }
35
+ export function globalArguments(input) {
36
+ const args = [];
37
+ let cwd;
38
+ let quiet = false;
39
+ let verbose = false;
40
+ let json = false;
41
+ for (let index = 0; index < input.length; index++) {
42
+ const arg = input[index];
43
+ if (arg === '-c' || arg === '--cwd' || arg.startsWith('--cwd=')) {
44
+ const value = arg.startsWith('--cwd=') ? arg.slice(6) : input[++index];
45
+ if (!value || value.startsWith('-') || cwd !== undefined)
46
+ throw new CliArgumentError('Specify one working directory with --cwd <directory>.', 'Run anhedral --help.');
47
+ cwd = value;
48
+ }
49
+ else if (arg === '--json')
50
+ json = true;
51
+ else if (arg === '--quiet' || arg === '-q')
52
+ quiet = true;
53
+ else if (arg === '--verbose')
54
+ verbose = true;
55
+ else
56
+ args.push(arg);
57
+ }
58
+ return { args, cwd, quiet, verbose, json };
59
+ }
60
+ export function validateCommand(command, args) {
61
+ if (!command || ['--help', '-h', '--version', '-v', 'help'].includes(command))
62
+ return;
63
+ const definition = commandDefinition(command);
64
+ if (!definition) {
65
+ const recommendation = suggest(command, [...Object.keys(COMMANDS), 'new', 'help']);
66
+ const name = /^[a-z-]{1,32}$/.test(command) ? `: ${command}` : '';
67
+ throw new CliArgumentError(`Unknown command${name}.${recommendation ? ` Did you mean ${recommendation}?` : ''}`, 'Run anhedral --help.', 'UNKNOWN_COMMAND');
68
+ }
69
+ const options = [...definition.options, ...GLOBAL_OPTIONS];
70
+ const flags = options.flatMap((option) => [option.flag, ...(option.aliases ?? [])]);
71
+ const seen = new Set();
72
+ for (let index = 0; index < args.length; index++) {
73
+ const argument = args[index];
74
+ if (!argument.startsWith('-'))
75
+ continue;
76
+ const separator = argument.indexOf('=');
77
+ const flag = separator < 0 ? argument : argument.slice(0, separator);
78
+ const option = options.find((entry) => entry.flag === flag || entry.aliases?.includes(flag));
79
+ if (!option) {
80
+ const recommendation = suggest(flag, flags);
81
+ const display = /^--?[a-z-]{1,64}$/.test(flag) ? `: ${flag}` : '';
82
+ throw new CliArgumentError(`Unknown ${command} option${display}.${recommendation ? ` Did you mean ${recommendation}?` : ''}`, `Run anhedral ${command} --help.`);
83
+ }
84
+ if (seen.has(option.flag) && !option.repeatable)
85
+ throw new CliArgumentError(`Specify ${option.flag} once.`, `Run anhedral ${command} --help.`);
86
+ seen.add(option.flag);
87
+ if (separator >= 0 && !option.arity)
88
+ throw new CliArgumentError('This option does not accept an inline value.', `Run anhedral ${command} --help.`);
89
+ if (option.arity) {
90
+ const value = separator >= 0 ? argument.slice(separator + 1) : args[index + 1];
91
+ if (!value || value.startsWith('-')) {
92
+ if (option.arity === 'required' || separator >= 0)
93
+ throw new CliArgumentError(`Supply a value for ${option.flag}.`, `Run anhedral ${command} --help.`);
94
+ }
95
+ else if (separator < 0)
96
+ index++;
97
+ }
98
+ }
99
+ }
100
+ export function inlineArguments(command, args) {
101
+ const definition = commandDefinition(command);
102
+ const flags = new Set(definition?.options
103
+ .filter((option) => option.arity)
104
+ .flatMap((option) => [option.flag, ...(option.aliases ?? [])]) ?? []);
105
+ return args.flatMap((argument) => {
106
+ const separator = argument.indexOf('=');
107
+ if (separator < 0 || !flags.has(argument.slice(0, separator)))
108
+ return [argument];
109
+ const flag = argument.slice(0, separator);
110
+ const value = argument.slice(separator + 1);
111
+ if (!value || value.startsWith('-'))
112
+ throw new CliArgumentError(`Supply a value for ${flag}.`, `Run anhedral ${command} --help.`);
113
+ return [flag, value];
114
+ });
115
+ }
@@ -0,0 +1,8 @@
1
+ export interface OutputOptions {
2
+ json?: boolean;
3
+ quiet?: boolean;
4
+ }
5
+ /** Human output is an explicit summary, never a recursive dump of provider payloads. */
6
+ export declare function formatResult(command: string, value: unknown): string;
7
+ export declare function printResult(command: string, value: unknown, options?: OutputOptions): void;
8
+ export declare function printProgress(message: string, options?: OutputOptions): void;
@@ -0,0 +1,211 @@
1
+ const record = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : {};
2
+ const entries = (value) => (Array.isArray(value) ? value.map(record) : []);
3
+ const string = (value) => typeof value === 'string' ? value.replace(/[\x00-\x08\x0b-\x1f\x7f]/g, '') : '';
4
+ const names = (value) => Array.isArray(value)
5
+ ? value
6
+ .filter((entry) => typeof entry === 'string')
7
+ .map(string)
8
+ .join(', ')
9
+ : '';
10
+ /** Human output is an explicit summary, never a recursive dump of provider payloads. */
11
+ export function formatResult(command, value) {
12
+ const data = record(value);
13
+ const lines = [];
14
+ const next = (action) => {
15
+ if (string(action))
16
+ lines.push(`Next: ${string(action)}`);
17
+ };
18
+ if (command === 'init' || command === 'new') {
19
+ lines.push(data.generated ? `Created ${string(data.root)}.` : `Stack preview for ${string(data.root)}.`, `Stack: ${names(data.products)}.`, `Hosting: ${string(data.hosting)}.`, '');
20
+ if (data.generated)
21
+ lines.push('Review the generated source and anhedral.providers.json.', 'In the project directory, start development with pnpm dev. Run pnpm check before delivery.', 'Next: anhedral auth, then connect the intended hosting account and preview anhedral ship --dry-run.');
22
+ else
23
+ lines.push('No files, provider resources or deployments were created.');
24
+ }
25
+ else if (command === 'add') {
26
+ lines.push(data.dryRun ? 'Addition preview.' : 'Stack parts added.', names(data.items) ? `Parts: ${names(data.items)}.` : 'No new stack parts.', `Project: ${string(data.root)}.`);
27
+ const files = Array.isArray(data.files) ? data.files : [];
28
+ if (files.length)
29
+ lines.push('', ...files.map((file) => ` ${string(file)}`));
30
+ if (data.filesChecked === false)
31
+ lines.push('Source files were not checked: this selection needs the networked web bootstrap.');
32
+ if (names(data.conflicts))
33
+ lines.push(`Conflicts: ${names(data.conflicts)}.`);
34
+ for (const preview of entries(data.previews))
35
+ lines.push('', string(preview.path), string(preview.unavailable ?? preview.diff ?? preview.content));
36
+ if (data.dryRun)
37
+ lines.push('', 'No application files were changed.');
38
+ else
39
+ next('Run anhedral doctor and review the selected services’ setup requirements.');
40
+ }
41
+ else if (command === 'inspect') {
42
+ lines.push(`Project: ${string(data.name) || 'Name unavailable'}.`, 'Local recorded evidence; authentication and live provider status are not checked.');
43
+ const selection = record(record(data.selection).data);
44
+ lines.push(`Stack: ${names(selection.products) || 'Unavailable'}.`);
45
+ for (const target of entries(record(data.targets).data)) {
46
+ const latest = record(target.latestAttempt), successful = record(target.lastSuccessful);
47
+ lines.push(` ${string(target.application)} — ${string(target.provider)} (${string(target.delivery)})`);
48
+ if (string(latest.state))
49
+ lines.push(` Latest recorded target result: ${string(latest.state)}.`);
50
+ if (string(latest.operationState))
51
+ lines.push(` Overall attempt: ${string(latest.operationState)}.`);
52
+ if (string(successful.id))
53
+ lines.push(` Last recorded success: ${string(successful.id)}.`);
54
+ if (string(target.error))
55
+ lines.push(` ${string(target.error)}`);
56
+ }
57
+ for (const check of entries(record(record(data.readiness).data).checks))
58
+ lines.push(` ${check.ok ? 'pass' : 'fail'} ${string(check.id)} — ${string(check.detail)}`);
59
+ for (const key of [
60
+ 'selection',
61
+ 'workspaces',
62
+ 'configuration',
63
+ 'targets',
64
+ 'setupProviders',
65
+ 'integrations',
66
+ 'cloudflareResources',
67
+ 'readiness',
68
+ 'operation',
69
+ ]) {
70
+ const section = record(data[key]);
71
+ if (section.state === 'unavailable')
72
+ lines.push(`${key}: ${string(section.error) || 'Unavailable'}.`);
73
+ }
74
+ next('Use --json for target-specific records. Review local checks, then explicitly run auth or ship when intended.');
75
+ }
76
+ else if (command === 'auth') {
77
+ const checks = entries(data.checks);
78
+ lines.push(checks.length ? 'Provider authentication' : 'This stack requires no provider authentication.');
79
+ for (const check of checks) {
80
+ lines.push(` ${string(check.provider).padEnd(12)}${string(check.state)}${string(check.access) && check.access !== 'none' ? ` (${string(check.access)} access)` : ''}`);
81
+ if (check.state !== 'verified' || check.access === 'runtime')
82
+ next(check.nextAction);
83
+ }
84
+ if (checks.length && string(data.note))
85
+ lines.push(string(data.note));
86
+ if (checks.length)
87
+ next(data.authenticated
88
+ ? 'Complete provider settings, connect the intended hosting account, then run anhedral ship --dry-run.'
89
+ : 'Resolve the reported access checks, then repeat anhedral auth --status.');
90
+ }
91
+ else if (command === 'integrations') {
92
+ lines.push(data.recovered
93
+ ? 'Integration resource reconciled.'
94
+ : 'Integration receipts (recorded locally).');
95
+ for (const provider of entries(data.providers)) {
96
+ lines.push(` ${string(provider.provider)}`);
97
+ for (const operation of entries(provider.operations))
98
+ lines.push(` ${string(operation.operation)}: ${string(operation.status)}`);
99
+ }
100
+ if (string(data.operation))
101
+ lines.push(`Operation: ${string(data.operation)}`);
102
+ next(data.nextAction ??
103
+ 'Use --json to inspect the resource IDs and safe recovery descriptors. Unknown operations must be verified before retrying.');
104
+ }
105
+ else if (command === 'registry') {
106
+ lines.push(`${string(data.name)} (${string(data.type)})`, string(data.description));
107
+ if (names(data.targets))
108
+ lines.push(`Files: ${names(data.targets)}`);
109
+ if (names(data.registryDependencies))
110
+ lines.push(`Requires: ${names(data.registryDependencies)}`);
111
+ const setup = record(data.setup);
112
+ for (const key of ['access', 'credentials', 'resources', 'verify'])
113
+ if (names(setup[key]))
114
+ lines.push(`${key}: ${names(setup[key])}`);
115
+ next(`Inspect anhedral add ${string(data.name)} --dry-run in your project.`);
116
+ }
117
+ else if (command === 'doctor') {
118
+ const local = record(data.local);
119
+ const checks = entries(data.checks ?? local.checks);
120
+ lines.push(data.localReady ? 'Local project checks passed.' : 'Local project checks need attention.');
121
+ for (const check of checks) {
122
+ lines.push(` ${check.ok ? 'pass' : 'fail'} ${string(check.id)}${string(check.detail) ? ` — ${string(check.detail)}` : ''}`);
123
+ if (check.ok === false)
124
+ next(check.nextAction);
125
+ }
126
+ if (data.providerVerification)
127
+ lines.push(`Provider evidence: ${string(data.providerVerification)}.`);
128
+ for (const check of entries(data.prerequisiteChecks)) {
129
+ lines.push(` ${check.ok ? 'pass' : 'fail'} ${string(check.id)} — ${string(check.detail)}`);
130
+ if (check.ok === false)
131
+ next(check.nextAction);
132
+ }
133
+ lines.push('Local readiness does not certify production behavior.');
134
+ }
135
+ else {
136
+ const project = record(data.project);
137
+ const release = record(data.release);
138
+ const state = string(data.state ?? release.state);
139
+ lines.push(data.mutation === false
140
+ ? `${command}: preview only.`
141
+ : `${command}${state ? `: ${state}` : ': completed'}.`);
142
+ if (string(project.name))
143
+ lines.push(`Project: ${string(project.name)} (${string(project.provider)}).`);
144
+ if (string(release.id))
145
+ lines.push(`Release: ${string(release.id)}`);
146
+ if (string(data.buildId))
147
+ lines.push(`Build: ${string(data.buildId)} (${string(data.platform)}, ${string(data.profile)}).`);
148
+ if (string(data.evidence))
149
+ lines.push(`Evidence: ${string(data.evidence)}.`);
150
+ if (typeof data.activeRecordedVersion === 'boolean')
151
+ lines.push(`Recorded production version: ${data.activeRecordedVersion ? 'active' : 'not confirmed'}.`);
152
+ for (const target of entries(data.provider))
153
+ lines.push(` ${string(target.target)}: ${target.activeRecordedVersion === true ? 'recorded version active' : 'recorded version not confirmed'}`);
154
+ const repository = record(data.repository);
155
+ if (string(repository.nameWithOwner))
156
+ lines.push(`Repository: ${string(repository.nameWithOwner)}`);
157
+ if (string(data.branch))
158
+ lines.push(`Branch: ${string(data.branch)}`);
159
+ for (const resource of entries(record(data.provisioning ?? data).resources))
160
+ lines.push(` ${string(resource.kind)} ${string(resource.name)}${string(resource.target) ? ` (${string(resource.target)})` : ''}`);
161
+ for (const key of ['downloadUrl', 'buildPageUrl', 'managementFile', 'file'])
162
+ if (string(data[key]))
163
+ lines.push(`${key}: ${string(data[key])}`);
164
+ if (string(release.url))
165
+ lines.push(`URL: ${string(release.url)}`);
166
+ const integration = record(data.integrations);
167
+ for (const provider of entries(integration.providers)) {
168
+ lines.push(` ${string(provider.provider)}${names(provider.missing) ? `: missing ${names(provider.missing)}` : ': configuration selected'}`);
169
+ }
170
+ for (const provider of entries(integration.results)) {
171
+ lines.push(` ${string(provider.provider)}: ${provider.ready ? 'configured' : 'setup pending'}`);
172
+ for (const action of Array.isArray(provider.nextActions) ? provider.nextActions : [])
173
+ next(action);
174
+ }
175
+ if (data.productVerification)
176
+ lines.push(`Product verification: ${string(data.productVerification)}.`);
177
+ const operation = record(data.operation);
178
+ if (string(operation.state)) {
179
+ lines.push(`Operation: ${string(operation.state)} (recorded locally).`);
180
+ for (const phase of entries(operation.phases))
181
+ lines.push(` ${string(phase.name)}: ${string(phase.state)}`);
182
+ }
183
+ for (const target of entries(data.targets)) {
184
+ if (string(target.provider)) {
185
+ const result = record(target.result);
186
+ const release = record(result.release);
187
+ lines.push(` ${string(target.provider)}: ${string(target.state ?? result.state ?? release.state) || 'planned'}`);
188
+ }
189
+ }
190
+ next(data.nextAction);
191
+ if (data.mutation === false)
192
+ lines.push('No provider resources or deployments were changed.');
193
+ else if (!data.nextAction && command === 'ship' && state === 'deployed')
194
+ next('Run anhedral verify to exercise the application’s configured acceptance checks.');
195
+ else if (!data.nextAction && command === 'connect')
196
+ next('Review and commit the connection manifest, then run anhedral ship --dry-run.');
197
+ if (!state && !data.nextAction && !Object.keys(project).length)
198
+ lines.push('Use --json for the complete recorded evidence.');
199
+ }
200
+ return lines.join('\n').trimEnd();
201
+ }
202
+ export function printResult(command, value, options = {}) {
203
+ if (options.json)
204
+ console.log(JSON.stringify(value, null, 2));
205
+ else if (!options.quiet)
206
+ console.log(formatResult(command, value));
207
+ }
208
+ export function printProgress(message, options = {}) {
209
+ if (process.stderr.isTTY && !options.json && !options.quiet)
210
+ console.error(message);
211
+ }
@@ -9,6 +9,11 @@ export interface CloudflareResponse {
9
9
  };
10
10
  }
11
11
  export type CloudflareRequest = (method: 'GET' | 'POST', endpoint: string, body?: unknown) => Promise<CloudflareResponse>;
12
- /** Credentials stay in this closure, never in returned receipts, arguments or files. */
13
- export declare function cloudflareRequest(project: ApplicationProject, directory: string, run: DeliveryRunner, env: NodeJS.ProcessEnv): Promise<CloudflareRequest>;
12
+ export interface CloudflareRequestOptions {
13
+ send?: typeof fetch;
14
+ timeoutMs?: number;
15
+ }
16
+ /** Credentials stay in this closure. Fixed account paths, bounded response reads and
17
+ * a whole-request deadline apply to reads and mutations; mutations never retry. */
18
+ export declare function cloudflareRequest(project: ApplicationProject, directory: string, run: DeliveryRunner, env: NodeJS.ProcessEnv, options?: CloudflareRequestOptions): Promise<CloudflareRequest>;
14
19
  export declare function listCloudflare(request: CloudflareRequest, endpoint: string): Promise<Record<string, unknown>[]>;