ai-cred 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/cli/commands/add.d.ts +8 -0
  2. package/dist/cli/commands/add.js +145 -0
  3. package/dist/cli/commands/add.js.map +1 -0
  4. package/dist/cli/commands/get.d.ts +7 -0
  5. package/dist/cli/commands/get.js +53 -0
  6. package/dist/cli/commands/get.js.map +1 -0
  7. package/dist/cli/commands/list.d.ts +7 -0
  8. package/dist/cli/commands/list.js +118 -0
  9. package/dist/cli/commands/list.js.map +1 -0
  10. package/dist/cli/commands/remove.d.ts +7 -0
  11. package/dist/cli/commands/remove.js +53 -0
  12. package/dist/cli/commands/remove.js.map +1 -0
  13. package/dist/cli/commands/update.d.ts +8 -0
  14. package/dist/cli/commands/update.js +140 -0
  15. package/dist/cli/commands/update.js.map +1 -0
  16. package/dist/cli/index.d.ts +7 -0
  17. package/dist/cli/index.js +25 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/cli/utils/display.d.ts +14 -0
  20. package/dist/cli/utils/display.js +31 -0
  21. package/dist/cli/utils/display.js.map +1 -0
  22. package/dist/cli/utils/prompt.d.ts +6 -0
  23. package/dist/cli/utils/prompt.js +66 -0
  24. package/dist/cli/utils/prompt.js.map +1 -0
  25. package/dist/core/audit-logger.d.ts +25 -0
  26. package/dist/core/audit-logger.js +41 -0
  27. package/dist/core/audit-logger.js.map +1 -0
  28. package/dist/core/audit-logger.test.d.ts +1 -0
  29. package/dist/core/audit-logger.test.js +89 -0
  30. package/dist/core/audit-logger.test.js.map +1 -0
  31. package/dist/core/errors.d.ts +23 -0
  32. package/dist/core/errors.js +78 -0
  33. package/dist/core/errors.js.map +1 -0
  34. package/dist/core/keychain-adapter.d.ts +65 -0
  35. package/dist/core/keychain-adapter.js +221 -0
  36. package/dist/core/keychain-adapter.js.map +1 -0
  37. package/dist/core/keychain-adapter.test.d.ts +1 -0
  38. package/dist/core/keychain-adapter.test.js +331 -0
  39. package/dist/core/keychain-adapter.test.js.map +1 -0
  40. package/dist/e2e/environment-isolation.test.d.ts +1 -0
  41. package/dist/e2e/environment-isolation.test.js +112 -0
  42. package/dist/e2e/environment-isolation.test.js.map +1 -0
  43. package/dist/server/index.d.ts +7 -0
  44. package/dist/server/index.js +26 -0
  45. package/dist/server/index.js.map +1 -0
  46. package/dist/server/tools.d.ts +10 -0
  47. package/dist/server/tools.js +230 -0
  48. package/dist/server/tools.js.map +1 -0
  49. package/dist/server/tools.test.d.ts +7 -0
  50. package/dist/server/tools.test.js +606 -0
  51. package/dist/server/tools.test.js.map +1 -0
  52. package/dist/types/credential.d.ts +77 -0
  53. package/dist/types/credential.js +46 -0
  54. package/dist/types/credential.js.map +1 -0
  55. package/dist/types/credential.test.d.ts +1 -0
  56. package/dist/types/credential.test.js +316 -0
  57. package/dist/types/credential.test.js.map +1 -0
  58. package/dist/utils/validation.d.ts +15 -0
  59. package/dist/utils/validation.js +34 -0
  60. package/dist/utils/validation.js.map +1 -0
  61. package/dist/utils/validation.test.d.ts +1 -0
  62. package/dist/utils/validation.test.js +139 -0
  63. package/dist/utils/validation.test.js.map +1 -0
  64. package/package.json +35 -0
