@agirails/sdk 2.4.2 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/ACTPClient.js +1 -1
  2. package/dist/ACTPClient.js.map +1 -1
  3. package/dist/cli/commands/deploy-check.d.ts +24 -0
  4. package/dist/cli/commands/deploy-check.d.ts.map +1 -0
  5. package/dist/cli/commands/deploy-check.js +316 -0
  6. package/dist/cli/commands/deploy-check.js.map +1 -0
  7. package/dist/cli/commands/deploy-env.d.ts +19 -0
  8. package/dist/cli/commands/deploy-env.d.ts.map +1 -0
  9. package/dist/cli/commands/deploy-env.js +123 -0
  10. package/dist/cli/commands/deploy-env.js.map +1 -0
  11. package/dist/cli/commands/init.d.ts.map +1 -1
  12. package/dist/cli/commands/init.js +15 -1
  13. package/dist/cli/commands/init.js.map +1 -1
  14. package/dist/cli/commands/publish.js +1 -1
  15. package/dist/cli/commands/publish.js.map +1 -1
  16. package/dist/cli/commands/register.js +1 -1
  17. package/dist/cli/commands/register.js.map +1 -1
  18. package/dist/cli/index.js +5 -0
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/cli/utils/config.d.ts +12 -0
  21. package/dist/cli/utils/config.d.ts.map +1 -1
  22. package/dist/cli/utils/config.js +61 -1
  23. package/dist/cli/utils/config.js.map +1 -1
  24. package/dist/level0/request.js +1 -1
  25. package/dist/level0/request.js.map +1 -1
  26. package/dist/level1/Agent.js +1 -1
  27. package/dist/level1/Agent.js.map +1 -1
  28. package/dist/wallet/keystore.d.ts +10 -3
  29. package/dist/wallet/keystore.d.ts.map +1 -1
  30. package/dist/wallet/keystore.js +91 -11
  31. package/dist/wallet/keystore.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/ACTPClient.ts +1 -1
  34. package/src/cli/commands/deploy-check.ts +364 -0
  35. package/src/cli/commands/deploy-env.ts +120 -0
  36. package/src/cli/commands/init.ts +15 -1
  37. package/src/cli/commands/publish.ts +1 -1
  38. package/src/cli/commands/register.ts +1 -1
  39. package/src/cli/index.ts +6 -0
  40. package/src/cli/utils/config.ts +68 -0
  41. package/src/level0/request.ts +1 -1
  42. package/src/level1/Agent.ts +1 -1
  43. package/src/wallet/keystore.ts +116 -11
@@ -27,20 +27,24 @@ exports._clearCache = exports.getCachedAddress = exports.resolvePrivateKey = voi
27
27
  /**
28
28
  * Keystore auto-resolution for ACTP wallets.
29
29
  *
30
- * Resolution order:
31
- * 1. ACTP_PRIVATE_KEY env var (backward compat, highest priority)
32
- * 2. .actp/keystore.json decrypted with ACTP_KEY_PASSWORD
33
- * 3. undefined (caller decides what to do)
30
+ * Resolution order (AIP-13):
31
+ * 1. ACTP_PRIVATE_KEY env var (policy-gated: mainnet/unknown = hard fail)
32
+ * 2. ACTP_KEYSTORE_BASE64 + ACTP_KEY_PASSWORD (deployment-safe, preferred)
33
+ * 3. .actp/keystore.json decrypted with ACTP_KEY_PASSWORD
34
+ * 4. undefined (caller decides what to do)
34
35
  */
35
36
  const fs = __importStar(require("fs"));
36
37
  const path = __importStar(require("path"));
37
38
  const ethers_1 = require("ethers");
39
+ const Logger_1 = require("../utils/Logger");
38
40
  /** 30-minute TTL for cached private keys */
39
41
  const CACHE_TTL_MS = 30 * 60 * 1000;
40
42
  // Cache keyed by resolved keystorePath to support multiple stateDirectories
41
43
  const _cache = new Map();
42
44
  // Separate cache for env-var-resolved key (no path dependency)
