@makerbi/remodex 1.5.1 → 1.5.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.
@@ -0,0 +1,501 @@
1
+ #!/usr/bin/env node
2
+ // FILE: remodex-jsonl-diagnose.js
3
+ // Purpose: Standalone diagnostic for Codex session JSONL history parsing.
4
+
5
+ const fs = require("fs");
6
+ const path = require("path");
7
+
8
+ const DEFAULT_RECENT_TURN_LIMIT = 5;
9
+ const DEFAULT_TEXT_PREVIEW_CHARS = 180;
10
+ const RELAY_SOFT_LIMIT_BYTES = 4 * 1024 * 1024;
11
+
12
+ function main(argv) {
13
+ const options = parseArgs(argv);
14
+ if (options.help || !options.filePath) {
15
+ printUsage();
16
+ process.exit(options.help ? 0 : 1);
17
+ }
18
+
19
+ const absolutePath = path.resolve(options.filePath);
20
+ const result = diagnoseSessionJsonl(absolutePath, options);
21
+ console.log(JSON.stringify(result, null, 2));
22
+
23
+ if (result.errors.file) {
24
+ process.exit(2);
25
+ }
26
+ if (result.parse.invalidJsonLines > 0 || result.history.turnCount === 0) {
27
+ process.exit(3);
28
+ }
29
+ }
30
+
31
+ function parseArgs(argv) {
32
+ const options = {
33
+ filePath: "",
34
+ recentTurns: DEFAULT_RECENT_TURN_LIMIT,
35
+ previewChars: DEFAULT_TEXT_PREVIEW_CHARS,
36
+ includeText: false,
37
+ help: false,
38
+ };
39
+
40
+ for (let index = 0; index < argv.length; index += 1) {
41
+ const arg = argv[index];
42
+ if (arg === "-h" || arg === "--help") {
43
+ options.help = true;
44
+ } else if (arg === "--show-text") {
45
+ options.includeText = true;
46
+ } else if (arg === "--recent-turns") {
47
+ options.recentTurns = readPositiveInteger(argv[index + 1], DEFAULT_RECENT_TURN_LIMIT);
48
+ index += 1;
49
+ } else if (arg === "--preview-chars") {
50
+ options.previewChars = readPositiveInteger(argv[index + 1], DEFAULT_TEXT_PREVIEW_CHARS);
51
+ index += 1;
52
+ } else if (!options.filePath) {
53
+ options.filePath = arg;
54
+ }
55
+ }
56
+
57
+ return options;
58
+ }
59
+
60
+ function printUsage() {
61
+ console.log([
62
+ "Usage:",
63
+ " remodex-jsonl-diagnose /path/to/session.jsonl",
64
+ "",
65
+ "Options:",
66
+ " --recent-turns N Number of recent parsed turns to summarize. Default: 5",
67
+ " --preview-chars N Text preview length per item. Default: 180",
68
+ " --show-text Include short text previews in the output",
69
+ ].join("\n"));
70
+ }
71
+
72
+ function diagnoseSessionJsonl(filePath, options = {}) {
73
+ const includeText = Boolean(options.includeText);
74
+ const previewChars = readPositiveInteger(options.previewChars, DEFAULT_TEXT_PREVIEW_CHARS);
75
+ const recentTurnsLimit = readPositiveInteger(options.recentTurns, DEFAULT_RECENT_TURN_LIMIT);
76
+ const summary = {
77
+ file: {
78
+ path: filePath,
79
+ exists: false,
80
+ bytes: 0,
81
+ },
82
+ parse: {
83
+ totalLines: 0,
84
+ blankLines: 0,
85
+ validJsonLines: 0,
86
+ invalidJsonLines: 0,
87
+ invalidSamples: [],
88
+ },
89
+ session: {
90
+ threadId: null,
91
+ cwd: null,
92
+ originator: null,
93
+ source: null,
94
+ sessionMetaCount: 0,
95
+ },
96
+ observed: {
97
+ topLevelTypes: {},
98
+ eventTypes: {},
99
+ responseItemTypes: {},
100
+ threadIds: [],
101
+ turnIds: [],
102
+ },
103
+ history: {
104
+ turnCount: 0,
105
+ itemCount: 0,
106
+ turnsWithNoItems: 0,
107
+ recentTurns: [],
108
+ },
109
+ relaySimulation: {
110
+ recentPageBytes: 0,
111
+ recentPageWithin4MiB: false,
112
+ compactSingleTurnBytes: 0,
113
+ compactSingleTurnWithin4MiB: false,
114
+ wouldReturnAtLeastOneTurn: false,
115
+ },
116
+ likelyIssue: "unknown",
117
+ errors: {
118
+ file: null,
119
+ },
120
+ };
121
+
122
+ let stat;
123
+ try {
124
+ stat = fs.statSync(filePath);
125
+ } catch (error) {
126
+ summary.errors.file = error.message;
127
+ return summary;
128
+ }
129
+
130
+ if (!stat.isFile()) {
131
+ summary.errors.file = "Path is not a file.";
132
+ return summary;
133
+ }
134
+
135
+ summary.file.exists = true;
136
+ summary.file.bytes = stat.size;
137
+
138
+ const turns = [];
139
+ const turnsById = new Map();
140
+ const threadIds = new Set();
141
+ const turnIds = new Set();
142
+ let activeTurnId = "";
143
+
144
+ const raw = fs.readFileSync(filePath, "utf8");
145
+ const lines = raw.split(/\r?\n/);
146
+ for (let index = 0; index < lines.length; index += 1) {
147
+ const lineNumber = index + 1;
148
+ const line = lines[index];
149
+ summary.parse.totalLines += 1;
150
+ if (!line.trim()) {
151
+ summary.parse.blankLines += 1;
152
+ continue;
153
+ }
154
+
155
+ let entry;
156
+ try {
157
+ entry = JSON.parse(line);
158
+ summary.parse.validJsonLines += 1;
159
+ } catch (error) {
160
+ summary.parse.invalidJsonLines += 1;
161
+ if (summary.parse.invalidSamples.length < 5) {
162
+ summary.parse.invalidSamples.push({
163
+ line: lineNumber,
164
+ error: error.message,
165
+ preview: line.slice(0, 240),
166
+ });
167
+ }
168
+ continue;
169
+ }
170
+
171
+ increment(summary.observed.topLevelTypes, readString(entry?.type) || "unknown");
172
+ collectKnownThreadIds(entry, threadIds);
173
+
174
+ if (entry?.type === "session_meta") {
175
+ summary.session.sessionMetaCount += 1;
176
+ const payload = objectValue(entry.payload);
177
+ summary.session.threadId ||= readString(payload?.id) || readString(payload?.thread_id) || readString(payload?.threadId);
178
+ summary.session.cwd ||= readString(payload?.cwd);
179
+ summary.session.originator ||= readString(payload?.originator);
180
+ summary.session.source ||= readString(payload?.source);
181
+ if (summary.session.threadId) {
182
+ threadIds.add(summary.session.threadId);
183
+ }
184
+ continue;
185
+ }
186
+
187
+ const embeddedTurns = findEmbeddedTurns(entry);
188
+ for (const turn of embeddedTurns) {
189
+ const turnId = readTurnId(turn) || `embedded-turn-line-${lineNumber}-${turns.length + 1}`;
190
+ const record = ensureTurn(turns, turnsById, turnId, lineNumber, entry.timestamp);
191
+ record.sourceKinds.add("embedded_turn");
192
+ if (Array.isArray(turn.items)) {
193
+ for (const item of turn.items) {
194
+ addItem(record, item, lineNumber, includeText, previewChars);
195
+ }
196
+ }
197
+ turnIds.add(turnId);
198
+ }
199
+
200
+ if (entry?.type === "event_msg") {
201
+ const payload = objectValue(entry.payload);
202
+ const eventType = readString(payload?.type) || "unknown";
203
+ increment(summary.observed.eventTypes, eventType);
204
+
205
+ if (eventType === "task_started") {
206
+ activeTurnId = readString(payload?.turn_id) || readString(payload?.turnId) || activeTurnId || `turn-line-${lineNumber}`;
207
+ const record = ensureTurn(turns, turnsById, activeTurnId, lineNumber, entry.timestamp);
208
+ record.status ||= "running";
209
+ record.sourceKinds.add("task_started");
210
+ turnIds.add(activeTurnId);
211
+ } else if (eventType === "task_complete") {
212
+ const turnId = readString(payload?.turn_id) || readString(payload?.turnId) || activeTurnId || `turn-line-${lineNumber}`;
213
+ const record = ensureTurn(turns, turnsById, turnId, lineNumber, entry.timestamp);
214
+ record.status = "completed";
215
+ record.sourceKinds.add("task_complete");
216
+ turnIds.add(turnId);
217
+ } else if (eventType === "user_message" || eventType === "agent_message" || eventType === "agent_reasoning") {
218
+ const turnId = readString(payload?.turn_id) || readString(payload?.turnId) || activeTurnId || `turn-line-${lineNumber}`;
219
+ const record = ensureTurn(turns, turnsById, turnId, lineNumber, entry.timestamp);
220
+ record.sourceKinds.add(eventType);
221
+ turnIds.add(turnId);
222
+ addItem(record, itemFromEventPayload(eventType, payload, lineNumber), lineNumber, includeText, previewChars);
223
+ }
224
+ } else if (entry?.type === "response_item") {
225
+ const payload = objectValue(entry.payload) || {};
226
+ const itemType = normalizeType(readString(payload.type)) || "unknown";
227
+ increment(summary.observed.responseItemTypes, itemType);
228
+ const turnId = readString(payload.turn_id) || readString(payload.turnId) || activeTurnId || `response-items-line-${lineNumber}`;
229
+ const record = ensureTurn(turns, turnsById, turnId, lineNumber, entry.timestamp);
230
+ record.sourceKinds.add("response_item");
231
+ turnIds.add(turnId);
232
+ addItem(record, payload, lineNumber, includeText, previewChars);
233
+ }
234
+ }
235
+
236
+ const sortedTurns = turns.sort((a, b) => a.firstLine - b.firstLine);
237
+ summary.observed.threadIds = Array.from(threadIds).sort();
238
+ summary.observed.turnIds = Array.from(turnIds).sort();
239
+ summary.history.turnCount = sortedTurns.length;
240
+ summary.history.itemCount = sortedTurns.reduce((total, turn) => total + turn.items.length, 0);
241
+ summary.history.turnsWithNoItems = sortedTurns.filter((turn) => turn.items.length === 0).length;
242
+ summary.history.recentTurns = sortedTurns.slice(-recentTurnsLimit).map((turn) => summarizeTurn(turn, includeText));
243
+
244
+ const recentPage = {
245
+ id: "diagnostic-thread-turns-list",
246
+ result: {
247
+ data: sortedTurns.slice(-recentTurnsLimit).reverse().map(toWireTurn),
248
+ nextCursor: sortedTurns.length > recentTurnsLimit ? "diagnostic-has-older-turns" : null,
249
+ },
250
+ };
251
+ const recentPageBytes = Buffer.byteLength(JSON.stringify(recentPage), "utf8");
252
+ const compactSingleTurn = sortedTurns.length > 0 ? {
253
+ id: "diagnostic-thread-turns-list",
254
+ result: {
255
+ data: [compactTurn(toWireTurn(sortedTurns[sortedTurns.length - 1]))],
256
+ nextCursor: sortedTurns.length > 1 ? "diagnostic-has-older-turns" : null,
257
+ },
258
+ } : null;
259
+ const compactSingleTurnBytes = compactSingleTurn
260
+ ? Buffer.byteLength(JSON.stringify(compactSingleTurn), "utf8")
261
+ : 0;
262
+
263
+ summary.relaySimulation.recentPageBytes = recentPageBytes;
264
+ summary.relaySimulation.recentPageWithin4MiB = recentPageBytes <= RELAY_SOFT_LIMIT_BYTES;
265
+ summary.relaySimulation.compactSingleTurnBytes = compactSingleTurnBytes;
266
+ summary.relaySimulation.compactSingleTurnWithin4MiB = compactSingleTurnBytes > 0
267
+ && compactSingleTurnBytes <= RELAY_SOFT_LIMIT_BYTES;
268
+ summary.relaySimulation.wouldReturnAtLeastOneTurn = summary.relaySimulation.recentPageWithin4MiB
269
+ ? sortedTurns.length > 0
270
+ : summary.relaySimulation.compactSingleTurnWithin4MiB;
271
+
272
+ summary.likelyIssue = classifyIssue(summary);
273
+ return summary;
274
+ }
275
+
276
+ function classifyIssue(summary) {
277
+ if (summary.errors.file) {
278
+ return "file_not_readable";
279
+ }
280
+ if (summary.parse.invalidJsonLines > 0 && summary.parse.validJsonLines === 0) {
281
+ return "jsonl_not_parseable";
282
+ }
283
+ if (summary.history.turnCount === 0 && summary.parse.validJsonLines > 0) {
284
+ return "jsonl_parseable_but_no_recognized_turns";
285
+ }
286
+ if (!summary.relaySimulation.wouldReturnAtLeastOneTurn) {
287
+ return "recognized_turns_but_payload_still_too_large";
288
+ }
289
+ return "jsonl_parseable_and_turns_extractable";
290
+ }
291
+
292
+ function ensureTurn(turns, turnsById, turnId, lineNumber, timestamp) {
293
+ const normalizedTurnId = readString(turnId) || `turn-line-${lineNumber}`;
294
+ let turn = turnsById.get(normalizedTurnId);
295
+ if (!turn) {
296
+ turn = {
297
+ id: normalizedTurnId,
298
+ firstLine: lineNumber,
299
+ lastLine: lineNumber,
300
+ createdAt: readString(timestamp) || null,
301
+ status: "",
302
+ sourceKinds: new Set(),
303
+ items: [],
304
+ };
305
+ turnsById.set(normalizedTurnId, turn);
306
+ turns.push(turn);
307
+ }
308
+ turn.lastLine = lineNumber;
309
+ return turn;
310
+ }
311
+
312
+ function addItem(turn, item, lineNumber, includeText, previewChars) {
313
+ if (!item || typeof item !== "object") {
314
+ return;
315
+ }
316
+ const text = firstText(item);
317
+ turn.items.push({
318
+ id: readString(item.id) || readString(item.item_id) || readString(item.itemId) || `item-line-${lineNumber}-${turn.items.length + 1}`,
319
+ type: readString(item.type) || "unknown",
320
+ role: readString(item.role) || null,
321
+ line: lineNumber,
322
+ textBytes: text ? Buffer.byteLength(text, "utf8") : 0,
323
+ textPreview: includeText && text ? truncateText(text, previewChars) : undefined,
324
+ rawBytes: Buffer.byteLength(JSON.stringify(item), "utf8"),
325
+ });
326
+ }
327
+
328
+ function itemFromEventPayload(eventType, payload, lineNumber) {
329
+ const role = eventType === "user_message" ? "user" : "assistant";
330
+ return {
331
+ id: readString(payload.id) || `${eventType}-line-${lineNumber}`,
332
+ type: eventType,
333
+ role,
334
+ text: readString(payload.message) || readString(payload.text) || readString(payload.summary) || "",
335
+ };
336
+ }
337
+
338
+ function summarizeTurn(turn, includeText) {
339
+ return {
340
+ id: turn.id,
341
+ firstLine: turn.firstLine,
342
+ lastLine: turn.lastLine,
343
+ status: turn.status || null,
344
+ sourceKinds: Array.from(turn.sourceKinds).sort(),
345
+ itemCount: turn.items.length,
346
+ rawItemBytes: turn.items.reduce((total, item) => total + item.rawBytes, 0),
347
+ textBytes: turn.items.reduce((total, item) => total + item.textBytes, 0),
348
+ items: turn.items.slice(-5).map((item) => ({
349
+ id: item.id,
350
+ type: item.type,
351
+ role: item.role,
352
+ line: item.line,
353
+ textBytes: item.textBytes,
354
+ rawBytes: item.rawBytes,
355
+ ...(includeText && item.textPreview !== undefined ? { textPreview: item.textPreview } : {}),
356
+ })),
357
+ };
358
+ }
359
+
360
+ function toWireTurn(turn) {
361
+ return {
362
+ id: turn.id,
363
+ createdAt: turn.createdAt,
364
+ status: turn.status || undefined,
365
+ items: turn.items.map((item) => ({
366
+ id: item.id,
367
+ type: item.type,
368
+ role: item.role || undefined,
369
+ text: item.textPreview || undefined,
370
+ remodexDiagnosticRawBytes: item.rawBytes,
371
+ remodexDiagnosticTextBytes: item.textBytes,
372
+ })),
373
+ };
374
+ }
375
+
376
+ function compactTurn(turn) {
377
+ return {
378
+ id: turn.id,
379
+ createdAt: turn.createdAt,
380
+ status: turn.status,
381
+ remodexDiagnosticCompacted: true,
382
+ items: (Array.isArray(turn.items) ? turn.items : []).slice(-1).map((item) => ({
383
+ id: item.id,
384
+ type: item.type || "relay_truncated_item",
385
+ role: item.role,
386
+ relayPayloadTruncated: true,
387
+ text: item.text ? truncateText(item.text, 1000) : undefined,
388
+ })),
389
+ };
390
+ }
391
+
392
+ function findEmbeddedTurns(entry) {
393
+ const candidates = [
394
+ entry?.turns,
395
+ entry?.thread?.turns,
396
+ entry?.payload?.turns,
397
+ entry?.payload?.thread?.turns,
398
+ entry?.result?.turns,
399
+ entry?.result?.thread?.turns,
400
+ entry?.result?.data,
401
+ entry?.result?.items,
402
+ entry?.result?.payload?.turns,
403
+ entry?.result?.payload?.data,
404
+ entry?.result?.payload?.items,
405
+ ];
406
+ return candidates.find((value) => Array.isArray(value)) || [];
407
+ }
408
+
409
+ function collectKnownThreadIds(value, output, depth = 0) {
410
+ if (!value || depth > 4) {
411
+ return;
412
+ }
413
+ if (Array.isArray(value)) {
414
+ for (const item of value.slice(0, 50)) {
415
+ collectKnownThreadIds(item, output, depth + 1);
416
+ }
417
+ return;
418
+ }
419
+ if (typeof value !== "object") {
420
+ return;
421
+ }
422
+
423
+ for (const key of ["thread_id", "threadId", "conversation_id", "conversationId"]) {
424
+ const id = readString(value[key]);
425
+ if (id) {
426
+ output.add(id);
427
+ }
428
+ }
429
+ for (const key of ["payload", "result", "thread"]) {
430
+ collectKnownThreadIds(value[key], output, depth + 1);
431
+ }
432
+ }
433
+
434
+ function readTurnId(turn) {
435
+ return readString(turn?.id) || readString(turn?.turnId) || readString(turn?.turn_id);
436
+ }
437
+
438
+ function firstText(value, depth = 0) {
439
+ if (!value || depth > 5) {
440
+ return "";
441
+ }
442
+ if (typeof value === "string") {
443
+ return value;
444
+ }
445
+ if (Array.isArray(value)) {
446
+ for (const item of value) {
447
+ const text = firstText(item, depth + 1);
448
+ if (text) {
449
+ return text;
450
+ }
451
+ }
452
+ return "";
453
+ }
454
+ if (typeof value !== "object") {
455
+ return "";
456
+ }
457
+ for (const key of ["text", "message", "summary", "output", "outputText", "output_text", "content"]) {
458
+ const text = firstText(value[key], depth + 1);
459
+ if (text) {
460
+ return text;
461
+ }
462
+ }
463
+ return "";
464
+ }
465
+
466
+ function objectValue(value) {
467
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
468
+ }
469
+
470
+ function readString(value) {
471
+ return typeof value === "string" && value.trim() ? value.trim() : "";
472
+ }
473
+
474
+ function readPositiveInteger(value, fallback) {
475
+ const number = Number(value);
476
+ return Number.isInteger(number) && number > 0 ? number : fallback;
477
+ }
478
+
479
+ function increment(record, key) {
480
+ record[key] = (record[key] || 0) + 1;
481
+ }
482
+
483
+ function normalizeType(value) {
484
+ return readString(value).toLowerCase().replace(/[\s_-]+/g, "");
485
+ }
486
+
487
+ function truncateText(value, maxChars) {
488
+ const text = readString(value);
489
+ if (!text || text.length <= maxChars) {
490
+ return text;
491
+ }
492
+ return `...\n${text.slice(-maxChars)}`;
493
+ }
494
+
495
+ if (require.main === module) {
496
+ main(process.argv.slice(2));
497
+ }
498
+
499
+ module.exports = {
500
+ diagnoseSessionJsonl,
501
+ };
package/bin/remodex.js CHANGED
@@ -49,7 +49,12 @@ async function main({
49
49
  exitImpl = process.exit,
50
50
  deps = defaultDeps,
51
51
  } = {}) {
52
- const { command, jsonOutput, watchThreadId } = parseCliArgs(argv.slice(2));
52
+ const {
53
+ command,
54
+ extraRelaySessions,
55
+ jsonOutput,
56
+ watchThreadId,
57
+ } = parseCliArgs(argv.slice(2));
53
58
 
54
59
  if (isVersionCommand(command)) {
55
60
  emitVersion({ jsonOutput, consoleImpl });
@@ -73,6 +78,7 @@ async function main({
73
78
  }
74
79
 
75
80
  if (command === "run") {
81
+ applyExtraRelaySessions(extraRelaySessions);
76
82
  deps.startBridge();
77
83
  return;
78
84
  }
@@ -233,7 +239,7 @@ async function main({
233
239
 
234
240
  consoleImpl.error(`Unknown command: ${command}`);
235
241
  consoleImpl.error(
236
- "Usage: remodex up | remodex run | remodex start | remodex restart | remodex stop | remodex status | "
242
+ "Usage: remodex up | remodex run [--extra-device|--extra-devices=N] | remodex start | remodex restart | remodex stop | remodex status | "
237
243
  + "remodex reset-pairing | remodex resume | remodex watch [threadId] | remodex --version | "
238
244
  + "append --json to start/restart/stop/status/reset-pairing/resume for machine-readable output"
239
245
  );
@@ -242,6 +248,7 @@ async function main({
242
248
 
243
249
  function parseCliArgs(rawArgs) {
244
250
  const positionals = [];
251
+ let extraRelaySessions = 0;
245
252
  let jsonOutput = false;
246
253
 
247
254
  for (const arg of rawArgs) {
@@ -250,16 +257,47 @@ function parseCliArgs(rawArgs) {
250
257
  continue;
251
258
  }
252
259
 
260
+ if (arg === "--extra-device" || arg === "--second-device" || arg === "--multi-device") {
261
+ extraRelaySessions = Math.max(extraRelaySessions, 1);
262
+ continue;
263
+ }
264
+
265
+ if (arg.startsWith("--extra-devices=")) {
266
+ extraRelaySessions = Math.max(
267
+ extraRelaySessions,
268
+ parsePositiveInteger(arg.slice("--extra-devices=".length))
269
+ );
270
+ continue;
271
+ }
272
+
253
273
  positionals.push(arg);
254
274
  }
255
275
 
256
276
  return {
257
277
  command: positionals[0] || "up",
278
+ extraRelaySessions,
258
279
  jsonOutput,
259
280
  watchThreadId: positionals[1] || "",
260
281
  };
261
282
  }
262
283
 
284
+ function applyExtraRelaySessions(extraRelaySessions) {
285
+ if (!extraRelaySessions || process.env.REMODEX_EXTRA_RELAY_SESSIONS) {
286
+ return;
287
+ }
288
+
289
+ process.env.REMODEX_EXTRA_RELAY_SESSIONS = String(Math.min(extraRelaySessions, 3));
290
+ }
291
+
292
+ function parsePositiveInteger(value) {
293
+ const parsed = Number.parseInt(value, 10);
294
+ if (!Number.isFinite(parsed) || parsed <= 0) {
295
+ return 0;
296
+ }
297
+
298
+ return parsed;
299
+ }
300
+
263
301
  function emitVersion({
264
302
  jsonOutput = false,
265
303
  consoleImpl = console,
@@ -312,4 +350,5 @@ function isVersionCommand(value) {
312
350
  module.exports = {
313
351
  isVersionCommand,
314
352
  main,
353
+ parseCliArgs,
315
354
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makerbi/remodex",
3
- "version": "1.5.1",
3
+ "version": "1.5.2",
4
4
  "description": "Local bridge between Codex and the Remodex mobile app. Run `remodex up` to start.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -9,7 +9,8 @@
9
9
  },
10
10
  "main": "src/index.js",
11
11
  "bin": {
12
- "remodex": "bin/remodex.js"
12
+ "remodex": "./bin/remodex.js",
13
+ "remodex-jsonl-diagnose": "./bin/remodex-jsonl-diagnose.js"
13
14
  },
14
15
  "files": [
15
16
  "bin/",
@@ -17,6 +18,7 @@
17
18
  ],
18
19
  "scripts": {
19
20
  "start": "node ./bin/remodex.js up",
21
+ "start:multi": "node ./bin/remodex.js run --extra-device",
20
22
  "test": "node --test ./test/*.test.js",
21
23
  "postinstall": "node ./src/bootstrap-codex-cli.js",
22
24
  "prepack": "node ./scripts/prepare-private-defaults.js",