open-research 0.1.18 → 0.1.20

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.
Files changed (2) hide show
  1. package/dist/cli.js +128 -18
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -811,7 +811,7 @@ function formatDateTime(value) {
811
811
  }
812
812
 
813
813
  // src/lib/cli/version.ts
814
- var PACKAGE_VERSION = "0.1.18";
814
+ var PACKAGE_VERSION = "0.1.20";
815
815
  function getPackageVersion() {
816
816
  return PACKAGE_VERSION;
817
817
  }
@@ -877,13 +877,13 @@ import {
877
877
  useDeferredValue,
878
878
  useEffect as useEffect2,
879
879
  useMemo as useMemo3,
880
- useRef,
880
+ useRef as useRef2,
881
881
  useState as useState4
882
882
  } from "react";
883
883
  import { Box as Box5, Text as Text5, useApp, useInput as useInput4 } from "ink";
884
884
 
885
885
  // src/tui/text-input.tsx
886
- import { useState, useEffect } from "react";
886
+ import { useState, useEffect, useRef } from "react";
887
887
  import { Box, Text, useInput } from "ink";
888
888
 
889
889
  // node_modules/chalk/source/vendor/ansi-styles/index.js
@@ -1383,6 +1383,22 @@ var source_default = chalk;
1383
1383
 
1384
1384
  // src/tui/text-input.tsx
1385
1385
  import { jsx } from "react/jsx-runtime";
