delimit-cli 1.0.0 → 2.1.1

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 (95) hide show
  1. package/.github/workflows/api-governance.yml +43 -0
  2. package/README.md +70 -113
  3. package/adapters/codex-skill.js +87 -0
  4. package/adapters/cursor-extension.js +190 -0
  5. package/adapters/gemini-action.js +93 -0
  6. package/adapters/openai-function.js +112 -0
  7. package/adapters/xai-plugin.js +151 -0
  8. package/bin/delimit-cli.js +921 -0
  9. package/bin/delimit.js +237 -1
  10. package/delimit.yml +19 -0
  11. package/hooks/evidence-status.sh +12 -0
  12. package/hooks/git/commit-msg +4 -0
  13. package/hooks/git/pre-commit +4 -0
  14. package/hooks/git/pre-push +4 -0
  15. package/hooks/install-hooks.sh +583 -0
  16. package/hooks/message-auth-hook.js +9 -0
  17. package/hooks/message-governance-hook.js +9 -0
  18. package/hooks/models/claude-post.js +4 -0
  19. package/hooks/models/claude-pre.js +4 -0
  20. package/hooks/models/codex-post.js +4 -0
  21. package/hooks/models/codex-pre.js +4 -0
  22. package/hooks/models/cursor-post.js +4 -0
  23. package/hooks/models/cursor-pre.js +4 -0
  24. package/hooks/models/gemini-post.js +4 -0
  25. package/hooks/models/gemini-pre.js +4 -0
  26. package/hooks/models/openai-post.js +4 -0
  27. package/hooks/models/openai-pre.js +4 -0
  28. package/hooks/models/windsurf-post.js +4 -0
  29. package/hooks/models/windsurf-pre.js +4 -0
  30. package/hooks/models/xai-post.js +4 -0
  31. package/hooks/models/xai-pre.js +4 -0
  32. package/hooks/post-bash-hook.js +13 -0
  33. package/hooks/post-mcp-hook.js +13 -0
  34. package/hooks/post-response-hook.js +4 -0
  35. package/hooks/post-tool-hook.js +126 -0
  36. package/hooks/post-write-hook.js +13 -0
  37. package/hooks/pre-bash-hook.js +30 -0
  38. package/hooks/pre-mcp-hook.js +13 -0
  39. package/hooks/pre-read-hook.js +13 -0
  40. package/hooks/pre-search-hook.js +13 -0
  41. package/hooks/pre-submit-hook.js +4 -0
  42. package/hooks/pre-task-hook.js +13 -0
  43. package/hooks/pre-tool-hook.js +121 -0
  44. package/hooks/pre-web-hook.js +13 -0
  45. package/hooks/pre-write-hook.js +31 -0
  46. package/hooks/test-hooks.sh +12 -0
  47. package/hooks/update-delimit.sh +6 -0
  48. package/lib/agent.js +509 -0
  49. package/lib/api-engine.js +156 -0
  50. package/lib/auth-setup.js +891 -0
  51. package/lib/decision-engine.js +474 -0
  52. package/lib/hooks-installer.js +416 -0
  53. package/lib/platform-adapters.js +353 -0
  54. package/lib/proxy-handler.js +114 -0
  55. package/package.json +38 -30
  56. package/scripts/infect.js +128 -0
  57. package/test-decision-engine.js +181 -0
  58. package/test-hook.js +27 -0
  59. package/dist/commands/validate.d.ts +0 -2
  60. package/dist/commands/validate.d.ts.map +0 -1
  61. package/dist/commands/validate.js +0 -106
  62. package/dist/commands/validate.js.map +0 -1
  63. package/dist/index.d.ts +0 -3
  64. package/dist/index.d.ts.map +0 -1
  65. package/dist/index.js +0 -71
  66. package/dist/index.js.map +0 -1
  67. package/dist/types/index.d.ts +0 -39
  68. package/dist/types/index.d.ts.map +0 -1
  69. package/dist/types/index.js +0 -3
  70. package/dist/types/index.js.map +0 -1
  71. package/dist/utils/api.d.ts +0 -3
  72. package/dist/utils/api.d.ts.map +0 -1
  73. package/dist/utils/api.js +0 -64
  74. package/dist/utils/api.js.map +0 -1
  75. package/dist/utils/file.d.ts +0 -7
  76. package/dist/utils/file.d.ts.map +0 -1
  77. package/dist/utils/file.js +0 -69
  78. package/dist/utils/file.js.map +0 -1
  79. package/dist/utils/logger.d.ts +0 -14
  80. package/dist/utils/logger.d.ts.map +0 -1
  81. package/dist/utils/logger.js +0 -28
  82. package/dist/utils/logger.js.map +0 -1
  83. package/dist/utils/masker.d.ts +0 -14
  84. package/dist/utils/masker.d.ts.map +0 -1
  85. package/dist/utils/masker.js +0 -89
  86. package/dist/utils/masker.js.map +0 -1
  87. package/src/commands/validate.ts +0 -150
  88. package/src/index.ts +0 -80
  89. package/src/types/index.ts +0 -41
  90. package/src/utils/api.ts +0 -68
  91. package/src/utils/file.ts +0 -71
  92. package/src/utils/logger.ts +0 -27
  93. package/src/utils/masker.ts +0 -101
  94. package/test-sensitive.yaml +0 -109
  95. package/tsconfig.json +0 -23
