@ex-machina/facets 0.1.0 → 0.2.2

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.
@@ -0,0 +1,87 @@
1
+ $ bun test
2
+ bun test v1.3.10 (30e609e0)
3
+
4
+ ::group::src/__tests__/e2e.test.ts:
5
+ Registered facets MCP server in .opencode/opencode.jsonc
6
+ Created facets.yaml
7
+ (pass) End-to-end > init → list → install → verify resources → uninstall [34.00ms]
8
+ Registered facets MCP server in .opencode/opencode.jsonc
9
+ Created facets.yaml
10
+ Project already configured for facets.
11
+ (pass) End-to-end > init is idempotent [3.00ms]
12
+
13
+ ::endgroup::
14
+
15
+ ::group::src/installation/__tests__/install.test.ts:
16
+ (pass) installFacet > installs local facet with skill [5.00ms]
17
+ (pass) installFacet > installs agent with assembled frontmatter [2.00ms]
18
+ (pass) installFacet > installs command with assembled frontmatter [2.00ms]
19
+ (pass) installFacet > installs platform tools [4.00ms]
20
+ (pass) installFacet > returns not_found for unknown facet [10.00ms]
21
+ (pass) installFacet > runs prereq checks and fails on bad command [3.00ms]
22
+ (pass) installFacet > forcePrereqCheck re-runs checks even when previously confirmed [7.00ms]
23
+ (pass) installFacet > installs facet from cache when not found locally [15.00ms]
24
+ (pass) installFacet > user declining prereqs cancels install [2.00ms]
25
+ (pass) uninstallFacet > removes installed resources [5.00ms]
26
+ (pass) uninstallFacet > returns not_found for unknown facet [1.00ms]
27
+
28
+ ::endgroup::
29
+
30
+ ::group::src/registry/__tests__/schemas.test.ts:
31
+ (pass) FacetManifest > accepts valid minimal manifest
32
+ (pass) FacetManifest > accepts full manifest with all fields
33
+ (pass) FacetManifest > rejects manifest without name [1.00ms]
34
+ (pass) FacetManifest > rejects manifest without version
35
+ (pass) FacetManifest > tolerates unrecognized fields
36
+ (pass) FacetManifest > accepts requires as string
37
+ (pass) FacetManifest > accepts requires as array [1.00ms]
38
+ (pass) FacetManifest > accepts prompt as string
39
+ (pass) FacetManifest > accepts prompt as object with file
40
+ (pass) FacetManifest > accepts prompt as object with url
41
+ (pass) FacetManifest > accepts agent with array-style tools
42
+ (pass) FacetsYaml > accepts valid dependency file
43
+ (pass) FacetsYaml > accepts empty dependency file
44
+ (pass) FacetsYaml > accepts local-only dependencies
45
+ (pass) FacetsLock > accepts valid lockfile
46
+ (pass) FacetsLock > accepts empty lockfile [1.00ms]
47
+ (pass) normalizeRequires > returns empty array for undefined
48
+ (pass) normalizeRequires > wraps string in array
49
+ (pass) normalizeRequires > passes array through
50
+ (pass) resolvePromptPath > returns string prompt as-is
51
+ (pass) resolvePromptPath > returns file path from object
52
+ (pass) resolvePromptPath > returns null for url prompt
53
+
54
+ ::endgroup::
55
+
56
+ ::group::src/registry/__tests__/loader.test.ts:
57
+ (pass) loadManifest > loads valid manifest [1.00ms]
58
+ (pass) loadManifest > returns error for missing file [1.00ms]
59
+ (pass) loadManifest > returns error for invalid YAML [1.00ms]
60
+ (pass) loadManifest > returns error for missing required fields [1.00ms]
61
+ (pass) loadManifest > tolerates unrecognized fields
62
+
63
+ ::endgroup::
64
+
65
+ ::group::src/discovery/__tests__/cache.test.ts:
66
+ (pass) resolveUrl > resolves relative path against manifest URL [2.00ms]
67
+ (pass) resolveUrl > resolves path in subdirectory
68
+ (pass) resolveUrl > resolves against nested base path [1.00ms]
69
+ (pass) cacheFacet > lockfile updated when remote facet is cached [2.00ms]
70
+ (pass) cacheFacet > fetches and caches resource files declared in manifest [3.00ms]
71
+
72
+ ::endgroup::
73
+
74
+ ::group::src/discovery/__tests__/list.test.ts:
75
+ (pass) listFacets > returns empty list when no facets exist [1.00ms]
76
+ (pass) listFacets > includes local facets [2.00ms]
77
+ (pass) listFacets > reports installed status correctly [2.00ms]
78
+ (pass) listFacets > includes requires as metadata [1.00ms]
79
+ (pass) listFacets > lists resource summaries [2.00ms]
80
+ (pass) listFacets > includes remote facets from facets.yaml [2.00ms]
81
+
82
+ ::endgroup::
83
+
84
+ 51 pass
85
+ 0 fail
86
+ 112 expect() calls
87
+ Ran 51 tests across 6 files. [481.00ms]
@@ -0,0 +1 @@
1
+ $ tsc --noEmit
package/CHANGELOG.md ADDED
@@ -0,0 +1,19 @@
1
+ # @ex-machina/facets
2
+
3
+ ## 0.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 124d984: Provenance changes
8
+
9
+ ## 0.2.1
10
+
11
+ ### Patch Changes
12
+
13
+ - b052d3d: Automatically make PRs that can merge
14
+
15
+ ## 0.2.0
16
+
17
+ ### Minor Changes
18
+
19
+ - e46bfb5: Test changeset workflow
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # @ex-machina/facets
2
+
3
+ [![npm](https://img.shields.io/npm/v/@ex-machina/facets)](https://www.npmjs.com/package/@ex-machina/facets)
4
+
5
+ Core library and CLI for discovering, installing, and managing facets — modular skills, agents, commands, and tools that extend AI coding assistants.
6
+
7
+ > **Status**: Early development (v0.1.0). APIs may change.
8
+
9
+ ## CLI
10
+
11
+ ```sh
12
+ Usage: facets <command> [options]
13
+
14
+ Commands:
15
+ init Set up project for facets
16
+ list List all facets and their status
17
+ add <url> Cache a remote facet by URL
18
+ install [name] Install a facet's resources
19
+ remove <name> Remove a facet
20
+ update [name] Update cached remote facets
21
+ cache clear Clear the global facet cache
22
+
23
+ Options:
24
+ --help, -h Show this help message
25
+ --version, -v Show version
26
+ ```
27
+
28
+ ## Library
29
+
30
+ The package also exports its core functions for programmatic use:
31
+
32
+ - **Registry** — `loadManifest`, `FacetManifestSchema`, `FacetsYamlSchema`, `FacetsLockSchema`
33
+ - **Discovery** — `listFacets`, `cacheFacet`, `updateFacet`, `updateAllFacets`, `clearCache`
34
+ - **Installation** — `installFacet`, `uninstallFacet`
35
+
36
+ ## License
37
+
38
+ [MIT](../../LICENSE)
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@ex-machina/facets",
3
- "version": "0.1.0",
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "https://github.com/ex-machina-co/facets",
6
+ "directory": "packages/facets"
7
+ },
8
+ "version": "0.2.2",
4
9
  "type": "module",
5
10
  "exports": {
6
11
  ".": "./src/index.ts"
@@ -13,9 +18,9 @@
13
18
  "test": "bun test"
14
19
  },
15
20
  "dependencies": {
21
+ "arktype": "2.1.29",
16
22
  "comment-json": "^4.2.5",
17
- "js-yaml": "^4.1.0",
18
- "zod": "^4.1.0"
23
+ "js-yaml": "^4.1.0"
19
24
  },
20
25
  "devDependencies": {
21
26
  "@types/bun": "latest",
@@ -23,5 +28,9 @@
23
28
  },
24
29
  "peerDependencies": {
25
30
  "typescript": "^5"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "provenance": true
26
35
  }
27
36
  }
