kadi-deploy 0.19.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 (52) hide show
  1. package/.env.example +6 -0
  2. package/.prettierrc +6 -0
  3. package/README.md +589 -0
  4. package/agent.json +23 -0
  5. package/index.js +11 -0
  6. package/package.json +42 -0
  7. package/quick-command.txt +92 -0
  8. package/scripts/preflight.js +458 -0
  9. package/scripts/preflight.sh +300 -0
  10. package/src/cli/bid-selector.ts +222 -0
  11. package/src/cli/colors.ts +216 -0
  12. package/src/cli/index.ts +11 -0
  13. package/src/cli/prompts.ts +190 -0
  14. package/src/cli/spinners.ts +165 -0
  15. package/src/commands/deploy-local.ts +475 -0
  16. package/src/commands/deploy.ts +1342 -0
  17. package/src/commands/down.ts +679 -0
  18. package/src/commands/index.ts +10 -0
  19. package/src/commands/lock.ts +571 -0
  20. package/src/config/agent-loader.ts +177 -0
  21. package/src/config/index.ts +9 -0
  22. package/src/display/deployment-info.ts +220 -0
  23. package/src/display/pricing.ts +137 -0
  24. package/src/display/resources.ts +234 -0
  25. package/src/enhanced-registry-manager.ts +892 -0
  26. package/src/index.ts +307 -0
  27. package/src/infrastructure/registry.ts +269 -0
  28. package/src/schemas/profiles.ts +529 -0
  29. package/src/secrets/broker-urls.ts +109 -0
  30. package/src/secrets/handshake.ts +407 -0
  31. package/src/secrets/index.ts +69 -0
  32. package/src/secrets/inject-env.ts +171 -0
  33. package/src/secrets/nonce.ts +31 -0
  34. package/src/secrets/normalize.ts +204 -0
  35. package/src/secrets/prepare.ts +152 -0
  36. package/src/secrets/validate.ts +243 -0
  37. package/src/secrets/vault.ts +80 -0
  38. package/src/types/akash.ts +116 -0
  39. package/src/types/container-registry-ability.d.ts +158 -0
  40. package/src/types/external.ts +49 -0
  41. package/src/types.ts +211 -0
  42. package/src/utils/akt-price.ts +74 -0
  43. package/tests/agent-loader.test.ts +239 -0
  44. package/tests/autonomous.test.ts +244 -0
  45. package/tests/down.test.ts +1143 -0
  46. package/tests/lock.test.ts +1148 -0
  47. package/tests/nonce.test.ts +34 -0
  48. package/tests/normalize.test.ts +270 -0
  49. package/tests/secrets-schema.test.ts +301 -0
  50. package/tests/types.test.ts +198 -0
  51. package/tsconfig.json +18 -0
  52. package/vitest.config.ts +9 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Agent Configuration Loader
