create-academic-research 0.1.16 → 0.1.18
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 +42 -5
- package/dist/src/capabilities.d.ts +3 -0
- package/dist/src/capabilities.js +106 -21
- package/dist/src/cli.js +117 -6
- package/dist/src/project.js +161 -4
- package/package.json +1 -1
- package/template/AGENTS.md +23 -2
- package/template/README.md +37 -1
- package/template/_gitignore +6 -0
- package/template/analysis_outputs/claim-audit.md +7 -0
- package/template/artifacts/artifact-checklist.md +54 -2
- package/template/artifacts/badge-evidence-ledger.csv +1 -0
- package/template/docs/agent/mcp-client-setup.md +12 -8
- package/template/docs/agent/mcp-setup.md +23 -0
- package/template/docs/agent/output-contracts.md +49 -5
- package/template/docs/agent/project-quality.md +80 -0
- package/template/docs/agent/repo-migration-playbook.md +20 -0
- package/template/docs/getting-started.md +37 -8
- package/template/docs/reproducibility/commands.md +12 -0
- package/template/experiments/campaigns/autonomous-campaign-template.md +68 -0
- package/template/experiments/campaigns/frontier-results.tsv +1 -0
- package/template/package.json +2 -1
- package/template/reports/paper/sota-survey.tex +29 -0
- package/template/repro_outputs/COMMANDS.md +4 -0
- package/template/repro_outputs/LOG.md +6 -0
- package/template/repro_outputs/PATCHES.md +7 -0
- package/template/repro_outputs/SUMMARY.md +21 -0
- package/template/repro_outputs/status.json +9 -0
- package/template/sota/citation-chasing-log.csv +1 -0
- package/template/sota/literature-matrix.csv +1 -1
- package/template/sota/paper-syntheses/.gitkeep +1 -0
- package/template/sota/reading-log.csv +1 -0
- package/template/sota/search-strategy.md +29 -0
- package/template/sources/markdown-linear/.gitkeep +1 -0
- package/template/tests/test_project_structure.py +18 -0
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ npx --yes github:VincenzoImp/create-academic-research my-project
|
|
|
41
41
|
| Sources | PDFs, derived Markdown, metadata, BibTeX, conversion ledger, source ledger. |
|
|
42
42
|
| Literature Review | Search strategy, screening decisions, literature matrix, SOTA synthesis, gaps, PRISMA flow. |
|
|
43
43
|
| Agent Memory | `AGENTS.md`, capability profile, MCP setup docs, generated MCP snippets, wiki index/log/templates. |
|
|
44
|
-
| Reproducibility | Python package scaffold, tests, experiment registry, output folders, artifact checklist. |
|
|
44
|
+
| Reproducibility | Python package scaffold, tests, experiment registry, autonomous campaign ledgers, output folders, artifact checklist. |
|
|
45
45
|
| Skills | Project-local installation flow for `VincenzoImp/academic-research-skills`. |
|
|
46
46
|
| MCP | Conservative default records for scholarly discovery plus documented optional integrations. |
|
|
47
47
|
|
|
@@ -115,7 +115,9 @@ Inside a generated project:
|
|
|
115
115
|
```bash
|
|
116
116
|
npm run doctor
|
|
117
117
|
npm run update
|
|
118
|
-
npm run
|
|
118
|
+
npm run update -- --apply
|
|
119
|
+
npm run setup -- --env-file .env.local
|
|
120
|
+
npm run workflow:literature
|
|
119
121
|
npm run rename -- --title "New Title" --slug new-title --package new_title
|
|
120
122
|
npm run agents:list
|
|
121
123
|
npm run skills:presets
|
|
@@ -173,13 +175,32 @@ Safe migrations are tracked in `.academic-research/managed-files.json`. The
|
|
|
173
175
|
manifest stores non-secret checksums for generator-owned files. If a file was
|
|
174
176
|
edited locally, update reports `skip` instead of overwriting it.
|
|
175
177
|
|
|
178
|
+
### Migration 0.1.17 -> 0.1.18
|
|
179
|
+
|
|
180
|
+
Projects created with `0.1.17` can migrate in place:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
npm run update
|
|
184
|
+
npm run update -- --apply
|
|
185
|
+
npm run doctor
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
The migration adds the project-quality contract, badge evidence ledger, SOTA
|
|
189
|
+
reading and citation-chasing ledgers, paper synthesis folders, linear reading
|
|
190
|
+
copies, autonomous experiment campaign files, claim-audit and reproduction
|
|
191
|
+
templates, and `workflow:literature`. Locally edited managed files are skipped;
|
|
192
|
+
new research record templates are created as user-owned where the project is
|
|
193
|
+
expected to fill them over time.
|
|
194
|
+
|
|
176
195
|
`academic-research init` initializes an existing repository without overwriting
|
|
177
196
|
existing files. It adds the research contract, merges lifecycle package scripts,
|
|
178
197
|
and preserves existing README, `.gitignore`, and custom package scripts.
|
|
179
198
|
|
|
180
|
-
`academic-research setup` is
|
|
199
|
+
`academic-research setup` is the friendly post-update recovery command. It
|
|
181
200
|
prints the active preset, agent, skill counts, selected MCP records, and next
|
|
182
|
-
commands
|
|
201
|
+
commands. When you pass `--env-file .env.local`, it may complete safe
|
|
202
|
+
project-local setup such as the Overleaf wrapper and generated MCP snippet. It
|
|
203
|
+
does not register global MCP clients.
|
|
183
204
|
|
|
184
205
|
Skills are project-local by default.
|
|
185
206
|
|
|
@@ -205,7 +226,7 @@ MCP commands are split by side-effect:
|
|
|
205
226
|
| `mcp env` | Print required/recommended env vars, hosted endpoints, local prerequisites, and setup commands for selected servers. Use `--dotenv --all` to print dotenv content or `--write .env.example --all` to regenerate `.env.example`. |
|
|
206
227
|
| `mcp enable` | Select an MCP server in project records and generated snippets. Use `--mode local`, `--mode remote`, or `--mode remote-custom --url <url>` where supported. |
|
|
207
228
|
| `mcp disable` | Remove an MCP server from project records and generated snippets. |
|
|
208
|
-
| `mcp setup` | Run or dry-run finite setup for manual-local integrations such as Overleaf. |
|
|
229
|
+
| `mcp setup` | Run or dry-run finite project-local setup for manual-local integrations such as Overleaf, including wrapper and generated snippet refresh. |
|
|
209
230
|
| `mcp client add` / `mcp client remove` | Register or remove supported MCP client entries, currently Codex, without writing secrets into client config. |
|
|
210
231
|
| `mcp install` | Run finite external tool install commands for selected MCP servers. It must not launch stdio MCP servers. |
|
|
211
232
|
| `mcp uninstall` | Run the external uninstall command when one exists. |
|
|
@@ -213,6 +234,11 @@ MCP commands are split by side-effect:
|
|
|
213
234
|
| `mcp doctor` | Validate enabled MCP records, generated snippets, required env vars, and documented manual prerequisites. Pass `--env-file .env.local` to read explicit local secrets. |
|
|
214
235
|
| `mcp probe` | Opt-in runtime check. Local stdio servers get a JSON-RPC handshake; remote endpoints are reported as configured without a network probe. |
|
|
215
236
|
|
|
237
|
+
Workflow commands are scenario-level shortcuts over skills and MCP records.
|
|
238
|
+
Use `npm run workflow:literature` when starting a serious SOTA, survey, or
|
|
239
|
+
related-work pass: it selects a practical literature stack with arXiv, DBLP,
|
|
240
|
+
Semantic Scholar, and OpenAlex remote graph search, then prints the next checks.
|
|
241
|
+
|
|
216
242
|
## Companion Skills
|
|
217
243
|
|
|
218
244
|
The generated project works best with:
|
|
@@ -266,6 +292,17 @@ a generated safe dotenv-loading wrapper after `mcp setup`.
|
|
|
266
292
|
Crossref and broad paper-search aggregators are kept as fallback/manual entries
|
|
267
293
|
until a project explicitly needs them.
|
|
268
294
|
|
|
295
|
+
For SOTA work, the recommended low-friction path is:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
npm run workflow:literature
|
|
299
|
+
npm run skills:install -- --preset literature
|
|
300
|
+
npm run mcp:status
|
|
301
|
+
npm run mcp:smoke -- --env-file .env.local
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Then use `$sota-literature-review` with a declared review scale and seed set.
|
|
305
|
+
|
|
269
306
|
Codex automatic registration supports custom remote endpoints when the URL is
|
|
270
307
|
stored in project config with `--url`. If the endpoint URL is kept private via
|
|
271
308
|
`--url-env`, Codex automatic registration is not available because the Codex CLI
|
|
@@ -166,6 +166,7 @@ interface SkillInstallOptions {
|
|
|
166
166
|
}
|
|
167
167
|
export declare function readCapabilities(root: string): Promise<CapabilityState>;
|
|
168
168
|
export declare function writeCapabilities(root: string, state: Partial<CapabilityState>): Promise<void>;
|
|
169
|
+
export declare function writeCapabilityGeneratedFiles(root: string, state: CapabilityState): Promise<void>;
|
|
169
170
|
export declare function writeMcpEnvironmentExample(root: string): Promise<void>;
|
|
170
171
|
export declare function initializeCapabilities(root: string, options?: InitializeCapabilitiesOptions): Promise<void>;
|
|
171
172
|
export declare function buildSkillInstallCommands(root: string, preset?: string, options?: SkillInstallOptions): Promise<string[][]>;
|
|
@@ -198,4 +199,6 @@ export declare function setupMcpServer(root: string, serverName: string, options
|
|
|
198
199
|
export declare function clientAddMcpServer(root: string, serverName: string, options?: McpClientOptions, runner?: Runner): Promise<McpClientResult>;
|
|
199
200
|
export declare function clientRemoveMcpServer(root: string, serverName: string, options?: McpClientOptions, runner?: Runner): Promise<McpClientResult>;
|
|
200
201
|
export declare function resolveMcpServerForState(state: CapabilityState, serverName: string, mode?: string): ResolvedMcpServer;
|
|
202
|
+
export declare function mcpMissingGeneratedSnippetMessage(serverName: string, server: ResolvedMcpServer, env?: NodeJS.ProcessEnv): string;
|
|
203
|
+
export declare function mcpLocalSetupGitignoreWarning(root: string): Promise<string | undefined>;
|
|
201
204
|
export declare function assertKnownMcpServers(servers: string[]): void;
|
package/dist/src/capabilities.js
CHANGED
|
@@ -24,21 +24,16 @@ export async function readCapabilities(root) {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
export async function writeCapabilities(root, state) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
preset: state.preset ?? "default",
|
|
31
|
-
scope: "project-local",
|
|
32
|
-
mcp_servers: mcpServers,
|
|
33
|
-
mcp_server_modes: normalizeMcpServerModeMap(state.mcp_server_modes ?? {}, mcpServers),
|
|
34
|
-
mcp_server_remote: normalizeMcpServerRemoteMap(state.mcp_server_remote ?? {}, mcpServers, state.mcp_server_modes ?? {})
|
|
35
|
-
};
|
|
36
|
-
await writeFile(join(root, "configs/capabilities.yaml"), YAML.stringify(serializeCapabilityState(next)), "utf8");
|
|
37
|
-
await writeCapabilityProfile(root, next);
|
|
38
|
-
await writeMcpSetup(root, next);
|
|
39
|
-
await writeMcpSnippet(root, next);
|
|
27
|
+
const next = normalizeCapabilityWriteState(state);
|
|
28
|
+
await writeCapabilityConfig(root, next);
|
|
29
|
+
await writeCapabilityGeneratedFiles(root, next);
|
|
40
30
|
await appendCapabilityLog(root, next);
|
|
41
31
|
}
|
|
32
|
+
export async function writeCapabilityGeneratedFiles(root, state) {
|
|
33
|
+
await writeCapabilityProfile(root, state);
|
|
34
|
+
await writeMcpSetup(root, state);
|
|
35
|
+
await writeMcpSnippet(root, state);
|
|
36
|
+
}
|
|
42
37
|
export async function writeMcpEnvironmentExample(root) {
|
|
43
38
|
await writeFile(join(root, ".env.example"), formatMcpDotenv(Object.keys(AGENT_STACK.mcp_servers)), "utf8");
|
|
44
39
|
}
|
|
@@ -321,6 +316,18 @@ export async function doctorMcpServers(root, options = {}) {
|
|
|
321
316
|
errors.push(`invalid generated MCP snippet: ${snippetPath}: ${message}`);
|
|
322
317
|
}
|
|
323
318
|
}
|
|
319
|
+
const lock = await readCapabilityLock(root);
|
|
320
|
+
const hasManualLocal = enabled.some((name) => {
|
|
321
|
+
if (!AGENT_STACK.mcp_servers[name])
|
|
322
|
+
return false;
|
|
323
|
+
const server = resolveMcpServerForState(state, name, modes[name]);
|
|
324
|
+
return server.connection_mode === "manual-local";
|
|
325
|
+
});
|
|
326
|
+
if (hasManualLocal) {
|
|
327
|
+
const gitignoreWarning = await mcpLocalSetupGitignoreWarning(root);
|
|
328
|
+
if (gitignoreWarning)
|
|
329
|
+
warnings.push(gitignoreWarning);
|
|
330
|
+
}
|
|
324
331
|
for (const name of enabled) {
|
|
325
332
|
const server = resolveMcpServerForState(state, name, modes[name]);
|
|
326
333
|
if (!server)
|
|
@@ -339,7 +346,6 @@ export async function doctorMcpServers(root, options = {}) {
|
|
|
339
346
|
warnings.push(`${name}: requires local service: ${server.local_service}`);
|
|
340
347
|
}
|
|
341
348
|
if (server.connection_mode === "manual-local") {
|
|
342
|
-
const lock = await readCapabilityLock(root);
|
|
343
349
|
if (lock.mcp[name]?.setup?.status !== "ready") {
|
|
344
350
|
warnings.push(`${name}: local setup not complete; run npm run mcp:setup -- ${name} --mode local --env-file .env.local`);
|
|
345
351
|
}
|
|
@@ -349,7 +355,14 @@ export async function doctorMcpServers(root, options = {}) {
|
|
|
349
355
|
continue;
|
|
350
356
|
}
|
|
351
357
|
if (!generatedServers.has(name)) {
|
|
352
|
-
errors.push(
|
|
358
|
+
errors.push(mcpMissingGeneratedSnippetMessage(name, server, env));
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
if (state.agent === "codex" &&
|
|
362
|
+
server.connection_mode === "manual-local" &&
|
|
363
|
+
lock.mcp[name]?.setup?.status === "ready" &&
|
|
364
|
+
lock.mcp[name]?.clients?.codex?.status !== "registered") {
|
|
365
|
+
warnings.push(`${name} is setup locally but not registered in Codex\nNEXT: npm run mcp:client:add -- ${name} --agent codex`);
|
|
353
366
|
}
|
|
354
367
|
}
|
|
355
368
|
return { ok: errors.length === 0, errors, warnings, enabled };
|
|
@@ -662,8 +675,15 @@ export async function readCapabilityLock(root) {
|
|
|
662
675
|
}
|
|
663
676
|
export async function setupMcpServer(root, serverName, options = {}, runner = defaultRunner) {
|
|
664
677
|
assertKnownMcpServers([serverName]);
|
|
665
|
-
const
|
|
666
|
-
const
|
|
678
|
+
const state = await readCapabilities(root);
|
|
679
|
+
const mode = normalizeMcpMode(serverName, options.mode ?? state.mcp_server_modes?.[serverName]);
|
|
680
|
+
const nextState = normalizeCapabilityWriteState({
|
|
681
|
+
...state,
|
|
682
|
+
mcp_servers: dedupe([...(state.mcp_servers ?? []), serverName]),
|
|
683
|
+
mcp_server_modes: { ...(state.mcp_server_modes ?? {}), [serverName]: mode },
|
|
684
|
+
mcp_server_remote: state.mcp_server_remote ?? {}
|
|
685
|
+
});
|
|
686
|
+
const server = resolveMcpServerForState(nextState, serverName, mode);
|
|
667
687
|
if (serverName !== "overleaf") {
|
|
668
688
|
const commands = server.setup_commands.length > 0 ? server.setup_commands : [];
|
|
669
689
|
return {
|
|
@@ -686,6 +706,7 @@ export async function setupMcpServer(root, serverName, options = {}, runner = de
|
|
|
686
706
|
`cd ${paths.relativeServer} && uv sync`,
|
|
687
707
|
`write wrapper ${paths.relativeWrapper}`
|
|
688
708
|
];
|
|
709
|
+
const gitignoreWarning = await mcpLocalSetupGitignoreWarning(root);
|
|
689
710
|
if (missing.length > 0) {
|
|
690
711
|
return {
|
|
691
712
|
ok: false,
|
|
@@ -693,7 +714,7 @@ export async function setupMcpServer(root, serverName, options = {}, runner = de
|
|
|
693
714
|
mode,
|
|
694
715
|
commands,
|
|
695
716
|
created: [],
|
|
696
|
-
warnings: [],
|
|
717
|
+
warnings: gitignoreWarning ? [gitignoreWarning] : [],
|
|
697
718
|
errors: [`overleaf: missing required environment variable(s): ${missing.join(", ")}`],
|
|
698
719
|
next: [`fill ${missing.join(", ")} in ${options.envFile ?? ".env.local"}`]
|
|
699
720
|
};
|
|
@@ -705,7 +726,7 @@ export async function setupMcpServer(root, serverName, options = {}, runner = de
|
|
|
705
726
|
mode,
|
|
706
727
|
commands,
|
|
707
728
|
created: [],
|
|
708
|
-
warnings: [],
|
|
729
|
+
warnings: gitignoreWarning ? [gitignoreWarning] : [],
|
|
709
730
|
errors: [],
|
|
710
731
|
next: [`run npm run mcp:setup -- overleaf --mode local --env-file ${options.envFile ?? ".env.local"}`]
|
|
711
732
|
};
|
|
@@ -727,17 +748,19 @@ export async function setupMcpServer(root, serverName, options = {}, runner = de
|
|
|
727
748
|
status: "ready",
|
|
728
749
|
server_path: paths.relativeServer,
|
|
729
750
|
wrapper_path: paths.relativeWrapper,
|
|
730
|
-
env_file:
|
|
751
|
+
env_file: mcpEnvFileRecord(root, options.envFile),
|
|
731
752
|
updated_at: nowIso()
|
|
732
753
|
};
|
|
733
754
|
});
|
|
755
|
+
await writeCapabilityConfig(root, nextState);
|
|
756
|
+
await writeCapabilityGeneratedFiles(root, nextState);
|
|
734
757
|
return {
|
|
735
758
|
ok: true,
|
|
736
759
|
server: serverName,
|
|
737
760
|
mode,
|
|
738
761
|
commands,
|
|
739
762
|
created: [paths.relativeWrapper, paths.relativeLauncher],
|
|
740
|
-
warnings: [],
|
|
763
|
+
warnings: gitignoreWarning ? [gitignoreWarning] : [],
|
|
741
764
|
errors: [],
|
|
742
765
|
next: [
|
|
743
766
|
"npm run mcp:client:add -- overleaf --agent codex",
|
|
@@ -824,6 +847,20 @@ function serializeCapabilityState(state) {
|
|
|
824
847
|
serialized.mcp_server_remote = state.mcp_server_remote;
|
|
825
848
|
return serialized;
|
|
826
849
|
}
|
|
850
|
+
function normalizeCapabilityWriteState(state) {
|
|
851
|
+
const mcpServers = [...(state.mcp_servers ?? [])];
|
|
852
|
+
return {
|
|
853
|
+
agent: assertKnownAgentTarget(state.agent),
|
|
854
|
+
preset: state.preset ?? "default",
|
|
855
|
+
scope: "project-local",
|
|
856
|
+
mcp_servers: mcpServers,
|
|
857
|
+
mcp_server_modes: normalizeMcpServerModeMap(state.mcp_server_modes ?? {}, mcpServers),
|
|
858
|
+
mcp_server_remote: normalizeMcpServerRemoteMap(state.mcp_server_remote ?? {}, mcpServers, state.mcp_server_modes ?? {})
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
async function writeCapabilityConfig(root, state) {
|
|
862
|
+
await writeFile(join(root, "configs/capabilities.yaml"), YAML.stringify(serializeCapabilityState(state)), "utf8");
|
|
863
|
+
}
|
|
827
864
|
function normalizeMcpServerModeMap(modes, servers) {
|
|
828
865
|
const selected = new Set(servers);
|
|
829
866
|
const result = {};
|
|
@@ -986,6 +1023,19 @@ function mcpModeKeyLabelForAction(mode) {
|
|
|
986
1023
|
return "manual setup";
|
|
987
1024
|
return "local";
|
|
988
1025
|
}
|
|
1026
|
+
export function mcpMissingGeneratedSnippetMessage(serverName, server, env = process.env) {
|
|
1027
|
+
const lines = [`${serverName}: enabled but missing from generated MCP snippet`];
|
|
1028
|
+
if (server.connection_mode === "manual-local") {
|
|
1029
|
+
lines.push(`NEXT: npm run mcp:setup -- ${serverName} --mode local --env-file .env.local`);
|
|
1030
|
+
const missing = server.required_env.filter((name) => !envHasValue(env, name));
|
|
1031
|
+
if (missing.length > 0)
|
|
1032
|
+
lines.push(`Missing env vars: ${missing.join(", ")}`);
|
|
1033
|
+
}
|
|
1034
|
+
else {
|
|
1035
|
+
lines.push("NEXT: npm run update -- --apply");
|
|
1036
|
+
}
|
|
1037
|
+
return lines.join("\n");
|
|
1038
|
+
}
|
|
989
1039
|
function mcpInstallSkip(serverName, server) {
|
|
990
1040
|
if (server.connection_mode === "manual-local") {
|
|
991
1041
|
return {
|
|
@@ -1098,6 +1148,41 @@ function ensureLockMcpEntry(lock, serverName, server) {
|
|
|
1098
1148
|
lock.mcp[serverName] = entry;
|
|
1099
1149
|
return entry;
|
|
1100
1150
|
}
|
|
1151
|
+
export async function mcpLocalSetupGitignoreWarning(root) {
|
|
1152
|
+
if (await mcpLocalSetupPathIsIgnored(root))
|
|
1153
|
+
return undefined;
|
|
1154
|
+
return [
|
|
1155
|
+
".academic-research/mcp/ is not ignored",
|
|
1156
|
+
"NEXT: add .academic-research/mcp/ to .gitignore before committing local MCP server files"
|
|
1157
|
+
].join("\n");
|
|
1158
|
+
}
|
|
1159
|
+
async function mcpLocalSetupPathIsIgnored(root) {
|
|
1160
|
+
try {
|
|
1161
|
+
const gitignore = await readFile(join(root, ".gitignore"), "utf8");
|
|
1162
|
+
return gitignore
|
|
1163
|
+
.split(/\r?\n/)
|
|
1164
|
+
.map((line) => line.trim())
|
|
1165
|
+
.filter((line) => line && !line.startsWith("#"))
|
|
1166
|
+
.some((line) => {
|
|
1167
|
+
const normalized = line.replaceAll("\\", "/").replace(/^\/+/, "");
|
|
1168
|
+
return normalized === ".academic-research/mcp/" || normalized === ".academic-research/mcp";
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
catch (error) {
|
|
1172
|
+
if (isMissingFileError(error))
|
|
1173
|
+
return false;
|
|
1174
|
+
throw error;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
function mcpEnvFileRecord(root, envFile) {
|
|
1178
|
+
if (!envFile)
|
|
1179
|
+
return ".env.local";
|
|
1180
|
+
const resolved = resolve(root, envFile);
|
|
1181
|
+
const relativePath = relative(root, resolved);
|
|
1182
|
+
if (!relativePath.startsWith("..") && !isAbsolute(relativePath))
|
|
1183
|
+
return toPosix(relativePath);
|
|
1184
|
+
return ".env.local";
|
|
1185
|
+
}
|
|
1101
1186
|
function overleafPaths(root) {
|
|
1102
1187
|
const relativeBase = ".academic-research/mcp/overleaf";
|
|
1103
1188
|
const wrapperDir = join(root, relativeBase);
|
package/dist/src/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, delimiter, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { assertKnownMcpServers, clientAddMcpServer, clientRemoveMcpServer, disableMcpServers, doctorMcpServers, enableMcpServers, DEFAULT_AGENT, getMcpLifecycleStatus, installMcpTools, installSkillIds, installSkills, formatMcpDotenv, listInstalledSkills, listMcpEnvironmentEntries, mergeMcpEnvironment, mcpToolCommandTexts, probeMcpServers, readCapabilities, readMcpEnvironmentFile, removeSkills, resolveMcpServerForState, setupMcpServer, uninstallMcpTools, updateSkills } from "./capabilities.js";
|
|
4
|
+
import { assertKnownMcpServers, clientAddMcpServer, clientRemoveMcpServer, disableMcpServers, doctorMcpServers, enableMcpServers, DEFAULT_AGENT, getMcpLifecycleStatus, installMcpTools, installSkillIds, installSkills, formatMcpDotenv, listInstalledSkills, listMcpEnvironmentEntries, mergeMcpEnvironment, mcpToolCommandTexts, probeMcpServers, readCapabilities, readMcpEnvironmentFile, removeSkills, resolveMcpServerForState, setupMcpServer, uninstallMcpTools, updateSkills, writeCapabilities } from "./capabilities.js";
|
|
5
5
|
import { createProject, doctorProject, initProject, renameProject, updateProject } from "./project.js";
|
|
6
6
|
import { askCreateOptions } from "./prompts.js";
|
|
7
7
|
import { AGENT_STACK, mcpModeLabel, mcpRecommendedMode, mcpServerModeKeys, mcpSupportedModeLabels, presetMcpServers, resolveMcpServer } from "./stack.js";
|
|
@@ -19,6 +19,8 @@ const CREATE_FLAGS = flagSchema([
|
|
|
19
19
|
"no-install-mcp-tools"
|
|
20
20
|
], ["title", "slug", "package", "preset", "profile", "agent"]);
|
|
21
21
|
const ROOT_FLAGS = flagSchema(["help"], ["root"]);
|
|
22
|
+
const SETUP_FLAGS = flagSchema(["help"], ["root", "env-file"]);
|
|
23
|
+
const WORKFLOW_FLAGS = flagSchema(["help"], ["root", "agent", "env-file"]);
|
|
22
24
|
const UPDATE_FLAGS = flagSchema(["help", "dry-run", "apply"], ["root"]);
|
|
23
25
|
const INIT_FLAGS = flagSchema(["help", "install-skills"], ["root", "title", "slug", "package", "preset", "profile", "agent"]);
|
|
24
26
|
const RENAME_FLAGS = flagSchema(["help"], ["root", "title", "slug", "package"]);
|
|
@@ -129,6 +131,8 @@ async function lifecycleMain(argv) {
|
|
|
129
131
|
return skillsCommand(argv.slice(1));
|
|
130
132
|
if (command === "mcp")
|
|
131
133
|
return mcpCommand(argv.slice(1));
|
|
134
|
+
if (command === "workflow")
|
|
135
|
+
return workflowCommand(argv.slice(1));
|
|
132
136
|
printLifecycleHelp();
|
|
133
137
|
return command === "help" ? 0 : 1;
|
|
134
138
|
}
|
|
@@ -173,6 +177,12 @@ async function updateCommand(argv) {
|
|
|
173
177
|
if (!apply && result.changes.length > 0) {
|
|
174
178
|
console.log("Run `npm run update -- --apply` from a generated project to write these managed changes.");
|
|
175
179
|
}
|
|
180
|
+
if (apply && await projectLocalMcpSetupNeeded(root)) {
|
|
181
|
+
console.log("");
|
|
182
|
+
console.log("Next:");
|
|
183
|
+
console.log("1. Run npm run setup -- --env-file .env.local to complete project-local tool setup.");
|
|
184
|
+
console.log("2. Run npm run doctor to verify the project.");
|
|
185
|
+
}
|
|
176
186
|
return 0;
|
|
177
187
|
}
|
|
178
188
|
async function initCommand(argv) {
|
|
@@ -198,13 +208,15 @@ async function initCommand(argv) {
|
|
|
198
208
|
return 0;
|
|
199
209
|
}
|
|
200
210
|
async function setupCommand(argv) {
|
|
201
|
-
const parsed = parseFlags(argv,
|
|
211
|
+
const parsed = parseFlags(argv, SETUP_FLAGS);
|
|
202
212
|
if (flagBool(parsed.flags, "help")) {
|
|
203
213
|
printSetupHelp();
|
|
204
214
|
return 0;
|
|
205
215
|
}
|
|
206
216
|
assertNoArguments(parsed.positionals, "setup");
|
|
207
217
|
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
218
|
+
const env = await mcpCommandEnvironment(root, parsed.flags);
|
|
219
|
+
const setupResults = await runProjectLocalMcpSetup(root, env, flagString(parsed.flags, "env-file"));
|
|
208
220
|
const project = await doctorProject(root);
|
|
209
221
|
const state = await readCapabilities(root);
|
|
210
222
|
const skills = await listInstalledSkills(root);
|
|
@@ -219,13 +231,30 @@ async function setupCommand(argv) {
|
|
|
219
231
|
console.log(`installed_skill_copies\t${skills.length}`);
|
|
220
232
|
console.log(`mcp_enabled\t${state.mcp_servers.length > 0 ? state.mcp_servers.join(",") : "none"}`);
|
|
221
233
|
console.log(`mcp_selected\t${state.mcp_servers.length > 0 ? state.mcp_servers.join(",") : "none"}`);
|
|
234
|
+
for (const result of setupResults) {
|
|
235
|
+
if (result.ok) {
|
|
236
|
+
console.log(`Completed project-local MCP setup: ${result.server}`);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
222
239
|
if (!project.ok) {
|
|
223
240
|
for (const error of project.errors)
|
|
224
241
|
console.error(`ERROR: ${error}`);
|
|
225
242
|
}
|
|
226
|
-
|
|
243
|
+
const setupWarnings = [];
|
|
244
|
+
for (const result of setupResults) {
|
|
245
|
+
for (const error of result.errors)
|
|
246
|
+
console.error(`ERROR: ${error}`);
|
|
247
|
+
setupWarnings.push(...result.warnings);
|
|
248
|
+
if (!result.ok && result.next.length > 0) {
|
|
249
|
+
console.log("");
|
|
250
|
+
console.log(`Next for ${result.server}`);
|
|
251
|
+
for (const command of result.next)
|
|
252
|
+
console.log(command);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
for (const warning of dedupeStrings([...setupWarnings, ...project.warnings]))
|
|
227
256
|
console.warn(`WARN: ${warning}`);
|
|
228
|
-
const lifecycle = await getMcpLifecycleStatus(root);
|
|
257
|
+
const lifecycle = await getMcpLifecycleStatus(root, { env });
|
|
229
258
|
console.log("");
|
|
230
259
|
console.log("Next Commands");
|
|
231
260
|
console.log(`npm run skills:install -- --preset ${state.preset}`);
|
|
@@ -598,6 +627,49 @@ async function mcpCommand(argv) {
|
|
|
598
627
|
}
|
|
599
628
|
throw new Error(`unknown mcp command: ${subcommand}`);
|
|
600
629
|
}
|
|
630
|
+
async function workflowCommand(argv) {
|
|
631
|
+
const subcommand = argv[0] ?? "help";
|
|
632
|
+
const parsed = parseFlags(argv.slice(1), WORKFLOW_FLAGS);
|
|
633
|
+
if (subcommand === "help" || subcommand === "--help" || subcommand === "-h" || flagBool(parsed.flags, "help")) {
|
|
634
|
+
printWorkflowHelp();
|
|
635
|
+
return 0;
|
|
636
|
+
}
|
|
637
|
+
if (subcommand !== "literature")
|
|
638
|
+
throw new Error(`unknown workflow command: ${subcommand}`);
|
|
639
|
+
assertNoArguments(parsed.positionals, "workflow literature");
|
|
640
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
641
|
+
const state = await readCapabilities(root);
|
|
642
|
+
const agent = flagString(parsed.flags, "agent") ?? state.agent;
|
|
643
|
+
const literatureServers = ["arxiv", "dblp", "semantic-scholar", "openalex"];
|
|
644
|
+
await writeCapabilities(root, {
|
|
645
|
+
...state,
|
|
646
|
+
agent,
|
|
647
|
+
preset: "literature",
|
|
648
|
+
mcp_servers: literatureServers,
|
|
649
|
+
mcp_server_modes: {
|
|
650
|
+
...state.mcp_server_modes,
|
|
651
|
+
openalex: "remote"
|
|
652
|
+
},
|
|
653
|
+
mcp_server_remote: state.mcp_server_remote
|
|
654
|
+
});
|
|
655
|
+
const env = await mcpCommandEnvironment(root, parsed.flags);
|
|
656
|
+
const lifecycle = await getMcpLifecycleStatus(root, { env });
|
|
657
|
+
const selected = lifecycle.servers.filter((server) => server.selected);
|
|
658
|
+
console.log("Literature Workflow");
|
|
659
|
+
console.log(`root\t${root}`);
|
|
660
|
+
console.log("preset\tliterature");
|
|
661
|
+
console.log(`mcp_selected\t${literatureServers.join(",")}`);
|
|
662
|
+
for (const item of selected) {
|
|
663
|
+
console.log(`mcp\t${item.id}\t${item.mode}\t${item.state}\t${friendlyNext(item.next)}`);
|
|
664
|
+
}
|
|
665
|
+
console.log("");
|
|
666
|
+
console.log("Next Commands");
|
|
667
|
+
console.log("npm run skills:install -- --preset literature");
|
|
668
|
+
console.log("npm run mcp:status");
|
|
669
|
+
console.log("npm run mcp:smoke -- --env-file .env.local");
|
|
670
|
+
console.log("Use $sota-literature-review with a declared scale, seed set, and citation-chasing budget.");
|
|
671
|
+
return 0;
|
|
672
|
+
}
|
|
601
673
|
async function mcpClientCommand(parsed) {
|
|
602
674
|
const action = parsed.positionals[0];
|
|
603
675
|
const server = parsed.positionals[1];
|
|
@@ -649,6 +721,28 @@ function setupNextCommands(item) {
|
|
|
649
721
|
commands.push(item.next.replace(/^run /, ""));
|
|
650
722
|
return dedupeStrings(commands);
|
|
651
723
|
}
|
|
724
|
+
async function runProjectLocalMcpSetup(root, env, envFile) {
|
|
725
|
+
const lifecycle = await getMcpLifecycleStatus(root, { env });
|
|
726
|
+
const results = [];
|
|
727
|
+
for (const item of lifecycle.servers) {
|
|
728
|
+
if (!item.selected || item.connection_mode !== "manual-local")
|
|
729
|
+
continue;
|
|
730
|
+
if (item.install === "ready" && item.snippet !== "missing")
|
|
731
|
+
continue;
|
|
732
|
+
results.push(await setupMcpServer(root, item.id, {
|
|
733
|
+
mode: item.mode_key,
|
|
734
|
+
envFile: envFile ?? ".env.local",
|
|
735
|
+
env
|
|
736
|
+
}));
|
|
737
|
+
}
|
|
738
|
+
return results;
|
|
739
|
+
}
|
|
740
|
+
async function projectLocalMcpSetupNeeded(root) {
|
|
741
|
+
const lifecycle = await getMcpLifecycleStatus(root);
|
|
742
|
+
return lifecycle.servers.some((item) => item.selected &&
|
|
743
|
+
item.connection_mode === "manual-local" &&
|
|
744
|
+
(item.install !== "ready" || item.snippet === "missing"));
|
|
745
|
+
}
|
|
652
746
|
function dedupeStrings(values) {
|
|
653
747
|
return [...new Set(values.filter(Boolean))];
|
|
654
748
|
}
|
|
@@ -899,7 +993,7 @@ function printMissingTargetHelp() {
|
|
|
899
993
|
}
|
|
900
994
|
function printLifecycleHelp() {
|
|
901
995
|
console.log([
|
|
902
|
-
"Usage: academic-research <doctor|update|init|setup|rename|agents|skills|mcp>",
|
|
996
|
+
"Usage: academic-research <doctor|update|init|setup|rename|agents|skills|mcp|workflow>",
|
|
903
997
|
"",
|
|
904
998
|
"Manage a generated academic research repository after creation.",
|
|
905
999
|
"",
|
|
@@ -908,6 +1002,22 @@ function printLifecycleHelp() {
|
|
|
908
1002
|
" -v, --version Show package version."
|
|
909
1003
|
].join("\n"));
|
|
910
1004
|
}
|
|
1005
|
+
function printWorkflowHelp() {
|
|
1006
|
+
console.log([
|
|
1007
|
+
"Usage: academic-research workflow <literature> [options]",
|
|
1008
|
+
"",
|
|
1009
|
+
"Prepare scenario-level research workflows without manually stitching every skill and MCP command.",
|
|
1010
|
+
"",
|
|
1011
|
+
"Workflows:",
|
|
1012
|
+
" literature Configure the practical SOTA stack for arXiv, DBLP, Semantic Scholar citation graph, and OpenAlex graph search.",
|
|
1013
|
+
"",
|
|
1014
|
+
"Options:",
|
|
1015
|
+
" --root <path> Project root. Default: current directory.",
|
|
1016
|
+
" --agent <id> Agent target for generated MCP snippets.",
|
|
1017
|
+
" --env-file <path> Read local env values for readiness reporting.",
|
|
1018
|
+
" -h, --help Show this help."
|
|
1019
|
+
].join("\n"));
|
|
1020
|
+
}
|
|
911
1021
|
function printUpdateHelp() {
|
|
912
1022
|
console.log([
|
|
913
1023
|
"Usage: academic-research update [options]",
|
|
@@ -943,10 +1053,11 @@ function printSetupHelp() {
|
|
|
943
1053
|
console.log([
|
|
944
1054
|
"Usage: academic-research setup [options]",
|
|
945
1055
|
"",
|
|
946
|
-
"Print project onboarding status and
|
|
1056
|
+
"Print project onboarding status and complete safe project-local setup when possible.",
|
|
947
1057
|
"",
|
|
948
1058
|
"Options:",
|
|
949
1059
|
" --root <path> Project root. Default: current directory.",
|
|
1060
|
+
" --env-file <path> Read local env values for guided project-local MCP setup.",
|
|
950
1061
|
" -h, --help Show this help."
|
|
951
1062
|
].join("\n"));
|
|
952
1063
|
}
|