agent-facets 0.2.2 → 0.3.3

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 (59) hide show
  1. package/bin/facet +181 -0
  2. package/bin/package.json +3 -0
  3. package/package.json +17 -37
  4. package/postinstall.mjs +210 -0
  5. package/.package.json.bak +0 -44
  6. package/.turbo/turbo-build.log +0 -3
  7. package/CHANGELOG.md +0 -85
  8. package/bunfig.toml +0 -2
  9. package/dist/facet +0 -0
  10. package/src/__tests__/cli.test.ts +0 -195
  11. package/src/__tests__/create-build.test.ts +0 -227
  12. package/src/__tests__/edit-integration.test.ts +0 -171
  13. package/src/__tests__/resolve-dir.test.ts +0 -95
  14. package/src/commands/build.ts +0 -58
  15. package/src/commands/create/index.ts +0 -76
  16. package/src/commands/create/types.ts +0 -9
  17. package/src/commands/create/wizard.tsx +0 -75
  18. package/src/commands/create-scaffold.ts +0 -184
  19. package/src/commands/edit/index.ts +0 -144
  20. package/src/commands/edit/wizard.tsx +0 -74
  21. package/src/commands/resolve-dir.ts +0 -98
  22. package/src/commands.ts +0 -40
  23. package/src/help.ts +0 -43
  24. package/src/index.ts +0 -10
  25. package/src/run.ts +0 -82
  26. package/src/suggest.ts +0 -35
  27. package/src/tui/components/asset-description.tsx +0 -17
  28. package/src/tui/components/asset-field-picker.tsx +0 -78
  29. package/src/tui/components/asset-inline-input.tsx +0 -91
  30. package/src/tui/components/asset-item.tsx +0 -44
  31. package/src/tui/components/asset-section.tsx +0 -191
  32. package/src/tui/components/button.tsx +0 -92
  33. package/src/tui/components/editable-field.tsx +0 -172
  34. package/src/tui/components/exit-toast.tsx +0 -20
  35. package/src/tui/components/reconciliation-item.tsx +0 -129
  36. package/src/tui/components/stage-row.tsx +0 -45
  37. package/src/tui/components/version-selector.tsx +0 -79
  38. package/src/tui/context/focus-mode-context.ts +0 -36
  39. package/src/tui/context/focus-order-context.ts +0 -68
  40. package/src/tui/context/form-state-context.ts +0 -260
  41. package/src/tui/editor.ts +0 -40
  42. package/src/tui/gradient.ts +0 -1
  43. package/src/tui/hooks/use-exit-keys.ts +0 -75
  44. package/src/tui/hooks/use-navigation-keys.ts +0 -34
  45. package/src/tui/layouts/wizard-layout.tsx +0 -41
  46. package/src/tui/theme.ts +0 -1
  47. package/src/tui/views/build/build-view.tsx +0 -152
  48. package/src/tui/views/create/confirm-view.tsx +0 -74
  49. package/src/tui/views/create/create-view.tsx +0 -158
  50. package/src/tui/views/create/wizard.tsx +0 -97
  51. package/src/tui/views/edit/edit-confirm-view.tsx +0 -93
  52. package/src/tui/views/edit/edit-types.ts +0 -34
  53. package/src/tui/views/edit/edit-view.tsx +0 -140
  54. package/src/tui/views/edit/manifest-to-form.ts +0 -38
  55. package/src/tui/views/edit/reconciliation-view.tsx +0 -170
  56. package/src/tui/views/edit/use-edit-session.ts +0 -125
  57. package/src/tui/views/edit/wizard.tsx +0 -129
  58. package/src/version.ts +0 -3
  59. package/tsconfig.json +0 -4
