@lardexx/arenacode 2.5.4 → 2.5.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/README.md +3 -1
- package/docs/PUBLISHING.md +9 -1
- package/package.json +1 -1
- package/src/cli.js +33 -12
- package/src/config.js +5 -2
- package/src/tui/app.js +14 -17
- package/src/tui/logo.js +4 -1
package/README.md
CHANGED
|
@@ -40,7 +40,9 @@ rebuilt from opencode 1.19.5's own source (`packages/tui/src/component/prompt`,
|
|
|
40
40
|
`logo.tsx`, `theme/assets/*.json`) and checked against frames captured from the installed binary in a pty.
|
|
41
41
|
|
|
42
42
|
```bash
|
|
43
|
-
arenacode # opens the TUI
|
|
43
|
+
arenacode # opens the TUI on a terminal; on the very first run it asks for URL + key first
|
|
44
|
+
arenacode ui # same thing, explicitly
|
|
45
|
+
arenacode chat # the plain line-by-line client, if you really want one
|
|
44
46
|
```
|
|
45
47
|
|
|
46
48
|
Home screen — two-tone block wordmark, centred prompt, tips, folder and version in the footer:
|
package/docs/PUBLISHING.md
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
1
|
# Publishing
|
|
2
2
|
|
|
3
|
-
**Live:** [`@lardexx/arenacode@2.5.
|
|
3
|
+
**Live:** [`@lardexx/arenacode@2.5.5`](https://www.npmjs.com/package/@lardexx/arenacode) — 34 files, 226 kB tarball,
|
|
4
4
|
no runtime dependencies. The command stays `arenacode`.
|
|
5
5
|
|
|
6
|
+
2.5.5 fixes `main()` defaulting the subcommand to `chat`, which sent every bare `arenacode` to the line CLI and
|
|
7
|
+
made the TUI unreachable without typing `ui`; a bare invocation now opens the interface, and on first run the
|
|
8
|
+
interface asks for URL + key instead of printing a dead-end notice.
|
|
9
|
+
|
|
10
|
+
2.5.4 made the hints launch-aware: run through npx and every message prints the command you can actually paste
|
|
11
|
+
(`npx @lardexx/arenacode init`), plus the one-line way to get the bare `arenacode` command.
|
|
12
|
+
|
|
6
13
|
```bash
|
|
7
14
|
npm install -g @lardexx/arenacode # then: arenacode
|
|
8
15
|
npx @lardexx/arenacode # or one-shot
|
|
@@ -30,6 +37,7 @@ npm itself suggests the escape hatch in that error: publish under a scope. Hence
|
|
|
30
37
|
| re-publish the same version | ❌ `409 Cannot publish over previously staged version "2.5.2"` |
|
|
31
38
|
| `npm stage list` / `stage approve` | ❌ "No staged versions" / `stage-id must be a valid UUID` — nothing to approve from the CLI |
|
|
32
39
|
| **bump to 2.5.3 and publish** | ✅ `+ @lardexx/arenacode@2.5.3`, `dist-tags.latest = 2.5.3` after ~2 min of async processing |
|
|
40
|
+
| 2.5.4 (npx-aware hints) | ✅ same path, again ~1–2 min of async processing before it appeared |
|
|
33
41
|
|
|
34
42
|
Two lessons for next time:
|
|
35
43
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lardexx/arenacode",
|
|
3
|
-
"version": "2.5.
|
|
3
|
+
"version": "2.5.6",
|
|
4
4
|
"description": "Terminal client for ArenaCode: talks to an arena-bridge, streams the Agent Mode timeline, and runs local commands and file operations on your PC. Node port of the ArenaCode desktop client.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|
package/src/cli.js
CHANGED
|
@@ -137,16 +137,17 @@ async function ensureAuth(ctx) {
|
|
|
137
137
|
// commands
|
|
138
138
|
// --------------------------------------------------------------------------- //
|
|
139
139
|
|
|
140
|
-
async function cmdInit(flags) {
|
|
140
|
+
async function cmdInit(flags, { tail = true } = {}) {
|
|
141
141
|
banner(VERSION);
|
|
142
142
|
const cfg = readConfig();
|
|
143
143
|
write(c.gray(` config file: ${configPath()}`));
|
|
144
144
|
write('');
|
|
145
145
|
const url = await ask(` Bridge URL ${cfg.url ? c.gray(`[${cfg.url}]`) : ''}: `, { default: cfg.url });
|
|
146
146
|
const key = await ask(` API key ${cfg.key ? c.gray('[configured]') : ''}: `, { default: cfg.key });
|
|
147
|
-
const folder = await ask(` Open folder ${c.gray(`[${cfg.folder || process.cwd()}]`)}: `, { default: cfg.folder || process.cwd() });
|
|
148
147
|
|
|
149
|
-
|
|
148
|
+
// no folder question: arenacode always works in the directory it was started from
|
|
149
|
+
const next = { ...cfg, url: String(url).replace(/\/+$/, ''), key: String(key) };
|
|
150
|
+
delete next.folder;
|
|
150
151
|
const file = writeConfig(next);
|
|
151
152
|
write(` ${c.green('✓')} saved ${c.gray(file)}`);
|
|
152
153
|
|
|
@@ -160,8 +161,10 @@ async function cmdInit(flags) {
|
|
|
160
161
|
const auth = await bridge.authCheck();
|
|
161
162
|
if (auth.status === 200) write(` ${c.green('✓')} API key accepted`);
|
|
162
163
|
else write(c.red(` ✗ API key rejected: ${(auth.json && auth.json.error) || auth.status}`));
|
|
163
|
-
|
|
164
|
-
|
|
164
|
+
if (tail) {
|
|
165
|
+
write('');
|
|
166
|
+
write(` ${c.gray('try:')} arenacode ask "list the files in my folder"`);
|
|
167
|
+
}
|
|
165
168
|
return 0;
|
|
166
169
|
}
|
|
167
170
|
|
|
@@ -182,7 +185,7 @@ function cmdConfig(positional, flags) {
|
|
|
182
185
|
const name = key;
|
|
183
186
|
const val = rest.join(' ');
|
|
184
187
|
if (!name || val === '') {
|
|
185
|
-
write(`usage: ${SELF} config set <url|key|
|
|
188
|
+
write(`usage: ${SELF} config set <url|key|lang|mode|pc_access> <value>`);
|
|
186
189
|
return 2;
|
|
187
190
|
}
|
|
188
191
|
const patch = {};
|
|
@@ -353,13 +356,25 @@ async function cmdAsk(positional, flags) {
|
|
|
353
356
|
|
|
354
357
|
/** Full-screen TUI (opencode-style): timeline, input box, dialogs, local cards. */
|
|
355
358
|
async function cmdUi(flags) {
|
|
356
|
-
|
|
357
|
-
if (!requireUrl(ctx)) return 1;
|
|
359
|
+
let ctx = context(flags);
|
|
358
360
|
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
361
|
+
if (!requireUrl(ctx)) return 1;
|
|
359
362
|
write(c.red(' the TUI needs an interactive terminal.'));
|
|
360
363
|
write(c.gray(' inside a script or a pipe use: arenacode ask "…" or arenacode chat'));
|
|
361
364
|
return 1;
|
|
362
365
|
}
|
|
366
|
+
|
|
367
|
+
// First run: `arenacode` (or `arenacode ui`) opens the setup here instead of printing two lines
|
|
368
|
+
// and quitting — the interface is what the user came for, the questions are just the way in.
|
|
369
|
+
if (!ctx.url) {
|
|
370
|
+
write('');
|
|
371
|
+
write(` ${c.bold('first run')} ${c.gray('· bridge URL and API key (Enter keeps the value in brackets)')}`);
|
|
372
|
+
write('');
|
|
373
|
+
await cmdInit(flags, { tail: false });
|
|
374
|
+
write('');
|
|
375
|
+
ctx = context(flags);
|
|
376
|
+
if (!ctx.url) return 1; // setup was abandoned: nothing to open
|
|
377
|
+
}
|
|
363
378
|
const { App } = require('./tui/app');
|
|
364
379
|
const app = new App({
|
|
365
380
|
url: ctx.url,
|
|
@@ -683,7 +698,10 @@ function help() {
|
|
|
683
698
|
|
|
684
699
|
async function main(argv) {
|
|
685
700
|
const { flags, positional } = parseArgs(argv);
|
|
686
|
-
|
|
701
|
+
// No default here: a bare `arenacode` must be distinguishable from `arenacode chat`, because only
|
|
702
|
+
// the bare form opens the TUI. Defaulting to 'chat' sent every plain invocation to the line CLI.
|
|
703
|
+
const [first = undefined, ...rest] = positional;
|
|
704
|
+
const command = first;
|
|
687
705
|
|
|
688
706
|
if (flags.version || command === 'version') {
|
|
689
707
|
write(VERSION);
|
|
@@ -724,13 +742,16 @@ async function main(argv) {
|
|
|
724
742
|
case 'handshake':
|
|
725
743
|
return cmdHandshake(flags);
|
|
726
744
|
default:
|
|
745
|
+
if (command === undefined) {
|
|
746
|
+
// bare invocation: the interface, when there is a terminal to draw it on
|
|
747
|
+
if (process.stdout.isTTY && process.stdin.isTTY) return cmdUi(flags);
|
|
748
|
+
help();
|
|
749
|
+
return 0;
|
|
750
|
+
}
|
|
727
751
|
if (command.startsWith('-')) {
|
|
728
752
|
// `arenacode "instruction"` convenience form
|
|
729
753
|
return cmdAsk(positional, flags);
|
|
730
754
|
}
|
|
731
|
-
if ((command === 'chat' || command === undefined) && process.stdout.isTTY && process.stdin.isTTY) {
|
|
732
|
-
return cmdUi(flags);
|
|
733
|
-
}
|
|
734
755
|
write(c.red(` unknown command: ${command}`));
|
|
735
756
|
help();
|
|
736
757
|
return 2;
|
package/src/config.js
CHANGED
|
@@ -121,9 +121,12 @@ function resolveKey(cfg, flagKey) {
|
|
|
121
121
|
return String(flagKey || process.env.ARENACODE_KEY || cfg.key || '').trim();
|
|
122
122
|
}
|
|
123
123
|
|
|
124
|
+
/**
|
|
125
|
+
* The folder arenacode works in: the one you launched it from. No stored default, so starting it in
|
|
126
|
+
* another project just works — `--folder` (or ARENACODE_FOLDER) still forces one.
|
|
127
|
+
*/
|
|
124
128
|
function openFolder(cfg, flagFolder) {
|
|
125
|
-
|
|
126
|
-
return folder;
|
|
129
|
+
return path.resolve(flagFolder || process.env.ARENACODE_FOLDER || process.cwd());
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
module.exports = {
|
package/src/tui/app.js
CHANGED
|
@@ -95,7 +95,10 @@ class App {
|
|
|
95
95
|
// ---------------------------------------------------------------- lifecycle
|
|
96
96
|
async start() {
|
|
97
97
|
this.term.enter();
|
|
98
|
-
this.term.on('key', (chunk) =>
|
|
98
|
+
this.term.on('key', (chunk) => {
|
|
99
|
+
this.parser.feed(chunk);
|
|
100
|
+
this.flush();
|
|
101
|
+
});
|
|
99
102
|
this.term.on('resize', () => this.invalidate());
|
|
100
103
|
this.running = true;
|
|
101
104
|
|
|
@@ -124,6 +127,14 @@ class App {
|
|
|
124
127
|
this.dirty = true;
|
|
125
128
|
}
|
|
126
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Keys used to wait for the render loop's next turn (up to 33 ms), which on Windows reads as
|
|
132
|
+
* lag when tapping through a list. Paint on the keystroke itself and keep the loop as fallback.
|
|
133
|
+
*/
|
|
134
|
+
flush() {
|
|
135
|
+
if (this.dirty && !this.rendering) this.render();
|
|
136
|
+
}
|
|
137
|
+
|
|
127
138
|
maybeRender() {
|
|
128
139
|
if (this.dirty && !this.rendering) this.render();
|
|
129
140
|
}
|
|
@@ -463,7 +474,6 @@ class App {
|
|
|
463
474
|
if (h) this.push({ kind: 'system', text: `bridge v${h.version} · ${h.executor_mode} · claimable ${h.tasks.claimable}` });
|
|
464
475
|
else this.toast('bridge unreachable', 'error');
|
|
465
476
|
} },
|
|
466
|
-
{ title: 'Bridge workspace files', run: () => this.openFiles() },
|
|
467
477
|
{ title: 'Help', hint: 'f1', run: () => this.openHelp() },
|
|
468
478
|
{ title: 'Quit', hint: 'ctrl+x q', run: () => this.quit() },
|
|
469
479
|
].map((cmd) => ({ ...cmd, slash: '/' + cmd.title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-+$/, ''), group: 'Commands' }));
|
|
@@ -474,7 +484,6 @@ class App {
|
|
|
474
484
|
return [
|
|
475
485
|
{ slash: '/new', title: 'Start a new session', hint: 'ctrl+x n', group: 'Session', run: () => this.commands()[1].run() },
|
|
476
486
|
{ slash: '/tasks', title: 'Show the task queue', hint: 'ctrl+x l', group: 'Session', run: () => this.openTasks() },
|
|
477
|
-
{ slash: '/files', title: 'Browse the workspace files', hint: '', group: 'Session', run: () => this.openFiles() },
|
|
478
487
|
{ slash: '/mode', title: 'Switch agent / plan', hint: 'tab', group: 'Session', run: () => this.commands()[5].run() },
|
|
479
488
|
{ slash: '/theme', title: 'Cycle the colour theme', hint: 'ctrl+x t', group: 'Session', run: () => this.commands()[6].run() },
|
|
480
489
|
{ slash: '/status', title: 'Ping the bridge', hint: '', group: 'Session', run: () => this.commands()[7].run() },
|
|
@@ -1071,12 +1080,11 @@ class App {
|
|
|
1071
1080
|
}
|
|
1072
1081
|
|
|
1073
1082
|
modelName() {
|
|
1074
|
-
return
|
|
1083
|
+
return 'auto';
|
|
1075
1084
|
}
|
|
1076
1085
|
|
|
1077
1086
|
providerLabel() {
|
|
1078
|
-
|
|
1079
|
-
return `bridge ${v}`;
|
|
1087
|
+
return 'arena';
|
|
1080
1088
|
}
|
|
1081
1089
|
|
|
1082
1090
|
statusText() {
|
|
@@ -1105,13 +1113,11 @@ class App {
|
|
|
1105
1113
|
|
|
1106
1114
|
if (this.isHome()) {
|
|
1107
1115
|
const logo = W2.centerLines(logoLines(t), W);
|
|
1108
|
-
const notify = this.notifyLine(W);
|
|
1109
1116
|
const blockH = logo.length + 1 + prompt.lines.length + 2 + 1;
|
|
1110
1117
|
const top = Math.max(1, Math.floor((H - 2 - blockH) / 2));
|
|
1111
1118
|
for (let i = 0; i < top; i++) frame.push('');
|
|
1112
1119
|
frame.push(...logo);
|
|
1113
1120
|
frame.push('');
|
|
1114
|
-
if (notify) frame.push(notify);
|
|
1115
1121
|
const promptStart = frame.length;
|
|
1116
1122
|
this._promptTop = promptStart;
|
|
1117
1123
|
frame.push(...prompt.lines);
|
|
@@ -1170,15 +1176,6 @@ class App {
|
|
|
1170
1176
|
if (this.dirty) setTimeout(() => this.maybeRender(), 16);
|
|
1171
1177
|
}
|
|
1172
1178
|
|
|
1173
|
-
notifyLine(W) {
|
|
1174
|
-
const t = this.theme;
|
|
1175
|
-
const st = this.state;
|
|
1176
|
-
if (!st.connected) {
|
|
1177
|
-
return W2.notifyLine(t, { width: W, command: '/connect', text: ` bridge offline at ${this.url}` });
|
|
1178
|
-
}
|
|
1179
|
-
return '';
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
1179
|
tipLine(W) {
|
|
1183
1180
|
const t = this.theme;
|
|
1184
1181
|
const tips = [
|
package/src/tui/logo.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
const { fgSeq, bgSeq, RESET, tint } = require('./theme');
|
|
13
13
|
|
|
14
14
|
const LOGO = {
|
|
15
|
-
left: [' ', '
|
|
15
|
+
left: [' ', ';::█ █▀▀█ █▀▀█ █▀▀▄ ;::█', '█;;█ █▀▀▄ █^^^ █__█ █;;█', ' : ▀~~▀ ▀▀▀▀ ▀~~▀ :'],
|
|
16
16
|
right: [' ▄ ', '█▀▀▀ █▀▀█ █▀▀█ █▀▀█', '█___ █__█ █__█ █^^^', '▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀'],
|
|
17
17
|
};
|
|
18
18
|
|
|
@@ -33,6 +33,9 @@ function paintLine(line, color, shadow, bold) {
|
|
|
33
33
|
else if (ch === '^') out += bgFill(shadow, '▀', color, bold);
|
|
34
34
|
else if (ch === '~') out += fgPaint(shadow, '▀', bold);
|
|
35
35
|
else if (ch === ',') out += fgPaint(shadow, '▄', bold);
|
|
36
|
+
// plain half blocks in the letter colour: lets a glyph put a stroke on only one half of a cell
|
|
37
|
+
else if (ch === ':') out += fgPaint(color, '▀', bold);
|
|
38
|
+
else if (ch === ';') out += fgPaint(color, '▄', bold);
|
|
36
39
|
else out += fgPaint(color, ch, bold);
|
|
37
40
|
}
|
|
38
41
|
return out;
|