@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.
- package/README.md +10 -4
- package/dist/cli.js +153 -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
|
-
|
|
198
|
-
PubMed/NCBI, ERIC, DOAJ, and Unpaywall
|
|
199
|
-
|
|
200
|
-
reliable
|
|
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.
|
|
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.
|
|
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.
|
|
32
|
-
"@longtable/core": "0.1.
|
|
33
|
-
"@longtable/memory": "0.1.
|
|
34
|
-
"@longtable/provider-claude": "0.1.
|
|
35
|
-
"@longtable/provider-codex": "0.1.
|
|
36
|
-
"@longtable/
|
|
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",
|