@pugi/cli 0.1.0-alpha.10

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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -0
  3. package/bin/run.js +2 -0
  4. package/dist/commands/jobs.js +245 -0
  5. package/dist/core/agents/loader.js +104 -0
  6. package/dist/core/agents/registry.js +69 -0
  7. package/dist/core/auto-open-browser.js +128 -0
  8. package/dist/core/bash-classifier.js +1001 -0
  9. package/dist/core/clipboard.js +70 -0
  10. package/dist/core/context/builder.js +114 -0
  11. package/dist/core/context/compaction-events.js +99 -0
  12. package/dist/core/context/compaction.js +602 -0
  13. package/dist/core/context/invariants.js +250 -0
  14. package/dist/core/context/markdown-loader.js +270 -0
  15. package/dist/core/credentials.js +355 -0
  16. package/dist/core/engine/adapter-runner.js +8 -0
  17. package/dist/core/engine/anvil-client.js +156 -0
  18. package/dist/core/engine/compaction-hook.js +154 -0
  19. package/dist/core/engine/index.js +12 -0
  20. package/dist/core/engine/native-pugi.js +369 -0
  21. package/dist/core/engine/noop.js +27 -0
  22. package/dist/core/engine/prompts.js +118 -0
  23. package/dist/core/engine/tool-bridge.js +313 -0
  24. package/dist/core/file-cache.js +29 -0
  25. package/dist/core/hooks.js +415 -0
  26. package/dist/core/index-store.js +260 -0
  27. package/dist/core/jobs/registry.js +462 -0
  28. package/dist/core/mcp/client.js +316 -0
  29. package/dist/core/mcp/registry.js +171 -0
  30. package/dist/core/mcp/trust.js +91 -0
  31. package/dist/core/path-security.js +63 -0
  32. package/dist/core/permission.js +309 -0
  33. package/dist/core/repl/cap-warning.js +91 -0
  34. package/dist/core/repl/clipboard-read.js +174 -0
  35. package/dist/core/repl/history-search.js +175 -0
  36. package/dist/core/repl/history.js +172 -0
  37. package/dist/core/repl/kill-ring.js +138 -0
  38. package/dist/core/repl/session.js +618 -0
  39. package/dist/core/repl/slash-commands.js +227 -0
  40. package/dist/core/repl/workspace-context.js +113 -0
  41. package/dist/core/session.js +258 -0
  42. package/dist/core/settings.js +59 -0
  43. package/dist/core/skills/loader.js +454 -0
  44. package/dist/core/skills/sources.js +480 -0
  45. package/dist/core/skills/trust.js +172 -0
  46. package/dist/core/subagents/dispatcher.js +258 -0
  47. package/dist/core/subagents/index.js +26 -0
  48. package/dist/core/subagents/spawn.js +86 -0
  49. package/dist/core/trust.js +109 -0
  50. package/dist/index.js +8 -0
  51. package/dist/runtime/cli.js +3405 -0
  52. package/dist/runtime/commands/agents.js +385 -0
  53. package/dist/runtime/commands/budget.js +192 -0
  54. package/dist/runtime/commands/config.js +231 -0
  55. package/dist/runtime/commands/privacy.js +107 -0
  56. package/dist/runtime/commands/skills.js +401 -0
  57. package/dist/runtime/commands/undo.js +329 -0
  58. package/dist/runtime/update-check.js +294 -0
  59. package/dist/tools/bash.js +660 -0
  60. package/dist/tools/file-tools.js +346 -0
  61. package/dist/tools/registry.js +25 -0
  62. package/dist/tools/web-fetch.js +535 -0
  63. package/dist/tui/agent-tree.js +66 -0
  64. package/dist/tui/conversation-pane.js +45 -0
  65. package/dist/tui/device-flow.js +142 -0
  66. package/dist/tui/input-box.js +474 -0
  67. package/dist/tui/login-picker.js +69 -0
  68. package/dist/tui/render.js +125 -0
  69. package/dist/tui/repl-render.js +240 -0
  70. package/dist/tui/repl-splash-art.js +64 -0
  71. package/dist/tui/repl-splash.js +111 -0
  72. package/dist/tui/repl.js +214 -0
  73. package/dist/tui/slash-palette.js +106 -0
  74. package/dist/tui/splash-data.js +61 -0
  75. package/dist/tui/splash.js +31 -0
  76. package/dist/tui/status-bar.js +71 -0
  77. package/dist/tui/update-banner.js +8 -0
  78. package/dist/tui/workspace-context.js +105 -0
  79. package/package.json +71 -0