@@ -1,28 +1,24 @@
1
- import { test, expect, describe, beforeEach, afterEach } from "bun:test"
2
- import { mkdtemp, rm } from "node:fs/promises"
3
- import { tmpdir } from "node:os"
4
- import path from "path"
5
- import yaml from "js-yaml"
6
- import { listFacets } from "../discovery/list.ts"
7
- import { installFacet } from "../installation/install.ts"
8
- import { uninstallFacet } from "../installation/uninstall.ts"
9
- import { initProject } from "../cli/init.ts"
1
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test'
2
+ import { mkdtemp, rm } from 'node:fs/promises'
3
+ import { tmpdir } from 'node:os'
4
+ import path from 'node:path'
5
+ import yaml from 'js-yaml'
6
+ import { initProject } from '../cli/init.ts'
7
+ import { listFacets } from '../discovery/list.ts'
8
+ import { installFacet } from '../installation/install.ts'
9
+ import { uninstallFacet } from '../installation/uninstall.ts'
10
10
 
11
11
  let projectRoot: string
12
12
 
13
13
  beforeEach(async () => {
14
- projectRoot = await mkdtemp(path.join(tmpdir(), "facets-e2e-"))
14
+ projectRoot = await mkdtemp(path.join(tmpdir(), 'facets-e2e-'))
15
15
  })
