@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 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 (the default when stdout is a terminal)
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:
@@ -1,8 +1,15 @@
1
1
  # Publishing
2
2
 
3
- **Live:** [`@lardexx/arenacode@2.5.3`](https://www.npmjs.com/package/@lardexx/arenacode) — 34 files, 226 kB tarball,
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.4",
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
- const next = { ...cfg, url: String(url).replace(/\/+$/, ''), key: String(key), folder: path.resolve(String(folder) || process.cwd()) };
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
- write('');
164
- write(` ${c.gray('try:')} arenacode ask "list the files in my folder"`);
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|folder|lang|mode|pc_access> <value>`);
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
- const ctx = context(flags);
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
- const [command = 'chat', ...rest] = positional;
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
- const folder = path.resolve(flagFolder || process.env.ARENACODE_FOLDER || cfg.folder || process.cwd());
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) => this.parser.feed(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 (this.state.health && (this.state.health.model || this.state.health.executor)) || 'arenacode';
1083
+ return 'auto';
1075
1084
  }
1076
1085
 
1077
1086
  providerLabel() {
1078
- const v = (this.state.health && this.state.health.version) || VERSION;
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;