1386
+ var PASTE_MARKER = "\uFFFC";
1387
+ function expandPasteMarkers(value, pasteMap) {
1388
+ let result = "";
1389
+ let pasteIdx = 0;
1390
+ const ids = [...pasteMap.keys()].sort((a, b) => a - b);
1391
+ for (const char of value) {
1392
+ if (char === PASTE_MARKER && pasteIdx < ids.length) {
1393
+ const entry = pasteMap.get(ids[pasteIdx]);
1394
+ result += entry?.text ?? "";
1395
+ pasteIdx++;
1396
+ } else {
1397
+ result += char;
1398
+ }
1399
+ }
1400
+ return result;
1401
+ }
1386
1402
  function prevWordBoundary(value, cursor) {
1387
1403
  let i = cursor;
1388
1404
  while (i > 0 && /\s/.test(value[i - 1])) i--;
@@ -1408,6 +1424,8 @@ function TextInput({
1408
1424
  cursorToEnd = 0
1409
1425
  }) {
1410
1426
  const [cursorOffset, setCursorOffset] = useState(originalValue.length);
1427
+ const pasteMapRef = useRef(/* @__PURE__ */ new Map());
1428
+ const pasteCounterRef = useRef(0);
1411
1429
  useEffect(() => {
1412
1430
  if (!focus || !showCursor) return;
1413
1431
  if (cursorOffset > originalValue.length) {
@@ -1419,25 +1437,55 @@ function TextInput({
1419
1437
  setCursorOffset(originalValue.length);
1420
1438
  }
1421
1439
  }, [cursorToEnd]);
1440
+ useEffect(() => {
1441
+ if (originalValue === "") {
1442
+ pasteMapRef.current.clear();
1443
+ }
1444
+ }, [originalValue]);
1445
+ function pasteBadge(entry) {
1446
+ return source_default.dim.cyan(`[Pasted text #${entry.id} +${entry.lineCount} lines]`);
1447
+ }
1422
1448
  function buildRendered() {
1423
1449
  if (showCursor && focus) {
1424
1450
  if (originalValue.length === 0) return source_default.inverse(" ");
1425
- let result = "";
1451
+ const pasteIds2 = [...pasteMapRef.current.keys()].sort((a, b) => a - b);
1452
+ let pasteIdx2 = 0;
1453
+ let result2 = "";
1426
1454
  let i = 0;
1427
1455
  for (const char of originalValue) {
1428
- if (i === cursorOffset) {
1429
- result += char === "\n" ? source_default.inverse(" ") + "\n" : source_default.inverse(char);
1456
+ if (char === PASTE_MARKER) {
1457
+ const entry = pasteIdx2 < pasteIds2.length ? pasteMapRef.current.get(pasteIds2[pasteIdx2]) : void 0;
1458
+ pasteIdx2++;
1459
+ if (i === cursorOffset) {
1460
+ result2 += entry ? pasteBadge(entry) + source_default.inverse(" ") : source_default.inverse(" ");
1461
+ } else {
1462
+ result2 += entry ? pasteBadge(entry) : "";
1463
+ }
1464
+ } else if (i === cursorOffset) {
1465
+ result2 += char === "\n" ? source_default.inverse(" ") + "\n" : source_default.inverse(char);
1430
1466
  } else {
1431
- result += char;
1467
+ result2 += char;
1432
1468
  }
1433
1469
  i++;
1434
1470
  }
1435
1471
  if (cursorOffset === originalValue.length) {
1436
- result += source_default.inverse(" ");
1472
+ result2 += source_default.inverse(" ");
1473
+ }
1474
+ return result2;
1475
+ }
1476
+ let result = "";
1477
+ const pasteIds = [...pasteMapRef.current.keys()].sort((a, b) => a - b);
1478
+ let pasteIdx = 0;
1479
+ for (const char of originalValue) {
1480
+ if (char === PASTE_MARKER) {
1481
+ const entry = pasteIdx < pasteIds.length ? pasteMapRef.current.get(pasteIds[pasteIdx]) : void 0;
1482
+ pasteIdx++;
1483
+ result += entry ? pasteBadge(entry) : "";
1484
+ } else {
1485
+ result += char;
1437
1486
  }
1438
- return result;
1439
1487
  }
1440
- return originalValue;
1488
+ return result;
1441
1489
  }
1442
1490
  const renderedPlaceholder = showCursor && focus && placeholder.length > 0 ? source_default.inverse(placeholder[0]) + source_default.grey(placeholder.slice(1)) : placeholder ? source_default.grey(placeholder) : void 0;
1443
1491
  useInput(
@@ -1464,7 +1512,8 @@ function TextInput({
1464
1512
  return;
1465
1513
  }
1466
1514
  if (key.return) {
1467
- onSubmit?.(originalValue);
1515
+ const expanded = expandPasteMarkers(originalValue, pasteMapRef.current);
1516
+ onSubmit?.(expanded);
1468
1517
  return;
1469
1518
  }
1470
1519
  let nextValue = originalValue;
@@ -1483,6 +1532,17 @@ function TextInput({
1483
1532
  nextValue = originalValue.slice(0, cursorOffset) + originalValue.slice(boundary);
1484
1533
  } else if (key.backspace || key.delete) {
1485
1534
  if (cursorOffset > 0) {
1535
+ const deletedChar = originalValue[cursorOffset - 1];
1536
+ if (deletedChar === PASTE_MARKER) {
1537
+ let markerIndex = 0;
1538
+ for (let ci = 0; ci < cursorOffset - 1; ci++) {
1539
+ if (originalValue[ci] === PASTE_MARKER) markerIndex++;
1540
+ }
1541
+ const ids = [...pasteMapRef.current.keys()].sort((a, b) => a - b);
1542
+ if (markerIndex < ids.length) {
1543
+ pasteMapRef.current.delete(ids[markerIndex]);
1544
+ }
1545
+ }
1486
1546
  nextValue = originalValue.slice(0, cursorOffset - 1) + originalValue.slice(cursorOffset);
1487
1547
  nextCursor--;
1488
1548
  }
@@ -1499,10 +1559,18 @@ function TextInput({
1499
1559
  } else if (key.rightArrow) {
1500
1560
  if (showCursor) nextCursor++;
1501
1561
  } else if (!key.ctrl && !key.meta) {
1502
- const clean = input2.replace(/\x1b\[[?>=!]*[0-9;]*[a-zA-Z]/g, "").replace(/[\x00-\x08\x0e-\x1f]/g, "");
1562
+ const clean = input2.replace(/\x1b\[[?>=!]*[0-9;]*[a-zA-Z~]/g, "").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?/g, "").replace(/\[20[01]~/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
1503
1563
  if (clean) {
1504
- nextValue = originalValue.slice(0, cursorOffset) + clean + originalValue.slice(cursorOffset);
1505
- nextCursor += clean.length;
1564
+ const lineCount = (clean.match(/\n/g) || []).length;
1565
+ if (lineCount >= 2) {
1566
+ const id = ++pasteCounterRef.current;
1567
+ pasteMapRef.current.set(id, { text: clean, lineCount, id });
1568
+ nextValue = originalValue.slice(0, cursorOffset) + PASTE_MARKER + originalValue.slice(cursorOffset);
1569
+ nextCursor += 1;
1570
+ } else {
1571
+ nextValue = originalValue.slice(0, cursorOffset) + clean + originalValue.slice(cursorOffset);
1572
+ nextCursor += clean.length;
1573
+ }
1506
1574
  }
1507
1575
  }
1508
1576
  nextCursor = Math.max(0, Math.min(nextCursor, nextValue.length));
@@ -6350,7 +6418,7 @@ var SLASH_COMMANDS = [
6350
6418
  { name: "btw", aliases: ["/aside"], description: "Ask a side question without affecting the main conversation", category: "session" },
6351
6419
  { name: "export", aliases: [], description: "Export conversation as markdown to a file", category: "session" },
6352
6420
  { name: "diff", aliases: ["/changes"], description: "Show files the agent has changed in this session", category: "workspace" },
6353
- { name: "api-keys", aliases: ["/keys"], description: "Set API keys for Semantic Scholar, OpenAlex (e.g. /api-keys semantic-scholar YOUR_KEY)", category: "system" },
6421
+ { name: "api-keys", aliases: ["/keys"], description: "Set API keys for Semantic Scholar, OpenAlex", category: "system" },
6354
6422
  { name: "doctor", aliases: [], description: "Diagnose auth, connectivity, and tool availability", category: "system" },
6355
6423
  { name: "preview", aliases: [], description: "Live preview a LaTeX file in browser (e.g. /preview papers/draft.tex)", category: "workspace" },
6356
6424
  { name: "memory", aliases: ["/memories"], description: "View or clear stored memories about you", category: "system" },
@@ -6369,8 +6437,50 @@ function matchSlashCommand(input2) {
6369
6437
  }
6370
6438
  return null;
6371
6439
  }
6440
+ var SUBCOMMAND_HINTS = {
6441
+ "api-keys": [
6442
+ { name: "api-keys semantic-scholar <key>", description: "Set your Semantic Scholar API key" },
6443
+ { name: "api-keys openalex <key>", description: "Set your OpenAlex API key" }
6444
+ ],
6445
+ "config": [
6446
+ { name: "config theme dark|light", description: "Set color theme" },
6447
+ { name: "config auto-approve on|off", description: "Toggle auto-approve mode" }
6448
+ ],
6449
+ "memory": [
6450
+ { name: "memory clear", description: "Clear all global memories" },
6451
+ { name: "memory clear project", description: "Clear project memories" },
6452
+ { name: "memory clear all", description: "Clear everything" },
6453
+ { name: "memory delete <id>", description: "Delete a specific memory" }
6454
+ ],
6455
+ "compact": [
6456
+ { name: "compact", description: "Compress conversation (auto-selects what to keep)" },
6457
+ { name: "compact keep the statistical findings", description: "Compress but prioritize specific content" }
6458
+ ],
6459
+ "export": [
6460
+ { name: "export", description: "Export to conversation-export.md" },
6461
+ { name: "export <filename>", description: "Export to a specific file" }
6462
+ ],
6463
+ "preview": [
6464
+ { name: "preview <path-to-tex>", description: "Live preview a LaTeX file in browser" }
6465
+ ]
6466
+ };
6372
6467
  function getUnifiedSuggestions(partial, allSkills) {
6373
6468
  if (!partial.startsWith("/")) return [];
6469
+ if (partial.includes(" ")) {
6470
+ const spaceIdx = partial.indexOf(" ");
6471
+ const cmdPart = partial.slice(1, spaceIdx).toLowerCase();
6472
+ const hints = SUBCOMMAND_HINTS[cmdPart];
6473
+ if (hints) {
6474
+ const argPart = partial.slice(spaceIdx + 1).toLowerCase();
6475
+ const filtered = argPart ? hints.filter((h) => h.name.toLowerCase().includes(argPart)) : hints;
6476
+ return filtered.map((h) => ({
6477
+ kind: "command",
6478
+ name: h.name,
6479
+ description: h.description
6480
+ }));
6481
+ }
6482
+ return [];
6483
+ }
6374
6484
  const search = partial.slice(1).toLowerCase();
6375
6485
  if (!search) {
6376
6486
  const cmds = SLASH_COMMANDS.map((c) => ({
@@ -6859,7 +6969,7 @@ function App({
6859
6969
  homeDir
6860
6970
  }) {
6861
6971
  const app = useApp();
6862
- const abortRef = useRef(null);
6972
+ const abortRef = useRef2(null);
6863
6973
  const [input2, setInput] = useState4("");
6864
6974
  const [composerFocused, setComposerFocused] = useState4(true);
6865
6975
  const [busy, setBusy] = useState4(false);
@@ -6891,7 +7001,7 @@ function App({
6891
7001
  const deferredPendingUpdates = useDeferredValue(pendingUpdates);
6892
7002
  const activityFrame = useAnimatedFrame(busy);
6893
7003
  const [agentQuestion, setAgentQuestion] = useState4(null);
6894
- const previewRef = useRef(null);
7004
+ const previewRef = useRef2(null);
6895
7005
  const isHome = deferredMessages.length === 0 && !busy;
6896
7006
  const hasWorkspace = workspacePath !== null;
6897
7007
  const hasAuth = authStatus === "connected";
@@ -6950,7 +7060,7 @@ function App({
6950
7060
  if (atMention) {
6951
7061
  return getFileSuggestions(atMention.partial, workspaceFiles);
6952
7062
  }
6953
- if (!input2.startsWith("/") || input2.includes(" ")) return [];
7063
+ if (!input2.startsWith("/")) return [];
6954
7064
  return getUnifiedSuggestions(input2, skills2);
6955
7065
  }, [input2, skills2, atMention, workspaceFiles]);
6956
7066
  useEffect2(() => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-research",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Local-first research CLI agent — discover papers, synthesize notes, run analysis, and draft artifacts from your terminal.",
5
5
  "type": "module",
6
6
  "license": "MIT",