office-core 0.1.0 → 0.1.2

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.
@@ -103,6 +103,7 @@ async function registerHostToken(input) {
103
103
  project_id: registration.project_id,
104
104
  workdir: input.workdir,
105
105
  token: registration.host_token,
106
+ room_cursor_seq: Number(registration.room_cursor_seq ?? 0),
106
107
  poll_ms: input.pollMs,
107
108
  });
108
109
  return registration.host_token;
@@ -293,7 +294,6 @@ async function switchProject(runtime, command) {
293
294
  sessions: [],
294
295
  }).catch(() => undefined);
295
296
  runtime.projectId = targetProjectId;
296
- runtime.roomCursorSeq = 0;
297
297
  runtime.token = await registerHostToken({
298
298
  baseUrl: runtime.baseUrl,
299
299
  projectId: runtime.projectId,
@@ -302,6 +302,8 @@ async function switchProject(runtime, command) {
302
302
  workdir: runtime.defaultWorkdir,
303
303
  pollMs: runtime.pollMs,
304
304
  });
305
+ const refreshedConfig = await loadHostConfig();
306
+ runtime.roomCursorSeq = Number(refreshedConfig?.room_cursor_seq ?? 0);
305
307
  const activeWorkdir = resolveProjectWorkdir(runtime.defaultWorkdir, runtime.projectId);
306
308
  await mkdir(activeWorkdir, { recursive: true });
307
309
  await upsertHostConfig({
@@ -1277,7 +1279,17 @@ export async function postJson(runtime, pathname, body, tokenOverride) {
1277
1279
  });
1278
1280
  }
1279
1281
  export async function postRoomMessageUpsert(runtime, body) {
1280
- return postJson(runtime, `/api/projects/${runtime.projectId}/room/messages/upsert`, body);
1282
+ try {
1283
+ return await postJson(runtime, `/api/projects/${runtime.projectId}/room/messages/upsert`, body);
1284
+ }
1285
+ catch (error) {
1286
+ const message = error instanceof Error ? error.message : String(error);
1287
+ if (!message.includes("/room/messages/upsert failed: 404")) {
1288
+ throw error;
1289
+ }
1290
+ const { message_id: _messageId, ...legacyBody } = body;
1291
+ return postJson(runtime, `/api/projects/${runtime.projectId}/room/messages`, legacyBody);
1292
+ }
1281
1293
  }
1282
1294
  function parseArgs(argv) {
1283
1295
  const result = {};
@@ -108,7 +108,7 @@ async function runInstallFlow(existing) {
108
108
  workdir,
109
109
  display_name,
110
110
  token: registration.host_token,
111
- room_cursor_seq: 0,
111
+ room_cursor_seq: Number(registration.room_cursor_seq ?? 0),
112
112
  poll_ms,
113
113
  auto_start,
114
114
  });
@@ -40,7 +40,7 @@ async function main() {
40
40
  workdir: args.workdir,
41
41
  display_name: displayName,
42
42
  token: registration.host_token,
43
- room_cursor_seq: 0,
43
+ room_cursor_seq: Number(registration.room_cursor_seq ?? 0),
44
44
  poll_ms: args.pollMs ? Number(args.pollMs) : undefined,
45
45
  auto_start: args.autoStart ? args.autoStart !== "false" : undefined,
46
46
  });
@@ -1,4 +1,4 @@
1
- import { createInterface, emitKeypressEvents } from "node:readline";
1
+ import { createInterface, emitKeypressEvents, clearScreenDown } from "node:readline";
2
2
  import { spawn } from "node:child_process";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
