@ancleto/spec 0.6.1 → 0.6.4
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 +184 -91
- package/agents/spec-writer.md +2 -2
- package/commands/{opsx-apply.md → cleto-apply.md} +4 -4
- package/commands/{opsx-archive.md → cleto-archive.md} +3 -3
- package/commands/{opsx-bulk-archive.md → cleto-bulk-archive.md} +1 -1
- package/commands/{opsx-continue.md → cleto-continue.md} +3 -3
- package/commands/{opsx-explore.md → cleto-explore.md} +1 -1
- package/commands/{opsx-ff.md → cleto-ff.md} +2 -2
- package/commands/{opsx-new.md → cleto-new.md} +4 -4
- package/commands/{opsx-onboard.md → cleto-onboard.md} +22 -22
- package/commands/{opsx-propose.md → cleto-propose.md} +3 -3
- package/commands/{opsx-recall.md → cleto-recall.md} +6 -6
- package/commands/{opsx-sync.md → cleto-sync.md} +1 -1
- package/commands/{opsx-verify.md → cleto-verify.md} +1 -1
- package/package.json +1 -1
- package/skills/{openspec-apply → ancleto-apply}/SKILL.md +2 -2
- package/skills/{openspec-archive → ancleto-archive}/SKILL.md +1 -1
- package/skills/{openspec-bulk-archive → ancleto-bulk-archive}/SKILL.md +1 -1
- package/skills/{openspec-continue → ancleto-continue}/SKILL.md +4 -4
- package/skills/{openspec-explore → ancleto-explore}/SKILL.md +1 -1
- package/skills/{openspec-ff → ancleto-ff}/SKILL.md +2 -2
- package/skills/{openspec-new → ancleto-new}/SKILL.md +2 -2
- package/skills/{openspec-onboard → ancleto-onboard}/SKILL.md +11 -11
- package/skills/{openspec-propose → ancleto-propose}/SKILL.md +4 -4
- package/skills/{openspec-recall → ancleto-recall}/SKILL.md +3 -3
- package/skills/{openspec-sync-specs → ancleto-sync-specs}/SKILL.md +1 -1
- package/skills/ancleto-upgrade/SKILL.md +2 -2
- package/skills/{openspec-verify → ancleto-verify}/SKILL.md +1 -1
- package/skills/{openspec-workflow → ancleto-workflow}/SKILL.md +17 -17
- package/skills/triage-clarifier/SKILL.md +1 -1
- package/src/cli/index.js +81 -34
- package/src/cli/ui.js +181 -0
package/src/cli/ui.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { setTimeout as sleep } from 'node:timers/promises'
|
|
2
|
+
|
|
3
|
+
const CYAN = '\x1b[36m'
|
|
4
|
+
const BOLD = '\x1b[1m'
|
|
5
|
+
const RESET = '\x1b[0m'
|
|
6
|
+
|
|
7
|
+
const CUP = [
|
|
8
|
+
' █████████████████████████████████',
|
|
9
|
+
' █████████████████████████████████████',
|
|
10
|
+
' ██ ██ ███',
|
|
11
|
+
' ██ ██ ██',
|
|
12
|
+
' ███ ███ ███',
|
|
13
|
+
' ███ █████████',
|
|
14
|
+
' ███ ███',
|
|
15
|
+
' ██████████████████████████████████████',
|
|
16
|
+
' ███████ █████████',
|
|
17
|
+
' ██████████████████████████████████',
|
|
18
|
+
' ██████████████████████████████'
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
const LOGO = [
|
|
22
|
+
' ██████ ██ ██ ██████ ██ ███████ ████████ ██████ ',
|
|
23
|
+
'██ ██ ███ ██ ██ ██ ██ ██ ██ ██',
|
|
24
|
+
'████████ ██ █ ██ ██ ██ █████ ██ ██ ██',
|
|
25
|
+
'██ ██ ██ █ ██ ██ ██ ██ ██ ██ ██',
|
|
26
|
+
'██ ██ ██ ███ ██████ ███████ ███████ ██ ██████ '
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
const ART = [...CUP, '', ...LOGO, '']
|
|
30
|
+
const TEXT_START = CUP.length + 1
|
|
31
|
+
|
|
32
|
+
const CUP_PALETTE = ['\x1b[38;5;94m', '\x1b[38;5;130m', '\x1b[38;5;172m', '\x1b[38;5;208m', '\x1b[38;5;214m', '\x1b[38;5;220m', '\x1b[38;5;230m']
|
|
33
|
+
const TEXT_PALETTE = ['\x1b[38;5;27m', '\x1b[38;5;33m', '\x1b[38;5;45m', '\x1b[38;5;51m', '\x1b[38;5;201m']
|
|
34
|
+
|
|
35
|
+
let activeBanner = null
|
|
36
|
+
|
|
37
|
+
function waveColorIndex(x, y, t, size) {
|
|
38
|
+
const v = Math.sin((x + y) * 0.15 + t)
|
|
39
|
+
return Math.round(((v + 1) / 2) * (size - 1))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function renderWaveLine(line, y, t, palette) {
|
|
43
|
+
let out = ''
|
|
44
|
+
let last = -1
|
|
45
|
+
for (let x = 0; x < line.length; x++) {
|
|
46
|
+
const ch = line[x]
|
|
47
|
+
if (ch === ' ') {
|
|
48
|
+
out += ' '
|
|
49
|
+
last = -1
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
const idx = waveColorIndex(x, y, t, palette.length)
|
|
53
|
+
if (idx !== last) {
|
|
54
|
+
out += palette[idx]
|
|
55
|
+
last = idx
|
|
56
|
+
}
|
|
57
|
+
out += ch
|
|
58
|
+
}
|
|
59
|
+
return out + RESET
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function stopBanner() {
|
|
63
|
+
if (activeBanner) activeBanner.stop()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function showBanner(delay = 80, duration = 20000) {
|
|
67
|
+
const out = process.stdout
|
|
68
|
+
|
|
69
|
+
if (!out.isTTY) {
|
|
70
|
+
for (const line of ART) out.write(line ? `${CYAN}${line}${RESET}\n` : '\n')
|
|
71
|
+
out.write('\n')
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
stopBanner()
|
|
76
|
+
const state = { below: 0, stopped: false, hidden: false }
|
|
77
|
+
const restoreCursor = () => {
|
|
78
|
+
if (state.hidden) {
|
|
79
|
+
out.write('\x1b[?25h')
|
|
80
|
+
state.hidden = false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const stop = () => {
|
|
84
|
+
if (state.stopped) return
|
|
85
|
+
state.stopped = true
|
|
86
|
+
process.removeListener('SIGINT', onSigint)
|
|
87
|
+
restoreCursor()
|
|
88
|
+
if (activeBanner === state) activeBanner = null
|
|
89
|
+
}
|
|
90
|
+
const onSigint = () => {
|
|
91
|
+
stop()
|
|
92
|
+
process.exit(0)
|
|
93
|
+
}
|
|
94
|
+
state.stop = stop
|
|
95
|
+
activeBanner = state
|
|
96
|
+
|
|
97
|
+
;(async () => {
|
|
98
|
+
out.write('\x1b[?25l')
|
|
99
|
+
state.hidden = true
|
|
100
|
+
process.once('SIGINT', onSigint)
|
|
101
|
+
const deadline = Date.now() + duration
|
|
102
|
+
let f = 0
|
|
103
|
+
try {
|
|
104
|
+
while (!state.stopped && Date.now() < deadline) {
|
|
105
|
+
if (f > 0) out.write(`\x1b[${ART.length + state.below}A`)
|
|
106
|
+
for (let y = 0; y < ART.length; y++) {
|
|
107
|
+
const line = ART[y]
|
|
108
|
+
const palette = y >= TEXT_START ? TEXT_PALETTE : CUP_PALETTE
|
|
109
|
+
out.write(`\x1b[2K${line ? renderWaveLine(line, y, f * 0.6, palette) : ''}\n`)
|
|
110
|
+
}
|
|
111
|
+
if (state.below > 0) out.write(`\x1b[${state.below}B`)
|
|
112
|
+
if (state.stopped) break
|
|
113
|
+
f++
|
|
114
|
+
await sleep(delay)
|
|
115
|
+
}
|
|
116
|
+
} finally {
|
|
117
|
+
stop()
|
|
118
|
+
}
|
|
119
|
+
})()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function selectOption(message, options, initial = 0) {
|
|
123
|
+
const stdin = process.stdin
|
|
124
|
+
const out = process.stdout
|
|
125
|
+
const start = Math.min(Math.max(Number(initial) || 0, 0), options.length - 1)
|
|
126
|
+
if (!stdin.isTTY || typeof stdin.setRawMode !== 'function') return options[start]
|
|
127
|
+
|
|
128
|
+
let index = start
|
|
129
|
+
let rendered = 0
|
|
130
|
+
const draw = () => {
|
|
131
|
+
if (rendered > 0) out.write(`\x1b[${rendered}A`)
|
|
132
|
+
let buf = ''
|
|
133
|
+
for (let i = 0; i < options.length; i++) {
|
|
134
|
+
const pointer = i === index ? `${CYAN}❯${RESET}` : ' '
|
|
135
|
+
const label = i === index ? `${BOLD}${options[i]}${RESET}` : options[i]
|
|
136
|
+
buf += `\x1b[2K ${pointer} ${label}\n`
|
|
137
|
+
}
|
|
138
|
+
out.write(buf)
|
|
139
|
+
rendered = options.length
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const SIGINT = Symbol('SIGINT')
|
|
143
|
+
let resolveKey
|
|
144
|
+
const key = new Promise((resolve) => { resolveKey = resolve })
|
|
145
|
+
const onData = (chunk) => {
|
|
146
|
+
const s = chunk.toString('utf8')
|
|
147
|
+
if (s === '\u0003') {
|
|
148
|
+
stopBanner()
|
|
149
|
+
resolveKey(SIGINT)
|
|
150
|
+
} else if (s === '\u001b[A') {
|
|
151
|
+
index = (index - 1 + options.length) % options.length
|
|
152
|
+
draw()
|
|
153
|
+
} else if (s === '\u001b[B') {
|
|
154
|
+
index = (index + 1) % options.length
|
|
155
|
+
draw()
|
|
156
|
+
} else if (s === '\r' || s === '\n') {
|
|
157
|
+
resolveKey(options[index])
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let result
|
|
162
|
+
try {
|
|
163
|
+
out.write(`${CYAN}?${RESET} ${BOLD}${message}${RESET}\n`)
|
|
164
|
+
stdin.setRawMode(true)
|
|
165
|
+
stdin.resume()
|
|
166
|
+
stdin.on('data', onData)
|
|
167
|
+
draw()
|
|
168
|
+
if (activeBanner) activeBanner.below += 1 + options.length
|
|
169
|
+
result = await key
|
|
170
|
+
out.write('\n')
|
|
171
|
+
if (activeBanner) activeBanner.below += 1
|
|
172
|
+
} catch {
|
|
173
|
+
result = undefined
|
|
174
|
+
} finally {
|
|
175
|
+
stdin.removeListener('data', onData)
|
|
176
|
+
try { stdin.setRawMode(false) } catch {}
|
|
177
|
+
stdin.pause()
|
|
178
|
+
}
|
|
179
|
+
if (result === SIGINT) process.exit(0)
|
|
180
|
+
return result === undefined ? options[start] : result
|
|
181
|
+
}
|