@erclx/canon 5.53.0 → 5.55.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/CHANGELOG.md +18 -0
- package/README.md +10 -9
- package/claude/.claude-plugin/plugin.json +1 -1
- package/claude/skills/canon-cli/SKILL.md +5 -5
- package/claude/skills/create-skill/SKILL.md +1 -1
- package/claude/skills/design-identity/REQUIREMENT.md +2 -2
- package/claude/skills/design-identity/SKILL.md +6 -6
- package/claude/skills/design-record/REQUIREMENT.md +2 -2
- package/claude/skills/design-record/SKILL.md +4 -4
- package/claude/skills/design-record/references/folder.md +1 -1
- package/claude/skills/design-taste/REQUIREMENT.md +1 -1
- package/claude/skills/document-health/SKILL.md +6 -6
- package/claude/skills/document-repair/SKILL.md +5 -5
- package/claude/skills/draw-figure/REQUIREMENT.md +1 -1
- package/claude/skills/draw-figure/SKILL.md +1 -1
- package/claude/skills/pick-by-eye/REQUIREMENT.md +4 -4
- package/claude/skills/pick-by-eye/SKILL.md +34 -41
- package/claude/skills/pick-by-eye/references/board.md +42 -0
- package/claude/skills/pick-by-eye/references/live-arms.md +3 -3
- package/claude/skills/plan-groundwork/SKILL.md +1 -1
- package/claude/skills/target-check/SKILL.md +6 -8
- package/claude/skills/target-check/references/domains.md +1 -4
- package/claude/skills/target-setup/SKILL.md +8 -8
- package/claude/skills/target-update/SKILL.md +5 -5
- package/claude/skills/teach-craft/REQUIREMENT.md +1 -1
- package/claude/skills/teach-craft/SKILL.md +32 -1
- package/claude/skills/teach-craft/references/adopted.md +113 -0
- package/claude/skills/teach-craft/references/lesson-craft.md +10 -2
- package/claude/skills/teach-craft/references/pedagogy.md +29 -2
- package/docs/agents/install-and-sync.md +2 -10
- package/docs/agents/output-shape.md +11 -10
- package/docs/target/migrations.md +8 -3
- package/docs/target/projects.md +1 -1
- package/docs/target/sync.md +1 -1
- package/docs/workflow/skill-map.md +0 -1
- package/docs/workflow/visual-design-workflow.md +1 -4
- package/governance/detect.toml +1 -6
- package/governance/renames.toml +0 -1
- package/package.json +2 -5
- package/scripts/core/install-check.sh +1 -1
- package/scripts/lib/ui.sh +12 -65
- package/src/audits/run.ts +1 -1
- package/src/claude/cases/misc.ts +0 -5
- package/src/claude/routing.ts +1 -1
- package/src/claude/skills-audit.ts +1 -0
- package/src/cli/help.ts +0 -3
- package/src/cli/run.ts +5 -6
- package/src/cli/ui.ts +87 -28
- package/src/cli.ts +6 -2
- package/src/commands/claude.ts +2 -3
- package/src/commands/design.ts +20 -324
- package/src/commands/gate.ts +5 -3
- package/src/commands/gov.ts +11 -21
- package/src/commands/labels.ts +1 -1
- package/src/commands/pr.ts +1 -1
- package/src/commands/sync.ts +5 -6
- package/src/commands/tooling.ts +5 -8
- package/src/commands/transcripts.ts +6 -11
- package/src/design/regen.ts +5 -32
- package/src/design/render.ts +1 -1
- package/src/design/tokens.ts +3 -2
- package/src/gate/sequencer.ts +1 -1
- package/src/gate/stages.ts +4 -20
- package/src/gov/audit.ts +1 -6
- package/src/hooks/post-merge.ts +2 -5
- package/src/markdown/scope.ts +2 -4
- package/src/process/harness.ts +3 -4
- package/src/sandbox/equivalence.ts +0 -1
- package/src/sandbox/provision.ts +8 -13
- package/src/sandbox/scenario.ts +6 -12
- package/src/shipped/references.ts +1 -2
- package/src/sync/check.ts +4 -29
- package/src/sync/engine.ts +3 -8
- package/src/sync/stamp.ts +6 -6
- package/src/sync/target.ts +1 -4
- package/src/sync/workflow.ts +3 -5
- package/src/tooling/verify-stack.ts +1 -1
- package/tooling/base/configs/.husky/post-merge +6 -10
- package/tooling/claude/reference.md +1 -1
- package/tooling/claude/seeds/canon/DESIGN.md +1 -1
- package/claude/skills/canvas-board/REQUIREMENT.md +0 -35
- package/claude/skills/canvas-board/SKILL.md +0 -143
- package/claude/skills/canvas-board/references/canvas.md +0 -170
- package/governance/rules/ui/canvas.md +0 -13
- package/scripts/core/check-color-source.sh +0 -51
- package/src/canvas/address.ts +0 -227
- package/src/canvas/arrange.ts +0 -66
- package/src/canvas/capture.ts +0 -391
- package/src/canvas/client/app.tsx +0 -188
- package/src/canvas/client/inspector/align-grid.tsx +0 -162
- package/src/canvas/client/inspector/color-field.tsx +0 -398
- package/src/canvas/client/inspector/color-inputs.tsx +0 -139
- package/src/canvas/client/inspector/color-picker.tsx +0 -668
- package/src/canvas/client/inspector/color-space.ts +0 -240
- package/src/canvas/client/inspector/field.tsx +0 -263
- package/src/canvas/client/inspector/fields.css +0 -763
- package/src/canvas/client/inspector/section.tsx +0 -27
- package/src/canvas/client/inspector/size-field.tsx +0 -69
- package/src/canvas/client/inspector/token-select.tsx +0 -109
- package/src/canvas/client/inspector/values.ts +0 -297
- package/src/canvas/client/inspector.tsx +0 -692
- package/src/canvas/client/keys.ts +0 -71
- package/src/canvas/client/layers.tsx +0 -118
- package/src/canvas/client/main.tsx +0 -71
- package/src/canvas/client/pages-panel.tsx +0 -212
- package/src/canvas/client/panel-handle.tsx +0 -90
- package/src/canvas/client/selection.tsx +0 -199
- package/src/canvas/client/shell.css +0 -762
- package/src/canvas/client/state.ts +0 -792
- package/src/canvas/client/surface.tsx +0 -803
- package/src/canvas/client/theme-panel.tsx +0 -55
- package/src/canvas/client/tools.tsx +0 -153
- package/src/canvas/content.ts +0 -781
- package/src/canvas/edit.ts +0 -575
- package/src/canvas/editing.ts +0 -203
- package/src/canvas/fit.ts +0 -238
- package/src/canvas/global.d.ts +0 -15
- package/src/canvas/history.ts +0 -116
- package/src/canvas/index.html +0 -20
- package/src/canvas/server.ts +0 -766
- package/src/canvas/tokens.ts +0 -221
- package/src/commands/canvas.ts +0 -1097
- package/src/design/adapter.ts +0 -59
- package/src/design/base.css +0 -203
- package/src/design/brand-favicon.ts +0 -18
- package/src/design/neutral.ts +0 -127
- package/src/design/project-fonts.ts +0 -392
package/governance/detect.toml
CHANGED
|
@@ -9,8 +9,7 @@
|
|
|
9
9
|
# root and in every folder to depth two. `files` are globs matched against the
|
|
10
10
|
# target's git-listed paths to depth two, and against each folder on those
|
|
11
11
|
# paths, so `.github/workflows` matches a folder holding a workflow. A glob with
|
|
12
|
-
# no wildcard is also tested on disk, which reaches a gitignored path
|
|
13
|
-
# `.canon/canvas`.
|
|
12
|
+
# no wildcard is also tested on disk, which reaches a gitignored path.
|
|
14
13
|
#
|
|
15
14
|
# Every rule no stack reaches needs a table here, which the `unreferenced-rules`
|
|
16
15
|
# gate stage checks, along with every key naming a live rule. A table for a rule
|
|
@@ -201,10 +200,6 @@ files = ["index.html", "*/index.html"]
|
|
|
201
200
|
deps = ["react", "vue", "svelte", "astro", "next", "solid-js", "preact"]
|
|
202
201
|
files = ["index.html", "*/index.html"]
|
|
203
202
|
|
|
204
|
-
["canvas"]
|
|
205
|
-
deps = []
|
|
206
|
-
files = [".canon/canvas"]
|
|
207
|
-
|
|
208
203
|
["design-reference"]
|
|
209
204
|
deps = []
|
|
210
205
|
files = ["canon/design/*/"]
|
package/governance/renames.toml
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erclx/canon",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "5.
|
|
4
|
+
"version": "5.55.0",
|
|
5
5
|
"description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
@@ -51,11 +51,9 @@
|
|
|
51
51
|
"web:e2e": "cd web && playwright test"
|
|
52
52
|
},
|
|
53
53
|
"dependencies": {
|
|
54
|
-
"@preact/signals": "^2.11.3",
|
|
55
54
|
"commander": "^13.1.0",
|
|
56
55
|
"execa": "^9.5.2",
|
|
57
|
-
"playwright-core": "1.64.1"
|
|
58
|
-
"preact": "^11.0.0"
|
|
56
|
+
"playwright-core": "1.64.1"
|
|
59
57
|
},
|
|
60
58
|
"devDependencies": {
|
|
61
59
|
"@astrojs/check": "^0.9.8",
|
|
@@ -68,7 +66,6 @@
|
|
|
68
66
|
"@types/bun": "^1.2.10",
|
|
69
67
|
"@types/micromatch": "^4.0.10",
|
|
70
68
|
"astro": "^6.1.9",
|
|
71
|
-
"happy-dom": "^20.14.5",
|
|
72
69
|
"husky": "^9.1.7",
|
|
73
70
|
"micromatch": "^4.0.8",
|
|
74
71
|
"oxfmt": "0.73.0",
|
|
@@ -88,7 +88,7 @@ mkdir -p "$TARGET_DIR"
|
|
|
88
88
|
log_info "Initialized git in $TARGET_DIR"
|
|
89
89
|
|
|
90
90
|
log_step "Run canon init"
|
|
91
|
-
(cd "$TARGET_DIR" &&
|
|
91
|
+
(cd "$TARGET_DIR" && bun run "$EXTRACT_DIR/src/cli.ts" init --stack base </dev/null 2>&1 | pipe_output) || log_error "canon init failed"
|
|
92
92
|
log_info "canon init completed"
|
|
93
93
|
|
|
94
94
|
log_step "Assert scaffold"
|
package/scripts/lib/ui.sh
CHANGED
|
@@ -6,93 +6,40 @@ if [ "${BASH_VERSINFO[0]:-0}" -lt 4 ]; then
|
|
|
6
6
|
exit 1
|
|
7
7
|
fi
|
|
8
8
|
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
supports_color() {
|
|
12
|
-
[ -z "${NO_COLOR:-}" ] && [ -t "$1" ]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
# Assigns the six palette names for one file descriptor. The blank palette keeps
|
|
16
|
-
# every frame character and drops only the escapes, so a captured run still
|
|
17
|
-
# reads as one block.
|
|
18
|
-
#
|
|
19
|
-
# Bash returns a string and nothing richer, so the palette arrives through the
|
|
20
|
-
# caller's own scope. A writer declaring all six `local` before calling this
|
|
21
|
-
# keeps the assignment to itself, which is what leaves the source-time answer
|
|
22
|
-
# below standing for anything that sources this file.
|
|
23
|
-
set_palette() {
|
|
24
|
-
if supports_color "$1"; then
|
|
25
|
-
GREEN='\033[0;32m'
|
|
26
|
-
RED='\033[0;31m'
|
|
27
|
-
YELLOW='\033[0;33m'
|
|
28
|
-
WHITE='\033[1;37m'
|
|
29
|
-
GREY='\033[0;90m'
|
|
30
|
-
NC='\033[0m'
|
|
31
|
-
else
|
|
32
|
-
GREEN=''
|
|
33
|
-
RED=''
|
|
34
|
-
YELLOW=''
|
|
35
|
-
WHITE=''
|
|
36
|
-
GREY=''
|
|
37
|
-
NC=''
|
|
38
|
-
fi
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
# Every writer below frames to stderr and asks about that stream at write time,
|
|
42
|
-
# so no answer is frozen before a caller's redirect is in place. This one answers
|
|
43
|
-
# for stdout instead, because it is read by a sourcing script writing its own
|
|
44
|
-
# frame with a bare `echo`, and that is the stream a bare `echo` reaches.
|
|
45
|
-
set_palette 1
|
|
9
|
+
# The frame writes no color. The scripts left on it run as setup and gate steps,
|
|
10
|
+
# where a pipe or a log reads the output more often than a terminal does.
|
|
46
11
|
|
|
47
12
|
log_info() {
|
|
48
|
-
|
|
49
|
-
set_palette 2
|
|
50
|
-
echo -e "${GREY}│${NC} ${GREEN}✓${NC} $1" >&2
|
|
13
|
+
echo -e "│ ✓ $1" >&2
|
|
51
14
|
}
|
|
52
15
|
log_warn() {
|
|
53
|
-
|
|
54
|
-
set_palette 2
|
|
55
|
-
echo -e "${GREY}│${NC} ${YELLOW}!${NC} $1" >&2
|
|
16
|
+
echo -e "│ ! $1" >&2
|
|
56
17
|
}
|
|
57
18
|
log_error() {
|
|
58
|
-
|
|
59
|
-
set_palette 2
|
|
60
|
-
echo -e "${GREY}│${NC} ${RED}✗${NC} $1" >&2
|
|
19
|
+
echo -e "│ ✗ $1" >&2
|
|
61
20
|
exit 1
|
|
62
21
|
}
|
|
63
22
|
log_step() {
|
|
64
|
-
|
|
65
|
-
set_palette 2
|
|
66
|
-
echo -e "${GREY}│${NC}\n${GREY}├${NC} ${WHITE}$1${NC}" >&2
|
|
23
|
+
echo -e "│\n├ $1" >&2
|
|
67
24
|
}
|
|
68
25
|
log_add() {
|
|
69
|
-
|
|
70
|
-
set_palette 2
|
|
71
|
-
echo -e "${GREY}│${NC} ${GREEN}+${NC} $1" >&2
|
|
26
|
+
echo -e "│ + $1" >&2
|
|
72
27
|
}
|
|
73
28
|
log_rem() {
|
|
74
|
-
|
|
75
|
-
set_palette 2
|
|
76
|
-
echo -e "${GREY}│${NC} ${RED}-${NC} $1" >&2
|
|
29
|
+
echo -e "│ - $1" >&2
|
|
77
30
|
}
|
|
78
31
|
|
|
79
32
|
pipe_output() {
|
|
80
|
-
|
|
81
|
-
set_palette 2
|
|
82
|
-
while IFS= read -r line; do echo -e "${GREY}│${NC} $line" >&2; done
|
|
33
|
+
while IFS= read -r line; do echo -e "│ $line" >&2; done
|
|
83
34
|
}
|
|
84
35
|
|
|
85
36
|
open_timeline() {
|
|
86
|
-
|
|
87
|
-
set_palette 2
|
|
88
|
-
echo -e "${GREY}┌${NC}" >&2
|
|
37
|
+
echo -e "┌" >&2
|
|
89
38
|
if [ -n "${1:-}" ]; then
|
|
90
|
-
echo -e "
|
|
39
|
+
echo -e "│ $1" >&2
|
|
91
40
|
fi
|
|
92
41
|
}
|
|
93
42
|
|
|
94
43
|
close_timeline() {
|
|
95
|
-
|
|
96
|
-
set_palette 2
|
|
97
|
-
echo -e "${GREY}└${NC}" >&2
|
|
44
|
+
echo -e "└" >&2
|
|
98
45
|
}
|
package/src/audits/run.ts
CHANGED
|
@@ -74,7 +74,7 @@ export function spawnAudit(root: string): Spawn {
|
|
|
74
74
|
// extending it. Spreading `process.env` alongside would put back the very
|
|
75
75
|
// variables it strips, and a hook's `GIT_DIR` would then point every
|
|
76
76
|
// history-reading verb at a repository nobody asked about.
|
|
77
|
-
env:
|
|
77
|
+
env: gitEnv(),
|
|
78
78
|
extendEnv: false,
|
|
79
79
|
})
|
|
80
80
|
|
package/src/claude/cases/misc.ts
CHANGED
|
@@ -22,11 +22,6 @@ export const MISC_CASES: readonly SkillCase[] = [
|
|
|
22
22
|
'This project has no logo yet. Draft one and give me a social card to go with it.',
|
|
23
23
|
expect: 'design-identity',
|
|
24
24
|
},
|
|
25
|
-
{
|
|
26
|
-
prompt:
|
|
27
|
-
'Draw the signup screen as a frame on the canvas, then restyle the element I just selected there.',
|
|
28
|
-
expect: 'canvas-board',
|
|
29
|
-
},
|
|
30
25
|
{
|
|
31
26
|
prompt:
|
|
32
27
|
'The operator picked the new settings panel, so land it in canon/design with an index row for it.',
|
package/src/claude/routing.ts
CHANGED
|
@@ -9,7 +9,7 @@ const TOKEN = /`([^`\s]+)`/g
|
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* A path a reader could open. Admits no `$`, `*`, or `=`, so an assignment
|
|
12
|
-
* (`
|
|
12
|
+
* (`NAME=value`) and a variable are both excluded.
|
|
13
13
|
*/
|
|
14
14
|
const PATH_TOKEN = /^[.A-Za-z0-9_][A-Za-z0-9._/-]*$/
|
|
15
15
|
|
package/src/cli/help.ts
CHANGED
|
@@ -43,7 +43,6 @@ export const COMMAND_GROUPS: { title: string; rows: CommandRow[] }[] = [
|
|
|
43
43
|
['inventory [subj]', 'Report one computed property across every route'],
|
|
44
44
|
['drive <url> <run>', 'Walk a page through interactions, measure each'],
|
|
45
45
|
['transcripts <url>', 'Fetch a YouTube transcript with frontmatter'],
|
|
46
|
-
['canvas [cmd]', 'Local canvas of pages and frames'],
|
|
47
46
|
],
|
|
48
47
|
},
|
|
49
48
|
{
|
|
@@ -98,8 +97,6 @@ const EXAMPLES = [
|
|
|
98
97
|
'canon indexes regen --dry-run --json',
|
|
99
98
|
'canon docs list --json',
|
|
100
99
|
'canon docs agents',
|
|
101
|
-
'canon design render',
|
|
102
|
-
'canon canvas list --json',
|
|
103
100
|
'canon capture assets/captures/install.html --selector .window --out assets/frames',
|
|
104
101
|
'canon inventory focus --json',
|
|
105
102
|
'canon drive http://localhost:4173 run.json --json',
|
package/src/cli/run.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { join } from 'node:path'
|
|
|
2
2
|
import { execa } from 'execa'
|
|
3
3
|
|
|
4
4
|
export interface CliRunOptions {
|
|
5
|
-
/**
|
|
5
|
+
/** Closes the child's stdin, which makes it headless. Defaults to false. */
|
|
6
6
|
readonly nonInteractive?: boolean
|
|
7
7
|
/** Defaults to `inherit`, so a child prompt still reaches the operator. */
|
|
8
8
|
readonly stdin?: 'ignore' | 'inherit'
|
|
@@ -28,13 +28,12 @@ export function cliRun(
|
|
|
28
28
|
options: CliRunOptions = {},
|
|
29
29
|
): () => Promise<boolean> {
|
|
30
30
|
return async () => {
|
|
31
|
-
const
|
|
32
|
-
?
|
|
33
|
-
:
|
|
31
|
+
const stdin = options.nonInteractive
|
|
32
|
+
? 'ignore'
|
|
33
|
+
: (options.stdin ?? 'inherit')
|
|
34
34
|
|
|
35
35
|
const result = await execa('bun', [path, ...args], {
|
|
36
|
-
stdio: [
|
|
37
|
-
env,
|
|
36
|
+
stdio: [stdin, 'inherit', 'inherit'],
|
|
38
37
|
reject: false,
|
|
39
38
|
})
|
|
40
39
|
|
package/src/cli/ui.ts
CHANGED
|
@@ -53,7 +53,38 @@ export function palette(stream: { isTTY?: boolean }): Palette {
|
|
|
53
53
|
return supportsColor(stream) ? COLOR : PLAIN
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
let quiet = false
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Silences the progress frame for a caller that asked for a record, since an
|
|
60
|
+
* agent reads both streams and the frame outweighed the data. Warnings and
|
|
61
|
+
* errors still write, since diagnostics reach stderr in every mode.
|
|
62
|
+
*/
|
|
63
|
+
export function setQuiet(on: boolean): void {
|
|
64
|
+
quiet = on
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The frame is drawn only for a terminal. A pipe or a file gets each line's
|
|
69
|
+
* mark alone, since a reader there gains nothing from `┌ │ ├ └` and an agent
|
|
70
|
+
* pays for every one of them.
|
|
71
|
+
*/
|
|
72
|
+
function framed(): boolean {
|
|
73
|
+
return process.stderr.isTTY === true
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function markLine(color: keyof Palette, mark: string, message: string): string {
|
|
77
|
+
if (!framed()) return `${mark} ${message}\n`
|
|
78
|
+
const { GREY, NC, [color]: tint } = palette(process.stderr)
|
|
79
|
+
return `${GREY}│${NC} ${tint}${mark}${NC} ${message}\n`
|
|
80
|
+
}
|
|
81
|
+
|
|
56
82
|
export function intro(title: string): void {
|
|
83
|
+
if (quiet) return
|
|
84
|
+
if (!framed()) {
|
|
85
|
+
process.stderr.write(`${title}\n`)
|
|
86
|
+
return
|
|
87
|
+
}
|
|
57
88
|
const { GREY, NC, WHITE } = palette(process.stderr)
|
|
58
89
|
process.stderr.write(`${GREY}┌${NC}\n${GREY}│${NC} ${WHITE}${title}${NC}\n`)
|
|
59
90
|
}
|
|
@@ -64,23 +95,22 @@ export function intro(title: string): void {
|
|
|
64
95
|
* leaving stdout clean for JSON and lists.
|
|
65
96
|
*/
|
|
66
97
|
export function logInfo(message: string): void {
|
|
67
|
-
|
|
68
|
-
process.stderr.write(
|
|
98
|
+
if (quiet) return
|
|
99
|
+
process.stderr.write(markLine('GREEN', '✓', message))
|
|
69
100
|
}
|
|
70
101
|
|
|
71
102
|
export function logWarn(message: string): void {
|
|
72
|
-
|
|
73
|
-
process.stderr.write(`${GREY}│${NC} ${YELLOW}!${NC} ${message}\n`)
|
|
103
|
+
process.stderr.write(markLine('YELLOW', '!', message))
|
|
74
104
|
}
|
|
75
105
|
|
|
76
106
|
export function logAdd(message: string): void {
|
|
77
|
-
|
|
78
|
-
process.stderr.write(
|
|
107
|
+
if (quiet) return
|
|
108
|
+
process.stderr.write(markLine('GREEN', '+', message))
|
|
79
109
|
}
|
|
80
110
|
|
|
81
111
|
export function logRemove(message: string): void {
|
|
82
|
-
|
|
83
|
-
process.stderr.write(
|
|
112
|
+
if (quiet) return
|
|
113
|
+
process.stderr.write(markLine('RED', '-', message))
|
|
84
114
|
}
|
|
85
115
|
|
|
86
116
|
/**
|
|
@@ -89,31 +119,48 @@ export function logRemove(message: string): void {
|
|
|
89
119
|
* exit code rather than terminating mid-write.
|
|
90
120
|
*/
|
|
91
121
|
export function logError(message: string): void {
|
|
92
|
-
|
|
93
|
-
process.stderr.write(`${GREY}│${NC} ${RED}✗${NC} ${message}\n`)
|
|
122
|
+
process.stderr.write(markLine('RED', '✗', message))
|
|
94
123
|
}
|
|
95
124
|
|
|
96
125
|
export function logStep(message: string): void {
|
|
126
|
+
if (quiet) return
|
|
127
|
+
if (!framed()) {
|
|
128
|
+
process.stderr.write(`${message}\n`)
|
|
129
|
+
return
|
|
130
|
+
}
|
|
97
131
|
const { GREY, NC, WHITE } = palette(process.stderr)
|
|
98
132
|
process.stderr.write(`${GREY}│${NC}\n${GREY}├${NC} ${WHITE}${message}${NC}\n`)
|
|
99
133
|
}
|
|
100
134
|
|
|
101
135
|
export function outro(): void {
|
|
136
|
+
if (quiet || !framed()) return
|
|
102
137
|
const { GREY, NC } = palette(process.stderr)
|
|
103
138
|
process.stderr.write(`${GREY}└${NC}\n`)
|
|
104
139
|
}
|
|
105
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Closes the frame on the green line a command ends a success on. The line
|
|
143
|
+
* sits below the frame rather than inside it, and a quiet run writes neither.
|
|
144
|
+
*/
|
|
145
|
+
export function outroSuccess(message: string, detail?: string): void {
|
|
146
|
+
if (quiet) return
|
|
147
|
+
outro()
|
|
148
|
+
const { GREEN, GREY, NC } = palette(process.stderr)
|
|
149
|
+
const suffix = detail === undefined ? '' : ` ${GREY}${detail}${NC}`
|
|
150
|
+
process.stderr.write(`${GREEN}✓ ${message}${NC}${suffix}\n`)
|
|
151
|
+
}
|
|
152
|
+
|
|
106
153
|
/**
|
|
107
154
|
* Indents borrowed output inside an open frame, matching `pipe_output` in
|
|
108
155
|
* `scripts/lib/ui.sh`. Used for a document the command did not compose itself,
|
|
109
156
|
* such as a pull request body or the output of a git mutation.
|
|
110
157
|
*/
|
|
111
158
|
export function pipeOutput(text: string): void {
|
|
159
|
+
if (quiet) return
|
|
112
160
|
const { GREY, NC } = palette(process.stderr)
|
|
161
|
+
const bar = framed() ? `${GREY}│${NC}` : ''
|
|
113
162
|
const lines = text.replace(/\n$/, '').split('\n')
|
|
114
|
-
process.stderr.write(
|
|
115
|
-
`${lines.map((line) => `${GREY}│${NC} ${line}`).join('\n')}\n`,
|
|
116
|
-
)
|
|
163
|
+
process.stderr.write(`${lines.map((line) => `${bar} ${line}`).join('\n')}\n`)
|
|
117
164
|
}
|
|
118
165
|
|
|
119
166
|
/** Counts a noun for a report line, where the plural is the bare `s` form. */
|
|
@@ -122,6 +169,10 @@ export function plural(count: number, noun: string): string {
|
|
|
122
169
|
}
|
|
123
170
|
|
|
124
171
|
export function frameError(message: string): void {
|
|
172
|
+
if (!framed()) {
|
|
173
|
+
process.stderr.write(`✗ ${message}\n`)
|
|
174
|
+
return
|
|
175
|
+
}
|
|
125
176
|
const { GREY, NC, RED } = palette(process.stderr)
|
|
126
177
|
process.stderr.write(
|
|
127
178
|
`${GREY}┌${NC}\n${GREY}│${NC} ${RED}✗${NC} ${message}\n${GREY}└${NC}\n`,
|
|
@@ -129,6 +180,11 @@ export function frameError(message: string): void {
|
|
|
129
180
|
}
|
|
130
181
|
|
|
131
182
|
export function frameSuccess(command: string, target: string): void {
|
|
183
|
+
if (quiet) return
|
|
184
|
+
if (!framed()) {
|
|
185
|
+
process.stderr.write(`${command}\n✓ ${target}\n`)
|
|
186
|
+
return
|
|
187
|
+
}
|
|
132
188
|
const { GREEN, GREY, NC, WHITE } = palette(process.stderr)
|
|
133
189
|
process.stderr.write(
|
|
134
190
|
`${GREY}┌${NC}\n${GREY}│${NC} ${WHITE}${command}${NC}\n${GREY}│${NC}\n${GREY}│${NC} ${GREEN}✓${NC} ${target}\n${GREY}└${NC}\n`,
|
|
@@ -136,17 +192,17 @@ export function frameSuccess(command: string, target: string): void {
|
|
|
136
192
|
}
|
|
137
193
|
|
|
138
194
|
/**
|
|
139
|
-
*
|
|
195
|
+
* A caller with no terminal on stdin is headless, since nobody can answer a
|
|
196
|
+
* prompt there.
|
|
140
197
|
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* command that quietly waits for a TTY nobody is watching rather than one that
|
|
146
|
-
* refuses and says why.
|
|
198
|
+
* Both variables are retired overrides that force headless on a terminal and
|
|
199
|
+
* stay readable permanently. Skill bodies, CI jobs, and operator scripts in
|
|
200
|
+
* other repositories and plugin caches this release cannot reach still set
|
|
201
|
+
* them, and dropping the read turns their call into a prompt nobody watches.
|
|
147
202
|
*/
|
|
148
203
|
export function isNonInteractive(): boolean {
|
|
149
204
|
return (
|
|
205
|
+
!process.stdin.isTTY ||
|
|
150
206
|
process.env.CANON_NON_INTERACTIVE === '1' ||
|
|
151
207
|
// canon-keep-retired
|
|
152
208
|
process.env.AITK_NON_INTERACTIVE === '1'
|
|
@@ -155,8 +211,8 @@ export function isNonInteractive(): boolean {
|
|
|
155
211
|
|
|
156
212
|
/**
|
|
157
213
|
* `nonInteractiveDefault` opts a prompt into resolving to its first option
|
|
158
|
-
*
|
|
159
|
-
* for the user leave it off.
|
|
214
|
+
* when the caller is headless instead of failing. Callers that would rather
|
|
215
|
+
* fail loudly than pick for the user leave it off.
|
|
160
216
|
*/
|
|
161
217
|
export async function select<Value>(opts: {
|
|
162
218
|
message: string
|
|
@@ -171,9 +227,11 @@ export async function select<Value>(opts: {
|
|
|
171
227
|
let cursor = 0
|
|
172
228
|
|
|
173
229
|
if (opts.nonInteractiveDefault && isNonInteractive()) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
230
|
+
if (!quiet) {
|
|
231
|
+
process.stderr.write(
|
|
232
|
+
markLine('GREY', '◇', `${message} ${options[0].label}`),
|
|
233
|
+
)
|
|
234
|
+
}
|
|
177
235
|
return options[0].value
|
|
178
236
|
}
|
|
179
237
|
|
|
@@ -196,10 +254,11 @@ export async function select<Value>(opts: {
|
|
|
196
254
|
}
|
|
197
255
|
|
|
198
256
|
return new Promise<Value>((resolve) => {
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
`${
|
|
257
|
+
if (isNonInteractive()) {
|
|
258
|
+
logError(
|
|
259
|
+
`${message} requires a TTY. ${opts.ttyHint ?? 'Pass an argument'}.`,
|
|
202
260
|
)
|
|
261
|
+
outro()
|
|
203
262
|
process.exit(1)
|
|
204
263
|
}
|
|
205
264
|
|
package/src/cli.ts
CHANGED
|
@@ -18,7 +18,6 @@ import { register as feedback } from '@/commands/feedback'
|
|
|
18
18
|
import { register as transcripts } from '@/commands/transcripts'
|
|
19
19
|
import { register as tasks } from '@/commands/tasks'
|
|
20
20
|
import { register as intake } from '@/commands/intake'
|
|
21
|
-
import { register as canvas } from '@/commands/canvas'
|
|
22
21
|
import { register as comments } from '@/commands/comments'
|
|
23
22
|
import { register as context } from '@/commands/context'
|
|
24
23
|
import { register as markdown } from '@/commands/markdown'
|
|
@@ -41,6 +40,7 @@ import { register as changelog } from '@/commands/changelog'
|
|
|
41
40
|
import { readInstalled, UNKNOWN_LABEL } from '@/version/installed'
|
|
42
41
|
import { renderHelp } from '@/cli/help'
|
|
43
42
|
import { renderAll, routeHelp } from '@/cli/help-all'
|
|
43
|
+
import { setQuiet } from '@/cli/ui'
|
|
44
44
|
|
|
45
45
|
function showHelp(): void {
|
|
46
46
|
console.log(renderHelp(process.stdout))
|
|
@@ -58,6 +58,11 @@ program.on('option:help', () => {
|
|
|
58
58
|
process.exit(0)
|
|
59
59
|
})
|
|
60
60
|
program.option('-h, --help', 'Show help')
|
|
61
|
+
// A caller asking for a record reads it from stdout, so the frame on stderr is
|
|
62
|
+
// noise it pays for. Warnings and errors still write.
|
|
63
|
+
program.hook('preAction', (_program, action) => {
|
|
64
|
+
if (action.opts().json === true) setQuiet(true)
|
|
65
|
+
})
|
|
61
66
|
|
|
62
67
|
init(program)
|
|
63
68
|
sandbox(program)
|
|
@@ -76,7 +81,6 @@ feedback(program)
|
|
|
76
81
|
transcripts(program)
|
|
77
82
|
tasks(program)
|
|
78
83
|
intake(program)
|
|
79
|
-
canvas(program)
|
|
80
84
|
comments(program)
|
|
81
85
|
context(program)
|
|
82
86
|
markdown(program)
|
package/src/commands/claude.ts
CHANGED
|
@@ -91,6 +91,7 @@ import {
|
|
|
91
91
|
logStep,
|
|
92
92
|
logWarn,
|
|
93
93
|
outro,
|
|
94
|
+
outroSuccess,
|
|
94
95
|
palette,
|
|
95
96
|
pipeOutput,
|
|
96
97
|
plural,
|
|
@@ -604,9 +605,7 @@ export function register(program: Command): void {
|
|
|
604
605
|
}
|
|
605
606
|
|
|
606
607
|
function succeed(message: string): number {
|
|
607
|
-
|
|
608
|
-
outro()
|
|
609
|
-
process.stderr.write(`${GREEN}✓ ${message}${NC}\n`)
|
|
608
|
+
outroSuccess(message)
|
|
610
609
|
return 0
|
|
611
610
|
}
|
|
612
611
|
|