@rbbtsn0w/adg 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +308 -0
- package/bin/adg.ts +758 -0
- package/docs/agents-spec.md +132 -0
- package/docs/authoring.md +352 -0
- package/package.json +50 -0
- package/schemas/adg-plugin.schema.json +77 -0
- package/schemas/marketplace.schema.json +86 -0
- package/schemas/plugin-lock.schema.json +90 -0
- package/src/adapters/anthropic.ts +54 -0
- package/src/adapters/index.ts +24 -0
- package/src/adapters/openai.ts +37 -0
- package/src/adapters/reverse.ts +60 -0
- package/src/agents/claude.ts +124 -0
- package/src/agents/codex.ts +67 -0
- package/src/agents/index.ts +12 -0
- package/src/agents/registry.ts +30 -0
- package/src/agents/types.ts +47 -0
- package/src/commands/adapt.ts +36 -0
- package/src/commands/import.ts +69 -0
- package/src/commands/init.ts +146 -0
- package/src/commands/install.ts +411 -0
- package/src/commands/link.ts +61 -0
- package/src/commands/list.ts +28 -0
- package/src/commands/marketplace.ts +198 -0
- package/src/commands/migrate.ts +84 -0
- package/src/commands/multiselect-skills.ts +137 -0
- package/src/commands/remove.ts +136 -0
- package/src/commands/select-agents.ts +45 -0
- package/src/commands/select-components.ts +66 -0
- package/src/commands/select-plugins.ts +28 -0
- package/src/commands/select-scope.ts +21 -0
- package/src/commands/update.ts +85 -0
- package/src/commands/validate.ts +57 -0
- package/src/components.ts +90 -0
- package/src/deps.ts +64 -0
- package/src/fsutil.ts +38 -0
- package/src/hash.ts +61 -0
- package/src/lock.ts +57 -0
- package/src/manifest.ts +113 -0
- package/src/marketplace.ts +41 -0
- package/src/package.ts +74 -0
- package/src/paths.ts +129 -0
- package/src/semver.ts +67 -0
- package/src/skills.ts +88 -0
- package/src/sources.ts +159 -0
- package/src/types.ts +140 -0
- package/vendor/skills/LICENSE +29 -0
- package/vendor/skills/PROVENANCE.md +60 -0
- package/vendor/skills/ThirdPartyNoticeText.txt +117 -0
- package/vendor/skills/package.json +143 -0
- package/vendor/skills/src/add.ts +1999 -0
- package/vendor/skills/src/agents.ts +755 -0
- package/vendor/skills/src/blob.ts +567 -0
- package/vendor/skills/src/cli.ts +387 -0
- package/vendor/skills/src/constants.ts +3 -0
- package/vendor/skills/src/detect-agent.ts +62 -0
- package/vendor/skills/src/find.ts +357 -0
- package/vendor/skills/src/frontmatter.ts +16 -0
- package/vendor/skills/src/git-tree.ts +36 -0
- package/vendor/skills/src/git.ts +277 -0
- package/vendor/skills/src/install.ts +91 -0
- package/vendor/skills/src/installer.ts +1097 -0
- package/vendor/skills/src/list.ts +231 -0
- package/vendor/skills/src/local-lock.ts +182 -0
- package/vendor/skills/src/plugin-manifest.ts +183 -0
- package/vendor/skills/src/prompts/search-multiselect.ts +387 -0
- package/vendor/skills/src/providers/index.ts +14 -0
- package/vendor/skills/src/providers/registry.ts +51 -0
- package/vendor/skills/src/providers/types.ts +97 -0
- package/vendor/skills/src/providers/wellknown.ts +804 -0
- package/vendor/skills/src/remove.ts +323 -0
- package/vendor/skills/src/sanitize.ts +65 -0
- package/vendor/skills/src/self-cli.ts +20 -0
- package/vendor/skills/src/skill-lock.ts +329 -0
- package/vendor/skills/src/skills.ts +316 -0
- package/vendor/skills/src/source-parser.ts +438 -0
- package/vendor/skills/src/sync.ts +478 -0
- package/vendor/skills/src/telemetry.ts +186 -0
- package/vendor/skills/src/test-utils.ts +73 -0
- package/vendor/skills/src/types.ts +128 -0
- package/vendor/skills/src/update-source.ts +90 -0
- package/vendor/skills/src/update.ts +749 -0
- package/vendor/skills/src/use.ts +675 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { readdir, rm, lstat } from 'fs/promises';
|
|
4
|
+
import { join } from 'path';
|
|
5
|
+
import { agents, detectInstalledAgents } from './agents.ts';
|
|
6
|
+
import { track } from './telemetry.ts';
|
|
7
|
+
import { detectAgent } from './detect-agent.ts';
|
|
8
|
+
import { removeSkillFromLock, getSkillFromLock } from './skill-lock.ts';
|
|
9
|
+
import type { AgentType } from './types.ts';
|
|
10
|
+
import {
|
|
11
|
+
getInstallPath,
|
|
12
|
+
getCanonicalPath,
|
|
13
|
+
getCanonicalSkillsDir,
|
|
14
|
+
sanitizeName,
|
|
15
|
+
} from './installer.ts';
|
|
16
|
+
|
|
17
|
+
export interface RemoveOptions {
|
|
18
|
+
global?: boolean;
|
|
19
|
+
agent?: string[];
|
|
20
|
+
yes?: boolean;
|
|
21
|
+
all?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function removeCommand(skillNames: string[], options: RemoveOptions) {
|
|
25
|
+
// Auto-enable non-interactive mode when running inside an AI agent
|
|
26
|
+
const agentResult = await detectAgent();
|
|
27
|
+
if (agentResult.isAgent) {
|
|
28
|
+
options.yes = true;
|
|
29
|
+
p.log.info(
|
|
30
|
+
pc.bgCyan(pc.black(pc.bold(` ${agentResult.agent.name} `))) +
|
|
31
|
+
' ' +
|
|
32
|
+
'Agent detected — removing non-interactively'
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const isGlobal = options.global ?? false;
|
|
37
|
+
const cwd = process.cwd();
|
|
38
|
+
|
|
39
|
+
const spinner = p.spinner();
|
|
40
|
+
|
|
41
|
+
spinner.start('Scanning for installed skills...');
|
|
42
|
+
const skillNamesSet = new Set<string>();
|
|
43
|
+
|
|
44
|
+
const scanDir = async (dir: string) => {
|
|
45
|
+
try {
|
|
46
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
47
|
+
for (const entry of entries) {
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
skillNamesSet.add(entry.name);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (err instanceof Error && (err as { code?: string }).code !== 'ENOENT') {
|
|
54
|
+
p.log.warn(`Could not scan directory ${dir}: ${err.message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
if (isGlobal) {
|
|
60
|
+
await scanDir(getCanonicalSkillsDir(true, cwd));
|
|
61
|
+
for (const agent of Object.values(agents)) {
|
|
62
|
+
if (agent.globalSkillsDir !== undefined) {
|
|
63
|
+
await scanDir(agent.globalSkillsDir);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
await scanDir(getCanonicalSkillsDir(false, cwd));
|
|
68
|
+
for (const agent of Object.values(agents)) {
|
|
69
|
+
await scanDir(join(cwd, agent.skillsDir));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const installedSkills = Array.from(skillNamesSet).sort();
|
|
74
|
+
spinner.stop(`Found ${installedSkills.length} unique installed skill(s)`);
|
|
75
|
+
|
|
76
|
+
if (installedSkills.length === 0) {
|
|
77
|
+
p.outro(pc.yellow('No skills found to remove.'));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Validate agent options BEFORE prompting for skill selection
|
|
82
|
+
if (options.agent && options.agent.length > 0) {
|
|
83
|
+
const validAgents = Object.keys(agents);
|
|
84
|
+
const invalidAgents = options.agent.filter((a) => !validAgents.includes(a));
|
|
85
|
+
|
|
86
|
+
if (invalidAgents.length > 0) {
|
|
87
|
+
p.log.error(`Invalid agents: ${invalidAgents.join(', ')}`);
|
|
88
|
+
p.log.info(`Valid agents: ${validAgents.join(', ')}`);
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let selectedSkills: string[] = [];
|
|
94
|
+
|
|
95
|
+
if (options.all) {
|
|
96
|
+
selectedSkills = installedSkills;
|
|
97
|
+
} else if (skillNames.length > 0) {
|
|
98
|
+
selectedSkills = installedSkills.filter((s) =>
|
|
99
|
+
skillNames.some((name) => name.toLowerCase() === s.toLowerCase())
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
if (selectedSkills.length === 0) {
|
|
103
|
+
p.log.error(`No matching skills found for: ${skillNames.join(', ')}`);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
const choices = installedSkills.map((s) => ({
|
|
108
|
+
value: s,
|
|
109
|
+
label: s,
|
|
110
|
+
}));
|
|
111
|
+
|
|
112
|
+
const selected = await p.multiselect({
|
|
113
|
+
message: `Select skills to remove ${pc.dim('(space to toggle)')}`,
|
|
114
|
+
options: choices,
|
|
115
|
+
required: true,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (p.isCancel(selected)) {
|
|
119
|
+
p.cancel('Removal cancelled');
|
|
120
|
+
process.exit(0);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
selectedSkills = selected as string[];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
let targetAgents: AgentType[];
|
|
127
|
+
if (options.agent && options.agent.length > 0) {
|
|
128
|
+
targetAgents = options.agent as AgentType[];
|
|
129
|
+
} else {
|
|
130
|
+
// When removing, we should target all known agents to ensure
|
|
131
|
+
// ghost symlinks are cleaned up, even if the agent is not detected.
|
|
132
|
+
targetAgents = Object.keys(agents) as AgentType[];
|
|
133
|
+
spinner.stop(`Targeting ${targetAgents.length} potential agent(s)`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!options.yes) {
|
|
137
|
+
console.log();
|
|
138
|
+
p.log.info('Skills to remove:');
|
|
139
|
+
for (const skill of selectedSkills) {
|
|
140
|
+
p.log.message(` ${pc.red('•')} ${skill}`);
|
|
141
|
+
}
|
|
142
|
+
console.log();
|
|
143
|
+
|
|
144
|
+
const confirmed = await p.confirm({
|
|
145
|
+
message: `Are you sure you want to uninstall ${selectedSkills.length} skill(s)?`,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
149
|
+
p.cancel('Removal cancelled');
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
spinner.start('Removing skills...');
|
|
155
|
+
|
|
156
|
+
const results: {
|
|
157
|
+
skill: string;
|
|
158
|
+
success: boolean;
|
|
159
|
+
source?: string;
|
|
160
|
+
sourceType?: string;
|
|
161
|
+
error?: string;
|
|
162
|
+
}[] = [];
|
|
163
|
+
|
|
164
|
+
for (const skillName of selectedSkills) {
|
|
165
|
+
try {
|
|
166
|
+
const canonicalPath = getCanonicalPath(skillName, { global: isGlobal, cwd });
|
|
167
|
+
|
|
168
|
+
for (const agentKey of targetAgents) {
|
|
169
|
+
const agent = agents[agentKey];
|
|
170
|
+
const skillPath = getInstallPath(skillName, agentKey, { global: isGlobal, cwd });
|
|
171
|
+
|
|
172
|
+
// Determine potential paths to cleanup. For universal agents, getInstallPath
|
|
173
|
+
// now returns the canonical path, so we also need to check their 'native'
|
|
174
|
+
// directory to clean up any legacy symlinks.
|
|
175
|
+
const pathsToCleanup = new Set([skillPath]);
|
|
176
|
+
const sanitizedName = sanitizeName(skillName);
|
|
177
|
+
if (isGlobal && agent.globalSkillsDir) {
|
|
178
|
+
pathsToCleanup.add(join(agent.globalSkillsDir, sanitizedName));
|
|
179
|
+
} else {
|
|
180
|
+
pathsToCleanup.add(join(cwd, agent.skillsDir, sanitizedName));
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
for (const pathToCleanup of pathsToCleanup) {
|
|
184
|
+
// Skip if this is the canonical path - we'll handle that after checking all agents
|
|
185
|
+
if (pathToCleanup === canonicalPath) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const stats = await lstat(pathToCleanup).catch(() => null);
|
|
191
|
+
if (stats) {
|
|
192
|
+
await rm(pathToCleanup, { recursive: true, force: true });
|
|
193
|
+
}
|
|
194
|
+
} catch (err) {
|
|
195
|
+
p.log.warn(
|
|
196
|
+
`Could not remove skill from ${agent.displayName}: ${
|
|
197
|
+
err instanceof Error ? err.message : String(err)
|
|
198
|
+
}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Only remove the canonical path if no other installed agents are using it.
|
|
205
|
+
// This prevents breaking other agents when uninstalling from a specific agent (#287).
|
|
206
|
+
const installedAgents = await detectInstalledAgents();
|
|
207
|
+
const remainingAgents = installedAgents.filter((a) => !targetAgents.includes(a));
|
|
208
|
+
|
|
209
|
+
let isStillUsed = false;
|
|
210
|
+
for (const agentKey of remainingAgents) {
|
|
211
|
+
const path = getInstallPath(skillName, agentKey, { global: isGlobal, cwd });
|
|
212
|
+
const exists = await lstat(path).catch(() => null);
|
|
213
|
+
if (exists) {
|
|
214
|
+
isStillUsed = true;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!isStillUsed) {
|
|
220
|
+
await rm(canonicalPath, { recursive: true, force: true });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const lockEntry = isGlobal ? await getSkillFromLock(skillName) : null;
|
|
224
|
+
const effectiveSource = lockEntry?.source || 'local';
|
|
225
|
+
const effectiveSourceType = lockEntry?.sourceType || 'local';
|
|
226
|
+
|
|
227
|
+
if (isGlobal) {
|
|
228
|
+
await removeSkillFromLock(skillName);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
results.push({
|
|
232
|
+
skill: skillName,
|
|
233
|
+
success: true,
|
|
234
|
+
source: effectiveSource,
|
|
235
|
+
sourceType: effectiveSourceType,
|
|
236
|
+
});
|
|
237
|
+
} catch (err) {
|
|
238
|
+
results.push({
|
|
239
|
+
skill: skillName,
|
|
240
|
+
success: false,
|
|
241
|
+
error: err instanceof Error ? err.message : String(err),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
spinner.stop('Removal process complete');
|
|
247
|
+
|
|
248
|
+
const successful = results.filter((r) => r.success);
|
|
249
|
+
const failed = results.filter((r) => !r.success);
|
|
250
|
+
|
|
251
|
+
// Track removal (grouped by source)
|
|
252
|
+
if (successful.length > 0) {
|
|
253
|
+
const bySource = new Map<string, { skills: string[]; sourceType?: string }>();
|
|
254
|
+
|
|
255
|
+
for (const r of successful) {
|
|
256
|
+
const source = r.source || 'local';
|
|
257
|
+
const existing = bySource.get(source) || { skills: [] };
|
|
258
|
+
existing.skills.push(r.skill);
|
|
259
|
+
existing.sourceType = r.sourceType;
|
|
260
|
+
bySource.set(source, existing);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (const [source, data] of bySource) {
|
|
264
|
+
track({
|
|
265
|
+
event: 'remove',
|
|
266
|
+
source,
|
|
267
|
+
skills: data.skills.join(','),
|
|
268
|
+
agents: targetAgents.join(','),
|
|
269
|
+
...(isGlobal && { global: '1' }),
|
|
270
|
+
sourceType: data.sourceType,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (successful.length > 0) {
|
|
276
|
+
p.log.success(pc.green(`Successfully removed ${successful.length} skill(s)`));
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (failed.length > 0) {
|
|
280
|
+
p.log.error(pc.red(`Failed to remove ${failed.length} skill(s)`));
|
|
281
|
+
for (const r of failed) {
|
|
282
|
+
p.log.message(` ${pc.red('✗')} ${r.skill}: ${r.error}`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
console.log();
|
|
287
|
+
p.outro(pc.green('Done!'));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Parse command line options for the remove command.
|
|
292
|
+
* Separates skill names from options flags.
|
|
293
|
+
*/
|
|
294
|
+
export function parseRemoveOptions(args: string[]): { skills: string[]; options: RemoveOptions } {
|
|
295
|
+
const options: RemoveOptions = {};
|
|
296
|
+
const skills: string[] = [];
|
|
297
|
+
|
|
298
|
+
for (let i = 0; i < args.length; i++) {
|
|
299
|
+
const arg = args[i];
|
|
300
|
+
|
|
301
|
+
if (arg === '-g' || arg === '--global') {
|
|
302
|
+
options.global = true;
|
|
303
|
+
} else if (arg === '-y' || arg === '--yes') {
|
|
304
|
+
options.yes = true;
|
|
305
|
+
} else if (arg === '--all') {
|
|
306
|
+
options.all = true;
|
|
307
|
+
} else if (arg === '-a' || arg === '--agent') {
|
|
308
|
+
options.agent = options.agent || [];
|
|
309
|
+
i++;
|
|
310
|
+
let nextArg = args[i];
|
|
311
|
+
while (i < args.length && nextArg && !nextArg.startsWith('-')) {
|
|
312
|
+
options.agent.push(nextArg);
|
|
313
|
+
i++;
|
|
314
|
+
nextArg = args[i];
|
|
315
|
+
}
|
|
316
|
+
i--; // Back up one since the loop will increment
|
|
317
|
+
} else if (arg && !arg.startsWith('-')) {
|
|
318
|
+
skills.push(arg);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
return { skills, options };
|
|
323
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitize untrusted strings before terminal output.
|
|
3
|
+
*
|
|
4
|
+
* Strips ALL terminal escape sequences from a string, including:
|
|
5
|
+
* - CSI sequences (ESC [ ... final_byte) — cursor movement, screen clear, SGR colors
|
|
6
|
+
* - OSC sequences (ESC ] ... BEL/ST) — window title, hyperlinks
|
|
7
|
+
* - Simple escapes (ESC followed by one char) — e.g. ESC 7 (save cursor)
|
|
8
|
+
* - C1 control codes (0x80–0x9F)
|
|
9
|
+
* - Raw control characters (BEL, BS, etc.) — except \t and \n which are safe
|
|
10
|
+
*
|
|
11
|
+
* This defends against CWE-150 (terminal escape injection) where
|
|
12
|
+
* untrusted data (e.g., skill name/description from SKILL.md frontmatter
|
|
13
|
+
* or remote APIs) could clear the screen, move the cursor, change the
|
|
14
|
+
* window title, or render attacker-controlled text that looks like
|
|
15
|
+
* legitimate CLI output.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// CSI sequences: ESC[ followed by parameter bytes (0x30-0x3F), intermediate bytes (0x20-0x2F), and a final byte (0x40-0x7E)
|
|
19
|
+
const CSI_RE = /\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g;
|
|
20
|
+
|
|
21
|
+
// OSC sequences: ESC] ... terminated by BEL (\x07) or ST (ESC\)
|
|
22
|
+
const OSC_RE = /\x1b\][\s\S]*?(?:\x07|\x1b\\)/g;
|
|
23
|
+
|
|
24
|
+
// DCS, PM, APC sequences: ESC P|^|_ ... terminated by ST (ESC\)
|
|
25
|
+
const DCS_PM_APC_RE = /\x1b[P^_][\s\S]*?(?:\x1b\\)/g;
|
|
26
|
+
|
|
27
|
+
// Simple two-byte escape sequences: ESC followed by a single char in 0x20-0x7E range
|
|
28
|
+
// Includes ESC 7 (DECSC), ESC 8 (DECRC), ESC c (RIS), ESC M (RI), etc.
|
|
29
|
+
const SIMPLE_ESC_RE = /\x1b[\x20-\x7e]/g;
|
|
30
|
+
|
|
31
|
+
// C1 control codes (0x80-0x9F) — used as 8-bit equivalents of ESC sequences
|
|
32
|
+
const C1_RE = /[\x80-\x9f]/g;
|
|
33
|
+
|
|
34
|
+
// Raw control characters except tab (\x09) and newline (\x0a)
|
|
35
|
+
// Includes BEL (\x07), BS (\x08), CR (\x0d), and others
|
|
36
|
+
const CONTROL_RE = /[\x00-\x06\x07\x08\x0b\x0c\x0d-\x1a\x1c-\x1f\x7f]/g;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Strip all terminal escape sequences and dangerous control characters
|
|
40
|
+
* from a string.
|
|
41
|
+
*
|
|
42
|
+
* Safe for use on untrusted input before printing to the terminal.
|
|
43
|
+
*/
|
|
44
|
+
export function stripTerminalEscapes(str: string): string {
|
|
45
|
+
return str
|
|
46
|
+
.replace(OSC_RE, '') // OSC first (longest match)
|
|
47
|
+
.replace(DCS_PM_APC_RE, '') // DCS/PM/APC
|
|
48
|
+
.replace(CSI_RE, '') // CSI sequences
|
|
49
|
+
.replace(SIMPLE_ESC_RE, '') // Simple ESC+char
|
|
50
|
+
.replace(C1_RE, '') // C1 control codes
|
|
51
|
+
.replace(CONTROL_RE, ''); // Raw control chars (keep \t \n)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Sanitize a skill metadata string (name, description, etc.) for safe terminal display.
|
|
56
|
+
*
|
|
57
|
+
* In addition to stripping escape sequences, this also trims whitespace and
|
|
58
|
+
* collapses internal newlines into spaces (skill names/descriptions should
|
|
59
|
+
* be single-line when displayed).
|
|
60
|
+
*/
|
|
61
|
+
export function sanitizeMetadata(str: string): string {
|
|
62
|
+
return stripTerminalEscapes(str)
|
|
63
|
+
.replace(/[\r\n]+/g, ' ')
|
|
64
|
+
.trim();
|
|
65
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// ADG patch (new file): standalone helper for re-invoking the vendored CLI on
|
|
2
|
+
// `cli.ts`. Kept dependency-free (no heavy transitive imports) so a test can
|
|
3
|
+
// import it without pulling update.ts's graph (which currently includes the
|
|
4
|
+
// detect-agent.ts ↔ @vercel/detect-agent API mismatch). Mirrors the rationale
|
|
5
|
+
// for git-tree.ts. See vendor/skills/PROVENANCE.md.
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Args for re-invoking Node on the vendored `cli.ts`. `process.execArgv` is
|
|
9
|
+
* forwarded so the child inherits the parent's Node flags (e.g.
|
|
10
|
+
* --experimental-strip-types, required to run TypeScript directly on Node
|
|
11
|
+
* 22.6–23.5). `execArgv` is a parameter so the forwarding can be tested with a
|
|
12
|
+
* non-empty flag set.
|
|
13
|
+
*/
|
|
14
|
+
export function selfCliArgv(
|
|
15
|
+
cliEntry: string,
|
|
16
|
+
args: string[],
|
|
17
|
+
execArgv: string[] = process.execArgv
|
|
18
|
+
): string[] {
|
|
19
|
+
return [...execArgv, cliEntry, ...args];
|
|
20
|
+
}
|