zames_pro 2.72.3 → 2.72.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/CHANGELOG.md +20 -0
- package/dist/config-commands.js +75 -0
- package/dist/index.js +6 -51
- package/dist/input/suggest.js +81 -0
- package/dist/input.js +11 -61
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.72.5] - 2026-10-09
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **input:** extract the pure slash-command suggestion logic into input/suggest.ts (C3)
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- **input:** extract the pure slash-command suggestion logic into input/suggest.ts (C3)
|
|
19
|
+
|
|
20
|
+
## [2.72.4] - 2026-10-09
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- **index:** extract the pure /config rendering into config-commands.ts (C3)
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **index:** extract the pure /config rendering into config-commands.ts (C3)
|
|
29
|
+
|
|
10
30
|
## [2.72.3] - 2026-10-09
|
|
11
31
|
|
|
12
32
|
### Changed
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// PURE rendering for the /config command's TEXT output (BACKLOG C3). These
|
|
2
|
+
// helpers format the schema into lines; they touch no browser/terminal state,
|
|
3
|
+
// so they are unit-tested without a live agent (the /config dispatch in
|
|
4
|
+
// index.ts only prints what these return). The interactive menu lives in
|
|
5
|
+
// config-menu.ts; the value read/write policy lives in config.ts.
|
|
6
|
+
import { theme } from './theme.js';
|
|
7
|
+
/**
|
|
8
|
+
* The value shown for a field in the LIST: the default marker, on/off for a
|
|
9
|
+
* boolean, a fixed mask for a password, otherwise the JSON form. Mirrors the
|
|
10
|
+
* long-used inline logic so the output is byte-for-byte identical.
|
|
11
|
+
*/
|
|
12
|
+
export function configListValue(field, value, t) {
|
|
13
|
+
if (value === undefined)
|
|
14
|
+
return t('cfg.menu.default');
|
|
15
|
+
if (typeof value === 'boolean')
|
|
16
|
+
return value ? t('common.on') : t('common.off');
|
|
17
|
+
if (/password/i.test(field.path) && String(value).length > 0)
|
|
18
|
+
return '********';
|
|
19
|
+
return JSON.stringify(value);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The value shown for `/config get <path>`: the default marker, a fixed mask
|
|
23
|
+
* for a password, otherwise the JSON form. Note this deliberately does NOT
|
|
24
|
+
* special-case booleans (the get subcommand has always printed true/false).
|
|
25
|
+
*/
|
|
26
|
+
export function configGetValue(path, value, t) {
|
|
27
|
+
if (value === undefined)
|
|
28
|
+
return t('cfg.menu.default');
|
|
29
|
+
if (/password/i.test(path) && String(value).length > 0)
|
|
30
|
+
return '********';
|
|
31
|
+
return JSON.stringify(value);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Human-readable hint of the accepted values for a field, used in the error
|
|
35
|
+
* message when /config set gets a bad value. Enum -> the allowed list,
|
|
36
|
+
* number -> a range, boolean -> "true|false".
|
|
37
|
+
*/
|
|
38
|
+
export function configValueHint(field, t) {
|
|
39
|
+
if (field.values && field.values.length)
|
|
40
|
+
return field.values.join('|');
|
|
41
|
+
if (field.type === 'boolean')
|
|
42
|
+
return 'true|false';
|
|
43
|
+
if (field.type === 'number') {
|
|
44
|
+
const lo = field.min !== undefined ? String(field.min) : '-';
|
|
45
|
+
const hi = field.max !== undefined ? String(field.max) : '-';
|
|
46
|
+
return t('cfg.range_hint', { min: lo, max: hi });
|
|
47
|
+
}
|
|
48
|
+
return field.type;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Render the full settings LIST as an array of lines (grouped by section).
|
|
52
|
+
* `get` reads the current value of a path (undefined = default). Returned as
|
|
53
|
+
* lines so index.ts only has to print them and the formatting is testable.
|
|
54
|
+
*/
|
|
55
|
+
export function formatConfigList(fields, get, t) {
|
|
56
|
+
const out = [theme.system(t('cfg.title'))];
|
|
57
|
+
let lastGroup = '';
|
|
58
|
+
for (const f of fields) {
|
|
59
|
+
const g = t(f.groupKey);
|
|
60
|
+
if (g !== lastGroup) {
|
|
61
|
+
out.push(theme.system('\n ' + g));
|
|
62
|
+
lastGroup = g;
|
|
63
|
+
}
|
|
64
|
+
const shown = configListValue(f, get(f.path), t);
|
|
65
|
+
const extra = f.values ? ' [' + f.values.join('|') + ']' : '';
|
|
66
|
+
out.push(' ' +
|
|
67
|
+
theme.user(f.path) +
|
|
68
|
+
theme.dim(' = ') +
|
|
69
|
+
theme.assistant(shown) +
|
|
70
|
+
theme.dim(extra) +
|
|
71
|
+
theme.dim(' # ' + t(f.labelKey)));
|
|
72
|
+
}
|
|
73
|
+
out.push(theme.dim('\n' + t('cfg.usage')));
|
|
74
|
+
return out;
|
|
75
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { promptOnce } from './prompt.js';
|
|
|
16
16
|
import { parseImagePaste, extForMime, saveToTemp, guessMime, formatSize, readClipboardImageDetailed, sniffMime, readWindowsClipboardFiles, hasClipboardTool, writeClipboardText, contentHash, } from './attachments.js';
|
|
17
17
|
import { loadConfig, DEFAULTS, CONFIG_PATHS, ZAMES_HOME, CONFIG_SCHEMA, getByPath, validateConfigValue, writeConfigValue, resetConfigValue, } from './config.js';
|
|
18
18
|
import { runConfigMenu } from './config-menu.js';
|
|
19
|
+
import { configGetValue, configValueHint, formatConfigList, } from './config-commands.js';
|
|
19
20
|
import { translate, normalizeLocale, localeDisplayName, isLocale, } from './i18n.js';
|
|
20
21
|
import { Transcript } from './transcript.js';
|
|
21
22
|
import { UndoStore } from './undo.js';
|
|
@@ -1528,55 +1529,16 @@ async function main() {
|
|
|
1528
1529
|
// Human-readable hint of the accepted values for a field, used in the error
|
|
1529
1530
|
// message when /config set gets a bad value. Enum -> the allowed list,
|
|
1530
1531
|
// number -> a range, boolean -> "true|false".
|
|
1531
|
-
function configValueHint(field) {
|
|
1532
|
-
if (field.values && field.values.length)
|
|
1533
|
-
return field.values.join('|');
|
|
1534
|
-
if (field.type === 'boolean')
|
|
1535
|
-
return 'true|false';
|
|
1536
|
-
if (field.type === 'number') {
|
|
1537
|
-
const lo = field.min !== undefined ? String(field.min) : '-';
|
|
1538
|
-
const hi = field.max !== undefined ? String(field.max) : '-';
|
|
1539
|
-
return t('cfg.range_hint', { min: lo, max: hi });
|
|
1540
|
-
}
|
|
1541
|
-
return field.type;
|
|
1542
|
-
}
|
|
1543
1532
|
function configResetField(field) {
|
|
1544
1533
|
resetConfigValue('home', field.path);
|
|
1545
1534
|
// Reset the runtime value to the default.
|
|
1546
1535
|
const def = getByPath(DEFAULTS, field.path);
|
|
1547
1536
|
setConfigRuntime(field.path, def);
|
|
1548
1537
|
}
|
|
1549
|
-
function configLabel(field) {
|
|
1550
|
-
return t(field.labelKey);
|
|
1551
|
-
}
|
|
1552
1538
|
function configShowList() {
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
const g = t(f.groupKey);
|
|
1557
|
-
if (g !== lastGroup) {
|
|
1558
|
-
console.log(theme.system('\n ' + g));
|
|
1559
|
-
lastGroup = g;
|
|
1560
|
-
}
|
|
1561
|
-
const cur = getByPath(config, f.path);
|
|
1562
|
-
const shown = cur === undefined
|
|
1563
|
-
? t('cfg.menu.default')
|
|
1564
|
-
: typeof cur === 'boolean'
|
|
1565
|
-
? cur
|
|
1566
|
-
? t('common.on')
|
|
1567
|
-
: t('common.off')
|
|
1568
|
-
: /password/i.test(f.path) && String(cur).length > 0
|
|
1569
|
-
? '********'
|
|
1570
|
-
: JSON.stringify(cur);
|
|
1571
|
-
const extra = f.values ? ' [' + f.values.join('|') + ']' : '';
|
|
1572
|
-
console.log(' ' +
|
|
1573
|
-
theme.user(f.path) +
|
|
1574
|
-
theme.dim(' = ') +
|
|
1575
|
-
theme.assistant(shown) +
|
|
1576
|
-
theme.dim(extra) +
|
|
1577
|
-
theme.dim(' # ' + configLabel(f)));
|
|
1578
|
-
}
|
|
1579
|
-
console.log(theme.dim('\n' + t('cfg.usage')));
|
|
1539
|
+
for (const line of formatConfigList(CONFIG_SCHEMA, (p) => getByPath(config, p), t)) {
|
|
1540
|
+
console.log(line);
|
|
1541
|
+
}
|
|
1580
1542
|
}
|
|
1581
1543
|
async function configOpenMenu() {
|
|
1582
1544
|
if (!(process.stdin.isTTY && process.stdout.isTTY)) {
|
|
@@ -1657,14 +1619,7 @@ async function main() {
|
|
|
1657
1619
|
return;
|
|
1658
1620
|
}
|
|
1659
1621
|
const cur = getByPath(config, key);
|
|
1660
|
-
console.log(theme.system(t('cfg.value', {
|
|
1661
|
-
v: key,
|
|
1662
|
-
value: cur === undefined
|
|
1663
|
-
? t('cfg.menu.default')
|
|
1664
|
-
: /password/i.test(key) && String(cur).length > 0
|
|
1665
|
-
? '********'
|
|
1666
|
-
: JSON.stringify(cur),
|
|
1667
|
-
})));
|
|
1622
|
+
console.log(theme.system(t('cfg.value', { v: key, value: configGetValue(key, cur, t) })));
|
|
1668
1623
|
return;
|
|
1669
1624
|
}
|
|
1670
1625
|
if (sub === 'set') {
|
|
@@ -1685,7 +1640,7 @@ async function main() {
|
|
|
1685
1640
|
catch {
|
|
1686
1641
|
console.error(theme.error(t('cfg.bad_value', {
|
|
1687
1642
|
v: key,
|
|
1688
|
-
type: configValueHint(field),
|
|
1643
|
+
type: configValueHint(field, t),
|
|
1689
1644
|
})));
|
|
1690
1645
|
return;
|
|
1691
1646
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// PURE slash-command suggestion logic — the match / paging / completion half
|
|
2
|
+
// of the LineEditor's «/» hint list (src/input.ts). No terminal access and no
|
|
3
|
+
// editor state: given the buffer text and the command list these decide which
|
|
4
|
+
// commands to show, the visible window, and the Tab completion. Split out
|
|
5
|
+
// (C3) so the fuzzy match can be unit-tested without driving the editor.
|
|
6
|
+
import { SUGGEST_PAGE, isSubsequence } from './layout.js';
|
|
7
|
+
/**
|
|
8
|
+
* The commands matching the current buffer. Shown only when the line starts
|
|
9
|
+
* with «/» and is a single short word. The match is fuzzy: an exact prefix
|
|
10
|
+
* first, then a substring, then a subsequence (e.g. "hst" -> "/history"), so
|
|
11
|
+
* a half-remembered command name still surfaces. Order is preserved.
|
|
12
|
+
*/
|
|
13
|
+
export function matchSlashCommands(buf, commands) {
|
|
14
|
+
const b = buf;
|
|
15
|
+
if (!b.startsWith('/'))
|
|
16
|
+
return [];
|
|
17
|
+
if (b.includes(String.fromCharCode(10)) || b.includes(' '))
|
|
18
|
+
return [];
|
|
19
|
+
if (b.length > 40)
|
|
20
|
+
return [];
|
|
21
|
+
const q = b.toLowerCase();
|
|
22
|
+
if (q === '/')
|
|
23
|
+
return commands;
|
|
24
|
+
// Match on the part AFTER the leading slash: comparing the raw query (which
|
|
25
|
+
// itself starts with '/') against the full name made the fuzzy subsequence
|
|
26
|
+
// match fire on the slash alone (e.g. "/x" matched "/self-fix").
|
|
27
|
+
const body = q.slice(1);
|
|
28
|
+
const prefix = [];
|
|
29
|
+
const substring = [];
|
|
30
|
+
const fuzzy = [];
|
|
31
|
+
// A single character only matches by PREFIX: a one-letter substring /
|
|
32
|
+
// subsequence match is far too loose ("x" would pull in "/self-fix").
|
|
33
|
+
const loose = body.length >= 2;
|
|
34
|
+
for (const c of commands) {
|
|
35
|
+
const name = c.name.toLowerCase().slice(1);
|
|
36
|
+
if (name.startsWith(body))
|
|
37
|
+
prefix.push(c);
|
|
38
|
+
else if (loose && name.includes(body))
|
|
39
|
+
substring.push(c);
|
|
40
|
+
else if (loose && isSubsequence(body, name))
|
|
41
|
+
fuzzy.push(c);
|
|
42
|
+
}
|
|
43
|
+
return prefix.concat(substring, fuzzy);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The visible slice of the suggestion list for a given scroll offset. Returns
|
|
47
|
+
* the shown commands, the CLAMPED offset and how many rows the block takes
|
|
48
|
+
* (the page plus an optional "more" line). Pure — the caller renders the rows.
|
|
49
|
+
*/
|
|
50
|
+
export function suggestWindow(sugg, offset) {
|
|
51
|
+
if (!sugg.length)
|
|
52
|
+
return { shown: [], off: 0, moreCount: 0 };
|
|
53
|
+
const pageSize = SUGGEST_PAGE;
|
|
54
|
+
const maxOffset = Math.max(0, sugg.length - pageSize);
|
|
55
|
+
const off = Math.max(0, Math.min(offset, maxOffset));
|
|
56
|
+
const shown = sugg.slice(off, off + pageSize);
|
|
57
|
+
const moreCount = sugg.length > pageSize ? sugg.length - shown.length : 0;
|
|
58
|
+
return { shown, off, moreCount };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Tab completion. With an explicit selection, insert that command. With a
|
|
62
|
+
* single match, insert it whole. Otherwise complete up to the longest common
|
|
63
|
+
* prefix of the matches. Returns the new buffer, or null when nothing should
|
|
64
|
+
* change.
|
|
65
|
+
*/
|
|
66
|
+
export function completeSlashCommand(buf, sugg, selected) {
|
|
67
|
+
if (!sugg.length)
|
|
68
|
+
return null;
|
|
69
|
+
if (selected >= 0 && selected < sugg.length) {
|
|
70
|
+
return sugg[selected].name + ' ';
|
|
71
|
+
}
|
|
72
|
+
if (sugg.length === 1)
|
|
73
|
+
return sugg[0].name + ' ';
|
|
74
|
+
let prefix = sugg[0].name;
|
|
75
|
+
for (const c of sugg) {
|
|
76
|
+
while (!c.name.toLowerCase().startsWith(prefix.toLowerCase())) {
|
|
77
|
+
prefix = prefix.slice(0, -1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return prefix.length > buf.length ? prefix : null;
|
|
81
|
+
}
|
package/dist/input.js
CHANGED
|
@@ -4,12 +4,15 @@ import { contentWidth } from './width.js';
|
|
|
4
4
|
import { randomThinkingPhrase, stripEllipsis, DOTS, renderDots, } from './spinner.js';
|
|
5
5
|
import { translate } from './i18n.js';
|
|
6
6
|
import { AttachmentStore, parseImagePaste, looksLikeFilePath, } from './attachments.js';
|
|
7
|
-
import { NL, CR, ESC, PASTE_START, PASTE_END, SUGGEST_PAGE,
|
|
7
|
+
import { NL, CR, ESC, PASTE_START, PASTE_END, SUGGEST_PAGE, pasteReplacement, expandPastes, safeJson, visRows, visLen, charWidth, layoutInput, formatTokenStatus, tokenStatusLevel, truncateToWidth, wrapToWidth, CONTEXT_LIMIT, } from './input/layout.js';
|
|
8
8
|
// The pure layout/formatting helpers (visual rows, cursor position, visible
|
|
9
9
|
// width, paste markers, token formatting) live in ./input/layout.js. They are
|
|
10
10
|
// re-exported here so every existing import from './input.js' keeps working,
|
|
11
11
|
// and so tests can import them from either module (C3 step 3).
|
|
12
12
|
export { PASTE_MIN_LINES, SUGGEST_PAGE, isSubsequence, countPasteLines, formatPasteMarker, pasteReplacement, expandPastes, visRows, visLen, charWidth, layoutInput, tokenStatusLevel, formatTokenStatus, formatCompactTokens, truncateToWidth, wrapToWidth, CONTEXT_LIMIT, CONTEXT_YELLOW_PCT, CONTEXT_RED_PCT, } from './input/layout.js';
|
|
13
|
+
// PURE slash-command suggestion logic (match / paging / completion), split out
|
|
14
|
+
// of the class so the fuzzy match is unit-tested without driving the editor.
|
|
15
|
+
import { matchSlashCommands, suggestWindow, completeSlashCommand, } from './input/suggest.js';
|
|
13
16
|
// How long a lone ESC is held before it is treated as the Escape key (and not
|
|
14
17
|
// as the head of a control sequence split across two reads). 40ms is below a
|
|
15
18
|
// human "two Escapes" interval but above typical chunk-split latency.
|
|
@@ -388,37 +391,7 @@ export class LineEditor {
|
|
|
388
391
|
// first, then a substring, then a subsequence (e.g. "hst" -> "/history"),
|
|
389
392
|
// so a half-remembered command name still surfaces. Order is preserved.
|
|
390
393
|
_suggestions() {
|
|
391
|
-
|
|
392
|
-
if (!b.startsWith('/'))
|
|
393
|
-
return [];
|
|
394
|
-
if (b.includes(NL) || b.includes(' '))
|
|
395
|
-
return [];
|
|
396
|
-
if (b.length > 40)
|
|
397
|
-
return [];
|
|
398
|
-
const q = b.toLowerCase();
|
|
399
|
-
if (q === '/')
|
|
400
|
-
return this.slashCommands;
|
|
401
|
-
// Match on the part AFTER the leading slash: comparing the raw query
|
|
402
|
-
// (which itself starts with '/') against the full name made the fuzzy
|
|
403
|
-
// subsequence match fire on the slash alone (e.g. "/x" matched
|
|
404
|
-
// "/self-fix"), so a stray letter pulled in unrelated commands.
|
|
405
|
-
const body = q.slice(1);
|
|
406
|
-
const prefix = [];
|
|
407
|
-
const substring = [];
|
|
408
|
-
const fuzzy = [];
|
|
409
|
-
// A single character only matches by PREFIX: a one-letter substring /
|
|
410
|
-
// subsequence match is far too loose ("x" would pull in "/self-fix").
|
|
411
|
-
const loose = body.length >= 2;
|
|
412
|
-
for (const c of this.slashCommands) {
|
|
413
|
-
const name = c.name.toLowerCase().slice(1);
|
|
414
|
-
if (name.startsWith(body))
|
|
415
|
-
prefix.push(c);
|
|
416
|
-
else if (loose && name.includes(body))
|
|
417
|
-
substring.push(c);
|
|
418
|
-
else if (loose && isSubsequence(body, name))
|
|
419
|
-
fuzzy.push(c);
|
|
420
|
-
}
|
|
421
|
-
return prefix.concat(substring, fuzzy);
|
|
394
|
+
return matchSlashCommands(this.buf, this.slashCommands);
|
|
422
395
|
}
|
|
423
396
|
// Render the suggestion block for the current query. `offset` scrolls the
|
|
424
397
|
// window so Ctrl+N/P can page through more than the visible size (the list
|
|
@@ -436,10 +409,7 @@ export class LineEditor {
|
|
|
436
409
|
}
|
|
437
410
|
if (!sugg.length)
|
|
438
411
|
return { lines: [], rows: 0 };
|
|
439
|
-
const
|
|
440
|
-
const maxOffset = Math.max(0, sugg.length - pageSize);
|
|
441
|
-
const off = Math.max(0, Math.min(offset, maxOffset));
|
|
442
|
-
const shown = sugg.slice(off, off + pageSize);
|
|
412
|
+
const { shown, off, moreCount } = suggestWindow(sugg, offset);
|
|
443
413
|
this._suggestOffset = off;
|
|
444
414
|
this._suggestCount = sugg.length;
|
|
445
415
|
const label = (c) => c.hint ? c.name + ' ' + c.hint : c.name;
|
|
@@ -450,12 +420,10 @@ export class LineEditor {
|
|
|
450
420
|
const desc = theme.dim(' ' + c.description);
|
|
451
421
|
return (selected ? theme.prompt(' ▸ ') : ' ') + name + desc;
|
|
452
422
|
});
|
|
453
|
-
if (
|
|
423
|
+
if (moreCount > 0) {
|
|
454
424
|
const t = translate(this.locale);
|
|
455
425
|
lines.push(theme.dim(' ' +
|
|
456
|
-
t('editor.more', {
|
|
457
|
-
n: String(sugg.length - shown.length),
|
|
458
|
-
}) +
|
|
426
|
+
t('editor.more', { n: String(moreCount) }) +
|
|
459
427
|
' ' +
|
|
460
428
|
t('editor.page_hint')));
|
|
461
429
|
}
|
|
@@ -497,27 +465,9 @@ export class LineEditor {
|
|
|
497
465
|
// Tab: complete the command up to the common prefix; if there is a single
|
|
498
466
|
// match — insert it whole and add a space.
|
|
499
467
|
_completeCommand() {
|
|
500
|
-
const
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
if (this._suggestSelected >= 0 && this._suggestSelected < sugg.length) {
|
|
504
|
-
this.buf = sugg[this._suggestSelected].name + ' ';
|
|
505
|
-
this.cursor = Array.from(this.buf).length;
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
if (sugg.length === 1) {
|
|
509
|
-
this.buf = sugg[0].name + ' ';
|
|
510
|
-
this.cursor = Array.from(this.buf).length;
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
let prefix = sugg[0].name;
|
|
514
|
-
for (const c of sugg) {
|
|
515
|
-
while (!c.name.toLowerCase().startsWith(prefix.toLowerCase())) {
|
|
516
|
-
prefix = prefix.slice(0, -1);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
if (prefix.length > this.buf.length) {
|
|
520
|
-
this.buf = prefix;
|
|
468
|
+
const next = completeSlashCommand(this.buf, this._suggestions(), this._suggestSelected);
|
|
469
|
+
if (next !== null) {
|
|
470
|
+
this.buf = next;
|
|
521
471
|
this.cursor = Array.from(this.buf).length;
|
|
522
472
|
}
|
|
523
473
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zames_pro",
|
|
3
|
-
"version": "2.72.
|
|
3
|
+
"version": "2.72.5",
|
|
4
4
|
"description": "Terminal coding agent that drives chat.deepseek.com through Playwright — no API key required. Reads and edits files, runs commands, commits to git, like Claude Code / Codex CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|