@longtable/cli 0.1.26 → 0.1.27

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 (3) hide show
  1. package/README.md +10 -4
  2. package/dist/cli.js +153 -3
  3. package/package.json +8 -7
package/README.md CHANGED
@@ -194,14 +194,20 @@ LongTable should not behave like a generic web scraper. Research search should
194
194
  start from scholarly routes when the user needs literature discovery, citation
195
195
  verification, publication metadata, or evidence-backed research decisions.
196
196
 
197
- Planned scholarly routes include arXiv, Crossref, OpenAlex, Semantic Scholar,
198
- PubMed/NCBI, ERIC, DOAJ, and Unpaywall. They have different setup requirements:
199
- some work without keys, some require a contact email, and some need API keys for
200
- reliable use.
197
+ `longtable search` routes research queries through arXiv, Crossref, OpenAlex,
198
+ Semantic Scholar, PubMed/NCBI, ERIC, DOAJ, and Unpaywall, then normalizes,
199
+ deduplicates, ranks, and labels results as evidence cards. Some sources work
200
+ without keys, some require a contact email, and some need API keys for reliable
201
+ use.
201
202
 
202
203
  Citation support should be checked explicitly. A reference can be useful as
203
204
  background while still failing to support the specific claim attached to it.
204
205
 
206
+ ```bash
207
+ longtable search --query "trust calibration measurement" --intent measurement
208
+ longtable search --query "trust calibration citation support" --intent citation --record
209
+ ```
210
+
205
211
  See:
206
212
 
