open-research 0.1.21 → 0.1.23

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 +155 -26
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
 
9
9
  // src/cli.ts
10
10
  import React5 from "react";
11
- import path21 from "path";
11
+ import path22 from "path";
12
12
  import { Command } from "commander";
13
13
  import { render } from "ink";
14
14
 
@@ -811,7 +811,7 @@ function formatDateTime(value) {
811
811
  }
812
812
 
813
813
  // src/lib/cli/version.ts
814
- var PACKAGE_VERSION = "0.1.21";
814
+ var PACKAGE_VERSION = "0.1.23";
815
815
  function getPackageVersion() {
816
816
  return PACKAGE_VERSION;
817
817
  }
@@ -871,7 +871,7 @@ async function ensureOpenResearchConfig(options) {
871
871
  }
872
872
 
873
873
  // src/tui/app.tsx
874
- import path20 from "path";
874
+ import path21 from "path";
875
875
  import {
876
876
  startTransition,
877
877
  useDeferredValue,
@@ -1384,6 +1384,8 @@ var source_default = chalk;
1384
1384
  // src/tui/text-input.tsx
1385
1385
  import { jsx } from "react/jsx-runtime";
1386
1386
  var PASTE_MARKER = "\uFFFC";
1387
+ var BRACKETED_PASTE_START = "[200~";
1388
+ var BRACKETED_PASTE_END = "[201~";
1387
1389
  function expandPasteMarkers(value, pasteMap) {
1388
1390
  let result = "";
1389
1391
  let pasteIdx = 0;
@@ -1428,6 +1430,7 @@ function TextInput({
1428
1430
  const cursorOffsetRef = useRef(originalValue.length);
1429
1431
  const pasteMapRef = useRef(/* @__PURE__ */ new Map());
1430
1432
  const pasteCounterRef = useRef(0);
1433
+ const bracketedPasteBufferRef = useRef(null);
1431
1434
  useEffect(() => {
1432
1435
  valueRef.current = originalValue;
1433
1436
  }, [originalValue]);
@@ -1498,6 +1501,28 @@ function TextInput({
1498
1501
  return result;
1499
1502
  }
1500
1503
  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;
1504
+ function sanitizeInput(raw) {
1505
+ return raw.replace(/\x1b\[[?>=!]*[0-9;]*[a-zA-Z~]/g, "").replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?/g, "").replace(/\[20[01]~/g, "").replace(/\d+;\d+;\d+[~u]/g, "").replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
1506
+ }
1507
+ function insertCleanText(raw, currentValue, currentCursor) {
1508
+ const clean = sanitizeInput(raw);
1509
+ if (!clean) {
1510
+ return { nextValue: currentValue, nextCursor: currentCursor };
1511
+ }
1512
+ const lineCount = (clean.match(/\n/g) || []).length;
1513
+ if (lineCount >= 2) {
1514
+ const id = ++pasteCounterRef.current;
1515
+ pasteMapRef.current.set(id, { text: clean, lineCount, id });
1516
+ return {
1517
+ nextValue: currentValue.slice(0, currentCursor) + PASTE_MARKER + currentValue.slice(currentCursor),
1518
+ nextCursor: currentCursor + 1
1519
+ };
1520
+ }
1521
+ return {
1522
+ nextValue: currentValue.slice(0, currentCursor) + clean + currentValue.slice(currentCursor),
1523
+ nextCursor: currentCursor + clean.length
1524
+ };
1525
+ }
1501
1526
  useInput(
1502
1527
  (input2, key) => {
1503
1528
  const currentValue = valueRef.current;
@@ -1517,7 +1542,7 @@ function TextInput({
1517
1542
  onTab?.();
1518
1543
  return;
1519
1544
  }
1520
- if (key.return && key.shift || key.return && key.meta) {
1545
+ if (key.return && key.shift || key.return && key.meta || input2 === "27;2;13~" || input2.includes("27;2;13")) {
1521
1546
  const inserted = currentValue.slice(0, currentCursor) + "\n" + currentValue.slice(currentCursor);
1522
1547
  cursorOffsetRef.current = currentCursor + 1;
1523
1548
  valueRef.current = inserted;
@@ -1537,10 +1562,12 @@ function TextInput({
1537
1562
  nextValue = currentValue.slice(0, boundary) + currentValue.slice(currentCursor);
1538
1563
  nextCursor = boundary;
1539
1564
  } else if (key.ctrl && input2 === "u") {
1540
- nextValue = currentValue.slice(currentCursor);
1541
- nextCursor = 0;
1565
+ const lineStart = currentValue.lastIndexOf("\n", currentCursor - 1) + 1;
1566
+ nextValue = currentValue.slice(0, lineStart) + currentValue.slice(currentCursor);
1567
+ nextCursor = lineStart;
1542
1568
  } else if (key.ctrl && input2 === "k") {
1543
- nextValue = currentValue.slice(0, currentCursor);
1569
+ const lineEnd = currentValue.indexOf("\n", currentCursor);
1570
+ nextValue = currentValue.slice(0, currentCursor) + (lineEnd === -1 ? "" : currentValue.slice(lineEnd));
1544
1571
  } else if (key.meta && key.delete) {
1545
1572
  const boundary = nextWordBoundary(currentValue, currentCursor);
1546
1573
  nextValue = currentValue.slice(0, currentCursor) + currentValue.slice(boundary);
@@ -1573,18 +1600,54 @@ function TextInput({
1573
1600
  } else if (key.rightArrow) {
1574
1601
  if (showCursor) nextCursor++;
1575
1602
  } else if (!key.ctrl && !key.meta) {
1576
- 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, "");
1577
- if (clean) {
1578
- const lineCount = (clean.match(/\n/g) || []).length;
1579
- if (lineCount >= 2) {
1580
- const id = ++pasteCounterRef.current;
1581
- pasteMapRef.current.set(id, { text: clean, lineCount, id });
1582
- nextValue = currentValue.slice(0, currentCursor) + PASTE_MARKER + currentValue.slice(currentCursor);
1583
- nextCursor += 1;
1584
- } else {
1585
- nextValue = currentValue.slice(0, currentCursor) + clean + currentValue.slice(currentCursor);
1586
- nextCursor += clean.length;
1603
+ const hasPasteMarkers = input2.includes(BRACKETED_PASTE_START) || input2.includes(BRACKETED_PASTE_END) || bracketedPasteBufferRef.current !== null;
1604
+ if (hasPasteMarkers) {
1605
+ let remaining = input2;
1606
+ let workingValue = currentValue;
1607
+ let workingCursor = currentCursor;
1608
+ while (remaining.length > 0) {
1609
+ if (bracketedPasteBufferRef.current === null) {
1610
+ const startIndex = remaining.indexOf(BRACKETED_PASTE_START);
1611
+ if (startIndex === -1) {
1612
+ const inserted2 = insertCleanText(remaining, workingValue, workingCursor);
1613
+ workingValue = inserted2.nextValue;
1614
+ workingCursor = inserted2.nextCursor;
1615
+ remaining = "";
1616
+ break;
1617
+ }
1618
+ const prefix = remaining.slice(0, startIndex);
1619
+ if (prefix) {
1620
+ const inserted2 = insertCleanText(prefix, workingValue, workingCursor);
1621
+ workingValue = inserted2.nextValue;
1622
+ workingCursor = inserted2.nextCursor;
1623
+ }
1624
+ bracketedPasteBufferRef.current = "";
1625
+ remaining = remaining.slice(startIndex + BRACKETED_PASTE_START.length);
1626
+ continue;
1627
+ }
1628
+ const endIndex = remaining.indexOf(BRACKETED_PASTE_END);
1629
+ if (endIndex === -1) {
1630
+ bracketedPasteBufferRef.current += remaining;
1631
+ remaining = "";
1632
+ break;
1633
+ }
1634
+ bracketedPasteBufferRef.current += remaining.slice(0, endIndex);
1635
+ const inserted = insertCleanText(
1636
+ bracketedPasteBufferRef.current,
1637
+ workingValue,
1638
+ workingCursor
1639
+ );
1640
+ workingValue = inserted.nextValue;
1641
+ workingCursor = inserted.nextCursor;
1642
+ bracketedPasteBufferRef.current = null;
1643
+ remaining = remaining.slice(endIndex + BRACKETED_PASTE_END.length);
1587
1644
  }
1645
+ nextValue = workingValue;
1646
+ nextCursor = workingCursor;
1647
+ } else {
1648
+ const inserted = insertCleanText(input2, currentValue, currentCursor);
1649
+ nextValue = inserted.nextValue;
1650
+ nextCursor = inserted.nextCursor;
1588
1651
  }
1589
1652
  }
1590
1653
  nextCursor = Math.max(0, Math.min(nextCursor, nextValue.length));
@@ -5351,6 +5414,7 @@ ${skill.prompt}`).join("\n\n");
5351
5414
  "- Be transparent. Show the user what you're doing and why.",
5352
5415
  "- When unsure, ask. Use ask_user rather than guessing.",
5353
5416
  "- For large outputs, redirect to a file and read selectively.",
5417
+ "- Always wrap file paths in backticks when mentioning them, e.g. `notes/brief.md` or `experiments/analysis.py`. This makes them clickable in the terminal.",
5354
5418
  "",
5355
5419
  `## Workspace
5356
5420
  Root: ${process.cwd()}
@@ -6553,6 +6617,66 @@ function truncate3(value, max = 96) {
6553
6617
  import { Box as Box4, Text as Text4 } from "ink";
6554
6618
 
6555
6619
  // src/tui/markdown.ts
6620
+ import path20 from "path";
6621
+ import fs21 from "fs";
6622
+ var FILE_EXTENSIONS = /* @__PURE__ */ new Set([
6623
+ ".py",
6624
+ ".ts",
6625
+ ".tsx",
6626
+ ".js",
6627
+ ".jsx",
6628
+ ".r",
6629
+ ".R",
6630
+ ".tex",
6631
+ ".bib",
6632
+ ".md",
6633
+ ".txt",
6634
+ ".json",
6635
+ ".yaml",
6636
+ ".yml",
6637
+ ".toml",
6638
+ ".csv",
6639
+ ".tsv",
6640
+ ".sh",
6641
+ ".bash",
6642
+ ".zsh",
6643
+ ".sql",
6644
+ ".html",
6645
+ ".css",
6646
+ ".xml",
6647
+ ".pdf",
6648
+ ".png",
6649
+ ".jpg",
6650
+ ".svg",
6651
+ ".gif",
6652
+ ".cfg",
6653
+ ".ini",
6654
+ ".env",
6655
+ ".lock",
6656
+ ".log"
6657
+ ]);
6658
+ function looksLikeFilePath(text) {
6659
+ if (text.length < 3 || text.length > 200) return false;
6660
+ if (text.includes(" ") || text.includes("\n")) return false;
6661
+ const ext = path20.extname(text).toLowerCase();
6662
+ if (FILE_EXTENSIONS.has(ext)) return true;
6663
+ if (text.includes("/") && !text.startsWith("http")) return true;
6664
+ return false;
6665
+ }
6666
+ function resolveFilePath(filePath) {
6667
+ if (path20.isAbsolute(filePath)) return filePath;
6668
+ return path20.resolve(process.cwd(), filePath);
6669
+ }
6670
+ function fileLink(displayText, filePath) {
6671
+ const absPath = resolveFilePath(filePath);
6672
+ try {
6673
+ fs21.statSync(absPath);
6674
+ } catch {
6675
+ return displayText;
6676
+ }
6677
+ const uri = `file://${absPath}`;
6678
+ return `\x1B]8;;${uri}\x1B\\${displayText}\x1B]8;;\x1B\\`;
6679
+ }
6556
6680
  function renderMarkdown(text) {
6557
6681
  if (!text || !text.trim()) return text;
6558
6682
  if (!/[*_`#\[\]>~\-]/.test(text) && !text.includes("```")) return text;
@@ -6635,7 +6759,12 @@ function renderMarkdown(text) {
6635
6759
  }
6636
6760
  function renderInline(text) {
6637
6761
  let result = text;
6638
- result = result.replace(/`([^`]+)`/g, (_, code) => source_default.cyan(code));
6762
+ result = result.replace(/`([^`]+)`/g, (_, code) => {
6763
+ if (looksLikeFilePath(code)) {
6764
+ return fileLink(source_default.cyan.underline(code), code);
6765
+ }
6766
+ return source_default.cyan(code);
6767
+ });
6639
6768
  result = result.replace(/\*\*\*(.+?)\*\*\*/g, (_, t) => source_default.bold.italic(t));
6640
6769
  result = result.replace(/___(.+?)___/g, (_, t) => source_default.bold.italic(t));
6641
6770
  result = result.replace(/\*\*(.+?)\*\*/g, (_, t) => source_default.bold(t));
@@ -8242,7 +8371,7 @@ ${error.stack}` : String(error)}` }
8242
8371
  statusParts,
8243
8372
  statusColor,
8244
8373
  tokenDisplay,
8245
- workspaceName: hasWorkspace ? path20.basename(workspacePath) : process.cwd(),
8374
+ workspaceName: hasWorkspace ? path21.basename(workspacePath) : process.cwd(),
8246
8375
  mode: agentMode,
8247
8376
  planningStatus: planningState.status
8248
8377
  }
@@ -8254,7 +8383,7 @@ ${error.stack}` : String(error)}` }
8254
8383
  var program = new Command();
8255
8384
  program.name("open-research").version(getPackageVersion()).description("Local-first research CLI powered by ChatGPT/Codex auth.").argument("[workspacePath]", "Optional workspace path to open").action(async (workspacePath) => {
8256
8385
  await ensureOpenResearchConfig();
8257
- const target = workspacePath ? path21.resolve(workspacePath) : process.cwd();
8386
+ const target = workspacePath ? path22.resolve(workspacePath) : process.cwd();
8258
8387
  const project = await loadWorkspaceProject(target);
8259
8388
  const auth2 = await loadStoredAuth();
8260
8389
  render(
@@ -8270,7 +8399,7 @@ program.name("open-research").version(getPackageVersion()).description("Local-fi
8270
8399
  });
8271
8400
  program.command("init").argument("[workspacePath]").description("Initialize an Open Research workspace.").action(async (workspacePath) => {
8272
8401
  await ensureOpenResearchConfig();
8273
- const target = path21.resolve(workspacePath ?? process.cwd());
8402
+ const target = path22.resolve(workspacePath ?? process.cwd());
8274
8403
  const project = await initWorkspace({ workspaceDir: target });
8275
8404
  console.log(`Initialized workspace: ${target}`);
8276
8405
  console.log(`Title: ${project.title}`);
@@ -8339,8 +8468,8 @@ skills.command("create").argument("[name]").description("Scaffold a new user ski
8339
8468
  });
8340
8469
  skills.command("edit").argument("<name>").description("Open a user skill in $EDITOR.").action(async (name) => {
8341
8470
  await ensureOpenResearchConfig();
8342
- const skillDir = path21.join(getOpenResearchSkillsDir(), name);
8343
- openInEditor(path21.join(skillDir, "SKILL.md"));
8471
+ const skillDir = path22.join(getOpenResearchSkillsDir(), name);
8472
+ openInEditor(path22.join(skillDir, "SKILL.md"));
8344
8473
  const validation = await validateSkillDirectory({ skillDir });
8345
8474
  if (!validation.ok) {
8346
8475
  console.error(validation.errors.join("\n"));
@@ -8351,9 +8480,9 @@ skills.command("edit").argument("<name>").description("Open a user skill in $EDI
8351
8480
  });
8352
8481
  skills.command("validate").argument("[nameOrPath]").description("Validate one user skill.").action(async (nameOrPath) => {
8353
8482
  await ensureOpenResearchConfig();
8354
- const skillDir = nameOrPath ? path21.isAbsolute(nameOrPath) ? nameOrPath : path21.join(getOpenResearchSkillsDir(), nameOrPath) : getOpenResearchSkillsDir();
8483
+ const skillDir = nameOrPath ? path22.isAbsolute(nameOrPath) ? nameOrPath : path22.join(getOpenResearchSkillsDir(), nameOrPath) : getOpenResearchSkillsDir();
8355
8484
  const stat = await import("fs/promises").then(
8356
- (fs21) => fs21.stat(skillDir).catch(() => null)
8485
+ (fs22) => fs22.stat(skillDir).catch(() => null)
8357
8486
  );
8358
8487
  if (!stat) {
8359
8488
  throw new Error(`Skill path not found: ${skillDir}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-research",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
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",