@@ -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
- }
package/src/help.ts DELETED
@@ -1,43 +0,0 @@
1
- import type { Command } from './commands.ts'
2
- import { version } from './version.ts'
3
-
4
- export function printGlobalHelp(commands: Record<string, Command>): void {
5
- const entries = Object.values(commands)
6
- const maxNameLength = Math.max(...entries.map((c) => c.name.length))
7
-
8
- const lines = [
9
- `facet v${version}`,
10
- '',
11
- 'Usage: facet <command> [options]',
12
- '',
13
- 'Commands:',
14
- ...entries.map((c) => ` ${c.name.padEnd(maxNameLength + 2)}${c.description}`),
15
- '',
16
- 'Options:',
17
- ' --help Show help',
18
- ' --version Show version',
19
- ]
20
-
21
- console.log(lines.join('\n'))
22
- }
23
-
24
- export function printCommandHelp(command: Command): void {
25
- const usage = command.usage ? ` ${command.usage}` : ''
26
-
27
- const lines = [`Usage: facet ${command.name}${usage} [options]`, '', ` ${command.description}`, '', 'Options:']
28
-
29
- if (command.flags) {
30
- const flagEntries = Object.entries(command.flags)
31
- const maxFlagLength = Math.max(...flagEntries.map(([name]) => `--${name}`.length), '--help'.length)
32
-
33
- for (const [name, def] of flagEntries) {
34
- lines.push(` ${`--${name}`.padEnd(maxFlagLength + 4)}${def.description}`)
35
- }
36
-
37
- lines.push(` ${'--help'.padEnd(maxFlagLength + 4)}Show help`)
38
- } else {
39
- lines.push(' --help Show help')
40
- }
41
-
42
- console.log(lines.join('\n'))
43
- }
package/src/index.ts DELETED
@@ -1,10 +0,0 @@
1
- import { commands } from './commands.ts'
2
- import { run } from './run.ts'
3
-
4
- try {
5
- const code = await run(process.argv.slice(2), commands)
6
- process.exit(code)
7
- } catch (error) {
8
- console.error(error instanceof Error ? error.message : 'An unexpected error occurred.')
9
- process.exit(2)
10
- }
package/src/run.ts DELETED
@@ -1,82 +0,0 @@
1
- import { parse } from '@bomb.sh/args'
2
- import type { Command } from './commands.ts'
3
- import { printCommandHelp, printGlobalHelp } from './help.ts'
4
- import { findClosestCommand } from './suggest.ts'
5
- import { version } from './version.ts'
6
-
7
- export async function run(argv: string[], commands: Record<string, Command>): Promise<number> {
8
- const args = parse(argv, {
9
- boolean: ['help', 'version'],
10
- })
11
-
12
- if (args.version) {
13
- console.log(version)
14
- return 0
15
- }
16
-
17
- const commandName = String(args._[0] ?? '')
18
-
19
- // No command given — show global help
20
- if (!commandName) {
21
- printGlobalHelp(commands)
22
- return 0
23
- }
24
-
25
- // Explicit `help` command: `facets help` or `facets help build`
26
- if (commandName === 'help') {
27
- const subCommandName = String(args._[1] ?? '')
28
- const subCommand = subCommandName ? commands[subCommandName] : undefined
29
- if (subCommand) {
30
- printCommandHelp(subCommand)
31
- } else {
32
- printGlobalHelp(commands)
33
- }
34
- return 0
35
- }
36
-
37
- const command = commands[commandName]
38
-
39
- if (!command) {
40
- const suggestion = findClosestCommand(commandName, Object.keys(commands))
41
- const message = suggestion
42
- ? `Unknown command "${commandName}". Did you mean "${suggestion}"?`
43
- : `Unknown command "${commandName}".`
44
- console.error(message)
45
- return 1
46
- }
47
-
48
- // Per-command help: `facets build --help`
49
- if (args.help) {
50
- printCommandHelp(command)
51
- return 0
52
- }
53
-
54
- // Build per-command flag parsing config
55
- const booleanFlags: string[] = []
56
- const stringFlags: string[] = []
57
-
58
- if (command.flags) {
59
- for (const [name, def] of Object.entries(command.flags)) {
60
- if (def.type === 'boolean') booleanFlags.push(name)
61
- else if (def.type === 'string') stringFlags.push(name)
62
- }
63
- }
64
-
65
- // Parse with per-command config
66
- const parsed = parse(argv.slice(1), {
67
- boolean: booleanFlags,
68
- string: stringFlags,
69
- })
70
-
71
- // Build positional args and flags
72
- const positionalArgs = parsed._.map(String)
73
- const flags: Record<string, unknown> = {}
74
-
75
- for (const name of [...booleanFlags, ...stringFlags]) {
76
- if (parsed[name] !== undefined) {
77
- flags[name] = parsed[name]
78
- }
79
- }
80
-
81
- return command.run(positionalArgs, flags)
82
- }
package/src/suggest.ts DELETED
@@ -1,35 +0,0 @@
1
- function levenshtein(a: string, b: string): number {
2
- const m = a.length
3
- const n = b.length
4
- const row: number[] = []
5
-
6
- for (let j = 0; j <= n; j++) row.push(j)
7
-
8
- for (let i = 1; i <= m; i++) {
9
- let prev = row[0] ?? 0
10
- row[0] = i
11
- for (let j = 1; j <= n; j++) {
12
- const current = row[j] ?? 0
13
- const cost = a[i - 1] === b[j - 1] ? 0 : 1
14
- row[j] = Math.min(current + 1, (row[j - 1] ?? 0) + 1, prev + cost)
15
- prev = current
16
- }
17
- }
18
-
19
- return row[n] ?? 0
20
- }
21
-
22
- export function findClosestCommand(input: string, commandNames: string[]): string | undefined {
23
- let best: string | undefined
24
- let bestDistance = Infinity
25
-
26
- for (const name of commandNames) {
27
- const distance = levenshtein(input, name)
28
- if (distance < bestDistance) {
29
- bestDistance = distance
30
- best = name
31
- }
32
- }
33
-
34
- return bestDistance <= 3 ? best : undefined
35
- }
@@ -1,17 +0,0 @@
1
- import { Box, Text } from 'ink'
2
-
3
- export function truncateDescription(text: string, maxLen = 50): string {
4
- const lines = text.split(/\r?\n/)
5
- const firstLine = lines[0] ?? text
6
- const hasMore = lines.length > 1
7
- if (firstLine.length <= maxLen) return hasMore ? `${firstLine}...` : firstLine
8
- return `${firstLine.slice(0, maxLen)}...`
9
- }
10
-
11
- export function AssetDescription({ description }: { description: string }) {
12
- return (
13
- <Box marginLeft={4}>
14
- <Text dimColor>{truncateDescription(description)}</Text>
15
- </Box>
16
- )
17
- }
@@ -1,78 +0,0 @@
1
- import { Box, Text, useInput } from 'ink'
2
- import { useEffect, useState } from 'react'
3
- import { useFocusMode } from '../context/focus-mode-context.ts'
4
- import { THEME } from '../theme.ts'
5
-
6
- export type AssetField = 'name' | 'description'
7
-
8
- export function AssetFieldPicker({
9
- name,
10
- description,
11
- initialField,
12
- onChoose,
13
- onCancel,
14
- }: {
15
- name: string
16
- description: string
17
- initialField?: AssetField
18
- onChoose: (field: AssetField) => void
19
- onCancel: () => void
20
- }) {
21
- const { setMode } = useFocusMode()
22
- const [field, setField] = useState<AssetField>(initialField ?? 'name')
23
-
24
- useEffect(() => {
25
- setMode('field-revision')
26
- return () => setMode('form-navigation')
27
- }, [setMode])
28
-
29
- useInput((_input, key) => {
30
- if (key.upArrow) setField('name')
31
- if (key.downArrow) setField('description')
32
- if (key.escape) onCancel()
33
- if (key.return) onChoose(field)
34
- })
35
-
36
- return (
37
- <Box flexDirection="column">
38
- <Box gap={1} marginLeft={2}>
39
- {field === 'name' ? (
40
- <>
41
- <Text color={THEME.primary} bold>
42
-
43
- </Text>
44
- <Text color={THEME.primary}>{name}</Text>
45
- <Text color={THEME.hint}>
46
- <Text color={THEME.keyword}>↑↓</Text> select · <Text color={THEME.keyword}>Enter</Text> edit ·{' '}
47
- <Text color={THEME.keyword}>Esc</Text> back
48
- </Text>
49
- </>
50
- ) : (
51
- <>
52
- <Text color={THEME.success}>•</Text>
53
- <Text>{name}</Text>
54
- </>
55
- )}
56
- </Box>
57
- <Box gap={1} marginLeft={2}>
58
- {field === 'description' ? (
59
- <>
60
- <Text color={THEME.primary} bold>
61
-
62
- </Text>
63
- <Text color={THEME.primary}>{description}</Text>
64
- <Text color={THEME.hint}>
65
- <Text color={THEME.keyword}>↑↓</Text> select · <Text color={THEME.keyword}>Enter</Text> edit ·{' '}
66
- <Text color={THEME.keyword}>Esc</Text> back
67
- </Text>
68
- </>
69
- ) : (
70
- <>
71
- <Text> </Text>
72
- <Text dimColor>{description}</Text>
73
- </>
74
- )}
75
- </Box>
76
- </Box>
77
- )
78
- }
@@ -1,91 +0,0 @@
1
- import { Box, Text, useInput } from 'ink'
2
- import TextInput from 'ink-text-input'
3
- import { THEME } from '../theme.ts'
4
-
5
- export function AssetInlineInput({
6
- value,
7
- placeholder,
8
- error,
9
- isFocused,
10
- onChange,
11
- validate,
12
- onError,
13
- onSubmit,
14
- onCancel,
15
- onDownArrow,
16
- }: {
17
- id: string
18
- value: string
19
- placeholder?: string
20
- error: string
21
- isFocused: boolean
22
- onChange: (value: string) => void
23
- validate?: (value: string) => string | undefined
24
- onError: (error: string) => void
25
- onSubmit: (name: string) => void
26
- onCancel: () => void
27
- onDownArrow?: () => void
28
- }) {
29
- useInput(
30
- (_input, key) => {
31
- if (!isFocused) return
32
- if (key.return) {
33
- const name = value || placeholder || ''
34
- if (!name) {
35
- onCancel()
36
- return
37
- }
38
- if (validate) {
39
- const err = validate(name)
40
- if (err) {
41
- onError(err)
42
- return
43
- }
44
- }
45
- onSubmit(name)
46
- return
47
- }
48
- if (key.escape) {
49
- onCancel()
50
- return
51
- }
52
- if (key.downArrow && onDownArrow) {
53
- onDownArrow()
54
- return
55
- }
56
- if (key.tab && !value && placeholder) {
57
- onChange(placeholder)
58
- return
59
- }
60
- },
61
- { isActive: isFocused },
62
- )
63
-
64
- return (
65
- <Box marginLeft={2} gap={1}>
66
- <Text color={THEME.tertiary}>{'>'}</Text>
67
- <TextInput value={value} onChange={onChange} placeholder={placeholder} focus={isFocused} />
68
- {error ? (
69
- <Text color={THEME.warning}>· {error}</Text>
70
- ) : value ? (
71
- <Text color={THEME.hint}>
72
- · <Text color={THEME.keyword}>Enter</Text> save ·{' '}
73
- {onDownArrow && (
74
- <>
75
- <Text color={THEME.keyword}>↓</Text> description ·{' '}
76
- </>
77
- )}
78
- <Text color={THEME.keyword}>Esc</Text> revert
79
- </Text>
80
- ) : placeholder ? (
81
- <Text color={THEME.hint}>
82
- · <Text color={THEME.keyword}>Tab</Text> to fill · <Text color={THEME.keyword}>Escape</Text> to cancel
83
- </Text>
84
- ) : (
85
- <Text color={THEME.hint}>
86
- · <Text color={THEME.keyword}>Enter</Text> to add · <Text color={THEME.keyword}>Escape</Text> to cancel
87
- </Text>
88
- )}
89
- </Box>
90
- )
91
- }
@@ -1,44 +0,0 @@
1
- import { Box, Text, useInput } from 'ink'
2
- import { THEME } from '../theme.ts'
3
-
4
- export function AssetItem({
5
- name,
6
- isFocused,
7
- onEdit,
8
- onRemove,
9
- }: {
10
- id: string
11
- name: string
12
- isFocused: boolean
13
- onEdit: () => void
14
- onRemove: () => void
15
- }) {
16
- useInput(
17
- (_input, key) => {
18
- if (key.return) onEdit()
19
- if (key.delete || key.backspace) onRemove()
20
- },
21
- { isActive: isFocused },
22
- )
23
-
24
- return (
25
- <Box gap={1} marginLeft={2}>
26
- {isFocused ? (
27
- <>
28
- <Text color={THEME.primary} bold>
29
-
30
- </Text>
31
- <Text color={THEME.primary}>{name}</Text>
32
- <Text color={THEME.hint}>
33
- <Text color={THEME.keyword}>Enter</Text> edit name · <Text color={THEME.keyword}>Del</Text> remove
34
- </Text>
35
- </>
36
- ) : (
37
- <>
38
- <Text color={THEME.success}>•</Text>
39
- <Text>{name}</Text>
40
- </>
41
- )}
42
- </Box>
43
- )
44
- }