@@ -44,6 +44,12 @@ function printBanner() {
44
44
  function printHeader(label) {
45
45
  console.log(` ${BOLD}${WHT}office core${R} ${DIM}-${R} ${BOLD}${label}${R}`);
46
46
  }
47
+ function truncateText(value, maxLength) {
48
+ if (maxLength <= 1) {
49
+ return value.slice(0, Math.max(0, maxLength));
50
+ }
51
+ return value.length <= maxLength ? value : `${value.slice(0, maxLength - 1)}…`;
52
+ }
47
53
  // ─── Box Drawing ────────────────────────────────────────────────────────────
48
54
  function wordWrap(text, width) {
49
55
  const out = [];
@@ -117,32 +123,73 @@ function findCmd(name) {
117
123
  // ─── Slash Menu Renderer ────────────────────────────────────────────────────
118
124
  let _menuLines = 0;
119
125
  let _menuSelection = 0;
126
+ let _menuScrollOffset = 0;
127
+ let _menuQuery = "";
128
+ const MAX_VISIBLE_SLASH_ITEMS = 6;
120
129
  function getSlashMatches(partial) {
121
130
  const q = partial.toLowerCase();
122
131
  return CMDS.filter((c) => c.name.startsWith(q) || c.alias?.some((a) => a.startsWith(q)));
123
132
  }
124
- function renderSlashMenu(partial) {
125
- clearSlashMenu();
133
+ function eraseSlashMenu() {
134
+ if (_menuLines === 0)
135
+ return;
136
+ process.stdout.write(`\x1b[s\n`);
137
+ clearScreenDown(process.stdout);
138
+ process.stdout.write(`\x1b[u`);
139
+ _menuLines = 0;
140
+ }
141
+ function resetSlashMenuState() {
142
+ _menuSelection = 0;
143
+ _menuScrollOffset = 0;
144
+ _menuQuery = "";
145
+ }
146
+ function renderSlashMenu(partial, preserveSelection = false) {
147
+ eraseSlashMenu();
148
+ if (!preserveSelection || partial !== _menuQuery) {
149
+ _menuSelection = 0;
150
+ _menuScrollOffset = 0;
151
+ }
152
+ _menuQuery = partial;
126
153
  const hits = getSlashMatches(partial);
127
- if (hits.length === 0)
154
+ if (hits.length === 0) {
155
+ resetSlashMenuState();
128
156
  return;
157
+ }
129
158
  _menuSelection = Math.max(0, Math.min(_menuSelection, hits.length - 1));
130
- const lines = hits.map((c) => {
131
- const args = c.args ? ` ${GRY}${c.args}${R}` : "";
159
+ if (_menuSelection < _menuScrollOffset) {
160
+ _menuScrollOffset = _menuSelection;
161
+ }
162
+ if (_menuSelection >= _menuScrollOffset + MAX_VISIBLE_SLASH_ITEMS) {
163
+ _menuScrollOffset = _menuSelection - MAX_VISIBLE_SLASH_ITEMS + 1;
164
+ }
165
+ const visibleHits = hits.slice(_menuScrollOffset, _menuScrollOffset + MAX_VISIBLE_SLASH_ITEMS);
166
+ const lines = [];
167
+ const terminalWidth = Math.max(40, process.stdout.columns ?? 100);
168
+ const commandWidth = Math.min(28, Math.max(16, Math.floor(terminalWidth * 0.32)));
169
+ const descWidth = Math.max(12, terminalWidth - commandWidth - 10);
170
+ if (_menuScrollOffset > 0) {
171
+ lines.push(` ${DIM}${truncateText("↑ more commands", terminalWidth - 4)}${R}`);
172
+ }
173
+ lines.push(...visibleHits.map((c) => {
174
+ const commandLabel = `/${c.name}${c.args ? ` ${c.args}` : ""}`;
175
+ const truncatedCommandLabel = truncateText(commandLabel, commandWidth);
176
+ const truncatedDesc = truncateText(c.desc, descWidth);
132
177
  return c === hits[_menuSelection]
133
- ? ` ${BOLD}${WHT}> /${c.name}${R}${args} ${DIM}${c.desc}${R}`
134
- : ` ${GRN}/${c.name}${R}${args} ${DIM}${c.desc}${R}`;
135
- });
136
- // Save cursor, newline, print menu, restore cursor
137
- process.stdout.write(`\x1b7\n${lines.join("\n")}\x1b8`);
178
+ ? ` ${BOLD}${WHT}> ${truncatedCommandLabel.padEnd(commandWidth)}${R} ${DIM}${truncatedDesc}${R}`
179
+ : ` ${GRN}${truncatedCommandLabel.padEnd(commandWidth)}${R} ${DIM}${truncatedDesc}${R}`;
180
+ }));
181
+ if (_menuScrollOffset + visibleHits.length < hits.length) {
182
+ lines.push(` ${DIM}${truncateText("↓ more commands", terminalWidth - 4)}${R}`);
183
+ }
184
+ process.stdout.write(`\x1b[s\n`);
185
+ clearScreenDown(process.stdout);
186
+ process.stdout.write(lines.join("\n"));
187
+ process.stdout.write(`\x1b[u`);
138
188
  _menuLines = lines.length;
139
189
  }
140
190
  function clearSlashMenu() {
141
- if (_menuLines === 0)
142
- return;
143
- process.stdout.write(`\x1b7\n\x1b[J\x1b8`);
144
- _menuLines = 0;
145
- _menuSelection = 0;
191
+ eraseSlashMenu();
192
+ resetSlashMenuState();
146
193
  }
147
194
  // ─── Tab Completer ──────────────────────────────────────────────────────────
148
195
  function completer(line) {
@@ -426,7 +473,16 @@ async function cmdSetup(_a, ctx) {
426
473
  if (!resp.ok)
427
474
  throw new Error(`${resp.status} ${await resp.text()}`);
428
475
  const reg = (await resp.json());
429
- await upsertHostConfig({ host_id: reg.host_id, base_url, project_id, workdir, display_name, token: reg.host_token, room_cursor_seq: 0, poll_ms: 900 });
476
+ await upsertHostConfig({
477
+ host_id: reg.host_id,
478
+ base_url,
479
+ project_id,
480
+ workdir,
481
+ display_name,
482
+ token: reg.host_token,
483
+ room_cursor_seq: Number(reg.room_cursor_seq ?? 0),
484
+ poll_ms: 900,
485
+ });
430
486
  console.log(` ${GRN}Registered!${R} Host ID: ${reg.host_id}`);
431
487
  console.log(` ${DIM}${getHostConfigPath()}${R}`);
432
488
  // Connect immediately
@@ -567,16 +623,16 @@ async function main() {
567
623
  key.name === "up"
568
624
  ? (_menuSelection + hits.length - 1) % hits.length
569
625
  : (_menuSelection + 1) % hits.length;
570
- renderSlashMenu(line.slice(1));
626
+ renderSlashMenu(line.slice(1), true);
571
627
  return;
572
628
  }
573
629
  if (hits.length > 0 && key?.name === "tab") {
574
630
  const selected = hits[_menuSelection] ?? hits[0];
575
631
  replaceInputLine(rl, `/${selected.name} `);
576
- renderSlashMenu(selected.name);
632
+ renderSlashMenu(selected.name, false);
577
633
  return;
578
634
  }
579
- renderSlashMenu(line.slice(1));
635
+ renderSlashMenu(line.slice(1), false);
580
636
  }
581
637
  else {
582
638
  clearSlashMenu();
@@ -328,6 +328,7 @@ export class ProjectDO {
328
328
  host_id: requestedHostId,
329
329
  host_token: hostToken,
330
330
  project_id: body.project_id,
331
+ room_cursor_seq: next.room.next_seq,
331
332
  room_settings: next.room.settings,
332
333
  });
333
334
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "office-core",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Installable CLI and local host runtime for Office Core",
5
5
  "type": "module",
6
6
  "bin": {