create-academic-research 0.1.0
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/LICENSE +21 -0
- package/README.md +160 -0
- package/SECURITY.md +8 -0
- package/dist/bin/academic-research.d.ts +2 -0
- package/dist/bin/academic-research.js +3 -0
- package/dist/bin/create-academic-research.d.ts +2 -0
- package/dist/bin/create-academic-research.js +3 -0
- package/dist/src/capabilities.d.ts +54 -0
- package/dist/src/capabilities.js +487 -0
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.js +451 -0
- package/dist/src/files.d.ts +8 -0
- package/dist/src/files.js +44 -0
- package/dist/src/names.d.ts +3 -0
- package/dist/src/names.js +64 -0
- package/dist/src/project.d.ts +28 -0
- package/dist/src/project.js +229 -0
- package/dist/src/prompts.d.ts +18 -0
- package/dist/src/prompts.js +29 -0
- package/dist/src/runner.d.ts +12 -0
- package/dist/src/runner.js +22 -0
- package/dist/src/stack.d.ts +30 -0
- package/dist/src/stack.js +180 -0
- package/package.json +59 -0
- package/template/AGENTS.md +47 -0
- package/template/README.md +68 -0
- package/template/analysis_outputs/.gitkeep +0 -0
- package/template/artifacts/artifact-checklist.md +7 -0
- package/template/artifacts/cache/.gitkeep +0 -0
- package/template/artifacts/data/.gitkeep +0 -0
- package/template/artifacts/models/.gitkeep +0 -0
- package/template/artifacts/releases/.gitkeep +0 -0
- package/template/configs/agent-stack.yaml +5 -0
- package/template/configs/capabilities.yaml +4 -0
- package/template/configs/default.yaml +15 -0
- package/template/data/external/.gitkeep +0 -0
- package/template/data/interim/.gitkeep +0 -0
- package/template/data/processed/.gitkeep +0 -0
- package/template/data/raw/.gitkeep +0 -0
- package/template/debug_outputs/.gitkeep +0 -0
- package/template/docs/agent/capability-profile.md +6 -0
- package/template/docs/agent/mcp-setup.md +4 -0
- package/template/docs/agent/output-contracts.md +8 -0
- package/template/docs/agent/research-program.md +3 -0
- package/template/docs/data_dictionary/README.md +3 -0
- package/template/docs/ethics/data-governance.md +3 -0
- package/template/docs/methodology/evaluation-plan.md +3 -0
- package/template/docs/methodology/research-design.md +4 -0
- package/template/docs/methodology/threats-to-validity.md +3 -0
- package/template/docs/reproducibility/README.md +3 -0
- package/template/docs/venue/venue-strategy.md +3 -0
- package/template/experiments/registry.csv +1 -0
- package/template/experiments/templates/experiment-record.md +23 -0
- package/template/explore_outputs/.gitkeep +0 -0
- package/template/notebooks/README.md +5 -0
- package/template/outputs/figures/.gitkeep +0 -0
- package/template/outputs/models/.gitkeep +0 -0
- package/template/outputs/tables/.gitkeep +0 -0
- package/template/package.json +17 -0
- package/template/pyproject.toml +41 -0
- package/template/reports/paper/.gitkeep +0 -0
- package/template/reports/proposal/.gitkeep +0 -0
- package/template/reports/rebuttal/README.md +3 -0
- package/template/reports/reviews/README.md +3 -0
- package/template/reports/slides/.gitkeep +0 -0
- package/template/repro_outputs/.gitkeep +0 -0
- package/template/sota/gaps.md +9 -0
- package/template/sota/literature-matrix.csv +1 -0
- package/template/sota/prisma-flow.md +4 -0
- package/template/sota/screening-decisions.csv +1 -0
- package/template/sota/search-strategy.md +14 -0
- package/template/sota/synthesis.md +9 -0
- package/template/sources/assets/.gitkeep +0 -0
- package/template/sources/bib/citation-audit.csv +1 -0
- package/template/sources/bib/references.bib +1 -0
- package/template/sources/conversion-ledger.csv +1 -0
- package/template/sources/markdown/.gitkeep +0 -0
- package/template/sources/metadata/.gitkeep +0 -0
- package/template/sources/pdfs/.gitkeep +0 -0
- package/template/sources/source-ledger.csv +1 -0
- package/template/src/project_package/__init__.py +1 -0
- package/template/tests/test_project_structure.py +25 -0
- package/template/train_outputs/.gitkeep +0 -0
- package/template/wiki/claims/.gitkeep +0 -0
- package/template/wiki/concepts/.gitkeep +0 -0
- package/template/wiki/contradictions.md +3 -0
- package/template/wiki/decisions/.gitkeep +0 -0
- package/template/wiki/experiments/.gitkeep +0 -0
- package/template/wiki/index.md +9 -0
- package/template/wiki/log.md +1 -0
- package/template/wiki/methods/.gitkeep +0 -0
- package/template/wiki/open_questions.md +3 -0
- package/template/wiki/questions/.gitkeep +0 -0
- package/template/wiki/sources/.gitkeep +0 -0
- package/template/wiki/synthesis.md +3 -0
- package/template/wiki/templates/.gitkeep +0 -0
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { disableMcpServers, doctorMcpServers, enableMcpServers, DEFAULT_AGENT, installMcpTools, installSkills, listInstalledSkills, mcpToolCommandTexts, readCapabilities, removeSkills, uninstallMcpTools, updateSkills } from "./capabilities.js";
|
|
5
|
+
import { createProject, doctorProject, renameProject } from "./project.js";
|
|
6
|
+
import { askCreateOptions } from "./prompts.js";
|
|
7
|
+
import { AGENT_STACK, presetMcpServers } from "./stack.js";
|
|
8
|
+
import { packageify, slugify, titleFromSlug } from "./names.js";
|
|
9
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
10
|
+
const packageVersion = readPackageVersion();
|
|
11
|
+
const CREATE_FLAGS = flagSchema(["yes", "help", "version", "install-skills", "no-install-skills", "install-mcp-tools"], ["title", "slug", "package", "preset", "profile", "agent"]);
|
|
12
|
+
const ROOT_FLAGS = flagSchema(["help"], ["root"]);
|
|
13
|
+
const RENAME_FLAGS = flagSchema(["help"], ["root", "title", "slug", "package"]);
|
|
14
|
+
const SKILLS_FLAGS = flagSchema(["help"], ["root", "preset", "agent"]);
|
|
15
|
+
const MCP_FLAGS = flagSchema(["help"], ["root", "agent"]);
|
|
16
|
+
export async function main(argv = process.argv.slice(2), mode = "create") {
|
|
17
|
+
try {
|
|
18
|
+
if (mode === "create")
|
|
19
|
+
return createMain(argv);
|
|
20
|
+
return lifecycleMain(argv);
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async function createMain(argv) {
|
|
28
|
+
const parsed = parseFlags(argv, CREATE_FLAGS);
|
|
29
|
+
const target = parsed.positionals[0];
|
|
30
|
+
if (flagBool(parsed.flags, "help")) {
|
|
31
|
+
printCreateHelp();
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
if (flagBool(parsed.flags, "version")) {
|
|
35
|
+
console.log(packageVersion);
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
if (!target) {
|
|
39
|
+
printCreateHelp();
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
if (flagBool(parsed.flags, "install-skills") && flagBool(parsed.flags, "no-install-skills")) {
|
|
43
|
+
throw new Error("cannot use --install-skills and --no-install-skills together");
|
|
44
|
+
}
|
|
45
|
+
if (parsed.positionals.length > 1) {
|
|
46
|
+
throw new Error(`unexpected argument: ${parsed.positionals[1]}`);
|
|
47
|
+
}
|
|
48
|
+
const targetName = basename(resolve(target));
|
|
49
|
+
const defaults = {
|
|
50
|
+
title: flagString(parsed.flags, "title") ?? titleFromSlug(targetName),
|
|
51
|
+
slug: flagString(parsed.flags, "slug") ?? slugify(targetName),
|
|
52
|
+
packageName: flagString(parsed.flags, "package") ?? packageify(targetName),
|
|
53
|
+
preset: flagString(parsed.flags, "preset") ?? "default",
|
|
54
|
+
agent: flagString(parsed.flags, "agent") ?? DEFAULT_AGENT,
|
|
55
|
+
installSkills: !flagBool(parsed.flags, "no-install-skills"),
|
|
56
|
+
installMcpTools: flagBool(parsed.flags, "install-mcp-tools")
|
|
57
|
+
};
|
|
58
|
+
const interactive = !flagBool(parsed.flags, "yes") && process.stdin.isTTY;
|
|
59
|
+
const installSkillsLock = flagBool(parsed.flags, "install-skills") || flagBool(parsed.flags, "no-install-skills")
|
|
60
|
+
? defaults.installSkills
|
|
61
|
+
: undefined;
|
|
62
|
+
const installMcpToolsLock = flagBool(parsed.flags, "install-mcp-tools") ? true : undefined;
|
|
63
|
+
const answers = interactive
|
|
64
|
+
? await askCreateOptions(defaults, {
|
|
65
|
+
installSkills: installSkillsLock,
|
|
66
|
+
installMcpTools: installMcpToolsLock
|
|
67
|
+
})
|
|
68
|
+
: defaults;
|
|
69
|
+
const result = await createProject({
|
|
70
|
+
target,
|
|
71
|
+
title: answers.title,
|
|
72
|
+
slug: answers.slug,
|
|
73
|
+
packageName: answers.packageName,
|
|
74
|
+
profile: flagString(parsed.flags, "profile") ?? "academic-general",
|
|
75
|
+
preset: answers.preset,
|
|
76
|
+
agent: answers.agent,
|
|
77
|
+
installSkills: answers.installSkills
|
|
78
|
+
});
|
|
79
|
+
if (answers.installMcpTools) {
|
|
80
|
+
await installMcpTools(result.root, presetMcpServers(answers.preset));
|
|
81
|
+
}
|
|
82
|
+
console.log(`Created ${result.slug} at ${result.root}`);
|
|
83
|
+
console.log("Next: cd into the project and run `npx academic-research doctor`.");
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
async function lifecycleMain(argv) {
|
|
87
|
+
const command = argv[0] ?? "help";
|
|
88
|
+
if (command === "--help" || command === "-h") {
|
|
89
|
+
printLifecycleHelp();
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
if (command === "--version" || command === "-v") {
|
|
93
|
+
console.log(packageVersion);
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
if (command === "doctor")
|
|
97
|
+
return doctorCommand(argv.slice(1));
|
|
98
|
+
if (command === "rename")
|
|
99
|
+
return renameCommand(argv.slice(1));
|
|
100
|
+
if (command === "skills")
|
|
101
|
+
return skillsCommand(argv.slice(1));
|
|
102
|
+
if (command === "mcp")
|
|
103
|
+
return mcpCommand(argv.slice(1));
|
|
104
|
+
printLifecycleHelp();
|
|
105
|
+
return command === "help" ? 0 : 1;
|
|
106
|
+
}
|
|
107
|
+
async function doctorCommand(argv) {
|
|
108
|
+
const parsed = parseFlags(argv, ROOT_FLAGS);
|
|
109
|
+
if (flagBool(parsed.flags, "help")) {
|
|
110
|
+
printLifecycleHelp();
|
|
111
|
+
return 0;
|
|
112
|
+
}
|
|
113
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
114
|
+
const result = await doctorProject(root);
|
|
115
|
+
for (const error of result.errors)
|
|
116
|
+
console.error(`ERROR: ${error}`);
|
|
117
|
+
if (result.ok)
|
|
118
|
+
console.log(`OK: ${root}`);
|
|
119
|
+
return result.ok ? 0 : 1;
|
|
120
|
+
}
|
|
121
|
+
async function renameCommand(argv) {
|
|
122
|
+
const parsed = parseFlags(argv, RENAME_FLAGS);
|
|
123
|
+
if (flagBool(parsed.flags, "help")) {
|
|
124
|
+
printLifecycleHelp();
|
|
125
|
+
return 0;
|
|
126
|
+
}
|
|
127
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
128
|
+
const result = await renameProject(root, {
|
|
129
|
+
title: flagString(parsed.flags, "title"),
|
|
130
|
+
slug: flagString(parsed.flags, "slug"),
|
|
131
|
+
packageName: flagString(parsed.flags, "package")
|
|
132
|
+
});
|
|
133
|
+
console.log(`Renamed project to ${result.slug}`);
|
|
134
|
+
return 0;
|
|
135
|
+
}
|
|
136
|
+
async function skillsCommand(argv) {
|
|
137
|
+
const subcommand = argv[0] ?? "list";
|
|
138
|
+
const parsed = parseFlags(argv.slice(1), SKILLS_FLAGS);
|
|
139
|
+
if (subcommand === "help" || subcommand === "--help" || subcommand === "-h" || flagBool(parsed.flags, "help")) {
|
|
140
|
+
printSkillsHelp();
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
if (subcommand === "list") {
|
|
144
|
+
assertOnlyOptions(parsed.flags, "skills list", ["root"]);
|
|
145
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
146
|
+
assertNoArguments(parsed.positionals, "skills list");
|
|
147
|
+
const skills = await listInstalledSkills(root);
|
|
148
|
+
if (skills.length === 0) {
|
|
149
|
+
console.log("No project-local skills installed.");
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
for (const skill of skills)
|
|
153
|
+
console.log(`${skill.name}\t${skill.path}`);
|
|
154
|
+
}
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
if (subcommand === "status") {
|
|
158
|
+
assertOnlyOptions(parsed.flags, "skills status", ["root"]);
|
|
159
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
160
|
+
assertNoArguments(parsed.positionals, "skills status");
|
|
161
|
+
const state = await readCapabilities(root);
|
|
162
|
+
const skills = await listInstalledSkills(root);
|
|
163
|
+
const skillRoots = new Set(skills.map((skill) => skill.root));
|
|
164
|
+
const skillIds = new Set(skills.map((skill) => skill.name));
|
|
165
|
+
console.log(`agent\t${state.agent}`);
|
|
166
|
+
console.log(`project_preset\t${state.preset}`);
|
|
167
|
+
console.log(`scope\t${state.scope}`);
|
|
168
|
+
console.log(`skill_roots\t${skillRoots.size}`);
|
|
169
|
+
console.log(`installed_skill_ids\t${skillIds.size}`);
|
|
170
|
+
console.log(`installed_skill_copies\t${skills.length}`);
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
if (subcommand === "presets") {
|
|
174
|
+
assertOnlyOptions(parsed.flags, "skills presets", []);
|
|
175
|
+
assertNoArguments(parsed.positionals, "skills presets");
|
|
176
|
+
for (const [name, preset] of Object.entries(AGENT_STACK.presets)) {
|
|
177
|
+
console.log(`${name}: ${preset.description}`);
|
|
178
|
+
}
|
|
179
|
+
return 0;
|
|
180
|
+
}
|
|
181
|
+
if (subcommand === "install") {
|
|
182
|
+
assertOnlyOptions(parsed.flags, "skills install", ["root", "preset", "agent"]);
|
|
183
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
184
|
+
assertNoArguments(parsed.positionals, "skills install");
|
|
185
|
+
const preset = flagString(parsed.flags, "preset") ?? "default";
|
|
186
|
+
const result = await installSkills(root, preset, {
|
|
187
|
+
agent: flagString(parsed.flags, "agent")
|
|
188
|
+
});
|
|
189
|
+
console.log(`Installed skill preset ${preset} with ${result.count ?? 0} command(s).`);
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
if (subcommand === "remove" || subcommand === "uninstall") {
|
|
193
|
+
if (flagString(parsed.flags, "agent")) {
|
|
194
|
+
throw new Error("skills remove is project-local and does not take --agent");
|
|
195
|
+
}
|
|
196
|
+
assertOnlyOptions(parsed.flags, `skills ${subcommand}`, ["root"]);
|
|
197
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
198
|
+
const result = await removeSkills(root, parsed.positionals);
|
|
199
|
+
console.log(`Removed ${result.count ?? 0} skill(s).`);
|
|
200
|
+
return 0;
|
|
201
|
+
}
|
|
202
|
+
if (subcommand === "update") {
|
|
203
|
+
assertOnlyOptions(parsed.flags, "skills update", ["root"]);
|
|
204
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
205
|
+
assertNoArguments(parsed.positionals, "skills update");
|
|
206
|
+
await updateSkills(root);
|
|
207
|
+
console.log("Updated project-local skills.");
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
throw new Error(`unknown skills command: ${subcommand}`);
|
|
211
|
+
}
|
|
212
|
+
async function mcpCommand(argv) {
|
|
213
|
+
const subcommand = argv[0] ?? "list";
|
|
214
|
+
const parsed = parseFlags(argv.slice(1), MCP_FLAGS);
|
|
215
|
+
if (subcommand === "help" || subcommand === "--help" || subcommand === "-h" || flagBool(parsed.flags, "help")) {
|
|
216
|
+
printMcpHelp();
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
if (subcommand === "list") {
|
|
220
|
+
assertOnlyOptions(parsed.flags, "mcp list", ["root"]);
|
|
221
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
222
|
+
assertNoArguments(parsed.positionals, "mcp list");
|
|
223
|
+
const state = await readCapabilities(root);
|
|
224
|
+
const enabled = new Set(state.mcp_servers ?? []);
|
|
225
|
+
for (const [name, server] of Object.entries(AGENT_STACK.mcp_servers)) {
|
|
226
|
+
const status = enabled.has(name) ? "enabled" : "available";
|
|
227
|
+
const installer = server.install_command || "manual";
|
|
228
|
+
console.log(`${status}\t${name}\t${server.source_need}\t${installer}`);
|
|
229
|
+
}
|
|
230
|
+
return 0;
|
|
231
|
+
}
|
|
232
|
+
if (subcommand === "enabled") {
|
|
233
|
+
assertOnlyOptions(parsed.flags, "mcp enabled", ["root"]);
|
|
234
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
235
|
+
assertNoArguments(parsed.positionals, "mcp enabled");
|
|
236
|
+
const state = await readCapabilities(root);
|
|
237
|
+
for (const name of state.mcp_servers ?? [])
|
|
238
|
+
console.log(name);
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
241
|
+
if (subcommand === "available") {
|
|
242
|
+
assertOnlyOptions(parsed.flags, "mcp available", []);
|
|
243
|
+
assertNoArguments(parsed.positionals, "mcp available");
|
|
244
|
+
for (const [name, server] of Object.entries(AGENT_STACK.mcp_servers)) {
|
|
245
|
+
const installer = server.install_command || "manual";
|
|
246
|
+
console.log(`${name}\t${server.source_need}\t${installer}`);
|
|
247
|
+
}
|
|
248
|
+
return 0;
|
|
249
|
+
}
|
|
250
|
+
if (subcommand === "commands") {
|
|
251
|
+
assertOnlyOptions(parsed.flags, "mcp commands", ["root"]);
|
|
252
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
253
|
+
const selected = parsed.positionals.length > 0 ? parsed.positionals : (await readCapabilities(root)).mcp_servers;
|
|
254
|
+
const commands = mcpToolCommandTexts(selected, "install_command");
|
|
255
|
+
for (const command of commands)
|
|
256
|
+
console.log(command);
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
if (subcommand === "enable") {
|
|
260
|
+
assertOnlyOptions(parsed.flags, "mcp enable", ["root", "agent"]);
|
|
261
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
262
|
+
assertSomeArguments(parsed.positionals, "mcp enable");
|
|
263
|
+
const agent = flagString(parsed.flags, "agent");
|
|
264
|
+
await enableMcpServers(root, parsed.positionals, agent ? { agent } : {});
|
|
265
|
+
return 0;
|
|
266
|
+
}
|
|
267
|
+
if (subcommand === "disable") {
|
|
268
|
+
assertOnlyOptions(parsed.flags, "mcp disable", ["root", "agent"]);
|
|
269
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
270
|
+
assertSomeArguments(parsed.positionals, "mcp disable");
|
|
271
|
+
const agent = flagString(parsed.flags, "agent");
|
|
272
|
+
await disableMcpServers(root, parsed.positionals, agent ? { agent } : {});
|
|
273
|
+
return 0;
|
|
274
|
+
}
|
|
275
|
+
if (subcommand === "install") {
|
|
276
|
+
assertOnlyOptions(parsed.flags, "mcp install", ["root"]);
|
|
277
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
278
|
+
const result = await installMcpTools(root, parsed.positionals);
|
|
279
|
+
console.log(`Ran ${result.count ?? 0} MCP install command(s).`);
|
|
280
|
+
return 0;
|
|
281
|
+
}
|
|
282
|
+
if (subcommand === "uninstall") {
|
|
283
|
+
assertOnlyOptions(parsed.flags, "mcp uninstall", ["root"]);
|
|
284
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
285
|
+
const result = await uninstallMcpTools(root, parsed.positionals);
|
|
286
|
+
console.log(`Ran ${result.count ?? 0} MCP uninstall command(s).`);
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
289
|
+
if (subcommand === "doctor") {
|
|
290
|
+
assertOnlyOptions(parsed.flags, "mcp doctor", ["root"]);
|
|
291
|
+
const root = resolve(flagString(parsed.flags, "root") ?? ".");
|
|
292
|
+
assertNoArguments(parsed.positionals, "mcp doctor");
|
|
293
|
+
const result = await doctorMcpServers(root);
|
|
294
|
+
for (const error of result.errors)
|
|
295
|
+
console.error(`ERROR: ${error}`);
|
|
296
|
+
for (const warning of result.warnings)
|
|
297
|
+
console.warn(`WARN: ${warning}`);
|
|
298
|
+
if (result.ok)
|
|
299
|
+
console.log(`OK: ${result.enabled.length} MCP server(s) enabled.`);
|
|
300
|
+
return result.ok ? 0 : 1;
|
|
301
|
+
}
|
|
302
|
+
throw new Error(`unknown mcp command: ${subcommand}`);
|
|
303
|
+
}
|
|
304
|
+
function parseFlags(argv, schema) {
|
|
305
|
+
const flags = {};
|
|
306
|
+
const positionals = [];
|
|
307
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
308
|
+
const arg = argv[index];
|
|
309
|
+
if (arg === "--") {
|
|
310
|
+
positionals.push(...argv.slice(index + 1));
|
|
311
|
+
break;
|
|
312
|
+
}
|
|
313
|
+
if (arg === "-h") {
|
|
314
|
+
if (!schema.boolean.has("help"))
|
|
315
|
+
throw new Error("unknown option: -h");
|
|
316
|
+
flags.help = true;
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (arg === "-v") {
|
|
320
|
+
if (!schema.boolean.has("version"))
|
|
321
|
+
throw new Error("unknown option: -v");
|
|
322
|
+
flags.version = true;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (arg.startsWith("-") && !arg.startsWith("--")) {
|
|
326
|
+
throw new Error(`unknown option: ${arg}`);
|
|
327
|
+
}
|
|
328
|
+
if (!arg.startsWith("--")) {
|
|
329
|
+
positionals.push(arg);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const raw = arg.slice(2);
|
|
333
|
+
const equals = raw.indexOf("=");
|
|
334
|
+
const key = equals === -1 ? raw : raw.slice(0, equals);
|
|
335
|
+
const inlineValue = equals === -1 ? undefined : raw.slice(equals + 1);
|
|
336
|
+
if (!schema.boolean.has(key) && !schema.string.has(key)) {
|
|
337
|
+
throw new Error(`unknown option: --${key}`);
|
|
338
|
+
}
|
|
339
|
+
if (schema.boolean.has(key)) {
|
|
340
|
+
if (inlineValue !== undefined)
|
|
341
|
+
throw new Error(`option does not take a value: --${key}`);
|
|
342
|
+
flags[key] = true;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
const value = inlineValue ?? argv[index + 1];
|
|
346
|
+
if (!value || value.startsWith("--")) {
|
|
347
|
+
throw new Error(`missing value for option: --${key}`);
|
|
348
|
+
}
|
|
349
|
+
flags[key] = value;
|
|
350
|
+
if (inlineValue === undefined) {
|
|
351
|
+
index += 1;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return { flags, positionals };
|
|
355
|
+
}
|
|
356
|
+
function flagSchema(booleanFlags, stringFlags) {
|
|
357
|
+
return { boolean: new Set(booleanFlags), string: new Set(stringFlags) };
|
|
358
|
+
}
|
|
359
|
+
function flagString(flags, key) {
|
|
360
|
+
const value = flags[key];
|
|
361
|
+
return typeof value === "string" ? value : undefined;
|
|
362
|
+
}
|
|
363
|
+
function flagBool(flags, key) {
|
|
364
|
+
return flags[key] === true;
|
|
365
|
+
}
|
|
366
|
+
function assertNoArguments(positionals, command) {
|
|
367
|
+
if (positionals.length > 0) {
|
|
368
|
+
throw new Error(`${command} does not take arguments: ${positionals.join(" ")}`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
function assertSomeArguments(positionals, command) {
|
|
372
|
+
if (positionals.length === 0) {
|
|
373
|
+
throw new Error(`${command} requires at least one argument`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
function assertOnlyOptions(flags, command, allowedOptions) {
|
|
377
|
+
const allowed = new Set([...allowedOptions, "help"]);
|
|
378
|
+
const unexpected = Object.entries(flags)
|
|
379
|
+
.filter(([, value]) => value !== undefined)
|
|
380
|
+
.map(([name]) => name)
|
|
381
|
+
.filter((name) => !allowed.has(name));
|
|
382
|
+
if (unexpected.length > 0) {
|
|
383
|
+
throw new Error(`${command} does not accept ${unexpected.map((name) => `--${name}`).join(", ")}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function printCreateHelp() {
|
|
387
|
+
console.log([
|
|
388
|
+
"Usage: create-academic-research <project-name> [options]",
|
|
389
|
+
"",
|
|
390
|
+
"Create an agent-ready academic research repository.",
|
|
391
|
+
"",
|
|
392
|
+
"Options:",
|
|
393
|
+
" --yes Use defaults without prompts.",
|
|
394
|
+
" --title <name> Project title. Default: title-cased project name.",
|
|
395
|
+
" --slug <name> Repository/package slug. Default: normalized project name.",
|
|
396
|
+
" --package <name> Python package name. Default: normalized project name.",
|
|
397
|
+
" --preset <name> Capability preset: minimal, default, literature, writing, full.",
|
|
398
|
+
" --profile <name> Project profile metadata. Default: academic-general.",
|
|
399
|
+
" --agent <name> Agent target. Default: auto-detect.",
|
|
400
|
+
" --install-skills Install project-local skills without prompting.",
|
|
401
|
+
" --no-install-skills Skip project-local skill installation.",
|
|
402
|
+
" --install-mcp-tools Run external MCP install commands after creation.",
|
|
403
|
+
" -h, --help Show this help.",
|
|
404
|
+
" -v, --version Show package version."
|
|
405
|
+
].join("\n"));
|
|
406
|
+
}
|
|
407
|
+
function printLifecycleHelp() {
|
|
408
|
+
console.log([
|
|
409
|
+
"Usage: academic-research <doctor|rename|skills|mcp>",
|
|
410
|
+
"",
|
|
411
|
+
"Manage a generated academic research repository after creation.",
|
|
412
|
+
"",
|
|
413
|
+
"Options:",
|
|
414
|
+
" -h, --help Show this help.",
|
|
415
|
+
" -v, --version Show package version."
|
|
416
|
+
].join("\n"));
|
|
417
|
+
}
|
|
418
|
+
function printSkillsHelp() {
|
|
419
|
+
console.log([
|
|
420
|
+
"Usage: academic-research skills <list|status|presets|install|remove|uninstall|update> [options]",
|
|
421
|
+
"",
|
|
422
|
+
"Manage project-local skill installs for a generated research repository.",
|
|
423
|
+
"",
|
|
424
|
+
"Options:",
|
|
425
|
+
" --root <path> Project root for list, status, install, remove, uninstall, update.",
|
|
426
|
+
" --preset <name> Capability preset for install.",
|
|
427
|
+
" --agent <name> Agent selector for install. Default: project capability agent.",
|
|
428
|
+
" -h, --help Show this help."
|
|
429
|
+
].join("\n"));
|
|
430
|
+
}
|
|
431
|
+
function printMcpHelp() {
|
|
432
|
+
console.log([
|
|
433
|
+
"Usage: academic-research mcp <list|enabled|available|commands|enable|disable|install|uninstall|doctor> [servers...]",
|
|
434
|
+
"",
|
|
435
|
+
"Manage MCP records and explicit external MCP tool installs.",
|
|
436
|
+
"",
|
|
437
|
+
"Options:",
|
|
438
|
+
" --root <path> Project root for project-state commands.",
|
|
439
|
+
" --agent <name> Agent for enable/disable generated snippets.",
|
|
440
|
+
" -h, --help Show this help."
|
|
441
|
+
].join("\n"));
|
|
442
|
+
}
|
|
443
|
+
function readPackageVersion() {
|
|
444
|
+
try {
|
|
445
|
+
const packageJson = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
|
|
446
|
+
return packageJson.version ?? "0.0.0";
|
|
447
|
+
}
|
|
448
|
+
catch {
|
|
449
|
+
return "0.0.0";
|
|
450
|
+
}
|
|
451
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function exists(path: string): Promise<boolean>;
|
|
2
|
+
export declare function isNonEmptyDirectory(path: string): Promise<boolean>;
|
|
3
|
+
export declare function copyDirectory(source: string, target: string): Promise<void>;
|
|
4
|
+
export declare function readText(path: string): Promise<string>;
|
|
5
|
+
export declare function writeText(path: string, content: string): Promise<void>;
|
|
6
|
+
export declare function readJson<T = unknown>(path: string): Promise<T>;
|
|
7
|
+
export declare function writeJson(path: string, value: unknown): Promise<void>;
|
|
8
|
+
export declare function movePath(source: string, target: string): Promise<void>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { cp, mkdir, readdir, readFile, rename, stat, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
export async function exists(path) {
|
|
4
|
+
try {
|
|
5
|
+
await stat(path);
|
|
6
|
+
return true;
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export async function isNonEmptyDirectory(path) {
|
|
13
|
+
if (!(await exists(path)))
|
|
14
|
+
return false;
|
|
15
|
+
const entries = await readdir(path);
|
|
16
|
+
return entries.length > 0;
|
|
17
|
+
}
|
|
18
|
+
export async function copyDirectory(source, target) {
|
|
19
|
+
await cp(source, target, {
|
|
20
|
+
recursive: true,
|
|
21
|
+
filter: (path) => {
|
|
22
|
+
const relative = path.startsWith(source) ? path.slice(source.length) : path;
|
|
23
|
+
const parts = relative.split(/[\\/]/).filter(Boolean);
|
|
24
|
+
return !parts.includes("node_modules") && !parts.includes("__pycache__");
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export async function readText(path) {
|
|
29
|
+
return readFile(path, "utf8");
|
|
30
|
+
}
|
|
31
|
+
export async function writeText(path, content) {
|
|
32
|
+
await mkdir(dirname(path), { recursive: true });
|
|
33
|
+
await writeFile(path, content, "utf8");
|
|
34
|
+
}
|
|
35
|
+
export async function readJson(path) {
|
|
36
|
+
return JSON.parse(await readText(path));
|
|
37
|
+
}
|
|
38
|
+
export async function writeJson(path, value) {
|
|
39
|
+
await writeText(path, `${JSON.stringify(value, null, 2)}\n`);
|
|
40
|
+
}
|
|
41
|
+
export async function movePath(source, target) {
|
|
42
|
+
await mkdir(dirname(target), { recursive: true });
|
|
43
|
+
await rename(source, target);
|
|
44
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const PYTHON_KEYWORDS = new Set([
|
|
2
|
+
"false",
|
|
3
|
+
"none",
|
|
4
|
+
"true",
|
|
5
|
+
"and",
|
|
6
|
+
"as",
|
|
7
|
+
"assert",
|
|
8
|
+
"async",
|
|
9
|
+
"await",
|
|
10
|
+
"break",
|
|
11
|
+
"class",
|
|
12
|
+
"continue",
|
|
13
|
+
"def",
|
|
14
|
+
"del",
|
|
15
|
+
"elif",
|
|
16
|
+
"else",
|
|
17
|
+
"except",
|
|
18
|
+
"finally",
|
|
19
|
+
"for",
|
|
20
|
+
"from",
|
|
21
|
+
"global",
|
|
22
|
+
"if",
|
|
23
|
+
"import",
|
|
24
|
+
"in",
|
|
25
|
+
"is",
|
|
26
|
+
"lambda",
|
|
27
|
+
"nonlocal",
|
|
28
|
+
"not",
|
|
29
|
+
"or",
|
|
30
|
+
"pass",
|
|
31
|
+
"raise",
|
|
32
|
+
"return",
|
|
33
|
+
"try",
|
|
34
|
+
"while",
|
|
35
|
+
"with",
|
|
36
|
+
"yield"
|
|
37
|
+
]);
|
|
38
|
+
export function slugify(value) {
|
|
39
|
+
const slug = String(value ?? "")
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
42
|
+
.replace(/^-+|-+$/g, "");
|
|
43
|
+
return slug || "academic-research-project";
|
|
44
|
+
}
|
|
45
|
+
export function packageify(value) {
|
|
46
|
+
let packageName = String(value ?? "")
|
|
47
|
+
.replace(/[^a-zA-Z0-9]+/g, "_")
|
|
48
|
+
.replace(/^_+|_+$/g, "")
|
|
49
|
+
.toLowerCase();
|
|
50
|
+
if (!packageName)
|
|
51
|
+
packageName = "academic_research_project";
|
|
52
|
+
if (/^[0-9]/.test(packageName))
|
|
53
|
+
packageName = `project_${packageName}`;
|
|
54
|
+
if (PYTHON_KEYWORDS.has(packageName))
|
|
55
|
+
packageName = `${packageName}_project`;
|
|
56
|
+
return packageName;
|
|
57
|
+
}
|
|
58
|
+
export function titleFromSlug(value) {
|
|
59
|
+
const words = String(value ?? "")
|
|
60
|
+
.split(/[-_]+/)
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
.map((word) => `${word[0]?.toUpperCase() ?? ""}${word.slice(1)}`);
|
|
63
|
+
return words.join(" ") || "Academic Research Project";
|
|
64
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface CreateProjectOptions {
|
|
2
|
+
target: string;
|
|
3
|
+
title?: string;
|
|
4
|
+
slug?: string;
|
|
5
|
+
packageName?: string;
|
|
6
|
+
profile?: string;
|
|
7
|
+
preset?: string;
|
|
8
|
+
agent?: string;
|
|
9
|
+
installSkills?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface RenameProjectOptions {
|
|
12
|
+
title?: string;
|
|
13
|
+
slug?: string;
|
|
14
|
+
packageName?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface ProjectResult {
|
|
17
|
+
root: string;
|
|
18
|
+
title: string;
|
|
19
|
+
slug: string;
|
|
20
|
+
packageName: string;
|
|
21
|
+
}
|
|
22
|
+
export interface DoctorResult {
|
|
23
|
+
ok: boolean;
|
|
24
|
+
errors: string[];
|
|
25
|
+
}
|
|
26
|
+
export declare function createProject(options: CreateProjectOptions): Promise<ProjectResult>;
|
|
27
|
+
export declare function renameProject(root: string, options: RenameProjectOptions): Promise<ProjectResult>;
|
|
28
|
+
export declare function doctorProject(root: string): Promise<DoctorResult>;
|