@howlil/ez-agents 2.0.0 → 3.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 (145) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +157 -110
  3. package/README.zh-CN.md +84 -84
  4. package/agents/ez-plan-checker.md +2 -2
  5. package/agents/ez-research-synthesizer.md +1 -1
  6. package/agents/ez-ui-auditor.md +0 -2
  7. package/agents/ez-ui-checker.md +2 -4
  8. package/agents/ez-ui-researcher.md +0 -2
  9. package/agents/ez-verifier.md +1 -1
  10. package/bin/install.js +211 -211
  11. package/commands/ez/debug.md +1 -1
  12. package/commands/ez/map-codebase.md +1 -1
  13. package/commands/ez/reapply-patches.md +3 -3
  14. package/commands/ez/research-phase.md +1 -1
  15. package/{get-shit-done → ez-agents}/bin/ez-tools.cjs +1 -1
  16. package/{get-shit-done → ez-agents}/bin/lib/assistant-adapter.cjs +205 -205
  17. package/{get-shit-done → ez-agents}/bin/lib/audit-exec.cjs +150 -150
  18. package/{get-shit-done → ez-agents}/bin/lib/auth.cjs +175 -175
  19. package/{get-shit-done → ez-agents}/bin/lib/circuit-breaker.cjs +118 -118
  20. package/{get-shit-done → ez-agents}/bin/lib/commands.cjs +666 -666
  21. package/{get-shit-done → ez-agents}/bin/lib/config.cjs +183 -183
  22. package/{get-shit-done → ez-agents}/bin/lib/core.cjs +495 -495
  23. package/{get-shit-done → ez-agents}/bin/lib/file-lock.cjs +236 -236
  24. package/{get-shit-done → ez-agents}/bin/lib/frontmatter.cjs +299 -299
  25. package/{get-shit-done → ez-agents}/bin/lib/fs-utils.cjs +153 -153
  26. package/{get-shit-done → ez-agents}/bin/lib/git-utils.cjs +203 -203
  27. package/{get-shit-done → ez-agents}/bin/lib/health-check.cjs +163 -163
  28. package/{get-shit-done → ez-agents}/bin/lib/index.cjs +113 -113
  29. package/{get-shit-done → ez-agents}/bin/lib/init.cjs +710 -710
  30. package/{get-shit-done → ez-agents}/bin/lib/logger.cjs +117 -117
  31. package/{get-shit-done → ez-agents}/bin/lib/milestone.cjs +241 -241
  32. package/{get-shit-done → ez-agents}/bin/lib/model-provider.cjs +146 -146
  33. package/{get-shit-done → ez-agents}/bin/lib/phase.cjs +908 -908
  34. package/{get-shit-done → ez-agents}/bin/lib/retry.cjs +119 -119
  35. package/{get-shit-done → ez-agents}/bin/lib/roadmap.cjs +305 -305
  36. package/{get-shit-done → ez-agents}/bin/lib/safe-exec.cjs +128 -128
  37. package/{get-shit-done → ez-agents}/bin/lib/safe-path.cjs +130 -130
  38. package/{get-shit-done → ez-agents}/bin/lib/state.cjs +721 -721
  39. package/{get-shit-done → ez-agents}/bin/lib/temp-file.cjs +239 -239
  40. package/{get-shit-done → ez-agents}/bin/lib/template.cjs +222 -222
  41. package/{get-shit-done → ez-agents}/bin/lib/test-file-lock.cjs +112 -112
  42. package/{get-shit-done → ez-agents}/bin/lib/test-graceful.cjs +93 -93
  43. package/{get-shit-done → ez-agents}/bin/lib/test-logger.cjs +60 -60
  44. package/{get-shit-done → ez-agents}/bin/lib/test-safe-exec.cjs +38 -38
  45. package/{get-shit-done → ez-agents}/bin/lib/test-safe-path.cjs +33 -33
  46. package/{get-shit-done → ez-agents}/bin/lib/test-temp-file.cjs +125 -125
  47. package/{get-shit-done → ez-agents}/bin/lib/timeout-exec.cjs +62 -62
  48. package/{get-shit-done → ez-agents}/bin/lib/verify.cjs +820 -820
  49. package/{get-shit-done → ez-agents}/references/checkpoints.md +776 -776
  50. package/{get-shit-done → ez-agents}/references/questioning.md +162 -162
  51. package/{get-shit-done → ez-agents}/references/tdd.md +263 -263
  52. package/{get-shit-done → ez-agents}/templates/codebase/concerns.md +310 -310
  53. package/{get-shit-done → ez-agents}/templates/codebase/conventions.md +307 -307
  54. package/{get-shit-done → ez-agents}/templates/codebase/integrations.md +280 -280
  55. package/{get-shit-done → ez-agents}/templates/codebase/stack.md +186 -186
  56. package/{get-shit-done → ez-agents}/templates/codebase/testing.md +480 -480
  57. package/{get-shit-done → ez-agents}/templates/config.json +37 -37
  58. package/{get-shit-done → ez-agents}/templates/continue-here.md +78 -78
  59. package/{get-shit-done → ez-agents}/templates/milestone-archive.md +123 -123
  60. package/{get-shit-done → ez-agents}/templates/milestone.md +115 -115
  61. package/{get-shit-done → ez-agents}/templates/requirements.md +231 -231
  62. package/{get-shit-done → ez-agents}/templates/research-project/ARCHITECTURE.md +204 -204
  63. package/{get-shit-done → ez-agents}/templates/research-project/FEATURES.md +147 -147
  64. package/{get-shit-done → ez-agents}/templates/research-project/PITFALLS.md +200 -200
  65. package/{get-shit-done → ez-agents}/templates/research-project/STACK.md +120 -120
  66. package/{get-shit-done → ez-agents}/templates/research-project/SUMMARY.md +170 -170
  67. package/{get-shit-done → ez-agents}/templates/retrospective.md +54 -54
  68. package/{get-shit-done → ez-agents}/templates/roadmap.md +202 -202
  69. package/{get-shit-done → ez-agents}/templates/summary-minimal.md +41 -41
  70. package/{get-shit-done → ez-agents}/templates/summary-standard.md +48 -48
  71. package/{get-shit-done → ez-agents}/templates/summary.md +248 -248
  72. package/{get-shit-done → ez-agents}/templates/user-setup.md +311 -311
  73. package/{get-shit-done → ez-agents}/templates/verification-report.md +322 -322
  74. package/{get-shit-done → ez-agents}/workflows/add-phase.md +112 -112
  75. package/{get-shit-done → ez-agents}/workflows/add-tests.md +351 -351
  76. package/{get-shit-done → ez-agents}/workflows/add-todo.md +158 -158
  77. package/{get-shit-done → ez-agents}/workflows/audit-milestone.md +332 -332
  78. package/{get-shit-done → ez-agents}/workflows/autonomous.md +743 -743
  79. package/{get-shit-done → ez-agents}/workflows/check-todos.md +177 -177
  80. package/{get-shit-done → ez-agents}/workflows/cleanup.md +152 -152
  81. package/{get-shit-done → ez-agents}/workflows/complete-milestone.md +766 -766
  82. package/ez-agents/workflows/debug.md +0 -0
  83. package/{get-shit-done → ez-agents}/workflows/diagnose-issues.md +219 -219
  84. package/{get-shit-done → ez-agents}/workflows/discovery-phase.md +289 -289
  85. package/{get-shit-done → ez-agents}/workflows/discuss-phase.md +762 -762
  86. package/{get-shit-done → ez-agents}/workflows/execute-phase.md +468 -468
  87. package/{get-shit-done → ez-agents}/workflows/execute-plan.md +483 -483
  88. package/{get-shit-done → ez-agents}/workflows/health.md +159 -159
  89. package/{get-shit-done → ez-agents}/workflows/help.md +492 -492
  90. package/{get-shit-done → ez-agents}/workflows/insert-phase.md +130 -130
  91. package/{get-shit-done → ez-agents}/workflows/list-phase-assumptions.md +178 -178
  92. package/{get-shit-done → ez-agents}/workflows/map-codebase.md +316 -316
  93. package/{get-shit-done → ez-agents}/workflows/new-milestone.md +384 -384
  94. package/{get-shit-done → ez-agents}/workflows/new-project.md +1111 -1111
  95. package/{get-shit-done → ez-agents}/workflows/node-repair.md +92 -92
  96. package/{get-shit-done → ez-agents}/workflows/pause-work.md +122 -122
  97. package/{get-shit-done → ez-agents}/workflows/plan-milestone-gaps.md +274 -274
  98. package/{get-shit-done → ez-agents}/workflows/plan-phase.md +651 -651
  99. package/{get-shit-done → ez-agents}/workflows/progress.md +382 -382
  100. package/{get-shit-done → ez-agents}/workflows/quick.md +610 -610
  101. package/{get-shit-done → ez-agents}/workflows/remove-phase.md +155 -155
  102. package/{get-shit-done → ez-agents}/workflows/research-phase.md +74 -74
  103. package/{get-shit-done → ez-agents}/workflows/resume-project.md +307 -307
  104. package/{get-shit-done → ez-agents}/workflows/set-profile.md +81 -81
  105. package/{get-shit-done → ez-agents}/workflows/settings.md +242 -242
  106. package/{get-shit-done → ez-agents}/workflows/stats.md +57 -57
  107. package/{get-shit-done → ez-agents}/workflows/transition.md +544 -544
  108. package/{get-shit-done → ez-agents}/workflows/ui-phase.md +290 -290
  109. package/{get-shit-done → ez-agents}/workflows/ui-review.md +157 -157
  110. package/{get-shit-done → ez-agents}/workflows/update.md +320 -320
  111. package/{get-shit-done → ez-agents}/workflows/validate-phase.md +167 -167
  112. package/{get-shit-done → ez-agents}/workflows/verify-phase.md +243 -243
  113. package/{get-shit-done → ez-agents}/workflows/verify-work.md +5 -5
  114. package/hooks/dist/ez-check-update.js +81 -0
  115. package/hooks/dist/ez-context-monitor.js +141 -0
  116. package/hooks/dist/ez-statusline.js +115 -0
  117. package/package.json +13 -3
  118. package/scripts/build-hooks.js +43 -43
  119. package/scripts/run-tests.cjs +29 -29
  120. /package/{get-shit-done → ez-agents}/references/continuation-format.md +0 -0
  121. /package/{get-shit-done → ez-agents}/references/decimal-phase-calculation.md +0 -0
  122. /package/{get-shit-done → ez-agents}/references/git-integration.md +0 -0
  123. /package/{get-shit-done → ez-agents}/references/git-planning-commit.md +0 -0
  124. /package/{get-shit-done → ez-agents}/references/model-profile-resolution.md +0 -0
  125. /package/{get-shit-done → ez-agents}/references/model-profiles.md +0 -0
  126. /package/{get-shit-done → ez-agents}/references/phase-argument-parsing.md +0 -0
  127. /package/{get-shit-done → ez-agents}/references/planning-config.md +0 -0
  128. /package/{get-shit-done → ez-agents}/references/ui-brand.md +0 -0
  129. /package/{get-shit-done → ez-agents}/references/verification-patterns.md +0 -0
  130. /package/{get-shit-done → ez-agents}/templates/DEBUG.md +0 -0
  131. /package/{get-shit-done → ez-agents}/templates/UAT.md +0 -0
  132. /package/{get-shit-done → ez-agents}/templates/UI-SPEC.md +0 -0
  133. /package/{get-shit-done → ez-agents}/templates/VALIDATION.md +0 -0
  134. /package/{get-shit-done → ez-agents}/templates/codebase/architecture.md +0 -0
  135. /package/{get-shit-done → ez-agents}/templates/codebase/structure.md +0 -0
  136. /package/{get-shit-done → ez-agents}/templates/context.md +0 -0
  137. /package/{get-shit-done → ez-agents}/templates/copilot-instructions.md +0 -0
  138. /package/{get-shit-done → ez-agents}/templates/debug-subagent-prompt.md +0 -0
  139. /package/{get-shit-done → ez-agents}/templates/discovery.md +0 -0
  140. /package/{get-shit-done → ez-agents}/templates/phase-prompt.md +0 -0
  141. /package/{get-shit-done → ez-agents}/templates/planner-subagent-prompt.md +0 -0
  142. /package/{get-shit-done → ez-agents}/templates/project.md +0 -0
  143. /package/{get-shit-done → ez-agents}/templates/research.md +0 -0
  144. /package/{get-shit-done → ez-agents}/templates/state.md +0 -0
  145. /package/{get-shit-done → ez-agents}/templates/summary-complex.md +0 -0
