gsd-opencode 1.9.2 → 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/agents/gsd-debugger.md +5 -5
  2. package/bin/gsd-install.js +105 -0
  3. package/bin/gsd.js +352 -0
  4. package/{command → commands}/gsd/add-phase.md +1 -1
  5. package/{command → commands}/gsd/audit-milestone.md +1 -1
  6. package/{command → commands}/gsd/debug.md +3 -3
  7. package/{command → commands}/gsd/discuss-phase.md +1 -1
  8. package/{command → commands}/gsd/execute-phase.md +1 -1
  9. package/{command → commands}/gsd/list-phase-assumptions.md +1 -1
  10. package/{command → commands}/gsd/map-codebase.md +1 -1
  11. package/{command → commands}/gsd/new-milestone.md +1 -1
  12. package/{command → commands}/gsd/new-project.md +3 -3
  13. package/{command → commands}/gsd/plan-phase.md +2 -2
  14. package/{command → commands}/gsd/research-phase.md +1 -1
  15. package/{command → commands}/gsd/verify-work.md +1 -1
  16. package/get-shit-done/workflows/list-phase-assumptions.md +1 -1
  17. package/get-shit-done/workflows/verify-work.md +5 -5
  18. package/lib/constants.js +193 -0
  19. package/package.json +34 -20
  20. package/src/commands/check.js +329 -0
  21. package/src/commands/config.js +337 -0
  22. package/src/commands/install.js +608 -0
  23. package/src/commands/list.js +256 -0
  24. package/src/commands/repair.js +519 -0
  25. package/src/commands/uninstall.js +732 -0
  26. package/src/commands/update.js +444 -0
  27. package/src/services/backup-manager.js +585 -0
  28. package/src/services/config.js +262 -0
  29. package/src/services/file-ops.js +830 -0
  30. package/src/services/health-checker.js +475 -0
  31. package/src/services/manifest-manager.js +301 -0
  32. package/src/services/migration-service.js +831 -0
  33. package/src/services/repair-service.js +846 -0
  34. package/src/services/scope-manager.js +303 -0
  35. package/src/services/settings.js +553 -0
  36. package/src/services/structure-detector.js +240 -0
  37. package/src/services/update-service.js +863 -0
  38. package/src/utils/hash.js +71 -0
  39. package/src/utils/interactive.js +222 -0
  40. package/src/utils/logger.js +128 -0
  41. package/src/utils/npm-registry.js +255 -0
  42. package/src/utils/path-resolver.js +226 -0
  43. /package/{command → commands}/gsd/add-todo.md +0 -0
  44. /package/{command → commands}/gsd/check-todos.md +0 -0
  45. /package/{command → commands}/gsd/complete-milestone.md +0 -0
  46. /package/{command → commands}/gsd/help.md +0 -0
  47. /package/{command → commands}/gsd/insert-phase.md +0 -0
  48. /package/{command → commands}/gsd/pause-work.md +0 -0
  49. /package/{command → commands}/gsd/plan-milestone-gaps.md +0 -0
  50. /package/{command → commands}/gsd/progress.md +0 -0
  51. /package/{command → commands}/gsd/quick.md +0 -0
  52. /package/{command → commands}/gsd/remove-phase.md +0 -0
  53. /package/{command → commands}/gsd/resume-work.md +0 -0
  54. /package/{command → commands}/gsd/set-model.md +0 -0
  55. /package/{command → commands}/gsd/set-profile.md +0 -0
  56. /package/{command → commands}/gsd/settings.md +0 -0
  57. /package/{command → commands}/gsd/update.md +0 -0
  58. /package/{command → commands}/gsd/whats-new.md +0 -0
@@ -46,7 +46,7 @@ Store resolved models for use in Task calls below.
46
46
  find .planning/phases -name "*-UAT.md" -type f 2>/dev/null | head -5
