@erclx/canon 5.3.0 → 5.5.0

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 (56) hide show
  1. package/README.md +1 -0
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/bash-cli-script/REQUIREMENT.md +10 -9
  4. package/claude/skills/bash-cli-script/SKILL.md +10 -7
  5. package/claude/skills/bash-cli-script/references/template.md +4 -28
  6. package/claude/skills/plan-groundwork/SKILL.md +1 -1
  7. package/claude/skills/read-frames/REQUIREMENT.md +1 -0
  8. package/claude/skills/read-frames/SKILL.md +1 -0
  9. package/claude/skills/teach-workspace/SKILL.md +4 -4
  10. package/claude/skills/ux-measure/REQUIREMENT.md +2 -1
  11. package/claude/skills/ux-measure/SKILL.md +3 -1
  12. package/claude/skills/ux-walkthrough/REQUIREMENT.md +3 -0
  13. package/claude/skills/ux-walkthrough/SKILL.md +1 -0
  14. package/docs/agents/architecture-record.md +5 -3
  15. package/docs/agents/context-audit-checks.md +1 -1
  16. package/docs/agents/context-audit.md +3 -3
  17. package/docs/agents/index.md +2 -2
  18. package/docs/agents/markdown-audit.md +1 -1
  19. package/docs/agents/pr-evidence.md +17 -0
  20. package/docs/agents/teach-authoring.md +3 -3
  21. package/docs/agents/teach.md +11 -5
  22. package/docs/workflow/skill-map.md +11 -10
  23. package/governance/rules/lang/120-bash.md +4 -5
  24. package/package.json +1 -1
  25. package/src/audits/catalog.ts +7 -1
  26. package/src/claude/cases/authoring.ts +0 -5
  27. package/src/commands/context.ts +25 -2
  28. package/src/commands/pr.ts +63 -41
  29. package/src/commands/serve.ts +2 -92
  30. package/src/commands/teach.ts +142 -17
  31. package/src/context/architecture.ts +82 -5
  32. package/src/context/gate.ts +10 -3
  33. package/src/gate/measures.ts +9 -4
  34. package/src/gate/stages.ts +1 -1
  35. package/src/serve/report.ts +110 -0
  36. package/src/teach/nav.ts +12 -1
  37. package/standards/architecture.md +14 -3
  38. package/standards/context.md +1 -1
  39. package/standards/decisions.md +6 -1
  40. package/standards/groundwork.md +1 -0
  41. package/standards/requirements.md +1 -1
  42. package/standards/rule.md +1 -0
  43. package/standards/skill.md +1 -1
  44. package/standards/standard.md +1 -0
  45. package/tooling/go/configs/scripts/verify.sh +47 -0
  46. package/tooling/go/reference.md +1 -1
  47. package/tooling/php/configs/scripts/verify.sh +47 -0
  48. package/tooling/php/reference.md +1 -1
  49. package/tooling/python/configs/scripts/verify.sh +47 -0
  50. package/tooling/python/reference.md +1 -1
  51. package/tooling/web/configs/scripts/verify.sh +47 -0
  52. package/tooling/web/reference.md +1 -1
  53. package/claude/skills/bash-script/REQUIREMENT.md +0 -36
  54. package/claude/skills/bash-script/SKILL.md +0 -100
  55. package/claude/skills/bash-script/references/patterns.md +0 -250
  56. package/claude/skills/bash-script/references/prompts.md +0 -100
@@ -51,6 +51,50 @@ run_base_phase() {
51
51
  log_info "$pass_msg"
52
52
  }
53
53
 
