@enfocussw/switch-scripting-context 25.11.0-beta.8

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/dist/init.js ADDED
@@ -0,0 +1,616 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.generateAgentsMd = generateAgentsMd;
37
+ exports.generateGeminiMd = generateGeminiMd;
38
+ exports.generateWindsurfRules = generateWindsurfRules;
39
+ exports.generateZedRules = generateZedRules;
40
+ exports.generateClineRules = generateClineRules;
41
+ exports.buildIdMap = buildIdMap;
42
+ exports.findMarkerRange = findMarkerRange;
43
+ exports.run = run;
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const readline = __importStar(require("readline"));
47
+ // ─── Markers ─────────────────────────────────────────────────────────────────
48
+ const MARKER_BEGIN = '<!-- switch-scripting-context begin -->';
49
+ const MARKER_END = '<!-- switch-scripting-context end -->';
50
+ // ─── Content generators ──────────────────────────────────────────────────────
51
+ function generateClaudeMd(docsDir) {
52
+ return `${MARKER_BEGIN}
53
+ ## Switch Scripting (Enfocus Switch)
54
+
55
+ @${docsDir}/switch-scripting.md
56
+ ${MARKER_END}`;
57
+ }
58
+ function generateCopilotInstructions(docsDir) {
59
+ return `${MARKER_BEGIN}
60
+ ## Switch Scripting (Enfocus Switch)
61
+
62
+ This project uses Enfocus Switch scripting (Node.js/TypeScript).
63
+ Consult the Switch scripting reference before writing or modifying code.
64
+
65
+ #file:../${docsDir}/switch-scripting.md
66
+ ${MARKER_END}`;
67
+ }
68
+ function generateCopilotScopedInstructions(docsDir) {
69
+ return `---
70
+ applyTo: "**/*.ts"
71
+ ---
72
+ ${MARKER_BEGIN}
73
+ You are working on an Enfocus Switch scripting project (Node.js/TypeScript).
74
+
75
+ #file:../../${docsDir}/switch-scripting.md
76
+ ${MARKER_END}`;
77
+ }
78
+ function generateCursorMdc(docsDir) {
79
+ const apiFiles = [
80
+ 'api-entry-points.md',
81
+ 'api-job.md',
82
+ 'api-flow-element.md',
83
+ 'api-connection.md',
84
+ 'api-http.md',
85
+ 'api-enums.md',
86
+ 'api-document-classes.md',
87
+ 'api-script-declaration.md',
88
+ 'api-script-structure.md',
89
+ 'api-tooling.md',
90
+ 'api-debugging.md',
91
+ 'api-vscode.md',
92
+ 'api-property-editors.md',
93
+ 'api-job-patterns.md',
94
+ 'api-switch.md',
95
+ 'api-execution-environment.md',
96
+ 'api-logging.md',
97
+ 'api-logs-and-dataroot.md',
98
+ ];
99
+ const fileList = apiFiles.map(f => `- \`${docsDir}/switch-api/${f}\``).join('\n');
100
+ return `---
101
+ description: Enfocus Switch scripting rules and API reference for Node.js/TypeScript scripts
102
+ globs: ["**/*.ts", "**/*.js"]
103
+ alwaysApply: false
104
+ ---
105
+
106
+ ${MARKER_BEGIN}
107
+ You are working on an Enfocus Switch scripting project (Node.js/TypeScript).
108
+
109
+ ## Core rules
110
+
111
+ - Entry point functions are top-level async functions — do NOT use \`export\`.
112
+ - Every \`jobArrived\` must end with exactly one \`job.sendTo*()\` or \`job.fail()\`.
113
+ - Use \`AccessLevel.ReadOnly\` for \`job.get()\` unless the file content will be modified.
114
+ - After \`createJob()\`, \`createChild()\`, or \`createDataset()\` with a file path, delete the source file after routing — Switch does not auto-remove it.
115
+ - Never edit \`<ScriptID>.xml\` or \`manifest.xml\` directly. For property/connection changes, instruct the user to use SwitchScripter.
116
+ - After editing \`main.ts\`, transpile with \`SwitchScriptTool --transpile <folder>\` — do not use \`tsc\` directly.
117
+ - Never use a script folder in production — pack with \`SwitchScriptTool --pack\` first; folders are excluded from flow exports.
118
+
119
+ ## API reference
120
+
121
+ Read \`${docsDir}/switch-scripting.md\` for the full index, then consult the relevant file:
122
+
123
+ ${fileList}
124
+ ${MARKER_END}`;
125
+ }
126
+ // ─── Shared inline content (for tools without @file import support) ──────────
127
+ const INLINE_CORE_RULES = `## Core rules
128
+
129
+ - Entry point functions are top-level async functions — do NOT use \`export\`.
130
+ - Every \`jobArrived\` must end with exactly one \`job.sendTo*()\` or \`job.fail()\`.
131
+ - Use \`AccessLevel.ReadOnly\` for \`job.get()\` unless the file content will be modified.
132
+ - After \`createJob()\`, \`createChild()\`, or \`createDataset()\` with a file path, delete the source file after routing — Switch does not auto-remove it.
133
+ - Never edit \`<ScriptID>.xml\` or \`manifest.xml\` directly. For property/connection changes, instruct the user to use SwitchScripter.
134
+ - After editing \`main.ts\`, transpile with \`SwitchScriptTool --transpile <folder>\` — do not use \`tsc\` directly.
135
+ - Never use a script folder in production — pack with \`SwitchScriptTool --pack\` first; folders are excluded from flow exports.`;
136
+ const API_FILES = [
137
+ 'api-entry-points.md',
138
+ 'api-job.md',
139
+ 'api-flow-element.md',
140
+ 'api-connection.md',
141
+ 'api-http.md',
142
+ 'api-enums.md',
143
+ 'api-document-classes.md',
144
+ 'api-script-declaration.md',
145
+ 'api-script-structure.md',
146
+ 'api-tooling.md',
147
+ 'api-debugging.md',
148
+ 'api-vscode.md',
149
+ 'api-property-editors.md',
150
+ 'api-job-patterns.md',
151
+ 'api-switch.md',
152
+ 'api-execution-environment.md',
153
+ 'api-logging.md',
154
+ 'api-logs-and-dataroot.md',
155
+ ];
156
+ function inlineApiFileList(docsDir) {
157
+ return API_FILES.map(f => `- \`${docsDir}/switch-api/${f}\``).join('\n');
158
+ }
159
+ function generateInlineBlock(docsDir) {
160
+ return `You are working on an Enfocus Switch scripting project (Node.js/TypeScript).
161
+
162
+ ${INLINE_CORE_RULES}
163
+
164
+ ## API reference
165
+
166
+ Read \`${docsDir}/switch-scripting.md\` for the full index, then consult the relevant file:
167
+
168
+ ${inlineApiFileList(docsDir)}`;
169
+ }
170
+ function generateAgentsMd(docsDir) {
171
+ return `${MARKER_BEGIN}
172
+ ## Switch Scripting (Enfocus Switch)
173
+
174
+ ${generateInlineBlock(docsDir)}
175
+ ${MARKER_END}`;
176
+ }
177
+ function generateGeminiMd(docsDir) {
178
+ return `${MARKER_BEGIN}
179
+ ## Switch Scripting (Enfocus Switch)
180
+
181
+ @${docsDir}/switch-scripting.md
182
+ ${MARKER_END}`;
183
+ }
184
+ function generateWindsurfRules(docsDir) {
185
+ return `${MARKER_BEGIN}
186
+ ## Switch Scripting (Enfocus Switch)
187
+
188
+ ${generateInlineBlock(docsDir)}
189
+ ${MARKER_END}`;
190
+ }
191
+ function generateZedRules(docsDir) {
192
+ return `${MARKER_BEGIN}
193
+ ## Switch Scripting (Enfocus Switch)
194
+
195
+ ${generateInlineBlock(docsDir)}
196
+ ${MARKER_END}`;
197
+ }
198
+ function generateClineRules(docsDir) {
199
+ return `${MARKER_BEGIN}
200
+ ## Switch Scripting (Enfocus Switch)
201
+
202
+ ${generateInlineBlock(docsDir)}
203
+ ${MARKER_END}`;
204
+ }
205
+ // ─── Tool registry ───────────────────────────────────────────────────────────
206
+ const TOOL_REGISTRY = [
207
+ {
208
+ id: 'claude',
209
+ label: 'Claude Code',
210
+ aliases: ['ClawCode'],
211
+ altIds: ['clawcode'],
212
+ files: [
213
+ {
214
+ targetPath: (root) => path.join(root, 'CLAUDE.md'),
215
+ generate: generateClaudeMd,
216
+ },
217
+ ],
218
+ },
219
+ {
220
+ id: 'copilot',
221
+ label: 'GitHub Copilot',
222
+ files: [
223
+ {
224
+ targetPath: (root) => path.join(root, '.github', 'copilot-instructions.md'),
225
+ generate: generateCopilotInstructions,
226
+ },
227
+ {
228
+ targetPath: (root) => path.join(root, '.github', 'instructions', 'switch-scripting.instructions.md'),
229
+ generate: generateCopilotScopedInstructions,
230
+ },
231
+ ],
232
+ },
233
+ {
234
+ id: 'cursor',
235
+ label: 'Cursor',
236
+ files: [
237
+ {
238
+ targetPath: (root) => path.join(root, '.cursor', 'rules', 'switch-scripting.mdc'),
239
+ generate: generateCursorMdc,
240
+ },
241
+ ],
242
+ },
243
+ {
244
+ id: 'codex',
245
+ label: 'Codex CLI / OpenCode',
246
+ altIds: ['opencode'],
247
+ files: [
248
+ {
249
+ targetPath: (root) => path.join(root, 'AGENTS.md'),
250
+ generate: generateAgentsMd,
251
+ },
252
+ ],
253
+ },
254
+ {
255
+ id: 'gemini',
256
+ label: 'Gemini CLI',
257
+ files: [
258
+ {
259
+ targetPath: (root) => path.join(root, 'GEMINI.md'),
260
+ generate: generateGeminiMd,
261
+ },
262
+ ],
263
+ },
264
+ {
265
+ id: 'windsurf',
266
+ label: 'Windsurf',
267
+ files: [
268
+ {
269
+ targetPath: (root) => path.join(root, '.windsurfrules'),
270
+ generate: generateWindsurfRules,
271
+ },
272
+ ],
273
+ },
274
+ {
275
+ id: 'zed',
276
+ label: 'Zed',
277
+ files: [
278
+ {
279
+ targetPath: (root) => path.join(root, '.rules'),
280
+ generate: generateZedRules,
281
+ },
282
+ ],
283
+ },
284
+ {
285
+ id: 'cline',
286
+ label: 'Cline',
287
+ files: [
288
+ {
289
+ targetPath: (root) => path.join(root, '.clinerules'),
290
+ generate: generateClineRules,
291
+ },
292
+ ],
293
+ },
294
+ ];
295
+ // ─── ID map ──────────────────────────────────────────────────────────────────
296
+ function buildIdMap(registry) {
297
+ const map = new Map();
298
+ for (const t of registry) {
299
+ map.set(t.id, t.id);
300
+ for (const alt of t.altIds ?? [])
301
+ map.set(alt, t.id);
302
+ }
303
+ return map;
304
+ }
305
+ // ─── Merge logic ─────────────────────────────────────────────────────────────
306
+ function findMarkerRange(content) {
307
+ const begin = content.indexOf(MARKER_BEGIN);
308
+ const end = content.indexOf(MARKER_END);
309
+ if (begin === -1 || end === -1)
310
+ return null;
311
+ if (begin >= end)
312
+ return null;
313
+ return [begin, end + MARKER_END.length];
314
+ }
315
+ function resolveFileAction(targetPath, block, force) {
316
+ if (!fs.existsSync(targetPath)) {
317
+ return { path: targetPath, action: 'create', content: block };
318
+ }
319
+ if (force) {
320
+ return { path: targetPath, action: 'overwrite', content: block };
321
+ }
322
+ const existing = fs.readFileSync(targetPath, 'utf8');
323
+ const range = findMarkerRange(existing);
324
+ if (range) {
325
+ const updated = existing.slice(0, range[0]) + block + existing.slice(range[1]);
326
+ return { path: targetPath, action: 'merge-replace', content: updated };
327
+ }
328
+ const separator = existing.endsWith('\n') ? '\n' : '\n\n';
329
+ return { path: targetPath, action: 'merge-append', content: existing + separator + block + '\n' };
330
+ }
331
+ function applyFileAction(action, dryRun) {
332
+ const rel = path.relative(process.cwd(), action.path);
333
+ const label = action.action === 'create' ? 'created'
334
+ : action.action === 'overwrite' ? 'overwritten'
335
+ : action.action === 'merge-replace' ? 'updated (replaced Switch section)'
336
+ : 'updated (appended Switch section)';
337
+ if (dryRun) {
338
+ console.log(` [dry-run] ${rel} → ${label}`);
339
+ return;
340
+ }
341
+ const dir = path.dirname(action.path);
342
+ if (!fs.existsSync(dir))
343
+ fs.mkdirSync(dir, { recursive: true });
344
+ fs.writeFileSync(action.path, action.content, 'utf8');
345
+ console.log(` ${rel} → ${label}`);
346
+ }
347
+ // ─── Docs copy ────────────────────────────────────────────────────────────────
348
+ function copyDocs(packageRoot, destDir, dryRun) {
349
+ const src = path.join(packageRoot, 'docs');
350
+ if (dryRun) {
351
+ console.log(` [dry-run] Copying docs/ → ${path.relative(process.cwd(), destDir)}/`);
352
+ return;
353
+ }
354
+ fs.cpSync(src, destDir, { recursive: true });
355
+ console.log(` docs → ${path.relative(process.cwd(), destDir)}/`);
356
+ }
357
+ // ─── .gitignore update ───────────────────────────────────────────────────────
358
+ function updateGitignore(targetRoot, docsDir, dryRun) {
359
+ const gitignorePath = path.join(targetRoot, '.gitignore');
360
+ const entry = `\n# switch-scripting-context (AI context docs — re-generate with npx switch-scripting-context init)\n${docsDir}/\n`;
361
+ if (!fs.existsSync(gitignorePath))
362
+ return;
363
+ const content = fs.readFileSync(gitignorePath, 'utf8');
364
+ if (content.includes(`${docsDir}/`))
365
+ return;
366
+ if (dryRun) {
367
+ console.log(` [dry-run] .gitignore → append ${docsDir}/ entry`);
368
+ return;
369
+ }
370
+ fs.appendFileSync(gitignorePath, entry, 'utf8');
371
+ console.log(` .gitignore → appended ${docsDir}/ entry`);
372
+ }
373
+ /** All AI config file paths that init may have created. */
374
+ function aiConfigPaths(targetRoot, docsDir) {
375
+ return TOOL_REGISTRY.flatMap(t => t.files.map(f => f.targetPath(targetRoot, docsDir)));
376
+ }
377
+ function removeAiConfig(filePath, dryRun) {
378
+ if (!fs.existsSync(filePath))
379
+ return;
380
+ const content = fs.readFileSync(filePath, 'utf8');
381
+ const range = findMarkerRange(content);
382
+ if (!range)
383
+ return; // nothing from us in this file
384
+ const rel = path.relative(process.cwd(), filePath);
385
+ const before = content.slice(0, range[0]).trim();
386
+ const after = content.slice(range[1]).trim();
387
+ if (!before && !after) {
388
+ // File is entirely our content — delete it
389
+ if (dryRun) {
390
+ console.log(` [dry-run] ${rel} → deleted`);
391
+ return;
392
+ }
393
+ fs.unlinkSync(filePath);
394
+ console.log(` ${rel} → deleted`);
395
+ }
396
+ else {
397
+ // File has other content — strip our section
398
+ const stripped = (before ? before + '\n' : '') + (after ? '\n' + after + '\n' : '');
399
+ if (dryRun) {
400
+ console.log(` [dry-run] ${rel} → removed Switch section`);
401
+ return;
402
+ }
403
+ fs.writeFileSync(filePath, stripped, 'utf8');
404
+ console.log(` ${rel} → removed Switch section`);
405
+ }
406
+ }
407
+ function removeGitignoreEntry(targetRoot, docsDir, dryRun) {
408
+ const gitignorePath = path.join(targetRoot, '.gitignore');
409
+ if (!fs.existsSync(gitignorePath))
410
+ return;
411
+ const content = fs.readFileSync(gitignorePath, 'utf8');
412
+ // Remove the comment line and the docsDir/ line added by init
413
+ const updated = content
414
+ .replace(/\n# switch-scripting-context \(AI context docs[^\n]*\)\n/g, '\n')
415
+ .replace(new RegExp(`\n${docsDir}/\n`, 'g'), '\n');
416
+ if (updated === content)
417
+ return;
418
+ if (dryRun) {
419
+ console.log(` [dry-run] .gitignore → removed ${docsDir}/ entry`);
420
+ return;
421
+ }
422
+ fs.writeFileSync(gitignorePath, updated, 'utf8');
423
+ console.log(` .gitignore → removed ${docsDir}/ entry`);
424
+ }
425
+ function remove(options) {
426
+ const targetRoot = process.cwd();
427
+ const docsDestDir = path.join(targetRoot, options.docsDir);
428
+ if (options.dryRun)
429
+ console.log('\nDry run — no files will be written.\n');
430
+ // 1. Remove docs dir
431
+ console.log('Removing docs...');
432
+ if (fs.existsSync(docsDestDir)) {
433
+ if (options.dryRun) {
434
+ console.log(` [dry-run] ${options.docsDir}/ → deleted`);
435
+ }
436
+ else {
437
+ fs.rmSync(docsDestDir, { recursive: true, force: true });
438
+ console.log(` ${options.docsDir}/ → deleted`);
439
+ }
440
+ }
441
+ else {
442
+ console.log(` ${options.docsDir}/ → not found, skipping`);
443
+ }
444
+ // 2. Remove AI config sections / files
445
+ console.log('\nCleaning AI config files...');
446
+ for (const filePath of aiConfigPaths(targetRoot, options.docsDir)) {
447
+ removeAiConfig(filePath, options.dryRun);
448
+ }
449
+ // 3. Update .gitignore
450
+ console.log('\nUpdating .gitignore...');
451
+ removeGitignoreEntry(targetRoot, options.docsDir, options.dryRun);
452
+ console.log('\nDone.\n');
453
+ }
454
+ // ─── Main init ───────────────────────────────────────────────────────────────
455
+ function init(options) {
456
+ const targetRoot = process.cwd();
457
+ const packageRoot = path.resolve(__dirname, '..');
458
+ const docsDestDir = path.join(targetRoot, options.docsDir);
459
+ if (options.dryRun)
460
+ console.log('\nDry run — no files will be written.\n');
461
+ // 1. Copy docs
462
+ console.log('Copying docs...');
463
+ copyDocs(packageRoot, docsDestDir, options.dryRun);
464
+ // 2. Generate AI config files
465
+ const toolActions = [];
466
+ for (const toolDef of TOOL_REGISTRY) {
467
+ if (!options.tools.includes(toolDef.id))
468
+ continue;
469
+ for (const f of toolDef.files) {
470
+ toolActions.push({
471
+ targetPath: f.targetPath(targetRoot, options.docsDir),
472
+ block: f.generate(options.docsDir),
473
+ });
474
+ }
475
+ }
476
+ if (toolActions.length > 0) {
477
+ console.log('\nGenerating AI config files...');
478
+ for (const { targetPath, block } of toolActions) {
479
+ const action = resolveFileAction(targetPath, block, options.force);
480
+ applyFileAction(action, options.dryRun);
481
+ }
482
+ }
483
+ // 3. Update .gitignore
484
+ console.log('\nUpdating .gitignore...');
485
+ updateGitignore(targetRoot, options.docsDir, options.dryRun);
486
+ console.log('\nDone. Re-run after upgrading switch-scripting-context to refresh docs and config.\n');
487
+ }
488
+ // ─── CLI arg parsing ──────────────────────────────────────────────────────────
489
+ function parseArgs(argv) {
490
+ const args = argv.slice(2);
491
+ const subcommand = args[0] ?? 'help';
492
+ let toolsExplicit = false;
493
+ const initOptions = {
494
+ tools: TOOL_REGISTRY.map(t => t.id),
495
+ docsDir: 'switch-docs',
496
+ force: false,
497
+ dryRun: false,
498
+ };
499
+ const removeOptions = {
500
+ docsDir: 'switch-docs',
501
+ dryRun: false,
502
+ };
503
+ for (let i = 1; i < args.length; i++) {
504
+ const arg = args[i];
505
+ if (arg === '--force') {
506
+ initOptions.force = true;
507
+ }
508
+ else if (arg === '--dry-run') {
509
+ initOptions.dryRun = true;
510
+ removeOptions.dryRun = true;
511
+ }
512
+ else if (arg === '--tools' && args[i + 1]) {
513
+ const idMap = buildIdMap(TOOL_REGISTRY);
514
+ const raw = args[++i].split(',').map(s => s.trim());
515
+ const invalid = raw.filter(t => !idMap.has(t));
516
+ if (invalid.length > 0) {
517
+ const allValid = [...idMap.keys()].sort().join(', ');
518
+ console.error(`Unknown tool(s): ${invalid.join(', ')}. Valid values: ${allValid}`);
519
+ process.exit(1);
520
+ }
521
+ initOptions.tools = raw.map(t => idMap.get(t));
522
+ toolsExplicit = true;
523
+ }
524
+ else if (arg === '--docs-dir' && args[i + 1]) {
525
+ initOptions.docsDir = args[++i];
526
+ removeOptions.docsDir = initOptions.docsDir;
527
+ }
528
+ else if (arg.startsWith('--')) {
529
+ console.error(`Unknown option: ${arg}`);
530
+ process.exit(1);
531
+ }
532
+ }
533
+ return { subcommand, initOptions, removeOptions, toolsExplicit };
534
+ }
535
+ // ─── Interactive tool prompt ──────────────────────────────────────────────────
536
+ function toolPromptLabel(t) {
537
+ if (t.aliases && t.aliases.length > 0) {
538
+ return `${t.label} (also covers ${t.aliases.join(', ')})`;
539
+ }
540
+ return t.label;
541
+ }
542
+ async function promptTools() {
543
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
544
+ console.log('\nWhich coding agent(s) do you use?');
545
+ console.log('Enter numbers separated by spaces (or press Enter to select all):\n');
546
+ TOOL_REGISTRY.forEach((t, i) => console.log(` ${i + 1}) ${toolPromptLabel(t)}`));
547
+ console.log();
548
+ return new Promise((resolve) => {
549
+ rl.question('> ', (answer) => {
550
+ rl.close();
551
+ const trimmed = answer.trim();
552
+ if (!trimmed) {
553
+ resolve(TOOL_REGISTRY.map(t => t.id));
554
+ return;
555
+ }
556
+ const indices = trimmed.split(/[\s,]+/).map(n => parseInt(n, 10) - 1);
557
+ const valid = indices.filter(i => i >= 0 && i < TOOL_REGISTRY.length);
558
+ if (valid.length === 0) {
559
+ resolve(TOOL_REGISTRY.map(t => t.id));
560
+ return;
561
+ }
562
+ resolve(valid.map(i => TOOL_REGISTRY[i].id));
563
+ });
564
+ });
565
+ }
566
+ function printHelp() {
567
+ console.log(`
568
+ Usage: switch-scripting-context <command> [options]
569
+
570
+ Commands:
571
+ init Copy docs and generate AI context config files
572
+ remove Remove all files and config added by init
573
+
574
+ Options (init):
575
+ --tools <list> Comma-separated tools to configure
576
+ IDs: claude, copilot, cursor, codex, gemini, windsurf, zed, cline
577
+ Aliases: opencode (→ codex), clawcode (→ claude)
578
+ Default: all tools
579
+ --docs-dir <dir> Destination folder for docs in target project (default: switch-docs)
580
+ --force Overwrite existing AI config files rather than merging
581
+ --dry-run Print what would happen without writing any files
582
+ --help Show this help message
583
+
584
+ Options (remove):
585
+ --docs-dir <dir> Docs folder to remove (default: switch-docs)
586
+ --dry-run Print what would happen without writing any files
587
+
588
+ Examples:
589
+ npx switch-scripting-context init
590
+ npx switch-scripting-context init --tools claude,copilot
591
+ npx switch-scripting-context init --docs-dir ai-docs --dry-run
592
+ npx switch-scripting-context remove
593
+ npx switch-scripting-context remove --dry-run
594
+ `);
595
+ }
596
+ // ─── Entry point ─────────────────────────────────────────────────────────────
597
+ async function run(argv = process.argv) {
598
+ const { subcommand, initOptions, removeOptions, toolsExplicit } = parseArgs(argv);
599
+ if (subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
600
+ printHelp();
601
+ }
602
+ else if (subcommand === 'init') {
603
+ if (!toolsExplicit) {
604
+ initOptions.tools = await promptTools();
605
+ }
606
+ init(initOptions);
607
+ }
608
+ else if (subcommand === 'remove') {
609
+ remove(removeOptions);
610
+ }
611
+ else {
612
+ console.error(`Unknown command: ${subcommand}`);
613
+ printHelp();
614
+ process.exit(1);
615
+ }
616
+ }
@@ -0,0 +1,63 @@
1
+ # Connection Class
2
+
3
+ A `Connection` represents an outgoing connection from the flow element. Obtain instances via `flowElement.getOutConnections()`.
4
+
5
+ ## Identity
6
+
7
+ ```ts
8
+ connection.getId(): string
9
+ ```
10
+ Unique ID for the connection. Stable across deactivate/reactivate, Switch restarts, holding/releasing connections, and renaming the flow itself. Changes on export/re-import, a flow upgrade, or renaming the flow *element* (not the flow).
11
+
12
+ ```ts
13
+ connection.getName(): string
14
+ ```
15
+ Display name as shown on the canvas. May be an empty string.
16
+
17
+ ```ts
18
+ connection.getType(): string
19
+ ```
20
+ Connection type. One of: `"Move"`, `"Filter"`, `"Traffic-data"`, `"Traffic-log"`, `"Traffic-datawithlog"`.
21
+
22
+ ## Connection properties
23
+
24
+ Connection properties are declared in the script's XML declaration (`ConnectionFields`) and configured per-connection in the canvas. See [api-script-declaration.md](api-script-declaration.md) for how to declare them (via SwitchScripter).
25
+
26
+ ```ts
27
+ connection.getPropertyStringValue(tag: string): Promise<string | string[]>
28
+ ```
29
+ Returns the connection property value as a string.
30
+
31
+ ```ts
32
+ connection.getPropertyType(tag: string): PropertyType
33
+ ```
34
+ Returns the `PropertyType` of the property.
35
+
36
+ ```ts
37
+ connection.getPropertyDisplayName(tag: string): string
38
+ ```
39
+ Returns the English display name of the property (for use in log messages).
40
+
41
+ ```ts
42
+ connection.hasProperty(tag: string): boolean
43
+ ```
44
+ Returns `true` if the property exists and is visible for the current configuration.
45
+
46
+ ## File count
47
+
48
+ ```ts
49
+ connection.getFileCount(nested?: boolean): Promise<number>
50
+ ```
51
+ Returns the number of files in the folder at the other end of this connection. If `nested` is `false`, counts only direct children; if `true` (the default, so it can be omitted), counts recursively (subfolders themselves are not counted).
52
+
53
+ ## Connection.Level enum
54
+
55
+ Used with `job.sendToData()` and `job.sendToLog()` for traffic light connections.
56
+
57
+ | Value | String |
58
+ |---|---|
59
+ | `Connection.Level.Success` | `"success"` |
60
+ | `Connection.Level.Warning` | `"warning"` |
61
+ | `Connection.Level.Error` | `"error"` |
62
+
63
+ Available as `EnfocusSwitch.Connection.Level.*` outside `main.ts`.