43
45
  let _envCache = null;
46
+ // Separate cache for base64-resolved key (no path dependency)
47
+ let _base64Cache = null;
44
48
  function isExpired(entry) {
45
49
  return Date.now() >= entry.expiresAt;
46
50
  }
@@ -70,12 +74,47 @@ function validateRawKey(raw, source) {
70
74
  return trimmed;
71
75
  }
72
76
  /**
73
- * Auto-resolve private key: env var keystore → undefined.
77
+ * Determine the effective network for ACTP_PRIVATE_KEY policy.
78
+ * Falls back to ACTP_NETWORK env var. Null means unknown (fail-closed).
79
+ */
80
+ function getEffectiveNetwork(options) {
81
+ return options?.network ?? process.env.ACTP_NETWORK ?? null;
82
+ }
83
+ /**
84
+ * Enforce ACTP_PRIVATE_KEY policy based on network (AIP-13).
85
+ * - mainnet/unknown: hard fail
86
+ * - testnet: warn once (on first resolution, not cache hits)
87
+ * - mock: silent
88
+ */
89
+ function enforcePrivateKeyPolicy(network) {
90
+ if (network === 'mock')
91
+ return;
92
+ if (network === 'testnet') {
93
+ // Warn once (only on first resolution — _envCache is null)
94
+ if (_envCache === null) {
95
+ Logger_1.sdkLogger.warn('ACTP_PRIVATE_KEY is deprecated. Use ACTP_KEYSTORE_BASE64 instead.\n' +
96
+ 'Run: actp deploy:env');
97
+ }
98
+ return;
99
+ }
100
+ // mainnet or unknown (null) — fail-closed
101
+ const networkLabel = network === 'mainnet' ? 'production' : 'unknown network (fail-closed)';
102
+ throw new Error(`ACTP_PRIVATE_KEY is not allowed in ${networkLabel}. Use ACTP_KEYSTORE_BASE64 instead.\n` +
103
+ 'Run: actp deploy:env\n' +
104
+ 'If this is testnet, set ACTP_NETWORK=testnet');
105
+ }
106
+ /**
107
+ * Auto-resolve private key: env var → base64 keystore → file keystore → undefined.
74
108
  * Never logs or prints the key itself.
109
+ *
110
+ * @param stateDirectory - Directory containing .actp/ (defaults to cwd)
111
+ * @param options - Options including network for ACTP_PRIVATE_KEY policy
75
112
  */
