@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.
- package/README.md +1 -0
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/bash-cli-script/REQUIREMENT.md +10 -9
- package/claude/skills/bash-cli-script/SKILL.md +10 -7
- package/claude/skills/bash-cli-script/references/template.md +4 -28
- package/claude/skills/plan-groundwork/SKILL.md +1 -1
- package/claude/skills/read-frames/REQUIREMENT.md +1 -0
- package/claude/skills/read-frames/SKILL.md +1 -0
- package/claude/skills/teach-workspace/SKILL.md +4 -4
- package/claude/skills/ux-measure/REQUIREMENT.md +2 -1
- package/claude/skills/ux-measure/SKILL.md +3 -1
- package/claude/skills/ux-walkthrough/REQUIREMENT.md +3 -0
- package/claude/skills/ux-walkthrough/SKILL.md +1 -0
- package/docs/agents/architecture-record.md +5 -3
- package/docs/agents/context-audit-checks.md +1 -1
- package/docs/agents/context-audit.md +3 -3
- package/docs/agents/index.md +2 -2
- package/docs/agents/markdown-audit.md +1 -1
- package/docs/agents/pr-evidence.md +17 -0
- package/docs/agents/teach-authoring.md +3 -3
- package/docs/agents/teach.md +11 -5
- package/docs/workflow/skill-map.md +11 -10
- package/governance/rules/lang/120-bash.md +4 -5
- package/package.json +1 -1
- package/src/audits/catalog.ts +7 -1
- package/src/claude/cases/authoring.ts +0 -5
- package/src/commands/context.ts +25 -2
- package/src/commands/pr.ts +63 -41
- package/src/commands/serve.ts +2 -92
- package/src/commands/teach.ts +142 -17
- package/src/context/architecture.ts +82 -5
- package/src/context/gate.ts +10 -3
- package/src/gate/measures.ts +9 -4
- package/src/gate/stages.ts +1 -1
- package/src/serve/report.ts +110 -0
- package/src/teach/nav.ts +12 -1
- package/standards/architecture.md +14 -3
- package/standards/context.md +1 -1
- package/standards/decisions.md +6 -1
- package/standards/groundwork.md +1 -0
- package/standards/requirements.md +1 -1
- package/standards/rule.md +1 -0
- package/standards/skill.md +1 -1
- package/standards/standard.md +1 -0
- package/tooling/go/configs/scripts/verify.sh +47 -0
- package/tooling/go/reference.md +1 -1
- package/tooling/php/configs/scripts/verify.sh +47 -0
- package/tooling/php/reference.md +1 -1
- package/tooling/python/configs/scripts/verify.sh +47 -0
- package/tooling/python/reference.md +1 -1
- package/tooling/web/configs/scripts/verify.sh +47 -0
- package/tooling/web/reference.md +1 -1
- package/claude/skills/bash-script/REQUIREMENT.md +0 -36
- package/claude/skills/bash-script/SKILL.md +0 -100
- package/claude/skills/bash-script/references/patterns.md +0 -250
- 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.
|
package/tooling/web/reference.md
CHANGED
|
@@ -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
|
-
```
|