@@ -1,150 +1,150 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * GSD Audit Exec — Command execution with full audit logging
5
- *
6
- * Logs all command executions to audit file for security review:
7
- * - Timestamp, command, arguments, context
8
- * - Duration and result status
9
- * - Error details if failed
10
- *
11
- * Usage:
12
- * const { auditExec } = require('./audit-exec.cjs');
13
- * const result = await auditExec('git', ['status'], { context: 'my-module' });
14
- */
15
-
16
- const { execFile } = require('child_process');
17
- const { promisify } = require('util');
18
- const { appendFileSync, existsSync, mkdirSync } = require('fs');
19
- const { join } = require('path');
20
- const Logger = require('./logger.cjs');
21
- const logger = new Logger();
22
-
23
- const execFileAsync = promisify(execFile);
24
-
25
- // Audit log file path
26
- const AUDIT_DIR = join('.planning', 'logs');
27
- const AUDIT_FILE = join(AUDIT_DIR, `audit-${new Date().toISOString().split('T')[0]}.jsonl`);
28
-
29
- // Ensure audit directory exists
30
- if (!existsSync(AUDIT_DIR)) {
31
- mkdirSync(AUDIT_DIR, { recursive: true });
32
- }
33
-
34
- /**
35
- * Write audit log entry
36
- * @param {Object} entry - Audit entry
37
- */
38
- function writeAudit(entry) {
39
- const line = JSON.stringify(entry) + '\n';
40
- appendFileSync(AUDIT_FILE, line, 'utf-8');
41
- }
42
-
43
- /**
44
- * Execute command with full audit logging
45
- * @param {string} cmd - Command to execute
46
- * @param {string[]} args - Command arguments
47
- * @param {Object} options - Execution options
48
- * @param {string} options.context - Calling context (which module/function)
49
- * @param {string} options.user - User identifier
50
- * @returns {Promise<string>} - Command stdout
51
- */
52
- async function auditExec(cmd, args = [], options = {}) {
53
- const { context = 'unknown', user = 'system', timeout = 30000 } = options;
54
-
55
- const entry = {
56
- timestamp: new Date().toISOString(),
57
- cmd,
58
- args,
59
- context,
60
- user,
61
- status: 'started'
62
- };
63
-
64
- // Log start
65
- writeAudit(entry);
66
- logger.info('Audit: command started', { cmd, args: args.join(' '), context });
67
-
68
- const startTime = Date.now();
69
-
70
- try {
71
- const result = await execFileAsync(cmd, args, {
72
- timeout,
73
- maxBuffer: 10 * 1024 * 1024
74
- });
75
-
76
- const duration = Date.now() - startTime;
77
-
78
- // Log success
79
- const successEntry = {
80
- ...entry,
81
- status: 'success',
82
- duration,
83
- stdout_length: result.stdout?.length || 0
84
- };
85
- writeAudit(successEntry);
86
-
87
- logger.debug('Audit: command completed', { cmd, duration, context });
88
-
89
- return result.stdout.trim();
90
- } catch (err) {
91
- const duration = Date.now() - startTime;
92
-
93
- // Log failure
94
- const errorEntry = {
95
- ...entry,
96
- status: 'error',
97
- duration,
98
- error: err.message,
99
- code: err.code,
100
- signal: err.signal
101
- };
102
- writeAudit(errorEntry);
103
-
104
- logger.error('Audit: command failed', { cmd, error: err.message, duration, context });
105
-
106
- throw err;
107
- }
108
- }
109
-
110
- /**
111
- * Get today's audit log path
112
- * @returns {string} - Audit file path
113
- */
114
- function getAuditFilePath() {
115
- return AUDIT_FILE;
116
- }
117
-
118
- /**
119
- * Read audit log entries for a specific date
120
- * @param {string} date - Date string (YYYY-MM-DD)
121
- * @returns {Object[]} - Array of audit entries
122
- */
123
- function readAuditLog(date = new Date().toISOString().split('T')[0]) {
124
- const filePath = join(AUDIT_DIR, `audit-${date}.jsonl`);
125
-
126
- if (!existsSync(filePath)) {
127
- return [];
128
- }
129
-
130
- const content = require('fs').readFileSync(filePath, 'utf-8');
131
- return content.trim().split('\n').map(line => JSON.parse(line));
132
- }
133
-
134
- /**
135
- * Search audit log for specific command
136
- * @param {string} cmdFilter - Command to filter by
137
- * @param {string} date - Date string
138
- * @returns {Object[]} - Matching entries
139
- */
140
- function searchAuditLog(cmdFilter, date) {
141
- const entries = readAuditLog(date);
142
- return entries.filter(e => e.cmd === cmdFilter);
143
- }
144
-
145
- module.exports = {
146
- auditExec,
147
- getAuditFilePath,
148
- readAuditLog,
149
- searchAuditLog
150
- };
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * GSD Audit Exec — Command execution with full audit logging
5
+ *
6
+ * Logs all command executions to audit file for security review:
7
+ * - Timestamp, command, arguments, context
8
+ * - Duration and result status
9
+ * - Error details if failed
10
+ *
11
+ * Usage:
12
+ * const { auditExec } = require('./audit-exec.cjs');
13
+ * const result = await auditExec('git', ['status'], { context: 'my-module' });
14
+ */
15
+
16
+ const { execFile } = require('child_process');
17
+ const { promisify } = require('util');
18
+ const { appendFileSync, existsSync, mkdirSync } = require('fs');
19
+ const { join } = require('path');
20
+ const Logger = require('./logger.cjs');
21
+ const logger = new Logger();
22
+
23
+ const execFileAsync = promisify(execFile);
24
+
25
+ // Audit log file path
26
+ const AUDIT_DIR = join('.planning', 'logs');
27
+ const AUDIT_FILE = join(AUDIT_DIR, `audit-${new Date().toISOString().split('T')[0]}.jsonl`);
28
+
29
+ // Ensure audit directory exists
30
+ if (!existsSync(AUDIT_DIR)) {
31
+ mkdirSync(AUDIT_DIR, { recursive: true });
32
+ }
33
+
34
+ /**
35
+ * Write audit log entry
36
+ * @param {Object} entry - Audit entry
37
+ */
38
+ function writeAudit(entry) {
39
+ const line = JSON.stringify(entry) + '\n';
40
+ appendFileSync(AUDIT_FILE, line, 'utf-8');
41
+ }
42
+
43
+ /**
44
+ * Execute command with full audit logging
45
+ * @param {string} cmd - Command to execute
46
+ * @param {string[]} args - Command arguments
47
+ * @param {Object} options - Execution options
48
+ * @param {string} options.context - Calling context (which module/function)
49
+ * @param {string} options.user - User identifier
50
+ * @returns {Promise<string>} - Command stdout
51
+ */
52
+ async function auditExec(cmd, args = [], options = {}) {
53
+ const { context = 'unknown', user = 'system', timeout = 30000 } = options;
54
+
55
+ const entry = {
56
+ timestamp: new Date().toISOString(),
57
+ cmd,
58
+ args,
59
+ context,
60
+ user,
61
+ status: 'started'
62
+ };
63
+
64
+ // Log start
65
+ writeAudit(entry);
66
+ logger.info('Audit: command started', { cmd, args: args.join(' '), context });
67
+
68
+ const startTime = Date.now();
69
+
70
+ try {
71
+ const result = await execFileAsync(cmd, args, {
72
+ timeout,
73
+ maxBuffer: 10 * 1024 * 1024
74
+ });
75
+
76
+ const duration = Date.now() - startTime;
77
+
78
+ // Log success
79
+ const successEntry = {
80
+ ...entry,
81
+ status: 'success',
82
+ duration,
83
+ stdout_length: result.stdout?.length || 0
84
+ };
85
+ writeAudit(successEntry);
86
+
87
+ logger.debug('Audit: command completed', { cmd, duration, context });
88
+
89
+ return result.stdout.trim();
90
+ } catch (err) {
91
+ const duration = Date.now() - startTime;
92
+
93
+ // Log failure
94
+ const errorEntry = {
95
+ ...entry,
96
+ status: 'error',
97
+ duration,
98
+ error: err.message,
99
+ code: err.code,
100
+ signal: err.signal
101
+ };
102
+ writeAudit(errorEntry);
103
+
104
+ logger.error('Audit: command failed', { cmd, error: err.message, duration, context });
105
+
106
+ throw err;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Get today's audit log path
112
+ * @returns {string} - Audit file path
113
+ */
114
+ function getAuditFilePath() {
115
+ return AUDIT_FILE;
116
+ }
117
+
118
+ /**
119
+ * Read audit log entries for a specific date
120
+ * @param {string} date - Date string (YYYY-MM-DD)
121
+ * @returns {Object[]} - Array of audit entries
122
+ */
123
+ function readAuditLog(date = new Date().toISOString().split('T')[0]) {
124
+ const filePath = join(AUDIT_DIR, `audit-${date}.jsonl`);
125
+
126
+ if (!existsSync(filePath)) {
127
+ return [];
128
+ }
129
+
130
+ const content = require('fs').readFileSync(filePath, 'utf-8');
131
+ return content.trim().split('\n').map(line => JSON.parse(line));
132
+ }
133
+
134
+ /**
135
+ * Search audit log for specific command
136
+ * @param {string} cmdFilter - Command to filter by
137
+ * @param {string} date - Date string
138
+ * @returns {Object[]} - Matching entries
139
+ */
140
+ function searchAuditLog(cmdFilter, date) {
141
+ const entries = readAuditLog(date);
142
+ return entries.filter(e => e.cmd === cmdFilter);
143
+ }
144
+
145
+ module.exports = {
146
+ auditExec,
147
+ getAuditFilePath,
148
+ readAuditLog,
149
+ searchAuditLog
150
+ };
@@ -1,175 +1,175 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * GSD Auth — Secure credential storage using system keychain
5
- *
6
- * Stores API keys securely using:
7
- * - keytar for system keychain (Windows Credential Manager, macOS Keychain, libsecret)
8
- * - Fallback to encrypted file storage if keytar unavailable
9
- *
10
- * Usage:
11
- * const { saveCredential, loadCredential } = require('./auth.cjs');
12
- * await saveCredential('anthropic', 'sk-...');
13
- */
14
-
15
- const Logger = require('./logger.cjs');
16
- const logger = new Logger();
17
- const path = require('path');
18
- const fs = require('fs');
19
- const os = require('os');
20
-
21
- const SERVICE_NAME = 'gsd-multi-model';
22
-
23
- // Provider account names
24
- const PROVIDERS = {
25
- ANTHROPIC: 'anthropic',
26
- MOONSHOT: 'moonshot',
27
- ALIBABA: 'alibaba',
28
- OPENAI: 'openai'
29
- };
30
-
31
- // Fallback storage path
32
- const FALLBACK_FILE = path.join(os.homedir(), '.gsd-credentials.json');
33
-
34
- // Try to load keytar
35
- let keytar = null;
36
- try {
37
- keytar = require('keytar');
38
- logger.info('keytar loaded — using system keychain');
39
- } catch (err) {
40
- logger.warn('keytar not available — using fallback storage');
41
- }
42
-
43
- /**
44
- * Save credential securely
45
- * @param {string} provider - Provider name (anthropic, moonshot, etc.)
46
- * @param {string} secret - API key or secret
47
- * @returns {Promise<boolean>} - Success status
48
- */
49
- async function saveCredential(provider, secret) {
50
- try {
51
- if (keytar) {
52
- await keytar.setPassword(SERVICE_NAME, provider, secret);
53
- logger.info('Credential saved to system keychain', { provider });
54
- return true;
55
- } else {
56
- // Fallback: save to file
57
- let credentials = {};
58
- if (fs.existsSync(FALLBACK_FILE)) {
59
- const content = fs.readFileSync(FALLBACK_FILE, 'utf-8');
60
- try {
61
- credentials = JSON.parse(content);
62
- } catch (e) {
63
- credentials = {};
64
- }
65
- }
66
- credentials[provider] = secret;
67
- fs.writeFileSync(FALLBACK_FILE, JSON.stringify(credentials, null, 2), 'utf-8');
68
- logger.warn('Credential saved to fallback file', { provider, path: FALLBACK_FILE });
69
- return true;
70
- }
71
- } catch (err) {
72
- logger.error('Failed to save credential', { provider, error: err.message });
73
- return false;
74
- }
75
- }
76
-
77
- /**
78
- * Load credential securely
79
- * @param {string} provider - Provider name
80
- * @returns {Promise<string|null>} - API key or null if not found
81
- */
82
- async function loadCredential(provider) {
83
- try {
84
- if (keytar) {
85
- const secret = await keytar.getPassword(SERVICE_NAME, provider);
86
- if (secret) {
87
- logger.debug('Credential loaded from keychain', { provider });
88
- }
89
- return secret || null;
90
- } else {
91
- // Fallback: load from file
92
- if (fs.existsSync(FALLBACK_FILE)) {
93
- const content = fs.readFileSync(FALLBACK_FILE, 'utf-8');
94
- const credentials = JSON.parse(content);
95
- return credentials[provider] || null;
96
- }
97
- return null;
98
- }
99
- } catch (err) {
100
- logger.error('Failed to load credential', { provider, error: err.message });
101
- return null;
102
- }
103
- }
104
-
105
- /**
106
- * Delete credential
107
- * @param {string} provider - Provider name
108
- * @returns {Promise<boolean>} - Success status
109
- */
110
- async function deleteCredential(provider) {
111
- try {
112
- if (keytar) {
113
- await keytar.deletePassword(SERVICE_NAME, provider);
114
- logger.info('Credential deleted from keychain', { provider });
115
- return true;
116
- } else {
117
- // Fallback: remove from file
118
- if (fs.existsSync(FALLBACK_FILE)) {
119
- const content = fs.readFileSync(FALLBACK_FILE, 'utf-8');
120
- const credentials = JSON.parse(content);
121
- if (credentials[provider]) {
122
- delete credentials[provider];
123
- fs.writeFileSync(FALLBACK_FILE, JSON.stringify(credentials, null, 2), 'utf-8');
124
- logger.info('Credential deleted from fallback file', { provider });
125
- return true;
126
- }
127
- }
128
- return false;
129
- }
130
- } catch (err) {
131
- logger.error('Failed to delete credential', { provider, error: err.message });
132
- return false;
133
- }
134
- }
135
-
136
- /**
137
- * List all stored providers
138
- * @returns {Promise<string[]>} - Array of provider names
139
- */
140
- async function listProviders() {
141
- const stored = [];
142
-
143
- if (keytar) {
144
- for (const [, name] of Object.entries(PROVIDERS)) {
145
- const cred = await keytar.getPassword(SERVICE_NAME, name);
146
- if (cred) stored.push(name);
147
- }
148
- } else {
149
- if (fs.existsSync(FALLBACK_FILE)) {
150
- const content = fs.readFileSync(FALLBACK_FILE, 'utf-8');
151
- const credentials = JSON.parse(content);
152
- return Object.keys(credentials);
153
- }
154
- }
155
-
156
- return stored;
157
- }
158
-
159
- /**
160
- * Check if keytar is available
161
- * @returns {boolean} - True if using system keychain
162
- */
163
- function isKeychainAvailable() {
164
- return keytar !== null;
165
- }
166
-
167
- module.exports = {
168
- saveCredential,
169
- loadCredential,
170
- deleteCredential,
171
- listProviders,
172
- isKeychainAvailable,
173
- PROVIDERS,
174
- SERVICE_NAME
175
- };
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * GSD Auth — Secure credential storage using system keychain
5
+ *
6
+ * Stores API keys securely using:
7
+ * - keytar for system keychain (Windows Credential Manager, macOS Keychain, libsecret)
8
+ * - Fallback to encrypted file storage if keytar unavailable
9
+ *
10
+ * Usage:
11
+ * const { saveCredential, loadCredential } = require('./auth.cjs');
12
+ * await saveCredential('anthropic', 'sk-...');
13
+ */
14
+
15
+ const Logger = require('./logger.cjs');
16
+ const logger = new Logger();
17
+ const path = require('path');
18
+ const fs = require('fs');
19
+ const os = require('os');
20
+
21
+ const SERVICE_NAME = 'ez-agents';
22
+
23
+ // Provider account names
24
+ const PROVIDERS = {
25
+ ANTHROPIC: 'anthropic',
26
+ MOONSHOT: 'moonshot',
27
+ ALIBABA: 'alibaba',
28
+ OPENAI: 'openai'
29
+ };
30
+
31
+ // Fallback storage path
32
+ const FALLBACK_FILE = path.join(os.homedir(), '.ez-credentials.json');
33
+
34
+ // Try to load keytar
35
+ let keytar = null;
36
+ try {
37
+ keytar = require('keytar');
38
+ logger.info('keytar loaded — using system keychain');
39
+ } catch (err) {
40
+ logger.warn('keytar not available — using fallback storage');
41
+ }
42
+
43
+ /**
44
+ * Save credential securely
45
+ * @param {string} provider - Provider name (anthropic, moonshot, etc.)
46
+ * @param {string} secret - API key or secret
47
+ * @returns {Promise<boolean>} - Success status
48
+ */
49
+ async function saveCredential(provider, secret) {
50
+ try {
51
+ if (keytar) {
52
+ await keytar.setPassword(SERVICE_NAME, provider, secret);
53
+ logger.info('Credential saved to system keychain', { provider });
54
+ return true;
55
+ } else {
56
+ // Fallback: save to file
57
+ let credentials = {};
58
+ if (fs.existsSync(FALLBACK_FILE)) {
59
+ const content = fs.readFileSync(FALLBACK_FILE, 'utf-8');
60
+ try {
61
+ credentials = JSON.parse(content);
62
+ } catch (e) {
63
+ credentials = {};
64
+ }
65
+ }
66
+ credentials[provider] = secret;
67
+ fs.writeFileSync(FALLBACK_FILE, JSON.stringify(credentials, null, 2), 'utf-8');
68
+ logger.warn('Credential saved to fallback file', { provider, path: FALLBACK_FILE });
69
+ return true;
70
+ }
71
+ } catch (err) {
72
+ logger.error('Failed to save credential', { provider, error: err.message });
73
+ return false;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Load credential securely
79
+ * @param {string} provider - Provider name
80
+ * @returns {Promise<string|null>} - API key or null if not found
81
+ */
82
+ async function loadCredential(provider) {
83
+ try {
84
+ if (keytar) {
85
+ const secret = await keytar.getPassword(SERVICE_NAME, provider);
86
+ if (secret) {
87
+ logger.debug('Credential loaded from keychain', { provider });
88
+ }
89
+ return secret || null;
90
+ } else {
91
+ // Fallback: load from file
92
+ if (fs.existsSync(FALLBACK_FILE)) {
93
+ const content = fs.readFileSync(FALLBACK_FILE, 'utf-8');
94
+ const credentials = JSON.parse(content);
95
+ return credentials[provider] || null;
96
+ }
97
+ return null;
98
+ }
99
+ } catch (err) {
100
+ logger.error('Failed to load credential', { provider, error: err.message });
101
+ return null;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Delete credential
107
+ * @param {string} provider - Provider name
108
+ * @returns {Promise<boolean>} - Success status
109
+ */
110
+ async function deleteCredential(provider) {
111
+ try {
112
+ if (keytar) {
113
+ await keytar.deletePassword(SERVICE_NAME, provider);
114
+ logger.info('Credential deleted from keychain', { provider });
115
+ return true;
116
+ } else {
117
+ // Fallback: remove from file
118
+ if (fs.existsSync(FALLBACK_FILE)) {
119
+ const content = fs.readFileSync(FALLBACK_FILE, 'utf-8');
120
+ const credentials = JSON.parse(content);
121
+ if (credentials[provider]) {
122
+ delete credentials[provider];
123
+ fs.writeFileSync(FALLBACK_FILE, JSON.stringify(credentials, null, 2), 'utf-8');
124
+ logger.info('Credential deleted from fallback file', { provider });
125
+ return true;
126
+ }
127
+ }
128
+ return false;
129
+ }
130
+ } catch (err) {
131
+ logger.error('Failed to delete credential', { provider, error: err.message });
132
+ return false;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * List all stored providers
138
+ * @returns {Promise<string[]>} - Array of provider names
139
+ */
140
+ async function listProviders() {
141
+ const stored = [];
142
+
143
+ if (keytar) {
144
+ for (const [, name] of Object.entries(PROVIDERS)) {
145
+ const cred = await keytar.getPassword(SERVICE_NAME, name);
146
+ if (cred) stored.push(name);
147
+ }
148
+ } else {
149
+ if (fs.existsSync(FALLBACK_FILE)) {
150
+ const content = fs.readFileSync(FALLBACK_FILE, 'utf-8');
151
+ const credentials = JSON.parse(content);
152
+ return Object.keys(credentials);
153
+ }
154
+ }
155
+
156
+ return stored;
157
+ }
158
+
159
+ /**
160
+ * Check if keytar is available
161
+ * @returns {boolean} - True if using system keychain
162
+ */
163
+ function isKeychainAvailable() {
164
+ return keytar !== null;
165
+ }
166
+
167
+ module.exports = {
168
+ saveCredential,
169
+ loadCredential,
170
+ deleteCredential,
171
+ listProviders,
172
+ isKeychainAvailable,
173
+ PROVIDERS,
174
+ SERVICE_NAME
175
+ };