open-research 0.1.7 → 0.1.9

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 +293 -48
  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 React4 from "react";
11
- import path19 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.7";
814
+ var PACKAGE_VERSION = "0.1.9";
815
815
  function getPackageVersion() {
816
816
  return PACKAGE_VERSION;
817
817
  }
@@ -860,7 +860,7 @@ async function ensureOpenResearchConfig(options) {
860
860
  }
861
861
 
862
862
  // src/tui/app.tsx
863
- import path18 from "path";
863
+ import path20 from "path";
864
864
  import {
865
865
  startTransition,
866
866
  useDeferredValue,
@@ -1526,10 +1526,28 @@ var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
1526
1526
  ".yml",
1527
1527
  ".csv"
1528
1528
  ]);
1529
+ var IGNORED_DIRS = /* @__PURE__ */ new Set([
1530
+ ".open-research",
1531
+ "node_modules",
1532
+ ".git",
1533
+ "dist",
1534
+ "build",
1535
+ ".next",
1536
+ "__pycache__",
1537
+ ".venv",
1538
+ "venv",
1539
+ ".cache",
1540
+ "target",
1541
+ ".tox",
1542
+ "coverage",
1543
+ ".nyc_output",
1544
+ ".parcel-cache",
1545
+ ".turbo"
1546
+ ]);
1529
1547
  async function walkDir(rootDir, currentDir, out) {
1530
1548
  const entries = await fs8.readdir(currentDir, { withFileTypes: true });
1531
1549
  for (const entry of entries) {
1532
- if (entry.name === ".open-research") {
1550
+ if (IGNORED_DIRS.has(entry.name)) {
1533
1551
  continue;
1534
1552
  }
1535
1553
  const fullPath = path7.join(currentDir, entry.name);
@@ -2286,9 +2304,6 @@ function getToolsForMode(mode) {
2286
2304
 
2287
2305
  // src/lib/agent/prompts/planning.ts
2288
2306
  function buildPlanningSystemPrompt(ctx, activeSkills) {
2289
- const workspaceMap = ctx.availableKeys.map(
2290
- (key) => `- ${key}${ctx.fileLabels?.[key] ? ` \u2014 ${ctx.fileLabels[key]}` : ""}`
2291
- ).join("\n");
2292
2307
  const skillText = activeSkills.map((skill) => `## Active Skill: ${skill.name}
2293
2308
  ${skill.prompt}`).join("\n\n");
2294
2309
  return [
@@ -2354,8 +2369,9 @@ ${skill.prompt}`).join("\n\n");
2354
2369
  "- If the user's intent is already crystal clear, you can produce the charter after just 1 question or even immediately.",
2355
2370
  "- Ground your proposed steps in what you've learned from the workspace and external search results.",
2356
2371
  "",
2357
- workspaceMap ? `## Workspace Files
2358
- ${workspaceMap}` : "## Workspace Files\nnone",
2372
+ `## Workspace
2373
+ Root: ${process.cwd()}
2374
+ Use list_directory to explore. Use search_workspace or read_file to read content.`,
2359
2375
  skillText
2360
2376
  ].filter(Boolean).join("\n");
2361
2377
  }
@@ -4913,6 +4929,72 @@ async function extractAndStoreMemories(input2) {
4913
4929
  return stored;
4914
4930
  }
4915
4931
 
4932
+ // src/lib/workspace/agents-md.ts
4933
+ import fs15 from "fs/promises";
4934
+ import path14 from "path";
4935
+ var AGENTS_MD_FILENAME = "AGENTS.md";
4936
+ async function readAgentsMd(workspaceDir) {
4937
+ try {
4938
+ return await fs15.readFile(path14.join(workspaceDir, AGENTS_MD_FILENAME), "utf8");
4939
+ } catch {
4940
+ return "";
4941
+ }
4942
+ }
4943
+ async function writeAgentsMd(workspaceDir, content) {
4944
+ await fs15.writeFile(path14.join(workspaceDir, AGENTS_MD_FILENAME), content, "utf8");
4945
+ }
4946
+ var UPDATE_SYSTEM_PROMPT = `You maintain an AGENTS.md file for a research workspace. This file gives future agent sessions instant context about the project \u2014 what it's about, what's been done, key files, and current direction.
4947
+
4948
+ You will receive:
4949
+ 1. The current AGENTS.md content (may be empty on first run)
4950
+ 2. A summary of what just happened in the latest conversation turn
4951
+
4952
+ Your job: decide if AGENTS.md should be updated based on the new information. If yes, output the FULL updated AGENTS.md content. If nothing meaningful changed, output exactly "NO_UPDATE".
4953
+
4954
+ Rules:
4955
+ - Keep it concise \u2014 under 2000 characters. This gets injected into every system prompt.
4956
+ - Structure: Project overview \u2192 Key files \u2192 Current state \u2192 Research direction
4957
+ - Only include information that helps a NEW agent session pick up where this one left off
4958
+ - Don't include conversation-specific details \u2014 only durable project knowledge
4959
+ - Update incrementally \u2014 preserve existing content, add/modify what changed
4960
+ - Use markdown with ## headings`;
4961
+ var UPDATE_USER_TEMPLATE = `Current AGENTS.md:
4962
+ ---
4963
+ {CURRENT_CONTENT}
4964
+ ---
4965
+
4966
+ Latest turn summary:
4967
+ User asked: {USER_MESSAGE}
4968
+ Agent did: {AGENT_SUMMARY}
4969
+
4970
+ Should AGENTS.md be updated? If yes, output the full updated content. If no meaningful project-level changes, output exactly "NO_UPDATE".`;
4971
+ async function maybeUpdateAgentsMd(input2) {
4972
+ if (input2.userMessage.length < 15) return false;
4973
+ if (input2.userMessage.startsWith("/")) return false;
4974
+ const currentContent = await readAgentsMd(input2.workspaceDir);
4975
+ const userPrompt = UPDATE_USER_TEMPLATE.replace("{CURRENT_CONTENT}", currentContent || "(empty \u2014 first time)").replace("{USER_MESSAGE}", input2.userMessage.slice(0, 500)).replace("{AGENT_SUMMARY}", input2.agentResponse.slice(0, 1500));
4976
+ try {
4977
+ const response = await input2.provider.callLLM({
4978
+ messages: [
4979
+ { role: "system", content: UPDATE_SYSTEM_PROMPT },
4980
+ { role: "user", content: userPrompt }
4981
+ ],
4982
+ model: input2.model ?? "gpt-5.4-mini",
4983
+ maxTokens: 2048,
4984
+ temperature: 0
4985
+ });
4986
+ const result = response.content.trim();
4987
+ if (result === "NO_UPDATE" || result.length < 20) {
4988
+ return false;
4989
+ }
4990
+ const cleaned = result.replace(/^```(?:markdown)?\n?/, "").replace(/\n?```$/, "").trim();
4991
+ await writeAgentsMd(input2.workspaceDir, cleaned);
4992
+ return true;
4993
+ } catch {
4994
+ return false;
4995
+ }
4996
+ }
4997
+
4916
4998
  // src/lib/agent/runtime.ts
4917
4999
  var TOOL_DESCRIPTIONS = {
4918
5000
  read_file: (a) => `Reading ${a.file_path ?? "file"}`,
@@ -4948,7 +5030,6 @@ function describeToolCall(name, args) {
4948
5030
  return fn ? fn(args) : `Running ${name}`;
4949
5031
  }
4950
5032
  function buildSystemPrompt(ctx, activeSkills) {
4951
- const workspaceMap = ctx.availableKeys.map((key) => `- ${key}${ctx.fileLabels?.[key] ? ` \u2014 ${ctx.fileLabels[key]}` : ""}`).join("\n");
4952
5033
  const skillText = activeSkills.map((skill) => `## Active Skill: ${skill.name}
4953
5034
  ${skill.prompt}`).join("\n\n");
4954
5035
  return [
@@ -4956,8 +5037,9 @@ ${skill.prompt}`).join("\n\n");
4956
5037
  "",
4957
5038
  "## Capabilities",
4958
5039
  "You have full access to the local filesystem and shell. You can:",
4959
- "- Read any file on disk with read_file (not limited to workspace files)",
4960
- "- List directories with list_directory to explore project structure",
5040
+ "- Read any file on disk with read_file",
5041
+ "- List directories with list_directory to explore the workspace and discover files",
5042
+ "- Search file contents with search_workspace",
4961
5043
  "- Run shell commands with run_command (python, R, node, LaTeX, curl, git, etc.)",
4962
5044
  "- Write new workspace files or edit existing ones",
4963
5045
  "- Search academic papers across OpenAlex, Semantic Scholar, and arXiv",
@@ -4966,15 +5048,17 @@ ${skill.prompt}`).join("\n\n");
4966
5048
  "- Activate research skills for specialized workflows",
4967
5049
  "",
4968
5050
  "## Principles",
4969
- "- Read before writing. Understand the workspace before making changes.",
5051
+ "- Start by exploring. Use list_directory and search_workspace to understand the workspace before acting.",
5052
+ "- Read before writing. Understand existing files before making changes.",
4970
5053
  "- Ground claims in sources. Cite papers and data, not assumptions.",
4971
5054
  "- Run code to verify. When you write a script, run it and check the output.",
4972
5055
  "- Be transparent. Show the user what you're doing and why.",
4973
5056
  "- When unsure, ask. Use ask_user rather than guessing.",
4974
5057
  "- For large outputs, redirect to a file and read selectively.",
4975
5058
  "",
4976
- workspaceMap ? `## Workspace Files
4977
- ${workspaceMap}` : "## Workspace Files\nnone",
5059
+ `## Workspace
5060
+ Root: ${process.cwd()}
5061
+ Use list_directory to explore. Use search_workspace or read_file to read content.`,
4978
5062
  skillText
4979
5063
  ].filter(Boolean).join("\n");
4980
5064
  }
@@ -5001,7 +5085,14 @@ async function runAgentTurn(input2) {
5001
5085
  const usage = input2.sessionUsage ?? createSessionUsage();
5002
5086
  const memories = await loadMemories({ homeDir: input2.homeDir });
5003
5087
  const memoryBlock = formatMemoriesForPrompt(memories);
5004
- const fullSystemPrompt = memoryBlock ? systemPrompt + "\n\n" + memoryBlock : systemPrompt;
5088
+ const agentsMd = input2.workspace.workspaceDir ? await readAgentsMd(input2.workspace.workspaceDir).catch(() => "") : "";
5089
+ const contextBlocks = [
5090
+ systemPrompt,
5091
+ memoryBlock ? memoryBlock : null,
5092
+ agentsMd ? `## Project Context (from AGENTS.md)
5093
+ ${agentsMd}` : null
5094
+ ].filter(Boolean).join("\n\n");
5095
+ const fullSystemPrompt = contextBlocks;
5005
5096
  let messages = [
5006
5097
  { role: "system", content: fullSystemPrompt },
5007
5098
  ...input2.history,
@@ -5068,6 +5159,18 @@ async function runAgentTurn(input2) {
5068
5159
  }
5069
5160
  }).catch(() => {
5070
5161
  });
5162
+ if (input2.workspace.workspaceDir) {
5163
+ maybeUpdateAgentsMd({
5164
+ workspaceDir: input2.workspace.workspaceDir,
5165
+ userMessage: input2.message,
5166
+ agentResponse: fullText,
5167
+ provider: input2.provider,
5168
+ model: "gpt-5.4-mini"
5169
+ }).then((updated) => {
5170
+ if (updated) input2.onAgentsMdUpdated?.();
5171
+ }).catch(() => {
5172
+ });
5173
+ }
5071
5174
  return {
5072
5175
  text: fullText,
5073
5176
  proposedUpdates,
@@ -5159,8 +5262,8 @@ function classifyUpdateRisk(update) {
5159
5262
  }
5160
5263
 
5161
5264
  // src/lib/workspace/apply-update.ts
5162
- import fs15 from "fs/promises";
5163
- import path14 from "path";
5265
+ import fs16 from "fs/promises";
5266
+ import path15 from "path";
5164
5267
  function resolveRelativePath(update) {
5165
5268
  if (update.key.startsWith("path:")) {
5166
5269
  return update.key.slice(5);
@@ -5181,20 +5284,20 @@ function resolveRelativePath(update) {
5181
5284
  }
5182
5285
  async function applyProposedUpdate(workspaceDir, update) {
5183
5286
  const relativePath = resolveRelativePath(update);
5184
- const absolutePath = path14.join(workspaceDir, relativePath);
5185
- await fs15.mkdir(path14.dirname(absolutePath), { recursive: true });
5186
- await fs15.writeFile(absolutePath, update.content, "utf8");
5287
+ const absolutePath = path15.join(workspaceDir, relativePath);
5288
+ await fs16.mkdir(path15.dirname(absolutePath), { recursive: true });
5289
+ await fs16.writeFile(absolutePath, update.content, "utf8");
5187
5290
  return absolutePath;
5188
5291
  }
5189
5292
 
5190
5293
  // src/lib/workspace/sessions.ts
5191
- import fs16 from "fs/promises";
5192
- import path15 from "path";
5294
+ import fs17 from "fs/promises";
5295
+ import path16 from "path";
5193
5296
  async function appendSessionEvent(workspaceDir, sessionId, event) {
5194
5297
  const sessionsDir = getWorkspaceSessionsDir(workspaceDir);
5195
- await fs16.mkdir(sessionsDir, { recursive: true });
5196
- const sessionFile = path15.join(sessionsDir, `${sessionId}.jsonl`);
5197
- await fs16.appendFile(sessionFile, `${JSON.stringify(event)}
5298
+ await fs17.mkdir(sessionsDir, { recursive: true });
5299
+ const sessionFile = path16.join(sessionsDir, `${sessionId}.jsonl`);
5300
+ await fs17.appendFile(sessionFile, `${JSON.stringify(event)}
5198
5301
  `, "utf8");
5199
5302
  }
5200
5303
  function parseEvents(raw) {
@@ -5210,7 +5313,7 @@ async function listSessions(workspaceDir) {
5210
5313
  const sessionsDir = getWorkspaceSessionsDir(workspaceDir);
5211
5314
  let files;
5212
5315
  try {
5213
- files = await fs16.readdir(sessionsDir);
5316
+ files = await fs17.readdir(sessionsDir);
5214
5317
  } catch {
5215
5318
  return [];
5216
5319
  }
@@ -5218,7 +5321,7 @@ async function listSessions(workspaceDir) {
5218
5321
  for (const file of files) {
5219
5322
  if (!file.endsWith(".jsonl")) continue;
5220
5323
  const id = file.replace(/\.jsonl$/, "");
5221
- const raw = await fs16.readFile(path15.join(sessionsDir, file), "utf8");
5324
+ const raw = await fs17.readFile(path16.join(sessionsDir, file), "utf8");
5222
5325
  const events = parseEvents(raw);
5223
5326
  if (events.length === 0) continue;
5224
5327
  const chatTurns = events.filter((e) => e.type === "chat.turn");
@@ -5239,8 +5342,8 @@ async function listSessions(workspaceDir) {
5239
5342
  }
5240
5343
  async function loadSessionHistory(workspaceDir, sessionId) {
5241
5344
  const sessionsDir = getWorkspaceSessionsDir(workspaceDir);
5242
- const sessionFile = path15.join(sessionsDir, `${sessionId}.jsonl`);
5243
- const raw = await fs16.readFile(sessionFile, "utf8");
5345
+ const sessionFile = path16.join(sessionsDir, `${sessionId}.jsonl`);
5346
+ const raw = await fs17.readFile(sessionFile, "utf8");
5244
5347
  const events = parseEvents(raw);
5245
5348
  const messages = [];
5246
5349
  const llmHistory = [];
@@ -5352,15 +5455,15 @@ function ConfigScreen({ items, onUpdate, onClose }) {
5352
5455
  }
5353
5456
 
5354
5457
  // src/lib/cli/update-check.ts
5355
- import fs17 from "fs/promises";
5356
- import path16 from "path";
5458
+ import fs18 from "fs/promises";
5459
+ import path17 from "path";
5357
5460
  import os4 from "os";
5358
5461
  var PACKAGE_NAME = "open-research";
5359
5462
  var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
5360
- var STATE_FILE = path16.join(os4.homedir(), ".open-research", "update-check.json");
5463
+ var STATE_FILE = path17.join(os4.homedir(), ".open-research", "update-check.json");
5361
5464
  async function readState() {
5362
5465
  try {
5363
- const raw = await fs17.readFile(STATE_FILE, "utf8");
5466
+ const raw = await fs18.readFile(STATE_FILE, "utf8");
5364
5467
  const parsed = JSON.parse(raw);
5365
5468
  return {
5366
5469
  lastCheck: typeof parsed.lastCheck === "number" ? parsed.lastCheck : 0,
@@ -5372,8 +5475,8 @@ async function readState() {
5372
5475
  }
5373
5476
  }
5374
5477
  async function writeState(state) {
5375
- await fs17.mkdir(path16.dirname(STATE_FILE), { recursive: true });
5376
- await fs17.writeFile(STATE_FILE, JSON.stringify(state), "utf8");
5478
+ await fs18.mkdir(path17.dirname(STATE_FILE), { recursive: true });
5479
+ await fs18.writeFile(STATE_FILE, JSON.stringify(state), "utf8");
5377
5480
  }
5378
5481
  function getCurrentVersion() {
5379
5482
  return getPackageVersion();
@@ -5425,10 +5528,136 @@ async function checkForUpdate() {
5425
5528
  }
5426
5529
  }
5427
5530
 
5531
+ // src/lib/workspace/init-agents-md.ts
5532
+ import fs19 from "fs/promises";
5533
+ import path18 from "path";
5534
+ var IGNORED_DIRS2 = /* @__PURE__ */ new Set([
5535
+ "node_modules",
5536
+ ".git",
5537
+ "dist",
5538
+ "build",
5539
+ ".next",
5540
+ "__pycache__",
5541
+ ".venv",
5542
+ "venv",
5543
+ ".cache",
5544
+ "target",
5545
+ ".open-research",
5546
+ "coverage"
5547
+ ]);
5548
+ var INTERESTING_FILES = /* @__PURE__ */ new Set([
5549
+ "package.json",
5550
+ "pyproject.toml",
5551
+ "Cargo.toml",
5552
+ "go.mod",
5553
+ "requirements.txt",
5554
+ "setup.py",
5555
+ "setup.cfg",
5556
+ "README.md",
5557
+ "README.txt",
5558
+ "readme.md",
5559
+ "Makefile",
5560
+ "Dockerfile",
5561
+ "docker-compose.yml",
5562
+ ".env.example",
5563
+ "tsconfig.json",
5564
+ "vitest.config.ts",
5565
+ "jest.config.js"
5566
+ ]);
5567
+ async function scanDirectoryShallow(dir, maxDepth = 2, depth = 0) {
5568
+ const results = [];
5569
+ if (depth > maxDepth) return results;
5570
+ try {
5571
+ const entries = await fs19.readdir(dir, { withFileTypes: true });
5572
+ for (const entry of entries) {
5573
+ if (IGNORED_DIRS2.has(entry.name)) continue;
5574
+ if (entry.name.startsWith(".") && depth === 0 && entry.isDirectory()) continue;
5575
+ const fullPath = path18.join(dir, entry.name);
5576
+ const relativePath = path18.relative(process.cwd(), fullPath);
5577
+ if (entry.isDirectory()) {
5578
+ results.push({ path: relativePath + "/", size: 0, isDir: true });
5579
+ const children = await scanDirectoryShallow(fullPath, maxDepth, depth + 1);
5580
+ results.push(...children);
5581
+ } else {
5582
+ const stat = await fs19.stat(fullPath).catch(() => null);
5583
+ results.push({ path: relativePath, size: stat?.size ?? 0, isDir: false });
5584
+ }
5585
+ }
5586
+ } catch {
5587
+ }
5588
+ return results;
5589
+ }
5590
+ async function readKeyFiles(dir) {
5591
+ const contents = {};
5592
+ for (const name of INTERESTING_FILES) {
5593
+ const filePath = path18.join(dir, name);
5594
+ try {
5595
+ const content = await fs19.readFile(filePath, "utf8");
5596
+ contents[name] = content.slice(0, 2e3);
5597
+ } catch {
5598
+ }
5599
+ }
5600
+ return contents;
5601
+ }
5602
+ var INIT_PROMPT = `You are creating an AGENTS.md file for a research workspace. This file will be injected into an AI research agent's system prompt every session to give it instant project context.
5603
+
5604
+ Based on the directory structure and key file contents below, write a concise AGENTS.md that covers:
5605
+
5606
+ ## Project Overview
5607
+ What is this project about? What type of research?
5608
+
5609
+ ## Structure
5610
+ Key directories and what they contain (only the important ones).
5611
+
5612
+ ## Key Files
5613
+ Important files and what they do (only the notable ones).
5614
+
5615
+ ## Research Context
5616
+ What research appears to be in progress based on the files?
5617
+
5618
+ ## Development
5619
+ How to build/run/test (if applicable, based on package.json or similar).
5620
+
5621
+ Rules:
5622
+ - Keep it under 1500 characters total
5623
+ - Be specific to THIS project, not generic
5624
+ - If it's unclear what the project does, say so and note what you can see
5625
+ - Use markdown with ## headings
5626
+ - Don't include obvious things ("node_modules contains npm packages")`;
5627
+ async function generateInitialAgentsMd(input2) {
5628
+ const dir = input2.workspaceDir;
5629
+ const files = await scanDirectoryShallow(dir);
5630
+ const tree = files.slice(0, 100).map((f) => `${f.isDir ? "d" : "f"} ${f.path}${f.size > 0 ? ` (${(f.size / 1024).toFixed(1)}KB)` : ""}`).join("\n");
5631
+ const keyFiles = await readKeyFiles(dir);
5632
+ const keyFileText = Object.entries(keyFiles).map(([name, content2]) => `### ${name}
5633
+ \`\`\`
5634
+ ${content2}
5635
+ \`\`\``).join("\n\n");
5636
+ const userMessage = `Directory: ${dir}
5637
+
5638
+ File tree:
5639
+ ${tree}
5640
+
5641
+ ${keyFileText ? `Key files:
5642
+ ${keyFileText}` : "No recognizable key files found."}`;
5643
+ const response = await input2.provider.callLLM({
5644
+ messages: [
5645
+ { role: "system", content: INIT_PROMPT },
5646
+ { role: "user", content: userMessage.slice(0, 3e4) }
5647
+ ],
5648
+ model: input2.model ?? "gpt-5.4-mini",
5649
+ maxTokens: 2048,
5650
+ temperature: 0
5651
+ });
5652
+ const content = response.content.replace(/^```(?:markdown)?\n?/, "").replace(/\n?```$/, "").trim();
5653
+ await writeAgentsMd(dir, content);
5654
+ return content;
5655
+ }
5656
+
5428
5657
  // src/lib/preview/server.ts
5429
5658
  import http2 from "http";
5430
- import fs18 from "fs";
5431
- import path17 from "path";
5659
+ import fs20 from "fs";
5660
+ import path19 from "path";
5432
5661
 
5433
5662
  // src/lib/preview/latex-to-html.ts
5434
5663
  function latexToHtml(latex) {
@@ -5695,11 +5924,11 @@ var HTML_TEMPLATE = `<!DOCTYPE html>
5695
5924
  </body>
5696
5925
  </html>`;
5697
5926
  function startPreviewServer(texPath) {
5698
- const resolved = path17.resolve(texPath);
5927
+ const resolved = path19.resolve(texPath);
5699
5928
  let currentHash = "";
5700
5929
  function getContentHash() {
5701
5930
  try {
5702
- const content = fs18.readFileSync(resolved, "utf8");
5931
+ const content = fs20.readFileSync(resolved, "utf8");
5703
5932
  return `${content.length}-${content.slice(0, 100)}-${content.slice(-100)}`;
5704
5933
  } catch {
5705
5934
  return "error";
@@ -5707,7 +5936,7 @@ function startPreviewServer(texPath) {
5707
5936
  }
5708
5937
  function renderPage() {
5709
5938
  try {
5710
- const latex = fs18.readFileSync(resolved, "utf8");
5939
+ const latex = fs20.readFileSync(resolved, "utf8");
5711
5940
  const htmlContent = latexToHtml(latex);
5712
5941
  currentHash = getContentHash();
5713
5942
  return HTML_TEMPLATE.replace("{{CONTENT}}", htmlContent);
@@ -6466,6 +6695,20 @@ function App({
6466
6695
  addSystemMessage(`Initialized workspace "${project.title}" at ${target}`);
6467
6696
  const scanned = await scanWorkspace(target);
6468
6697
  startTransition(() => setWorkspaceFiles(scanned.files));
6698
+ if (hasAuth) {
6699
+ addSystemMessage("Generating AGENTS.md...");
6700
+ try {
6701
+ const provider = await createProviderFromStoredAuth({ homeDir });
6702
+ await generateInitialAgentsMd({
6703
+ workspaceDir: target,
6704
+ provider,
6705
+ model: "gpt-5.4-mini"
6706
+ });
6707
+ addSystemMessage("AGENTS.md created \u2014 project context will be loaded on every session.");
6708
+ } catch {
6709
+ addSystemMessage("AGENTS.md generation skipped (connect auth first).");
6710
+ }
6711
+ }
6469
6712
  } catch (err) {
6470
6713
  addSystemMessage(`Init failed: ${err instanceof Error ? err.message : String(err)}`);
6471
6714
  } finally {
@@ -6983,6 +7226,7 @@ ${msg.text}
6983
7226
  const provider = await createProviderFromStoredAuth({ homeDir });
6984
7227
  const workspace = await scanWorkspace(workspacePath);
6985
7228
  const workspaceContext = {
7229
+ workspaceDir: workspacePath,
6986
7230
  runId: sessionId,
6987
7231
  workspaceFiles: Object.fromEntries(workspace.files.map((f) => [f.key, f.content])),
6988
7232
  availableKeys: workspace.files.map((f) => f.key),
@@ -7115,6 +7359,7 @@ ${msg.text}
7115
7359
  const provider = await createProviderFromStoredAuth({ homeDir });
7116
7360
  const workspace = await scanWorkspace(workspacePath);
7117
7361
  const workspaceContext = {
7362
+ workspaceDir: workspacePath,
7118
7363
  runId: sessionId,
7119
7364
  workspaceFiles: Object.fromEntries(workspace.files.map((f) => [f.key, f.content])),
7120
7365
  availableKeys: workspace.files.map((f) => f.key),
@@ -7460,7 +7705,7 @@ ${msg.text}
7460
7705
  statusParts,
7461
7706
  statusColor,
7462
7707
  tokenDisplay,
7463
- workspaceName: hasWorkspace ? path18.basename(workspacePath) : process.cwd(),
7708
+ workspaceName: hasWorkspace ? path20.basename(workspacePath) : process.cwd(),
7464
7709
  mode: agentMode,
7465
7710
  planningStatus: planningState.status
7466
7711
  }
@@ -7472,7 +7717,7 @@ ${msg.text}
7472
7717
  var program = new Command();
7473
7718
  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) => {
7474
7719
  await ensureOpenResearchConfig();
7475
- const target = workspacePath ? path19.resolve(workspacePath) : process.cwd();
7720
+ const target = workspacePath ? path21.resolve(workspacePath) : process.cwd();
7476
7721
  const project = await loadWorkspaceProject(target);
7477
7722
  const auth2 = await loadStoredAuth();
7478
7723
  render(
@@ -7488,7 +7733,7 @@ program.name("open-research").version(getPackageVersion()).description("Local-fi
7488
7733
  });
7489
7734
  program.command("init").argument("[workspacePath]").description("Initialize an Open Research workspace.").action(async (workspacePath) => {
7490
7735
  await ensureOpenResearchConfig();
7491
- const target = path19.resolve(workspacePath ?? process.cwd());
7736
+ const target = path21.resolve(workspacePath ?? process.cwd());
7492
7737
  const project = await initWorkspace({ workspaceDir: target });
7493
7738
  console.log(`Initialized workspace: ${target}`);
7494
7739
  console.log(`Title: ${project.title}`);
@@ -7557,8 +7802,8 @@ skills.command("create").argument("[name]").description("Scaffold a new user ski
7557
7802
  });
7558
7803
  skills.command("edit").argument("<name>").description("Open a user skill in $EDITOR.").action(async (name) => {
7559
7804
  await ensureOpenResearchConfig();
7560
- const skillDir = path19.join(getOpenResearchSkillsDir(), name);
7561
- openInEditor(path19.join(skillDir, "SKILL.md"));
7805
+ const skillDir = path21.join(getOpenResearchSkillsDir(), name);
7806
+ openInEditor(path21.join(skillDir, "SKILL.md"));
7562
7807
  const validation = await validateSkillDirectory({ skillDir });
7563
7808
  if (!validation.ok) {
7564
7809
  console.error(validation.errors.join("\n"));
@@ -7569,9 +7814,9 @@ skills.command("edit").argument("<name>").description("Open a user skill in $EDI
7569
7814
  });
7570
7815
  skills.command("validate").argument("[nameOrPath]").description("Validate one user skill.").action(async (nameOrPath) => {
7571
7816
  await ensureOpenResearchConfig();
7572
- const skillDir = nameOrPath ? path19.isAbsolute(nameOrPath) ? nameOrPath : path19.join(getOpenResearchSkillsDir(), nameOrPath) : getOpenResearchSkillsDir();
7817
+ const skillDir = nameOrPath ? path21.isAbsolute(nameOrPath) ? nameOrPath : path21.join(getOpenResearchSkillsDir(), nameOrPath) : getOpenResearchSkillsDir();
7573
7818
  const stat = await import("fs/promises").then(
7574
- (fs19) => fs19.stat(skillDir).catch(() => null)
7819
+ (fs21) => fs21.stat(skillDir).catch(() => null)
7575
7820
  );
7576
7821
  if (!stat) {
7577
7822
  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.7",
3
+ "version": "0.1.9",
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",