16
16
 
17
17
  afterEach(async () => {
18
18
  await rm(projectRoot, { recursive: true, force: true })
19
19
  })
20
20
 
21
- async function writeLocalFacet(
22
- name: string,
23
- manifest: Record<string, unknown>,
24
- files?: Record<string, string>,
25
- ) {
21
+ async function writeLocalFacet(name: string, manifest: Record<string, unknown>, files?: Record<string, string>) {
26
22
  const dir = `${projectRoot}/.opencode/facets/${name}`
27
23
  await Bun.$`mkdir -p ${dir}`
28
24
  await Bun.write(`${dir}/facet.yaml`, yaml.dump(manifest))
@@ -33,57 +29,57 @@ async function writeLocalFacet(
33
29
  }
34
30
  }
35
31
 
36
- describe("End-to-end", () => {
37
- test("init → list → install → verify resources → uninstall", async () => {
32
+ describe('End-to-end', () => {
33
+ test('init → list → install → verify resources → uninstall', async () => {
38
34
  // 1. Init the project
39
35
  await initProject(projectRoot)
40
36
 
41
37
  // Verify opencode.jsonc was created with MCP server registered
42
38
  const configText = await Bun.file(`${projectRoot}/.opencode/opencode.jsonc`).text()
43
- expect(configText).toContain("facets-mcp")
39
+ expect(configText).toContain('facets-mcp')
44
40
 
45
41
  // Verify facets.yaml was created
46
42
  expect(await Bun.file(`${projectRoot}/.opencode/facets.yaml`).exists()).toBe(true)
47
43
 
48
44
  // 2. Create a local facet
49
45
  await writeLocalFacet(
50
- "test-facet",
46
+ 'test-facet',
51
47
  {
52
- name: "test-facet",
53
- version: "1.0.0",
54
- description: "End-to-end test facet",
55
- skills: ["e2e-skill"],
48
+ name: 'test-facet',
49
+ version: '1.0.0',
50
+ description: 'End-to-end test facet',
51
+ skills: ['e2e-skill'],
56
52
  agents: {
57
- "e2e-agent": {
58
- description: "E2E test agent",
59
- prompt: "prompts/e2e-agent.md",
53
+ 'e2e-agent': {
54
+ description: 'E2E test agent',
55
+ prompt: 'prompts/e2e-agent.md',
60
56
  platforms: {
61
57
  opencode: { tools: { write: false } },
62
58
  },
63
59
  },
64
60
  },
65
61
  commands: {
66
- "e2e-cmd": {
67
- description: "E2E test command",
68
- prompt: "prompts/e2e-cmd.md",
62
+ 'e2e-cmd': {
63
+ description: 'E2E test command',
64
+ prompt: 'prompts/e2e-cmd.md',
69
65
  },
70
66
  },
71
67
  },
72
68
  {
73
- "skills/e2e-skill/SKILL.md": "# E2E Skill\nThis is a test skill.",
74
- "prompts/e2e-agent.md": "You are an end-to-end test agent.",
75
- "prompts/e2e-cmd.md": "Run the e2e test command.",
69
+ 'skills/e2e-skill/SKILL.md': '# E2E Skill\nThis is a test skill.',
70
+ 'prompts/e2e-agent.md': 'You are an end-to-end test agent.',
71
+ 'prompts/e2e-cmd.md': 'Run the e2e test command.',
76
72
  },
77
73
  )
78
74
 
79
75
  // 3. List — should show the facet as not installed
80
76
  let list = await listFacets(projectRoot)
81
77
  expect(list.facets).toHaveLength(1)
82
- expect(list.facets[0]!.name).toBe("test-facet")
83
- expect(list.facets[0]!.installed).toBe(false)
78
+ expect(list.facets[0]?.name).toBe('test-facet')
79
+ expect(list.facets[0]?.installed).toBe(false)
84
80
 
85
81
  // 4. Install
86
- const installResult = await installFacet("test-facet", projectRoot)
82
+ const installResult = await installFacet('test-facet', projectRoot)
87
83
  expect(installResult.success).toBe(true)
88
84
  if (installResult.success) {
89
85
  expect(installResult.resources).toHaveLength(3)
@@ -96,15 +92,15 @@ describe("End-to-end", () => {
96
92
 
97
93
  // Verify agent file has assembled frontmatter
98
94
  const agentContent = await Bun.file(`${projectRoot}/.opencode/agents/e2e-agent.md`).text()
99
- expect(agentContent).toContain("description: E2E test agent")
100
- expect(agentContent).toContain("You are an end-to-end test agent.")
95
+ expect(agentContent).toContain('description: E2E test agent')
96
+ expect(agentContent).toContain('You are an end-to-end test agent.')
101
97
 
102
98
  // 6. List — should now show as installed
103
99
  list = await listFacets(projectRoot)
104
- expect(list.facets[0]!.installed).toBe(true)
100
+ expect(list.facets[0]?.installed).toBe(true)
105
101
 
106
102
  // 7. Uninstall
107
- const uninstallResult = await uninstallFacet("test-facet", projectRoot)
103
+ const uninstallResult = await uninstallFacet('test-facet', projectRoot)
108
104
  expect(uninstallResult.success).toBe(true)
109
105
 
110
106
  // 8. Verify resources removed
@@ -114,10 +110,10 @@ describe("End-to-end", () => {
114
110
 
115
111
  // 9. List — should be back to not installed
116
112
  list = await listFacets(projectRoot)
117
- expect(list.facets[0]!.installed).toBe(false)
113
+ expect(list.facets[0]?.installed).toBe(false)
118
114
  })
119
115
 
120
- test("init is idempotent", async () => {
116
+ test('init is idempotent', async () => {
121
117
  await initProject(projectRoot)
122
118
  await initProject(projectRoot)
123
119
 
package/src/cli/init.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { parse, stringify } from "comment-json"
2
- import { facetsYamlPath } from "../registry/files.ts"
1
+ import { parse, stringify } from 'comment-json'
2
+ import { facetsYamlPath } from '../registry/files.ts'
3
3
 
4
- const OPENCODE_CONFIG_PATH = ".opencode/opencode.jsonc"
4
+ const OPENCODE_CONFIG_PATH = '.opencode/opencode.jsonc'
5
5
 
6
6
  const MCP_SERVER_CONFIG = {
7
- type: "local",
8
- command: ["bunx", "facets-mcp"],
7
+ type: 'local',
8
+ command: ['bunx', 'facets-mcp'],
9
9
  enabled: true,
10
10
  } as const
11
11
 
@@ -30,13 +30,13 @@ export async function initProject(projectRoot: string): Promise<void> {
30
30
  } catch {
31
31
  // Config doesn't exist — create a new one
32
32
  config = {}
33
- configText = "{}"
33
+ configText = '{}'
34
34
  }
35
35
 
36
36
  // Check if MCP server is already registered
37
37
  const mcp = (config.mcp ?? {}) as Record<string, unknown>
38
38
  if (mcp.facets) {
39
- console.log("Project already configured for facets.")
39
+ console.log('Project already configured for facets.')
40
40
  return
41
41
  }
42
42
 
@@ -46,13 +46,13 @@ export async function initProject(projectRoot: string): Promise<void> {
46
46
 
47
47
  // Write back preserving comments
48
48
  const newConfig = stringify(config, null, 2)
49
- await Bun.write(configPath, newConfig + "\n")
49
+ await Bun.write(configPath, `${newConfig}\n`)
50
50
  console.log(`Registered facets MCP server in ${OPENCODE_CONFIG_PATH}`)
51
51
 
52
52
  // Create facets.yaml if absent
53
53
  const yamlPath = facetsYamlPath(projectRoot)
54
54
  if (!(await Bun.file(yamlPath).exists())) {
55
- await Bun.write(yamlPath, "# Facet dependencies for this project\nlocal: []\nremote: {}\n")
56
- console.log("Created facets.yaml")
55
+ await Bun.write(yamlPath, '# Facet dependencies for this project\nlocal: []\nremote: {}\n')
56
+ console.log('Created facets.yaml')
57
57
  }
58
58
  }
package/src/cli.ts CHANGED
@@ -1,10 +1,26 @@
1
1
  #!/usr/bin/env bun
2
- import { parseArgs } from "util"
3
- import { listFacets } from "./discovery/list.ts"
4
- import { cacheFacet } from "./discovery/cache.ts"
5
- import { clearCache } from "./discovery/clear.ts"
6
- import { installFacet } from "./installation/install.ts"
7
- import { uninstallFacet } from "./installation/uninstall.ts"
2
+ import { createInterface } from 'node:readline'
3
+ import pkg from '../package.json' with { type: 'json' }
4
+ import { cacheFacet } from './discovery/cache.ts'
5
+ import { clearCache } from './discovery/clear.ts'
6
+ import { listFacets } from './discovery/list.ts'
7
+ import { installFacet } from './installation/install.ts'
8
+ import { uninstallFacet } from './installation/uninstall.ts'
9
+
10
+ async function promptPrereqApproval(commands: string[]): Promise<boolean> {
11
+ console.log('The following prerequisite checks will be run:')
12
+ for (const cmd of commands) {
13
+ console.log(` ${cmd}`)
14
+ }
15
+
16
+ const rl = createInterface({ input: process.stdin, output: process.stdout })
17
+ const answer = await new Promise<string>((resolve) => {
18
+ rl.question('Run these prerequisite checks? (y/N) ', resolve)
19
+ })
20
+ rl.close()
21
+
22
+ return answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes'
23
+ }
8
24
 
9
25
  const HELP = `Usage: facets <command> [options]
10
26
 
@@ -24,39 +40,39 @@ Options:
24
40
  async function main() {
25
41
  const args = process.argv.slice(2)
26
42
 
27
- if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
43
+ if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
28
44
  console.log(HELP)
29
45
  process.exit(0)
30
46
  }
31
47
 
32
- if (args.includes("--version") || args.includes("-v")) {
33
- console.log("0.1.0")
48
+ if (args.includes('--version') || args.includes('-v')) {
49
+ console.log(pkg.version)
34
50
  process.exit(0)
35
51
  }
36
52
 
37
53
  const command = args[0]
38
54
 
39
55
  switch (command) {
40
- case "init":
56
+ case 'init':
41
57
  await cmdInit()
42
58
  break
43
- case "list":
59
+ case 'list':
44
60
  await cmdList()
45
61
  break
46
- case "add":
62
+ case 'add':
47
63
  await cmdAdd(args[1])
48
64
  break
49
- case "install":
65
+ case 'install':
50
66
  await cmdInstall(args[1])
51
67
  break
52
- case "remove":
68
+ case 'remove':
53
69
  await cmdRemove(args[1])
54
70
  break
55
- case "update":
71
+ case 'update':
56
72
  await cmdUpdate(args[1])
57
73
  break
58
- case "cache":
59
- if (args[1] === "clear") {
74
+ case 'cache':
75
+ if (args[1] === 'clear') {
60
76
  await cmdCacheClear()
61
77
  } else {
62
78
  console.error(`Unknown cache subcommand: ${args[1]}`)
@@ -72,7 +88,7 @@ async function main() {
72
88
  }
73
89
 
74
90
  async function cmdInit() {
75
- const { initProject } = await import("./cli/init.ts")
91
+ const { initProject } = await import('./cli/init.ts')
76
92
  await initProject(process.cwd())
77
93
  }
78
94
 
@@ -81,24 +97,27 @@ async function cmdList() {
81
97
  const result = await listFacets(projectRoot)
82
98
 
83
99
  if (result.facets.length === 0) {
84
- console.log("No facets declared.")
100
+ console.log('No facets declared.')
85
101
  return
86
102
  }
87
103
 
88
104
  for (const facet of result.facets) {
89
- const status = facet.installed ? "installed" : "not installed"
90
- const version = facet.version ? `v${facet.version}` : ""
91
- const source = facet.source === "local" ? "local" : "remote"
105
+ const status = facet.installed ? 'installed' : 'not installed'
106
+ const version = facet.version ? `v${facet.version}` : ''
107
+ const source = facet.source === 'local' ? 'local' : 'remote'
92
108
  console.log(` ${facet.name} ${version} (${source}) [${status}]`)
93
109
  if (facet.description) {
94
110
  console.log(` ${facet.description}`)
95
111
  }
112
+ if (facet.requires.length > 0) {
113
+ console.log(` requires: ${facet.requires.join(', ')}`)
114
+ }
96
115
  }
97
116
  }
98
117
 
99
118
  async function cmdAdd(url: string | undefined) {
100
119
  if (!url) {
101
- console.error("Usage: facets add <url>")
120
+ console.error('Usage: facets add <url>')
102
121
  process.exit(1)
103
122
  }
104
123
 
@@ -121,7 +140,9 @@ async function cmdInstall(name: string | undefined) {
121
140
  const list = await listFacets(projectRoot)
122
141
  for (const facet of list.facets) {
123
142
  if (!facet.installed) {
124
- const result = await installFacet(facet.name, projectRoot)
143
+ const result = await installFacet(facet.name, projectRoot, {
144
+ onPrereqApproval: promptPrereqApproval,
145
+ })
125
146
  if (result.success) {
126
147
  console.log(`Installed: ${facet.name}`)
127
148
  } else {
@@ -132,7 +153,9 @@ async function cmdInstall(name: string | undefined) {
132
153
  return
133
154
  }
134
155
 
135
- const result = await installFacet(name, projectRoot)
156
+ const result = await installFacet(name, projectRoot, {
157
+ onPrereqApproval: promptPrereqApproval,
158
+ })
136
159
  if (result.success) {
137
160
  console.log(`Installed: ${name}`)
138
161
  for (const r of result.resources) {
@@ -140,7 +163,7 @@ async function cmdInstall(name: string | undefined) {
140
163
  }
141
164
  } else {
142
165
  console.error(`Failed to install ${name}: ${result.reason}`)
143
- if (result.reason === "prereq" && "failure" in result) {
166
+ if (result.reason === 'prereq' && 'failure' in result) {
144
167
  console.error(` Command failed: ${result.failure.command}`)
145
168
  }
146
169
  process.exit(1)
@@ -149,7 +172,7 @@ async function cmdInstall(name: string | undefined) {
149
172
 
150
173
  async function cmdRemove(name: string | undefined) {
151
174
  if (!name) {
152
- console.error("Usage: facets remove <name>")
175
+ console.error('Usage: facets remove <name>')
153
176
  process.exit(1)
154
177
  }
155
178
 
@@ -165,7 +188,7 @@ async function cmdRemove(name: string | undefined) {
165
188
  }
166
189
 
167
190
  async function cmdUpdate(name: string | undefined) {
168
- const { updateFacet, updateAllFacets } = await import("./discovery/cache.ts")
191
+ const { updateFacet, updateAllFacets } = await import('./discovery/cache.ts')
169
192
 
170
193
  if (name) {
171
194
  const result = await updateFacet(name, process.cwd())
@@ -197,7 +220,7 @@ async function cmdUpdate(name: string | undefined) {
197
220
 
198
221
  async function cmdCacheClear() {
199
222
  await clearCache()
200
- console.log("Cache cleared.")
223
+ console.log('Cache cleared.')
201
224
  }
202
225
 
203
226
  main().catch((err) => {