47
47
  ```
48
48
 
49
- **If active sessions exist AND no $ARGUMENTS provided:**
49
+ **If active sessions exist AND no `$ARGUMENTS` provided:**
50
50
 
51
51
  read each file's frontmatter (status, phase) and Current Test section.
52
52
 
@@ -68,12 +68,12 @@ Wait for user response.
68
68
  - If user replies with number (1, 2) → Load that file, go to `resume_from_file`
69
69
  - If user replies with phase number → Treat as new session, go to `create_uat_file`
70
70
 
71
- **If active sessions exist AND $ARGUMENTS provided:**
71
+ **If active sessions exist AND `$ARGUMENTS` provided:**
72
72
 
73
73
  Check if session exists for that phase. If yes, offer to resume or restart.
74
74
  If no, continue to `create_uat_file`.
75
75
 
76
- **If no active sessions AND no $ARGUMENTS:**
76
+ **If no active sessions AND no `$ARGUMENTS`:**
77
77
 
78
78
  ```
79
79
  No active UAT sessions.
@@ -81,7 +81,7 @@ No active UAT sessions.
81
81
  Provide a phase number to start testing (e.g., /gsd-verify-work 4)
82
82
  ```
83
83
 
84
- **If no active sessions AND $ARGUMENTS provided:**
84
+ **If no active sessions AND `$ARGUMENTS` provided:**
85
85
 
86
86
  Continue to `create_uat_file`.
87
87
  </step>
@@ -89,7 +89,7 @@ Continue to `create_uat_file`.
89
89
  <step name="find_summaries">
90
90
  **Find what to test:**
91
91
 
92
- Parse $ARGUMENTS as phase number (e.g., "4") or plan number (e.g., "04-02").
92
+ Parse `$ARGUMENTS` as phase number (e.g., "4") or plan number (e.g., "04-02").
93
93
 
94
94
  ```bash
95
95
  # Find phase directory (match both zero-padded and unpadded)
