@rse/ase 2.0.4 → 2.0.5
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/dst/ase-hook.js +12 -3
- package/package.json +5 -5
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.github/plugin/plugin.json +1 -1
- package/plugin/mods/ase-mods-common.ts +444 -0
- package/plugin/mods/ase-mods-grill.ts +312 -0
- package/plugin/mods/ase-mods-help-browse.ts +405 -0
- package/plugin/mods/ase-mods-help-compose.ts +561 -0
- package/plugin/mods/ase-mods-help-find.ts +426 -0
- package/plugin/mods/ase-mods-help-hint.ts +134 -0
- package/plugin/mods/ase-mods-help-show.ts +142 -0
- package/plugin/mods/ase-mods.ts +214 -381
- package/plugin/skills/ase-code-lint/SKILL.md +15 -15
- package/plugin/skills/ase-code-lint/help.md +21 -8
- package/plugin/skills/ase-help-intent/SKILL.md +2 -1
- package/plugin/skills/ase-help-intent/data.md +42 -16
- package/plugin/skills/ase-spec-lint/SKILL.md +15 -15
- package/plugin/skills/ase-spec-lint/help.md +21 -8
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/*
|
|
2
|
+
** Agentic Software Engineering (ASE)
|
|
3
|
+
** Copyright (c) 2025-2026 Dr. Ralf S. Engelschall <rse@engelschall.com>
|
|
4
|
+
** Licensed under Apache 2.0 <https://spdx.org/licenses/Apache-2.0>
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/* Claude Code function hooks module ("mod", EARLY ACCESS):
|
|
8
|
+
Manual band, toggling for a skill command (bare or with its options and
|
|
9
|
+
arguments already typed), once suffixed with a blank and "//", the entire
|
|
10
|
+
manual page of the skill in the band above the prompt (scrolled by the
|
|
11
|
+
wheel), while the "//" is removed again from the prompt, so the command
|
|
12
|
+
can be typed on, and the band also closes once the prompt no longer starts
|
|
13
|
+
with the skill command, the synopsis form opens, or the prompt is
|
|
14
|
+
submitted. */
|
|
15
|
+
|
|
16
|
+
import type { Elements, PromptBox, RenderElement } from "claude-code"
|
|
17
|
+
import { type Engine, type Char, type BandRender, type BandScroll, type View,
|
|
18
|
+
uniform, flow, layout, wheel, panel } from "./ase-mods-common.ts"
|
|
19
|
+
|
|
20
|
+
/* the trigger of a manual band inside the prompt (a skill command suffixed
|
|
21
|
+
with a blank and "//", with its typed options and arguments, if any,
|
|
22
|
+
separated by a blank from it), the skill command at the start of the prompt,
|
|
23
|
+
the share of the terminal height taken by the band outside fullscreen, the
|
|
24
|
+
indentation of the section bodies, and the title placed into the top edge
|
|
25
|
+
of the band */
|
|
26
|
+
const MANUAL = /^\/(?:ase:)?(ase-[a-z0-9]+-[a-z0-9]+)(?:\s[^]*)?\s\/\/$/
|
|
27
|
+
const COMMAND = /^\/(?:ase:)?(ase-[a-z0-9]+-[a-z0-9]+)(?![\w-])/
|
|
28
|
+
const HEIGHT = 0.5
|
|
29
|
+
const INDENT = 2
|
|
30
|
+
const TITLE = "[ ⧉ ASE: Show Skill Manpage ]"
|
|
31
|
+
|
|
32
|
+
/* a manual band: the scroll state of its frame, the skill, its manual page,
|
|
33
|
+
and the manual page lines with the columns they were wrapped to */
|
|
34
|
+
type Manual = View & {
|
|
35
|
+
skill: string,
|
|
36
|
+
manual: string,
|
|
37
|
+
lines: Char[][],
|
|
38
|
+
columns: number
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* convert a manual page into lines of styled characters, hard-wrapped to the
|
|
42
|
+
given columns: each section with its heading in bold and its body indented
|
|
43
|
+
(with the lines of the SYNOPSIS kept apart, as in the manual page) */
|
|
44
|
+
export const page = (manual: string, columns: number): Char[][] => {
|
|
45
|
+
const lines: Char[][] = []
|
|
46
|
+
for (const part of manual.split(/^(?=## {2})/m).filter((part) => part.startsWith("## "))) {
|
|
47
|
+
const [ head, ...body ] = part.split("\n")
|
|
48
|
+
const name = head!.replace(/^## {2}/, "").trim()
|
|
49
|
+
const text = name === "SYNOPSIS" ?
|
|
50
|
+
body.filter((line) => line.trim() !== "").flatMap((line) => flow(line, columns - INDENT, true)) :
|
|
51
|
+
flow(body.join("\n"), columns - INDENT, true)
|
|
52
|
+
if (lines.length > 0)
|
|
53
|
+
lines.push([])
|
|
54
|
+
lines.push(uniform(name, true, undefined),
|
|
55
|
+
...text.map((line) => line.length > 0 ? [ ...uniform(" ".repeat(INDENT), false, undefined), ...line ] : line))
|
|
56
|
+
}
|
|
57
|
+
return lines
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* the manual band: its state and its operations */
|
|
61
|
+
export const modHelpShow = () => {
|
|
62
|
+
/* the manual pages of the skills (loaded on first use, undefined
|
|
63
|
+
for a skill without a manual page) and the currently shown band */
|
|
64
|
+
const manuals = new Map<string, string | undefined>()
|
|
65
|
+
let shown: Manual | undefined
|
|
66
|
+
|
|
67
|
+
/* determine whether the manual band is shown */
|
|
68
|
+
const active = () => shown !== undefined
|
|
69
|
+
|
|
70
|
+
/* close the manual band */
|
|
71
|
+
const reset = () => {
|
|
72
|
+
shown = undefined
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/* determine the manual page lines of the manual band, wrapped to the given columns */
|
|
76
|
+
const wrapped = (current: Manual, columns: number): Char[][] => {
|
|
77
|
+
if (current.columns !== columns) {
|
|
78
|
+
current.lines = page(current.manual, columns)
|
|
79
|
+
current.columns = columns
|
|
80
|
+
}
|
|
81
|
+
return current.lines
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* show the manual band of a skill with a manual page (loaded on first use),
|
|
85
|
+
and tell whether it is shown */
|
|
86
|
+
const show = async (skill: string, engine: Pick<Engine, "read">): Promise<boolean> => {
|
|
87
|
+
if (!manuals.has(skill))
|
|
88
|
+
manuals.set(skill, await engine.read(`skills/${skill}/help.md`).catch(() => undefined))
|
|
89
|
+
const manual = manuals.get(skill)
|
|
90
|
+
if (manual === undefined)
|
|
91
|
+
return false
|
|
92
|
+
shown = { skill, manual, lines: [], columns: 0, top: 0, scroll: 0, page: 1, split: 0 }
|
|
93
|
+
return true
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* for a skill command with a manual page, once suffixed with the trigger,
|
|
97
|
+
toggle its manual band (closing it, if already shown for the skill, else
|
|
98
|
+
showing it) and remove the trigger from the prompt again */
|
|
99
|
+
const toggle = async (box: PromptBox, engine: Pick<Engine, "read">): Promise<PromptBox | undefined> => {
|
|
100
|
+
const command = MANUAL.exec(box.text)
|
|
101
|
+
if (command === null)
|
|
102
|
+
return undefined
|
|
103
|
+
if (shown?.skill === command[1])
|
|
104
|
+
shown = undefined
|
|
105
|
+
else if (!await show(command[1]!, engine))
|
|
106
|
+
return undefined
|
|
107
|
+
const text = box.text.slice(0, -2)
|
|
108
|
+
return { text, cursor: text.length }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/* track the prompt and close the manual band once the prompt no longer
|
|
112
|
+
starts with its skill command */
|
|
113
|
+
const track = (text: string): void => {
|
|
114
|
+
if (shown !== undefined && COMMAND.exec(text)?.[1] !== shown.skill)
|
|
115
|
+
shown = undefined
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/* scroll the manual page in the band by the wheel
|
|
119
|
+
(and tell whether the band took the scrolling) */
|
|
120
|
+
const scroll = (e: BandScroll): boolean => {
|
|
121
|
+
if (shown === undefined)
|
|
122
|
+
return false
|
|
123
|
+
wheel(e, shown)
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* draw the manual page into the band above the prompt (terminal only),
|
|
128
|
+
yielding to a survey */
|
|
129
|
+
const render = (e: BandRender, ui: Elements["terminal"]): RenderElement | undefined => {
|
|
130
|
+
if (shown === undefined || e.props.hasSurvey)
|
|
131
|
+
return undefined
|
|
132
|
+
const geometry = layout(e, 0, HEIGHT)
|
|
133
|
+
return panel(ui, geometry, shown, {
|
|
134
|
+
title: TITLE,
|
|
135
|
+
hints: (geometry.overflow ? "wheel or CTRL+x + TAB + ↑/↓/PgUp/PgDn: scroll · " : "") +
|
|
136
|
+
"//: close · !!: Compose Skill Command · (keep typing the command)",
|
|
137
|
+
lines: wrapped(shown, geometry.span - 3)
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return { active, reset, toggle, track, scroll, render }
|
|
142
|
+
}
|