agent-facets 0.3.0 → 0.3.5

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 (60) hide show
  1. package/bin/facet +1 -1
  2. package/package.json +16 -37
  3. package/{scripts/postinstall.mjs → postinstall.mjs} +1 -1
  4. package/.package.json.bak +0 -45
  5. package/.turbo/turbo-build.log +0 -3
  6. package/CHANGELOG.md +0 -95
  7. package/bunfig.toml +0 -2
  8. package/dist/facet +0 -0
  9. package/src/__tests__/cli.test.ts +0 -195
  10. package/src/__tests__/create-build.test.ts +0 -227
  11. package/src/__tests__/edit-integration.test.ts +0 -171
  12. package/src/__tests__/launcher.test.ts +0 -106
  13. package/src/__tests__/postinstall.test.ts +0 -196
  14. package/src/__tests__/resolve-dir.test.ts +0 -95
  15. package/src/commands/build.ts +0 -58
  16. package/src/commands/create/index.ts +0 -76
  17. package/src/commands/create/types.ts +0 -9
  18. package/src/commands/create/wizard.tsx +0 -75
  19. package/src/commands/create-scaffold.ts +0 -184
  20. package/src/commands/edit/index.ts +0 -144
  21. package/src/commands/edit/wizard.tsx +0 -74
  22. package/src/commands/resolve-dir.ts +0 -98
  23. package/src/commands.ts +0 -40
  24. package/src/help.ts +0 -43
  25. package/src/index.ts +0 -10
  26. package/src/run.ts +0 -82
  27. package/src/suggest.ts +0 -35
  28. package/src/tui/components/asset-description.tsx +0 -17
  29. package/src/tui/components/asset-field-picker.tsx +0 -78
  30. package/src/tui/components/asset-inline-input.tsx +0 -91
  31. package/src/tui/components/asset-item.tsx +0 -44
  32. package/src/tui/components/asset-section.tsx +0 -191
  33. package/src/tui/components/button.tsx +0 -92
  34. package/src/tui/components/editable-field.tsx +0 -172
  35. package/src/tui/components/exit-toast.tsx +0 -20
  36. package/src/tui/components/reconciliation-item.tsx +0 -129
  37. package/src/tui/components/stage-row.tsx +0 -45
  38. package/src/tui/components/version-selector.tsx +0 -79
  39. package/src/tui/context/focus-mode-context.ts +0 -36
  40. package/src/tui/context/focus-order-context.ts +0 -68
  41. package/src/tui/context/form-state-context.ts +0 -260
  42. package/src/tui/editor.ts +0 -40
  43. package/src/tui/gradient.ts +0 -1
  44. package/src/tui/hooks/use-exit-keys.ts +0 -75
  45. package/src/tui/hooks/use-navigation-keys.ts +0 -34
  46. package/src/tui/layouts/wizard-layout.tsx +0 -41
  47. package/src/tui/theme.ts +0 -1
  48. package/src/tui/views/build/build-view.tsx +0 -152
  49. package/src/tui/views/create/confirm-view.tsx +0 -74
  50. package/src/tui/views/create/create-view.tsx +0 -158
  51. package/src/tui/views/create/wizard.tsx +0 -97
  52. package/src/tui/views/edit/edit-confirm-view.tsx +0 -93
  53. package/src/tui/views/edit/edit-types.ts +0 -34
  54. package/src/tui/views/edit/edit-view.tsx +0 -140
  55. package/src/tui/views/edit/manifest-to-form.ts +0 -38
  56. package/src/tui/views/edit/reconciliation-view.tsx +0 -170
  57. package/src/tui/views/edit/use-edit-session.ts +0 -125
  58. package/src/tui/views/edit/wizard.tsx +0 -129
  59. package/src/version.ts +0 -3
  60. package/tsconfig.json +0 -4