@@ -0,0 +1,193 @@
1
+ /**
2
+ * Shared constants for the GSD-OpenCode CLI.
3
+ *
4
+ * This module centralizes all configuration values, file paths, and error codes
5
+ * used throughout the CLI to ensure consistency and maintainability.
6
+ *
7
+ * All exports are immutable constants. Do not modify these values at runtime.
8
+ *
9
+ * @module constants
10
+ */
11
+
12
+ /**
13
+ * Default global configuration directory path.
14
+ * Resolved relative to user's home directory.
15
+ * @type {string}
16
+ * @example
17
+ * // On macOS/Linux: ~/.config/opencode
18
+ * // On Windows: ~\AppData\Roaming\opencode
19
+ */
20
+ export const DEFAULT_CONFIG_DIR = '.config/opencode';
21
+
22
+ /**
23
+ * Local configuration directory name.
24
+ * Created in the current working directory for project-specific configuration.
25
+ * @type {string}
26
+ * @example
27
+ * // Creates: ./.opencode/ in project root
28
+ */
29
+ export const LOCAL_CONFIG_DIR = '.opencode';
30
+
31
+ /**
32
+ * Name of the version tracking file.
33
+ * Used to store the installed version of GSD-OpenCode.
34
+ * Stored in get-shit-done/ directory since it's fully owned by gsd-opencode.
35
+ * @type {string}
36
+ */
37
+ export const VERSION_FILE = 'get-shit-done/VERSION';
38
+
39
+ /**
40
+ * Regex patterns for path replacement in markdown files.
41
+ * These patterns are used to update internal references during installation.
42
+ * @type {Object.<string, RegExp>}
43
+ */
44
+ export const PATH_PATTERNS = {
45
+ /**
46
+ * Pattern to match @gsd-opencode/ references in markdown files.
47
+ * Used for replacing package references with actual paths.
48
+ */
49
+ gsdReference: /@gsd-opencode\//g
50
+ };
51
+
52
+ /**
53
+ * Source directories to copy during installation.
54
+ * These directories contain the core GSD-OpenCode assets.
55
+ *
56
+ * All directories use the 'commands' (plural) structure consistently
57
+ * in both source and destination. The FileOperations service copies
58
+ * files directly from source to target without path transformation.
59
+ *
60
+ * @type {string[]}
61
+ */
62
+ export const DIRECTORIES_TO_COPY = ['agents', 'commands', 'get-shit-done'];
63
+
64
+ /**
65
+ * Command directory mapping for source-to-destination path transformation.
66
+ *
67
+ * Since the source package now uses 'commands/' (plural) and the destination
68
+ * also uses 'commands/' (plural), this mapping ensures consistency.
69
+ * This enables future transformations if needed.
70
+ *
71
+ * @type {Object.<string, string>}
72
+ * @example
73
+ * // During install, files from sourceDir/commands/gsd/ are copied to targetDir/commands/gsd/
74
+ * const sourceDirName = COMMAND_DIR_MAPPING[destDirName]; // 'commands'
75
+ */
76
+ export const COMMAND_DIR_MAPPING = {
77
+ 'commands': 'commands' // Both source and destination use 'commands/'
78
+ };
79
+
80
+ /**
81
+ * Name of the manifest file that tracks all installed files.
82
+ * Used for safe uninstallation with namespace protection.
83
+ * Stored in get-shit-done/ directory since it's fully owned by gsd-opencode.
84
+ * @type {string}
85
+ */
86
+ export const MANIFEST_FILENAME = 'get-shit-done/INSTALLED_FILES.json';
87
+
88
+ /**
89
+ * Directory name for uninstall backups.
90
+ * Created within the installation directory to store backups before removal.
91
+ * @type {string}
92
+ */
93
+ export const UNINSTALL_BACKUP_DIR = '.backups';
94
+
95
+ /**
96
+ * Legacy command directory name (singular).
97
+ * Used for detecting and migrating old installations.
98
+ * @type {string}
99
+ */
100
+ export const OLD_COMMAND_DIR = 'command';
101
+
102
+ /**
103
+ * New command directory name (plural).
104
+ * Used as the default for fresh installations.
105
+ * @type {string}
106
+ */
107
+ export const NEW_COMMAND_DIR = 'commands';
108
+
109
+ /**
110
+ * Structure type constants for directory structure detection.
111
+ * Used to identify which command directory structure is present.
112
+ * @type {Object.<string, string>}
113
+ */
114
+ export const STRUCTURE_TYPES = {
115
+ /** Legacy structure: command/gsd/ (singular) */
116
+ OLD: 'old',
117
+
118
+ /** New structure: commands/gsd/ (plural) */
119
+ NEW: 'new',
120
+
121
+ /** Both structures exist (dual/migration state) */
122
+ DUAL: 'dual',
123
+
124
+ /** Neither structure exists (fresh install) */
125
+ NONE: 'none'
126
+ };
127
+
128
+ /**
129
+ * Allowed namespace patterns for safe uninstallation.
130
+ * Files in these namespaces are safe to delete during uninstall.
131
+ * Files outside these namespaces are NEVER deleted, even if tracked.
132
+ *
133
+ * Patterns:
134
+ * - agents/gsd-* (gsd-opencode specific agents)
135
+ * - command/gsd/* (gsd-opencode specific commands - legacy)
136
+ * - commands/gsd/* (gsd-opencode specific commands - new)
137
+ * - skills/gsd-* (gsd-opencode specific skills)
138
+ * - get-shit-done/* (fully owned by gsd-opencode)
139
+ *
140
+ * @type {RegExp[]}
141
+ */
142
+ export const ALLOWED_NAMESPACES = [
143
+ /^agents\/gsd-/, // agents/gsd-* directories
144
+ /^command\/gsd\//, // command/gsd/* files (legacy structure)
145
+ /^commands\/gsd\//, // commands/gsd/* files (new structure)
146
+ /^skills\/gsd-/, // skills/gsd-* directories
147
+ /^get-shit-done\// // get-shit-done/ directory - fully owned
148
+ ];
149
+
150
+ /**
151
+ * Exit codes for different failure modes.
152
+ * Follows Unix convention where 0 = success, non-zero = error.
153
+ * @type {Object.<string, number>}
154
+ */
155
+ export const ERROR_CODES = {
156
+ /** Operation completed successfully. */
157
+ SUCCESS: 0,
158
+
159
+ /** General error occurred (unspecified failure). */
160
+ GENERAL_ERROR: 1,
161
+
162
+ /** Permission denied (insufficient file system permissions). */
163
+ PERMISSION_ERROR: 2,
164
+
165
+ /** Path traversal detected (security violation). */
166
+ PATH_TRAVERSAL: 3,
167
+
168
+ /** Process interrupted by user (SIGINT, Ctrl+C). */
169
+ INTERRUPTED: 130
170
+ };
171
+
172
+ /**
173
+ * Default export combining all constants.
174
+ * Useful for importing all constants at once.
175
+ *
176
+ * @example
177
+ * import constants from './lib/constants.js';
178
+ * console.log(constants.DEFAULT_CONFIG_DIR);
179
+ */
180
+ export default {
181
+ DEFAULT_CONFIG_DIR,
182
+ LOCAL_CONFIG_DIR,
183
+ VERSION_FILE,
184
+ PATH_PATTERNS,
185
+ DIRECTORIES_TO_COPY,
186
+ MANIFEST_FILENAME,
187
+ UNINSTALL_BACKUP_DIR,
188
+ OLD_COMMAND_DIR,
189
+ NEW_COMMAND_DIR,
190
+ STRUCTURE_TYPES,
191
+ ALLOWED_NAMESPACES,
192
+ ERROR_CODES
193
+ };
package/package.json CHANGED
@@ -1,14 +1,9 @@
1
1
  {
2
2
  "name": "gsd-opencode",
3
- "version": "1.9.2",
4
- "description": "A meta-prompting, context engineering and spec-driven development system for OpenCode by TÂCHES.",
5
- "keywords": [
6
- "opencode",
7
- "ai",
8
- "meta-prompting",
9
- "context-engineering",
10
- "spec-driven-development"
11
- ],
3
+ "version": "1.10.1",
4
+ "description": "GSD-OpenCode distribution manager - install, verify, and maintain your GSD-OpenCode installation",
5
+ "type": "module",
6
+ "main": "bin/gsd.js",
12
7
  "homepage": "https://github.com/rokicool/gsd-opencode#readme",
13
8
  "bugs": {
14
9
  "url": "https://github.com/rokicool/gsd-opencode/issues"
@@ -17,23 +12,42 @@
17
12
  "type": "git",
18
13
  "url": "git+https://github.com/rokicool/gsd-opencode.git"
19
14
  },
20
- "license": "MIT",
21
- "author": "TÂCHES & rokicool",
22
- "type": "commonjs",
23
- "main": "index.js",
24
15
  "bin": {
25
- "gsd-opencode": "bin/install.js"
16
+ "gsd-opencode": "bin/gsd.js",
17
+ "gsd-install": "bin/gsd-install.js"
26
18
  },
19
+ "scripts": {
20
+ "test": "vitest run",
21
+ "test:watch": "vitest"
22
+ },
23
+ "keywords": [
24
+ "opencode",
25
+ "ai",
26
+ "meta-prompting",
27
+ "context-engineering",
28
+ "spec-driven-development"
29
+ ],
30
+ "author": "TÂCHES & rokicool",
31
+ "license": "MIT",
27
32
  "files": [
28
33
  "agents",
29
34
  "bin",
30
- "command",
31
- "get-shit-done"
35
+ "commands",
36
+ "get-shit-done",
37
+ "src",
38
+ "lib"
32
39
  ],
33
- "scripts": {
34
- "test": "echo \"Error: no test specified\" && exit 1"
35
- },
36
40
  "engines": {
37
- "node": ">=16.7.0"
41
+ "node": ">=18.0.0"
42
+ },
43
+ "dependencies": {
44
+ "@iarna/toml": "^2.2.5",
45
+ "@inquirer/prompts": "^8.2.0",
46
+ "chalk": "^5.6.2",
47
+ "commander": "^12.1.0",
48
+ "ora": "^9.3.0"
49
+ },
50
+ "devDependencies": {
51
+ "vitest": "^3.0.0"
38
52
  }
39
53
  }