@@ -0,0 +1,454 @@
1
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, join, resolve } from 'node:path';
4
+ const FRONTMATTER_DELIM = '---';
5
+ /**
6
+ * Parse a markdown file with YAML frontmatter into a structured form.
7
+ *
8
+ * Throws when:
9
+ * - no frontmatter delimiter pair is present
10
+ * - frontmatter does not parse as the minimal YAML grammar
11
+ * - required keys `name`, `description`, `metadata.type` are missing
12
+ *
13
+ * Frontmatter `tools` accepts either flow array (`[a, b, c]`) or block
14
+ * array (newline + ` - a` lines). Strings are unquoted, single, or
15
+ * double quoted. Multi-line scalar values are NOT supported (no `>` or
16
+ * `|` folding) — Skills do not need them.
17
+ */
18
+ export function parseSkillMarkdown(source) {
19
+ const trimmed = source.replace(/^/, '');
20
+ if (!trimmed.startsWith(`${FRONTMATTER_DELIM}\n`) && !trimmed.startsWith(`${FRONTMATTER_DELIM}\r\n`)) {
21
+ throw new Error('SKILL_PARSE: missing opening "---" frontmatter delimiter on line 1');
22
+ }
23
+ const newlineAfterOpen = trimmed.indexOf('\n');
24
+ const rest = trimmed.slice(newlineAfterOpen + 1);
25
+ const closeIdx = findClosingDelimiter(rest);
26
+ if (closeIdx < 0) {
27
+ throw new Error('SKILL_PARSE: missing closing "---" frontmatter delimiter');
28
+ }
29
+ const frontmatterRaw = rest.slice(0, closeIdx);
30
+ const body = rest.slice(closeIdx + FRONTMATTER_DELIM.length).replace(/^\r?\n/, '');
31
+ const parsed = parseFrontmatter(frontmatterRaw);
32
+ const validated = validateFrontmatter(parsed);
33
+ return { frontmatter: validated, body, source };
34
+ }
35
+ function findClosingDelimiter(text) {
36
+ // Walk line-by-line so we never match `---` that appears inside a
37
+ // body separator or table row.
38
+ const lines = text.split('\n');
39
+ let offset = 0;
40
+ for (const line of lines) {
41
+ if (line.trimEnd() === FRONTMATTER_DELIM) {
42
+ return offset;
43
+ }
44
+ offset += line.length + 1; // +1 for the newline we split on
45
+ }
46
+ return -1;
47
+ }
48
+ /**
49
+ * Parse the minimal YAML grammar described in the file header into a
50
+ * shallow node tree. Two levels of nesting cover every real skill /
51
+ * agent we have inspected; deeper nesting throws a clear error so the
52
+ * operator knows the parser is the gap.
53
+ */
54
+ function parseFrontmatter(raw) {
55
+ const lines = raw.split('\n');
56
+ const root = {};
57
+ let i = 0;
58
+ while (i < lines.length) {
59
+ const line = lines[i] ?? '';
60
+ if (line.trim() === '' || line.trim().startsWith('#')) {
61
+ i++;
62
+ continue;
63
+ }
64
+ if (/^\s/.test(line)) {
65
+ throw new Error(`SKILL_PARSE: unexpected indented line at root scope: "${line}"`);
66
+ }
67
+ const colonIdx = line.indexOf(':');
68
+ if (colonIdx < 0) {
69
+ throw new Error(`SKILL_PARSE: expected "key: value" on line "${line}"`);
70
+ }
71
+ const key = line.slice(0, colonIdx).trim();
72
+ const rest = line.slice(colonIdx + 1).trim();
73
+ if (rest === '') {
74
+ // Two follow-up shapes: block object (indented `key: value`) OR
75
+ // block array (indented `- item`). Decide by inspecting the next
76
+ // non-blank line.
77
+ const peek = peekNextNonBlank(lines, i + 1);
78
+ if (peek === null) {
79
+ root[key] = { kind: 'object', value: {} };
80
+ i++;
81
+ continue;
82
+ }
83
+ const peekTrim = peek.line.trimStart();
84
+ if (peekTrim.startsWith('- ')) {
85
+ const { items, consumed } = parseBlockArray(lines, i + 1);
86
+ root[key] = { kind: 'array', value: items };
87
+ i += consumed + 1;
88
+ continue;
89
+ }
90
+ const { obj, consumed } = parseBlockObject(lines, i + 1);
91
+ root[key] = { kind: 'object', value: obj };
92
+ i += consumed + 1;
93
+ continue;
94
+ }
95
+ if (rest.startsWith('[') && rest.endsWith(']')) {
96
+ root[key] = { kind: 'array', value: parseFlowArray(rest) };
97
+ i++;
98
+ continue;
99
+ }
100
+ root[key] = { kind: 'scalar', value: stripQuotes(rest) };
101
+ i++;
102
+ }
103
+ return root;
104
+ }
105
+ function peekNextNonBlank(lines, from) {
106
+ for (let i = from; i < lines.length; i++) {
107
+ const line = lines[i] ?? '';
108
+ if (line.trim() === '')
109
+ continue;
110
+ if (line.trim().startsWith('#'))
111
+ continue;
112
+ return { line, index: i };
113
+ }
114
+ return null;
115
+ }
116
+ function parseBlockObject(lines, from) {
117
+ const obj = {};
118
+ let i = from;
119
+ let consumed = 0;
120
+ while (i < lines.length) {
121
+ const line = lines[i] ?? '';
122
+ if (line.trim() === '' || line.trim().startsWith('#')) {
123
+ i++;
124
+ consumed++;
125
+ continue;
126
+ }
127
+ if (!/^\s/.test(line)) {
128
+ break;
129
+ }
130
+ const trimmed = line.trimStart();
131
+ const colonIdx = trimmed.indexOf(':');
132
+ if (colonIdx < 0) {
133
+ throw new Error(`SKILL_PARSE: expected "key: value" inside object, got "${line}"`);
134
+ }
135
+ const key = trimmed.slice(0, colonIdx).trim();
136
+ const rest = trimmed.slice(colonIdx + 1).trim();
137
+ if (rest === '') {
138
+ // Nested object or block array — peek for shape.
139
+ const peek = peekNextNonBlank(lines, i + 1);
140
+ if (peek && peek.line.length > line.length - line.trimStart().length) {
141
+ const peekTrim = peek.line.trimStart();
142
+ if (peekTrim.startsWith('- ')) {
143
+ const arr = parseBlockArray(lines, i + 1);
144
+ obj[key] = { kind: 'array', value: arr.items };
145
+ i += arr.consumed + 1;
146
+ consumed += arr.consumed + 1;
147
+ continue;
148
+ }
149
+ // Deeper objects are out of scope for α7.0 — keep frontmatter
150
+ // shape predictable. Throw with a clear pointer.
151
+ throw new Error(`SKILL_PARSE: nested objects deeper than 1 level are not supported (key "${key}")`);
152
+ }
153
+ obj[key] = { kind: 'object', value: {} };
154
+ i++;
155
+ consumed++;
156
+ continue;
157
+ }
158
+ if (rest.startsWith('[') && rest.endsWith(']')) {
159
+ obj[key] = { kind: 'array', value: parseFlowArray(rest) };
160
+ }
161
+ else {
162
+ obj[key] = { kind: 'scalar', value: stripQuotes(rest) };
163
+ }
164
+ i++;
165
+ consumed++;
166
+ }
167
+ return { obj, consumed };
168
+ }
169
+ function parseBlockArray(lines, from) {
170
+ const items = [];
171
+ let i = from;
172
+ let consumed = 0;
173
+ while (i < lines.length) {
174
+ const line = lines[i] ?? '';
175
+ if (line.trim() === '' || line.trim().startsWith('#')) {
176
+ i++;
177
+ consumed++;
178
+ continue;
179
+ }
180
+ if (!/^\s/.test(line)) {
181
+ break;
182
+ }
183
+ const trimmed = line.trimStart();
184
+ if (!trimmed.startsWith('- ')) {
185
+ break;
186
+ }
187
+ items.push(stripQuotes(trimmed.slice(2).trim()));
188
+ i++;
189
+ consumed++;
190
+ }
191
+ return { items, consumed };
192
+ }
193
+ function parseFlowArray(raw) {
194
+ const inner = raw.slice(1, -1).trim();
195
+ if (inner === '')
196
+ return [];
197
+ return inner.split(',').map((piece) => stripQuotes(piece.trim()));
198
+ }
199
+ function stripQuotes(value) {
200
+ if (value.length >= 2) {
201
+ const first = value[0];
202
+ const last = value[value.length - 1];
203
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
204
+ return value.slice(1, -1);
205
+ }
206
+ }
207
+ return value;
208
+ }
209
+ function validateFrontmatter(raw) {
210
+ const name = expectScalar(raw, 'name');
211
+ const description = expectScalar(raw, 'description');
212
+ // Two frontmatter dialects coexist in the wild:
213
+ //
214
+ // 1. Pugi-native — explicit `metadata: { type: skill|agent, ... }`.
215
+ // Future Pugi-published skills + agents use this so the kind is
216
+ // self-declared at parse time.
217
+ //
218
+ // 2. Anthropic Skills — flat top-level keys, no `metadata` block, no
219
+ // `type` field. Every file inside `github.com/anthropics/skills`
220
+ // follows this shape (e.g. `algorithmic-art`, `pdf`, `pptx`).
221
+ //
222
+ // We accept both. When `metadata` is absent we synthesize one with
223
+ // `type` defaulted to `skill` — the on-disk layout (`SKILL.md` in a
224
+ // directory) already disambiguates skill vs agent, and the agent
225
+ // loader explicitly overrides this when the file lives at
226
+ // `~/.pugi/agents/<slug>.md`.
227
+ const metadataNode = raw['metadata'];
228
+ let metadata;
229
+ if (metadataNode && metadataNode.kind === 'object') {
230
+ const metaRaw = metadataNode.value;
231
+ const typeNode = metaRaw['type'];
232
+ let type = 'skill';
233
+ if (typeNode) {
234
+ if (typeNode.kind !== 'scalar') {
235
+ throw new Error('SKILL_PARSE: metadata.type must be a scalar string');
236
+ }
237
+ if (typeNode.value !== 'skill' && typeNode.value !== 'agent') {
238
+ throw new Error(`SKILL_PARSE: metadata.type must be "skill" or "agent" (got "${typeNode.value}")`);
239
+ }
240
+ type = typeNode.value;
241
+ }
242
+ metadata = { type };
243
+ for (const [key, node] of Object.entries(metaRaw)) {
244
+ if (key === 'type')
245
+ continue;
246
+ if (node.kind === 'scalar') {
247
+ metadata[key] = node.value;
248
+ }
249
+ else if (node.kind === 'array') {
250
+ metadata[key] = node.value;
251
+ }
252
+ else {
253
+ metadata[key] = flattenObject(node.value);
254
+ }
255
+ }
256
+ }
257
+ else if (metadataNode) {
258
+ throw new Error('SKILL_PARSE: "metadata" must be an object when present');
259
+ }
260
+ else {
261
+ metadata = { type: 'skill' };
262
+ }
263
+ const fm = { name, description, metadata };
264
+ for (const [key, node] of Object.entries(raw)) {
265
+ if (key === 'name' || key === 'description' || key === 'metadata')
266
+ continue;
267
+ if (node.kind === 'scalar') {
268
+ fm[key] = node.value;
269
+ // Anthropic Skills flat dialect — also surface select top-level
270
+ // keys inside metadata so downstream consumers (Mira system
271
+ // prompt builder, `skills list` table) have one consistent path.
272
+ if (key === 'license' || key === 'version' || key === 'model' || key === 'allowed-tools') {
273
+ const metaKey = key === 'allowed-tools' ? 'tools' : key;
274
+ const metaBag = metadata;
275
+ if (metaBag[metaKey] === undefined) {
276
+ if (key === 'allowed-tools') {
277
+ // allowed-tools is conventionally comma-separated in the flat dialect.
278
+ metaBag[metaKey] = node.value.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
279
+ }
280
+ else {
281
+ metaBag[metaKey] = node.value;
282
+ }
283
+ }
284
+ }
285
+ }
286
+ else if (node.kind === 'array') {
287
+ fm[key] = node.value;
288
+ }
289
+ else {
290
+ fm[key] = flattenObject(node.value);
291
+ }
292
+ }
293
+ return fm;
294
+ }
295
+ function flattenObject(obj) {
296
+ const out = {};
297
+ for (const [key, node] of Object.entries(obj)) {
298
+ if (node.kind === 'scalar')
299
+ out[key] = node.value;
300
+ else if (node.kind === 'array')
301
+ out[key] = node.value;
302
+ else
303
+ out[key] = flattenObject(node.value);
304
+ }
305
+ return out;
306
+ }
307
+ function expectScalar(obj, key) {
308
+ const node = obj[key.split('.').pop() ?? key];
309
+ if (!node) {
310
+ throw new Error(`SKILL_PARSE: required key "${key}" is missing`);
311
+ }
312
+ if (node.kind !== 'scalar') {
313
+ throw new Error(`SKILL_PARSE: key "${key}" must be a scalar string`);
314
+ }
315
+ return node.value;
316
+ }
317
+ /* ----------------------------- Slug validation ----------------------------- */
318
+ /**
319
+ * Slug grammar for skill + agent names. Allowed characters:
320
+ * - lowercase ASCII letters, digits, hyphen, underscore, dot
321
+ * - 1–64 characters total, must START with [a-z0-9]
322
+ *
323
+ * Anything outside this grammar is rejected BEFORE the name reaches a
324
+ * `path.join` call. Attacker payloads we close here:
325
+ * - `../../foo` (path traversal segment)
326
+ * - `/etc/passwd` (absolute path)
327
+ * - `..\\..\\foo` (Windows traversal)
328
+ * - `\0name` (null-byte truncation)
329
+ * - `name\\with\\backslash` (Windows separator)
330
+ * - `..` or `.` (dot-only segments)
331
+ * - empty string (matches existing skill on globbed list)
332
+ * - 65+ char strings (DoS via huge directory names)
333
+ * - uppercase / unicode (collision on case-insensitive filesystems)
334
+ *
335
+ * The CLI commands also accept `--as <slug>` so this guard runs on the
336
+ * operator-supplied override AND on the parsed frontmatter `name`. Both
337
+ * paths feed the same `path.join` so both need the same gate.
338
+ */
339
+ const VALID_SLUG = /^[a-z0-9][a-z0-9._-]{0,63}$/;
340
+ export function assertValidSlug(name, context) {
341
+ if (typeof name !== 'string') {
342
+ throw new Error(`SLUG_INVALID: ${context} name must be a string, got ${typeof name}`);
343
+ }
344
+ // Defense in depth: separators, null bytes, dot-only get rejected
345
+ // ahead of the regex so the error message points at the precise vector
346
+ // rather than the catch-all "must match" form.
347
+ if (name.includes('/') || name.includes('\\')) {
348
+ throw new Error(`SLUG_FORBIDDEN: ${context} name contains a path separator: ${JSON.stringify(name)}`);
349
+ }
350
+ if (name.includes('\0')) {
351
+ throw new Error(`SLUG_FORBIDDEN: ${context} name contains a null byte: ${JSON.stringify(name)}`);
352
+ }
353
+ if (/^\.+$/.test(name)) {
354
+ throw new Error(`SLUG_FORBIDDEN: ${context} name is a dot-only segment: ${JSON.stringify(name)}`);
355
+ }
356
+ if (!VALID_SLUG.test(name)) {
357
+ throw new Error(`SLUG_INVALID: ${context} name ${JSON.stringify(name)} must match ${VALID_SLUG.source} (lowercase letters/digits/._-, 1-64 chars, starting alphanumeric).`);
358
+ }
359
+ }
360
+ export function globalSkillsDir() {
361
+ const home = process.env.PUGI_HOME ?? resolve(homedir(), '.pugi');
362
+ return join(home, 'skills');
363
+ }
364
+ export function workspaceSkillsDir(workspaceRoot) {
365
+ return join(workspaceRoot, '.pugi', 'skills');
366
+ }
367
+ export function globalSkillDir(name) {
368
+ assertValidSlug(name, 'skill');
369
+ return join(globalSkillsDir(), name);
370
+ }
371
+ export function workspaceSkillDir(workspaceRoot, name) {
372
+ assertValidSlug(name, 'skill');
373
+ return join(workspaceSkillsDir(workspaceRoot), name);
374
+ }
375
+ export function listSkills(scope, workspaceRoot) {
376
+ const dir = scope === 'global' ? globalSkillsDir() : workspaceSkillsDir(workspaceRoot);
377
+ if (!existsSync(dir))
378
+ return [];
379
+ return readdirSync(dir, { withFileTypes: true })
380
+ .filter((entry) => entry.isDirectory())
381
+ .sort((a, b) => a.name.localeCompare(b.name))
382
+ .map((entry) => loadSkill(join(dir, entry.name), scope))
383
+ .filter((skill) => skill !== null);
384
+ }
385
+ function loadSkill(skillDir, scope) {
386
+ const skillMd = join(skillDir, 'SKILL.md');
387
+ if (!existsSync(skillMd))
388
+ return null;
389
+ try {
390
+ const source = readFileSync(skillMd, 'utf8');
391
+ const parsed = parseSkillMarkdown(source);
392
+ if (parsed.frontmatter.metadata.type !== 'skill') {
393
+ return null;
394
+ }
395
+ // Defense in depth: frontmatter.name is operator-controlled (anyone
396
+ // can author a SKILL.md). Reject before it can flow into trust
397
+ // registry keys, log strings, or any path operation downstream.
398
+ assertValidSlug(parsed.frontmatter.name, 'skill');
399
+ return {
400
+ name: parsed.frontmatter.name,
401
+ scope,
402
+ dir: skillDir,
403
+ skillMdPath: skillMd,
404
+ frontmatter: parsed.frontmatter,
405
+ body: parsed.body,
406
+ source,
407
+ };
408
+ }
409
+ catch {
410
+ return null;
411
+ }
412
+ }
413
+ /**
414
+ * Copy a fetched skill payload into its install location. Caller is
415
+ * responsible for trust-prompting + hashing before this is invoked.
416
+ *
417
+ * Refuses to install when the payload does not contain a `SKILL.md` at
418
+ * its root: that file is the contract every consumer (Mira system
419
+ * prompt, `skills list`, `skills info`) reads from.
420
+ */
421
+ export function installSkill(input) {
422
+ // assertValidSlug runs inside globalSkillDir/workspaceSkillDir below,
423
+ // but we call it explicitly here too so the error surfaces before any
424
+ // filesystem state changes (cpSync) — fail-closed ordering.
425
+ assertValidSlug(input.name, 'skill');
426
+ const skillMd = join(input.payloadDir, 'SKILL.md');
427
+ if (!existsSync(skillMd)) {
428
+ throw new Error(`SKILL_INSTALL: payload at ${input.payloadDir} has no SKILL.md at its root`);
429
+ }
430
+ const target = input.scope === 'global'
431
+ ? globalSkillDir(input.name)
432
+ : workspaceSkillDir(input.workspaceRoot, input.name);
433
+ mkdirSync(dirname(target), { recursive: true });
434
+ // Idempotent install — remove any prior install so a re-install does
435
+ // not leave orphaned files from the previous payload.
436
+ if (existsSync(target)) {
437
+ rmSync(target, { recursive: true, force: true });
438
+ }
439
+ // verbatimSymlinks: any symlink that slipped through (e.g. via a
440
+ // legitimately-symlinked local source) is copied as a symlink rather
441
+ // than followed — so a malicious symlink in the payload cannot exfil
442
+ // contents from outside the payload root into our install target.
443
+ cpSync(input.payloadDir, target, { recursive: true, verbatimSymlinks: true });
444
+ return target;
445
+ }
446
+ export function removeSkill(name, scope, workspaceRoot) {
447
+ assertValidSlug(name, 'skill');
448
+ const target = scope === 'global' ? globalSkillDir(name) : workspaceSkillDir(workspaceRoot, name);
449
+ if (!existsSync(target))
450
+ return false;
451
+ rmSync(target, { recursive: true, force: true });
452
+ return true;
453
+ }
454
+ //# sourceMappingURL=loader.js.map