@@ -0,0 +1,891 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Delimit Authentication Setup
5
+ * Handles secure credential collection and storage for new users
6
+ */
7
+
8
+ const readline = require('readline');
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const crypto = require('crypto');
12
+ const { execSync } = require('child_process');
13
+ const chalk = require('chalk');
14
+
15
+ class DelimitAuthSetup {
16
+ constructor() {
17
+ this.configDir = path.join(process.env.HOME, '.delimit');
18
+ this.credentialsFile = path.join(this.configDir, 'credentials.enc');
19
+ this.authConfigFile = path.join(this.configDir, 'auth.json');
20
+
21
+ // Ensure config directory exists
22
+ if (!fs.existsSync(this.configDir)) {
23
+ fs.mkdirSync(this.configDir, { recursive: true, mode: 0o700 });
24
+ }
25
+ }
26
+
27
+ async setup(options = {}) {
28
+ console.log(chalk.blue.bold('\nšŸ” Delimit Authentication Setup\n'));
29
+
30
+ const credentials = {};
31
+
32
+ // Check for existing credentials
33
+ if (fs.existsSync(this.credentialsFile) && !options.force) {
34
+ const overwrite = await this.prompt(
35
+ 'Existing credentials found. Overwrite? (y/N): ',
36
+ 'n'
37
+ );
38
+ if (overwrite.toLowerCase() !== 'y') {
39
+ console.log(chalk.yellow('Using existing credentials.'));
40
+ return this.loadCredentials();
41
+ }
42
+ }
43
+
44
+ // Collect credentials based on what's installed
45
+ console.log(chalk.cyan('\nšŸ“‹ Detecting installed tools...\n'));
46
+
47
+ // GitHub credentials
48
+ if (this.isInstalled('git')) {
49
+ console.log(chalk.yellow('GitHub Configuration:'));
50
+ credentials.github = await this.setupGitHub();
51
+ }
52
+
53
+ // AI Tool credentials
54
+ const aiTools = {
55
+ 'claude': 'Anthropic Claude',
56
+ 'openai': 'OpenAI GPT',
57
+ 'gemini': 'Google Gemini',
58
+ 'codex': 'GitHub Copilot'
59
+ };
60
+
61
+ for (const [tool, name] of Object.entries(aiTools)) {
62
+ if (this.isInstalled(tool) || options.all) {
63
+ const setup = await this.prompt(
64
+ `\nSetup ${name}? (y/N): `,
65
+ 'n'
66
+ );
67
+ if (setup.toLowerCase() === 'y') {
68
+ console.log(chalk.yellow(`\n${name} Configuration:`));
69
+ credentials[tool] = await this.setupAITool(tool, name);
70
+ }
71
+ }
72
+ }
73
+
74
+ // MCP Server credentials
75
+ if (this.isInstalled('claude') || options.all) {
76
+ const setupMcp = await this.prompt(
77
+ '\nSetup MCP server authentication? (y/N): ',
78
+ 'n'
79
+ );
80
+ if (setupMcp.toLowerCase() === 'y') {
81
+ console.log(chalk.yellow('\nMCP Server Configuration:'));
82
+ credentials.mcp = await this.setupMCP();
83
+ }
84
+ }
85
+
86
+ // Cloud Provider credentials
87
+ const setupCloud = await this.prompt(
88
+ '\nSetup cloud provider credentials? (y/N): ',
89
+ 'n'
90
+ );
91
+ if (setupCloud.toLowerCase() === 'y') {
92
+ console.log(chalk.yellow('\nCloud Provider Configuration:'));
93
+ credentials.cloud = await this.setupCloudProviders();
94
+ }
95
+
96
+ // Database credentials
97
+ const setupDb = await this.prompt(
98
+ '\nSetup database connections? (y/N): ',
99
+ 'n'
100
+ );
101
+ if (setupDb.toLowerCase() === 'y') {
102
+ console.log(chalk.yellow('\nDatabase Configuration:'));
103
+ credentials.databases = await this.setupDatabases();
104
+ }
105
+
106
+ // Container registries
107
+ const setupRegistry = await this.prompt(
108
+ '\nSetup container registries? (y/N): ',
109
+ 'n'
110
+ );
111
+ if (setupRegistry.toLowerCase() === 'y') {
112
+ console.log(chalk.yellow('\nContainer Registry Configuration:'));
113
+ credentials.registries = await this.setupRegistries();
114
+ }
115
+
116
+ // Package managers
117
+ const setupPackages = await this.prompt(
118
+ '\nSetup package manager credentials? (y/N): ',
119
+ 'n'
120
+ );
121
+ if (setupPackages.toLowerCase() === 'y') {
122
+ console.log(chalk.yellow('\nPackage Manager Configuration:'));
123
+ credentials.packages = await this.setupPackageManagers();
124
+ }
125
+
126
+ // Monitoring and observability
127
+ const setupMonitoring = await this.prompt(
128
+ '\nSetup monitoring services? (y/N): ',
129
+ 'n'
130
+ );
131
+ if (setupMonitoring.toLowerCase() === 'y') {
132
+ console.log(chalk.yellow('\nMonitoring Configuration:'));
133
+ credentials.monitoring = await this.setupMonitoring();
134
+ }
135
+
136
+ // Organization settings
137
+ const setupOrg = await this.prompt(
138
+ '\nSetup organization policies? (y/N): ',
139
+ 'n'
140
+ );
141
+ if (setupOrg.toLowerCase() === 'y') {
142
+ console.log(chalk.yellow('\nOrganization Configuration:'));
143
+ credentials.organization = await this.setupOrganization();
144
+ }
145
+
146
+ // Save credentials securely
147
+ await this.saveCredentials(credentials);
148
+
149
+ // Configure Git globally if GitHub was setup
150
+ if (credentials.github) {
151
+ await this.configureGit(credentials.github);
152
+ }
153
+
154
+ // Create environment file
155
+ await this.createEnvironmentFile(credentials);
156
+
157
+ console.log(chalk.green.bold('\nāœ… Authentication setup complete!\n'));
158
+ this.printSummary(credentials);
159
+
160
+ return credentials;
161
+ }
162
+
163
+ async setupGitHub() {
164
+ const github = {};
165
+
166
+ // Check for existing Git config
167
+ try {
168
+ github.username = execSync('git config --global user.name', { encoding: 'utf8' }).trim();
169
+ github.email = execSync('git config --global user.email', { encoding: 'utf8' }).trim();
170
+ console.log(chalk.gray(` Found: ${github.username} <${github.email}>`));
171
+ } catch (e) {
172
+ github.username = await this.prompt(' GitHub username: ');
173
+ github.email = await this.prompt(' GitHub email: ');
174
+ }
175
+
176
+ // GitHub Personal Access Token
177
+ const needToken = await this.prompt(' Add GitHub Personal Access Token? (y/N): ', 'n');
178
+ if (needToken.toLowerCase() === 'y') {
179
+ github.token = await this.promptSecret(' GitHub PAT: ');
180
+
181
+ // Token scopes
182
+ console.log(chalk.gray('\n Required scopes for full functionality:'));
183
+ console.log(chalk.gray(' • repo (Full control of private repositories)'));
184
+ console.log(chalk.gray(' • workflow (Update GitHub Action workflows)'));
185
+ console.log(chalk.gray(' • write:packages (Upload packages to GitHub Package Registry)'));
186
+ console.log(chalk.gray(' • read:org (Read org and team membership)'));
187
+ }
188
+
189
+ // SSH Key setup
190
+ const needSsh = await this.prompt(' Setup SSH key for GitHub? (y/N): ', 'n');
191
+ if (needSsh.toLowerCase() === 'y') {
192
+ github.sshKey = await this.setupSSHKey(github.email);
193
+ }
194
+
195
+ // GitHub CLI token (for gh command)
196
+ if (this.isInstalled('gh')) {
197
+ const needGhToken = await this.prompt(' Setup GitHub CLI (gh) authentication? (y/N): ', 'n');
198
+ if (needGhToken.toLowerCase() === 'y') {
199
+ github.ghToken = await this.promptSecret(' GitHub CLI token: ');
200
+ }
201
+ }
202
+
203
+ return github;
204
+ }
205
+
206
+ async setupAITool(tool, name) {
207
+ const config = {};
208
+
209
+ switch (tool) {
210
+ case 'claude':
211
+ config.apiKey = await this.promptSecret(` Anthropic API key: `);
212
+ config.model = await this.prompt(` Default model (claude-3-opus-20240229): `, 'claude-3-opus-20240229');
213
+ break;
214
+
215
+ case 'openai':
216
+ config.apiKey = await this.promptSecret(` OpenAI API key: `);
217
+ config.organization = await this.prompt(` Organization ID (optional): `, '');
218
+ config.model = await this.prompt(` Default model (gpt-4): `, 'gpt-4');
219
+ break;
220
+
221
+ case 'gemini':
222
+ config.apiKey = await this.promptSecret(` Google AI API key: `);
223
+ config.projectId = await this.prompt(` GCP Project ID (optional): `, '');
224
+ break;
225
+
226
+ case 'codex':
227
+ config.token = await this.promptSecret(` GitHub Copilot token: `);
228
+ break;
229
+ }
230
+
231
+ // Rate limits and safety
232
+ config.maxTokens = await this.prompt(` Max tokens per request (4000): `, '4000');
233
+ config.rateLimit = await this.prompt(` Requests per minute (10): `, '10');
234
+
235
+ return config;
236
+ }
237
+
238
+ async setupMCP() {
239
+ const mcp = {};
240
+
241
+ console.log(chalk.gray('\n MCP servers can require authentication for:'));
242
+ console.log(chalk.gray(' • Database connections'));
243
+ console.log(chalk.gray(' • API endpoints'));
244
+ console.log(chalk.gray(' • Cloud services'));
245
+
246
+ const servers = [
247
+ 'delimit-gov',
248
+ 'delimit-mem',
249
+ 'delimit-os',
250
+ 'delimit-vault',
251
+ 'delimit-deploy'
252
+ ];
253
+
254
+ for (const server of servers) {
255
+ const needAuth = await this.prompt(`\n Setup ${server} authentication? (y/N): `, 'n');
256
+ if (needAuth.toLowerCase() === 'y') {
257
+ mcp[server] = {};
258
+
259
+ // Check what type of auth is needed
260
+ const authType = await this.prompt(' Auth type (token/credentials/oauth): ', 'token');
261
+
262
+ switch (authType) {
263
+ case 'token':
264
+ mcp[server].token = await this.promptSecret(' API token: ');
265
+ break;
266
+ case 'credentials':
267
+ mcp[server].username = await this.prompt(' Username: ');
268
+ mcp[server].password = await this.promptSecret(' Password: ');
269
+ break;
270
+ case 'oauth':
271
+ mcp[server].clientId = await this.prompt(' Client ID: ');
272
+ mcp[server].clientSecret = await this.promptSecret(' Client secret: ');
273
+ break;
274
+ }
275
+
276
+ mcp[server].endpoint = await this.prompt(' Endpoint URL (http://localhost:8080): ', 'http://localhost:8080');
277
+ }
278
+ }
279
+
280
+ return mcp;
281
+ }
282
+
283
+ async setupCloudProviders() {
284
+ const cloud = {};
285
+
286
+ // AWS
287
+ const setupAws = await this.prompt(' Setup AWS credentials? (y/N): ', 'n');
288
+ if (setupAws.toLowerCase() === 'y') {
289
+ cloud.aws = {};
290
+ cloud.aws.accessKeyId = await this.prompt(' AWS Access Key ID: ');
291
+ cloud.aws.secretAccessKey = await this.promptSecret(' AWS Secret Access Key: ');
292
+ cloud.aws.region = await this.prompt(' Default region (us-east-1): ', 'us-east-1');
293
+
294
+ const needMfa = await this.prompt(' MFA device ARN (optional): ', '');
295
+ if (needMfa) cloud.aws.mfaDevice = needMfa;
296
+ }
297
+
298
+ // Google Cloud
299
+ const setupGcp = await this.prompt(' Setup Google Cloud credentials? (y/N): ', 'n');
300
+ if (setupGcp.toLowerCase() === 'y') {
301
+ cloud.gcp = {};
302
+ cloud.gcp.projectId = await this.prompt(' GCP Project ID: ');
303
+
304
+ const keyFile = await this.prompt(' Service account key file path: ');
305
+ if (keyFile && fs.existsSync(keyFile)) {
306
+ cloud.gcp.keyFile = keyFile;
307
+ } else {
308
+ cloud.gcp.clientEmail = await this.prompt(' Service account email: ');
309
+ cloud.gcp.privateKey = await this.promptSecret(' Private key (paste entire key): ');
310
+ }
311
+ }
312
+
313
+ // Azure
314
+ const setupAzure = await this.prompt(' Setup Azure credentials? (y/N): ', 'n');
315
+ if (setupAzure.toLowerCase() === 'y') {
316
+ cloud.azure = {};
317
+ cloud.azure.tenantId = await this.prompt(' Azure Tenant ID: ');
318
+ cloud.azure.clientId = await this.prompt(' Client ID: ');
319
+ cloud.azure.clientSecret = await this.promptSecret(' Client Secret: ');
320
+ cloud.azure.subscriptionId = await this.prompt(' Subscription ID: ');
321
+ }
322
+
323
+ // DigitalOcean
324
+ const setupDo = await this.prompt(' Setup DigitalOcean credentials? (y/N): ', 'n');
325
+ if (setupDo.toLowerCase() === 'y') {
326
+ cloud.digitalocean = {};
327
+ cloud.digitalocean.token = await this.promptSecret(' DigitalOcean API Token: ');
328
+ }
329
+
330
+ // Cloudflare
331
+ const setupCf = await this.prompt(' Setup Cloudflare credentials? (y/N): ', 'n');
332
+ if (setupCf.toLowerCase() === 'y') {
333
+ cloud.cloudflare = {};
334
+ cloud.cloudflare.email = await this.prompt(' Cloudflare email: ');
335
+ cloud.cloudflare.apiKey = await this.promptSecret(' Global API Key: ');
336
+ cloud.cloudflare.zoneId = await this.prompt(' Zone ID (optional): ', '');
337
+ }
338
+
339
+ return cloud;
340
+ }
341
+
342
+ async setupDatabases() {
343
+ const databases = {};
344
+
345
+ // PostgreSQL
346
+ const setupPg = await this.prompt(' Setup PostgreSQL? (y/N): ', 'n');
347
+ if (setupPg.toLowerCase() === 'y') {
348
+ databases.postgresql = {};
349
+ databases.postgresql.host = await this.prompt(' Host (localhost): ', 'localhost');
350
+ databases.postgresql.port = await this.prompt(' Port (5432): ', '5432');
351
+ databases.postgresql.database = await this.prompt(' Database name: ');
352
+ databases.postgresql.username = await this.prompt(' Username: ');
353
+ databases.postgresql.password = await this.promptSecret(' Password: ');
354
+ databases.postgresql.ssl = await this.prompt(' Use SSL? (Y/n): ', 'y');
355
+ }
356
+
357
+ // MySQL
358
+ const setupMysql = await this.prompt(' Setup MySQL? (y/N): ', 'n');
359
+ if (setupMysql.toLowerCase() === 'y') {
360
+ databases.mysql = {};
361
+ databases.mysql.host = await this.prompt(' Host (localhost): ', 'localhost');
362
+ databases.mysql.port = await this.prompt(' Port (3306): ', '3306');
363
+ databases.mysql.database = await this.prompt(' Database name: ');
364
+ databases.mysql.username = await this.prompt(' Username: ');
365
+ databases.mysql.password = await this.promptSecret(' Password: ');
366
+ }
367
+
368
+ // MongoDB
369
+ const setupMongo = await this.prompt(' Setup MongoDB? (y/N): ', 'n');
370
+ if (setupMongo.toLowerCase() === 'y') {
371
+ databases.mongodb = {};
372
+ const useUri = await this.prompt(' Use connection URI? (Y/n): ', 'y');
373
+ if (useUri.toLowerCase() === 'y') {
374
+ databases.mongodb.uri = await this.promptSecret(' MongoDB URI: ');
375
+ } else {
376
+ databases.mongodb.host = await this.prompt(' Host (localhost): ', 'localhost');
377
+ databases.mongodb.port = await this.prompt(' Port (27017): ', '27017');
378
+ databases.mongodb.database = await this.prompt(' Database name: ');
379
+ databases.mongodb.username = await this.prompt(' Username: ');
380
+ databases.mongodb.password = await this.promptSecret(' Password: ');
381
+ }
382
+ }
383
+
384
+ // Redis
385
+ const setupRedis = await this.prompt(' Setup Redis? (y/N): ', 'n');
386
+ if (setupRedis.toLowerCase() === 'y') {
387
+ databases.redis = {};
388
+ databases.redis.host = await this.prompt(' Host (localhost): ', 'localhost');
389
+ databases.redis.port = await this.prompt(' Port (6379): ', '6379');
390
+ databases.redis.password = await this.promptSecret(' Password (optional): ');
391
+ databases.redis.db = await this.prompt(' Database number (0): ', '0');
392
+ }
393
+
394
+ return databases;
395
+ }
396
+
397
+ async setupRegistries() {
398
+ const registries = {};
399
+
400
+ // Docker Hub
401
+ const setupDocker = await this.prompt(' Setup Docker Hub? (y/N): ', 'n');
402
+ if (setupDocker.toLowerCase() === 'y') {
403
+ registries.dockerhub = {};
404
+ registries.dockerhub.username = await this.prompt(' Docker Hub username: ');
405
+ registries.dockerhub.password = await this.promptSecret(' Docker Hub password: ');
406
+ registries.dockerhub.email = await this.prompt(' Email: ');
407
+ }
408
+
409
+ // GitHub Container Registry
410
+ const setupGhcr = await this.prompt(' Setup GitHub Container Registry? (y/N): ', 'n');
411
+ if (setupGhcr.toLowerCase() === 'y') {
412
+ registries.ghcr = {};
413
+ registries.ghcr.username = await this.prompt(' GitHub username: ');
414
+ registries.ghcr.token = await this.promptSecret(' Personal Access Token: ');
415
+ }
416
+
417
+ // AWS ECR
418
+ const setupEcr = await this.prompt(' Setup AWS ECR? (y/N): ', 'n');
419
+ if (setupEcr.toLowerCase() === 'y') {
420
+ registries.ecr = {};
421
+ registries.ecr.region = await this.prompt(' AWS Region: ');
422
+ registries.ecr.registryId = await this.prompt(' Registry ID: ');
423
+ }
424
+
425
+ // Google Artifact Registry
426
+ const setupGar = await this.prompt(' Setup Google Artifact Registry? (y/N): ', 'n');
427
+ if (setupGar.toLowerCase() === 'y') {
428
+ registries.gar = {};
429
+ registries.gar.location = await this.prompt(' Location: ');
430
+ registries.gar.repository = await this.prompt(' Repository name: ');
431
+ }
432
+
433
+ // Private registry
434
+ const setupPrivate = await this.prompt(' Setup private registry? (y/N): ', 'n');
435
+ if (setupPrivate.toLowerCase() === 'y') {
436
+ registries.private = {};
437
+ registries.private.url = await this.prompt(' Registry URL: ');
438
+ registries.private.username = await this.prompt(' Username: ');
439
+ registries.private.password = await this.promptSecret(' Password: ');
440
+ }
441
+
442
+ return registries;
443
+ }
444
+
445
+ async setupPackageManagers() {
446
+ const packages = {};
447
+
448
+ // NPM
449
+ const setupNpm = await this.prompt(' Setup NPM registry? (y/N): ', 'n');
450
+ if (setupNpm.toLowerCase() === 'y') {
451
+ packages.npm = {};
452
+ packages.npm.registry = await this.prompt(' Registry URL (https://registry.npmjs.org/): ', 'https://registry.npmjs.org/');
453
+ packages.npm.token = await this.promptSecret(' Auth token: ');
454
+
455
+ const needScope = await this.prompt(' Scoped packages (@org)? (y/N): ', 'n');
456
+ if (needScope.toLowerCase() === 'y') {
457
+ packages.npm.scope = await this.prompt(' Scope name: ');
458
+ }
459
+ }
460
+
461
+ // PyPI
462
+ const setupPypi = await this.prompt(' Setup PyPI? (y/N): ', 'n');
463
+ if (setupPypi.toLowerCase() === 'y') {
464
+ packages.pypi = {};
465
+ packages.pypi.username = await this.prompt(' PyPI username: ');
466
+ packages.pypi.password = await this.promptSecret(' PyPI password: ');
467
+ packages.pypi.repository = await this.prompt(' Repository (pypi): ', 'pypi');
468
+ }
469
+
470
+ // Maven
471
+ const setupMaven = await this.prompt(' Setup Maven? (y/N): ', 'n');
472
+ if (setupMaven.toLowerCase() === 'y') {
473
+ packages.maven = {};
474
+ packages.maven.repository = await this.prompt(' Repository URL: ');
475
+ packages.maven.username = await this.prompt(' Username: ');
476
+ packages.maven.password = await this.promptSecret(' Password: ');
477
+ }
478
+
479
+ // RubyGems
480
+ const setupGem = await this.prompt(' Setup RubyGems? (y/N): ', 'n');
481
+ if (setupGem.toLowerCase() === 'y') {
482
+ packages.rubygems = {};
483
+ packages.rubygems.apiKey = await this.promptSecret(' API Key: ');
484
+ }
485
+
486
+ // Cargo (Rust)
487
+ const setupCargo = await this.prompt(' Setup Cargo? (y/N): ', 'n');
488
+ if (setupCargo.toLowerCase() === 'y') {
489
+ packages.cargo = {};
490
+ packages.cargo.token = await this.promptSecret(' Crates.io token: ');
491
+ }
492
+
493
+ return packages;
494
+ }
495
+
496
+ async setupMonitoring() {
497
+ const monitoring = {};
498
+
499
+ // Datadog
500
+ const setupDatadog = await this.prompt(' Setup Datadog? (y/N): ', 'n');
501
+ if (setupDatadog.toLowerCase() === 'y') {
502
+ monitoring.datadog = {};
503
+ monitoring.datadog.apiKey = await this.promptSecret(' API Key: ');
504
+ monitoring.datadog.appKey = await this.promptSecret(' Application Key: ');
505
+ monitoring.datadog.site = await this.prompt(' Site (datadoghq.com): ', 'datadoghq.com');
506
+ }
507
+
508
+ // New Relic
509
+ const setupNewrelic = await this.prompt(' Setup New Relic? (y/N): ', 'n');
510
+ if (setupNewrelic.toLowerCase() === 'y') {
511
+ monitoring.newrelic = {};
512
+ monitoring.newrelic.accountId = await this.prompt(' Account ID: ');
513
+ monitoring.newrelic.apiKey = await this.promptSecret(' API Key: ');
514
+ monitoring.newrelic.licenseKey = await this.promptSecret(' License Key: ');
515
+ }
516
+
517
+ // Sentry
518
+ const setupSentry = await this.prompt(' Setup Sentry? (y/N): ', 'n');
519
+ if (setupSentry.toLowerCase() === 'y') {
520
+ monitoring.sentry = {};
521
+ monitoring.sentry.dsn = await this.promptSecret(' DSN: ');
522
+ monitoring.sentry.org = await this.prompt(' Organization slug: ');
523
+ monitoring.sentry.project = await this.prompt(' Project slug: ');
524
+ monitoring.sentry.authToken = await this.promptSecret(' Auth token: ');
525
+ }
526
+
527
+ // PagerDuty
528
+ const setupPager = await this.prompt(' Setup PagerDuty? (y/N): ', 'n');
529
+ if (setupPager.toLowerCase() === 'y') {
530
+ monitoring.pagerduty = {};
531
+ monitoring.pagerduty.apiKey = await this.promptSecret(' API Key: ');
532
+ monitoring.pagerduty.integrationKey = await this.promptSecret(' Integration Key: ');
533
+ }
534
+
535
+ // Prometheus/Grafana
536
+ const setupProm = await this.prompt(' Setup Prometheus/Grafana? (y/N): ', 'n');
537
+ if (setupProm.toLowerCase() === 'y') {
538
+ monitoring.prometheus = {};
539
+ monitoring.prometheus.url = await this.prompt(' Prometheus URL: ');
540
+ monitoring.grafana = {};
541
+ monitoring.grafana.url = await this.prompt(' Grafana URL: ');
542
+ monitoring.grafana.apiKey = await this.promptSecret(' Grafana API Key: ');
543
+ }
544
+
545
+ return monitoring;
546
+ }
547
+
548
+ async setupOrganization() {
549
+ const org = {};
550
+
551
+ org.name = await this.prompt(' Organization name: ');
552
+ org.domain = await this.prompt(' Organization domain: ');
553
+
554
+ // SSO/SAML
555
+ const hasSso = await this.prompt(' Use SSO/SAML? (y/N): ', 'n');
556
+ if (hasSso.toLowerCase() === 'y') {
557
+ org.sso = {};
558
+ org.sso.provider = await this.prompt(' Provider (okta/auth0/azure): ');
559
+ org.sso.domain = await this.prompt(' SSO domain: ');
560
+ org.sso.clientId = await this.prompt(' Client ID: ');
561
+ org.sso.clientSecret = await this.promptSecret(' Client secret: ');
562
+ }
563
+
564
+ // Policy server
565
+ const hasPolicyServer = await this.prompt(' Use organization policy server? (y/N): ', 'n');
566
+ if (hasPolicyServer.toLowerCase() === 'y') {
567
+ org.policyUrl = await this.prompt(' Policy server URL: ');
568
+ org.policyToken = await this.promptSecret(' Policy server token: ');
569
+ }
570
+
571
+ // VPN/Proxy
572
+ const hasVpn = await this.prompt(' Configure VPN/Proxy? (y/N): ', 'n');
573
+ if (hasVpn.toLowerCase() === 'y') {
574
+ org.proxy = {};
575
+ org.proxy.http = await this.prompt(' HTTP proxy: ');
576
+ org.proxy.https = await this.prompt(' HTTPS proxy: ');
577
+ org.proxy.noProxy = await this.prompt(' No proxy (localhost,127.0.0.1): ', 'localhost,127.0.0.1');
578
+ }
579
+
580
+ // Audit requirements
581
+ org.auditLevel = await this.prompt(' Audit level (basic/detailed/verbose): ', 'detailed');
582
+ org.requireApproval = await this.prompt(' Require approval for enforce mode? (Y/n): ', 'y');
583
+
584
+ return org;
585
+ }
586
+
587
+ async setupSSHKey(email) {
588
+ const sshDir = path.join(process.env.HOME, '.ssh');
589
+ const keyPath = path.join(sshDir, 'id_ed25519_delimit');
590
+
591
+ // Check for existing key
592
+ if (fs.existsSync(keyPath)) {
593
+ console.log(chalk.gray(` Found existing SSH key: ${keyPath}`));
594
+ return keyPath;
595
+ }
596
+
597
+ // Generate new key
598
+ console.log(chalk.cyan(' Generating new SSH key...'));
599
+
600
+ if (!fs.existsSync(sshDir)) {
601
+ fs.mkdirSync(sshDir, { mode: 0o700 });
602
+ }
603
+
604
+ try {
605
+ execSync(`ssh-keygen -t ed25519 -C "${email}" -f ${keyPath} -N ""`, { stdio: 'pipe' });
606
+
607
+ // Set proper permissions
608
+ fs.chmodSync(keyPath, 0o600);
609
+ fs.chmodSync(`${keyPath}.pub`, 0o644);
610
+
611
+ // Display public key
612
+ const publicKey = fs.readFileSync(`${keyPath}.pub`, 'utf8');
613
+ console.log(chalk.green('\n SSH key generated successfully!'));
614
+ console.log(chalk.cyan('\n Add this public key to GitHub:'));
615
+ console.log(chalk.white(` ${publicKey}`));
616
+ console.log(chalk.gray('\n https://github.com/settings/keys'));
617
+
618
+ // Add to SSH agent
619
+ try {
620
+ execSync(`ssh-add ${keyPath}`, { stdio: 'pipe' });
621
+ console.log(chalk.green(' Added to SSH agent'));
622
+ } catch (e) {
623
+ console.log(chalk.yellow(' Could not add to SSH agent (start ssh-agent first)'));
624
+ }
625
+
626
+ return keyPath;
627
+ } catch (e) {
628
+ console.log(chalk.red(' Failed to generate SSH key:', e.message));
629
+ return null;
630
+ }
631
+ }
632
+
633
+ async configureGit(github) {
634
+ console.log(chalk.cyan('\nšŸ”§ Configuring Git...'));
635
+
636
+ try {
637
+ // Set user info
638
+ if (github.username) {
639
+ execSync(`git config --global user.name "${github.username}"`, { stdio: 'pipe' });
640
+ }
641
+ if (github.email) {
642
+ execSync(`git config --global user.email "${github.email}"`, { stdio: 'pipe' });
643
+ }
644
+
645
+ // Set up credential helper
646
+ if (github.token) {
647
+ // Create credentials file for HTTPS
648
+ const credFile = path.join(this.configDir, 'git-credentials');
649
+ const credContent = `https://${github.username}:${github.token}@github.com\n`;
650
+ fs.writeFileSync(credFile, credContent, { mode: 0o600 });
651
+
652
+ execSync(`git config --global credential.helper "store --file=${credFile}"`, { stdio: 'pipe' });
653
+ console.log(chalk.green(' āœ“ Git credentials configured'));
654
+ }
655
+
656
+ // Configure Delimit hooks
657
+ const hooksPath = path.join(this.configDir, 'hooks');
658
+ execSync(`git config --global core.hooksPath ${hooksPath}`, { stdio: 'pipe' });
659
+ console.log(chalk.green(' āœ“ Delimit hooks configured'));
660
+
661
+ } catch (e) {
662
+ console.log(chalk.yellow(' Warning: Could not configure Git:', e.message));
663
+ }
664
+ }
665
+
666
+ async createEnvironmentFile(credentials) {
667
+ const envFile = path.join(this.configDir, 'env.sh');
668
+ let envContent = '#!/bin/bash\n# Delimit Environment Configuration\n\n';
669
+
670
+ // GitHub
671
+ if (credentials.github?.token) {
672
+ envContent += `export GITHUB_TOKEN="${credentials.github.token}"\n`;
673
+ }
674
+ if (credentials.github?.ghToken) {
675
+ envContent += `export GH_TOKEN="${credentials.github.ghToken}"\n`;
676
+ }
677
+
678
+ // AI Tools
679
+ if (credentials.claude?.apiKey) {
680
+ envContent += `export ANTHROPIC_API_KEY="${credentials.claude.apiKey}"\n`;
681
+ }
682
+ if (credentials.openai?.apiKey) {
683
+ envContent += `export OPENAI_API_KEY="${credentials.openai.apiKey}"\n`;
684
+ }
685
+ if (credentials.gemini?.apiKey) {
686
+ envContent += `export GOOGLE_AI_API_KEY="${credentials.gemini.apiKey}"\n`;
687
+ }
688
+
689
+ // Organization
690
+ if (credentials.organization?.policyUrl) {
691
+ envContent += `export DELIMIT_ORG_POLICY_URL="${credentials.organization.policyUrl}"\n`;
692
+ envContent += `export DELIMIT_ORG_POLICY_TOKEN="${credentials.organization.policyToken}"\n`;
693
+ }
694
+
695
+ // Delimit settings
696
+ envContent += `\n# Delimit Settings\n`;
697
+ envContent += `export DELIMIT_CONFIGURED=true\n`;
698
+ envContent += `export DELIMIT_AUTH_FILE="${this.credentialsFile}"\n`;
699
+
700
+ fs.writeFileSync(envFile, envContent, { mode: 0o600 });
701
+
702
+ // Add to bashrc if not present
703
+ const bashrcPath = path.join(process.env.HOME, '.bashrc');
704
+ const sourceLine = `source ${envFile}`;
705
+
706
+ if (fs.existsSync(bashrcPath)) {
707
+ const bashrc = fs.readFileSync(bashrcPath, 'utf8');
708
+ if (!bashrc.includes(sourceLine)) {
709
+ fs.appendFileSync(bashrcPath, `\n# Delimit Authentication\n${sourceLine}\n`);
710
+ }
711
+ }
712
+ }
713
+
714
+ async saveCredentials(credentials) {
715
+ // Generate encryption key from machine ID
716
+ const machineId = this.getMachineId();
717
+ const key = crypto.createHash('sha256').update(machineId).digest();
718
+
719
+ // Encrypt credentials
720
+ const iv = crypto.randomBytes(16);
721
+ const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
722
+
723
+ const encrypted = Buffer.concat([
724
+ iv,
725
+ cipher.update(JSON.stringify(credentials, null, 2)),
726
+ cipher.final()
727
+ ]);
728
+
729
+ // Save encrypted file
730
+ fs.writeFileSync(this.credentialsFile, encrypted, { mode: 0o600 });
731
+
732
+ // Save auth config (non-sensitive)
733
+ const authConfig = {
734
+ configured: true,
735
+ timestamp: new Date().toISOString(),
736
+ tools: Object.keys(credentials)
737
+ };
738
+ fs.writeFileSync(this.authConfigFile, JSON.stringify(authConfig, null, 2));
739
+ }
740
+
741
+ async loadCredentials() {
742
+ if (!fs.existsSync(this.credentialsFile)) {
743
+ return null;
744
+ }
745
+
746
+ try {
747
+ // Generate decryption key
748
+ const machineId = this.getMachineId();
749
+ const key = crypto.createHash('sha256').update(machineId).digest();
750
+
751
+ // Read encrypted file
752
+ const encrypted = fs.readFileSync(this.credentialsFile);
753
+ const iv = encrypted.slice(0, 16);
754
+ const data = encrypted.slice(16);
755
+
756
+ // Decrypt
757
+ const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
758
+ const decrypted = Buffer.concat([
759
+ decipher.update(data),
760
+ decipher.final()
761
+ ]);
762
+
763
+ return JSON.parse(decrypted.toString());
764
+ } catch (e) {
765
+ console.log(chalk.red('Failed to load credentials:', e.message));
766
+ return null;
767
+ }
768
+ }
769
+
770
+ getMachineId() {
771
+ // Get a unique machine identifier
772
+ try {
773
+ const hostname = require('os').hostname();
774
+ const cpuInfo = require('os').cpus()[0].model;
775
+ return `${hostname}-${cpuInfo}`;
776
+ } catch (e) {
777
+ return 'default-machine-id';
778
+ }
779
+ }
780
+
781
+ isInstalled(tool) {
782
+ try {
783
+ execSync(`which ${tool}`, { stdio: 'ignore' });
784
+ return true;
785
+ } catch {
786
+ return false;
787
+ }
788
+ }
789
+
790
+ prompt(question, defaultValue = '') {
791
+ const rl = readline.createInterface({
792
+ input: process.stdin,
793
+ output: process.stdout
794
+ });
795
+
796
+ return new Promise((resolve) => {
797
+ rl.question(question, (answer) => {
798
+ rl.close();
799
+ resolve(answer || defaultValue);
800
+ });
801
+ });
802
+ }
803
+
804
+ promptSecret(question) {
805
+ const rl = readline.createInterface({
806
+ input: process.stdin,
807
+ output: process.stdout
808
+ });
809
+
810
+ return new Promise((resolve) => {
811
+ // Hide input
812
+ process.stdout.write(question);
813
+
814
+ let secret = '';
815
+ process.stdin.setRawMode(true);
816
+ process.stdin.resume();
817
+ process.stdin.setEncoding('utf8');
818
+
819
+ const onData = (char) => {
820
+ char = char.toString('utf8');
821
+
822
+ switch (char) {
823
+ case '\n':
824
+ case '\r':
825
+ case '\u0004':
826
+ process.stdin.setRawMode(false);
827
+ process.stdin.pause();
828
+ process.stdin.removeListener('data', onData);
829
+ process.stdout.write('\n');
830
+ rl.close();
831
+ resolve(secret);
832
+ break;
833
+ case '\u0003':
834
+ process.exit();
835
+ break;
836
+ case '\u007f':
837
+ if (secret.length > 0) {
838
+ secret = secret.slice(0, -1);
839
+ process.stdout.write('\b \b');
840
+ }
841
+ break;
842
+ default:
843
+ secret += char;
844
+ process.stdout.write('*');
845
+ break;
846
+ }
847
+ };
848
+
849
+ process.stdin.on('data', onData);
850
+ });
851
+ }
852
+
853
+ printSummary(credentials) {
854
+ console.log(chalk.cyan('šŸ“‹ Configuration Summary:'));
855
+
856
+ if (credentials.github) {
857
+ console.log(chalk.white(` • GitHub: ${credentials.github.username} <${credentials.github.email}>`));
858
+ if (credentials.github.token) console.log(chalk.gray(' - Personal Access Token configured'));
859
+ if (credentials.github.sshKey) console.log(chalk.gray(' - SSH key configured'));
860
+ if (credentials.github.ghToken) console.log(chalk.gray(' - GitHub CLI configured'));
861
+ }
862
+
863
+ for (const tool of ['claude', 'openai', 'gemini', 'codex']) {
864
+ if (credentials[tool]) {
865
+ console.log(chalk.white(` • ${tool}: Configured`));
866
+ }
867
+ }
868
+
869
+ if (credentials.mcp && Object.keys(credentials.mcp).length > 0) {
870
+ console.log(chalk.white(` • MCP Servers: ${Object.keys(credentials.mcp).length} configured`));
871
+ }
872
+
873
+ if (credentials.organization) {
874
+ console.log(chalk.white(` • Organization: ${credentials.organization.name}`));
875
+ }
876
+
877
+ console.log(chalk.cyan('\nšŸŽÆ Next Steps:'));
878
+ console.log(chalk.white(' 1. Restart your shell to load environment'));
879
+ console.log(chalk.white(' 2. Run "delimit test auth" to verify credentials'));
880
+ console.log(chalk.white(' 3. Run "delimit install --hooks all" to complete setup'));
881
+ }
882
+ }
883
+
884
+ // Export for use as module
885
+ module.exports = DelimitAuthSetup;
886
+
887
+ // Run if executed directly
888
+ if (require.main === module) {
889
+ const setup = new DelimitAuthSetup();
890
+ setup.setup({ all: process.argv.includes('--all') }).catch(console.error);
891
+ }