@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/CHANGELOG.md +68 -0
- package/README.md +163 -0
- package/bin/cli.js +8 -0
- package/dist/init.d.ts +19 -0
- package/dist/init.js +616 -0
- package/docs/switch-api/api-connection.md +63 -0
- package/docs/switch-api/api-debugging.md +51 -0
- package/docs/switch-api/api-document-classes.md +167 -0
- package/docs/switch-api/api-entry-points.md +82 -0
- package/docs/switch-api/api-enums.md +153 -0
- package/docs/switch-api/api-execution-environment.md +70 -0
- package/docs/switch-api/api-flow-element.md +112 -0
- package/docs/switch-api/api-http.md +87 -0
- package/docs/switch-api/api-job-patterns.md +79 -0
- package/docs/switch-api/api-job.md +156 -0
- package/docs/switch-api/api-logging.md +53 -0
- package/docs/switch-api/api-logs-and-dataroot.md +71 -0
- package/docs/switch-api/api-property-editors.md +144 -0
- package/docs/switch-api/api-script-declaration.md +351 -0
- package/docs/switch-api/api-script-structure.md +85 -0
- package/docs/switch-api/api-switch.md +75 -0
- package/docs/switch-api/api-tooling.md +61 -0
- package/docs/switch-api/api-vscode.md +62 -0
- package/docs/switch-scripting.md +40 -0
- package/package.json +60 -0
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`.
|