@@ -0,0 +1,8 @@
1
+ /**
2
+ * CLI subcommand: ai-cred add <service>
3
+ *
4
+ * Stores a new credential in the macOS Keychain.
5
+ * Prompts interactively for secret fields not provided via flags.
6
+ */
7
+ import { Command } from 'commander';
8
+ export declare function makeAddCommand(): Command;
@@ -0,0 +1,145 @@
1
+ /**
2
+ * CLI subcommand: ai-cred add <service>
3
+ *
4
+ * Stores a new credential in the macOS Keychain.
5
+ * Prompts interactively for secret fields not provided via flags.
6
+ */
7
+ import { Command } from 'commander';
8
+ import { KeychainAdapter, KeychainError } from '../../core/keychain-adapter.js';
9
+ import { CredentialSchema, EnvironmentSchema } from '../../types/credential.js';
10
+ import { ServiceNameSchema } from '../../utils/validation.js';
11
+ import { promptSecret } from '../utils/prompt.js';
12
+ import { auditLog } from '../../core/audit-logger.js';
13
+ async function withAdapter() {
14
+ const adapter = new KeychainAdapter();
15
+ await adapter.initialize();
16
+ return adapter;
17
+ }
18
+ export function makeAddCommand() {
19
+ return new Command('add')
20
+ .description('Store a new credential')
21
+ .argument('<service>', 'Service name')
22
+ .requiredOption('-t, --type <type>', 'Credential type (ssh|jenkins|portainer|aws|api-key)')
23
+ .option('-e, --env <environment>', 'Environment', 'global')
24
+ .option('--notes <text>', 'Optional notes')
25
+ // SSH options
26
+ .option('--host <host>', 'SSH host')
27
+ .option('--port <port>', 'SSH port')
28
+ .option('--username <user>', 'Username')
29
+ .option('--password <pass>', 'Password')
30
+ .option('--key-path <path>', 'SSH key path')
31
+ // Jenkins/Portainer options
32
+ .option('--url <url>', 'Service URL')
33
+ .option('--api-token <token>', 'API token')
34
+ // AWS options
35
+ .option('--access-key-id <id>', 'AWS access key ID')
36
+ .option('--secret-access-key <key>', 'AWS secret access key')
37
+ .option('--region <region>', 'AWS region')
38
+ .option('--profile <name>', 'AWS profile name')
39
+ // API key options
40
+ .option('--key <key>', 'API key value')
41
+ .action(async (service, opts) => {
42
+ // Validate service name
43
+ const svcResult = ServiceNameSchema.safeParse(service);
44
+ if (!svcResult.success) {
45
+ process.stderr.write(`Error: ${svcResult.error.issues[0]?.message ?? 'Invalid service name'}\n`);
46
+ process.exit(1);
47
+ }
48
+ // Validate environment
49
+ const envResult = EnvironmentSchema.safeParse(opts.env);
50
+ if (!envResult.success) {
51
+ process.stderr.write(`Error: ${envResult.error.issues[0]?.message ?? 'Invalid environment'}\n`);
52
+ process.exit(1);
53
+ }
54
+ const env = envResult.data;
55
+ // Build credential object based on type
56
+ const credential = await buildCredential(opts);
57
+ if (!credential)
58
+ return; // buildCredential handles errors
59
+ // Validate through Zod schema
60
+ const credResult = CredentialSchema.safeParse(credential);
61
+ if (!credResult.success) {
62
+ const issues = credResult.error.issues.map((i) => i.message).join(', ');
63
+ process.stderr.write(`Validation error: ${issues}\n`);
64
+ process.exit(1);
65
+ }
66
+ try {
67
+ const adapter = await withAdapter();
68
+ await adapter.store(env, service, credResult.data);
69
+ await auditLog({ operation: 'store', service, environment: env });
70
+ console.log(`Stored credential '${service}' in ${env}`);
71
+ }
72
+ catch (err) {
73
+ if (err instanceof KeychainError) {
74
+ process.stderr.write(`Error: ${err.message}\n`);
75
+ }
76
+ else {
77
+ process.stderr.write(`Error: Failed to store credential\n`);
78
+ }
79
+ process.exit(1);
80
+ }
81
+ });
82
+ }
83
+ async function buildCredential(opts) {
84
+ const type = opts.type;
85
+ const notes = opts.notes;
86
+ switch (type) {
87
+ case 'ssh': {
88
+ const password = opts.password ?? (await promptSecret('Password (or press Enter to skip): ') || undefined);
89
+ const cred = {
90
+ type: 'ssh',
91
+ host: opts.host ?? '',
92
+ port: opts.port ? parseInt(opts.port, 10) : 22,
93
+ username: opts.username ?? '',
94
+ ...(password && { password }),
95
+ ...(opts.keyPath && { keyPath: opts.keyPath }),
96
+ ...(notes && { notes }),
97
+ };
98
+ return cred;
99
+ }
100
+ case 'jenkins': {
101
+ const apiToken = opts.apiToken ?? await promptSecret('API Token: ');
102
+ return {
103
+ type: 'jenkins',
104
+ url: opts.url ?? '',
105
+ username: opts.username ?? '',
106
+ apiToken,
107
+ ...(notes && { notes }),
108
+ };
109
+ }
110
+ case 'portainer': {
111
+ const apiToken = opts.apiToken ?? await promptSecret('API Token: ');
112
+ return {
113
+ type: 'portainer',
114
+ url: opts.url ?? '',
115
+ apiToken,
116
+ ...(notes && { notes }),
117
+ };
118
+ }
119
+ case 'aws': {
120
+ const secretAccessKey = opts.secretAccessKey ?? await promptSecret('Secret Access Key: ');
121
+ return {
122
+ type: 'aws',
123
+ accessKeyId: opts.accessKeyId ?? '',
124
+ secretAccessKey,
125
+ region: opts.region ?? '',
126
+ ...(opts.profile && { profile: opts.profile }),
127
+ ...(notes && { notes }),
128
+ };
129
+ }
130
+ case 'api-key': {
131
+ const key = opts.key ?? await promptSecret('API Key: ');
132
+ return {
133
+ type: 'api-key',
134
+ key,
135
+ ...(opts.url && { url: opts.url }),
136
+ ...(notes && { notes }),
137
+ };
138
+ }
139
+ default:
140
+ process.stderr.write(`Error: Unknown credential type '${type}'. Must be ssh|jenkins|portainer|aws|api-key.\n`);
141
+ process.exit(1);
142
+ return null;
143
+ }
144
+ }
145
+ //# sourceMappingURL=add.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add.js","sourceRoot":"","sources":["../../../src/cli/commands/add.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * CLI subcommand: ai-cred get <service>
3
+ *
4
+ * Retrieves and prints a credential as JSON.
5
+ */
6
+ import { Command } from 'commander';
7
+ export declare function makeGetCommand(): Command;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * CLI subcommand: ai-cred get <service>
3
+ *
4
+ * Retrieves and prints a credential as JSON.
5
+ */
6
+ import { Command } from 'commander';
7
+ import { KeychainAdapter, KeychainError } from '../../core/keychain-adapter.js';
8
+ import { EnvironmentSchema } from '../../types/credential.js';
9
+ import { ServiceNameSchema } from '../../utils/validation.js';
10
+ import { auditLog } from '../../core/audit-logger.js';
11
+ async function withAdapter() {
12
+ const adapter = new KeychainAdapter();
13
+ await adapter.initialize();
14
+ return adapter;
15
+ }
16
+ export function makeGetCommand() {
17
+ return new Command('get')
18
+ .description('Retrieve a credential')
19
+ .argument('<service>', 'Service name')
20
+ .option('-e, --env <environment>', 'Environment', 'global')
21
+ .action(async (service, opts) => {
22
+ const svcResult = ServiceNameSchema.safeParse(service);
23
+ if (!svcResult.success) {
24
+ process.stderr.write(`Error: ${svcResult.error.issues[0]?.message ?? 'Invalid service name'}\n`);
25
+ process.exit(1);
26
+ }
27
+ const envResult = EnvironmentSchema.safeParse(opts.env);
28
+ if (!envResult.success) {
29
+ process.stderr.write(`Error: ${envResult.error.issues[0]?.message ?? 'Invalid environment'}\n`);
30
+ process.exit(1);
31
+ }
32
+ const env = envResult.data;
33
+ try {
34
+ const adapter = await withAdapter();
35
+ const credential = await adapter.get(env, service);
36
+ await auditLog({ operation: 'get', service, environment: env });
37
+ console.log(JSON.stringify(credential, null, 2));
38
+ }
39
+ catch (err) {
40
+ if (err instanceof KeychainError && err.exitCode === 44) {
41
+ process.stderr.write(`No credential found for '${service}' in ${env}\n`);
42
+ }
43
+ else if (err instanceof KeychainError) {
44
+ process.stderr.write(`Error: ${err.message}\n`);
45
+ }
46
+ else {
47
+ process.stderr.write(`Error: Failed to retrieve credential\n`);
48
+ }
49
+ process.exit(1);
50
+ }
51
+ });
52
+ }
53
+ //# sourceMappingURL=get.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get.js","sourceRoot":"","sources":["../../../src/cli/commands/get.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAEtD,KAAK,UAAU,WAAW;IACxB,MAAM,OAAO,GAAG,IAAI,eAAe,EAAE,CAAC;IACtC,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;IAC3B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,OAAO,CAAC,KAAK,CAAC;SACtB,WAAW,CAAC,uBAAuB,CAAC;SACpC,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;SACrC,MAAM,CAAC,yBAAyB,EAAE,aAAa,EAAE,QAAQ,CAAC;SAC1D,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,IAAqB,EAAE,EAAE;QACvD,MAAM,SAAS,GAAG,iBAAiB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACvD,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,sBAAsB,IAAI,CAAC,CAAC;YACjG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,SAAS,GAAG,iBAAiB,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxD,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,qBAAqB,IAAI,CAAC,CAAC;YAChG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC;QAE3B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC;YACpC,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YACnD,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC;YAChE,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,aAAa,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;gBACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,OAAO,QAAQ,GAAG,IAAI,CAAC,CAAC;YAC3E,CAAC;iBAAM,IAAI,GAAG,YAAY,aAAa,EAAE,CAAC;gBACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YAClD,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;YACjE,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * CLI subcommand: ai-cred list
3
+ *
4
+ * Lists all credentials with masked secret preview in a formatted table.
5
+ */
6
+ import { Command } from 'commander';
7
+ export declare function makeListCommand(): Command;
@@ -0,0 +1,118 @@
1
+ /**
2
+ * CLI subcommand: ai-cred list
3
+ *
4
+ * Lists all credentials with masked secret preview in a formatted table.
5
+ */
6
+ import { Command } from 'commander';
7
+ import { KeychainAdapter, KeychainError } from '../../core/keychain-adapter.js';
8
+ import { EnvironmentSchema } from '../../types/credential.js';
9
+ import { maskSecret, getPrimarySecret } from '../utils/display.js';
10
+ async function withAdapter() {
11
+ const adapter = new KeychainAdapter();
12
+ await adapter.initialize();
13
+ return adapter;
14
+ }
15
+ export function makeListCommand() {
16
+ return new Command('list')
17
+ .description('List all credentials')
18
+ .option('-e, --env <environment>', 'Filter by environment')
19
+ .action(async (opts) => {
20
+ // Validate environment filter if provided
21
+ let envFilter;
22
+ if (opts.env) {
23
+ const envResult = EnvironmentSchema.safeParse(opts.env);
24
+ if (!envResult.success) {
25
+ process.stderr.write(`Error: ${envResult.error.issues[0]?.message ?? 'Invalid environment'}\n`);
26
+ process.exit(1);
27
+ }
28
+ envFilter = envResult.data;
29
+ }
30
+ try {
31
+ const adapter = await withAdapter();
32
+ let items = await adapter.list();
33
+ if (envFilter) {
34
+ items = items.filter((item) => item.environment === envFilter);
35
+ }
36
+ if (items.length === 0) {
37
+ console.log('No credentials found.');
38
+ return;
39
+ }
40
+ // Fetch full credentials for masked secret preview (N+1 acceptable for <100 items)
41
+ const rows = [];
42
+ for (const item of items) {
43
+ try {
44
+ const credential = await adapter.get(item.environment, item.service);
45
+ const primarySecret = getPrimarySecret(credential);
46
+ const masked = maskSecret(primarySecret);
47
+ const notes = ('notes' in credential && typeof credential.notes === 'string')
48
+ ? (credential.notes.length > 30 ? credential.notes.slice(0, 27) + '...' : credential.notes)
49
+ : '';
50
+ rows.push({
51
+ service: item.service,
52
+ environment: item.environment,
53
+ type: item.type,
54
+ secret: masked,
55
+ notes,
56
+ });
57
+ }
58
+ catch {
59
+ // If we can't fetch a credential, show what we have
60
+ rows.push({
61
+ service: item.service,
62
+ environment: item.environment,
63
+ type: item.type,
64
+ secret: '(error)',
65
+ notes: '',
66
+ });
67
+ }
68
+ }
69
+ // Calculate column widths
70
+ const headers = { service: 'Service', environment: 'Environment', type: 'Type', secret: 'Secret', notes: 'Notes' };
71
+ const colWidths = {
72
+ service: Math.max(headers.service.length, ...rows.map((r) => r.service.length)),
73
+ environment: Math.max(headers.environment.length, ...rows.map((r) => r.environment.length)),
74
+ type: Math.max(headers.type.length, ...rows.map((r) => r.type.length)),
75
+ secret: Math.max(headers.secret.length, ...rows.map((r) => r.secret.length)),
76
+ notes: Math.max(headers.notes.length, ...rows.map((r) => r.notes.length)),
77
+ };
78
+ // Print header
79
+ const headerLine = [
80
+ headers.service.padEnd(colWidths.service),
81
+ headers.environment.padEnd(colWidths.environment),
82
+ headers.type.padEnd(colWidths.type),
83
+ headers.secret.padEnd(colWidths.secret),
84
+ headers.notes.padEnd(colWidths.notes),
85
+ ].join(' ');
86
+ console.log(headerLine);
87
+ const separator = [
88
+ '-'.repeat(colWidths.service),
89
+ '-'.repeat(colWidths.environment),
90
+ '-'.repeat(colWidths.type),
91
+ '-'.repeat(colWidths.secret),
92
+ '-'.repeat(colWidths.notes),
93
+ ].join(' ');
94
+ console.log(separator);
95
+ // Print rows
96
+ for (const row of rows) {
97
+ const line = [
98
+ row.service.padEnd(colWidths.service),
99
+ row.environment.padEnd(colWidths.environment),
100
+ row.type.padEnd(colWidths.type),
101
+ row.secret.padEnd(colWidths.secret),
102
+ row.notes.padEnd(colWidths.notes),
103
+ ].join(' ');
104
+ console.log(line);
105
+ }
106
+ }
107
+ catch (err) {
108
+ if (err instanceof KeychainError) {
109
+ process.stderr.write(`Error: ${err.message}\n`);
110
+ }
111
+ else {
112
+ process.stderr.write(`Error: Failed to list credentials\n`);
113
+ }
114
+ process.exit(1);
115
+ }
116
+ });
117
+ }
118
+ //# sourceMappingURL=list.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list.js","sourceRoot":"","sources":["../../../src/cli/commands/list.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * CLI subcommand: ai-cred remove <service>
3
+ *
4
+ * Deletes a credential from the macOS Keychain.
5
+ */
6
+ import { Command } from 'commander';
7
+ export declare function makeRemoveCommand(): Command;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * CLI subcommand: ai-cred remove <service>
3
+ *
4
+ * Deletes a credential from the macOS Keychain.
5
+ */
6
+ import { Command } from 'commander';
7
+ import { KeychainAdapter, KeychainError } from '../../core/keychain-adapter.js';
8
+ import { EnvironmentSchema } from '../../types/credential.js';
9
+ import { ServiceNameSchema } from '../../utils/validation.js';
10
+ import { auditLog } from '../../core/audit-logger.js';
11
+ async function withAdapter() {
12
+ const adapter = new KeychainAdapter();
13
+ await adapter.initialize();
14
+ return adapter;
15
+ }
16
+ export function makeRemoveCommand() {
17
+ return new Command('remove')
18
+ .description('Delete a credential')
19
+ .argument('<service>', 'Service name')
20
+ .option('-e, --env <environment>', 'Environment', 'global')
21
+ .action(async (service, opts) => {
22
+ const svcResult = ServiceNameSchema.safeParse(service);
23
+ if (!svcResult.success) {
24
+ process.stderr.write(`Error: ${svcResult.error.issues[0]?.message ?? 'Invalid service name'}\n`);
25
+ process.exit(1);
26
+ }
27
+ const envResult = EnvironmentSchema.safeParse(opts.env);
28
+ if (!envResult.success) {
29
+ process.stderr.write(`Error: ${envResult.error.issues[0]?.message ?? 'Invalid environment'}\n`);
30
+ process.exit(1);
31
+ }
32
+ const env = envResult.data;
33
+ try {
34
+ const adapter = await withAdapter();
35
+ await adapter.delete(env, service);
36
+ await auditLog({ operation: 'delete', service, environment: env });
37
+ console.log(`Removed credential '${service}' from ${env}`);
38
+ }
39
+ catch (err) {
40
+ if (err instanceof KeychainError && err.exitCode === 44) {
41
+ process.stderr.write(`No credential found for '${service}' in ${env}\n`);
42
+ }
43
+ else if (err instanceof KeychainError) {
44
+ process.stderr.write(`Error: ${err.message}\n`);
45
+ }
46
+ else {
47
+ process.stderr.write(`Error: Failed to remove credential\n`);
48
+ }
49
+ process.exit(1);
50
+ }
51
+ });
52
+ }
53
+ //# sourceMappingURL=remove.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove.js","sourceRoot":"","sources":["../../../src/cli/commands/remove.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAChF,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAEtD,KAAK,UAAU,WAAW;IACxB,MAAM,OAAO,GAAG,IAAI,eAAe,EAAE,CAAC;IACtC,MAAM,OAAO,CAAC,UAAU,EAAE,CAAC;IAC3B,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,IAAI,OAAO,CAAC,QAAQ,CAAC;SACzB,WAAW,CAAC,qBAAqB,CAAC;SAClC,QAAQ,CAAC,WAAW,EAAE,cAAc,CAAC;SACrC,MAAM,CAAC,yBAAyB,EAAE,aAAa,EAAE,QAAQ,CAAC;SAC1D,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,IAAqB,EAAE,EAAE;QACvD,MAAM,SAAS,GAAG,iBAAiB,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QACvD,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,sBAAsB,IAAI,CAAC,CAAC;YACjG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,SAAS,GAAG,iBAAiB,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxD,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC;YACvB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,qBAAqB,IAAI,CAAC,CAAC;YAChG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC;QAE3B,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,WAAW,EAAE,CAAC;YACpC,MAAM,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YACnC,MAAM,QAAQ,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,CAAC;YACnE,OAAO,CAAC,GAAG,CAAC,uBAAuB,OAAO,UAAU,GAAG,EAAE,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,aAAa,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,EAAE,CAAC;gBACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,4BAA4B,OAAO,QAAQ,GAAG,IAAI,CAAC,CAAC;YAC3E,CAAC;iBAAM,IAAI,GAAG,YAAY,aAAa,EAAE,CAAC;gBACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YAClD,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;YAC/D,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * CLI subcommand: ai-cred update <service>
3
+ *
4
+ * Replaces a credential via upsert (-U flag on add-generic-password).
5
+ * Full replacement: all fields must be provided.
6
+ */
7
+ import { Command } from 'commander';
8
+ export declare function makeUpdateCommand(): Command;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * CLI subcommand: ai-cred update <service>
3
+ *
4
+ * Replaces a credential via upsert (-U flag on add-generic-password).
5
+ * Full replacement: all fields must be provided.
6
+ */
7
+ import { Command } from 'commander';
8
+ import { KeychainAdapter, KeychainError } from '../../core/keychain-adapter.js';
9
+ import { CredentialSchema, EnvironmentSchema } from '../../types/credential.js';
10
+ import { ServiceNameSchema } from '../../utils/validation.js';
11
+ import { promptSecret } from '../utils/prompt.js';
12
+ import { auditLog } from '../../core/audit-logger.js';
13
+ async function withAdapter() {
14
+ const adapter = new KeychainAdapter();
15
+ await adapter.initialize();
16
+ return adapter;
17
+ }
18
+ export function makeUpdateCommand() {
19
+ return new Command('update')
20
+ .description('Update (replace) a credential')
21
+ .argument('<service>', 'Service name')
22
+ .requiredOption('-t, --type <type>', 'Credential type (ssh|jenkins|portainer|aws|api-key)')
23
+ .option('-e, --env <environment>', 'Environment', 'global')
24
+ .option('--notes <text>', 'Optional notes')
25
+ // SSH options
26
+ .option('--host <host>', 'SSH host')
27
+ .option('--port <port>', 'SSH port')
28
+ .option('--username <user>', 'Username')
29
+ .option('--password <pass>', 'Password')
30
+ .option('--key-path <path>', 'SSH key path')
31
+ // Jenkins/Portainer options
32
+ .option('--url <url>', 'Service URL')
33
+ .option('--api-token <token>', 'API token')
34
+ // AWS options
35
+ .option('--access-key-id <id>', 'AWS access key ID')
36
+ .option('--secret-access-key <key>', 'AWS secret access key')
37
+ .option('--region <region>', 'AWS region')
38
+ .option('--profile <name>', 'AWS profile name')
39
+ // API key options
40
+ .option('--key <key>', 'API key value')
41
+ .action(async (service, opts) => {
42
+ const svcResult = ServiceNameSchema.safeParse(service);
43
+ if (!svcResult.success) {
44
+ process.stderr.write(`Error: ${svcResult.error.issues[0]?.message ?? 'Invalid service name'}\n`);
45
+ process.exit(1);
46
+ }
47
+ const envResult = EnvironmentSchema.safeParse(opts.env);
48
+ if (!envResult.success) {
49
+ process.stderr.write(`Error: ${envResult.error.issues[0]?.message ?? 'Invalid environment'}\n`);
50
+ process.exit(1);
51
+ }
52
+ const env = envResult.data;
53
+ const credential = await buildCredential(opts);
54
+ if (!credential)
55
+ return;
56
+ const credResult = CredentialSchema.safeParse(credential);
57
+ if (!credResult.success) {
58
+ const issues = credResult.error.issues.map((i) => i.message).join(', ');
59
+ process.stderr.write(`Validation error: ${issues}\n`);
60
+ process.exit(1);
61
+ }
62
+ try {
63
+ const adapter = await withAdapter();
64
+ await adapter.store(env, service, credResult.data);
65
+ await auditLog({ operation: 'update', service, environment: env });
66
+ console.log(`Updated credential '${service}' in ${env}`);
67
+ }
68
+ catch (err) {
69
+ if (err instanceof KeychainError) {
70
+ process.stderr.write(`Error: ${err.message}\n`);
71
+ }
72
+ else {
73
+ process.stderr.write(`Error: Failed to update credential\n`);
74
+ }
75
+ process.exit(1);
76
+ }
77
+ });
78
+ }
79
+ async function buildCredential(opts) {
80
+ const type = opts.type;
81
+ const notes = opts.notes;
82
+ switch (type) {
83
+ case 'ssh': {
84
+ const password = opts.password ?? (await promptSecret('Password (or press Enter to skip): ') || undefined);
85
+ return {
86
+ type: 'ssh',
87
+ host: opts.host ?? '',
88
+ port: opts.port ? parseInt(opts.port, 10) : 22,
89
+ username: opts.username ?? '',
90
+ ...(password && { password }),
91
+ ...(opts.keyPath && { keyPath: opts.keyPath }),
92
+ ...(notes && { notes }),
93
+ };
94
+ }
95
+ case 'jenkins': {
96
+ const apiToken = opts.apiToken ?? await promptSecret('API Token: ');
97
+ return {
98
+ type: 'jenkins',
99
+ url: opts.url ?? '',
100
+ username: opts.username ?? '',
101
+ apiToken,
102
+ ...(notes && { notes }),
103
+ };
104
+ }
105
+ case 'portainer': {
106
+ const apiToken = opts.apiToken ?? await promptSecret('API Token: ');
107
+ return {
108
+ type: 'portainer',
109
+ url: opts.url ?? '',
110
+ apiToken,
111
+ ...(notes && { notes }),
112
+ };
113
+ }
114
+ case 'aws': {
115
+ const secretAccessKey = opts.secretAccessKey ?? await promptSecret('Secret Access Key: ');
116
+ return {
117
+ type: 'aws',
118
+ accessKeyId: opts.accessKeyId ?? '',
119
+ secretAccessKey,
120
+ region: opts.region ?? '',
121
+ ...(opts.profile && { profile: opts.profile }),
122
+ ...(notes && { notes }),
123
+ };
124
+ }
125
+ case 'api-key': {
126
+ const key = opts.key ?? await promptSecret('API Key: ');
127
+ return {
128
+ type: 'api-key',
129
+ key,
130
+ ...(opts.url && { url: opts.url }),
131
+ ...(notes && { notes }),
132
+ };
133
+ }
134
+ default:
135
+ process.stderr.write(`Error: Unknown credential type '${type}'. Must be ssh|jenkins|portainer|aws|api-key.\n`);
136
+ process.exit(1);
137
+ return null;
138
+ }
139
+ }
140
+ //# sourceMappingURL=update.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"update.js","sourceRoot":"","sources":["../../../src/cli/commands/update.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * ai-cred CLI entry point.
3
+ *
4
+ * Provides terminal-based credential management backed by the macOS Keychain.
5
+ * Development usage: npx tsx src/cli/index.ts <command>
6
+ */
7
+ export {};
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ai-cred CLI entry point.
4
+ *
5
+ * Provides terminal-based credential management backed by the macOS Keychain.
6
+ * Development usage: npx tsx src/cli/index.ts <command>
7
+ */
8
+ import { Command } from 'commander';
9
+ import { makeAddCommand } from './commands/add.js';
10
+ import { makeGetCommand } from './commands/get.js';
11
+ import { makeListCommand } from './commands/list.js';
12
+ import { makeRemoveCommand } from './commands/remove.js';
13
+ import { makeUpdateCommand } from './commands/update.js';
14
+ const program = new Command();
15
+ program
16
+ .name('ai-cred')
17
+ .description('Manage credentials stored in macOS Keychain')
18
+ .version('1.0.0');
19
+ program.addCommand(makeAddCommand());
20
+ program.addCommand(makeGetCommand());
21
+ program.addCommand(makeListCommand());
22
+ program.addCommand(makeRemoveCommand());
23
+ program.addCommand(makeUpdateCommand());
24
+ program.parse();
25
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAEzD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,SAAS,CAAC;KACf,WAAW,CAAC,6CAA6C,CAAC;KAC1D,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC,CAAC;AACrC,OAAO,CAAC,UAAU,CAAC,cAAc,EAAE,CAAC,CAAC;AACrC,OAAO,CAAC,UAAU,CAAC,eAAe,EAAE,CAAC,CAAC;AACtC,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AAExC,OAAO,CAAC,KAAK,EAAE,CAAC"}