@howlil/ez-agents 2.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.
- package/LICENSE +21 -0
- package/README.md +845 -0
- package/README.zh-CN.md +702 -0
- package/agents/ez-codebase-mapper.md +770 -0
- package/agents/ez-debugger.md +1255 -0
- package/agents/ez-executor.md +487 -0
- package/agents/ez-integration-checker.md +443 -0
- package/agents/ez-nyquist-auditor.md +176 -0
- package/agents/ez-phase-researcher.md +553 -0
- package/agents/ez-plan-checker.md +706 -0
- package/agents/ez-planner.md +1307 -0
- package/agents/ez-project-researcher.md +629 -0
- package/agents/ez-research-synthesizer.md +247 -0
- package/agents/ez-roadmapper.md +650 -0
- package/agents/ez-ui-auditor.md +441 -0
- package/agents/ez-ui-checker.md +302 -0
- package/agents/ez-ui-researcher.md +355 -0
- package/agents/ez-verifier.md +579 -0
- package/bin/install.js +2862 -0
- package/bin/update.js +214 -0
- package/commands/ez/add-phase.md +43 -0
- package/commands/ez/add-tests.md +41 -0
- package/commands/ez/add-todo.md +47 -0
- package/commands/ez/audit-milestone.md +36 -0
- package/commands/ez/autonomous.md +41 -0
- package/commands/ez/check-todos.md +45 -0
- package/commands/ez/cleanup.md +18 -0
- package/commands/ez/complete-milestone.md +136 -0
- package/commands/ez/debug.md +168 -0
- package/commands/ez/discuss-phase.md +90 -0
- package/commands/ez/execute-phase.md +41 -0
- package/commands/ez/health.md +22 -0
- package/commands/ez/help.md +22 -0
- package/commands/ez/insert-phase.md +32 -0
- package/commands/ez/join-discord.md +18 -0
- package/commands/ez/list-phase-assumptions.md +46 -0
- package/commands/ez/map-codebase.md +71 -0
- package/commands/ez/new-milestone.md +44 -0
- package/commands/ez/new-project.md +42 -0
- package/commands/ez/pause-work.md +38 -0
- package/commands/ez/plan-milestone-gaps.md +34 -0
- package/commands/ez/plan-phase.md +45 -0
- package/commands/ez/progress.md +24 -0
- package/commands/ez/quick.md +45 -0
- package/commands/ez/reapply-patches.md +124 -0
- package/commands/ez/remove-phase.md +31 -0
- package/commands/ez/research-phase.md +190 -0
- package/commands/ez/resume-work.md +40 -0
- package/commands/ez/set-profile.md +34 -0
- package/commands/ez/settings.md +36 -0
- package/commands/ez/stats.md +18 -0
- package/commands/ez/ui-phase.md +34 -0
- package/commands/ez/ui-review.md +32 -0
- package/commands/ez/update.md +37 -0
- package/commands/ez/validate-phase.md +35 -0
- package/commands/ez/verify-work.md +38 -0
- package/get-shit-done/bin/ez-tools.cjs +598 -0
- package/get-shit-done/bin/lib/assistant-adapter.cjs +205 -0
- package/get-shit-done/bin/lib/audit-exec.cjs +150 -0
- package/get-shit-done/bin/lib/auth.cjs +175 -0
- package/get-shit-done/bin/lib/circuit-breaker.cjs +118 -0
- package/get-shit-done/bin/lib/commands.cjs +666 -0
- package/get-shit-done/bin/lib/config.cjs +183 -0
- package/get-shit-done/bin/lib/core.cjs +495 -0
- package/get-shit-done/bin/lib/file-lock.cjs +236 -0
- package/get-shit-done/bin/lib/frontmatter.cjs +299 -0
- package/get-shit-done/bin/lib/fs-utils.cjs +153 -0
- package/get-shit-done/bin/lib/git-utils.cjs +203 -0
- package/get-shit-done/bin/lib/health-check.cjs +163 -0
- package/get-shit-done/bin/lib/index.cjs +113 -0
- package/get-shit-done/bin/lib/init.cjs +710 -0
- package/get-shit-done/bin/lib/logger.cjs +117 -0
- package/get-shit-done/bin/lib/milestone.cjs +241 -0
- package/get-shit-done/bin/lib/model-provider.cjs +146 -0
- package/get-shit-done/bin/lib/phase.cjs +908 -0
- package/get-shit-done/bin/lib/retry.cjs +119 -0
- package/get-shit-done/bin/lib/roadmap.cjs +305 -0
- package/get-shit-done/bin/lib/safe-exec.cjs +128 -0
- package/get-shit-done/bin/lib/safe-path.cjs +130 -0
- package/get-shit-done/bin/lib/state.cjs +721 -0
- package/get-shit-done/bin/lib/temp-file.cjs +239 -0
- package/get-shit-done/bin/lib/template.cjs +222 -0
- package/get-shit-done/bin/lib/test-file-lock.cjs +112 -0
- package/get-shit-done/bin/lib/test-graceful.cjs +93 -0
- package/get-shit-done/bin/lib/test-logger.cjs +60 -0
- package/get-shit-done/bin/lib/test-safe-exec.cjs +38 -0
- package/get-shit-done/bin/lib/test-safe-path.cjs +33 -0
- package/get-shit-done/bin/lib/test-temp-file.cjs +125 -0
- package/get-shit-done/bin/lib/timeout-exec.cjs +62 -0
- package/get-shit-done/bin/lib/verify.cjs +820 -0
- package/get-shit-done/references/checkpoints.md +776 -0
- package/get-shit-done/references/continuation-format.md +249 -0
- package/get-shit-done/references/decimal-phase-calculation.md +65 -0
- package/get-shit-done/references/git-integration.md +248 -0
- package/get-shit-done/references/git-planning-commit.md +38 -0
- package/get-shit-done/references/model-profile-resolution.md +34 -0
- package/get-shit-done/references/model-profiles.md +93 -0
- package/get-shit-done/references/phase-argument-parsing.md +61 -0
- package/get-shit-done/references/planning-config.md +200 -0
- package/get-shit-done/references/questioning.md +162 -0
- package/get-shit-done/references/tdd.md +263 -0
- package/get-shit-done/references/ui-brand.md +160 -0
- package/get-shit-done/references/verification-patterns.md +612 -0
- package/get-shit-done/templates/DEBUG.md +164 -0
- package/get-shit-done/templates/UAT.md +247 -0
- package/get-shit-done/templates/UI-SPEC.md +100 -0
- package/get-shit-done/templates/VALIDATION.md +76 -0
- package/get-shit-done/templates/codebase/architecture.md +255 -0
- package/get-shit-done/templates/codebase/concerns.md +310 -0
- package/get-shit-done/templates/codebase/conventions.md +307 -0
- package/get-shit-done/templates/codebase/integrations.md +280 -0
- package/get-shit-done/templates/codebase/stack.md +186 -0
- package/get-shit-done/templates/codebase/structure.md +285 -0
- package/get-shit-done/templates/codebase/testing.md +480 -0
- package/get-shit-done/templates/config.json +37 -0
- package/get-shit-done/templates/context.md +352 -0
- package/get-shit-done/templates/continue-here.md +78 -0
- package/get-shit-done/templates/copilot-instructions.md +7 -0
- package/get-shit-done/templates/debug-subagent-prompt.md +91 -0
- package/get-shit-done/templates/discovery.md +146 -0
- package/get-shit-done/templates/milestone-archive.md +123 -0
- package/get-shit-done/templates/milestone.md +115 -0
- package/get-shit-done/templates/phase-prompt.md +610 -0
- package/get-shit-done/templates/planner-subagent-prompt.md +117 -0
- package/get-shit-done/templates/project.md +184 -0
- package/get-shit-done/templates/requirements.md +231 -0
- package/get-shit-done/templates/research-project/ARCHITECTURE.md +204 -0
- package/get-shit-done/templates/research-project/FEATURES.md +147 -0
- package/get-shit-done/templates/research-project/PITFALLS.md +200 -0
- package/get-shit-done/templates/research-project/STACK.md +120 -0
- package/get-shit-done/templates/research-project/SUMMARY.md +170 -0
- package/get-shit-done/templates/research.md +552 -0
- package/get-shit-done/templates/retrospective.md +54 -0
- package/get-shit-done/templates/roadmap.md +202 -0
- package/get-shit-done/templates/state.md +176 -0
- package/get-shit-done/templates/summary-complex.md +59 -0
- package/get-shit-done/templates/summary-minimal.md +41 -0
- package/get-shit-done/templates/summary-standard.md +48 -0
- package/get-shit-done/templates/summary.md +248 -0
- package/get-shit-done/templates/user-setup.md +311 -0
- package/get-shit-done/templates/verification-report.md +322 -0
- package/get-shit-done/workflows/add-phase.md +112 -0
- package/get-shit-done/workflows/add-tests.md +351 -0
- package/get-shit-done/workflows/add-todo.md +158 -0
- package/get-shit-done/workflows/audit-milestone.md +332 -0
- package/get-shit-done/workflows/autonomous.md +743 -0
- package/get-shit-done/workflows/check-todos.md +177 -0
- package/get-shit-done/workflows/cleanup.md +152 -0
- package/get-shit-done/workflows/complete-milestone.md +766 -0
- package/get-shit-done/workflows/diagnose-issues.md +219 -0
- package/get-shit-done/workflows/discovery-phase.md +289 -0
- package/get-shit-done/workflows/discuss-phase.md +762 -0
- package/get-shit-done/workflows/execute-phase.md +468 -0
- package/get-shit-done/workflows/execute-plan.md +483 -0
- package/get-shit-done/workflows/health.md +159 -0
- package/get-shit-done/workflows/help.md +492 -0
- package/get-shit-done/workflows/insert-phase.md +130 -0
- package/get-shit-done/workflows/list-phase-assumptions.md +178 -0
- package/get-shit-done/workflows/map-codebase.md +316 -0
- package/get-shit-done/workflows/new-milestone.md +384 -0
- package/get-shit-done/workflows/new-project.md +1111 -0
- package/get-shit-done/workflows/node-repair.md +92 -0
- package/get-shit-done/workflows/pause-work.md +122 -0
- package/get-shit-done/workflows/plan-milestone-gaps.md +274 -0
- package/get-shit-done/workflows/plan-phase.md +651 -0
- package/get-shit-done/workflows/progress.md +382 -0
- package/get-shit-done/workflows/quick.md +610 -0
- package/get-shit-done/workflows/remove-phase.md +155 -0
- package/get-shit-done/workflows/research-phase.md +74 -0
- package/get-shit-done/workflows/resume-project.md +307 -0
- package/get-shit-done/workflows/set-profile.md +81 -0
- package/get-shit-done/workflows/settings.md +242 -0
- package/get-shit-done/workflows/stats.md +57 -0
- package/get-shit-done/workflows/transition.md +544 -0
- package/get-shit-done/workflows/ui-phase.md +290 -0
- package/get-shit-done/workflows/ui-review.md +157 -0
- package/get-shit-done/workflows/update.md +320 -0
- package/get-shit-done/workflows/validate-phase.md +167 -0
- package/get-shit-done/workflows/verify-phase.md +243 -0
- package/get-shit-done/workflows/verify-work.md +584 -0
- package/package.json +55 -0
- package/scripts/build-hooks.js +43 -0
- package/scripts/run-tests.cjs +29 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GSD Assistant Adapters — Unified interface for AI coding assistants
|
|
5
|
+
*
|
|
6
|
+
* Adapters for: Claude Code, OpenCode, Gemini CLI, Codex
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const { createAdapter } = require('./assistant-adapter.cjs');
|
|
10
|
+
* const adapter = createAdapter('claude-code');
|
|
11
|
+
* await adapter.spawnAgent('planner', { prompt: '...' });
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const Logger = require('./logger.cjs');
|
|
15
|
+
const logger = new Logger();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Base adapter interface
|
|
19
|
+
*/
|
|
20
|
+
class AssistantAdapter {
|
|
21
|
+
constructor(name) {
|
|
22
|
+
if (new.target === AssistantAdapter) {
|
|
23
|
+
throw new Error('AssistantAdapter is abstract - use a concrete subclass');
|
|
24
|
+
}
|
|
25
|
+
this.name = name;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Spawn a subagent
|
|
30
|
+
* @param {string} type - Agent type
|
|
31
|
+
* @param {Object} options - Agent options
|
|
32
|
+
* @returns {Promise<Object>} - Agent result
|
|
33
|
+
*/
|
|
34
|
+
async spawnAgent(type, options) {
|
|
35
|
+
throw new Error('spawnAgent must be implemented');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Call a tool/function
|
|
40
|
+
* @param {string} tool - Tool name
|
|
41
|
+
* @param {Object} params - Tool parameters
|
|
42
|
+
* @returns {Promise<any>} - Tool result
|
|
43
|
+
*/
|
|
44
|
+
async callTool(tool, params) {
|
|
45
|
+
throw new Error('callTool must be implemented');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Select model for task
|
|
50
|
+
* @param {string} taskType - Task type (planning, execution, verification)
|
|
51
|
+
* @returns {string} - Model name
|
|
52
|
+
*/
|
|
53
|
+
selectModel(taskType) {
|
|
54
|
+
throw new Error('selectModel must be implemented');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get adapter info
|
|
59
|
+
* @returns {Object} - Adapter information
|
|
60
|
+
*/
|
|
61
|
+
getInfo() {
|
|
62
|
+
return {
|
|
63
|
+
name: this.name,
|
|
64
|
+
type: this.constructor.name
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Claude Code adapter
|
|
71
|
+
*/
|
|
72
|
+
class ClaudeCodeAdapter extends AssistantAdapter {
|
|
73
|
+
constructor() {
|
|
74
|
+
super('claude-code');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async spawnAgent(type, options) {
|
|
78
|
+
logger.info('Claude Code: spawning agent', { type });
|
|
79
|
+
// Would use Claude Code's Task tool in production
|
|
80
|
+
return { type, status: 'completed', result: '[Claude Code agent result]' };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async callTool(tool, params) {
|
|
84
|
+
logger.info('Claude Code: calling tool', { tool });
|
|
85
|
+
// Would use Claude Code's tool system
|
|
86
|
+
return { tool, status: 'success' };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
selectModel(taskType) {
|
|
90
|
+
const models = {
|
|
91
|
+
planning: 'claude-3-opus',
|
|
92
|
+
execution: 'claude-3-sonnet',
|
|
93
|
+
verification: 'claude-3-sonnet'
|
|
94
|
+
};
|
|
95
|
+
return models[taskType] || models.execution;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* OpenCode adapter
|
|
101
|
+
*/
|
|
102
|
+
class OpenCodeAdapter extends AssistantAdapter {
|
|
103
|
+
constructor() {
|
|
104
|
+
super('opencode');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async spawnAgent(type, options) {
|
|
108
|
+
logger.info('OpenCode: spawning agent', { type });
|
|
109
|
+
return { type, status: 'completed', result: '[OpenCode agent result]' };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async callTool(tool, params) {
|
|
113
|
+
logger.info('OpenCode: calling tool', { tool });
|
|
114
|
+
return { tool, status: 'success' };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
selectModel(taskType) {
|
|
118
|
+
return 'gpt-4-turbo';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Gemini CLI adapter
|
|
124
|
+
*/
|
|
125
|
+
class GeminiAdapter extends AssistantAdapter {
|
|
126
|
+
constructor() {
|
|
127
|
+
super('gemini');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async spawnAgent(type, options) {
|
|
131
|
+
logger.info('Gemini: spawning agent', { type });
|
|
132
|
+
return { type, status: 'completed', result: '[Gemini agent result]' };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async callTool(tool, params) {
|
|
136
|
+
logger.info('Gemini: calling tool', { tool });
|
|
137
|
+
return { tool, status: 'success' };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
selectModel(taskType) {
|
|
141
|
+
return 'gemini-pro';
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Codex adapter
|
|
147
|
+
*/
|
|
148
|
+
class CodexAdapter extends AssistantAdapter {
|
|
149
|
+
constructor() {
|
|
150
|
+
super('codex');
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async spawnAgent(type, options) {
|
|
154
|
+
logger.info('Codex: spawning agent', { type });
|
|
155
|
+
return { type, status: 'completed', result: '[Codex agent result]' };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async callTool(tool, params) {
|
|
159
|
+
logger.info('Codex: calling tool', { tool });
|
|
160
|
+
return { tool, status: 'success' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
selectModel(taskType) {
|
|
164
|
+
return 'codex-latest';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Factory function to create adapter
|
|
170
|
+
* @param {string} type - Adapter type
|
|
171
|
+
* @returns {AssistantAdapter} - Adapter instance
|
|
172
|
+
*/
|
|
173
|
+
function createAdapter(type) {
|
|
174
|
+
const adapters = {
|
|
175
|
+
'claude-code': ClaudeCodeAdapter,
|
|
176
|
+
'opencode': OpenCodeAdapter,
|
|
177
|
+
'gemini': GeminiAdapter,
|
|
178
|
+
'codex': CodexAdapter
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const AdapterClass = adapters[type];
|
|
182
|
+
if (!AdapterClass) {
|
|
183
|
+
throw new Error(`Unknown adapter type: ${type}. Available: ${Object.keys(adapters).join(', ')}`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return new AdapterClass();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get available adapters
|
|
191
|
+
* @returns {string[]} - List of adapter names
|
|
192
|
+
*/
|
|
193
|
+
function getAvailableAdapters() {
|
|
194
|
+
return ['claude-code', 'opencode', 'gemini', 'codex'];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = {
|
|
198
|
+
AssistantAdapter,
|
|
199
|
+
ClaudeCodeAdapter,
|
|
200
|
+
OpenCodeAdapter,
|
|
201
|
+
GeminiAdapter,
|
|
202
|
+
CodexAdapter,
|
|
203
|
+
createAdapter,
|
|
204
|
+
getAvailableAdapters
|
|
205
|
+
};
|
|
@@ -0,0 +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
|
+
};
|
|
@@ -0,0 +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
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GSD Circuit Breaker — Prevent cascading failures
|
|
5
|
+
*
|
|
6
|
+
* Implements circuit breaker pattern:
|
|
7
|
+
* - CLOSED: Normal operation
|
|
8
|
+
* - OPEN: Failing, reject requests
|
|
9
|
+
* - HALF_OPEN: Testing if service recovered
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* const CircuitBreaker = require('./circuit-breaker.cjs');
|
|
13
|
+
* const breaker = new CircuitBreaker({ failureThreshold: 5 });
|
|
14
|
+
* const result = await breaker.execute(() => riskyOperation());
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const Logger = require('./logger.cjs');
|
|
18
|
+
const logger = new Logger();
|
|
19
|
+
|
|
20
|
+
class CircuitBreaker {
|
|
21
|
+
/**
|
|
22
|
+
* Create circuit breaker
|
|
23
|
+
* @param {Object} options - Configuration
|
|
24
|
+
*/
|
|
25
|
+
constructor(options = {}) {
|
|
26
|
+
this.failureThreshold = options.failureThreshold || 5;
|
|
27
|
+
this.resetTimeout = options.resetTimeout || 60000;
|
|
28
|
+
this.state = 'CLOSED';
|
|
29
|
+
this.failures = 0;
|
|
30
|
+
this.lastFailureTime = null;
|
|
31
|
+
this.successes = 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Execute operation with circuit breaker protection
|
|
36
|
+
* @param {Function} operation - Async function to execute
|
|
37
|
+
* @returns {Promise<any>} - Result of operation
|
|
38
|
+
*/
|
|
39
|
+
async execute(operation) {
|
|
40
|
+
// Check if circuit is OPEN
|
|
41
|
+
if (this.state === 'OPEN') {
|
|
42
|
+
const timeSinceFailure = Date.now() - this.lastFailureTime;
|
|
43
|
+
|
|
44
|
+
if (timeSinceFailure > this.resetTimeout) {
|
|
45
|
+
// Try to recover
|
|
46
|
+
this.state = 'HALF_OPEN';
|
|
47
|
+
this.failures = 0;
|
|
48
|
+
logger.info('Circuit breaker HALF_OPEN - testing recovery');
|
|
49
|
+
} else {
|
|
50
|
+
const waitTime = Math.round((this.resetTimeout - timeSinceFailure) / 1000);
|
|
51
|
+
logger.warn('Circuit breaker OPEN - rejecting request', { waitTime });
|
|
52
|
+
throw new Error(`Circuit breaker is OPEN. Try again in ${waitTime}s`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const result = await operation();
|
|
58
|
+
|
|
59
|
+
// Success - reset counters if in HALF_OPEN
|
|
60
|
+
if (this.state === 'HALF_OPEN') {
|
|
61
|
+
this.state = 'CLOSED';
|
|
62
|
+
this.failures = 0;
|
|
63
|
+
logger.info('Circuit breaker CLOSED - service recovered');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.successes++;
|
|
67
|
+
return result;
|
|
68
|
+
} catch (err) {
|
|
69
|
+
this.failures++;
|
|
70
|
+
this.lastFailureTime = Date.now();
|
|
71
|
+
|
|
72
|
+
// Open circuit if threshold reached
|
|
73
|
+
if (this.failures >= this.failureThreshold) {
|
|
74
|
+
this.state = 'OPEN';
|
|
75
|
+
logger.error('Circuit breaker OPEN - failure threshold reached', {
|
|
76
|
+
failures: this.failures
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Get current state
|
|
86
|
+
* @returns {string} - CLOSED, OPEN, or HALF_OPEN
|
|
87
|
+
*/
|
|
88
|
+
getState() {
|
|
89
|
+
return this.state;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get stats
|
|
94
|
+
* @returns {Object} - Statistics
|
|
95
|
+
*/
|
|
96
|
+
getStats() {
|
|
97
|
+
return {
|
|
98
|
+
state: this.state,
|
|
99
|
+
failures: this.failures,
|
|
100
|
+
successes: this.successes,
|
|
101
|
+
failureThreshold: this.failureThreshold,
|
|
102
|
+
lastFailureTime: this.lastFailureTime
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Reset circuit breaker
|
|
108
|
+
*/
|
|
109
|
+
reset() {
|
|
110
|
+
this.state = 'CLOSED';
|
|
111
|
+
this.failures = 0;
|
|
112
|
+
this.successes = 0;
|
|
113
|
+
this.lastFailureTime = null;
|
|
114
|
+
logger.info('Circuit breaker reset');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = CircuitBreaker;
|