54
+ check_markdown_bans() {
55
+ if ! command -v canon >/dev/null 2>&1; then
56
+ log_info "Skipped: no canon binary on PATH. Install with \`bun install --global @erclx/canon\`."
57
+ return 0
58
+ fi
59
+
60
+ local files=()
61
+ while IFS= read -r file; do
62
+ files+=("$file")
63
+ done < <(git ls-files --cached --others --exclude-standard -- '*.md' ':(exclude)CHANGELOG.md' ':(exclude)**/CHANGELOG.md')
64
+
65
+ if [ ${#files[@]} -eq 0 ]; then
66
+ log_info "No markdown file to check outside the exclusion set."
67
+ return 0
68
+ fi
69
+
70
+ local output code
71
+ if output=$(canon markdown audit "${files[@]}" 2>&1); then
72
+ code=0
73
+ else
74
+ code=$?
75
+ fi
76
+
77
+ case "$code" in
78
+ 0)
79
+ log_info "No banned character"
80
+ ;;
81
+ 1)
82
+ log_info "Skipped: canon markdown audit refused and measured nothing."
83
+ ;;
84
+ 2)
85
+ echo "$output" | pipe_output
86
+ log_error "Markdown prose carries a banned character, or a relative link resolves to nothing on disk"
87
+ ;;
88
+ 3)
89
+ echo "$output" | pipe_output
90
+ log_error "The markdown audit shipped an empty ban set, so the corpus was walked and nothing was looked for"
91
+ ;;
92
+ *)
93
+ log_error "canon markdown audit exited $code, which is neither a pass nor a finding"
94
+ ;;
95
+ esac
96
+ }
97
+
54
98
  run_check() {
55
99
  local cmd=$1
56
100
  local err_msg=$2
@@ -85,6 +129,9 @@ main() {
85
129
  run_check "bun run test:run" "Unit tests failed"
86
130
  log_info "Unit tests passed"
87
131
 
132
+ log_step "Markdown bans"
133
+ check_markdown_bans
134
+
88
135
  if [ "$NESTED" = false ]; then
89
136
  echo -e "${GREY}└${NC}\n"
90
137
  echo -e "${GREEN}✓ Verification passed${NC}"
@@ -27,7 +27,7 @@ The python stack also ships `tests/test_smoke.py` as a copy-once seed. `pytest`
27
27
  - `pytest.ini`: tests live under `tests/`, source under `src/`.
28
28
  - `.coveragerc`: branch coverage on `src/`, html report under `.coverage_cache/html`.
29
29
  - `.python-version`: pinned to `3.14` to match `uv init` defaults.
30
- - `scripts/verify.sh`: overrides base verify to add Typecheck (`mypy`), Lint (`ruff check && ruff format --check`), and Tests (`pytest -v`) phases.
30
+ - `scripts/verify.sh`: overrides base verify to add Typecheck (`mypy`), Lint (`ruff check && ruff format --check`), and Tests (`pytest -v`) phases, then the same markdown bans phase base ships.
31
31
 
32
32
  ## Hybrid project shape
33
33
 
@@ -50,6 +50,50 @@ run_base_phase() {
50
50
  log_info "$pass_msg"
51
51
  }
52
52
 
53
+ check_markdown_bans() {
54
+ if ! command -v canon >/dev/null 2>&1; then
55
+ log_info "Skipped: no canon binary on PATH. Install with \`bun install --global @erclx/canon\`."
56
+ return 0
57
+ fi
58
+
59
+ local files=()
60
+ while IFS= read -r file; do
61
+ files+=("$file")
62
+ done < <(git ls-files --cached --others --exclude-standard -- '*.md' ':(exclude)CHANGELOG.md' ':(exclude)**/CHANGELOG.md')
63
+
64
+ if [ ${#files[@]} -eq 0 ]; then
65
+ log_info "No markdown file to check outside the exclusion set."
66
+ return 0
67
+ fi
68
+
69
+ local output code
70
+ if output=$(canon markdown audit "${files[@]}" 2>&1); then
71
+ code=0
72
+ else
73
+ code=$?
74
+ fi
75
+
76
+ case "$code" in
77
+ 0)
78
+ log_info "No banned character"
79
+ ;;
80
+ 1)
81
+ log_info "Skipped: canon markdown audit refused and measured nothing."
82
+ ;;
83
+ 2)
84
+ echo "$output" | pipe_output
85
+ log_error "Markdown prose carries a banned character, or a relative link resolves to nothing on disk"
86
+ ;;
87
+ 3)
88
+ echo "$output" | pipe_output
89
+ log_error "The markdown audit shipped an empty ban set, so the corpus was walked and nothing was looked for"
90
+ ;;
91
+ *)
92
+ log_error "canon markdown audit exited $code, which is neither a pass nor a finding"
93
+ ;;
94
+ esac
95
+ }
96
+
53
97
  run_check() {
54
98
  local cmd=$1
55
99
  local err_msg=$2
@@ -88,6 +132,9 @@ main() {
88
132
  run_check "bun run build" "Build failed"
89
133
  log_info "Build passed"
90
134
 
135
+ log_step "Markdown bans"
136
+ check_markdown_bans
137
+
91
138
  # Runs whenever the stack shipped a card exclusion check, which keeps the
92
139
  # stage stack-agnostic here and the check itself in the stack that owns the
93
140
  # card route's shape. It reads the build above, so it sits after it.
@@ -128,6 +128,6 @@ The sweep under `screenshots/` is ignored again, and only a flagged case's `evid
128
128
 
129
129
  ## Verify script
130
130
 
131
- The web layer's `scripts/verify.sh` replaces the base version. Order: typecheck, lint, format, spelling, shell, unit tests, build, then the card exclusion check where one is installed. Stack adapters may override if their typecheck or build differs.
131
+ The web layer's `scripts/verify.sh` replaces the base version. Order: typecheck, lint, format, spelling, shell, unit tests, build, markdown bans, then the card exclusion check where one is installed. Stack adapters may override if their typecheck or build differs.
132
132
 
133
133
  The card stage is conditional on `scripts/check-card-exclusion.sh` existing rather than on the stack, so a project with no card route runs one less stage and needs no override to skip it. A stack that ships a card route ships that script, and the check reads the build the stage before it produced.
@@ -1,36 +0,0 @@
1
- ---
2
- name: bash-script
3
- description: Scope boundary for the interactive script house style and the stream discipline underneath it
4
- ---
5
-
6
- # Bash script requirement
7
-
8
- ## Gap
9
-
10
- Without this skill, a session asked for a human-facing shell tool writes one whose frame decoration, log lines, and prompts all land on stdout, so the script cannot be piped and any data it emits arrives mixed with its own presentation.
11
-
12
- Three more failures share a cause, which is that an interactive script is written as though a person is always watching. A prompt blocks forever when stdin is not a terminal, which is the shape every CI job and every agent run has. A cancellation path prints both a cancel line and an error, or an exit trap prints a closing frame the success path already printed, so the same run reports twice. And each generated script picks its own icons, casing, and frame characters, so a folder of them reads as several unrelated tools.
13
-
14
- The visual conventions this skill fixes are otherwise arbitrary. Their value is that they are the same across every script, which is a property no individual script can establish for itself.
15
-
16
- ## Must
17
-
18
- - Keep every frame, log line, and prompt on stderr, and reserve stdout for data. `--help` is the one exception, since its consumer is a person rather than a pipe.
19
- - Guard every prompt on an attached terminal and refuse inside the frame rather than blocking on a read that will never return
20
- - Give the timeline one owner per exit path, so success, cancellation, and error each close it exactly once
21
- - Copy the shared templates rather than restating them, keeping only the colors and functions the script uses
22
-
23
- ## Must not
24
-
25
- - Assert a convention the generated script cannot be checked against by reading it. A rule that cannot be verified from the output is style with no gate behind it.
26
- - Carry a second copy of the timeline, logging, or prompt implementations. The bundled reference is the single copy, and a body that restates it drifts from it.
27
-
28
- ## Guards
29
-
30
- - A request for a script with no human at the terminal stops and routes to `bash-cli-script` rather than generating a timeline nothing will render
31
-
32
- ## Out of scope
33
-
34
- - Non-interactive automation, CI, and agent-run scripts: `bash-cli-script`, which keeps the error handling and the stdout contract and drops the timeline, the icons, and the prompts
35
- - GitHub Actions workflow files: `ci-workflow`
36
- - What the generated script does. This skill fixes the shape of the output and the stream it goes to, and the commands belong to the request.
@@ -1,100 +0,0 @@
1
- ---
2
- name: bash-script
3
- description: Generates production Bash scripts with a visual timeline UI, state-based interactive prompts, and strict error handling. Use when asked for "a human-facing shell tool", an interactive bash script, or a DevOps tool with framed terminal output. Do NOT use for a non-interactive automation, CI, or pipeline script, that is `bash-cli-script`.
4
- ---
5
-
6
- # Bash script
7
-
8
- Generate production-ready Bash scripts for DevOps and CLI workflows. Enforce strict formatting with a visual timeline UI and state-based interactivity.
9
-
10
- Load `${CLAUDE_SKILL_DIR}/references/patterns.md` for the timeline lifecycle, logging, help-screen, and full-script code templates, and `${CLAUDE_SKILL_DIR}/references/prompts.md` for the `ask()` and `select_option()` templates when the script prompts. `${CLAUDE_SKILL_DIR}` expands to this skill's own directory, so the path resolves from any project. Copy those definitions verbatim, keeping only the colors and functions the script actually uses.
11
-
12
- ## Guards
13
-
14
- - A request for a script with no human at the terminal stops and routes to `bash-cli-script`. CI jobs, cron entries, agent-run tasks, and any script whose output is consumed by a pipe render no timeline, so generating one costs the frame and returns nothing.
15
-
16
- ## Script setup
17
-
18
- - Start with `#!/usr/bin/env bash`, `set -e`, and `set -o pipefail`.
19
- - Bash 4+ features (namerefs, associative arrays) are allowed.
20
- - Implement a visual help screen via `show_help` if the script accepts arguments.
21
- - Do not rely on unset variables. Use `${VAR:-default}`.
22
-
23
- ## Visual timeline
24
-
25
- - Maintain a vertical timeline (`│`) from `┌` to `└` throughout all output.
26
- - Write all frame output (`┌`, `│`, `├`, `└`, log lines, prompts) to stderr via `>&2`. Write data (JSON, lists, piped values) to stdout. `--help` is the exception and prints to stdout.
27
- - Open the timeline once at the start of `main()` via `open_timeline "Title"`, before any logic, prompts, or checks.
28
- - Close the timeline via `trap close_timeline EXIT`, registered immediately after `open_timeline`.
29
- - On success, disable with `trap - EXIT`, then print `└\n` and the success message manually.
30
- - On cancellation and error, never print `└` manually. The trap owns those exits.
31
- - Use state transitions for interactive prompts: `◆` active, `◇` inactive.
32
- - Do not add diamonds (`◆` or `◇`) to non-interactive log functions.
33
- - On cancellation, show `◇ ... Cancelled`, exit 1, and make no `log_error` call. Both `ask()` and `select_option()` handle escape cancellation identically.
34
- - Guard interactive prompts against non-TTY stdin with `[ -t 0 ]` and render a framed `log_error` when a TTY is required but absent. Never let `read` block silently on piped input.
35
-
36
- ## Code style
37
-
38
- - Decompose by responsibility: each function does one thing, `main()` orchestrates only.
39
- - Name functions verb-first: `validate_input`, `deploy_service`, `install_dependencies`.
40
- - Do not use global variables except exports from `ask()`.
41
- - Do not define unused color variables.
42
- - Comment only a fact the reader cannot recover from the code, and follow the code-comment rule in `.claude/rules/` when the project installs it.
43
- - Use sentence case for section headers and log messages. Proper nouns and product names retain their casing.
44
- - Quote variables inside parameter expansions: `"${file#"$dir"/}"` not `"${file#$dir/}"`.
45
- - Quote variables in test brackets: `[ "$i" -eq "$cur" ]` not `[ $i -eq $cur ]`.
46
- - Guard commands that return non-zero on valid empty results: `grep ... || true`, `diff ... || true`.
47
-
48
- ## Output hygiene
49
-
50
- - Show external tool output by default (git, npm, gh).
51
- - Include context in error messages: `log_error "npm install failed: check package.json"`.
52
- - Do not echo command names before running them. Output speaks for itself.
53
- - Do not log "Starting..." and "Finished..." around every action.
54
- - Do not log intermediate variable assignments.
55
- - Use `log_add` for item writes (files created, entries added, keys written).
56
- - Use `log_info` for status confirmations only ("up to date", "check passed").
57
- - Use `log_warn` for drift, skipped states, or recoverable issues.
58
- - Never use `log_info` for file or entry writes.
59
-
60
- ## Icon usage
61
-
62
- Interactive prompts only:
63
-
64
- - `◆` (green): active user input required
65
- - `◇` (grey): completed input, transition via `\r\033[K` to rewrite the `◆` line in place
66
- - `❯` (green): selected option in a menu
67
- - plain text (grey): unselected option in a menu
68
-
69
- Non-interactive logs:
70
-
71
- - `├` section branch (`log_step`)
72
- - `✓` (green): success (`log_info`)
73
- - `!` (yellow): warning (`log_warn`)
74
- - `✗` (red): error (`log_error`)
75
- - `+` (green): add item (`log_add`)
76
- - `-` (red): remove item (`log_rem`)
77
-
78
- ## Validation
79
-
80
- Before responding, verify:
81
-
82
- - File starts with `#!/usr/bin/env bash`, `set -e`, `set -o pipefail` and uses exactly 2 spaces for indentation.
83
- - Timeline opens via `open_timeline "Title"`, which writes `┌` and `│ Title` to stderr.
84
- - Timeline closes via `trap close_timeline EXIT` registered immediately after `open_timeline`. Success paths use `trap - EXIT` then manual `└\n` (to stderr) then the success message. Cancellation and error paths never print `└` manually.
85
- - `open_timeline` and `close_timeline` are defined and write to stderr via `>&2`.
86
- - All frame output (`│`, `├`, `└`, log lines, interactive prompts) writes to stderr via `>&2`. Stdout carries data only. `--help` is the exception.
87
- - Interactive prompts (`ask`, `select_option`) guard with `[ -t 0 ]` and call `log_error` if stdin is not a TTY.
88
- - The timeline (`│`) appears in all log functions, and interactive prompts use `◆` → `◇` transitions.
89
- - `ask()` uses `\r\033[K` to rewrite the `◆` line in place, with no `\033[1A` cursor-up sequences.
90
- - `ask()` drains trailing escape bytes with `read -rsn2 -t 0.001 _ || true` before cancelling.
91
- - All log messages use sentence case (proper nouns and product names exempt).
92
- - Only defined color variables are used in the script.
93
- - Cancellation shows a single `◇ ... Cancelled` line without a subsequent `log_error`.
94
- - Escape key in `ask()` triggers `◇ ... Cancelled` and exits, consistent with `select_option()`.
95
- - Functions follow single responsibility: each does one thing, `main()` delegates to helpers.
96
- - Logging is concise: no "Starting.../Finished..." bloat, no intermediate variable logging.
97
- - `log_add` is used for all file, entry, and key writes.
98
- - `log_info` is used for status confirmations only, not writes.
99
- - Every section header uses `log_step`, including the first one after the title block.
100
- - File ends with exactly one empty line.
@@ -1,250 +0,0 @@
1
- # Bash script patterns
2
-
3
- Code templates for the `bash-script` skill. Copy verbatim, keeping only the colors and functions the script uses.
4
-
5
- ## Timeline structure
6
-
7
- ```plaintext
8
- ┌ # Start boundary (alone on its own line)
9
- │ Title # Script or context title (immediately after ┌)
10
- │ # Persistent vertical line (grey)
11
- ├ Section Branch # Section headers (no diamond)
12
- │ ✓ Log message # Info/success logs
13
- │ ! Warning # Warning logs
14
- │ ✗ Error # Error logs
15
- └ # End boundary
16
- ```
17
-
18
- ## Color palette
19
-
20
- Define only the colors the script uses from this set:
21
-
22
- ```bash
23
- GREEN='\033[0;32m' # Success/Active
24
- RED='\033[0;31m' # Error/Delete
25
- YELLOW='\033[0;33m' # Warning
26
- WHITE='\033[1;37m' # Active text
27
- GREY='\033[0;90m' # Timeline/Inactive
28
- CYAN='\033[0;36m' # Optional accent
29
- MAGENTA='\033[0;35m' # Optional highlight
30
- NC='\033[0m' # Reset
31
- ```
32
-
33
- ## Timeline lifecycle
34
-
35
- Define `open_timeline` and `close_timeline`. Call `open_timeline "Title"` at the start of `main()`, then register `close_timeline` as the EXIT trap. The trap guarantees `└` prints on every exit path: normal completion, `exit 1` from cancellation, or unexpected errors. Do not print `└` manually anywhere else.
36
-
37
- ```bash
38
- open_timeline() {
39
- echo -e "${GREY}┌${NC}" >&2
40
- [ -n "${1:-}" ] && echo -e "${GREY}│${NC} ${WHITE}$1${NC}" >&2
41
- }
42
-
43
- close_timeline() {
44
- echo -e "${GREY}└${NC}" >&2
45
- }
46
- ```
47
-
48
- Register the trap inside `main()` right after opening:
49
-
50
- ```bash
51
- open_timeline "Script title"
52
- trap close_timeline EXIT
53
- ```
54
-
55
- ## Logging
56
-
57
- All log functions include the `│` prefix and write to stderr.
58
-
59
- ```bash
60
- log_info() { echo -e "${GREY}│${NC} ${GREEN}✓${NC} $1" >&2; }
61
- log_warn() { echo -e "${GREY}│${NC} ${YELLOW}!${NC} $1" >&2; }
62
- log_error() { echo -e "${GREY}│${NC} ${RED}✗${NC} $1" >&2; exit 1; }
63
- log_step() { echo -e "${GREY}│${NC}\n${GREY}├${NC} ${WHITE}$1${NC}" >&2; }
64
- log_add() { echo -e "${GREY}│${NC} ${GREEN}+${NC} $1" >&2; }
65
- log_rem() { echo -e "${GREY}│${NC} ${RED}-${NC} $1" >&2; }
66
- ```
67
-
68
- ## Section headers
69
-
70
- Use `log_step` for every section header, including the first. It emits a leading blank `│` line to separate the banner from the first section and to give breathing room between subsequent sections:
71
-
72
- ```bash
73
- open_timeline "Script title"
74
- trap close_timeline EXIT
75
-
76
- log_step "Deploy"
77
- log_step "Verify"
78
- ```
79
-
80
- Renders as:
81
-
82
- ```plaintext
83
- ┌
84
- │ Script title
85
- │
86
- ├ Deploy
87
- ...
88
- │
89
- ├ Verify
90
- ...
91
- └
92
- ```
93
-
94
- ## Help system
95
-
96
- ```bash
97
- show_help() {
98
- echo -e "${GREY}┌${NC}"
99
- echo -e "${GREY}├${NC} ${WHITE}Usage:${NC} ./script.sh [options]"
100
- echo -e "${GREY}│${NC}"
101
- echo -e "${GREY}│${NC} ${WHITE}Options:${NC}"
102
- echo -e "${GREY}│${NC} -h, --help ${GREY}# Show this help message${NC}"
103
- echo -e "${GREY}│${NC} [flag] ${GREY}# [Description]${NC}"
104
- echo -e "${GREY}└${NC}"
105
- exit 0
106
- }
107
- ```
108
-
109
- ## Error handling helper
110
-
111
- ```bash
112
- run_check() {
113
- local cmd=$1
114
- local err_msg=$2
115
- if ! eval "$cmd"; then
116
- log_error "$err_msg"
117
- fi
118
- }
119
- ```
120
-
121
- ## Full script structure
122
-
123
- ```bash
124
- #!/usr/bin/env bash
125
- set -e
126
- set -o pipefail
127
-
128
- [Color definitions - only used colors]
129
-
130
- [Function definitions - only needed functions]
131
-
132
- open_timeline() {
133
- echo -e "${GREY}┌${NC}" >&2
134
- [ -n "${1:-}" ] && echo -e "${GREY}│${NC} ${WHITE}$1${NC}" >&2
135
- }
136
-
137
- close_timeline() {
138
- echo -e "${GREY}└${NC}" >&2
139
- }
140
-
141
- check_dependencies() {
142
- [Verify required tools installed]
143
- }
144
-
145
- main() {
146
- check_dependencies
147
-
148
- open_timeline "Script title"
149
- trap close_timeline EXIT
150
-
151
- log_step "First section"
152
- [Script logic with timeline maintained]
153
-
154
- trap - EXIT
155
- echo -e "${GREY}└${NC}\n" >&2
156
- echo -e "${GREEN}✓ Final success message${NC}"
157
- }
158
-
159
- main "$@"
160
- ```
161
-
162
- ## Worked example
163
-
164
- ```bash
165
- #!/usr/bin/env bash
166
- set -e
167
- set -o pipefail
168
-
169
- GREEN='\033[0;32m'
170
- RED='\033[0;31m'
171
- YELLOW='\033[0;33m'
172
- WHITE='\033[1;37m'
173
- GREY='\033[0;90m'
174
- NC='\033[0m'
175
-
176
- log_info() { echo -e "${GREY}│${NC} ${GREEN}✓${NC} $1" >&2; }
177
- log_error() { echo -e "${GREY}│${NC} ${RED}✗${NC} $1" >&2; exit 1; }
178
- log_step() { echo -e "${GREY}│${NC}\n${GREY}├${NC} ${WHITE}$1${NC}" >&2; }
179
- log_add() { echo -e "${GREY}│${NC} ${GREEN}+${NC} $1" >&2; }
180
-
181
- open_timeline() {
182
- echo -e "${GREY}┌${NC}" >&2
183
- [ -n "${1:-}" ] && echo -e "${GREY}│${NC} ${WHITE}$1${NC}" >&2
184
- }
185
-
186
- close_timeline() {
187
- echo -e "${GREY}└${NC}" >&2
188
- }
189
-
190
- ask() {
191
- local prompt_text=$1
192
- local var_name=$2
193
- local default_val=$3
194
- local input=""
195
- local char
196
- local display_default=""
197
- if [ -n "$default_val" ]; then
198
- display_default=" (${default_val})"
199
- fi
200
- if [ ! -t 0 ]; then
201
- log_error "${prompt_text} requires a TTY"
202
- fi
203
- echo -e "${GREY}│${NC}" >&2
204
- echo -ne "${GREEN}◆${NC} ${prompt_text}${display_default} " >&2
205
- while IFS= read -r -s -n1 char; do
206
- if [[ $char == $'\x1b' ]]; then
207
- read -rsn2 -t 0.001 _ || true
208
- echo -ne "\r\033[K" >&2
209
- echo -e "${GREY}◇${NC} ${prompt_text} ${RED}Cancelled${NC}" >&2
210
- exit 1
211
- elif [[ $char == $'\x7f' || $char == $'\x08' ]]; then
212
- if [ -n "$input" ]; then
213
- input="${input%?}"
214
- echo -ne "\b \b" >&2
215
- fi
216
- elif [[ -z "$char" ]]; then
217
- break
218
- else
219
- input+="$char"
220
- echo -n "$char" >&2
221
- fi
222
- done
223
- [ -z "$input" ] && input="$default_val"
224
- echo -ne "\r\033[K" >&2
225
- echo -e "${GREY}◇${NC} ${prompt_text} ${WHITE}${input}${NC}" >&2
226
- export "$var_name"="$input"
227
- }
228
-
229
- check_dependencies() {
230
- command -v npm >/dev/null 2>&1 || log_error "npm not installed"
231
- }
232
-
233
- main() {
234
- check_dependencies
235
-
236
- open_timeline "Project Setup"
237
- trap close_timeline EXIT
238
-
239
- ask "Project name?" "PROJECT_NAME" "my-app"
240
-
241
- log_step "Installing dependencies"
242
- npm install vite
243
- log_add "vite@latest"
244
-
245
- log_info "Setup complete"
246
- echo -e "\n${GREEN}✓ Project created successfully${NC}"
247
- }
248
-
249
- main "$@"
250
- ```
@@ -1,100 +0,0 @@
1
- # Bash script prompts
2
-
3
- Interactive prompt templates for the `bash-script` skill, read only when the script prompts. Copy verbatim, alongside the colors and logging functions from `patterns.md`.
4
-
5
- Both prompts transition `◆` to `◇`, guard against non-TTY stdin with `[ -t 0 ]`, and write UI to stderr.
6
-
7
- ```bash
8
- ask() {
9
- local prompt_text=$1
10
- local var_name=$2
11
- local default_val=$3
12
- local input=""
13
- local char
14
- local display_default=""
15
- if [ -n "$default_val" ]; then
16
- display_default=" (${default_val})"
17
- fi
18
- if [ ! -t 0 ]; then
19
- log_error "${prompt_text} requires a TTY"
20
- fi
21
- echo -e "${GREY}│${NC}" >&2
22
- echo -ne "${GREEN}◆${NC} ${prompt_text}${display_default} " >&2
23
- while IFS= read -r -s -n1 char; do
24
- if [[ $char == $'\x1b' ]]; then
25
- read -rsn2 -t 0.001 _ || true
26
- echo -ne "\r\033[K" >&2
27
- echo -e "${GREY}◇${NC} ${prompt_text} ${RED}Cancelled${NC}" >&2
28
- exit 1
29
- elif [[ $char == $'\x7f' || $char == $'\x08' ]]; then
30
- if [ -n "$input" ]; then
31
- input="${input%?}"
32
- echo -ne "\b \b" >&2
33
- fi
34
- elif [[ -z "$char" ]]; then
35
- break
36
- else
37
- input+="$char"
38
- echo -n "$char" >&2
39
- fi
40
- done
41
- [ -z "$input" ] && input="$default_val"
42
- echo -ne "\r\033[K" >&2
43
- echo -e "${GREY}◇${NC} ${prompt_text} ${WHITE}${input}${NC}" >&2
44
- export "$var_name"="$input"
45
- }
46
- ```
47
-
48
- ```bash
49
- select_option() {
50
- local prompt_text=$1
51
- shift
52
- local options=("$@")
53
- local cur=0
54
- local count=${#options[@]}
55
-
56
- if [ ! -t 0 ]; then
57
- log_error "${prompt_text} requires a TTY"
58
- fi
59
-
60
- echo -ne "${GREY}│${NC}\n${GREEN}◆${NC} ${prompt_text}\n" >&2
61
-
62
- while true; do
63
- for i in "${!options[@]}"; do
64
- if [ "$i" -eq "$cur" ]; then
65
- echo -e "${GREY}│${NC} ${GREEN}❯ ${options[$i]}${NC}" >&2
66
- else
67
- echo -e "${GREY}│${NC} ${GREY}${options[$i]}${NC}" >&2
68
- fi
69
- done
70
-
71
- read -rsn1 key
72
- case "$key" in
73
- $'\x1b')
74
- if read -rsn2 -t 0.001 key_seq; then
75
- if [[ "$key_seq" == "[A" ]]; then cur=$(( (cur - 1 + count) % count )); fi
76
- if [[ "$key_seq" == "[B" ]]; then cur=$(( (cur + 1) % count )); fi
77
- else
78
- echo -en "\033[$((count + 1))A\033[J" >&2
79
- echo -e "\033[1A${GREY}│${NC}\n${GREY}◇${NC} ${prompt_text} ${RED}Cancelled${NC}" >&2
80
- exit 1
81
- fi
82
- ;;
83
- "k") cur=$(( (cur - 1 + count) % count ));;
84
- "j") cur=$(( (cur + 1) % count ));;
85
- "q")
86
- echo -en "\033[$((count + 1))A\033[J" >&2
87
- echo -e "\033[1A${GREY}│${NC}\n${GREY}◇${NC} ${prompt_text} ${RED}Cancelled${NC}" >&2
88
- exit 1
89
- ;;
90
- "") break ;;
91
- esac
92
-
93
- echo -en "\033[${count}A" >&2
94
- done
95
-
96
- echo -en "\033[$((count + 1))A\033[J" >&2
97
- echo -e "\033[1A${GREY}│${NC}\n${GREY}◇${NC} ${prompt_text} ${WHITE}${options[$cur]}${NC}" >&2
98
- SELECTED_OPTION="${options[$cur]}"
99
- }
100
- ```