@saschabrunnerch/arcgis-maps-sdk-js-ai-context 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +163 -201
- package/bin/cli.js +157 -173
- package/contexts/4.34/{claude → skills}/arcgis-3d-advanced/SKILL.md +586 -586
- package/contexts/4.34/{claude → skills}/arcgis-advanced-layers/SKILL.md +431 -431
- package/contexts/4.34/{claude → skills}/arcgis-analysis-services/SKILL.md +607 -607
- package/contexts/4.34/{claude → skills}/arcgis-authentication/SKILL.md +301 -301
- package/contexts/4.34/{claude → skills}/arcgis-cim-symbols/SKILL.md +486 -486
- package/contexts/4.34/{claude → skills}/arcgis-coordinates-projection/SKILL.md +406 -406
- package/contexts/4.34/{claude → skills}/arcgis-core-maps/SKILL.md +739 -739
- package/contexts/4.34/{claude → skills}/arcgis-core-utilities/SKILL.md +732 -732
- package/contexts/4.34/{claude → skills}/arcgis-custom-rendering/SKILL.md +445 -445
- package/contexts/4.34/{claude → skills}/arcgis-editing-advanced/SKILL.md +702 -702
- package/contexts/4.34/{claude → skills}/arcgis-feature-effects/SKILL.md +393 -393
- package/contexts/4.34/{claude → skills}/arcgis-geometry-operations/SKILL.md +489 -489
- package/contexts/4.34/{claude → skills}/arcgis-imagery/SKILL.md +307 -307
- package/contexts/4.34/{claude → skills}/arcgis-interaction/SKILL.md +572 -572
- package/contexts/4.34/{claude → skills}/arcgis-knowledge-graphs/SKILL.md +582 -582
- package/contexts/4.34/{claude → skills}/arcgis-layers/SKILL.md +601 -601
- package/contexts/4.34/{claude → skills}/arcgis-map-tools/SKILL.md +668 -668
- package/contexts/4.34/{claude → skills}/arcgis-media-layers/SKILL.md +290 -290
- package/contexts/4.34/{claude → skills}/arcgis-portal-content/SKILL.md +679 -679
- package/contexts/4.34/{claude → skills}/arcgis-scene-effects/SKILL.md +512 -512
- package/contexts/4.34/{claude → skills}/arcgis-smart-mapping/SKILL.md +686 -686
- package/contexts/4.34/skills/arcgis-starter-app/SKILL.md +273 -0
- package/contexts/4.34/skills/arcgis-starter-app-extended/SKILL.md +649 -0
- package/contexts/4.34/{claude → skills}/arcgis-tables-forms/SKILL.md +877 -877
- package/contexts/4.34/{claude → skills}/arcgis-time-animation/SKILL.md +722 -722
- package/contexts/4.34/{claude → skills}/arcgis-utility-networks/SKILL.md +301 -301
- package/contexts/4.34/{claude → skills}/arcgis-visualization/SKILL.md +580 -580
- package/contexts/4.34/{claude → skills}/arcgis-widgets-ui/SKILL.md +574 -574
- package/lib/installer.js +294 -379
- package/package.json +45 -45
- package/contexts/4.34/copilot/arcgis-3d.instructions.md +0 -267
- package/contexts/4.34/copilot/arcgis-analysis.instructions.md +0 -294
- package/contexts/4.34/copilot/arcgis-arcade.instructions.md +0 -234
- package/contexts/4.34/copilot/arcgis-authentication.instructions.md +0 -187
- package/contexts/4.34/copilot/arcgis-cim-symbols.instructions.md +0 -177
- package/contexts/4.34/copilot/arcgis-core-maps.instructions.md +0 -246
- package/contexts/4.34/copilot/arcgis-core-utilities.instructions.md +0 -247
- package/contexts/4.34/copilot/arcgis-editing.instructions.md +0 -262
- package/contexts/4.34/copilot/arcgis-geometry.instructions.md +0 -225
- package/contexts/4.34/copilot/arcgis-layers.instructions.md +0 -278
- package/contexts/4.34/copilot/arcgis-popup-templates.instructions.md +0 -266
- package/contexts/4.34/copilot/arcgis-portal-advanced.instructions.md +0 -275
- package/contexts/4.34/copilot/arcgis-smart-mapping.instructions.md +0 -184
- package/contexts/4.34/copilot/arcgis-time-animation.instructions.md +0 -112
- package/contexts/4.34/copilot/arcgis-visualization.instructions.md +0 -321
- package/contexts/4.34/copilot/arcgis-widgets-ui.instructions.md +0 -277
- /package/contexts/4.34/{claude → skills}/arcgis-arcade/SKILL.md +0 -0
- /package/contexts/4.34/{claude → skills}/arcgis-popup-templates/SKILL.md +0 -0
package/lib/installer.js
CHANGED
|
@@ -1,379 +1,294 @@
|
|
|
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
|
|
173
|
-
*/
|
|
174
|
-
function
|
|
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, '.
|
|
183
|
-
const srcPath = path.join(getContextsDir(), version, '
|
|
184
|
-
|
|
185
|
-
header(`Installing
|
|
186
|
-
info(`Source: ${srcPath}`);
|
|
187
|
-
info(`Target: ${destPath}`);
|
|
188
|
-
|
|
189
|
-
if (!fs.existsSync(srcPath)) {
|
|
190
|
-
error(`
|
|
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}
|
|
205
|
-
success(`Location: ${colorize('cyan', destPath)}`);
|
|
206
|
-
return true;
|
|
207
|
-
} catch (err) {
|
|
208
|
-
error(`Failed to install
|
|
209
|
-
return false;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
*
|
|
215
|
-
*/
|
|
216
|
-
function
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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 Agent Skills
|
|
173
|
+
*/
|
|
174
|
+
function installSkills(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, '.github', 'skills');
|
|
183
|
+
const srcPath = path.join(getContextsDir(), version, 'skills');
|
|
184
|
+
|
|
185
|
+
header(`Installing Agent Skills (SDK ${version})`);
|
|
186
|
+
info(`Source: ${srcPath}`);
|
|
187
|
+
info(`Target: ${destPath}`);
|
|
188
|
+
|
|
189
|
+
if (!fs.existsSync(srcPath)) {
|
|
190
|
+
error(`Agent 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} Agent Skills files for SDK ${version}`);
|
|
205
|
+
success(`Location: ${colorize('cyan', destPath)}`);
|
|
206
|
+
return true;
|
|
207
|
+
} catch (err) {
|
|
208
|
+
error(`Failed to install Agent Skills: ${err.message}`);
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* List available contexts
|
|
215
|
+
*/
|
|
216
|
+
function listContexts() {
|
|
217
|
+
const contextsDir = getContextsDir();
|
|
218
|
+
const versions = getAvailableVersions();
|
|
219
|
+
|
|
220
|
+
header('Available Agent Skills');
|
|
221
|
+
log('');
|
|
222
|
+
|
|
223
|
+
// Show available versions
|
|
224
|
+
log(`${colorize('bold', colorize('blue', 'Available SDK Versions'))}`);
|
|
225
|
+
if (versions.length > 0) {
|
|
226
|
+
const latestVersion = versions[versions.length - 1];
|
|
227
|
+
for (const version of versions) {
|
|
228
|
+
const isLatest = version === latestVersion;
|
|
229
|
+
const label = isLatest ? ` ${colorize('green', '(latest)')}` : '';
|
|
230
|
+
log(` ${colorize('green', '\u2022')} ${colorize('cyan', version)}${label}`);
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
log(` ${colorize('dim', 'No versions available')}`);
|
|
234
|
+
}
|
|
235
|
+
log('');
|
|
236
|
+
|
|
237
|
+
// Show contents for latest version
|
|
238
|
+
const latestVersion = getLatestVersion();
|
|
239
|
+
if (!latestVersion) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Agent Skills
|
|
244
|
+
const skillsDir = path.join(contextsDir, latestVersion, 'skills');
|
|
245
|
+
if (fs.existsSync(skillsDir)) {
|
|
246
|
+
log(`${colorize('bold', colorize('blue', `Agent Skills (SDK ${latestVersion})`))}`);
|
|
247
|
+
log(colorize('dim', ` Installs to: .github/skills/`));
|
|
248
|
+
|
|
249
|
+
const entries = fs.readdirSync(skillsDir, { withFileTypes: true })
|
|
250
|
+
.filter(e => e.isDirectory())
|
|
251
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
252
|
+
|
|
253
|
+
for (const entry of entries) {
|
|
254
|
+
const skillPath = path.join(skillsDir, entry.name, 'SKILL.md');
|
|
255
|
+
if (fs.existsSync(skillPath)) {
|
|
256
|
+
// Read first few lines to get name and description
|
|
257
|
+
const content = fs.readFileSync(skillPath, 'utf8');
|
|
258
|
+
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
259
|
+
const descMatch = content.match(/^description:\s*(.+)$/m);
|
|
260
|
+
|
|
261
|
+
const name = nameMatch ? nameMatch[1] : entry.name;
|
|
262
|
+
const desc = descMatch ? descMatch[1].substring(0, 60) + (descMatch[1].length > 60 ? '...' : '') : '';
|
|
263
|
+
|
|
264
|
+
log(` ${colorize('green', '\u2022')} ${colorize('cyan', name)}`);
|
|
265
|
+
if (desc) {
|
|
266
|
+
log(` ${colorize('dim', desc)}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const skillCount = entries.length;
|
|
272
|
+
log(` ${colorize('dim', `(${skillCount} skills total)`)}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
log('');
|
|
276
|
+
log(colorize('dim', 'Usage:'));
|
|
277
|
+
log(colorize('dim', ' npx @saschabrunnerch/arcgis-maps-sdk-js-ai-context skills # Install skills (latest)'));
|
|
278
|
+
log(colorize('dim', ' npx @saschabrunnerch/arcgis-maps-sdk-js-ai-context skills --sdk 4.34 # Install for specific version'));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
module.exports = {
|
|
282
|
+
installSkills,
|
|
283
|
+
listContexts,
|
|
284
|
+
getAvailableVersions,
|
|
285
|
+
getLatestVersion,
|
|
286
|
+
colors,
|
|
287
|
+
colorize,
|
|
288
|
+
log,
|
|
289
|
+
success,
|
|
290
|
+
error,
|
|
291
|
+
info,
|
|
292
|
+
warning,
|
|
293
|
+
header,
|
|
294
|
+
};
|