@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/README.md +4 -2
- package/contexts/4.34/claude/arcgis-starter-app/SKILL.md +273 -0
- package/contexts/4.34/claude/arcgis-starter-app-extended/SKILL.md +649 -0
- package/contexts/4.34/copilot/arcgis-starter-app-extended.instructions.md +643 -0
- package/contexts/4.34/copilot/arcgis-starter-app.instructions.md +268 -0
- package/lib/installer.js +379 -379
- package/package.json +1 -1
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
|
+
};
|