Adds an /explorer command that opens a file explorer pane rooted at the folder Claude Code was started in

claude code란 Anthropic에서 만든 개발자용 AI 어시스턴트 입니다.
클로드 코드는 agent, skill, hook, command 등을 통해 보다 확장성 있는 LLM 워크플로우를 구축할 수 있도록 도와줍니다.
이 글을 통해 클로드 코드를 활용하여 코드 생산 뿐 아니라, 나만의 개발 워크플로우를 자동화하고 싶은 개발자 또는 LLM을 터미널 환경에 깊숙이 통합하고 싶은 DevOps 엔지니어가 읽기 적합합니다.
주로 다루는 내용은 다음과 같습니다.
해당 내용을 실행하기 위해선 claude code 2.x 이상의 버전이 필요합니다.
만약, 1.x라면 2.x로 업데이트 해야합니다.
참고로 본문 내용에서는 2가지 형태의 코드 블록이 있습니다.
$ shell 명령어
> 클로드 코드 프롬프트
클로드 코드는 이미 정의된 명령어 또는 사용자 정의 슬래시 명령어를 다음과 같이 사용이 가능 합니다.
사용자 정의 슬래시는 ~/.claude/commands 또는 .claude/commands 경로에 마크다운 파일로 관리됩니다.
> /[command-name] [arguments]
또는 네임스페이스를 다음과 같이 지정할 수 있습니다.
~/.claude/[NAMESPACE]/commands 또는 .claude/[NAMESPACE]/commands
> /[NAMESPACE]:[command-name] [arguments]
$ echo "Analyze this code for performance issues and suggest optimizations:" > .claude/commands/optimize.md
$ echo "Review this code for security vulnerabilities:" > .claude/commands/security-review.md
$ echo 'Fix issue #$ARGUMENTS following our coding standards' > .claude/commands/fix-issue.md
$ echo 'Review PR #$1 with priority $2 and assign to $3' > .claude/commands/review-pr.md
> /fix-issue 123 high-priority
/fix-issue is running… 123 high-priority
⏺ I'll fix issue #123 with high priority following your coding standards.
Let me start by fetching the issue details from GitHub.
⏺ Bash(gh issue view 123 --json title,body,labels,state,comments)
⎿ Running…
──────────────────────────────────────────────────────────────────────────────────────
Bash command
gh issue view 123 --json title,body,labels,state,comments
Fetch issue #123 details from GitHub
Do you want to proceed?
❯ 1. Yes
2. Yes, and don't ask again for gh issue view commands in
/Users/jeongtae/Desktop/claude-code-advanced-guide
3. No, and tell Claude what to do differently (esc)
> /review-pr 456 high alice
/review-pr is running… 456 high alice
⏺ I'll review PR #456 with high priority and prepare it for assignment to alice.
Let me start by fetching the PR information using the GitHub CLI.
⏺ Bash(gh pr view 456 --json
title,body,author,state,headRefName,baseRefName,files,commits)
⎿ Interrupted · What should Claude do instead?
@를 이용하면 파일을 참조할 수 있습니다.
$ echo 'Compare @src/need_debugger.py with @src/need_review.py' > .claude/commands/referrence-file.md
> /referrence-file
/referrence-file is running…
⏺ I'll compare these two Python files for you. Let me read both files first.
⏺ Read(src/need_debugger.py)
⎿ Error reading file
⏺ Read(src/need_review.py)
⎿ Error reading file
⏺ The files don't exist at those paths. Let me search for them in the repository.
⏺ Search(pattern: "**/need_debugger.py")
⎿ Found 1 file (ctrl+o to expand)
⏺ Search(pattern: "**/need_review.py")
⎿ Found 1 file (ctrl+o to expand)
⏺ Found them! They're in the root directory. Let me read both files now.
⏺ Read(need_debugger.py)
⎿ Read 12 lines
⏺ Read(need_review.py)
⎿ Read 7 lines
⏺ Comparison of need_debugger.py vs need_review.py
need_debugger.py (need_debugger.py:1-12)
def main() -> None:
student = {
"name": "Alice",
"age": 21,
"courses": ["Math", "Science", "Art"]
}
print(student["a"]) # BUG: KeyError
if __name__ == "__main__":
main()
Issues:
- Bug at line 8: Attempts to access student["a"] which doesn't exist, causing a
KeyError
- Should likely be student["age"] or student["name"]
need_review.py (need_review.py:1-7)
def main() -> None:
print("Hello, World!")
if __name__ == "__main__":
main()
Characteristics:
- Simple "Hello, World!" program
- No bugs, but basic implementation
- Could potentially benefit from code review for production standards
Key Differences:
1. Complexity: need_debugger.py has more complex logic with a dictionary
structure, while need_review.py is minimal
2. Purpose: need_debugger.py clearly has a bug that needs debugging, while
need_review.py is functional but basic
3. Error state: need_debugger.py will crash at runtime, need_review.py runs
successfully
The naming suggests these files are examples for testing specialized agents -
one needs the debugger agent (has a bug), while the other needs the
code-reviewer agent (works but could be reviewed for quality).
커맨드 정의를 위한 마크다운 파일에 !배시 명령어 형태로 배시 명령어를 추가할 수 있습니다.
---
allowed-tools: Bash(git add:*), Bash(git status:*), Bash(git commit:*)
argument-hint: [message]
description: Create a git commit
model: claude-3-5-haiku-20241022
---
Create a git commit with message: $ARGUMENTS
allowed-tools: 명령어가 사용할 수 있는 도구 목록
argument-hint: 슬래시 명령어에 필요한 인수
description: 명령어의 간단한 설명
model: 특정 모델 문자열
disable-model-invocation: 슬래시 명령어가 호출하지 말아야 하는 명령어
.claude/commands/bash-command.md
---
allowed-tools: Bash(git add:*), Bash(git status:*), Bash(git commit:*)
description: Create a git commit
---
## Context
- Current git status: !`git status`
- Current git diff (staged and unstaged changes): !`git diff HEAD`
- Current branch: !`git branch --show-current`
- Recent commits: !`git log --oneline -10`
## Your task
Based on the above changes, create a single git commit.
> /bash-command
> /bash-command is running…
⎿ Allowed 3 tools for this command
⏺ I can see there's a modification to README.md that adds documentation about bash
command execution in command definitions. Let me create a commit for this
change.
⏺ Bash(git add README.md && git commit -m "Add documentation for bash command
execution in custom commands…)
⎿ [main 5fb2eb9] Add documentation for bash command execution in custom command
s
1 file changed, 22 insertions(+)
⏺ Bash(git status)
⎿ On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
… +4 lines (ctrl+o to expand)
⏺ Commit created successfully! The changes to README.md have been committed with
the message documenting the new bash command execution feature.
I notice there's also a modification to .claude/bash-command-log.txt, which
appears to be a log file. Would you like me to commit that as well, or should we
ignore log files?
깃 로그를 살펴보면 다음과 같이 로그가 생성된 것을 확인할 수 있습니다.
$ git log
commit 5fb2eb93632702846b8d7f1bf344cba7f97fa2fc (HEAD -> main)
Author: pjt3591oo <pjt3591oo@maskit.co.kr>
Date: Thu Oct 30 04:48:28 2025 +0900
Add documentation for bash command execution in custom commands
Added Korean documentation explaining how to use !`bash command` syntax in command definition markdown files, with a practical example showing git-related commands.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
commit 72c2cda2ec614f5f3ef28e56262e9c6e44a1046a
Author: pjt3591oo <pjt3591oo@maskit.co.kr>
Date: Thu Oct 30 04:47:24 2025 +0900
first commit
클로드 코드는 네이티브 수준에서 기본 명령어를 제공합니다.
/mcp, /plugin, /hook, /agents 등
사용 가능한 명령어는 클로드 프롬프트에 /를 입력하면 사용 가능한 명령어 목록을 확인할 수 있습니다.
──────────────────────────────────────────────────────────────────────────────────────
> /
──────────────────────────────────────────────────────────────────────────────────────
/doctor Diagnose and verify your Claude Code installation
and settings
/exit (quit) Exit the REPL
/export Export the current conversation to a file or
clipboard
/feedback (bug) Submit feedback about Claude Code
/help Show help and available commands
/hooks Manage hook configurations for tool events
/ide Manage IDE integrations and show status
/init Initialize a new CLAUDE.md file with codebase
documentation
/install-github-app Set up Claude GitHub Actions for a repository
/login Sign in with your Anthropic account
/hooks 명령어를 통해 훅 관리 가능
PreToolUse: 도구 호출 전에 실행됩니다 (차단 가능)
PostToolUse: 도구 호출 완료 후 실행됩니다
UserPromptSubmit: 사용자가 프롬프트를 제출할 때, Claude가 처리하기 전에 실행됩니다
Notification: Claude Code가 알림을 보낼 때 실행됩니다
Stop: Claude Code가 응답을 완료할 때 실행됩니다
SubagentStop: 하위 에이전트 작업이 완료될 때 실행됩니다
PreCompact: Claude Code가 압축 작업을 실행하려고 할 때 실행됩니다
SessionStart: Claude Code가 새 세션을 시작하거나 기존 세션을 재개할 때 실행됩니다
SessionEnd: Claude Code 세션이 종료될 때 실행됩니다
> /hook
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Hook Configuration │
│ │
│ Hooks are shell commands you can register to run during Claude Code processing. Docs │
│ • Each hook event has its own input and output behavior │
│ • Multiple hooks can be registered per event, executed in parallel │
│ • Any changes to hooks outside of /hooks require a restart │
│ • Timeout: 60 seconds │
│ │
│ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ │ ⚠ CRITICAL SECURITY WARNING - USE AT YOUR OWN RISK │
│ │ Hooks execute arbitrary shell commands with YOUR full user permissions without confirmation. │
│ │ • You are SOLELY RESPONSIBLE for ensuring your hooks are safe and secure │
│ │ • Hooks can modify, delete, or access ANY files your user account can access │
│ │ • Malicious or poorly written hooks can cause irreversible data loss or system damage │
│ │ • Anthropic provides NO WARRANTY and assumes NO LIABILITY for any damages resulting from hook usage │
│ │ • Only use hooks from trusted sources to prevent data exfiltration │
│ │ • Review the hooks documentation before proceeding │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ │
│ Select hook event: │
│ ❯ 1. PreToolUse - Before tool execution │
│ 2. PostToolUse - After tool execution │
│ 3. Notification - When notifications are sent │
│ 4. UserPromptSubmit - When the user submits a prompt │
│ ↓ 5. SessionStart - When a new session is started │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Enter to acknowledge risks and continue · Esc to exit
select hook event: 목록중 하나 적절히 선택 1번을 선택하면 다음과 같이 1. + add new matcher 를 선택 하라고 나옴.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ PreToolUse - Tool Matchers │
│ │
│ Input to command is JSON of tool call arguments. │
│ Exit code 0 - stdout/stderr not shown │
│ Exit code 2 - show stderr to model and block tool call │
│ Other exit codes - show stderr to user only but continue with tool call │
│ │
│ ❯ 1. + Add new matcher… No matchers configured yet │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Enter to select · Esc to go back
1번을 선택하면 다음과 같이 PreToolUser는 matcher로 지정된 툴(여기서의 툴은 권한을 의미합니다.)이 호출되면, 특정 동작을 수행합니다.
여기서는 Bash 를 명시합니다. 만약 모든 도구를 사용허가를 한다면 * 입력하면 됩니다.
다음과 같이 실제 어떤 동작을 할지 추가하라고 나옵니다.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ PreToolUse - Matcher: bash │
│ │
│ Input to command is JSON of tool call arguments. │
│ Exit code 0 - stdout/stderr not shown │
│ Exit code 2 - show stderr to model and block tool call │
│ Other exit codes - show stderr to user only but continue with tool call │
│ │
│ ❯ 1. + Add new hook… No hooks configured yet │
│ │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Enter to select · Esc to go back
1번을 선택하면 훅의 실질적인 동작을 정의할 수 있습니다.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Add new hook │
│ │
│ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ │ ⚠ CRITICAL SECURITY WARNING │
│ │ Hooks execute arbitrary shell commands with YOUR full user permissions. By proceeding, you acknowledge: │
│ │ • You are SOLELY responsible for any commands you configure │
│ │ • Hooks can modify, delete, or access ANY files your user can access │
│ │ • Anthropic provides NO WARRANTY and assumes NO LIABILITY for damages │
│ │ • USE AT YOUR OWN RISK - Test thoroughly before production use │
│ │ • Review the hooks documentation before proceeding │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ │
│ Event: PreToolUse - Before tool execution │
│ │
│ Input to command is JSON of tool call arguments. │
│ Exit code 0 - stdout/stderr not shown │
│ Exit code 2 - show stderr to model and block tool call │
│ Other exit codes - show stderr to user only but continue with tool call │
│ │
│ Matcher: bash │
│ │
│ Command: │
│ │
│ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ │ │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ │
│ Examples: │
│ • jq -r '.tool_input.file_path | select(endswith(".go"))' | xargs -r gofmt -w │
│ • jq -r '.tool_input | \"[\" + (.description // \"No description\") + \"] \" + .command' >> \"$CLAUDE_PROJECT_DIR/.claude/bash-command-log.txt\" │
│ • /usr/local/bin/security_check.sh │
│ • python3 ~/hooks/validate_changes.py │
│ │
│ │
│ ⚠ Security Best Practices: │
│ • Use absolute paths for custom scripts (~/scripts/check.sh not check.sh) │
│ • Avoid using sudo - hooks run with your user permissions │
│ • Be cautious with patterns that match sensitive files (.env, .ssh/*, secrets.*) │
│ • Validate and sanitize input paths (reject ../ paths, check expected formats) │
│ • Avoid piping untrusted content to shells (curl ... | sh, | bash) │
│ • Use restrictive file permissions (chmod 644, not 777) │
│ • Quote all variable expansions to prevent injection: "$VAR" │
│ • Keep error checking enabled in scripts (avoid set +e) │
│ By adding this hook, you accept all responsibility for its execution and any consequences. │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Enter to confirm · Esc to cancel
여기서는 다음과 같이 정의하도록 했습니다. 해당 명령어는 실행된 명령어를 ./.claude/bash-command-log.txt에 저장합니다.
jq -r '.tool_input | \"[\" + (.description // \"No description\") + \"] \" + .command' >> \"$CLAUDE_PROJECT_DIR/.claude/bash-command-log.txt\"
훅을 정의하면 어느 경로에서 관리할지 나옵니다. 여기서는 적절히 원하는 경로를 선택하면 됩니다.
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Save hook configuration │
│ │
│ Event: PreToolUse - Before tool execution │
│ Matcher: bash │
│ Command: jq -r '"\(.tool_input.command) - \(.tool_input.description // "No description")"' >> ~/.claude/bash-command-log.txt │
│ │
│ Where should this hook be saved? │
│ │
│ ❯ 1. Project settings (local) Saved in .claude/settings.local.json │
│ 2. Project settings Checked in at .claude/settings.json hooks/register.js 243 lines1// my-mod/hooks/register.js
2// /explorer : Claude Code를 실행한 폴더를 루트로 하는 파일 탐색기 Pane
3// - 루트 밖으로는 나갈 수 없음
4// - 폴더는 열고 닫을 수 있음 (v 열림 / > 닫힘)
5// - n: 새 파일, d: 새 폴더, r: 새로고침
6// - 새 파일/폴더는 Enter를 누른 시점에 "마지막으로 선택된 경로" 기준으로 생성
7
8const PANE = 'explorer'
9const ICON_OPEN = 'v'
10const ICON_CLOSED = '>'
11const MAX_ROWS = 500
12
13// ── 상태 (모듈 변수: 코드를 저장해 핫리로드되면 초기화됨) ──
14let root = '' // 세션 시작 시점의 실행 경로 (고정)
15let error = '' // pane에 빨간 글씨로 보여줄 오류
16let selected = '' // 마지막으로 누른 행의 경로 (없으면 root가 대상)
17let selectedIsDir = true
18let creating = null // { kind: 'file' | 'dir' } (위치는 생성 시점에 계산)
19let draft = '' // 입력창에 쓰던 이름 (행을 선택해도 유지)
20const expanded = new Set() // 펼쳐진 폴더 경로
21const cache = new Map() // 폴더 경로 -> [{ name, isDir, isLink }]
22
23// ── 경로 유틸 (항상 '/' 구분자, 끝 슬래시 없음) ──
24const norm = (p) => String(p).replace(/\\/g, '/').replace(/\/+$/, '')
25const joinPath = (base, name) => norm(base) + '/' + name
26const dirOf = (p) => norm(p).slice(0, norm(p).lastIndexOf('/'))
27const baseName = (p) => norm(p).split('/').pop() || norm(p)
28const isWindows = () => /^[A-Za-z]:/.test(root)
29const errText = (err) => String(err && err.message ? err.message : err)
30
31// 글자/숫자/공백/_ - . 만 허용 (cmd에 넘기는 mkdir 인자를 안전하게 유지)
32const validName = (n) =>
33 /^[\p{L}\p{N}_. \-]+$/u.test(n) && !/^\.+$/.test(n) && n === n.trim() && !n.endsWith('.')
34
35// 루트 자신이거나 루트 아래 경로일 때만 true (Windows 대소문자 무시)
36function isInside(p) {
37 const a = norm(p).toLowerCase()
38 const r = root.toLowerCase()
39 return a === r || a.startsWith(r + '/')
40}
41
42// 생성 위치: 폴더를 선택했으면 그 폴더, 파일이면 그 파일이 있는 폴더, 없으면 루트
43function targetParent() {
44 if (!selected) return root
45 return selectedIsDir ? selected : dirOf(selected)
46}
47
48// $를 최상위 함수에 넘기는 것은 mod 검증 규칙상 허용됨
49async function loadDir($, target) {
50 if (!isInside(target)) {
51 error = 'outside root: ' + target
52 return
53 }
54 try {
55 const raw = await $.fs.list(target) // [{ name, kind, size, isLink }]
56 const rows = raw
57 .filter((it) => it.name && it.name !== '.' && it.name !== '..')
58 .map((it) => ({
59 name: it.name,
60 isLink: !!it.isLink,
61 isDir: /dir|folder/i.test(String(it.kind)) && !it.isLink, // 링크는 펼치지 않음
62 }))
63 rows.sort((a, b) => (a.isDir === b.isDir ? a.name.localeCompare(b.name) : a.isDir ? -1 : 1))
64 cache.set(target, rows)
65 error = ''
66 } catch (err) {
67 error = errText(err)
68 }
69}
70
71// 실패하면 오류 문자열, 성공하면 '' 를 돌려줌
72async function createEntry($, kind, parent, rawName) {
73 const name = String(rawName).trim()
74 if (!validName(name)) return 'invalid name'
75 if (!isInside(parent)) return 'outside root'
76 const full = joinPath(parent, name)
77 try {
78 if (await $.fs.exists(full)) return 'already exists: ' + name
79 if (kind === 'file') {
80 await $.fs.write(full, '')
81 } else {
82 // $.process.run은 셸을 쓰지 않으므로 Windows에서는 cmd /c 로 mkdir을 호출
83 const native = isWindows() ? full.replace(/\//g, '\\') : full
84 const argv = isWindows() ? ['cmd', '/c', 'mkdir', native] : ['mkdir', native]
85 const r = await $.process.run(argv)
86 if (r.exitCode !== 0) return (r.stderr || 'mkdir failed').trim()
87 }
88 } catch (err) {
89 return errText(err)
90 }
91 expanded.add(parent) // 만든 항목이 보이도록 대상 폴더를 펼침
92 await loadDir($, parent)
93 return ''
94}
95
96// 루트를 맨 위 행으로 두고, 펼쳐진 폴더만 아래로 풀어서 한 줄 목록으로 만든다
97function flatten() {
98 const out = [{ name: baseName(root), isDir: true, full: root, depth: 0 }]
99 const walk = (path, depth) => {
100 for (const it of cache.get(path) ?? []) {
101 if (out.length >= MAX_ROWS) return
102 const full = joinPath(path, it.name)
103 out.push({ ...it, full, depth })
104 if (it.isDir && expanded.has(full)) walk(full, depth + 1)
105 }
106 }
107 if (expanded.has(root)) walk(root, 1)
108 return out
109}
110
111export function register(on) {
112 // 세션 시작: 루트 고정 + /explorer 명령 등록
113 on('session.start', async ($, e, next) => {
114 if (!root) root = norm(await $.session.cwd())
115 await $.command.register({ name: 'explorer', description: 'Open the file explorer pane' })
116 return next(e)
117 })
118
119 // /explorer 실행: 루트를 펼친 상태로 읽고 Pane을 연다
120 on('command.run', { command: 'explorer' }, async ($) => {
121 if (!root) root = norm(await $.session.cwd())
122 if (!cache.has(root)) {
123 expanded.add(root)
124 await loadDir($, root)
125 }
126 await $.ui.open({ id: PANE, title: 'Explorer', focus: true, closeOnEscape: true })
127 return {}
128 })
129
130 // Pane 그리기
131 on('ui.render', { component: 'Pane' }, async ($, e, next) => {
132 if (e.requestId !== PANE) return next(e)
133 const { Box, Text, Button, Input } = $.ui.resolve(e)
134 const redraw = () => $.ui.invalidate('ui.render')
135
136 const toggle = async (full) => {
137 if (expanded.has(full)) {
138 expanded.delete(full)
139 } else {
140 expanded.add(full)
141 if (!cache.has(full)) await loadDir($, full)
142 }
143 redraw()
144 }
145
146 const refresh = async () => {
147 cache.clear()
148 await loadDir($, root)
149 for (const p of [...expanded]) await loadDir($, p)
150 redraw()
151 }
152
153 const startCreate = (kind) => {
154 creating = { kind }
155 draft = ''
156 error = ''
157 redraw()
158 }
159
160 // 파일/폴더 행: 누르면 선택되고, 폴더면 토글
161 const rows = flatten().map((it, i) => {
162 const mark = it.isDir ? (expanded.has(it.full) ? ICON_OPEN : ICON_CLOSED) : ' '
163 const tail = it.full === selected ? ' <' : it.isLink ? ' @' : ''
164 return Button({
165 key: 'n-' + i,
166 plain: true,
167 label: ' '.repeat(it.depth) + mark + ' ' + it.name + tail,
168 onPress: () => {
169 selected = it.full
170 selectedIsDir = it.isDir
171 if (it.isDir) return toggle(it.full)
172 redraw()
173 },
174 })
175 })
176
177 // 새 파일/폴더 입력창. 위치는 매 렌더마다, 그리고 Enter 시점에 다시 계산
178 const parent = targetParent()
179 const form = creating
180 ? [
181 Text({
182 dimColor: true,
183 children: [
184 (creating.kind === 'file' ? 'new file in ' : 'new folder in ') +
185 (parent.slice(root.length + 1) || '.'),
186 ],
187 }),
188 Input({
189 key: 'create-name',
190 label: creating.kind === 'file' ? 'File' : 'Folder',
191 placeholder: 'name, then Enter',
192 value: draft,
193 submitLabel: 'create',
194 autoFocus: true,
195 onInput: (value) => {
196 draft = value
197 },
198 onSubmit: async (value) => {
199 // Enter를 누른 시점에 선택된 경로를 다시 읽는다
200 const target = targetParent()
201 const msg = await createEntry($, creating.kind, target, value)
202 if (msg) {
203 error = msg
204 } else {
205 creating = null
206 draft = ''
207 error = ''
208 }
209 redraw()
210 },
211 }),
212 Button({
213 key: 'create-cancel',
214 label: 'cancel',
215 plain: true,
216 onPress: () => {
217 creating = null
218 draft = ''
219 redraw()
220 },
221 }),
222 ]
223 : []
224
225 return Box({
226 flexDirection: 'column',
227 children: [
228 Box({
229 flexDirection: 'row',
230 columnGap: 3,
231 children: [
232 Button({ key: 'new-file', label: 'new file', hotkey: 'n', plain: true, onPress: () => startCreate('file') }),
233 Button({ key: 'new-dir', label: 'new folder', hotkey: 'd', plain: true, onPress: () => startCreate('dir') }),
234 Button({ key: 'refresh', label: 'refresh', hotkey: 'r', plain: true, onPress: refresh }),
235 ],
236 }),
237 ...form,
238 ...(error ? [Text({ color: 'red', wrap: 'wrap', children: [error] })] : []),
239 ...rows,
240 ],
241 })
242 })
243}