open-research 0.1.23 → 0.1.25

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 +85 -100
  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 path22 from "path";
11
+ import path21 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.23";
814
+ var PACKAGE_VERSION = "0.1.25";
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 path21 from "path";
874
+ import path20 from "path";
875
875
  import {
876
876
  startTransition,
877
877
  useDeferredValue,
@@ -5414,7 +5414,7 @@ ${skill.prompt}`).join("\n\n");
5414
5414
  "- Be transparent. Show the user what you're doing and why.",
5415
5415
  "- When unsure, ask. Use ask_user rather than guessing.",
5416
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.",
5417
+ "- Always wrap file paths in backticks: `notes/brief.md`, `experiments/analysis.py`. Include line references as `src/file.ts:42`. This makes them clickable.",
5418
5418
  "",
5419
5419
  `## Workspace
5420
5420
  Root: ${process.cwd()}
@@ -6617,67 +6617,7 @@ function truncate3(value, max = 96) {
6617
6617
  import { Box as Box4, Text as Text4 } from "ink";
6618
6618
 
6619
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
- }
6680
- function renderMarkdown(text) {
6620
+ function renderMarkdown(text, options = {}) {
6681
6621
  if (!text || !text.trim()) return text;
6682
6622
  if (!/[*_`#\[\]>~\-]/.test(text) && !text.includes("```")) return text;
6683
6623
  const lines = text.split("\n");
@@ -6713,13 +6653,9 @@ function renderMarkdown(text) {
6713
6653
  if (headingMatch) {
6714
6654
  const level = headingMatch[1].length;
6715
6655
  const content = renderInline(headingMatch[2]);
6716
- if (level === 1) {
6717
- output2.push(source_default.bold.cyan(content));
6718
- } else if (level === 2) {
6719
- output2.push(source_default.bold.white(content));
6720
- } else {
6721
- output2.push(source_default.bold(content));
6722
- }
6656
+ if (level === 1) output2.push(source_default.bold.cyan(content));
6657
+ else if (level === 2) output2.push(source_default.bold.white(content));
6658
+ else output2.push(source_default.bold(content));
6723
6659
  continue;
6724
6660
  }
6725
6661
  if (/^[-*_]{3,}\s*$/.test(line.trim())) {
@@ -6733,17 +6669,12 @@ function renderMarkdown(text) {
6733
6669
  }
6734
6670
  const ulMatch = line.match(/^(\s*)[*+-]\s+(.+)$/);
6735
6671
  if (ulMatch) {
6736
- const indent = ulMatch[1];
6737
- const content = renderInline(ulMatch[2]);
6738
- output2.push(`${indent}${source_default.gray("\u2022")} ${content}`);
6672
+ output2.push(`${ulMatch[1]}${source_default.gray("\u2022")} ${renderInline(ulMatch[2])}`);
6739
6673
  continue;
6740
6674
  }
6741
6675
  const olMatch = line.match(/^(\s*)(\d+)[.)]\s+(.+)$/);
6742
6676
  if (olMatch) {
6743
- const indent = olMatch[1];
6744
- const num = olMatch[2];
6745
- const content = renderInline(olMatch[3]);
6746
- output2.push(`${indent}${source_default.gray(num + ".")} ${content}`);
6677
+ output2.push(`${olMatch[1]}${source_default.gray(olMatch[2] + ".")} ${renderInline(olMatch[3])}`);
6747
6678
  continue;
6748
6679
  }
6749
6680
  output2.push(renderInline(line));
@@ -6759,12 +6690,7 @@ function renderMarkdown(text) {
6759
6690
  }
6760
6691
  function renderInline(text) {
6761
6692
  let result = text;
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
- });
6693
+ result = result.replace(/`([^`]+)`/g, (_, code) => source_default.cyan(code));
6768
6694
  result = result.replace(/\*\*\*(.+?)\*\*\*/g, (_, t) => source_default.bold.italic(t));
6769
6695
  result = result.replace(/___(.+?)___/g, (_, t) => source_default.bold.italic(t));
6770
6696
  result = result.replace(/\*\*(.+?)\*\*/g, (_, t) => source_default.bold(t));
@@ -7144,12 +7070,14 @@ function App({
7144
7070
  const [cursorToEnd, setCursorToEnd] = useState4(0);
7145
7071
  const [screen, setScreen] = useState4("main");
7146
7072
  const [resumeSessions, setResumeSessions] = useState4([]);
7073
+ const [ctrlCPending, setCtrlCPending] = useState4(false);
7147
7074
  const sessionId = useMemo3(() => crypto.randomUUID(), []);
7148
7075
  const deferredMessages = useDeferredValue(messages);
7149
7076
  const deferredPendingUpdates = useDeferredValue(pendingUpdates);
7150
7077
  const activityFrame = useAnimatedFrame(busy);
7151
7078
  const [agentQuestion, setAgentQuestion] = useState4(null);
7152
7079
  const previewRef = useRef2(null);
7080
+ const ctrlCTimerRef = useRef2(null);
7153
7081
  const isHome = deferredMessages.length === 0 && !busy;
7154
7082
  const hasWorkspace = workspacePath !== null;
7155
7083
  const hasAuth = authStatus === "connected";
@@ -7202,6 +7130,13 @@ function App({
7202
7130
  cancelled = true;
7203
7131
  };
7204
7132
  }, [homeDir]);
7133
+ useEffect2(() => {
7134
+ return () => {
7135
+ if (ctrlCTimerRef.current) {
7136
+ clearTimeout(ctrlCTimerRef.current);
7137
+ }
7138
+ };
7139
+ }, []);
7205
7140
  const [selectedSuggestion, setSelectedSuggestion] = useState4(-1);
7206
7141
  const atMention = useMemo3(() => extractAtMention(input2), [input2]);
7207
7142
  const suggestions = useMemo3(() => {
@@ -7733,7 +7668,58 @@ ${msg.text}
7733
7668
  addSystemMessage(`Rejected: ${next.summary}`);
7734
7669
  });
7735
7670
  }
7671
+ function clearCtrlCPending() {
7672
+ if (ctrlCTimerRef.current) {
7673
+ clearTimeout(ctrlCTimerRef.current);
7674
+ ctrlCTimerRef.current = null;
7675
+ }
7676
+ setCtrlCPending(false);
7677
+ }
7678
+ function armCtrlCExitWindow() {
7679
+ if (ctrlCTimerRef.current) {
7680
+ clearTimeout(ctrlCTimerRef.current);
7681
+ }
7682
+ setCtrlCPending(true);
7683
+ ctrlCTimerRef.current = setTimeout(() => {
7684
+ ctrlCTimerRef.current = null;
7685
+ setCtrlCPending(false);
7686
+ }, 3e3);
7687
+ }
7688
+ function returnToMainScreen() {
7689
+ setScreen("main");
7690
+ setComposerFocused(true);
7691
+ }
7736
7692
  useInput4((key, inputKey) => {
7693
+ if (inputKey.ctrl && key === "c") {
7694
+ if (busy) {
7695
+ clearCtrlCPending();
7696
+ if (abortRef.current) {
7697
+ abortRef.current.abort();
7698
+ addSystemMessage("Interrupting agent...");
7699
+ }
7700
+ return;
7701
+ }
7702
+ if (screen !== "main") {
7703
+ clearCtrlCPending();
7704
+ returnToMainScreen();
7705
+ return;
7706
+ }
7707
+ if (planningState.status === "charter-review") {
7708
+ clearCtrlCPending();
7709
+ rejectCharter();
7710
+ return;
7711
+ }
7712
+ if (ctrlCPending) {
7713
+ clearCtrlCPending();
7714
+ app.exit();
7715
+ return;
7716
+ }
7717
+ armCtrlCExitWindow();
7718
+ return;
7719
+ }
7720
+ if (ctrlCPending) {
7721
+ clearCtrlCPending();
7722
+ }
7737
7723
  if (inputKey.shift && inputKey.tab) {
7738
7724
  setAgentMode((prev) => {
7739
7725
  const modes = ["manual-review", "auto-approve", "auto-research"];
@@ -8132,6 +8118,7 @@ ${error.stack}` : String(error)}` }
8132
8118
  addSystemMessage("Charter cancelled. Planning reset.");
8133
8119
  }
8134
8120
  const statusParts = [];
8121
+ if (ctrlCPending) statusParts.push("Press Ctrl+C again to exit.");
8135
8122
  if (hasAuth) statusParts.push("connected");
8136
8123
  else statusParts.push("no auth");
8137
8124
  if (hasWorkspace) statusParts.push(`${workspaceFiles.length} files`);
@@ -8139,7 +8126,7 @@ ${error.stack}` : String(error)}` }
8139
8126
  if (skills2.length > 0) statusParts.push(`${skills2.length} skills`);
8140
8127
  statusParts.push(agentMode);
8141
8128
  if (deferredPendingUpdates.length > 0) statusParts.push(`${deferredPendingUpdates.length} pending`);
8142
- const statusColor = busy ? "yellow" : !hasAuth ? "red" : deferredPendingUpdates.length > 0 ? "magenta" : "green";
8129
+ const statusColor = busy ? "yellow" : ctrlCPending ? "yellow" : !hasAuth ? "red" : deferredPendingUpdates.length > 0 ? "magenta" : "green";
8143
8130
  const configItems = useMemo3(() => [
8144
8131
  {
8145
8132
  key: "defaults.model",
@@ -8194,8 +8181,7 @@ ${error.stack}` : String(error)}` }
8194
8181
  await saveOpenResearchConfig(updated, { homeDir });
8195
8182
  }
8196
8183
  function handleConfigClose() {
8197
- setScreen("main");
8198
- setComposerFocused(true);
8184
+ returnToMainScreen();
8199
8185
  }
8200
8186
  if (screen === "resume") {
8201
8187
  return /* @__PURE__ */ jsx5(
@@ -8213,12 +8199,10 @@ ${error.stack}` : String(error)}` }
8213
8199
  } catch (err) {
8214
8200
  addSystemMessage(`Failed: ${err instanceof Error ? err.message : String(err)}`);
8215
8201
  }
8216
- setScreen("main");
8217
- setComposerFocused(true);
8202
+ returnToMainScreen();
8218
8203
  },
8219
8204
  onCancel: () => {
8220
- setScreen("main");
8221
- setComposerFocused(true);
8205
+ returnToMainScreen();
8222
8206
  }
8223
8207
  }
8224
8208
  );
@@ -8371,7 +8355,7 @@ ${error.stack}` : String(error)}` }
8371
8355
  statusParts,
8372
8356
  statusColor,
8373
8357
  tokenDisplay,
8374
- workspaceName: hasWorkspace ? path21.basename(workspacePath) : process.cwd(),
8358
+ workspaceName: hasWorkspace ? path20.basename(workspacePath) : process.cwd(),
8375
8359
  mode: agentMode,
8376
8360
  planningStatus: planningState.status
8377
8361
  }
@@ -8383,7 +8367,7 @@ ${error.stack}` : String(error)}` }
8383
8367
  var program = new Command();
8384
8368
  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) => {
8385
8369
  await ensureOpenResearchConfig();
8386
- const target = workspacePath ? path22.resolve(workspacePath) : process.cwd();
8370
+ const target = workspacePath ? path21.resolve(workspacePath) : process.cwd();
8387
8371
  const project = await loadWorkspaceProject(target);
8388
8372
  const auth2 = await loadStoredAuth();
8389
8373
  render(
@@ -8394,12 +8378,13 @@ program.name("open-research").version(getPackageVersion()).description("Local-fi
8394
8378
  screen: "home",
8395
8379
  pendingUpdates: []
8396
8380
  }
8397
- })
8381
+ }),
8382
+ { exitOnCtrlC: false }
8398
8383
  );
8399
8384
  });
8400
8385
  program.command("init").argument("[workspacePath]").description("Initialize an Open Research workspace.").action(async (workspacePath) => {
8401
8386
  await ensureOpenResearchConfig();
8402
- const target = path22.resolve(workspacePath ?? process.cwd());
8387
+ const target = path21.resolve(workspacePath ?? process.cwd());
8403
8388
  const project = await initWorkspace({ workspaceDir: target });
8404
8389
  console.log(`Initialized workspace: ${target}`);
8405
8390
  console.log(`Title: ${project.title}`);
@@ -8468,8 +8453,8 @@ skills.command("create").argument("[name]").description("Scaffold a new user ski
8468
8453
  });
8469
8454
  skills.command("edit").argument("<name>").description("Open a user skill in $EDITOR.").action(async (name) => {
8470
8455
  await ensureOpenResearchConfig();
8471
- const skillDir = path22.join(getOpenResearchSkillsDir(), name);
8472
- openInEditor(path22.join(skillDir, "SKILL.md"));
8456
+ const skillDir = path21.join(getOpenResearchSkillsDir(), name);
8457
+ openInEditor(path21.join(skillDir, "SKILL.md"));
8473
8458
  const validation = await validateSkillDirectory({ skillDir });
8474
8459
  if (!validation.ok) {
8475
8460
  console.error(validation.errors.join("\n"));
@@ -8480,9 +8465,9 @@ skills.command("edit").argument("<name>").description("Open a user skill in $EDI
8480
8465
  });
8481
8466
  skills.command("validate").argument("[nameOrPath]").description("Validate one user skill.").action(async (nameOrPath) => {
8482
8467
  await ensureOpenResearchConfig();
8483
- const skillDir = nameOrPath ? path22.isAbsolute(nameOrPath) ? nameOrPath : path22.join(getOpenResearchSkillsDir(), nameOrPath) : getOpenResearchSkillsDir();
8468
+ const skillDir = nameOrPath ? path21.isAbsolute(nameOrPath) ? nameOrPath : path21.join(getOpenResearchSkillsDir(), nameOrPath) : getOpenResearchSkillsDir();
8484
8469
  const stat = await import("fs/promises").then(
8485
- (fs22) => fs22.stat(skillDir).catch(() => null)
8470
+ (fs21) => fs21.stat(skillDir).catch(() => null)
8486
8471
  );
8487
8472
  if (!stat) {
8488
8473
  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.23",
3
+ "version": "0.1.25",
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",