gsd-opencode 1.20.2 → 1.20.4

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 (27) hide show
  1. package/commands/gsd/gsd-check-profile.md +30 -0
  2. package/get-shit-done/bin/gsd-oc-commands/allow-read-config.cjs +235 -0
  3. package/get-shit-done/bin/gsd-oc-commands/check-oc-config-json.cjs +169 -0
  4. package/get-shit-done/bin/gsd-oc-commands/check-opencode-json.cjs +86 -0
  5. package/get-shit-done/bin/gsd-oc-commands/get-profile.cjs +117 -0
  6. package/get-shit-done/bin/gsd-oc-commands/set-profile.cjs +357 -0
  7. package/get-shit-done/bin/gsd-oc-commands/update-opencode-json.cjs +199 -0
  8. package/get-shit-done/bin/gsd-oc-commands/validate-models.cjs +75 -0
  9. package/get-shit-done/bin/gsd-oc-lib/oc-config.cjs +205 -0
  10. package/get-shit-done/bin/gsd-oc-lib/oc-core.cjs +113 -0
  11. package/get-shit-done/bin/gsd-oc-lib/oc-models.cjs +133 -0
  12. package/get-shit-done/bin/gsd-oc-lib/oc-profile-config.cjs +409 -0
  13. package/get-shit-done/bin/gsd-oc-tools.cjs +136 -0
  14. package/get-shit-done/bin/lib/oc-config.cjs +200 -0
  15. package/get-shit-done/bin/lib/oc-core.cjs +114 -0
  16. package/get-shit-done/bin/lib/oc-models.cjs +133 -0
  17. package/get-shit-done/bin/test/allow-read-config.test.cjs +262 -0
  18. package/get-shit-done/bin/test/fixtures/oc-config-invalid.json +14 -0
  19. package/get-shit-done/bin/test/fixtures/oc-config-valid.json +22 -0
  20. package/get-shit-done/bin/test/get-profile.test.cjs +447 -0
  21. package/get-shit-done/bin/test/oc-profile-config.test.cjs +377 -0
  22. package/get-shit-done/bin/test/pivot-profile.test.cjs +276 -0
  23. package/get-shit-done/bin/test/set-profile.test.cjs +301 -0
  24. package/get-shit-done/workflows/oc-check-profile.md +181 -0
  25. package/get-shit-done/workflows/oc-set-profile.md +98 -234
  26. package/get-shit-done/workflows/settings.md +4 -3
  27. package/package.json +2 -2
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: gsd-check-profile
3
+ description: Validate gsd-opencode profile configuration
4
+ allowed-tools:
5
+ - read
6
+ - bash
7
+ ---
8
+ <objective>
9
+ Validate gsd-opencode profile configuration across both `opencode.json` and `.planning/oc_config.json`, then report results.
10
+
11
+ Routes to the oc-check-profile workflow which handles:
12
+ - Validating all agent model IDs exist in the opencode models catalog
13
+ - Validating gsd-opencode profile structure and current profile exists
14
+ - Reporting results with severity classification (OK/WARNING/ERROR)
15
+ - Recommending /gsd-set-profile when issues are found
16
+ </objective>
17
+
18
+ <execution_context>
19
+ @~/.config/opencode/get-shit-done/workflows/oc-check-profile.md
20
+ </execution_context>
21
+
22
+ <process>
23
+ **Follow the oc-check-profile workflow** from `@~/.config/opencode/get-shit-done/workflows/oc-check-profile.md`.
24
+
25
+ The workflow handles all logic including:
26
+ 1. Running both validations (check-opencode-json and check-config-json)
27
+ 2. Classifying results by severity (OK/WARNING/ERROR)
28
+ 3. Reporting results with structured diagnostic output
29
+ 4. Recommending /gsd-set-profile when errors are found
30
+ </process>
@@ -0,0 +1,235 @@
1
+ /**
2
+ * allow-read-config.cjs — Add external_directory permission to read GSD config folder
3
+ *
4
+ * Creates or updates local opencode.json with permission to access:
5
+ * ~/.config/opencode/get-shit-done/
6
+ *
7
+ * This allows gsd-opencode commands to read workflow files, templates, and
8
+ * configuration from the global GSD installation directory.
9
+ *
10
+ * Usage:
11
+ * node allow-read-config.cjs # Add read permission
12
+ * node allow-read-config.cjs --dry-run # Preview changes
13
+ * node allow-read-config.cjs --verbose # Verbose output
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const os = require('os');
19
+ const { output, error, createBackup } = require('../gsd-oc-lib/oc-core.cjs');
20
+
21
+ /**
22
+ * Error codes for allow-read-config operations
23
+ */
24
+ const ERROR_CODES = {
25
+ WRITE_FAILED: 'WRITE_FAILED',
26
+ APPLY_FAILED: 'APPLY_FAILED',
27
+ ROLLBACK_FAILED: 'ROLLBACK_FAILED',
28
+ INVALID_ARGS: 'INVALID_ARGS'
29
+ };
30
+
31
+ /**
32
+ * Get the GSD config directory path
33
+ * Uses environment variable if set, otherwise defaults to ~/.config/opencode/get-shit-done
34
+ *
35
+ * @returns {string} GSD config directory path
36
+ */
37
+ function getGsdConfigDir() {
38
+ const envDir = process.env.OPENCODE_CONFIG_DIR;
39
+ if (envDir) {
40
+ return envDir;
41
+ }
42
+
43
+ const homeDir = os.homedir();
44
+ return path.join(homeDir, '.config', 'opencode', 'get-shit-done');
45
+ }
46
+
47
+ /**
48
+ * Build the external_directory permission pattern
49
+ *
50
+ * @param {string} gsdDir - GSD config directory
51
+ * @returns {string} Permission pattern with wildcard
52
+ */
53
+ function buildPermissionPattern(gsdDir) {
54
+ // Use ** for recursive matching (all subdirectories and files)
55
+ return `${gsdDir}/**`;
56
+ }
57
+
58
+ /**
59
+ * Check if permission already exists in opencode.json
60
+ *
61
+ * @param {Object} opencodeData - Parsed opencode.json content
62
+ * @param {string} pattern - Permission pattern to check
63
+ * @returns {boolean} True if permission exists
64
+ */
65
+ function permissionExists(opencodeData, pattern) {
66
+ const permissions = opencodeData.permission;
67
+
68
+ if (!permissions) {
69
+ return false;
70
+ }
71
+
72
+ const externalDirPerms = permissions.external_directory;
73
+ if (!externalDirPerms || typeof externalDirPerms !== 'object') {
74
+ return false;
75
+ }
76
+
77
+ // Check if the pattern exists and is set to "allow"
78
+ return externalDirPerms[pattern] === 'allow';
79
+ }
80
+
81
+ /**
82
+ * Main command function
83
+ *
84
+ * @param {string} cwd - Current working directory
85
+ * @param {string[]} args - Command line arguments
86
+ */
87
+ function allowReadConfig(cwd, args) {
88
+ const verbose = args.includes('--verbose');
89
+ const dryRun = args.includes('--dry-run');
90
+ const raw = args.includes('--raw');
91
+
92
+ const log = verbose ? (...args) => console.error('[allow-read-config]', ...args) : () => {};
93
+
94
+ const opencodePath = path.join(cwd, 'opencode.json');
95
+ const backupsDir = path.join(cwd, '.planning', 'backups');
96
+ const gsdConfigDir = getGsdConfigDir();
97
+ const permissionPattern = buildPermissionPattern(gsdConfigDir);
98
+
99
+ log('Starting allow-read-config command');
100
+ log(`GSD config directory: ${gsdConfigDir}`);
101
+ log(`Permission pattern: ${permissionPattern}`);
102
+
103
+ // Check for invalid arguments
104
+ const validFlags = ['--verbose', '--dry-run', '--raw'];
105
+ const invalidArgs = args.filter(arg =>
106
+ arg.startsWith('--') && !validFlags.includes(arg)
107
+ );
108
+
109
+ if (invalidArgs.length > 0) {
110
+ error(`Unknown arguments: ${invalidArgs.join(', ')}`, 'INVALID_ARGS');
111
+ }
112
+
113
+ // Load or create opencode.json
114
+ let opencodeData;
115
+ let fileExisted = false;
116
+
117
+ if (fs.existsSync(opencodePath)) {
118
+ try {
119
+ const content = fs.readFileSync(opencodePath, 'utf8');
120
+ opencodeData = JSON.parse(content);
121
+ fileExisted = true;
122
+ log('Loaded existing opencode.json');
123
+ } catch (err) {
124
+ error(`Failed to parse opencode.json: ${err.message}`, 'INVALID_JSON');
125
+ }
126
+ } else {
127
+ // Create initial opencode.json structure
128
+ opencodeData = {
129
+ "$schema": "https://opencode.ai/config.json"
130
+ };
131
+ log('Creating new opencode.json');
132
+ }
133
+
134
+ // Check if permission already exists
135
+ const exists = permissionExists(opencodeData, permissionPattern);
136
+
137
+ if (exists) {
138
+ log('Permission already exists');
139
+ output({
140
+ success: true,
141
+ data: {
142
+ dryRun: dryRun,
143
+ action: 'permission_exists',
144
+ pattern: permissionPattern,
145
+ message: 'Permission already configured'
146
+ }
147
+ });
148
+ process.exit(0);
149
+ }
150
+
151
+ // Dry-run mode - preview changes
152
+ if (dryRun) {
153
+ log('Dry-run mode - no changes will be made');
154
+
155
+ const changes = [];
156
+ if (!fileExisted) {
157
+ changes.push('Create opencode.json');
158
+ }
159
+ changes.push(`Add external_directory permission: ${permissionPattern}`);
160
+
161
+ output({
162
+ success: true,
163
+ data: {
164
+ dryRun: true,
165
+ action: 'add_permission',
166
+ pattern: permissionPattern,
167
+ gsdConfigDir: gsdConfigDir,
168
+ changes: changes,
169
+ message: fileExisted ? 'Would update opencode.json' : 'Would create opencode.json'
170
+ }
171
+ });
172
+ process.exit(0);
173
+ }
174
+
175
+ // Create backup if file exists
176
+ let backupPath = null;
177
+ if (fileExisted) {
178
+ // Ensure backup directory exists
179
+ if (!fs.existsSync(backupsDir)) {
180
+ fs.mkdirSync(backupsDir, { recursive: true });
181
+ }
182
+
183
+ backupPath = createBackup(opencodePath, backupsDir);
184
+ log(`Backup created: ${backupPath}`);
185
+ }
186
+
187
+ // Initialize permission structure if needed
188
+ if (!opencodeData.permission) {
189
+ opencodeData.permission = {};
190
+ }
191
+
192
+ if (!opencodeData.permission.external_directory) {
193
+ opencodeData.permission.external_directory = {};
194
+ }
195
+
196
+ // Add the permission
197
+ opencodeData.permission.external_directory[permissionPattern] = 'allow';
198
+
199
+ log('Permission added to opencode.json');
200
+
201
+ // Write updated opencode.json
202
+ try {
203
+ fs.writeFileSync(opencodePath, JSON.stringify(opencodeData, null, 2) + '\n', 'utf8');
204
+ log('Updated opencode.json');
205
+ } catch (err) {
206
+ // Rollback if backup exists
207
+ if (backupPath) {
208
+ try {
209
+ fs.copyFileSync(backupPath, opencodePath);
210
+ } catch (rollbackErr) {
211
+ error(
212
+ `Failed to write opencode.json AND failed to rollback: ${rollbackErr.message}`,
213
+ 'ROLLBACK_FAILED'
214
+ );
215
+ }
216
+ }
217
+ error(`Failed to write opencode.json: ${err.message}`, 'WRITE_FAILED');
218
+ }
219
+
220
+ output({
221
+ success: true,
222
+ data: {
223
+ action: 'add_permission',
224
+ pattern: permissionPattern,
225
+ gsdConfigDir: gsdConfigDir,
226
+ opencodePath: opencodePath,
227
+ backup: backupPath,
228
+ created: !fileExisted,
229
+ message: fileExisted ? 'opencode.json updated' : 'opencode.json created'
230
+ }
231
+ });
232
+ process.exit(0);
233
+ }
234
+
235
+ module.exports = allowReadConfig;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * check-oc-config-json.cjs — Validate profile configuration in .planning/oc_config.json
3
+ *
4
+ * Command module that validates .planning/oc_config.json profile configuration.
5
+ * Validates:
6
+ * - current_oc_profile field exists and refers to a profile in profiles.presets
7
+ * - profiles.presets.{current_oc_profile} contains required keys: planning, execution, verification
8
+ * - All model IDs exist in opencode models catalog
9
+ * Outputs JSON envelope format with validation results.
10
+ *
11
+ * Usage: node check-oc-config-json.cjs [cwd]
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { output, error } = require('../gsd-oc-lib/oc-core.cjs');
17
+ const { getModelCatalog } = require('../gsd-oc-lib/oc-models.cjs');
18
+
19
+ /**
20
+ * Main command function
21
+ *
22
+ * @param {string} cwd - Current working directory
23
+ * @param {string[]} args - Command line arguments
24
+ */
25
+ function checkOcConfigJson(cwd, args) {
26
+ const verbose = args.includes('--verbose');
27
+ const configPath = path.join(cwd, '.planning', 'oc_config.json');
28
+
29
+ // Check if oc_config.json exists
30
+ if (!fs.existsSync(configPath)) {
31
+ error('.planning/oc_config.json not found', 'CONFIG_NOT_FOUND');
32
+ }
33
+
34
+ // read and parse config
35
+ let config;
36
+ try {
37
+ const content = fs.readFileSync(configPath, 'utf8');
38
+ config = JSON.parse(content);
39
+ } catch (err) {
40
+ if (err instanceof SyntaxError) {
41
+ error('.planning/oc_config.json is not valid JSON', 'INVALID_JSON');
42
+ }
43
+ error(`Failed to read config: ${err.message}`, 'READ_FAILED');
44
+ }
45
+
46
+ const issues = [];
47
+
48
+ // Extract profile information
49
+ const currentOcProfile = config.current_oc_profile;
50
+ const presets = config.profiles?.presets;
51
+
52
+ // Validate current_oc_profile field (required)
53
+ if (currentOcProfile === undefined) {
54
+ issues.push({
55
+ field: 'current_oc_profile',
56
+ value: '(missing)',
57
+ reason: 'current_oc_profile field is required'
58
+ });
59
+ } else if (presets && typeof presets === 'object' && !presets[currentOcProfile]) {
60
+ const availableProfiles = presets ? Object.keys(presets).join(', ') : 'none';
61
+ issues.push({
62
+ field: 'current_oc_profile',
63
+ value: currentOcProfile,
64
+ reason: `Profile "${currentOcProfile}" not found in profiles.presets. Available: ${availableProfiles}`
65
+ });
66
+ }
67
+
68
+ // Validate profiles.presets section exists
69
+ if (!presets || typeof presets !== 'object') {
70
+ issues.push({
71
+ field: 'profiles.presets',
72
+ value: '(missing or invalid)',
73
+ reason: 'profiles.presets section is required'
74
+ });
75
+ const result = {
76
+ success: false,
77
+ data: {
78
+ passed: false,
79
+ current_oc_profile: currentOcProfile || null,
80
+ profile_data: null,
81
+ issues
82
+ },
83
+ error: {
84
+ code: 'INVALID_PROFILE',
85
+ message: `${issues.length} invalid profile configuration(s) found`
86
+ }
87
+ };
88
+ output(result);
89
+ process.exit(1);
90
+ }
91
+
92
+ // Validate profile structure if current profile exists
93
+ if (currentOcProfile && presets[currentOcProfile]) {
94
+ const profile = presets[currentOcProfile];
95
+
96
+ // Validate that profile has required keys: planning, execution, verification
97
+ const requiredKeys = ['planning', 'execution', 'verification'];
98
+ for (const key of requiredKeys) {
99
+ if (profile[key] === undefined) {
100
+ issues.push({
101
+ field: `profiles.presets.${currentOcProfile}.${key}`,
102
+ value: '(missing)',
103
+ reason: `${key} model is required for ${currentOcProfile} profile`
104
+ });
105
+ } else if (typeof profile[key] !== 'string') {
106
+ issues.push({
107
+ field: `profiles.presets.${currentOcProfile}.${key}`,
108
+ value: profile[key],
109
+ reason: `${key} must be a string model ID`
110
+ });
111
+ }
112
+ }
113
+
114
+ // Validate model IDs against catalog
115
+ if (verbose) {
116
+ console.error('[verbose] Fetching model catalog...');
117
+ }
118
+
119
+ const catalogResult = getModelCatalog();
120
+ if (!catalogResult.success) {
121
+ error(catalogResult.error.message, catalogResult.error.code);
122
+ }
123
+
124
+ const validModels = catalogResult.models;
125
+
126
+ if (verbose) {
127
+ console.error(`[verbose] Found ${validModels.length} models in catalog`);
128
+ console.error('[verbose] Validating profile model IDs...');
129
+ }
130
+
131
+ for (const key of requiredKeys) {
132
+ if (profile[key] && typeof profile[key] === 'string') {
133
+ if (!validModels.includes(profile[key])) {
134
+ issues.push({
135
+ field: `profiles.presets.${currentOcProfile}.${key}`,
136
+ value: profile[key],
137
+ reason: `Model ID not found in opencode models catalog`
138
+ });
139
+ } else if (verbose) {
140
+ console.error(`[verbose] ✓ profiles.presets.${currentOcProfile}.${key}: ${profile[key]} (valid)`);
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ const passed = issues.length === 0;
147
+
148
+ const result = {
149
+ success: passed,
150
+ data: {
151
+ passed,
152
+ current_oc_profile: currentOcProfile || null,
153
+ profile_data: currentOcProfile && presets ? presets[currentOcProfile] : null,
154
+ issues
155
+ }
156
+ };
157
+
158
+ if (!passed) {
159
+ result.error = {
160
+ code: 'INVALID_PROFILE',
161
+ message: `${issues.length} invalid profile configuration(s) found`
162
+ };
163
+ }
164
+
165
+ output(result);
166
+ process.exit(passed ? 0 : 1);
167
+ }
168
+
169
+ module.exports = checkOcConfigJson;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * check-opencode-json.cjs — Validate model IDs in opencode.json
3
+ *
4
+ * Command module that validates opencode.json model IDs against the opencode models catalog.
5
+ * Outputs JSON envelope format with validation results.
6
+ *
7
+ * Usage: node check-opencode-json.cjs [cwd] [--verbose]
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const { output, error } = require('../gsd-oc-lib/oc-core.cjs');
13
+ const { getModelCatalog, validateModelIds } = require('../gsd-oc-lib/oc-models.cjs');
14
+
15
+ /**
16
+ * Main command function
17
+ *
18
+ * @param {string} cwd - Current working directory
19
+ * @param {string[]} args - Command line arguments
20
+ */
21
+ function checkOpencodeJson(cwd, args) {
22
+ const verbose = args.includes('--verbose');
23
+ const opencodePath = path.join(cwd, 'opencode.json');
24
+
25
+ // Check if opencode.json exists
26
+ if (!fs.existsSync(opencodePath)) {
27
+ error('opencode.json not found in current directory', 'CONFIG_NOT_FOUND');
28
+ }
29
+
30
+ if (verbose) {
31
+ console.error(`[verbose] Validating: ${opencodePath}`);
32
+ }
33
+
34
+ // Fetch model catalog
35
+ if (verbose) {
36
+ console.error('[verbose] Fetching model catalog from opencode models...');
37
+ }
38
+
39
+ const catalogResult = getModelCatalog();
40
+ if (!catalogResult.success) {
41
+ error(catalogResult.error.message, catalogResult.error.code);
42
+ }
43
+
44
+ if (verbose) {
45
+ console.error(`[verbose] Found ${catalogResult.models.length} models in catalog`);
46
+ }
47
+
48
+ // Validate model IDs
49
+ if (verbose) {
50
+ console.error('[verbose] Validating model IDs...');
51
+ }
52
+
53
+ try {
54
+ const validationResult = validateModelIds(opencodePath, catalogResult.models);
55
+
56
+ const result = {
57
+ success: true,
58
+ data: validationResult
59
+ };
60
+
61
+ // Exit code based on validation result
62
+ if (validationResult.valid) {
63
+ output(result);
64
+ process.exit(0);
65
+ } else {
66
+ // Add error details for invalid models
67
+ result.error = {
68
+ code: 'INVALID_MODEL_ID',
69
+ message: `${validationResult.invalidCount} invalid model ID(s) found`
70
+ };
71
+ output(result);
72
+ process.exit(1);
73
+ }
74
+ } catch (err) {
75
+ if (err.message === 'CONFIG_NOT_FOUND') {
76
+ error('opencode.json not found', 'CONFIG_NOT_FOUND');
77
+ } else if (err.message === 'INVALID_JSON') {
78
+ error('opencode.json is not valid JSON', 'INVALID_JSON');
79
+ } else {
80
+ error(err.message, 'VALIDATION_FAILED');
81
+ }
82
+ }
83
+ }
84
+
85
+ // Export for use by main router
86
+ module.exports = checkOpencodeJson;
@@ -0,0 +1,117 @@
1
+ /**
2
+ * get-profile.cjs — Retrieve profile definitions from oc_config.json
3
+ *
4
+ * Command module that exports getProfile(cwd, args) function with two operation modes:
5
+ * 1. No parameters: Returns current profile definition
6
+ * 2. Profile name parameter: Returns specified profile definition
7
+ *
8
+ * Output format: JSON envelope {success: true, data: {profileName: {planning, execution, verification}}}
9
+ * Flags: --raw (output raw JSON without envelope), --verbose (output diagnostics to stderr)
10
+ *
11
+ * Usage: node get-profile.cjs [profile-name] [--raw] [--verbose]
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { output, error } = require('../gsd-oc-lib/oc-core.cjs');
17
+ const { loadOcProfileConfig } = require('../gsd-oc-lib/oc-profile-config.cjs');
18
+
19
+ /**
20
+ * Main command function
21
+ *
22
+ * @param {string} cwd - Current working directory
23
+ * @param {string[]} args - Command line arguments
24
+ */
25
+ function getProfile(cwd, args) {
26
+ const verbose = args.includes('--verbose');
27
+ const raw = args.includes('--raw');
28
+ const log = verbose ? (...args) => console.error('[get-profile]', ...args) : () => {};
29
+
30
+ // Filter out flags to get profile name argument
31
+ const profileArgs = args.filter(arg => !arg.startsWith('--'));
32
+
33
+ // Check for too many arguments
34
+ if (profileArgs.length > 1) {
35
+ error('Too many arguments. Usage: get-profile [profile-name]', 'INVALID_ARGS');
36
+ }
37
+
38
+ const profileName = profileArgs.length > 0 ? profileArgs[0] : null;
39
+
40
+ log('Loading oc_config.json');
41
+
42
+ // Load oc_config.json
43
+ const loadResult = loadOcProfileConfig(cwd);
44
+ if (!loadResult.success) {
45
+ error(loadResult.error.message, loadResult.error.code);
46
+ }
47
+
48
+ const { config, configPath } = loadResult;
49
+
50
+ log(`Config loaded from ${configPath}`);
51
+
52
+ // ========== MODE 1: No parameters (get current profile) ==========
53
+ if (!profileName) {
54
+ log('Mode 1: Getting current profile');
55
+
56
+ // Check current_oc_profile is set
57
+ if (!config.current_oc_profile) {
58
+ error(
59
+ 'current_oc_profile not set in oc_config.json. Run set-profile first.',
60
+ 'MISSING_CURRENT_PROFILE'
61
+ );
62
+ }
63
+
64
+ const currentProfileName = config.current_oc_profile;
65
+ log(`Current profile: ${currentProfileName}`);
66
+
67
+ // Check profile exists in profiles.presets
68
+ const presets = config.profiles?.presets;
69
+ if (!presets || !presets[currentProfileName]) {
70
+ const availableProfiles = presets ? Object.keys(presets).join(', ') : 'none';
71
+ error(
72
+ `Current profile "${currentProfileName}" not found in profiles.presets. Available profiles: ${availableProfiles}`,
73
+ 'PROFILE_NOT_FOUND'
74
+ );
75
+ }
76
+
77
+ const profile = presets[currentProfileName];
78
+ const result = { [currentProfileName]: profile };
79
+
80
+ log(`Returning profile definition for "${currentProfileName}"`);
81
+
82
+ if (raw) {
83
+ output(result, true, JSON.stringify(result, null, 2));
84
+ } else {
85
+ output({ success: true, data: result });
86
+ }
87
+ process.exit(0);
88
+ }
89
+
90
+ // ========== MODE 2: Profile name parameter (get specific profile) ==========
91
+ log(`Mode 2: Getting profile "${profileName}"`);
92
+
93
+ // Check profile exists in profiles.presets
94
+ // Note: Does NOT require current_oc_profile to be set
95
+ const presets = config.profiles?.presets;
96
+ if (!presets || !presets[profileName]) {
97
+ const availableProfiles = presets ? Object.keys(presets).join(', ') : 'none';
98
+ error(
99
+ `Profile "${profileName}" not found in profiles.presets. Available profiles: ${availableProfiles}`,
100
+ 'PROFILE_NOT_FOUND'
101
+ );
102
+ }
103
+
104
+ const profile = presets[profileName];
105
+ const result = { [profileName]: profile };
106
+
107
+ log(`Returning profile definition for "${profileName}"`);
108
+
109
+ if (raw) {
110
+ output(result, true, JSON.stringify(result, null, 2));
111
+ } else {
112
+ output({ success: true, data: result });
113
+ }
114
+ process.exit(0);
115
+ }
116
+
117
+ module.exports = getProfile;