207
213
  - [Research Search](https://github.com/HosungYou/LongTable/blob/main/docs/RESEARCH-SEARCH.md)
package/dist/cli.js CHANGED
@@ -4,10 +4,11 @@ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
4
4
  import { execSync } from "node:child_process";
5
5
  import { emitKeypressEvents } from "node:readline";
6
6
  import { createInterface } from "node:readline/promises";
7
- import { stdin as input, stdout as output, cwd, exit } from "node:process";
7
+ import { stdin as input, stdout as output, cwd, env, exit } from "node:process";
8
8
  import { dirname, join, resolve } from "node:path";
9
9
  import { homedir } from "node:os";
10
10
  import { classifyCheckpointTrigger } from "@longtable/checkpoints";
11
+ import { assessSearchSourceCapabilities, buildResearchSearchIntent, runResearchSearch } from "@longtable/search";
11
12
  import { buildProviderChoices, buildQuickSetupFlow, createPersistedSetupOutput, installRuntimeConfigFromStoredSetup, loadSetupOutput, renderInstallSummary, renderSetupSummary, resolveDefaultRuntimeConfigPath, resolveDefaultSetupPath, saveSetupOutput, saveSetupAndRuntimeConfig, serializeSetupOutput, writeRuntimeConfig } from "@longtable/setup";
12
13
  import { buildCodexSkillSpecs, buildCodexThinWrappedPrompt, installCodexSkills, listInstalledCodexSkills, renderQuestionRecordPrompt, removeCodexSkills, resolveCodexSkillsDir, runCodexThinWrapper } from "@longtable/provider-codex";
13
14
  import { buildClaudeSkillSpecs, installClaudeSkills, listInstalledClaudeSkills, renderQuestionRecordInput, removeClaudeSkills, resolveClaudeSkillsDir } from "@longtable/provider-claude";
@@ -42,7 +43,7 @@ const ANSI = {
42
43
  green: "\u001B[32m"
43
44
  };
44
45
  const LONGTABLE_MCP_SERVER_NAME = "longtable-state";
45
- const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.26";
46
+ const LONGTABLE_MCP_PACKAGE_VERSION = "0.1.27";
46
47
  const LONGTABLE_MCP_MARKER_START = "# LongTable state MCP START";
47
48
  const LONGTABLE_MCP_MARKER_END = "# LongTable state MCP END";
48
49
  function style(text, prefix) {
@@ -88,6 +89,7 @@ function usage() {
88
89
  " longtable show [--json] [--path <file>]",
89
90
  " longtable install [--json] [--path <file>] [--runtime-path <file>]",
90
91
  " longtable mcp install [--provider codex|claude|all] [--write] [--checkpoint-ui off|interactive|strong] [--json] [--codex-config <path>] [--claude-settings <path>] [--package <spec>]",
92
+ " longtable search --query <text> [--intent literature|theory|measurement|citation|metadata|venue] [--field <text>] [--source all|crossref,arxiv,openalex,semantic_scholar,pubmed,eric,doaj,unpaywall] [--must <term[,term]>] [--exclude <term[,term]>] [--limit <n>] [--allow-partial] [--record] [--cwd <path>] [--json]",
91
93
  " longtable sentinel --prompt <text> [--cwd <path>] [--json] [--record]",
92
94
  " longtable team --prompt <text> [--role <role[,role]>] [--debate] [--rounds 3|5] [--cwd <path>] [--json]",
93
95
  " longtable ask [--prompt <text>] [--print] [--json] [--setup <path>] [--cwd <path>]",
@@ -125,7 +127,7 @@ function parseArgs(argv) {
125
127
  const values = {};
126
128
  let subcommand = maybeSubcommand;
127
129
  const modeCommand = command && VALID_MODES.has(command);
128
- const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "panel", "decide", "sentinel", "team"].includes(command);
130
+ const directCommand = command && ["init", "setup", "start", "resume", "doctor", "status", "roles", "show", "install", "mcp", "codex", "claude", "ask", "clarify", "question", "panel", "decide", "sentinel", "team", "search"].includes(command);
129
131
  let startIndex = 1;
130
132
  if (modeCommand) {
131
133
  subcommand = undefined;
@@ -1951,6 +1953,150 @@ async function runPanelCommand(args) {
1951
1953
  });
1952
1954
  exit(exitCode);
1953
1955
  }
1956
+ function parseLimit(value) {
1957
+ if (typeof value !== "string") {
1958
+ return undefined;
1959
+ }
1960
+ const parsed = Number(value);
1961
+ if (!Number.isInteger(parsed) || parsed <= 0) {
1962
+ throw new Error(`Invalid search limit: ${value}`);
1963
+ }
1964
+ return parsed;
1965
+ }
1966
+ async function confirmPartialSearch(skippedSources) {
1967
+ if (!input.isTTY || !output.isTTY) {
1968
+ return false;
1969
+ }
1970
+ const rl = createInterface({ input, output });
1971
+ try {
1972
+ console.log("Some scholarly sources are unavailable:");
1973
+ for (const source of skippedSources) {
1974
+ console.log(`- ${source.source}: ${source.reason ?? "unavailable"}`);
1975
+ }
1976
+ const answer = await rl.question("Continue with the available sources? [y/N] ");
1977
+ return /^y(es)?$/i.test(answer.trim());
1978
+ }
1979
+ finally {
1980
+ rl.close();
1981
+ }
1982
+ }
1983
+ function renderEvidenceRunSummary(run, recordedPath) {
1984
+ const lines = [
1985
+ "LongTable Search",
1986
+ `- status: ${run.status}`,
1987
+ `- query: ${run.intent.query}`,
1988
+ `- intent: ${run.intent.kind}`,
1989
+ `- sources: ${run.sourceReports.map((report) => `${report.source}:${report.status}`).join(", ")}`
1990
+ ];
1991
+ if (run.blockedReason) {
1992
+ lines.push(`- blocked: ${run.blockedReason}`);
1993
+ }
1994
+ if (recordedPath) {
1995
+ lines.push(`- recorded: ${recordedPath}`);
1996
+ }
1997
+ if (run.warnings.length > 0) {
1998
+ lines.push("- warnings:");
1999
+ for (const warning of run.warnings) {
2000
+ lines.push(` - ${warning}`);
2001
+ }
2002
+ }
2003
+ if (run.cards.length === 0) {
2004
+ lines.push("- cards: none");
2005
+ return lines.join("\n");
2006
+ }
2007
+ lines.push("- top evidence cards:");
2008
+ for (const card of run.cards.slice(0, 8)) {
2009
+ const identifiers = [
2010
+ card.doi ? `doi:${card.doi}` : "",
2011
+ card.pmid ? `pmid:${card.pmid}` : "",
2012
+ card.arxivId ? `arxiv:${card.arxivId}` : ""
2013
+ ].filter(Boolean).join(", ");
2014
+ lines.push(` - ${card.title}`);
2015
+ lines.push(` score: ${card.relevanceScore}; support: ${card.citationSupportStatus}; depth: ${card.evidenceDepth}; sources: ${card.sourceRoutes.join(", ")}`);
2016
+ if (identifiers) {
2017
+ lines.push(` ids: ${identifiers}`);
2018
+ }
2019
+ if (card.url) {
2020
+ lines.push(` url: ${card.url}`);
2021
+ }
2022
+ }
2023
+ return lines.join("\n");
2024
+ }
2025
+ async function recordEvidenceRun(run, workingDirectory) {
2026
+ const context = await loadProjectContextFromDirectory(workingDirectory);
2027
+ if (!context) {
2028
+ throw new Error("`longtable search --record` requires a LongTable project workspace. Run inside a project or pass --cwd.");
2029
+ }
2030
+ const evidenceDir = join(context.project.projectPath, ".longtable", "evidence");
2031
+ await mkdir(evidenceDir, { recursive: true });
2032
+ const evidencePath = join(evidenceDir, `${run.id}.json`);
2033
+ await writeJsonFile(evidencePath, run);
2034
+ const state = await loadWorkspaceState(context);
2035
+ state.workingState = {
2036
+ ...state.workingState,
2037
+ recentEvidenceRun: {
2038
+ id: run.id,
2039
+ query: run.intent.query,
2040
+ intent: run.intent.kind,
2041
+ status: run.status,
2042
+ cardCount: run.cards.length,
2043
+ path: evidencePath
2044
+ }
2045
+ };
2046
+ state.artifactRecords.push({
2047
+ id: `artifact_${run.id}`,
2048
+ timestamp: run.createdAt,
2049
+ artifactType: "evidence_search",
2050
+ stakes: "internal_draft",
2051
+ source: "longtable search",
2052
+ location: evidencePath,
2053
+ provenanceSummary: `Scholar-first search for "${run.intent.query}" using ${run.intent.requestedSources.join(", ")}.`
2054
+ });
2055
+ await writeFile(context.stateFilePath, `${JSON.stringify(state, null, 2)}\n`, "utf8");
2056
+ await syncCurrentWorkspaceView(context);
2057
+ return evidencePath;
2058
+ }
2059
+ async function runSearch(args) {
2060
+ const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
2061
+ const projectContext = await loadProjectContextFromDirectory(workingDirectory);
2062
+ const searchInput = {
2063
+ query: typeof args.query === "string" ? args.query : undefined,
2064
+ prompt: typeof args.prompt === "string" ? args.prompt : undefined,
2065
+ projectGoal: projectContext?.session.currentGoal,
2066
+ projectBlocker: projectContext?.session.currentBlocker,
2067
+ intent: typeof args.intent === "string" ? args.intent : undefined,
2068
+ field: typeof args.field === "string" ? args.field : undefined,
2069
+ must: typeof args.must === "string" ? args.must : undefined,
2070
+ exclude: typeof args.exclude === "string" ? args.exclude : undefined,
2071
+ sources: typeof args.source === "string" ? args.source : undefined,
2072
+ limit: parseLimit(args.limit),
2073
+ source: "cli"
2074
+ };
2075
+ const plannedIntent = buildResearchSearchIntent(searchInput);
2076
+ const skippedSources = assessSearchSourceCapabilities(plannedIntent.requestedSources, env)
2077
+ .filter((capability) => !capability.enabled);
2078
+ let allowPartial = args["allow-partial"] === true;
2079
+ if (skippedSources.length > 0 && !allowPartial) {
2080
+ allowPartial = await confirmPartialSearch(skippedSources);
2081
+ }
2082
+ const run = await runResearchSearch({
2083
+ ...searchInput,
2084
+ env,
2085
+ allowPartial
2086
+ });
2087
+ let recordedPath;
2088
+ if (args.record === true && run.status !== "blocked") {
2089
+ recordedPath = await recordEvidenceRun(run, workingDirectory);
2090
+ }
2091
+ if (args.json === true) {
2092
+ console.log(JSON.stringify({
2093
+ run,
2094
+ files: recordedPath ? { evidence: recordedPath } : undefined
2095
+ }, null, 2));
2096
+ return;
2097
+ }
2098
+ console.log(renderEvidenceRunSummary(run, recordedPath));
2099
+ }
1954
2100
  async function runQuestion(args) {
1955
2101
  const workingDirectory = typeof args.cwd === "string" ? args.cwd : cwd();
1956
2102
  const prompt = await resolvePrompt(typeof args.prompt === "string" ? args.prompt : undefined);
@@ -2760,6 +2906,10 @@ async function main() {
2760
2906
  await runMcpSubcommand(subcommand, values);
2761
2907
  return;
2762
2908
  }
2909
+ if (command === "search") {
2910
+ await runSearch(values);
2911
+ return;
2912
+ }
2763
2913
  if (command === "ask") {
2764
2914
  await runAsk(values);
2765
2915
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@longtable/cli",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "private": false,
5
5
  "description": "Researcher-facing LongTable CLI",
6
6
  "type": "module",
@@ -28,12 +28,13 @@
28
28
  "typecheck": "tsc -p tsconfig.json --noEmit"
29
29
  },
30
30
  "dependencies": {
31
- "@longtable/checkpoints": "0.1.26",
32
- "@longtable/core": "0.1.26",
33
- "@longtable/memory": "0.1.26",
34
- "@longtable/provider-claude": "0.1.26",
35
- "@longtable/provider-codex": "0.1.26",
36
- "@longtable/setup": "0.1.26"
31
+ "@longtable/checkpoints": "0.1.27",
32
+ "@longtable/core": "0.1.27",
33
+ "@longtable/memory": "0.1.27",
34
+ "@longtable/provider-claude": "0.1.27",
35
+ "@longtable/provider-codex": "0.1.27",
36
+ "@longtable/search": "0.1.27",
37
+ "@longtable/setup": "0.1.27"
37
38
  },
38
39
  "devDependencies": {
39
40
  "@types/node": "^22.10.1",