zames_pro 2.75.4 → 2.75.6
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 +25 -0
- package/README.md +12 -6
- package/dist/agent-loop.js +61 -1
- package/dist/browser.js +40 -8
- package/dist/commands.js +0 -1
- package/dist/config.js +8 -3
- package/dist/deepseek-ui.js +1 -3
- package/dist/i18n/common.js +13 -16
- package/dist/i18n/config.js +2 -2
- package/dist/i18n/help.js +6 -10
- package/dist/i18n/spinner.js +16 -5
- package/dist/index.js +0 -49
- package/dist/net-capture.js +43 -11
- package/dist/provider-handoff.js +0 -4
- package/dist/qwen-ui.js +0 -8
- package/dist/run-task.js +24 -1
- package/dist/shell.js +0 -6
- package/dist/system-prompt.js +52 -2
- package/dist/tools.js +33 -0
- package/dist/tui/AskQuestion.js +108 -0
- package/dist/tui/FullScreenApp.js +2 -1
- package/dist/tui/InputLine.js +0 -3
- package/dist/tui/StatusBar.js +5 -10
- package/dist/tui/TuiEditor.js +0 -43
- package/dist/tui/keys.js +0 -2
- package/dist/tui/run-ask.js +22 -0
- package/dist/tui/store.js +0 -2
- package/dist/tui/widgets.js +3 -9
- package/package.json +3 -2
- package/scripts/build-readme.mts +0 -101
- package/scripts/changelog-draft.mts +0 -93
- package/scripts/clean-dist.mts +0 -17
- package/scripts/coverage-gate.mts +0 -112
- package/scripts/ctd-live-driver.mts +0 -51
- package/scripts/ctd-live.tsx +0 -33
- package/scripts/escd-live-driver.mts +0 -37
- package/scripts/eslint-ts6.mts +0 -58
- package/scripts/inline-status-driver.mts +0 -31
- package/scripts/inline-status.tsx +0 -33
- package/scripts/mouse-churn.mts +0 -34
- package/scripts/qwen-models-smoke.mts +0 -117
- package/scripts/qwen-smoke.mts +0 -221
- package/scripts/release.mts +0 -199
- package/scripts/render-demo.mts +0 -154
- package/scripts/render-how-it-works.mts +0 -229
- package/scripts/ru-shot.mts +0 -20
- package/scripts/self-smoke.mts +0 -309
- package/scripts/tui-comp-shot.mts +0 -63
- package/scripts/tui-components.tsx +0 -62
- package/scripts/tui-config-driver.mts +0 -82
- package/scripts/tui-config-probe.tsx +0 -74
- package/scripts/tui-edit-driver.mts +0 -100
- package/scripts/tui-edit-probe.tsx +0 -38
- package/scripts/tui-editor-probe.tsx +0 -24
- package/scripts/tui-input-driver.mts +0 -54
- package/scripts/tui-input-shot.tsx +0 -59
- package/scripts/tui-log-driver.mts +0 -35
- package/scripts/tui-log-shot.tsx +0 -48
- package/scripts/tui-longpaste-driver.mts +0 -56
- package/scripts/tui-longpaste.tsx +0 -35
- package/scripts/tui-manyselect-shot.mts +0 -34
- package/scripts/tui-manyselect.tsx +0 -17
- package/scripts/tui-mouse-churn-probe.tsx +0 -28
- package/scripts/tui-paste-driver.mts +0 -88
- package/scripts/tui-paste-kinds.tsx +0 -76
- package/scripts/tui-pastetest.mts +0 -38
- package/scripts/tui-probe.tsx +0 -62
- package/scripts/tui-repro.tsx +0 -64
- package/scripts/tui-resize.mts +0 -70
- package/scripts/tui-scroll-driver.mts +0 -53
- package/scripts/tui-scroll-probe.tsx +0 -46
- package/scripts/tui-shot.mts +0 -106
- package/scripts/tui-status-driver.mts +0 -24
- package/scripts/tui-status-shot.tsx +0 -27
- package/scripts/tui-submit.tsx +0 -57
- package/scripts/tui-wheel-driver.mts +0 -61
- package/scripts/tui-wheel-probe.tsx +0 -48
- package/scripts/tui-widgets-driver.mts +0 -20
- package/scripts/tui-widgets.tsx +0 -44
package/scripts/qwen-smoke.mts
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
// Qwen (chat.qwen.ai) self-smoke: a REAL isolated QwenBrowser instance against
|
|
2
|
-
// the live site, to catch integration regressions (login, send, answer) that
|
|
3
|
-
// unit tests cannot.
|
|
4
|
-
//
|
|
5
|
-
// Isolation: HOME is pointed at a throwaway dir BEFORE importing any project
|
|
6
|
-
// module (they compute ~/.zames paths at load). The real Qwen profile
|
|
7
|
-
// (~/.zames/qwen-profile) is COPIED read-only into the temp HOME so the
|
|
8
|
-
// browser starts already signed in; the live profile is NEVER opened. The
|
|
9
|
-
// Playwright browser cache is symlinked read-only. Nothing in the operator's
|
|
10
|
-
// ~/.zames is written.
|
|
11
|
-
//
|
|
12
|
-
// Usage: npx tsx scripts/qwen-smoke.mts [--headed] [--no-reuse-profile]
|
|
13
|
-
// Exit code 0 = all scenarios passed; 2 = login failed (could not start).
|
|
14
|
-
|
|
15
|
-
import fs from 'fs'
|
|
16
|
-
import path from 'path'
|
|
17
|
-
import os from 'os'
|
|
18
|
-
|
|
19
|
-
const HEADED = process.argv.includes('--headed')
|
|
20
|
-
const REUSE_PROFILE = !process.argv.includes('--no-reuse-profile')
|
|
21
|
-
const REAL_HOME = os.homedir()
|
|
22
|
-
const PROFILE_SRC = path.join(REAL_HOME, '.zames', 'qwen-profile')
|
|
23
|
-
|
|
24
|
-
if (!process.env.ZAMES_QWEN_SMOKE_HOME) {
|
|
25
|
-
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'zames-qwen-smoke-'))
|
|
26
|
-
process.env.HOME = tmp
|
|
27
|
-
process.env.ZAMES_QWEN_SMOKE_HOME = tmp
|
|
28
|
-
fs.mkdirSync(path.join(tmp, '.zames'), { recursive: true })
|
|
29
|
-
fs.mkdirSync(path.join(tmp, '.cache'), { recursive: true })
|
|
30
|
-
const realCfg = path.join(REAL_HOME, '.zames', 'config.json')
|
|
31
|
-
if (fs.existsSync(realCfg)) {
|
|
32
|
-
fs.copyFileSync(realCfg, path.join(tmp, '.zames', 'config.json'))
|
|
33
|
-
}
|
|
34
|
-
const realPw = path.join(REAL_HOME, '.cache', 'ms-playwright')
|
|
35
|
-
if (fs.existsSync(realPw)) {
|
|
36
|
-
fs.symlinkSync(realPw, path.join(tmp, '.cache', 'ms-playwright'))
|
|
37
|
-
}
|
|
38
|
-
if (REUSE_PROFILE && fs.existsSync(PROFILE_SRC)) {
|
|
39
|
-
fs.cpSync(PROFILE_SRC, path.join(tmp, '.zames', 'qwen-profile'), {
|
|
40
|
-
recursive: true,
|
|
41
|
-
filter: (src) => !/^Singleton/.test(path.basename(src)),
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
const SMOKE_HOME = process.env.ZAMES_QWEN_SMOKE_HOME as string
|
|
46
|
-
|
|
47
|
-
// --- project modules (dynamic: they read HOME at import) --------------------
|
|
48
|
-
const { QwenBrowser } = await import('../src/qwen-browser.ts')
|
|
49
|
-
const { loadConfig } = await import('../src/config.ts')
|
|
50
|
-
|
|
51
|
-
const cfg = loadConfig()
|
|
52
|
-
|
|
53
|
-
interface SmokeResult {
|
|
54
|
-
name: string
|
|
55
|
-
ok: boolean
|
|
56
|
-
note?: string
|
|
57
|
-
}
|
|
58
|
-
const results: SmokeResult[] = []
|
|
59
|
-
let loginFailed = false
|
|
60
|
-
const record = (name: string, ok: boolean, note?: string): void => {
|
|
61
|
-
results.push({ name, ok, note })
|
|
62
|
-
console.log((ok ? 'PASS ' : 'FAIL ') + name + (note ? ' — ' + note : ''))
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const browser = new QwenBrowser({
|
|
66
|
-
headless: !HEADED,
|
|
67
|
-
debug: process.env.ZAMES_QWEN_SMOKE_DEBUG === '1',
|
|
68
|
-
channel: cfg.browserChannel,
|
|
69
|
-
auth: cfg.browser.auth,
|
|
70
|
-
locale: 'ru',
|
|
71
|
-
minSendIntervalMs: 0,
|
|
72
|
-
answerTimeoutMs: 180000,
|
|
73
|
-
webSearch: false,
|
|
74
|
-
deepThinking: false,
|
|
75
|
-
})
|
|
76
|
-
browser.onNotice = (t: string) => console.log(' [notice] ' + t)
|
|
77
|
-
|
|
78
|
-
async function main() {
|
|
79
|
-
console.log(
|
|
80
|
-
'qwen-smoke: HOME=' +
|
|
81
|
-
SMOKE_HOME +
|
|
82
|
-
' headed=' +
|
|
83
|
-
HEADED +
|
|
84
|
-
' reuse-profile=' +
|
|
85
|
-
REUSE_PROFILE,
|
|
86
|
-
)
|
|
87
|
-
await browser.launch()
|
|
88
|
-
|
|
89
|
-
// Bound the login wait so a cold/expired session fails fast with a clear
|
|
90
|
-
// hint instead of hanging until the caller's tool timeout.
|
|
91
|
-
const loginPromise = browser.waitForLogin()
|
|
92
|
-
const timedOut = await Promise.race([
|
|
93
|
-
loginPromise.then(() => false),
|
|
94
|
-
new Promise<boolean>((r) => setTimeout(() => r(true), 60000)),
|
|
95
|
-
])
|
|
96
|
-
if (timedOut) {
|
|
97
|
-
loginFailed = true
|
|
98
|
-
record('login', false, 'timed out after 60s')
|
|
99
|
-
} else {
|
|
100
|
-
const logged = await browser.isLoggedIn().catch(() => false)
|
|
101
|
-
record('login', logged, logged ? '' : 'not signed in (guest)')
|
|
102
|
-
if (!logged) loginFailed = true
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (!loginFailed) {
|
|
106
|
-
// 1) new chat + a SIMPLE ask that needs no tools: the model must answer.
|
|
107
|
-
try {
|
|
108
|
-
await browser.newChat()
|
|
109
|
-
const id = await browser.getCurrentChatId()
|
|
110
|
-
record('newChat', true, 'id=' + (id || '(none)'))
|
|
111
|
-
} catch (e) {
|
|
112
|
-
record('newChat', false, (e as Error).message)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
const ans = await browser.ask('Reply with exactly: PONG', {
|
|
117
|
-
agent: true,
|
|
118
|
-
})
|
|
119
|
-
const ok = /PONG/i.test(ans)
|
|
120
|
-
record('ask:simple', ok, ok ? '' : 'got: ' + ans.slice(0, 80))
|
|
121
|
-
} catch (e) {
|
|
122
|
-
record('ask:simple', false, (e as Error).message)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 2) a second send right after the first: guards the send-verify path (the
|
|
126
|
-
// "text entered but send button not pressed" regression).
|
|
127
|
-
try {
|
|
128
|
-
const ans = await browser.ask('Reply with exactly: PONG2', {
|
|
129
|
-
agent: true,
|
|
130
|
-
})
|
|
131
|
-
const ok = /PONG2/i.test(ans)
|
|
132
|
-
record('ask:second-send', ok, ok ? '' : 'got: ' + ans.slice(0, 80))
|
|
133
|
-
} catch (e) {
|
|
134
|
-
record('ask:second-send', false, (e as Error).message)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// 3) model catalogue + token usage (best-effort, non-fatal).
|
|
138
|
-
try {
|
|
139
|
-
const models = await browser.listModels()
|
|
140
|
-
record('listModels', models.length > 0, 'n=' + models.length)
|
|
141
|
-
} catch (e) {
|
|
142
|
-
record('listModels', false, (e as Error).message)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const usage = browser.getLastTokenUsage()
|
|
146
|
-
record('tokenUsage', usage !== null, 'usage=' + String(usage))
|
|
147
|
-
|
|
148
|
-
// 4) tool-call emission: the agent-loop depends on the model returning a
|
|
149
|
-
// parseable JSON tool call. Ask for one explicitly and check the PARSER
|
|
150
|
-
// (not the model's prose).
|
|
151
|
-
try {
|
|
152
|
-
const { parseToolCall } = await import('../src/answer-parse.ts')
|
|
153
|
-
const ans = await browser.ask(
|
|
154
|
-
'Call the Read tool for the file package.json. Reply with EXACTLY one ' +
|
|
155
|
-
'JSON tool-call object and nothing else: ' +
|
|
156
|
-
'{"tool":"Read","args":{"path":"..."}}',
|
|
157
|
-
{ agent: true },
|
|
158
|
-
)
|
|
159
|
-
const parsed = parseToolCall(ans)
|
|
160
|
-
const call = Array.isArray(parsed) ? parsed[0] : parsed
|
|
161
|
-
const ok = !!call && call.tool === 'Read'
|
|
162
|
-
record('toolCall', ok, ok ? '' : 'got: ' + ans.slice(0, 100))
|
|
163
|
-
} catch (e) {
|
|
164
|
-
record('toolCall', false, (e as Error).message)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// History restore: after the sends above the chat must have messages.
|
|
168
|
-
try {
|
|
169
|
-
const id = await browser.getCurrentChatId()
|
|
170
|
-
const msgs = id ? await browser.fetchChatMessages(id) : []
|
|
171
|
-
record(
|
|
172
|
-
'history',
|
|
173
|
-
msgs.length > 0,
|
|
174
|
-
'id=' + (id || '-') + ' msgs=' + msgs.length,
|
|
175
|
-
)
|
|
176
|
-
} catch (e) {
|
|
177
|
-
record('history', false, (e as Error).message)
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Attachments: a SIGNED-IN session uploads a file; a GUEST cannot, so a
|
|
181
|
-
// guest failure is reported as a SKIP (not a failure) — the guest limit is
|
|
182
|
-
// a site restriction, not a regression.
|
|
183
|
-
try {
|
|
184
|
-
const tmpFile = path.join(SMOKE_HOME, 'zames-smoke-note.txt')
|
|
185
|
-
fs.writeFileSync(tmpFile, 'The secret word is BANANA')
|
|
186
|
-
const failed = await browser._attachFiles([
|
|
187
|
-
{ path: tmpFile, name: 'zames-smoke-note.txt', mime: 'text/plain' },
|
|
188
|
-
])
|
|
189
|
-
const attached = failed.length === 0
|
|
190
|
-
const isGuest = !(await browser.isLoggedIn().catch(() => false))
|
|
191
|
-
if (attached) record('attachments', true, '')
|
|
192
|
-
else if (isGuest)
|
|
193
|
-
record('attachments', true, 'skipped (guest cannot upload)')
|
|
194
|
-
else record('attachments', false, JSON.stringify(failed))
|
|
195
|
-
} catch (e) {
|
|
196
|
-
record('attachments', false, (e as Error).message)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
await browser.close().catch(() => {})
|
|
201
|
-
|
|
202
|
-
const failed = results.filter((r) => !r.ok)
|
|
203
|
-
console.log(
|
|
204
|
-
'\nqwen-smoke: ' +
|
|
205
|
-
(results.length - failed.length) +
|
|
206
|
-
'/' +
|
|
207
|
-
results.length +
|
|
208
|
-
' passed',
|
|
209
|
-
)
|
|
210
|
-
if (!process.env.ZAMES_QWEN_SMOKE_KEEP) {
|
|
211
|
-
fs.rmSync(SMOKE_HOME, { recursive: true, force: true })
|
|
212
|
-
} else {
|
|
213
|
-
console.log('kept HOME: ' + SMOKE_HOME)
|
|
214
|
-
}
|
|
215
|
-
process.exit(loginFailed ? 2 : failed.length ? 1 : 0)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
main().catch((e) => {
|
|
219
|
-
console.error('qwen-smoke crashed:', (e as Error).message)
|
|
220
|
-
process.exit(1)
|
|
221
|
-
})
|
package/scripts/release.mts
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process'
|
|
2
|
-
import fs from 'node:fs'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { fileURLToPath } from 'node:url'
|
|
5
|
-
import {
|
|
6
|
-
parseConventionalCommit,
|
|
7
|
-
groupCommits,
|
|
8
|
-
renderChangelogDraft,
|
|
9
|
-
} from '../src/changelog.js'
|
|
10
|
-
|
|
11
|
-
// One-command release prep (BACKLOG G4). The manual sequence — bump
|
|
12
|
-
// package.json, write a dated CHANGELOG section, commit `chore: release X.Y.Z`,
|
|
13
|
-
// tag — is easy to get wrong, so this script does the deterministic parts and
|
|
14
|
-
// STOPS before the push. Pushing the branch and the tag is deliberately a
|
|
15
|
-
// separate, explicit step: the tag triggers the npm publish workflow, and that
|
|
16
|
-
// must never happen by accident.
|
|
17
|
-
//
|
|
18
|
-
// Usage:
|
|
19
|
-
// npm run release patch # bug fixes
|
|
20
|
-
// npm run release minor # new features
|
|
21
|
-
// npm run release major # breaking changes
|
|
22
|
-
// npm run release patch --no-tests # skip the local test run (CI still runs)
|
|
23
|
-
// npm run release patch --dry-run # print the plan, change nothing
|
|
24
|
-
//
|
|
25
|
-
// Run via tsx so it can reuse src/changelog.ts (the conventional-commit
|
|
26
|
-
// parser) — a plain .mjs cannot import the TypeScript sources. The other
|
|
27
|
-
// helper scripts are .mts for the same reason; postinstall stays .mjs because
|
|
28
|
-
// it runs before tsx is guaranteed to be installed.
|
|
29
|
-
|
|
30
|
-
const NL = String.fromCharCode(10)
|
|
31
|
-
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
32
|
-
|
|
33
|
-
function git(args: string[], allowFail = false): string {
|
|
34
|
-
const r = spawnSync('git', args, { cwd: root, encoding: 'utf-8' })
|
|
35
|
-
if (r.status !== 0 && !allowFail) {
|
|
36
|
-
console.error('git ' + args.join(' ') + ' failed:' + NL + (r.stderr || ''))
|
|
37
|
-
process.exit(1)
|
|
38
|
-
}
|
|
39
|
-
return (r.stdout || '').toString()
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function fail(msg: string): never {
|
|
43
|
-
console.error('release: ' + msg)
|
|
44
|
-
process.exit(1)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function run(cmd: string, args: string[]): void {
|
|
48
|
-
const r = spawnSync(cmd, args, {
|
|
49
|
-
cwd: root,
|
|
50
|
-
stdio: 'inherit',
|
|
51
|
-
shell: process.platform === 'win32',
|
|
52
|
-
})
|
|
53
|
-
if (r.status !== 0) fail(cmd + ' ' + args.join(' ') + ' exited ' + r.status)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const argv = process.argv.slice(2)
|
|
57
|
-
const flags = new Set(argv.filter((a) => a.startsWith('--')))
|
|
58
|
-
const bump = argv.find((a) => !a.startsWith('--'))
|
|
59
|
-
const dryRun = flags.has('--dry-run')
|
|
60
|
-
const skipTests = flags.has('--no-tests')
|
|
61
|
-
|
|
62
|
-
if (bump !== 'patch' && bump !== 'minor' && bump !== 'major') {
|
|
63
|
-
fail('usage: npm run release patch|minor|major [--no-tests] [--dry-run]')
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// 1. Clean tree: a release commit must contain ONLY the version bump and the
|
|
67
|
-
// changelog section, never half-finished work.
|
|
68
|
-
const dirty = git(['status', '--porcelain']).trim()
|
|
69
|
-
if (dirty) {
|
|
70
|
-
fail('working tree is not clean:' + NL + dirty)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// 2. Gates. The git hooks run these on commit/push too, but a release should
|
|
74
|
-
// fail EARLY, before package.json/CHANGELOG are touched.
|
|
75
|
-
if (!skipTests) {
|
|
76
|
-
run('npm', ['run', 'typecheck'])
|
|
77
|
-
run('npm', ['test'])
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 3. Compute the next version from package.json.
|
|
81
|
-
const pkgPath = path.join(root, 'package.json')
|
|
82
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as {
|
|
83
|
-
version: string
|
|
84
|
-
}
|
|
85
|
-
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(pkg.version)
|
|
86
|
-
if (!m) fail('package.json version is not a plain x.y.z: ' + pkg.version)
|
|
87
|
-
let [major, minor, patch] = [Number(m[1]), Number(m[2]), Number(m[3])]
|
|
88
|
-
if (bump === 'major') {
|
|
89
|
-
major += 1
|
|
90
|
-
minor = 0
|
|
91
|
-
patch = 0
|
|
92
|
-
} else if (bump === 'minor') {
|
|
93
|
-
minor += 1
|
|
94
|
-
patch = 0
|
|
95
|
-
} else {
|
|
96
|
-
patch += 1
|
|
97
|
-
}
|
|
98
|
-
const next = major + '.' + minor + '.' + patch
|
|
99
|
-
const tag = 'v' + next
|
|
100
|
-
|
|
101
|
-
if (git(['tag', '--list', tag]).trim()) {
|
|
102
|
-
fail('tag ' + tag + ' already exists')
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 4. Build the dated CHANGELOG section from conventional commits since the
|
|
106
|
-
// last tag. English commit subjects become the section body; the operator can
|
|
107
|
-
// still reword the section afterwards.
|
|
108
|
-
const latestTag = git(['describe', '--tags', '--abbrev=0'], true).trim()
|
|
109
|
-
const ref =
|
|
110
|
-
latestTag ||
|
|
111
|
-
git(['rev-list', '--max-parents=0', 'HEAD']).trim().split(NL).pop() ||
|
|
112
|
-
'HEAD'
|
|
113
|
-
const subjects = git([
|
|
114
|
-
'log',
|
|
115
|
-
'--no-merges',
|
|
116
|
-
'--pretty=format:%s',
|
|
117
|
-
ref + '..HEAD',
|
|
118
|
-
])
|
|
119
|
-
.split(NL)
|
|
120
|
-
.map((s) => s.trim())
|
|
121
|
-
.filter(Boolean)
|
|
122
|
-
const parsed = subjects
|
|
123
|
-
.map(parseConventionalCommit)
|
|
124
|
-
.filter((c): c is NonNullable<typeof c> => c !== null)
|
|
125
|
-
let body = renderChangelogDraft(groupCommits(parsed))
|
|
126
|
-
|
|
127
|
-
// The conventional draft skips chore/docs/test/etc, so a release whose only
|
|
128
|
-
// change is a docs fix (a README tweak users WILL see on the npm page) would
|
|
129
|
-
// otherwise collapse to a useless "Internal improvements." placeholder. Fall
|
|
130
|
-
// back to the raw commit subjects under Changed instead of that stub.
|
|
131
|
-
if (!body) {
|
|
132
|
-
const fallback = subjects.filter((s) => !/^chore(:|\(release\))/i.test(s))
|
|
133
|
-
if (fallback.length) {
|
|
134
|
-
body = ['## Changed', ''].concat(fallback.map((s) => '- ' + s)).join(NL)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// CHANGELOG groups are `### Added` (Keep a Changelog), the draft renders
|
|
139
|
-
// `## Added` — normalize here.
|
|
140
|
-
const sectionBody = body
|
|
141
|
-
.split(NL)
|
|
142
|
-
.map((line) => (line.startsWith('## ') ? '#' + line : line))
|
|
143
|
-
.join(NL)
|
|
144
|
-
const date = new Date().toISOString().slice(0, 10)
|
|
145
|
-
const section =
|
|
146
|
-
'## [' +
|
|
147
|
-
next +
|
|
148
|
-
'] - ' +
|
|
149
|
-
date +
|
|
150
|
-
NL +
|
|
151
|
-
NL +
|
|
152
|
-
(sectionBody || '### Changed' + NL + NL + '- Internal improvements.') +
|
|
153
|
-
NL
|
|
154
|
-
|
|
155
|
-
if (dryRun) {
|
|
156
|
-
console.log('release(dry-run): ' + pkg.version + ' -> ' + next)
|
|
157
|
-
console.log('tag: ' + tag)
|
|
158
|
-
console.log('since: ' + ref)
|
|
159
|
-
console.log('CHANGELOG section:' + NL + section)
|
|
160
|
-
process.exit(0)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// 5. Insert the version section in place of the Unreleased BODY. The
|
|
164
|
-
// Unreleased body is auto-synced from commits (see changelog:sync), so it is
|
|
165
|
-
// the same content as the new section — leaving it in place duplicated every
|
|
166
|
-
// entry (the real "### Fixed" block twice). Replace the whole body up to the
|
|
167
|
-
// next version heading with the new section.
|
|
168
|
-
const changelogPath = path.join(root, 'CHANGELOG.md')
|
|
169
|
-
const changelog = fs.readFileSync(changelogPath, 'utf-8')
|
|
170
|
-
const anchor = '## [Unreleased]'
|
|
171
|
-
const idx = changelog.indexOf(anchor)
|
|
172
|
-
if (idx < 0) fail('CHANGELOG.md has no "' + anchor + '" heading')
|
|
173
|
-
const afterAnchor = idx + anchor.length
|
|
174
|
-
const rest = changelog.slice(afterAnchor)
|
|
175
|
-
const nextHeading = rest.search(/\n## \[/)
|
|
176
|
-
const tail = nextHeading < 0 ? NL : rest.slice(nextHeading)
|
|
177
|
-
const updated = changelog.slice(0, afterAnchor) + NL + NL + section + tail
|
|
178
|
-
fs.writeFileSync(changelogPath, updated)
|
|
179
|
-
|
|
180
|
-
// 6. Bump package.json, preserving its exact formatting (single-line replace
|
|
181
|
-
// of the version value only).
|
|
182
|
-
const pkgText = fs.readFileSync(pkgPath, 'utf-8')
|
|
183
|
-
fs.writeFileSync(
|
|
184
|
-
pkgPath,
|
|
185
|
-
pkgText.replace(
|
|
186
|
-
'"version": "' + pkg.version + '"',
|
|
187
|
-
'"version": "' + next + '"',
|
|
188
|
-
),
|
|
189
|
-
)
|
|
190
|
-
|
|
191
|
-
// 7. Commit + tag. The push is intentionally NOT done here.
|
|
192
|
-
git(['add', 'package.json', 'CHANGELOG.md'])
|
|
193
|
-
git(['commit', '-m', 'chore: release ' + next])
|
|
194
|
-
git(['tag', tag])
|
|
195
|
-
|
|
196
|
-
console.log(NL + 'release: committed and tagged ' + tag)
|
|
197
|
-
console.log('Next (explicit) step — publish via CI:')
|
|
198
|
-
console.log(' git push')
|
|
199
|
-
console.log(' git push origin ' + tag)
|
package/scripts/render-demo.mts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
// Render docs/demo.png — the terminal screenshot used in README.md.
|
|
2
|
-
//
|
|
3
|
-
// Why a generator instead of a live screenshot: the demo must be
|
|
4
|
-
// REPRODUCIBLE and ENGLISH (the README is English). A live capture depends on
|
|
5
|
-
// the operator's locale/theme/font and would drift. This renders a fixed HTML
|
|
6
|
-
// page that mirrors the REAL editor layout (src/input.ts _writeBlock) and the
|
|
7
|
-
// theme palette (src/theme.ts) with the DejaVu Sans Mono font that the
|
|
8
|
-
// container actually ships, then screenshots it with headless Chromium.
|
|
9
|
-
//
|
|
10
|
-
// Run: npm run render-demo (tsx scripts/render-demo.mts)
|
|
11
|
-
//
|
|
12
|
-
// Isolation: chromium.launch() uses a THROWAWAY profile, never ~/.zames/profile
|
|
13
|
-
// (see AGENTS.md — never touch the live agent's browser/profile).
|
|
14
|
-
|
|
15
|
-
import { chromium } from 'playwright'
|
|
16
|
-
import path from 'node:path'
|
|
17
|
-
import { fileURLToPath } from 'node:url'
|
|
18
|
-
|
|
19
|
-
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
20
|
-
const out = path.join(root, 'docs', 'demo.png')
|
|
21
|
-
|
|
22
|
-
const C = {
|
|
23
|
-
bg: '#23272e',
|
|
24
|
-
user: '#8fd4f0',
|
|
25
|
-
prompt: '#c8b06a',
|
|
26
|
-
dir: '#7fc4f0',
|
|
27
|
-
assistant: '#cfc9b0',
|
|
28
|
-
tool: '#c6a97e',
|
|
29
|
-
toolResult: '#8a9bb5',
|
|
30
|
-
system: '#808896',
|
|
31
|
-
dim: '#5b616e',
|
|
32
|
-
taskSummary: '#8fa3c8',
|
|
33
|
-
toggleOn: '#6fd0b0',
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const esc = (s: unknown): string =>
|
|
37
|
-
String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
|
|
38
|
-
const span = (color: string, text: string, bold = false): string =>
|
|
39
|
-
'<span style="color:' +
|
|
40
|
-
color +
|
|
41
|
-
(bold ? ';font-weight:700' : '') +
|
|
42
|
-
'">' +
|
|
43
|
-
esc(text) +
|
|
44
|
-
'</span>'
|
|
45
|
-
|
|
46
|
-
// One rendered session, line by line (mirrors the real banner + answer flow).
|
|
47
|
-
const lines: string[] = []
|
|
48
|
-
const push = (html: string): void => {
|
|
49
|
-
lines.push('<div class="l">' + html + '</div>')
|
|
50
|
-
}
|
|
51
|
-
const blank = () => push(' ')
|
|
52
|
-
|
|
53
|
-
push(span(C.system, 'Working directory: ') + span(C.dir, '/home/dev/zames_pro'))
|
|
54
|
-
push(span(C.system, 'Version: ') + span(C.user, '2.64.1'))
|
|
55
|
-
push(
|
|
56
|
-
span(C.system, 'Transcript: ') +
|
|
57
|
-
span(C.dim, '~/.zames/logs/zames_pro-2026-10-07.log'),
|
|
58
|
-
)
|
|
59
|
-
blank()
|
|
60
|
-
push(
|
|
61
|
-
span(
|
|
62
|
-
C.system,
|
|
63
|
-
'Interactive mode. Enter a task. Commands — /help. Exit — /exit.',
|
|
64
|
-
),
|
|
65
|
-
)
|
|
66
|
-
push(
|
|
67
|
-
span(
|
|
68
|
-
C.system,
|
|
69
|
-
'While the agent works you can type the next message — it goes to the queue (Enter — send, Esc — abort).',
|
|
70
|
-
),
|
|
71
|
-
)
|
|
72
|
-
push(
|
|
73
|
-
span(
|
|
74
|
-
C.dim,
|
|
75
|
-
'Hint: "/" lists commands, Ctrl+R searches history, paste an image with Ctrl+Shift+V.',
|
|
76
|
-
),
|
|
77
|
-
)
|
|
78
|
-
blank()
|
|
79
|
-
push(
|
|
80
|
-
span(C.prompt, '❯ ', true) +
|
|
81
|
-
span(C.dir, 'zames_pro') +
|
|
82
|
-
span(C.dim, ' › ') +
|
|
83
|
-
span(C.user, 'Show the version from package.json.'),
|
|
84
|
-
)
|
|
85
|
-
blank()
|
|
86
|
-
push(span(C.tool, '🔧 Read') + ' ' + span(C.dim, '{"path":"package.json"}'))
|
|
87
|
-
push(
|
|
88
|
-
span(C.toolResult, ' → ') +
|
|
89
|
-
span(
|
|
90
|
-
C.toolResult,
|
|
91
|
-
'{\n "name": "zames_pro",\n "version": "2.64.1", …\n}',
|
|
92
|
-
),
|
|
93
|
-
)
|
|
94
|
-
blank()
|
|
95
|
-
push(span(C.assistant, '● Answer'))
|
|
96
|
-
push(
|
|
97
|
-
span(C.assistant, 'The version in ') +
|
|
98
|
-
'<span style="color:' +
|
|
99
|
-
C.assistant +
|
|
100
|
-
';background:#2c313a;padding:0 3px;border-radius:3px">package.json</span>' +
|
|
101
|
-
span(C.assistant, ' is ') +
|
|
102
|
-
span(C.assistant, '2.64.1', true) +
|
|
103
|
-
span(C.assistant, '.'),
|
|
104
|
-
)
|
|
105
|
-
blank()
|
|
106
|
-
push(span(C.taskSummary, '· duration: 6s · tools: 1 · tokens: 1.2k'))
|
|
107
|
-
push(span(C.dim, '─'.repeat(78)))
|
|
108
|
-
|
|
109
|
-
// Status line (right-aligned context + toggles) and the permanent input line.
|
|
110
|
-
const status =
|
|
111
|
-
'<div class="l status"><span class="grow"></span>' +
|
|
112
|
-
span(C.toggleOn, '🧠 ') +
|
|
113
|
-
span(C.toggleOn, '🌐 ') +
|
|
114
|
-
span(C.system, 'ctx: 12.4k · 1.2%') +
|
|
115
|
-
'</div>'
|
|
116
|
-
const input =
|
|
117
|
-
'<div class="l">' +
|
|
118
|
-
span(C.prompt, '❯ ', true) +
|
|
119
|
-
span(C.dir, 'zames_pro') +
|
|
120
|
-
span(C.dim, ' › ')
|
|
121
|
-
|
|
122
|
-
const html =
|
|
123
|
-
'<!doctype html><html><head><meta charset="utf-8"><style>' +
|
|
124
|
-
'@font-face{font-family:ZM;src:local("DejaVu Sans Mono")}' +
|
|
125
|
-
'html,body{margin:0;padding:0;background:' +
|
|
126
|
-
C.bg +
|
|
127
|
-
'}' +
|
|
128
|
-
'body{font-family:"DejaVu Sans Mono","Noto Color Emoji",monospace;font-size:15px;line-height:1.45;' +
|
|
129
|
-
'color:' +
|
|
130
|
-
C.system +
|
|
131
|
-
';-webkit-font-smoothing:antialiased}' +
|
|
132
|
-
'.screen{box-sizing:border-box;width:1197px;height:562px;padding:14px 18px;overflow:hidden;' +
|
|
133
|
-
'display:flex;flex-direction:column}' +
|
|
134
|
-
'.l{white-space:pre-wrap;word-break:break-word}' +
|
|
135
|
-
'.status{display:flex;align-items:center;margin-top:auto}' +
|
|
136
|
-
'.grow{flex:1}' +
|
|
137
|
-
'</style></head><body><div class="screen">' +
|
|
138
|
-
lines.join('') +
|
|
139
|
-
status +
|
|
140
|
-
input +
|
|
141
|
-
'</div></body></html>'
|
|
142
|
-
|
|
143
|
-
const browser = await chromium.launch({ headless: true })
|
|
144
|
-
try {
|
|
145
|
-
const page = await browser.newPage({
|
|
146
|
-
viewport: { width: 1197, height: 562 },
|
|
147
|
-
deviceScaleFactor: 2,
|
|
148
|
-
})
|
|
149
|
-
await page.setContent(html, { waitUntil: 'load' })
|
|
150
|
-
await page.screenshot({ path: out })
|
|
151
|
-
console.log('wrote', path.relative(root, out))
|
|
152
|
-
} finally {
|
|
153
|
-
await browser.close()
|
|
154
|
-
}
|