@saschabrunnerch/arcgis-maps-sdk-js-ai-context 0.0.1 → 0.0.2

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/lib/installer.js CHANGED
@@ -1,379 +1,379 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
-
4
- // ANSI color codes (built-in, no dependencies)
5
- const colors = {
6
- reset: '\x1b[0m',
7
- bold: '\x1b[1m',
8
- dim: '\x1b[2m',
9
- green: '\x1b[32m',
10
- yellow: '\x1b[33m',
11
- blue: '\x1b[34m',
12
- magenta: '\x1b[35m',
13
- cyan: '\x1b[36m',
14
- red: '\x1b[31m',
15
- white: '\x1b[37m',
16
- };
17
-
18
- function colorize(color, text) {
19
- return `${colors[color]}${text}${colors.reset}`;
20
- }
21
-
22
- function log(message) {
23
- console.log(message);
24
- }
25
-
26
- function success(message) {
27
- log(`${colorize('green', '\u2714')} ${message}`);
28
- }
29
-
30
- function error(message) {
31
- log(`${colorize('red', '\u2718')} ${message}`);
32
- }
33
-
34
- function info(message) {
35
- log(`${colorize('cyan', '\u2139')} ${message}`);
36
- }
37
-
38
- function warning(message) {
39
- log(`${colorize('yellow', '\u26A0')} ${message}`);
40
- }
41
-
42
- function header(message) {
43
- log(`\n${colorize('bold', colorize('magenta', message))}`);
44
- }
45
-
46
- /**
47
- * Get the contexts directory path (where bundled contexts are stored)
48
- */
49
- function getContextsDir() {
50
- return path.join(__dirname, '..', 'contexts');
51
- }
52
-
53
- /**
54
- * Get available SDK versions
55
- */
56
- function getAvailableVersions() {
57
- const contextsDir = getContextsDir();
58
-
59
- if (!fs.existsSync(contextsDir)) {
60
- return [];
61
- }
62
-
63
- const entries = fs.readdirSync(contextsDir, { withFileTypes: true });
64
- const versions = entries
65
- .filter(e => e.isDirectory() && /^\d+\.\d+$/.test(e.name))
66
- .map(e => e.name)
67
- .sort((a, b) => {
68
- const [aMajor, aMinor] = a.split('.').map(Number);
69
- const [bMajor, bMinor] = b.split('.').map(Number);
70
- if (aMajor !== bMajor) return aMajor - bMajor;
71
- return aMinor - bMinor;
72
- });
73
-
74
- return versions;
75
- }
76
-
77
- /**
78
- * Get the latest available version
79
- */
80
- function getLatestVersion() {
81
- const versions = getAvailableVersions();
82
- return versions.length > 0 ? versions[versions.length - 1] : null;
83
- }
84
-
85
- /**
86
- * Copy directory recursively
87
- */
88
- function copyDirSync(src, dest) {
89
- if (!fs.existsSync(src)) {
90
- throw new Error(`Source directory does not exist: ${src}`);
91
- }
92
-
93
- fs.mkdirSync(dest, { recursive: true });
94
-
95
- const entries = fs.readdirSync(src, { withFileTypes: true });
96
-
97
- for (const entry of entries) {
98
- const srcPath = path.join(src, entry.name);
99
- const destPath = path.join(dest, entry.name);
100
-
101
- if (entry.isDirectory()) {
102
- copyDirSync(srcPath, destPath);
103
- } else {
104
- fs.copyFileSync(srcPath, destPath);
105
- }
106
- }
107
- }
108
-
109
- /**
110
- * Check if directory is writable
111
- */
112
- function isWritable(dirPath) {
113
- try {
114
- const testFile = path.join(dirPath, '.write-test-' + Date.now());
115
- fs.mkdirSync(dirPath, { recursive: true });
116
- fs.writeFileSync(testFile, '');
117
- fs.unlinkSync(testFile);
118
- return true;
119
- } catch {
120
- return false;
121
- }
122
- }
123
-
124
- /**
125
- * Count files in a directory recursively
126
- */
127
- function countFiles(dirPath) {
128
- if (!fs.existsSync(dirPath)) return 0;
129
-
130
- let count = 0;
131
- const entries = fs.readdirSync(dirPath, { withFileTypes: true });
132
-
133
- for (const entry of entries) {
134
- if (entry.isDirectory()) {
135
- count += countFiles(path.join(dirPath, entry.name));
136
- } else {
137
- count++;
138
- }
139
- }
140
-
141
- return count;
142
- }
143
-
144
- /**
145
- * List directory contents recursively with indentation
146
- */
147
- function listDirContents(dirPath, indent = '') {
148
- if (!fs.existsSync(dirPath)) return [];
149
-
150
- const items = [];
151
- const entries = fs.readdirSync(dirPath, { withFileTypes: true }).sort((a, b) => {
152
- // Directories first, then files
153
- if (a.isDirectory() && !b.isDirectory()) return -1;
154
- if (!a.isDirectory() && b.isDirectory()) return 1;
155
- return a.name.localeCompare(b.name);
156
- });
157
-
158
- for (const entry of entries) {
159
- const isDir = entry.isDirectory();
160
- const icon = isDir ? '\u{1F4C1}' : '\u{1F4C4}';
161
- items.push(`${indent}${icon} ${entry.name}`);
162
-
163
- if (isDir) {
164
- items.push(...listDirContents(path.join(dirPath, entry.name), indent + ' '));
165
- }
166
- }
167
-
168
- return items;
169
- }
170
-
171
- /**
172
- * Install Claude skills
173
- */
174
- function installClaude(targetDir = process.cwd(), sdkVersion = null) {
175
- const version = sdkVersion || getLatestVersion();
176
-
177
- if (!version) {
178
- error('No SDK versions available');
179
- return false;
180
- }
181
-
182
- const destPath = path.join(targetDir, '.claude', 'skills', 'arcgis-maps-sdk-js');
183
- const srcPath = path.join(getContextsDir(), version, 'claude');
184
-
185
- header(`Installing Claude Skills (SDK ${version})`);
186
- info(`Source: ${srcPath}`);
187
- info(`Target: ${destPath}`);
188
-
189
- if (!fs.existsSync(srcPath)) {
190
- error(`Claude skills not found for SDK version ${version}`);
191
- return false;
192
- }
193
-
194
- const parentDir = path.dirname(destPath);
195
- if (!isWritable(parentDir)) {
196
- error(`Cannot write to directory: ${parentDir}`);
197
- error('Please check permissions and try again');
198
- return false;
199
- }
200
-
201
- try {
202
- copyDirSync(srcPath, destPath);
203
- const fileCount = countFiles(destPath);
204
- success(`Installed ${fileCount} Claude skill files for SDK ${version}`);
205
- success(`Location: ${colorize('cyan', destPath)}`);
206
- return true;
207
- } catch (err) {
208
- error(`Failed to install Claude skills: ${err.message}`);
209
- return false;
210
- }
211
- }
212
-
213
- /**
214
- * Install GitHub Copilot instructions
215
- */
216
- function installCopilot(targetDir = process.cwd(), sdkVersion = null) {
217
- const version = sdkVersion || getLatestVersion();
218
-
219
- if (!version) {
220
- error('No SDK versions available');
221
- return false;
222
- }
223
-
224
- const destPath = path.join(targetDir, '.github', 'instructions');
225
- const srcPath = path.join(getContextsDir(), version, 'copilot');
226
-
227
- header(`Installing GitHub Copilot Instructions (SDK ${version})`);
228
- info(`Source: ${srcPath}`);
229
- info(`Target: ${destPath}`);
230
-
231
- if (!fs.existsSync(srcPath)) {
232
- error(`Copilot instructions not found for SDK version ${version}`);
233
- return false;
234
- }
235
-
236
- if (!isWritable(destPath)) {
237
- error(`Cannot write to directory: ${destPath}`);
238
- error('Please check permissions and try again');
239
- return false;
240
- }
241
-
242
- try {
243
- copyDirSync(srcPath, destPath);
244
- const fileCount = countFiles(destPath);
245
- success(`Installed ${fileCount} Copilot instruction files for SDK ${version}`);
246
- success(`Location: ${colorize('cyan', destPath)}`);
247
- return true;
248
- } catch (err) {
249
- error(`Failed to install Copilot instructions: ${err.message}`);
250
- return false;
251
- }
252
- }
253
-
254
- /**
255
- * Install all contexts
256
- */
257
- function installAll(targetDir = process.cwd(), sdkVersion = null) {
258
- const version = sdkVersion || getLatestVersion();
259
-
260
- header(`Installing All AI Context Files (SDK ${version})`);
261
-
262
- const claudeResult = installClaude(targetDir, version);
263
- const copilotResult = installCopilot(targetDir, version);
264
-
265
- log('');
266
- if (claudeResult && copilotResult) {
267
- success(colorize('bold', 'All installations completed successfully!'));
268
- return true;
269
- } else {
270
- warning('Some installations failed. See errors above.');
271
- return false;
272
- }
273
- }
274
-
275
- /**
276
- * List available contexts
277
- */
278
- function listContexts() {
279
- const contextsDir = getContextsDir();
280
- const versions = getAvailableVersions();
281
-
282
- header('Available AI Context Files');
283
- log('');
284
-
285
- // Show available versions
286
- log(`${colorize('bold', colorize('blue', 'Available SDK Versions'))}`);
287
- if (versions.length > 0) {
288
- const latestVersion = versions[versions.length - 1];
289
- for (const version of versions) {
290
- const isLatest = version === latestVersion;
291
- const label = isLatest ? ` ${colorize('green', '(latest)')}` : '';
292
- log(` ${colorize('green', '\u2022')} ${colorize('cyan', version)}${label}`);
293
- }
294
- } else {
295
- log(` ${colorize('dim', 'No versions available')}`);
296
- }
297
- log('');
298
-
299
- // Show contents for latest version
300
- const latestVersion = getLatestVersion();
301
- if (!latestVersion) {
302
- return;
303
- }
304
-
305
- // Claude skills
306
- const claudeDir = path.join(contextsDir, latestVersion, 'claude');
307
- if (fs.existsSync(claudeDir)) {
308
- log(`${colorize('bold', colorize('blue', `Claude Skills (SDK ${latestVersion})`))}`);
309
- log(colorize('dim', ` Installs to: .claude/skills/arcgis-maps-sdk-js/`));
310
-
311
- const entries = fs.readdirSync(claudeDir, { withFileTypes: true })
312
- .filter(e => e.isDirectory())
313
- .sort((a, b) => a.name.localeCompare(b.name));
314
-
315
- for (const entry of entries) {
316
- const skillPath = path.join(claudeDir, entry.name, 'SKILL.md');
317
- if (fs.existsSync(skillPath)) {
318
- // Read first few lines to get name and description
319
- const content = fs.readFileSync(skillPath, 'utf8');
320
- const nameMatch = content.match(/^name:\s*(.+)$/m);
321
- const descMatch = content.match(/^description:\s*(.+)$/m);
322
-
323
- const name = nameMatch ? nameMatch[1] : entry.name;
324
- const desc = descMatch ? descMatch[1].substring(0, 60) + (descMatch[1].length > 60 ? '...' : '') : '';
325
-
326
- log(` ${colorize('green', '\u2022')} ${colorize('cyan', name)}`);
327
- if (desc) {
328
- log(` ${colorize('dim', desc)}`);
329
- }
330
- }
331
- }
332
-
333
- const skillCount = entries.length;
334
- log(` ${colorize('dim', `(${skillCount} skills total)`)}`);
335
- }
336
-
337
- log('');
338
-
339
- // Copilot instructions
340
- const copilotDir = path.join(contextsDir, latestVersion, 'copilot');
341
- if (fs.existsSync(copilotDir)) {
342
- log(`${colorize('bold', colorize('blue', `GitHub Copilot Instructions (SDK ${latestVersion})`))}`);
343
- log(colorize('dim', ` Installs to: .github/instructions/`));
344
-
345
- const files = fs.readdirSync(copilotDir)
346
- .filter(f => f.endsWith('.md') || f.endsWith('.instructions.md'))
347
- .sort();
348
-
349
- for (const file of files) {
350
- log(` ${colorize('green', '\u2022')} ${colorize('cyan', file)}`);
351
- }
352
-
353
- log(` ${colorize('dim', `(${files.length} file${files.length !== 1 ? 's' : ''} total)`)}`);
354
- }
355
-
356
- log('');
357
- log(colorize('dim', 'Usage:'));
358
- log(colorize('dim', ' npx @saschabrunnerch/arcgis-maps-sdk-js-ai-context claude # Install Claude skills (latest)'));
359
- log(colorize('dim', ' npx @saschabrunnerch/arcgis-maps-sdk-js-ai-context claude --sdk 4.34 # Install for specific version'));
360
- log(colorize('dim', ' npx @saschabrunnerch/arcgis-maps-sdk-js-ai-context copilot # Install Copilot instructions'));
361
- log(colorize('dim', ' npx @saschabrunnerch/arcgis-maps-sdk-js-ai-context all # Install everything'));
362
- }
363
-
364
- module.exports = {
365
- installClaude,
366
- installCopilot,
367
- installAll,
368
- listContexts,
369
- getAvailableVersions,
370
- getLatestVersion,
371
- colors,
372
- colorize,
373
- log,
374
- success,
375
- error,
376
- info,
377
- warning,
378
- header,
379
- };
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ // ANSI color codes (built-in, no dependencies)
5
+ const colors = {
6
+ reset: '\x1b[0m',
7
+ bold: '\x1b[1m',
8
+ dim: '\x1b[2m',
9
+ green: '\x1b[32m',
10
+ yellow: '\x1b[33m',
11
+ blue: '\x1b[34m',
12
+ magenta: '\x1b[35m',
13
+ cyan: '\x1b[36m',
14
+ red: '\x1b[31m',
15
+ white: '\x1b[37m',
16
+ };
17
+
18
+ function colorize(color, text) {
19
+ return `${colors[color]}${text}${colors.reset}`;
20
+ }
21
+
22
+ function log(message) {
23
+ console.log(message);
24
+ }
25
+
26
+ function success(message) {
27
+ log(`${colorize('green', '\u2714')} ${message}`);
28
+ }
29
+
30
+ function error(message) {
31
+ log(`${colorize('red', '\u2718')} ${message}`);
32
+ }
33
+
34
+ function info(message) {
35
+ log(`${colorize('cyan', '\u2139')} ${message}`);
36
+ }
37
+
38
+ function warning(message) {
39
+ log(`${colorize('yellow', '\u26A0')} ${message}`);
40
+ }
41
+
42
+ function header(message) {
43
+ log(`\n${colorize('bold', colorize('magenta', message))}`);
44
+ }
45
+
46
+ /**
47
+ * Get the contexts directory path (where bundled contexts are stored)
48
+ */
49
+ function getContextsDir() {
50
+ return path.join(__dirname, '..', 'contexts');
51
+ }
52
+
53
+ /**
54
+ * Get available SDK versions
55
+ */
56
+ function getAvailableVersions() {
57
+ const contextsDir = getContextsDir();
58
+
59
+ if (!fs.existsSync(contextsDir)) {
60
+ return [];
61
+ }
62
+
63
+ const entries = fs.readdirSync(contextsDir, { withFileTypes: true });
64
+ const versions = entries
65
+ .filter(e => e.isDirectory() && /^\d+\.\d+$/.test(e.name))
66
+ .map(e => e.name)
67
+ .sort((a, b) => {
68
+ const [aMajor, aMinor] = a.split('.').map(Number);
69
+ const [bMajor, bMinor] = b.split('.').map(Number);
70
+ if (aMajor !== bMajor) return aMajor - bMajor;
71
+ return aMinor - bMinor;
72
+ });
73
+
74
+ return versions;
75
+ }
76
+
77
+ /**
78
+ * Get the latest available version
79
+ */
80
+ function getLatestVersion() {
81
+ const versions = getAvailableVersions();
82
+ return versions.length > 0 ? versions[versions.length - 1] : null;
83
+ }
84
+
85
+ /**
86
+ * Copy directory recursively
87
+ */
88
+ function copyDirSync(src, dest) {
89
+ if (!fs.existsSync(src)) {
90
+ throw new Error(`Source directory does not exist: ${src}`);
91
+ }
92
+
93
+ fs.mkdirSync(dest, { recursive: true });
94
+
95
+ const entries = fs.readdirSync(src, { withFileTypes: true });
96
+
97
+ for (const entry of entries) {
98
+ const srcPath = path.join(src, entry.name);
99
+ const destPath = path.join(dest, entry.name);
100
+
101
+ if (entry.isDirectory()) {
102
+ copyDirSync(srcPath, destPath);
103
+ } else {
104
+ fs.copyFileSync(srcPath, destPath);
105
+ }
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Check if directory is writable
111
+ */
112
+ function isWritable(dirPath) {
113
+ try {
114
+ const testFile = path.join(dirPath, '.write-test-' + Date.now());
115
+ fs.mkdirSync(dirPath, { recursive: true });
116
+ fs.writeFileSync(testFile, '');
117
+ fs.unlinkSync(testFile);
118
+ return true;
119
+ } catch {
120
+ return false;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Count files in a directory recursively
126
+ */
127
+ function countFiles(dirPath) {
128
+ if (!fs.existsSync(dirPath)) return 0;
129
+
130
+ let count = 0;
131
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
132
+
133
+ for (const entry of entries) {
134
+ if (entry.isDirectory()) {
135
+ count += countFiles(path.join(dirPath, entry.name));
136
+ } else {
137
+ count++;
138
+ }
139
+ }
140
+
141
+ return count;
142
+ }
143
+
144
+ /**
145
+ * List directory contents recursively with indentation
146
+ */
147
+ function listDirContents(dirPath, indent = '') {
148
+ if (!fs.existsSync(dirPath)) return [];
149
+
150
+ const items = [];
151
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true }).sort((a, b) => {
152
+ // Directories first, then files
153
+ if (a.isDirectory() && !b.isDirectory()) return -1;
154
+ if (!a.isDirectory() && b.isDirectory()) return 1;
155
+ return a.name.localeCompare(b.name);
156
+ });
157
+
158
+ for (const entry of entries) {
159
+ const isDir = entry.isDirectory();
160
+ const icon = isDir ? '\u{1F4C1}' : '\u{1F4C4}';
161
+ items.push(`${indent}${icon} ${entry.name}`);
162
+
163
+ if (isDir) {
164
+ items.push(...listDirContents(path.join(dirPath, entry.name), indent + ' '));
165
+ }
166
+ }
167
+
168
+ return items;
169
+ }
170
+
171
+ /**
172
+ * Install Claude skills
173
+ */
174
+ function installClaude(targetDir = process.cwd(), sdkVersion = null) {
175
+ const version = sdkVersion || getLatestVersion();
176
+
177
+ if (!version) {
178
+ error('No SDK versions available');
179
+ return false;
180
+ }
181
+
182
+ const destPath = path.join(targetDir, '.claude', 'skills', 'arcgis-maps-sdk-js');
183
+ const srcPath = path.join(getContextsDir(), version, 'claude');
184
+
185
+ header(`Installing Claude Skills (SDK ${version})`);
186
+ info(`Source: ${srcPath}`);
187
+ info(`Target: ${destPath}`);
188
+
189
+ if (!fs.existsSync(srcPath)) {
190
+ error(`Claude skills not found for SDK version ${version}`);
191
+ return false;
192
+ }
193
+
194
+ const parentDir = path.dirname(destPath);
195
+ if (!isWritable(parentDir)) {
196
+ error(`Cannot write to directory: ${parentDir}`);
197
+ error('Please check permissions and try again');
198
+ return false;
199
+ }
200
+
201
+ try {
202
+ copyDirSync(srcPath, destPath);
203
+ const fileCount = countFiles(destPath);
204
+ success(`Installed ${fileCount} Claude skill files for SDK ${version}`);
205
+ success(`Location: ${colorize('cyan', destPath)}`);
206
+ return true;
207
+ } catch (err) {
208
+ error(`Failed to install Claude skills: ${err.message}`);
209
+ return false;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Install GitHub Copilot instructions
215
+ */
216
+ function installCopilot(targetDir = process.cwd(), sdkVersion = null) {
217
+ const version = sdkVersion || getLatestVersion();
218
+
219
+ if (!version) {
220
+ error('No SDK versions available');
221
+ return false;
222
+ }
223
+
224
+ const destPath = path.join(targetDir, '.github', 'instructions');
225
+ const srcPath = path.join(getContextsDir(), version, 'copilot');
226
+
227
+ header(`Installing GitHub Copilot Instructions (SDK ${version})`);
228
+ info(`Source: ${srcPath}`);
229
+ info(`Target: ${destPath}`);
230
+
231
+ if (!fs.existsSync(srcPath)) {
232
+ error(`Copilot instructions not found for SDK version ${version}`);
233
+ return false;
234
+ }
235
+
236
+ if (!isWritable(destPath)) {
237
+ error(`Cannot write to directory: ${destPath}`);
238
+ error('Please check permissions and try again');
239
+ return false;
240
+ }
241
+
242
+ try {
243
+ copyDirSync(srcPath, destPath);
244
+ const fileCount = countFiles(destPath);
245
+ success(`Installed ${fileCount} Copilot instruction files for SDK ${version}`);
246
+ success(`Location: ${colorize('cyan', destPath)}`);
247
+ return true;
248
+ } catch (err) {
249
+ error(`Failed to install Copilot instructions: ${err.message}`);
250
+ return false;
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Install all contexts
256
+ */
257
+ function installAll(targetDir = process.cwd(), sdkVersion = null) {
258
+ const version = sdkVersion || getLatestVersion();
259
+
260
+ header(`Installing All AI Context Files (SDK ${version})`);
261
+
262
+ const claudeResult = installClaude(targetDir, version);
263
+ const copilotResult = installCopilot(targetDir, version);
264
+
265
+ log('');
266
+ if (claudeResult && copilotResult) {
267
+ success(colorize('bold', 'All installations completed successfully!'));
268
+ return true;
269
+ } else {
270
+ warning('Some installations failed. See errors above.');
271
+ return false;
272
+ }
273
+ }
274
+
275
+ /**
276
+ * List available contexts
277
+ */
278
+ function listContexts() {
279
+ const contextsDir = getContextsDir();
280
+ const versions = getAvailableVersions();
281
+
282
+ header('Available AI Context Files');
283
+ log('');
284
+
285
+ // Show available versions
286
+ log(`${colorize('bold', colorize('blue', 'Available SDK Versions'))}`);
287
+ if (versions.length > 0) {
288
+ const latestVersion = versions[versions.length - 1];
289
+ for (const version of versions) {
290
+ const isLatest = version === latestVersion;
291
+ const label = isLatest ? ` ${colorize('green', '(latest)')}` : '';
292
+ log(` ${colorize('green', '\u2022')} ${colorize('cyan', version)}${label}`);
293
+ }
294
+ } else {
295
+ log(` ${colorize('dim', 'No versions available')}`);
296
+ }
297
+ log('');
298
+
299
+ // Show contents for latest version
300
+ const latestVersion = getLatestVersion();
301
+ if (!latestVersion) {
302
+ return;
303
+ }
304
+
305
+ // Claude skills
306
+ const claudeDir = path.join(contextsDir, latestVersion, 'claude');
307
+ if (fs.existsSync(claudeDir)) {
308
+ log(`${colorize('bold', colorize('blue', `Claude Skills (SDK ${latestVersion})`))}`);
309
+ log(colorize('dim', ` Installs to: .claude/skills/arcgis-maps-sdk-js/`));
310
+
311
+ const entries = fs.readdirSync(claudeDir, { withFileTypes: true })
312
+ .filter(e => e.isDirectory())
313
+ .sort((a, b) => a.name.localeCompare(b.name));
314
+
315
+ for (const entry of entries) {
316
+ const skillPath = path.join(claudeDir, entry.name, 'SKILL.md');
317
+ if (fs.existsSync(skillPath)) {
318
+ // Read first few lines to get name and description
319
+ const content = fs.readFileSync(skillPath, 'utf8');
320
+ const nameMatch = content.match(/^name:\s*(.+)$/m);
321
+ const descMatch = content.match(/^description:\s*(.+)$/m);
322
+
323
+ const name = nameMatch ? nameMatch[1] : entry.name;
324
+ const desc = descMatch ? descMatch[1].substring(0, 60) + (descMatch[1].length > 60 ? '...' : '') : '';
325
+
326
+ log(` ${colorize('green', '\u2022')} ${colorize('cyan', name)}`);
327
+ if (desc) {
328
+ log(` ${colorize('dim', desc)}`);
329
+ }
330
+ }
331
+ }
332
+
333
+ const skillCount = entries.length;
334
+ log(` ${colorize('dim', `(${skillCount} skills total)`)}`);
335
+ }
336
+
337
+ log('');
338
+
339
+ // Copilot instructions
340
+ const copilotDir = path.join(contextsDir, latestVersion, 'copilot');
341
+ if (fs.existsSync(copilotDir)) {
342
+ log(`${colorize('bold', colorize('blue', `GitHub Copilot Instructions (SDK ${latestVersion})`))}`);
343
+ log(colorize('dim', ` Installs to: .github/instructions/`));
344
+
345
+ const files = fs.readdirSync(copilotDir)
346
+ .filter(f => f.endsWith('.md') || f.endsWith('.instructions.md'))
347
+ .sort();
348
+
349
+ for (const file of files) {
350
+ log(` ${colorize('green', '\u2022')} ${colorize('cyan', file)}`);
351
+ }
352
+
353
+ log(` ${colorize('dim', `(${files.length} file${files.length !== 1 ? 's' : ''} total)`)}`);
354
+ }
355
+
356
+ log('');
357
+ log(colorize('dim', 'Usage:'));
358
+ log(colorize('dim', ' npx @saschabrunnerch/arcgis-maps-sdk-js-ai-context claude # Install Claude skills (latest)'));
359
+ log(colorize('dim', ' npx @saschabrunnerch/arcgis-maps-sdk-js-ai-context claude --sdk 4.34 # Install for specific version'));
360
+ log(colorize('dim', ' npx @saschabrunnerch/arcgis-maps-sdk-js-ai-context copilot # Install Copilot instructions'));
361
+ log(colorize('dim', ' npx @saschabrunnerch/arcgis-maps-sdk-js-ai-context all # Install everything'));
362
+ }
363
+
364
+ module.exports = {
365
+ installClaude,
366
+ installCopilot,
367
+ installAll,
368
+ listContexts,
369
+ getAvailableVersions,
370
+ getLatestVersion,
371
+ colors,
372
+ colorize,
373
+ log,
374
+ success,
375
+ error,
376
+ info,
377
+ warning,
378
+ header,
379
+ };