@@ -1,76 +0,0 @@
1
- import { join } from 'node:path'
2
- import { createInterface } from 'node:readline'
3
- import { FACET_MANIFEST_FILE } from '@agent-facets/core'
4
- import { type } from 'arktype'
5
- import type { Command } from '../../commands.ts'
6
- import { writeScaffold } from '../create-scaffold.ts'
7
- import { resolveTargetDir } from '../resolve-dir.ts'
8
- import { runCreateWizardInk } from './wizard.tsx'
9
-
10
- export type { CreateOptions } from '../create-scaffold.ts'
11
- export { writeScaffold } from '../create-scaffold.ts'
12
-
13
- const CreateFlags = type({ 'force?': 'boolean' })
14
-
15
- async function confirmOverwrite(display: string): Promise<boolean> {
16
- const rl = createInterface({ input: process.stdin, output: process.stdout })
17
-
18
- return new Promise((resolve) => {
19
- rl.question(`A facet already exists in ${display}. Overwrite? (y/N) `, (answer) => {
20
- rl.close()
21
- resolve(answer.toLowerCase() === 'y')
22
- })
23
- })
24
- }
25
-
26
- export const createCommand: Command = {
27
- name: 'create',
28
- description: 'Create a new facet project interactively',
29
- usage: '[directory]',
30
- flags: {
31
- force: { type: 'boolean', description: 'Overwrite existing facet.json' },
32
- },
33
- run: async (args: string[], flags: Record<string, unknown>): Promise<number> => {
34
- const resolved = await resolveTargetDir(args[0], { mustExist: false })
35
- if (!resolved.ok) {
36
- console.error(resolved.message)
37
- return 1
38
- }
39
-
40
- const targetDir = resolved.dir
41
- const displayDir = resolved.display
42
-
43
- // Validate flags via Arktype
44
- const validatedFlags = CreateFlags(flags)
45
- if (validatedFlags instanceof type.errors) {
46
- console.error(`Invalid flags: ${validatedFlags.summary}`)
47
- return 1
48
- }
49
-
50
- // Overwrite protection
51
- const manifestExists = await Bun.file(join(targetDir, FACET_MANIFEST_FILE)).exists()
52
- if (manifestExists && !validatedFlags.force) {
53
- const confirmed = await confirmOverwrite(displayDir)
54
- if (!confirmed) {
55
- console.log('Cancelled.')
56
- return 1
57
- }
58
- }
59
-
60
- const opts = await runCreateWizardInk()
61
- if (!opts) {
62
- console.log('\nCancelled.')
63
- return 1
64
- }
65
-
66
- const files = await writeScaffold(opts, targetDir)
67
-
68
- console.log(`\nFacet created: ${opts.name} → ${displayDir}`)
69
- for (const file of files) {
70
- console.log(` ${displayDir}/${file}`)
71
- }
72
- console.log(`\nRun "facet build${args[0] ? ` ${displayDir}` : ''}" to validate your facet.`)
73
-
74
- return 0
75
- },
76
- }
@@ -1,9 +0,0 @@
1
- export type AssetType = 'skill' | 'agent' | 'command'
2
-
3
- export const ASSET_TYPES: AssetType[] = ['skill', 'command', 'agent']
4
-
5
- export const ASSET_LABELS: Record<AssetType, string> = {
6
- skill: 'Skills',
7
- agent: 'Agents',
8
- command: 'Commands',
9
- }
@@ -1,75 +0,0 @@
1
- import { render } from 'ink'
2
- import type { AssetSectionKey } from '../../tui/context/form-state-context.ts'
3
- import { openInEditorSync } from '../../tui/editor.ts'
4
- import type { WizardSnapshot } from '../../tui/views/create/wizard.tsx'
5
- import { CreateWizard } from '../../tui/views/create/wizard.tsx'
6
- import type { CreateOptions } from '../create-scaffold.ts'
7
-
8
- interface EditorRequest {
9
- section: AssetSectionKey
10
- name: string
11
- description: string
12
- }
13
-
14
- export async function runCreateWizardInk(): Promise<CreateOptions | null> {
15
- let result: CreateOptions | null = null
16
- let snapshot: WizardSnapshot | undefined
17
- let pendingEditor: EditorRequest | null = null
18
- let done = false
19
-
20
- while (!done) {
21
- pendingEditor = null
22
-
23
- await new Promise<void>((resolve) => {
24
- const instance = render(
25
- <CreateWizard
26
- snapshot={snapshot}
27
- onComplete={(opts) => {
28
- result = opts
29
- }}
30
- onCancel={() => {
31
- result = null
32
- }}
33
- onSnapshot={(s) => {
34
- snapshot = s
35
- }}
36
- onRequestEditor={(section, name, description) => {
37
- pendingEditor = { section, name, description }
38
- instance.unmount()
39
- }}
40
- />,
41
- )
42
-
43
- instance.waitUntilExit().then(() => resolve())
44
- })
45
-
46
- if (pendingEditor) {
47
- const req = pendingEditor as EditorRequest
48
- const edited = openInEditorSync(req.description, `${req.name}.md`)
49
- if (snapshot) {
50
- const section = snapshot.form.assets[req.section]
51
- snapshot = {
52
- ...snapshot,
53
- selectedItem: undefined,
54
- form: {
55
- ...snapshot.form,
56
- assets: {
57
- ...snapshot.form.assets,
58
- [req.section]: {
59
- ...section,
60
- descriptions: {
61
- ...section.descriptions,
62
- ...(edited !== null ? { [req.name]: edited.trim() } : {}),
63
- },
64
- },
65
- },
66
- },
67
- }
68
- }
69
- } else {
70
- done = true
71
- }
72
- }
73
-
74
- return result
75
- }
@@ -1,184 +0,0 @@
1
- import { mkdir } from 'node:fs/promises'
2
- import { join } from 'node:path'
3
- import { FACET_MANIFEST_FILE, KEBAB_CASE } from '@agent-facets/core'
4
-
5
- // --- Types ---
6
-
7
- export interface CreateOptions {
8
- name: string
9
- version: string
10
- description: string
11
- skills: string[]
12
- agents: string[]
13
- commands: string[]
14
- }
15
-
16
- // --- Defaults ---
17
-
18
- export const DEFAULT_VERSION = '0.0.0'
19
-
20
- // --- Validation ---
21
-
22
- export { KEBAB_CASE }
23
- export const SEMVER = /^\d+\.\d+\.\d+$/
24
-
25
- export function isValidKebabCase(value: string): boolean {
26
- return KEBAB_CASE.test(value)
27
- }
28
-
29
- export function isValidSemVer(value: string): boolean {
30
- return SEMVER.test(value)
31
- }
32
-
33
- // --- Template generation ---
34
-
35
- function toTitleCase(kebab: string): string {
36
- return kebab
37
- .split('-')
38
- .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
39
- .join(' ')
40
- }
41
-
42
- export function skillTemplate(name: string): string {
43
- return `# ${toTitleCase(name)}
44
-
45
- <!-- This is a starter skill template. Replace this content with your skill's instructions. -->
46
- <!-- Skills provide reusable knowledge and guidelines that agents and commands can reference. -->
47
- <!-- A skill needs a description (required) — the description helps consumers decide -->
48
- <!-- whether to use this skill. The prompt content is this file. -->
49
-
50
- ## Purpose
51
-
52
- Describe what this skill teaches or what guidelines it provides.
53
-
54
- ## Guidelines
55
-
56
- - Add your skill's guidelines here
57
- - Each guideline should be clear and actionable
58
- `
59
- }
60
-
61
- export function agentTemplate(name: string): string {
62
- return `# ${toTitleCase(name)}
63
-
64
- <!-- This is a starter agent template. Replace this content with your agent's prompt. -->
65
- <!-- Agents are AI assistant personas with specific roles, behaviors, and tool access. -->
66
-
67
- ## Role
68
-
69
- Describe this agent's role and responsibilities.
70
-
71
- ## Behavior
72
-
73
- - Define how this agent should behave
74
- - Specify what tools it should use
75
- - Describe its communication style
76
- `
77
- }
78
-
79
- export function commandTemplate(name: string): string {
80
- return `# ${toTitleCase(name)}
81
-
82
- <!-- This is a starter command template. Replace this content with your command's prompt. -->
83
- <!-- Commands are user-invokable actions that perform specific tasks. -->
84
-
85
- ## Task
86
-
87
- Describe what this command does when invoked.
88
-
89
- ## Steps
90
-
91
- 1. First step
92
- 2. Second step
93
- 3. Final step
94
- `
95
- }
96
-
97
- // --- Manifest generation ---
98
-
99
- export function generateManifest(opts: CreateOptions): string {
100
- const manifest: Record<string, unknown> = {
101
- name: opts.name,
102
- version: opts.version,
103
- }
104
-
105
- if (opts.description) {
106
- manifest.description = opts.description
107
- }
108
-
109
- if (opts.skills.length > 0) {
110
- const skills: Record<string, { description: string }> = {}
111
- for (const skill of opts.skills) {
112
- skills[skill] = { description: `A ${toTitleCase(skill)} skill` }
113
- }
114
- manifest.skills = skills
115
- }
116
-
117
- if (opts.agents.length > 0) {
118
- const agents: Record<string, { description: string }> = {}
119
- for (const agent of opts.agents) {
120
- agents[agent] = { description: `A ${toTitleCase(agent)} agent` }
121
- }
122
- manifest.agents = agents
123
- }
124
-
125
- if (opts.commands.length > 0) {
126
- const commands: Record<string, { description: string }> = {}
127
- for (const command of opts.commands) {
128
- commands[command] = { description: `A ${toTitleCase(command)} command` }
129
- }
130
- manifest.commands = commands
131
- }
132
-
133
- return JSON.stringify(manifest, null, 2)
134
- }
135
-
136
- // --- File listing preview ---
137
-
138
- export function previewFiles(opts: CreateOptions): string[] {
139
- const files: string[] = [FACET_MANIFEST_FILE]
140
- for (const skill of opts.skills) {
141
- files.push(`skills/${skill}/SKILL.md`)
142
- }
143
- for (const agent of opts.agents) {
144
- files.push(`agents/${agent}.md`)
145
- }
146
- for (const command of opts.commands) {
147
- files.push(`commands/${command}.md`)
148
- }
149
- return files
150
- }
151
-
152
- // --- Scaffold writing ---
153
-
154
- export async function writeScaffold(opts: CreateOptions, targetDir: string): Promise<string[]> {
155
- const files: string[] = []
156
-
157
- // Write manifest
158
- const manifestPath = join(targetDir, FACET_MANIFEST_FILE)
159
- await Bun.write(manifestPath, generateManifest(opts))
160
- files.push(FACET_MANIFEST_FILE)
161
-
162
- // Write skill files (Agent Skills directory convention: skills/<name>/SKILL.md)
163
- for (const skill of opts.skills) {
164
- await mkdir(join(targetDir, 'skills', skill), { recursive: true })
165
- await Bun.write(join(targetDir, `skills/${skill}/SKILL.md`), skillTemplate(skill))
166
- files.push(`skills/${skill}/SKILL.md`)
167
- }
168
-
169
- // Write agent files
170
- for (const agent of opts.agents) {
171
- await mkdir(join(targetDir, 'agents'), { recursive: true })
172
- await Bun.write(join(targetDir, `agents/${agent}.md`), agentTemplate(agent))
173
- files.push(`agents/${agent}.md`)
174
- }
175
-
176
- // Write command files
177
- for (const command of opts.commands) {
178
- await mkdir(join(targetDir, 'commands'), { recursive: true })
179
- await Bun.write(join(targetDir, `commands/${command}.md`), commandTemplate(command))
180
- files.push(`commands/${command}.md`)
181
- }
182
-
183
- return files
184
- }
@@ -1,144 +0,0 @@
1
- import { mkdir } from 'node:fs/promises'
2
- import { join } from 'node:path'
3
- import {
4
- type FacetManifest,
5
- hasFrontMatter,
6
- loadManifest,
7
- reconcile,
8
- scanAssets,
9
- writeManifest,
10
- } from '@agent-facets/core'
11
- import type { Command } from '../../commands.ts'
12
- import type { EditContext, EditOperation, EditResult, ReconciliationItem } from '../../tui/views/edit/edit-types.ts'
13
- import { agentTemplate, commandTemplate, skillTemplate } from '../create-scaffold.ts'
14
- import { resolveTargetDir } from '../resolve-dir.ts'
15
- import { runEditWizardInk } from './wizard.tsx'
16
-
17
- export async function buildEditContext(
18
- rootDir: string,
19
- ): Promise<{ ok: true; context: EditContext } | { ok: false; exitCode: number }> {
20
- // Load manifest
21
- const loadResult = await loadManifest(rootDir)
22
- if (!loadResult.ok) {
23
- // Hard error — show errors and exit
24
- console.error('Manifest is invalid:')
25
- for (const err of loadResult.errors) {
26
- console.error(` ${err.message}`)
27
- }
28
- console.error('\nFix facet.json and try again.')
29
- return { ok: false, exitCode: 1 }
30
- }
31
-
32
- const manifest = loadResult.data
33
-
34
- // Scan disk for assets
35
- const discovered = await scanAssets(rootDir)
36
-
37
- // Run reconciliation
38
- const recon = reconcile(manifest, discovered)
39
-
40
- // Build reconciliation items
41
- const items: ReconciliationItem[] = []
42
-
43
- for (const addition of recon.additions) {
44
- items.push({ kind: 'addition', type: addition.type, name: addition.name, path: addition.path })
45
- }
46
-
47
- for (const missing of recon.missing) {
48
- items.push({ kind: 'missing', type: missing.type, name: missing.name, expectedPath: missing.expectedPath })
49
- }
50
-
51
- // Check matched assets for front matter
52
- for (const matched of recon.matched) {
53
- const filePath = join(rootDir, matched.path)
54
- const content = await Bun.file(filePath).text()
55
- if (hasFrontMatter(content)) {
56
- items.push({ kind: 'front-matter', type: matched.type, name: matched.name, path: matched.path })
57
- }
58
- }
59
-
60
- return { ok: true, context: { rootDir, manifest, reconciliationItems: items } }
61
- }
62
-
63
- export async function applyOperations(
64
- manifest: FacetManifest,
65
- operations: EditOperation[],
66
- rootDir: string,
67
- ): Promise<void> {
68
- for (const op of operations) {
69
- switch (op.op) {
70
- case 'write-manifest':
71
- await writeManifest(manifest, rootDir)
72
- break
73
-
74
- case 'scaffold': {
75
- if (op.type === 'skills') {
76
- const dir = join(rootDir, 'skills', op.name)
77
- await mkdir(dir, { recursive: true })
78
- await Bun.write(join(dir, 'SKILL.md'), skillTemplate(op.name))
79
- } else if (op.type === 'agents') {
80
- await mkdir(join(rootDir, 'agents'), { recursive: true })
81
- await Bun.write(join(rootDir, `agents/${op.name}.md`), agentTemplate(op.name))
82
- } else if (op.type === 'commands') {
83
- await mkdir(join(rootDir, 'commands'), { recursive: true })
84
- await Bun.write(join(rootDir, `commands/${op.name}.md`), commandTemplate(op.name))
85
- }
86
- break
87
- }
88
-
89
- case 'delete-file': {
90
- const path =
91
- op.type === 'skills' ? join(rootDir, 'skills', op.name, 'SKILL.md') : join(rootDir, op.type, `${op.name}.md`)
92
- try {
93
- const { unlink } = await import('node:fs/promises')
94
- await unlink(path)
95
- } catch {
96
- // File already gone — that's fine
97
- }
98
- break
99
- }
100
-
101
- case 'strip-front-matter': {
102
- const { extractFrontMatter } = await import('@agent-facets/core')
103
- const filePath = join(rootDir, op.path)
104
- const raw = await Bun.file(filePath).text()
105
- const { content } = extractFrontMatter(raw)
106
- await Bun.write(filePath, content)
107
- break
108
- }
109
- }
110
- }
111
- }
112
-
113
- export const editCommand: Command = {
114
- name: 'edit',
115
- description: 'Edit a facet project interactively',
116
- usage: '[directory]',
117
- run: async (args: string[], _flags: Record<string, unknown>): Promise<number> => {
118
- const resolved = await resolveTargetDir(args[0], { mustExist: true, facetMustExist: true })
119
- if (!resolved.ok) {
120
- console.error(resolved.message)
121
- return 1
122
- }
123
-
124
- const rootDir = resolved.dir
125
- const displayDir = resolved.display
126
-
127
- const loaded = await buildEditContext(rootDir)
128
- if (!loaded.ok) return loaded.exitCode
129
-
130
- const result: EditResult = await runEditWizardInk(loaded.context)
131
-
132
- if (result.outcome === 'cancelled') {
133
- console.log('\nCancelled — no changes applied.')
134
- return 1
135
- }
136
-
137
- await applyOperations(result.manifest, result.operations, rootDir)
138
-
139
- console.log(`\nChanges applied to ${displayDir}`)
140
- console.log(`Run "facet build${args[0] ? ` ${displayDir}` : ''}" to validate your facet.`)
141
-
142
- return 0
143
- },
144
- }
@@ -1,74 +0,0 @@
1
- import { render } from 'ink'
2
- import type { AssetSectionKey } from '../../tui/context/form-state-context.ts'
3
- import { openInEditorSync } from '../../tui/editor.ts'
4
- import type { EditContext, EditResult } from '../../tui/views/edit/edit-types.ts'
5
- import type { EditWizardSnapshot } from '../../tui/views/edit/wizard.tsx'
6
- import { EditWizard } from '../../tui/views/edit/wizard.tsx'
7
-
8
- interface EditorRequest {
9
- section: AssetSectionKey
10
- name: string
11
- description: string
12
- }
13
-
14
- export async function runEditWizardInk(context: EditContext): Promise<EditResult> {
15
- let result: EditResult = { outcome: 'cancelled' }
16
- let snapshot: EditWizardSnapshot | undefined
17
- let pendingEditor: EditorRequest | null = null
18
- let done = false
19
-
20
- while (!done) {
21
- pendingEditor = null
22
-
23
- await new Promise<void>((resolve) => {
24
- const instance = render(
25
- <EditWizard
26
- context={context}
27
- snapshot={snapshot}
28
- onComplete={(r) => {
29
- result = r
30
- }}
31
- onSnapshot={(s) => {
32
- snapshot = s
33
- }}
34
- onRequestEditor={(section, name, description) => {
35
- pendingEditor = { section, name, description }
36
- instance.unmount()
37
- }}
38
- />,
39
- )
40
-
41
- instance.waitUntilExit().then(() => resolve())
42
- })
43
-
44
- if (pendingEditor) {
45
- const req = pendingEditor as EditorRequest
46
- const edited = openInEditorSync(req.description, `${req.name}.md`)
47
- if (snapshot) {
48
- snapshot = {
49
- ...snapshot,
50
- selectedItem: undefined,
51
- formState: snapshot.formState
52
- ? {
53
- ...snapshot.formState,
54
- assets: {
55
- ...snapshot.formState.assets,
56
- [req.section]: {
57
- ...snapshot.formState.assets[req.section],
58
- descriptions: {
59
- ...snapshot.formState.assets[req.section].descriptions,
60
- ...(edited !== null ? { [req.name]: edited.trim() } : {}),
61
- },
62
- },
63
- },
64
- }
65
- : undefined,
66
- }
67
- }
68
- } else {
69
- done = true
70
- }
71
- }
72
-
73
- return result
74
- }
@@ -1,98 +0,0 @@
1
- import { mkdir, stat } from 'node:fs/promises'
2
- import { dirname, join, resolve } from 'node:path'
3
- import { FACET_MANIFEST_FILE } from '@agent-facets/core'
4
-
5
- export interface ResolvedDir {
6
- ok: true
7
- dir: string
8
- display: string
9
- }
10
-
11
- export interface ResolvedDirError {
12
- ok: false
13
- message: string
14
- }
15
-
16
- export type ResolveResult = ResolvedDir | ResolvedDirError
17
-
18
- export interface ResolveOptions {
19
- mustExist: boolean
20
- facetMustExist?: boolean
21
- }
22
-
23
- /**
24
- * Validates and resolves a directory argument for CLI commands.
25
- *
26
- * Handles:
27
- * - No argument → uses process.cwd(), display as '.'
28
- * - Argument ending with facet.json → silently uses parent directory
29
- * - Argument is a non-directory file → error
30
- * - Directory doesn't exist + mustExist false → auto-creates it
31
- * - Directory doesn't exist + mustExist true → error
32
- * - facetMustExist true but no facet.json → error
33
- */
34
- export async function resolveTargetDir(arg: string | undefined, opts: ResolveOptions): Promise<ResolveResult> {
35
- const display = arg || '.'
36
-
37
- // No argument → current directory
38
- if (!arg) {
39
- const dir = process.cwd()
40
-
41
- if (opts.facetMustExist) {
42
- const manifestExists = await Bun.file(join(dir, FACET_MANIFEST_FILE)).exists()
43
- if (!manifestExists) {
44
- return { ok: false, message: `No ${FACET_MANIFEST_FILE} found in ${display}` }
45
- }
46
- }
47
-
48
- return { ok: true, dir, display }
49
- }
50
-
51
- // Pointing to facet.json directly → use parent directory
52
- if (arg === FACET_MANIFEST_FILE || arg.endsWith(`/${FACET_MANIFEST_FILE}`)) {
53
- const dir = resolve(dirname(arg))
54
-
55
- const dirStat = await stat(dir).catch(() => null)
56
- if (!dirStat?.isDirectory()) {
57
- return { ok: false, message: `Directory does not exist: ${dirname(arg)}` }
58
- }
59
-
60
- if (opts.facetMustExist) {
61
- const manifestExists = await Bun.file(join(dir, FACET_MANIFEST_FILE)).exists()
62
- if (!manifestExists) {
63
- return { ok: false, message: `No ${FACET_MANIFEST_FILE} found in ${display}` }
64
- }
65
- }
66
-
67
- return { ok: true, dir, display: dirname(arg) || '.' }
68
- }
69
-
70
- // Check if the path exists
71
- const pathStat = await stat(arg).catch(() => null)
72
-
73
- // Path exists but is a file, not a directory
74
- if (pathStat && !pathStat.isDirectory()) {
75
- return { ok: false, message: `Expected a directory, not a file: ${arg}` }
76
- }
77
-
78
- // Path doesn't exist
79
- if (!pathStat) {
80
- if (opts.mustExist) {
81
- return { ok: false, message: `Directory does not exist: ${arg}` }
82
- }
83
-
84
- // Auto-create for commands that allow it (e.g., create)
85
- await mkdir(arg, { recursive: true })
86
- }
87
-
88
- const dir = resolve(arg)
89
-
90
- if (opts.facetMustExist) {
91
- const manifestExists = await Bun.file(join(dir, FACET_MANIFEST_FILE)).exists()
92
- if (!manifestExists) {
93
- return { ok: false, message: `No ${FACET_MANIFEST_FILE} found in ${display}` }
94
- }
95
- }
96
-
97
- return { ok: true, dir, display }
98
- }
package/src/commands.ts DELETED
@@ -1,40 +0,0 @@
1
- import { buildCommand } from './commands/build.ts'
2
- import { createCommand } from './commands/create/index.ts'
3
- import { editCommand } from './commands/edit/index.ts'
4
-
5
- export type FlagDef = {
6
- type: 'boolean' | 'string'
7
- description: string
8
- }
9
-
10
- export type Command = {
11
- name: string
12
- description: string
13
- usage?: string
14
- flags?: Record<string, FlagDef>
15
- run: (args: string[], flags: Record<string, unknown>) => Promise<number>
16
- }
17
-
18
- function stubCommand(name: string, description: string): Command {
19
- return {
20
- name,
21
- description,
22
- run: async (_args, _flags) => {
23
- console.log(`"${name}" is not yet implemented.`)
24
- return 0
25
- },
26
- }
27
- }
28
-
29
- export const commands: Record<string, Command> = {
30
- add: stubCommand('add', 'Add a facet to the project'),
31
- build: buildCommand,
32
- create: createCommand,
33
- edit: editCommand,
34
- info: stubCommand('info', 'Show information about a facet'),
35
- install: stubCommand('install', 'Install all facets from the lockfile'),
36
- list: stubCommand('list', 'List installed facets'),
37
- publish: stubCommand('publish', 'Publish a facet to the registry'),
38
- remove: stubCommand('remove', 'Remove a facet from the project'),
39
- upgrade: stubCommand('upgrade', 'Upgrade installed facets'),
40
- }