76
- async function resolvePrivateKey(stateDirectory) {
77
- // 1. Env var (highest priority, backward compat)
113
+ async function resolvePrivateKey(stateDirectory, options) {
114
+ // 1. ACTP_PRIVATE_KEY (highest priority, policy-gated)
78
115
  if (process.env.ACTP_PRIVATE_KEY) {
116
+ const network = getEffectiveNetwork(options);
117
+ enforcePrivateKeyPolicy(network);
79
118
  if (_envCache && !isExpired(_envCache))
80
119
  return _envCache.key;
81
120
  const key = validateRawKey(process.env.ACTP_PRIVATE_KEY, 'ACTP_PRIVATE_KEY env var');
@@ -83,7 +122,44 @@ async function resolvePrivateKey(stateDirectory) {
83
122
  _envCache = { key, address, expiresAt: Date.now() + CACHE_TTL_MS };
84
123
  return key;
85
124
  }
86
- // 2. Resolve keystore path
125
+ // 2. ACTP_KEYSTORE_BASE64 (deployment-safe, preferred for production)
126
+ if (process.env.ACTP_KEYSTORE_BASE64) {
127
+ if (_base64Cache && !isExpired(_base64Cache))
128
+ return _base64Cache.key;
129
+ const raw = process.env.ACTP_KEYSTORE_BASE64.replace(/\s/g, '');
130
+ let decoded;
131
+ try {
132
+ decoded = Buffer.from(raw, 'base64').toString('utf-8');
133
+ }
134
+ catch {
135
+ throw new Error('ACTP_KEYSTORE_BASE64 is not valid base64.\n' +
136
+ 'Run: actp deploy:env');
137
+ }
138
+ try {
139
+ JSON.parse(decoded);
140
+ }
141
+ catch {
142
+ throw new Error('ACTP_KEYSTORE_BASE64 is not valid encrypted keystore JSON.\n' +
143
+ 'Run: actp deploy:env');
144
+ }
145
+ const password = process.env.ACTP_KEY_PASSWORD;
146
+ if (!password) {
147
+ throw new Error('ACTP_KEYSTORE_BASE64 is set but ACTP_KEY_PASSWORD is not set.\n' +
148
+ 'Set it: export ACTP_KEY_PASSWORD="your-password"');
149
+ }
150
+ let wallet;
151
+ try {
152
+ wallet = (await ethers_1.Wallet.fromEncryptedJson(decoded, password));
153
+ }
154
+ catch (err) {
155
+ // Sanitize: do not leak keystore content in error messages
156
+ const message = err instanceof Error ? err.message : 'unknown error';
157
+ throw new Error(`Failed to decrypt ACTP_KEYSTORE_BASE64: ${message}`);
158
+ }
159
+ _base64Cache = { key: wallet.privateKey, address: wallet.address, expiresAt: Date.now() + CACHE_TTL_MS };
160
+ return wallet.privateKey;
161
+ }
162
+ // 3. Resolve keystore path
87
163
  if (stateDirectory) {
88
164
  validateStateDirectory(stateDirectory);
89
165
  }
@@ -91,13 +167,13 @@ async function resolvePrivateKey(stateDirectory) {
91
167
  ? path.join(stateDirectory, '.actp')
92
168
  : path.join(process.cwd(), '.actp');
93
169
  const keystorePath = path.resolve(actpDir, 'keystore.json');
94
- // 3. Cache hit (keyed by resolved path, with TTL)
170
+ // 4. Cache hit (keyed by resolved path, with TTL)
95
171
  const cached = _cache.get(keystorePath);
96
172
  if (cached && !isExpired(cached))
97
173
  return cached.key;
98
174
  if (cached)
99
175
  _cache.delete(keystorePath); // expired
100
- // 4. Keystore file
176
+ // 5. Keystore file
101
177
  if (!fs.existsSync(keystorePath))
102
178
  return undefined;
103
179
  const password = process.env.ACTP_KEY_PASSWORD;
@@ -113,12 +189,15 @@ async function resolvePrivateKey(stateDirectory) {
113
189
  exports.resolvePrivateKey = resolvePrivateKey;
114
190
  /**
115
191
  * Get cached address from last resolvePrivateKey() call.
116
- * Works for both env-var and keystore resolution paths.
192
+ * Works for env-var, base64, and keystore resolution paths.
117
193
  */
118
194
  function getCachedAddress(stateDirectory) {
119
195
  // Env var path
120
196
  if (_envCache && !isExpired(_envCache))
121
197
  return _envCache.address;
198
+ // Base64 path
199
+ if (_base64Cache && !isExpired(_base64Cache))
200
+ return _base64Cache.address;
122
201
  // Keystore path — look up by resolved path
123
202
  const actpDir = stateDirectory
124
203
  ? path.join(stateDirectory, '.actp')
@@ -137,6 +216,7 @@ exports.getCachedAddress = getCachedAddress;
137
216
  function _clearCache() {
138
217
  _cache.clear();
139
218
  _envCache = null;
219
+ _base64Cache = null;
140
220
  }
141
221
  exports._clearCache = _clearCache;
142
222
  //# sourceMappingURL=keystore.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"keystore.js","sourceRoot":"","sources":["../../src/wallet/keystore.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"keystore.js","sourceRoot":"","sources":["../../src/wallet/keystore.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agirails/sdk",
3
- "version": "2.4.2",
3
+ "version": "2.5.0",
4
4
  "description": "AGIRAILS SDK for the ACTP (Agent Commerce Transaction Protocol) - Unified mock + blockchain support",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/ACTPClient.ts CHANGED
@@ -736,7 +736,7 @@ export class ACTPClient {
736
736
  // Auto-detect private key from keystore / env var if not provided
737
737
  if (!config.privateKey) {
738
738
  const { resolvePrivateKey } = await import('./wallet/keystore');
739
- const resolved = await resolvePrivateKey(config.stateDirectory);
739
+ const resolved = await resolvePrivateKey(config.stateDirectory, { network: config.mode });
740
740
  if (resolved) {
741
741
  config = { ...config, privateKey: resolved };
742
742
  } else {
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Deploy:check Command - Pre-deploy security audit (AIP-13)
3
+ *
4
+ * Scans the project for common deployment security issues:
5
+ * - Missing ignore files
6
+ * - Raw private keys in env files, Dockerfiles, CI configs
7
+ * - Symlinked keystore
8
+ * - Incorrect file permissions
9
+ *
10
+ * Exit code 0: all checks pass (warnings allowed)
11
+ * Exit code 1: at least one FAIL check
12
+ *
13
+ * @module cli/commands/deploy-check
14
+ */
15
+
16
+ import * as fs from 'fs';
17
+ import * as path from 'path';
18
+ import { Command } from 'commander';
19
+ import { Output, ExitCode } from '../utils/output';
20
+ import { getActpDir } from '../utils/config';
21
+
22
+ // ============================================================================
23
+ // Types
24
+ // ============================================================================
25
+
26
+ type CheckSeverity = 'FAIL' | 'WARN';
27
+ type CheckResult = 'PASS' | 'FAIL' | 'WARN' | 'SKIP';
28
+
29
+ interface CheckOutput {
30
+ name: string;
31
+ result: CheckResult;
32
+ message?: string;
33
+ }
34
+
35
+ // ============================================================================
36
+ // Command Definition
37
+ // ============================================================================
38
+
39
+ export function createDeployCheckCommand(): Command {
40
+ const cmd = new Command('deploy:check')
41
+ .description('Pre-deploy security audit (AIP-13)')
42
+ .option('--json', 'Output as JSON')
43
+ .option('-q, --quiet', 'Only show failures')
44
+ .action(async (options) => {
45
+ const output = new Output(
46
+ options.json ? 'json' : options.quiet ? 'quiet' : 'human'
47
+ );
48
+
49
+ try {
50
+ const exitCode = runDeployCheck(options, output);
51
+ process.exit(exitCode);
52
+ } catch (error) {
53
+ output.errorResult({
54
+ code: 'DEPLOY_CHECK_FAILED',
55
+ message: (error as Error).message,
56
+ });
57
+ process.exit(ExitCode.ERROR);
58
+ }
59
+ });
60
+
61
+ return cmd;
62
+ }
63
+
64
+ // ============================================================================
65
+ // Implementation
66
+ // ============================================================================
67
+
68
+ /** Regex matching a raw private key pattern */
69
+ const RAW_KEY_PATTERN = /0x[0-9a-fA-F]{64}/;
70
+ const RAW_KEY_ENV_PATTERN = /ACTP_PRIVATE_KEY\s*=\s*0x/;
71
+
72
+ interface DeployCheckOptions {
73
+ json?: boolean;
74
+ quiet?: boolean;
75
+ }
76
+
77
+ export function runDeployCheck(options: DeployCheckOptions, output: Output): number {
78
+ const projectRoot = process.cwd();
79
+ const actpDir = getActpDir(projectRoot);
80
+ const results: CheckOutput[] = [];
81
+ let failCount = 0;
82
+ let warnCount = 0;
83
+ let passCount = 0;
84
+
85
+ // ── FAIL checks ──────────────────────────────────────────────────────
86
+
87
+ // 1. .actp/ in .gitignore
88
+ results.push(checkIgnoreFile(projectRoot, '.gitignore', '.actp', 'FAIL'));
89
+
90
+ // 2. .actp/ in .dockerignore
91
+ results.push(checkIgnoreFile(projectRoot, '.dockerignore', '.actp', 'FAIL'));
92
+
93
+ // 3. No ACTP_PRIVATE_KEY in .env* files
94
+ results.push(checkNoRawKeysInEnvFiles(projectRoot));
95
+
96
+ // 4. No raw keys in Dockerfiles
97
+ results.push(checkNoRawKeysInFiles(
98
+ projectRoot,
99
+ ['Dockerfile', 'Dockerfile.*', 'docker-compose.yml', 'docker-compose.yaml', 'compose.yml', 'compose.yaml'],
100
+ 'No raw keys in Dockerfiles'
101
+ ));
102
+
103
+ // 5. No raw keys in CI/workflow files
104
+ results.push(checkNoRawKeysInCIFiles(projectRoot));
105
+
106
+ // 6. Keystore is not a symlink
107
+ results.push(checkKeystoreNotSymlink(actpDir));
108
+
109
+ // ── WARN checks ──────────────────────────────────────────────────────
110
+
111
+ // 7. ACTP_KEYSTORE_BASE64 or keystore.json exists
112
+ results.push(checkKeystoreAvailable(actpDir));
113
+
114
+ // 8. Keystore file permissions (POSIX only)
115
+ results.push(checkKeystorePermissions(actpDir));
116
+
117
+ // ── Output ───────────────────────────────────────────────────────────
118
+
119
+ for (const check of results) {
120
+ if (check.result === 'FAIL') failCount++;
121
+ else if (check.result === 'WARN') warnCount++;
122
+ else if (check.result === 'PASS') passCount++;
123
+ }
124
+
125
+ if (options.json) {
126
+ output.result({
127
+ checks: results,
128
+ summary: { passed: passCount, warnings: warnCount, failed: failCount },
129
+ });
130
+ } else {
131
+ output.print('actp deploy:check');
132
+ for (const check of results) {
133
+ if (options.quiet && check.result !== 'FAIL') continue;
134
+ const tag = `[${check.result}]`;
135
+ const msg = check.message ? ` ${check.message}` : '';
136
+ output.print(` ${tag} ${check.name}${msg}`);
137
+ }
138
+ output.print('');
139
+ output.print(` ${passCount} passed, ${warnCount} warnings, ${failCount} failed`);
140
+ }
141
+
142
+ return failCount > 0 ? ExitCode.ERROR : ExitCode.SUCCESS;
143
+ }
144
+
145
+ // ============================================================================
146
+ // Individual Checks
147
+ // ============================================================================
148
+
149
+ function checkIgnoreFile(
150
+ projectRoot: string,
151
+ fileName: string,
152
+ entry: string,
153
+ _severity: CheckSeverity
154
+ ): CheckOutput {
155
+ const filePath = path.join(projectRoot, fileName);
156
+ const name = `${entry} in ${fileName}`;
157
+
158
+ if (!fs.existsSync(filePath)) {
159
+ return { name, result: 'FAIL', message: `${fileName} does not exist` };
160
+ }
161
+
162
+ const content = fs.readFileSync(filePath, 'utf-8');
163
+ if (content.includes(entry)) {
164
+ return { name, result: 'PASS' };
165
+ }
166
+
167
+ return { name, result: 'FAIL', message: `${entry} not found in ${fileName}` };
168
+ }
169
+
170
+ function checkNoRawKeysInEnvFiles(projectRoot: string): CheckOutput {
171
+ const name = 'No raw keys in .env files';
172
+ const envFiles = findMatchingFiles(projectRoot, ['.env', '.env.*', '.env.local', '.env.production']);
173
+
174
+ for (const file of envFiles) {
175
+ const content = safeReadFile(file);
176
+ if (content && (RAW_KEY_PATTERN.test(content) || RAW_KEY_ENV_PATTERN.test(content))) {
177
+ return { name, result: 'FAIL', message: `Raw key found in ${path.basename(file)}` };
178
+ }
179
+ }
180
+
181
+ return { name, result: 'PASS' };
182
+ }
183
+
184
+ function checkNoRawKeysInFiles(
185
+ projectRoot: string,
186
+ patterns: string[],
187
+ name: string
188
+ ): CheckOutput {
189
+ const files = findMatchingFiles(projectRoot, patterns);
190
+
191
+ for (const file of files) {
192
+ const content = safeReadFile(file);
193
+ if (content && RAW_KEY_PATTERN.test(content)) {
194
+ return { name, result: 'FAIL', message: `Raw key found in ${path.basename(file)}` };
195
+ }
196
+ }
197
+
198
+ return { name, result: 'PASS' };
199
+ }
200
+
201
+ function checkNoRawKeysInCIFiles(projectRoot: string): CheckOutput {
202
+ const name = 'No raw keys in CI/workflow files';
203
+
204
+ // Collect all CI-related files
205
+ const files: string[] = [];
206
+
207
+ // GitHub workflows
208
+ const workflowDir = path.join(projectRoot, '.github', 'workflows');
209
+ if (fs.existsSync(workflowDir)) {
210
+ try {
211
+ const entries = fs.readdirSync(workflowDir);
212
+ for (const entry of entries) {
213
+ if (entry.endsWith('.yml') || entry.endsWith('.yaml')) {
214
+ files.push(path.join(workflowDir, entry));
215
+ }
216
+ }
217
+ } catch { /* ignore read errors */ }
218
+ }
219
+
220
+ // Platform config files
221
+ const platformFiles = [
222
+ 'railway.json', 'fly.toml', 'vercel.json',
223
+ 'pm2.config.js', 'pm2.config.cjs', 'ecosystem.config.js', 'ecosystem.config.cjs',
224
+ ];
225
+ for (const pf of platformFiles) {
226
+ const fp = path.join(projectRoot, pf);
227
+ if (fs.existsSync(fp)) files.push(fp);
228
+ }
229
+
230
+ for (const file of files) {
231
+ const content = safeReadFile(file);
232
+ if (content && (RAW_KEY_PATTERN.test(content) || RAW_KEY_ENV_PATTERN.test(content))) {
233
+ return { name, result: 'FAIL', message: `Raw key found in ${path.relative(projectRoot, file)}` };
234
+ }
235
+ }
236
+
237
+ return { name, result: 'PASS' };
238
+ }
239
+
240
+ function checkKeystoreNotSymlink(actpDir: string): CheckOutput {
241
+ const name = 'Keystore is not a symlink';
242
+ const keystorePath = path.join(actpDir, 'keystore.json');
243
+
244
+ if (!fs.existsSync(keystorePath)) {
245
+ return { name, result: 'PASS' };
246
+ }
247
+
248
+ try {
249
+ const stat = fs.lstatSync(keystorePath);
250
+ if (stat.isSymbolicLink()) {
251
+ return { name, result: 'FAIL', message: 'keystore.json is a symlink (security risk)' };
252
+ }
253
+ } catch { /* ignore */ }
254
+
255
+ return { name, result: 'PASS' };
256
+ }
257
+
258
+ function checkKeystoreAvailable(actpDir: string): CheckOutput {
259
+ const name = 'Keystore available';
260
+ const keystorePath = path.join(actpDir, 'keystore.json');
261
+
262
+ if (process.env.ACTP_KEYSTORE_BASE64) {
263
+ return { name, result: 'PASS' };
264
+ }
265
+
266
+ if (fs.existsSync(keystorePath)) {
267
+ return { name, result: 'PASS' };
268
+ }
269
+
270
+ return { name, result: 'WARN', message: 'ACTP_KEYSTORE_BASE64 not set (OK for local dev)' };
271
+ }
272
+
273
+ function checkKeystorePermissions(actpDir: string): CheckOutput {
274
+ const name = 'Keystore file permissions';
275
+ const keystorePath = path.join(actpDir, 'keystore.json');
276
+
277
+ // Skip on Windows
278
+ if (process.platform === 'win32') {
279
+ return { name, result: 'SKIP', message: 'Permission check skipped on Windows' };
280
+ }
281
+
282
+ if (!fs.existsSync(keystorePath)) {
283
+ return { name, result: 'PASS' };
284
+ }
285
+
286
+ try {
287
+ const stat = fs.statSync(keystorePath);
288
+ const mode = stat.mode & 0o777;
289
+ if (mode === 0o600) {
290
+ return { name, result: 'PASS' };
291
+ }
292
+ return { name, result: 'WARN', message: `Permissions are 0o${mode.toString(8)} (expected 0o600)` };
293
+ } catch {
294
+ return { name, result: 'WARN', message: 'Could not check file permissions' };
295
+ }
296
+ }
297
+
298
+ // ============================================================================
299
+ // Helpers
300
+ // ============================================================================
301
+
302
+ /** Directories to skip during recursive scan */
303
+ const SKIP_DIRS = new Set(['node_modules', '.git', '.actp', 'dist', 'build', '.next', 'coverage']);
304
+
305
+ /** Max recursion depth to prevent runaway scans */
306
+ const MAX_SCAN_DEPTH = 5;
307
+
308
+ function findMatchingFiles(projectRoot: string, patterns: string[], maxDepth = MAX_SCAN_DEPTH): string[] {
309
+ const files: string[] = [];
310
+ walkDir(projectRoot, patterns, files, 0, maxDepth);
311
+ return files;
312
+ }
313
+
314
+ function walkDir(dir: string, patterns: string[], files: string[], depth: number, maxDepth: number): void {
315
+ if (depth > maxDepth) return;
316
+
317
+ let entries: string[];
318
+ try {
319
+ entries = fs.readdirSync(dir);
320
+ } catch { return; }
321
+
322
+ for (const entry of entries) {
323
+ const fullPath = path.join(dir, entry);
324
+
325
+ // Match files against patterns
326
+ for (const pattern of patterns) {
327
+ if (matchPattern(entry, pattern)) {
328
+ try {
329
+ if (fs.statSync(fullPath).isFile()) {
330
+ files.push(fullPath);
331
+ }
332
+ } catch { /* ignore */ }
333
+ break;
334
+ }
335
+ }
336
+
337
+ // Recurse into subdirectories (skip noise dirs)
338
+ if (!SKIP_DIRS.has(entry)) {
339
+ try {
340
+ if (fs.statSync(fullPath).isDirectory()) {
341
+ walkDir(fullPath, patterns, files, depth + 1, maxDepth);
342
+ }
343
+ } catch { /* ignore */ }
344
+ }
345
+ }
346
+ }
347
+
348
+ function matchPattern(filename: string, pattern: string): boolean {
349
+ // Simple glob: support * and exact match
350
+ if (pattern === filename) return true;
351
+ if (pattern.includes('*')) {
352
+ const regex = new RegExp('^' + pattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
353
+ return regex.test(filename);
354
+ }
355
+ return false;
356
+ }
357
+
358
+ function safeReadFile(filePath: string): string | null {
359
+ try {
360
+ return fs.readFileSync(filePath, 'utf-8');
361
+ } catch {
362
+ return null;
363
+ }
364
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Deploy:env Command - Generate deployment environment variables (AIP-13)
3
+ *
4
+ * Reads .actp/keystore.json, base64-encodes it, and outputs env vars
5
+ * ready for any deployment platform (Railway, Vercel, Fly.io, Hetzner, etc.).
6
+ *
7
+ * @module cli/commands/deploy-env
8
+ */
9
+
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { Command } from 'commander';
13
+ import { Output, ExitCode } from '../utils/output';
14
+ import { getActpDir } from '../utils/config';
15
+
16
+ // ============================================================================
17
+ // Command Definition
18
+ // ============================================================================
19
+
20
+ export function createDeployEnvCommand(): Command {
21
+ const cmd = new Command('deploy:env')
22
+ .description('Generate deployment env vars from keystore (AIP-13)')
23
+ .option('--format <format>', 'Output format: shell (default), docker, json', 'shell')
24
+ .option('--json', 'Machine-readable JSON output')
25
+ .option('-q, --quiet', 'Output only the base64 string (for piping)')
26
+ .action(async (options) => {
27
+ const output = new Output(
28
+ options.json ? 'json' : options.quiet ? 'quiet' : 'human'
29
+ );
30
+
31
+ try {
32
+ runDeployEnv(options, output);
33
+ } catch (error) {
34
+ output.errorResult({
35
+ code: 'DEPLOY_ENV_FAILED',
36
+ message: (error as Error).message,
37
+ });
38
+ process.exit(ExitCode.ERROR);
39
+ }
40
+ });
41
+
42
+ return cmd;
43
+ }
44
+
45
+ // ============================================================================
46
+ // Implementation
47
+ // ============================================================================
48
+
49
+ interface DeployEnvOptions {
50
+ format: string;
51
+ json?: boolean;
52
+ quiet?: boolean;
53
+ }
54
+
55
+ export function runDeployEnv(options: DeployEnvOptions, output: Output): void {
56
+ const projectRoot = process.cwd();
57
+ const actpDir = getActpDir(projectRoot);
58
+ const keystorePath = path.join(actpDir, 'keystore.json');
59
+
60
+ if (!fs.existsSync(keystorePath)) {
61
+ throw new Error(
62
+ 'No keystore found at ' + keystorePath + '\n' +
63
+ 'Run "actp init" first to generate a wallet.'
64
+ );
65
+ }
66
+
67
+ const keystoreContent = fs.readFileSync(keystorePath, 'utf-8');
68
+
69
+ // Validate it's valid JSON
70
+ try {
71
+ JSON.parse(keystoreContent);
72
+ } catch {
73
+ throw new Error('Keystore file is corrupted (not valid JSON).');
74
+ }
75
+
76
+ const base64 = Buffer.from(keystoreContent).toString('base64');
77
+
78
+ // Quiet mode: just the base64 string (for piping)
79
+ if (options.quiet) {
80
+ output.result({ keystoreBase64: base64 }, { quietKey: 'keystoreBase64' });
81
+ return;
82
+ }
83
+
84
+ // JSON mode
85
+ if (options.json || options.format === 'json') {
86
+ output.result({
87
+ keystoreBase64: base64,
88
+ passwordVar: 'ACTP_KEY_PASSWORD',
89
+ });
90
+ return;
91
+ }
92
+
93
+ // Docker format
94
+ if (options.format === 'docker') {
95
+ output.print('# Add to your Dockerfile:');
96
+ output.print(`ENV ACTP_KEYSTORE_BASE64="${base64}"`);
97
+ output.print('ENV ACTP_KEY_PASSWORD="<your keystore password>"');
98
+ output.print('');
99
+ output.print('# WARNING: Prefer build args or secrets over ENV for sensitive values.');
100
+ return;
101
+ }
102
+
103
+ // Shell format (default)
104
+ output.print('# Set these environment variables on your deployment platform:');
105
+ output.print(`ACTP_KEYSTORE_BASE64=${base64}`);
106
+ output.print('ACTP_KEY_PASSWORD=<your keystore password>');
107
+ output.print('');
108
+ output.print('# SECURITY BEST PRACTICE:');
109
+ output.print('# Store ACTP_KEYSTORE_BASE64 and ACTP_KEY_PASSWORD in SEPARATE secret scopes');
110
+ output.print('# where your platform supports it (e.g., different secret groups, vaults, or teams).');
111
+ output.print('# This preserves the two-factor security model.');
112
+ output.print('#');
113
+ output.print('# WARNING: Never echo these values in CI/CD logs or commit them to git.');
114
+ output.print('');
115
+ output.print('# Platform-specific examples:');
116
+ output.print(`# Railway: railway variables set ACTP_KEYSTORE_BASE64="${base64}"`);
117
+ output.print('# Vercel: vercel env add ACTP_KEYSTORE_BASE64');
118
+ output.print(`# Fly.io: fly secrets set ACTP_KEYSTORE_BASE64="${base64}"`);
119
+ output.print('# Hetzner: Add to your .env on the server (ensure .env is in .gitignore)');
120
+ }