@@ -0,0 +1,329 @@
1
+ /**
2
+ * Check command for GSD-OpenCode CLI.
3
+ *
4
+ * This module provides the check functionality to verify installation health,
5
+ * showing detailed pass/fail results for file existence, version matching,
6
+ * and file integrity checks.
7
+ *
8
+ * Implements requirements:
9
+ * - CLI-03: User can run gsd-opencode check to verify installation health
10
+ * - CHECK-01: Check verifies all required files exist
11
+ * - CHECK-02: Check verifies installed version matches expected version
12
+ * - CHECK-03: Check detects corrupted or modified files
13
+ * - CHECK-04: Check provides clear pass/fail output for each verification
14
+ * - CHECK-05: Check returns appropriate exit codes (0 for healthy, non-zero for issues)
15
+ * - ERROR-03: All commands support --verbose flag for detailed debugging output
16
+ * - ERROR-06: CLI shows consistent branding and formatted output using colors
17
+ *
18
+ * @module check
19
+ */
20
+
21
+ import { ScopeManager } from '../services/scope-manager.js';
22
+ import { HealthChecker } from '../services/health-checker.js';
23
+ import { logger, setVerbose } from '../utils/logger.js';
24
+ import { ERROR_CODES } from '../../lib/constants.js';
25
+ import fs from 'fs/promises';
26
+ import path from 'path';
27
+ import { fileURLToPath } from 'url';
28
+
29
+ /**
30
+ * Gets the package version from package.json.
31
+ *
32
+ * @returns {Promise<string>} The package version
33
+ * @private
34
+ */
35
+ async function getPackageVersion() {
36
+ try {
37
+ const __filename = fileURLToPath(import.meta.url);
38
+ const __dirname = path.dirname(__filename);
39
+ const packageRoot = path.resolve(__dirname, '../..');
40
+ const packageJsonPath = path.join(packageRoot, 'package.json');
41
+
42
+ const content = await fs.readFile(packageJsonPath, 'utf-8');
43
+ const pkg = JSON.parse(content);
44
+ return pkg.version || '1.0.0';
45
+ } catch (error) {
46
+ logger.warning('Could not read package version, using 1.0.0');
47
+ return '1.0.0';
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Displays health check results with pass/fail indicators.
53
+ *
54
+ * @param {Object} results - Health check results from HealthChecker
55
+ * @param {string} scopeLabel - Label for this scope
56
+ * @private
57
+ */
58
+ function displayCheckResults(results, scopeLabel) {
59
+ logger.heading(`${scopeLabel} Installation Health`);
60
+ logger.dim('================================');
61
+
62
+ if (!results) {
63
+ logger.info('Not installed');
64
+ logger.dim('');
65
+ return;
66
+ }
67
+
68
+ const { categories } = results;
69
+
70
+ // Files check section
71
+ logger.dim('');
72
+ logger.info('Required Files');
73
+ if (categories.files && categories.files.checks) {
74
+ for (const check of categories.files.checks) {
75
+ const status = check.passed ? 'OK' : (check.error || 'Missing');
76
+ if (check.passed) {
77
+ logger.success(`${check.name}: ${status}`);
78
+ } else {
79
+ logger.error(`${check.name}: ${status}`);
80
+ }
81
+ }
82
+ }
83
+
84
+ // Version check section
85
+ if (categories.version) {
86
+ logger.dim('');
87
+ logger.info('Version Verification');
88
+ const versionCheck = categories.version.checks[0];
89
+ if (versionCheck.passed) {
90
+ logger.success(`Version: ${versionCheck.installed} - OK`);
91
+ } else {
92
+ const errorMsg = versionCheck.error
93
+ ? `Version check failed - ${versionCheck.error}`
94
+ : `Version mismatch (installed: ${versionCheck.installed || 'none'}, expected: ${versionCheck.expected})`;
95
+ logger.error(errorMsg);
96
+ }
97
+ }
98
+
99
+ // Integrity check section
100
+ logger.dim('');
101
+ logger.info('File Integrity');
102
+ if (categories.integrity && categories.integrity.checks) {
103
+ for (const check of categories.integrity.checks) {
104
+ const relativePath = check.relative || path.basename(check.file);
105
+ const message = check.passed
106
+ ? `${relativePath} - OK`
107
+ : `${relativePath} - ${check.error || 'Corrupted or missing'}`;
108
+
109
+ if (check.passed) {
110
+ logger.success(message);
111
+ } else {
112
+ logger.error(message);
113
+ }
114
+ }
115
+ }
116
+
117
+ // Structure check section (NEW)
118
+ if (categories.structure) {
119
+ logger.dim('');
120
+ logger.info('Directory Structure');
121
+ const structure = categories.structure;
122
+ const statusText = structure.label;
123
+
124
+ if (structure.type === 'dual') {
125
+ logger.error(`${statusText} - Action required`);
126
+ logger.dim(' Both old (command/gsd/) and new (commands/gsd/) structures detected.');
127
+ logger.dim(' This can happen if an update was interrupted.');
128
+ logger.dim(" Run 'gsd-opencode update' to complete migration");
129
+ } else if (structure.needsMigration) {
130
+ logger.warning(`${statusText} - Migration recommended`);
131
+ logger.dim(" Run 'gsd-opencode update' to migrate to new structure");
132
+ } else if (structure.type === 'new') {
133
+ logger.success(`${statusText} - OK`);
134
+ } else if (structure.type === 'none') {
135
+ logger.info(`${statusText}`);
136
+ } else {
137
+ logger.info(statusText);
138
+ }
139
+ }
140
+
141
+ // Overall status
142
+ logger.dim('');
143
+ if (results.passed) {
144
+ logger.success('All checks passed - Installation is healthy');
145
+ } else {
146
+ logger.error('Some checks failed - Issues detected');
147
+ }
148
+ logger.dim('');
149
+ }
150
+
151
+ /**
152
+ * Checks health for a single scope.
153
+ *
154
+ * @param {string} scope - 'global' or 'local'
155
+ * @param {Object} options - Options
156
+ * @param {boolean} options.verbose - Enable verbose output
157
+ * @returns {Promise<Object>} Health check results
158
+ * @private
159
+ */
160
+ async function checkScope(scope, options = {}) {
161
+ const scopeManager = new ScopeManager({ scope });
162
+ const scopeLabel = scope.charAt(0).toUpperCase() + scope.slice(1);
163
+
164
+ logger.debug(`Checking ${scope} installation...`);
165
+
166
+ const isInstalled = await scopeManager.isInstalled();
167
+ if (!isInstalled) {
168
+ logger.debug(`No ${scope} installation found`);
169
+ return {
170
+ installed: false,
171
+ scope,
172
+ scopeLabel,
173
+ results: null,
174
+ passed: false
175
+ };
176
+ }
177
+
178
+ logger.debug(`${scope} installation detected, running health checks...`);
179
+
180
+ const healthChecker = new HealthChecker(scopeManager);
181
+ const expectedVersion = await getPackageVersion();
182
+
183
+ try {
184
+ const results = await healthChecker.checkAll({
185
+ expectedVersion,
186
+ verbose: options.verbose
187
+ });
188
+
189
+ return {
190
+ installed: true,
191
+ scope,
192
+ scopeLabel,
193
+ results,
194
+ passed: results.passed
195
+ };
196
+ } catch (error) {
197
+ logger.debug(`Error during health check: ${error.message}`);
198
+ return {
199
+ installed: true,
200
+ scope,
201
+ scopeLabel,
202
+ results: null,
203
+ passed: false,
204
+ error: error.message
205
+ };
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Handles errors with helpful messages.
211
+ *
212
+ * @param {Error} error - The error to handle
213
+ * @param {boolean} verbose - Whether verbose mode is enabled
214
+ * @returns {number} Exit code for the error
215
+ * @private
216
+ */
217
+ function handleError(error, verbose) {
218
+ if (verbose) {
219
+ logger.debug(`Error details: ${error.stack || error.message}`);
220
+ logger.debug(`Error code: ${error.code}`);
221
+ }
222
+
223
+ if (error.code === 'EACCES') {
224
+ logger.error('Permission denied: Cannot access installation directory');
225
+ logger.dim('');
226
+ logger.dim('Suggestion: Check directory permissions or run with appropriate privileges.');
227
+ return ERROR_CODES.PERMISSION_ERROR;
228
+ }
229
+
230
+ logger.error(`Failed to check installation health: ${error.message}`);
231
+
232
+ if (!verbose) {
233
+ logger.dim('');
234
+ logger.dim('Suggestion: Run with --verbose for detailed error information');
235
+ }
236
+
237
+ return ERROR_CODES.GENERAL_ERROR;
238
+ }
239
+
240
+ /**
241
+ * Main check command function.
242
+ *
243
+ * @param {Object} options - Command options
244
+ * @param {boolean} [options.global] - Check global installation only
245
+ * @param {boolean} [options.local] - Check local installation only
246
+ * @param {boolean} [options.verbose] - Enable verbose output
247
+ * @returns {Promise<number>} Exit code
248
+ */
249
+ export async function checkCommand(options = {}) {
250
+ const verbose = options.verbose || false;
251
+ setVerbose(verbose);
252
+
253
+ logger.debug('Starting check command');
254
+ logger.debug(`Options: global=${options.global}, local=${options.local}, verbose=${verbose}`);
255
+
256
+ try {
257
+ logger.heading('GSD-OpenCode Installation Health');
258
+ logger.dim('================================');
259
+
260
+ const scopesToCheck = [];
261
+ if (options.global) {
262
+ scopesToCheck.push('global');
263
+ } else if (options.local) {
264
+ scopesToCheck.push('local');
265
+ } else {
266
+ scopesToCheck.push('global', 'local');
267
+ }
268
+
269
+ let anyInstalled = false;
270
+ let allPassed = true;
271
+
272
+ for (const scope of scopesToCheck) {
273
+ try {
274
+ const result = await checkScope(scope, options);
275
+
276
+ if (result.installed) {
277
+ anyInstalled = true;
278
+ if (!result.passed) {
279
+ allPassed = false;
280
+ }
281
+ }
282
+
283
+ if (scopesToCheck.length > 1) {
284
+ logger.dim('');
285
+ }
286
+
287
+ displayCheckResults(result.results, result.scopeLabel);
288
+
289
+ if (verbose && result.error) {
290
+ logger.debug(`Error checking ${scope}: ${result.error}`);
291
+ }
292
+ } catch (error) {
293
+ logger.error(`Failed to check ${scope} installation: ${error.message}`);
294
+ allPassed = false;
295
+
296
+ if (verbose) {
297
+ logger.debug(error.stack);
298
+ }
299
+ }
300
+ }
301
+
302
+ if (!anyInstalled) {
303
+ logger.dim('');
304
+ logger.dim('Not installed anywhere');
305
+ logger.dim('');
306
+ logger.info("Run 'gsd-opencode install' to install");
307
+ logger.dim('');
308
+ return ERROR_CODES.SUCCESS;
309
+ }
310
+
311
+ const exitCode = allPassed ? ERROR_CODES.SUCCESS : ERROR_CODES.GENERAL_ERROR;
312
+
313
+ if (verbose) {
314
+ logger.debug(`Check complete. Exit code: ${exitCode}`);
315
+ }
316
+
317
+ return exitCode;
318
+
319
+ } catch (error) {
320
+ if (error.name === 'AbortPromptError' || error.message?.includes('cancel')) {
321
+ logger.info('Command cancelled by user');
322
+ return ERROR_CODES.INTERRUPTED;
323
+ }
324
+
325
+ return handleError(error, verbose);
326
+ }
327
+ }
328
+
329
+ export default checkCommand;