@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.
- package/.turbo/turbo-test.log +87 -0
- package/.turbo/turbo-types.log +1 -0
- package/CHANGELOG.md +19 -0
- package/README.md +38 -0
- package/package.json +12 -3
- package/src/__tests__/e2e.test.ts +37 -41
- package/src/cli/init.ts +10 -10
- package/src/cli.ts +52 -29
- package/src/discovery/__tests__/cache.test.ts +130 -0
- package/src/discovery/__tests__/list.test.ts +54 -54
- package/src/discovery/cache.ts +13 -14
- package/src/discovery/clear.ts +2 -2
- package/src/discovery/list.ts +19 -19
- package/src/index.ts +16 -20
- package/src/installation/__tests__/install.test.ts +157 -85
- package/src/installation/install.ts +37 -27
- package/src/installation/status.ts +1 -1
- package/src/installation/uninstall.ts +13 -13
- package/src/registry/__tests__/loader.test.ts +22 -22
- package/src/registry/__tests__/schemas.test.ts +112 -111
- package/src/registry/files.ts +9 -8
- package/src/registry/loader.ts +7 -7
- package/src/registry/schemas.ts +61 -74
|
@@ -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
|
+
[](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
|
-
"
|
|
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 {
|
|
2
|
-
import { mkdtemp, rm } from
|
|
3
|
-
import { tmpdir } from
|
|
4
|
-
import path from
|
|
5
|
-
import yaml from
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
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(),
|
|
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(
|
|
37
|
-
test(
|
|
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(
|
|
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
|
-
|
|
46
|
+
'test-facet',
|
|
51
47
|
{
|
|
52
|
-
name:
|
|
53
|
-
version:
|
|
54
|
-
description:
|
|
55
|
-
skills: [
|
|
48
|
+
name: 'test-facet',
|
|
49
|
+
version: '1.0.0',
|
|
50
|
+
description: 'End-to-end test facet',
|
|
51
|
+
skills: ['e2e-skill'],
|
|
56
52
|
agents: {
|
|
57
|
-
|
|
58
|
-
description:
|
|
59
|
-
prompt:
|
|
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
|
-
|
|
67
|
-
description:
|
|
68
|
-
prompt:
|
|
62
|
+
'e2e-cmd': {
|
|
63
|
+
description: 'E2E test command',
|
|
64
|
+
prompt: 'prompts/e2e-cmd.md',
|
|
69
65
|
},
|
|
70
66
|
},
|
|
71
67
|
},
|
|
72
68
|
{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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]
|
|
83
|
-
expect(list.facets[0]
|
|
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(
|
|
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(
|
|
100
|
-
expect(agentContent).toContain(
|
|
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]
|
|
100
|
+
expect(list.facets[0]?.installed).toBe(true)
|
|
105
101
|
|
|
106
102
|
// 7. Uninstall
|
|
107
|
-
const uninstallResult = await uninstallFacet(
|
|
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]
|
|
113
|
+
expect(list.facets[0]?.installed).toBe(false)
|
|
118
114
|
})
|
|
119
115
|
|
|
120
|
-
test(
|
|
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
|
|
2
|
-
import { facetsYamlPath } from
|
|
1
|
+
import { parse, stringify } from 'comment-json'
|
|
2
|
+
import { facetsYamlPath } from '../registry/files.ts'
|
|
3
3
|
|
|
4
|
-
const OPENCODE_CONFIG_PATH =
|
|
4
|
+
const OPENCODE_CONFIG_PATH = '.opencode/opencode.jsonc'
|
|
5
5
|
|
|
6
6
|
const MCP_SERVER_CONFIG = {
|
|
7
|
-
type:
|
|
8
|
-
command: [
|
|
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(
|
|
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
|
|
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,
|
|
56
|
-
console.log(
|
|
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 {
|
|
3
|
-
import {
|
|
4
|
-
import { cacheFacet } from
|
|
5
|
-
import { clearCache } from
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
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(
|
|
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(
|
|
33
|
-
console.log(
|
|
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
|
|
56
|
+
case 'init':
|
|
41
57
|
await cmdInit()
|
|
42
58
|
break
|
|
43
|
-
case
|
|
59
|
+
case 'list':
|
|
44
60
|
await cmdList()
|
|
45
61
|
break
|
|
46
|
-
case
|
|
62
|
+
case 'add':
|
|
47
63
|
await cmdAdd(args[1])
|
|
48
64
|
break
|
|
49
|
-
case
|
|
65
|
+
case 'install':
|
|
50
66
|
await cmdInstall(args[1])
|
|
51
67
|
break
|
|
52
|
-
case
|
|
68
|
+
case 'remove':
|
|
53
69
|
await cmdRemove(args[1])
|
|
54
70
|
break
|
|
55
|
-
case
|
|
71
|
+
case 'update':
|
|
56
72
|
await cmdUpdate(args[1])
|
|
57
73
|
break
|
|
58
|
-
case
|
|
59
|
-
if (args[1] ===
|
|
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(
|
|
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(
|
|
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 ?
|
|
90
|
-
const version = facet.version ? `v${facet.version}` :
|
|
91
|
-
const source = facet.source ===
|
|
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(
|
|
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 ===
|
|
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(
|
|
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(
|
|
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(
|
|
223
|
+
console.log('Cache cleared.')
|
|
201
224
|
}
|
|
202
225
|
|
|
203
226
|
main().catch((err) => {
|