3
+ *
4
+ * Handles loading and parsing agent.json files from the project directory.
5
+ * Provides validation and helpful error messages when agent.json is missing or invalid.
6
+ *
7
+ * @module config/agent-loader
8
+ */
9
+
10
+ import path from 'node:path';
11
+ import fsPromises from 'node:fs/promises';
12
+ import type { AgentConfig } from '../types.js';
13
+ import { validateProfile, type Profile } from '../schemas/profiles.js';
14
+
15
+ /**
16
+ * Error thrown when agent.json cannot be loaded or parsed
17
+ */
18
+ export class AgentConfigError extends Error {
19
+ constructor(
20
+ message: string,
21
+ public readonly cause?: Error
22
+ ) {
23
+ super(message);
24
+ this.name = 'AgentConfigError';
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Loads and parses agent.json from the specified project directory
30
+ *
31
+ * @param projectRoot - Absolute path to project directory containing agent.json
32
+ * @returns Parsed agent configuration
33
+ * @throws {AgentConfigError} If file cannot be read or parsed
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * try {
38
+ * const config = await loadAgentConfig('/path/to/project');
39
+ * console.log(`Loaded agent: ${config.name} v${config.version}`);
40
+ * } catch (error) {
41
+ * if (error instanceof AgentConfigError) {
42
+ * console.error('Failed to load agent.json:', error.message);
43
+ * }
44
+ * }
45
+ * ```
46
+ */
47
+ export async function loadAgentConfig(projectRoot: string): Promise<AgentConfig> {
48
+ const agentJsonPath = path.join(projectRoot, 'agent.json');
49
+
50
+ try {
51
+ const raw = await fsPromises.readFile(agentJsonPath, 'utf8');
52
+ const config = JSON.parse(raw);
53
+
54
+ // Basic validation
55
+ if (!config.name) {
56
+ throw new AgentConfigError(
57
+ 'agent.json is missing required field: "name"'
58
+ );
59
+ }
60
+
61
+ return config;
62
+ } catch (error) {
63
+ if (error instanceof AgentConfigError) {
64
+ throw error;
65
+ }
66
+
67
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
68
+ throw new AgentConfigError(
69
+ `agent.json not found in ${projectRoot}\n` +
70
+ 'Make sure you are in a KADI agent project directory.'
71
+ );
72
+ }
73
+
74
+ if (error instanceof SyntaxError) {
75
+ throw new AgentConfigError(
76
+ `agent.json contains invalid JSON: ${error.message}`,
77
+ error
78
+ );
79
+ }
80
+
81
+ throw new AgentConfigError(
82
+ `Failed to load agent.json: ${(error as Error).message}`,
83
+ error as Error
84
+ );
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Gets list of available deploy profiles from agent configuration
90
+ *
91
+ * Filters out special keys like 'services' and 'networks' that are not profiles.
92
+ *
93
+ * @param config - Loaded agent configuration
94
+ * @returns Array of profile names
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * const config = await loadAgentConfig('./');
99
+ * const profiles = getDeployProfiles(config);
100
+ * console.log('Available profiles:', profiles.join(', '));
101
+ * ```
102
+ */
103
+ export function getDeployProfiles(config: AgentConfig): string[] {
104
+ if (!config.deploy) return [];
105
+
106
+ return Object.keys(config.deploy).filter(
107
+ (key) => key !== 'services' && key !== 'networks'
108
+ );
109
+ }
110
+
111
+ /**
112
+ * Validates that a profile exists in the agent configuration
113
+ *
114
+ * @param config - Loaded agent configuration
115
+ * @param profileName - Profile name to validate
116
+ * @returns True if profile exists
117
+ *
118
+ * @example
119
+ * ```typescript
120
+ * if (!hasProfile(config, 'production')) {
121
+ * console.error('Production profile not found');
122
+ * }
123
+ * ```
124
+ */
125
+ export function hasProfile(config: AgentConfig, profileName: string): boolean {
126
+ return getDeployProfiles(config).includes(profileName);
127
+ }
128
+
129
+ /**
130
+ * Gets and validates a specific deploy profile from agent configuration
131
+ *
132
+ * Validates the profile structure using Zod schemas to ensure all required
133
+ * fields are present and correctly typed. Throws descriptive errors if
134
+ * validation fails.
135
+ *
136
+ * @param config - Loaded agent configuration
137
+ * @param profileName - Profile name to retrieve
138
+ * @returns Validated profile configuration or undefined if not found
139
+ * @throws Error if profile exists but fails validation
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * const prodProfile = getProfile(config, 'production');
144
+ * if (prodProfile) {
145
+ * // prodProfile is fully typed and validated
146
+ * console.log('Target:', prodProfile.target);
147
+ * }
148
+ * ```
149
+ */
150
+ export function getProfile(config: AgentConfig, profileName: string): Profile | undefined {
151
+ const rawProfile = config.deploy?.[profileName];
152
+ if (!rawProfile) {
153
+ return undefined;
154
+ }
155
+
156
+ // Validate profile structure with Zod
157
+ return validateProfile(rawProfile, profileName);
158
+ }
159
+
160
+ /**
161
+ * Gets the first available profile from agent configuration
162
+ *
163
+ * Useful when user doesn't specify a profile and we want to use a sensible default.
164
+ *
165
+ * @param config - Loaded agent configuration
166
+ * @returns First profile name or undefined if no profiles exist
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * const defaultProfile = getFirstProfile(config);
171
+ * console.log('Using profile:', defaultProfile || 'none');
172
+ * ```
173
+ */
174
+ export function getFirstProfile(config: AgentConfig): string | undefined {
175
+ const profiles = getDeployProfiles(config);
176
+ return profiles.length > 0 ? profiles[0] : undefined;
177
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Configuration Layer Barrel Export
3
+ *
4
+ * Centralized export point for all configuration utilities.
5
+ *
6
+ * @module config
7
+ */
8
+
9
+ export * from './agent-loader.js';
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Deployment Information Display
3
+ *
4
+ * Formats and displays comprehensive deployment information from deploy-ability's
5
+ * AkashDeploymentData type. Provides a beautiful, informative summary of deployments.
6
+ *
7
+ * @module display/deployment-info
8
+ */
9
+
10
+ import type { AkashDeploymentData } from '@kadi.build/deploy-ability/types';
11
+ import {
12
+ bold,
13
+ dim,
14
+ success,
15
+ info,
16
+ highlight,
17
+ formatKeyValue,
18
+ formatUrl,
19
+ formatHeader
20
+ } from '../cli/colors.js';
21
+ import { displayLeasePrice, displayLeasePriceWithUsd } from './pricing.js';
22
+ import { displayServiceResources, displayServicesTable } from './resources.js';
23
+
24
+ /**
25
+ * Displays comprehensive deployment information
26
+ *
27
+ * Shows all relevant deployment details including:
28
+ * - Deployment ID and status
29
+ * - Provider information
30
+ * - Lease pricing
31
+ * - Service resources
32
+ * - Endpoints (if available)
33
+ * - Quick access links
34
+ *
35
+ * @param data - Deployment data from deploy-ability
36
+ * @param aktPriceUsd - Optional AKT market price for USD conversion
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * const result = await deployToAkash(options);
41
+ * if (result.success) {
42
+ * displayDeploymentInfo(result.data);
43
+ * }
44
+ * ```
45
+ */
46
+ export function displayDeploymentInfo(
47
+ data: AkashDeploymentData,
48
+ aktPriceUsd?: number
49
+ ): void {
50
+ const separator = '─'.repeat(70);
51
+
52
+ console.log('');
53
+ console.log(success('✓ Deployment Successful!'));
54
+ console.log('');
55
+ console.log(separator);
56
+ console.log(formatHeader('Deployment Information'));
57
+ console.log(separator);
58
+ console.log('');
59
+
60
+ // Core deployment details
61
+ console.log(formatKeyValue('Deployment ID (DSEQ)', bold(data.dseq.toString())));
62
+ console.log(formatKeyValue('Owner', data.owner));
63
+ console.log(formatKeyValue('Provider', data.provider));
64
+ console.log(formatKeyValue('Network', highlight(data.network)));
65
+ console.log(formatKeyValue('Status', data.status === 'active' ? success('Active') : dim('Closed')));
66
+ console.log(formatKeyValue('Deployed At', data.deployedAt.toLocaleString()));
67
+ console.log('');
68
+
69
+ // Provider information
70
+ console.log(formatHeader('Provider Details'));
71
+ console.log(separator);
72
+ console.log('');
73
+ console.log(formatKeyValue('Provider URI', data.providerUri));
74
+ console.log(formatKeyValue('Group Sequence', data.gseq.toString()));
75
+ console.log(formatKeyValue('Order Sequence', data.oseq.toString()));
76
+ console.log('');
77
+
78
+ // Lease pricing
79
+ console.log(formatHeader('Lease Pricing'));
80
+ console.log(separator);
81
+ console.log('');
82
+ console.log(formatKeyValue('Created At', data.leaseCreatedAt));
83
+
84
+ if (aktPriceUsd) {
85
+ displayLeasePriceWithUsd(data.leasePrice, aktPriceUsd);
86
+ } else {
87
+ displayLeasePrice(data.leasePrice);
88
+ }
89
+ console.log('');
90
+
91
+ // Service resources
92
+ if (data.services && data.services.length > 0) {
93
+ console.log(formatHeader('Services'));
94
+ console.log(separator);
95
+ console.log('');
96
+ displayServiceResources(data.services);
97
+ }
98
+
99
+ // Endpoints (if available)
100
+ if (data.endpoints && Object.keys(data.endpoints).length > 0) {
101
+ console.log(formatHeader('Service Endpoints'));
102
+ console.log(separator);
103
+ console.log('');
104
+ for (const [serviceName, endpoint] of Object.entries(data.endpoints)) {
105
+ console.log(formatKeyValue(serviceName, formatUrl(endpoint)));
106
+ }
107
+ console.log('');
108
+ }
109
+
110
+ // Quick access links
111
+ console.log(formatHeader('Quick Access'));
112
+ console.log(separator);
113
+ console.log('');
114
+
115
+ const consoleUrl = data.network === 'mainnet'
116
+ ? `https://console.akash.network/deployments/${data.dseq}`
117
+ : `https://console-sandbox.akash.network/deployments/${data.dseq}`;
118
+
119
+ console.log(formatKeyValue('Akash Console', formatUrl(consoleUrl)));
120
+ console.log(formatKeyValue('Provider Dashboard', formatUrl(data.providerUri)));
121
+ console.log('');
122
+
123
+ console.log(dim('💡 Your deployment is now live on the Akash Network!'));
124
+ console.log('');
125
+ }
126
+
127
+ /**
128
+ * Displays a compact deployment summary
129
+ *
130
+ * Shows only the most important information in a concise format.
131
+ * Useful for quick confirmations or when displaying multiple deployments.
132
+ *
133
+ * @param data - Deployment data from deploy-ability
134
+ * @param aktPriceUsd - Optional AKT market price for USD conversion
135
+ *
136
+ * @example
137
+ * ```typescript
138
+ * displayDeploymentSummary(result.data, 0.52);
139
+ * // Output:
140
+ * // ✓ Deployed DSEQ 12345 to testnet
141
+ * // Provider: akash1...
142
+ * // Cost: 320.55 AKT/month ($166.69/month)
143
+ * ```
144
+ */
145
+ export function displayDeploymentSummary(data: AkashDeploymentData, aktPriceUsd?: number): void {
146
+ console.log('');
147
+ console.log(success(`✓ Deployed DSEQ ${bold(data.dseq.toString())} to ${highlight(data.network)}`));
148
+ console.log(` Provider: ${dim(data.provider)}`);
149
+
150
+ // Display cost with USD if price available
151
+ const aktCost = data.leasePrice.akt.perMonth.toFixed(2);
152
+ if (aktPriceUsd) {
153
+ const usdCost = data.leasePrice.toUSD(aktPriceUsd).perMonth.toFixed(2);
154
+ console.log(` Cost: ${info(`${aktCost} AKT/month`)} ${dim(`($${usdCost}/month)`)}`);
155
+ } else {
156
+ console.log(` Cost: ${info(`${aktCost} AKT/month`)}`);
157
+ }
158
+
159
+ if (data.services && data.services.length > 0) {
160
+ const serviceCount = data.services.length;
161
+ const replicaCount = data.services.reduce((sum, s) => sum + s.replicaCount, 0);
162
+ console.log(` Services: ${info(serviceCount.toString())} (${replicaCount} total replicas)`);
163
+ }
164
+
165
+ console.log('');
166
+ }
167
+
168
+ /**
169
+ * Displays deployment profile information
170
+ *
171
+ * Shows which profile was used for deployment and its key settings.
172
+ *
173
+ * @param profileName - Profile name that was used
174
+ * @param data - Deployment data
175
+ *
176
+ * @example
177
+ * ```typescript
178
+ * displayProfileInfo('production', result.data);
179
+ * ```
180
+ */
181
+ export function displayProfileInfo(profileName: string, data: AkashDeploymentData): void {
182
+ console.log(formatHeader('Deployment Profile'));
183
+ console.log(` ${formatKeyValue('Profile Name', bold(profileName))}`);
184
+ console.log(` ${formatKeyValue('Network', data.network)}`);
185
+ console.log(` ${formatKeyValue('Target', 'akash')}`);
186
+ console.log('');
187
+ }
188
+
189
+ /**
190
+ * Displays dry-run information
191
+ *
192
+ * Shows what would be deployed without actually executing the deployment.
193
+ *
194
+ * @param profileName - Profile name
195
+ * @param sdl - Generated SDL content
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * displayDryRunInfo('production', sdlContent);
200
+ * ```
201
+ */
202
+ export function displayDryRunInfo(profileName: string, sdl: string): void {
203
+ console.log('');
204
+ console.log(formatHeader('Dry Run - Deployment Preview'));
205
+ console.log(` ${formatKeyValue('Profile', bold(profileName))}`);
206
+ console.log('');
207
+ console.log(dim('Generated SDL:'));
208
+ console.log('─'.repeat(70));
209
+ console.log(sdl);
210
+ console.log('─'.repeat(70));
211
+ console.log('');
212
+ console.log(dim('ℹ️ This is a dry run - no deployment was created'));
213
+ console.log('');
214
+ }
215
+
216
+ /**
217
+ * Barrel export
218
+ */
219
+ export * from './pricing.js';
220
+ export * from './resources.js';
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Lease Pricing Display Utilities
3
+ *
4
+ * Formats and displays lease pricing information from deploy-ability's LeasePrice type.
5
+ * Provides multiple views: per-block, per-hour, per-month in both uAKT and AKT.
6
+ *
7
+ * @module display/pricing
8
+ */
9
+
10
+ import type { LeasePrice } from '@kadi.build/deploy-ability/akash';
11
+ import { formatKeyValue, success, dim } from '../cli/colors.js';
12
+
13
+ /**
14
+ * Displays lease pricing in a formatted, human-readable manner
15
+ *
16
+ * Shows pricing in multiple time units (block, hour, month) and denominations (uAKT, AKT).
17
+ *
18
+ * @param leasePrice - Lease price object from deploy-ability
19
+ * @param indent - Indentation level (default: 0)
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * displayLeasePrice(deploymentData.leasePrice);
24
+ * // Output:
25
+ * // Price per block: 450 uAKT (0.000450 AKT)
26
+ * // Price per hour: 443,705 uAKT (0.443705 AKT)
27
+ * // Price per month: 320,548,800 uAKT (320.548800 AKT)
28
+ * ```
29
+ */
30
+ export function displayLeasePrice(leasePrice: LeasePrice, indent: number = 0): void {
31
+ const prefix = ' '.repeat(indent);
32
+
33
+ console.log(`${prefix}${formatKeyValue('Price per block', `${leasePrice.uakt.perBlock} uAKT (${leasePrice.akt.perBlock} AKT)`)}`);
34
+ console.log(`${prefix}${formatKeyValue('Price per hour', `${leasePrice.uakt.perHour.toLocaleString()} uAKT (${leasePrice.akt.perHour} AKT)`)}`);
35
+ console.log(`${prefix}${formatKeyValue('Price per month', `${leasePrice.uakt.perMonth.toLocaleString()} uAKT (${success(leasePrice.akt.perMonth + ' AKT')})`)}`)
36
+ ;
37
+ }
38
+
39
+ /**
40
+ * Displays lease pricing with USD conversion
41
+ *
42
+ * Shows pricing in uAKT, AKT, and USD when AKT market price is provided.
43
+ *
44
+ * @param leasePrice - Lease price object from deploy-ability
45
+ * @param aktPriceUsd - Current AKT market price in USD (optional)
46
+ * @param indent - Indentation level (default: 0)
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * // With USD conversion
51
+ * displayLeasePriceWithUsd(deploymentData.leasePrice, 0.50);
52
+ * // Output includes USD amounts:
53
+ * // Price per month: 320.55 AKT ($160.27 USD)
54
+ * ```
55
+ */
56
+ export function displayLeasePriceWithUsd(
57
+ leasePrice: LeasePrice,
58
+ aktPriceUsd?: number,
59
+ indent: number = 0
60
+ ): void {
61
+ const prefix = ' '.repeat(indent);
62
+
63
+ if (aktPriceUsd) {
64
+ const usdPrices = leasePrice.toUSD(aktPriceUsd);
65
+
66
+ console.log(`${prefix}${formatKeyValue('Price per block', `${leasePrice.uakt.perBlock} uAKT ($${usdPrices.perBlock.toFixed(6)} USD)`)}`);
67
+ console.log(`${prefix}${formatKeyValue('Price per hour', `${leasePrice.akt.perHour} AKT ($${usdPrices.perHour.toFixed(4)} USD)`)}`);
68
+ console.log(`${prefix}${formatKeyValue('Price per month', `${success(leasePrice.akt.perMonth + ' AKT')} (${success('$' + usdPrices.perMonth.toFixed(2) + ' USD')})`)}`)
69
+ ;
70
+ } else {
71
+ // No USD price available, use standard display
72
+ displayLeasePrice(leasePrice, indent);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Gets a compact one-line pricing summary
78
+ *
79
+ * Useful for displaying in summaries or lists.
80
+ *
81
+ * @param leasePrice - Lease price object from deploy-ability
82
+ * @returns Formatted price string
83
+ *
84
+ * @example
85
+ * ```typescript
86
+ * const summary = getPricingSummary(leasePrice);
87
+ * console.log(`Monthly cost: ${summary}`);
88
+ * // Output: "Monthly cost: 320.55 AKT/month"
89
+ * ```
90
+ */
91
+ export function getPricingSummary(leasePrice: LeasePrice): string {
92
+ return `${leasePrice.akt.perMonth} AKT/month`;
93
+ }
94
+
95
+ /**
96
+ * Gets a compact one-line pricing summary with USD
97
+ *
98
+ * @param leasePrice - Lease price object from deploy-ability
99
+ * @param aktPriceUsd - Current AKT market price in USD
100
+ * @returns Formatted price string with USD
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * const summary = getPricingSummaryWithUsd(leasePrice, 0.50);
105
+ * console.log(`Monthly cost: ${summary}`);
106
+ * // Output: "Monthly cost: 320.55 AKT ($160.27 USD)/month"
107
+ * ```
108
+ */
109
+ export function getPricingSummaryWithUsd(leasePrice: LeasePrice, aktPriceUsd: number): string {
110
+ const usdPrices = leasePrice.toUSD(aktPriceUsd);
111
+ return `${leasePrice.akt.perMonth} AKT ($${usdPrices.perMonth.toFixed(2)} USD)/month`;
112
+ }
113
+
114
+ /**
115
+ * Displays pricing calculation methodology
116
+ *
117
+ * Shows the formulas and constants used for price calculations.
118
+ * Useful for transparency and debugging.
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * displayPricingMethodology();
123
+ * // Output:
124
+ * // Pricing Calculation:
125
+ * // - Average block time: 6.098 seconds
126
+ * // - Blocks per hour: 590.3...
127
+ * // - Average month: 30.437 days
128
+ * ```
129
+ */
130
+ export function displayPricingMethodology(): void {
131
+ console.log(dim('\nPricing Calculation Methodology:'));
132
+ console.log(dim(' • Average block time: 6.098 seconds'));
133
+ console.log(dim(' • Blocks per hour: 590.36 (3600 / 6.098)'));
134
+ console.log(dim(' • Hours per month: 730.48 (30.437 days)'));
135
+ console.log(dim(' • Per month = per block × blocks/hour × hours/month'));
136
+ console.log(dim(' • Source: Akash Console pricing logic\n'));
137
+ }