lumina-wiki 0.1.0 → 0.3.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/README.md +45 -3
- package/bin/lumina.js +25 -17
- package/package.json +6 -2
- package/src/installer/banner.js +83 -0
- package/src/installer/commands.js +58 -35
- package/src/installer/prompts.js +46 -18
- package/src/scripts/schemas.mjs +2 -2
- package/src/skills/core/ingest/SKILL.md +19 -1
- package/src/skills/packs/reading/chapter-ingest/SKILL.md +12 -5
- package/src/skills/packs/reading/character-track/SKILL.md +2 -2
- package/src/skills/packs/reading/plot-recap/SKILL.md +3 -3
- package/src/skills/packs/reading/theme-map/SKILL.md +2 -2
- package/src/skills/packs/research/discover/SKILL.md +2 -2
- package/src/skills/packs/research/prefill/SKILL.md +3 -3
- package/src/skills/packs/research/setup/SKILL.md +14 -3
- package/src/skills/packs/research/survey/SKILL.md +3 -3
- package/src/templates/.env.example +1 -1
- package/src/templates/README.md +36 -29
- package/src/templates/_lumina/config/lumina.config.yaml +2 -0
- package/src/tools/extract_pdf.py +190 -0
- package/src/tools/fetch_wikipedia.py +1 -1
- package/src/tools/requirements.txt +10 -10
package/README.md
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
# Lumina-Wiki
|
|
6
6
|
|
|
7
|
-
> **
|
|
7
|
+
> **Where Knowledge Starts to Glow.**
|
|
8
|
+
>
|
|
9
|
+
> The LLM-maintained Knowledge Artifact for Technical Research.
|
|
8
10
|
|
|
9
11
|
Lumina-Wiki is a ready-to-use implementation of the **[LLM-Wiki vision](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f)** articulated by **Andrej Karpathy, founding member of OpenAI and former Director of AI at Tesla.**
|
|
10
12
|
|
|
@@ -161,7 +163,25 @@ These are the scripts that the agent's skills use to perform actions.
|
|
|
161
163
|
|
|
162
164
|
---
|
|
163
165
|
|
|
164
|
-
## 6.
|
|
166
|
+
## 6. What's Coming Next
|
|
167
|
+
|
|
168
|
+
The current release is **v0.2** (preview). The full plan lives in [`ROADMAP.md`](./ROADMAP.md). Headline items:
|
|
169
|
+
|
|
170
|
+
**v1.0.0 — First Stable**
|
|
171
|
+
- **Daily search & fetch** — watchlist queries (`_lumina/config/watchlist.yml`) run on a cadence; new arXiv / Semantic Scholar hits land in `raw/discovered/<date>/` automatically.
|
|
172
|
+
- New `/lumi-daily` skill to triage what landed since last run.
|
|
173
|
+
- Stability lock for the v0.1 surface (CLI flags, exit codes, schema field names).
|
|
174
|
+
- Cross-platform CI matrix (macOS + Linux + Windows, Node 20 + 22).
|
|
175
|
+
|
|
176
|
+
**v2.0.0 — Research Pack Source Expansion**
|
|
177
|
+
- **New paper sources:** OpenAlex, Unpaywall, CORE (Priority 1) → OpenReview, Hugging Face Papers, Papers With Code (Priority 2) → Crossref, DOAJ, research-blog RSS (Priority 3).
|
|
178
|
+
- **Paper ranking:** new `/lumi-rank` skill surfacing influential-citation count, field-normalized citation rank, Scite support/contrast tally, and Altmetric attention — all into a `ranking:` block on the paper's frontmatter.
|
|
179
|
+
|
|
180
|
+
**Want to help?** Pick any unchecked item in `ROADMAP.md`, open an issue to claim it, then send a PR. Source fetchers all follow the same pattern in `src/tools/` (CLI + JSON, no async, exit codes `0/2/3`) so they're a friendly first contribution. See the local-dev steps below.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 7. Contributing & License
|
|
165
185
|
|
|
166
186
|
<details>
|
|
167
187
|
<summary>🛠️ Local Development (for contributors)</summary>
|
|
@@ -186,6 +206,10 @@ npm run test:all
|
|
|
186
206
|
<a name="vietnamese"></a>
|
|
187
207
|
***README Tiếng Việt***
|
|
188
208
|
|
|
209
|
+
> **Where Knowledge Starts to Glow.**
|
|
210
|
+
>
|
|
211
|
+
> Khối Tri thức được duy trì bởi LLM dành cho nghiên cứu kỹ thuật.
|
|
212
|
+
|
|
189
213
|
## 1. Luồng làm việc cốt lõi
|
|
190
214
|
|
|
191
215
|
Lumina-Wiki hoạt động dựa trên một nguyên tắc đơn giản: tách biệt tài liệu thô của bạn khỏi khối kiến thức có cấu trúc của AI.
|
|
@@ -318,7 +342,25 @@ Lumina tạo ra một không gian làm việc với mục đích rõ ràng cho t
|
|
|
318
342
|
|
|
319
343
|
---
|
|
320
344
|
|
|
321
|
-
## 6.
|
|
345
|
+
## 6. Lộ trình sắp tới
|
|
346
|
+
|
|
347
|
+
Phiên bản hiện tại là **v0.2** (preview). Kế hoạch đầy đủ ở [`ROADMAP.md`](./ROADMAP.md). Những hạng mục chính:
|
|
348
|
+
|
|
349
|
+
**v1.0.0 — Bản ổn định đầu tiên**
|
|
350
|
+
- **Daily search & fetch** — watchlist (`_lumina/config/watchlist.yml`) chạy theo lịch; paper mới từ arXiv / Semantic Scholar tự động đáp xuống `raw/discovered/<ngày>/`.
|
|
351
|
+
- Skill mới `/lumi-daily` để triage những gì vừa thu thập kể từ lần chạy trước.
|
|
352
|
+
- Khoá ổn định bề mặt v0.1 (CLI flags, exit codes, tên trường schema).
|
|
353
|
+
- CI matrix đa nền tảng (macOS + Linux + Windows, Node 20 + 22).
|
|
354
|
+
|
|
355
|
+
**v2.0.0 — Mở rộng nguồn paper cho Research Pack**
|
|
356
|
+
- **Nguồn paper mới:** OpenAlex, Unpaywall, CORE (Ưu tiên 1) → OpenReview, Hugging Face Papers, Papers With Code (Ưu tiên 2) → Crossref, DOAJ, RSS từ các blog research lab (Ưu tiên 3).
|
|
357
|
+
- **Đánh giá paper:** skill mới `/lumi-rank` đưa các chỉ số influential-citation count, xếp hạng theo lĩnh vực, Scite support/contrast, và Altmetric vào block `ranking:` trong frontmatter.
|
|
358
|
+
|
|
359
|
+
**Muốn đóng góp?** Chọn bất kỳ hạng mục chưa tick trong `ROADMAP.md`, mở issue để nhận, rồi gửi PR. Các fetcher nguồn paper đều tuân theo cùng pattern trong `src/tools/` (CLI + JSON, không async, exit codes `0/2/3`) nên rất phù hợp cho lần contribute đầu tiên. Xem hướng dẫn dev cục bộ bên dưới.
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## 7. Đóng góp & Giấy phép
|
|
322
364
|
|
|
323
365
|
<details>
|
|
324
366
|
<summary>🛠️ Phát triển cục bộ (dành cho người đóng góp)</summary>
|
package/bin/lumina.js
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
* lumina --help — print usage
|
|
11
11
|
*
|
|
12
12
|
* Flags (all commands):
|
|
13
|
-
* --
|
|
13
|
+
* --directory <path> — installation directory (defaults to current directory)
|
|
14
|
+
* --cwd <path> — backward-compat alias for --directory
|
|
14
15
|
* --yes, -y — accept all defaults (non-interactive / CI)
|
|
15
16
|
* --no-update — skip npm registry version check
|
|
16
17
|
* --re-link — recompute symlink/junction/copy strategy
|
|
@@ -71,7 +72,7 @@ async function handleVersionOptionIfPresent(argv) {
|
|
|
71
72
|
const handledVersion = await handleVersionOptionIfPresent(process.argv);
|
|
72
73
|
if (handledVersion) process.exit(0);
|
|
73
74
|
|
|
74
|
-
const { Command } = await import('commander');
|
|
75
|
+
const { Command, Option } = await import('commander');
|
|
75
76
|
const program = new Command();
|
|
76
77
|
|
|
77
78
|
program
|
|
@@ -86,18 +87,20 @@ Exit codes:
|
|
|
86
87
|
3 upgrade incompatibility (manifest references unknown pack)
|
|
87
88
|
|
|
88
89
|
Flags applicable to all commands:
|
|
89
|
-
--
|
|
90
|
-
--yes, -y
|
|
91
|
-
--no-update
|
|
92
|
-
--re-link
|
|
93
|
-
--packs <list>
|
|
94
|
-
--ide-targets <list> target
|
|
90
|
+
--directory <path> installation directory (defaults to current directory)
|
|
91
|
+
--yes, -y accept all defaults; non-interactive (CI use)
|
|
92
|
+
--no-update skip npm registry version check
|
|
93
|
+
--re-link recompute symlink/junction/copy strategy from platform
|
|
94
|
+
--packs <list> install packs: core,research,reading
|
|
95
|
+
--ide-targets <list> target CLIs: claude_code,codex,gemini_cli,qwen,iflow,cursor,generic
|
|
96
|
+
codex covers all AGENTS.md-compatible CLIs
|
|
97
|
+
(Codex, Amp, Crush, Goose, Auggie, OpenCode, etc.)
|
|
95
98
|
|
|
96
99
|
Examples:
|
|
97
100
|
npx lumina-wiki install
|
|
98
101
|
lumina install --yes
|
|
99
102
|
lumina install --yes --packs core,research,reading --ide-targets claude_code,codex
|
|
100
|
-
lumina install --
|
|
103
|
+
lumina install --directory /path/to/project
|
|
101
104
|
lumina uninstall
|
|
102
105
|
lumina --version
|
|
103
106
|
`);
|
|
@@ -106,7 +109,8 @@ Examples:
|
|
|
106
109
|
// Global options
|
|
107
110
|
// ---------------------------------------------------------------------------
|
|
108
111
|
program
|
|
109
|
-
.option('--
|
|
112
|
+
.option('--directory <path>', 'installation directory', process.cwd())
|
|
113
|
+
.addOption(new Option('--cwd <path>', 'alias for --directory').hideHelp())
|
|
110
114
|
.option('-y, --yes', 'accept all defaults (non-interactive)')
|
|
111
115
|
.option('--no-update', 'skip npm registry version check')
|
|
112
116
|
.option('--re-link', 'recompute symlink strategy from current platform capabilities');
|
|
@@ -124,26 +128,29 @@ program
|
|
|
124
128
|
program
|
|
125
129
|
.command('install')
|
|
126
130
|
.description('scaffold or upgrade a Lumina Wiki workspace')
|
|
127
|
-
.option('--
|
|
131
|
+
.option('--directory <path>', 'installation directory')
|
|
132
|
+
.addOption(new Option('--cwd <path>', 'alias for --directory').hideHelp())
|
|
128
133
|
.option('-y, --yes', 'accept all defaults')
|
|
129
134
|
.option('--no-update', 'skip update check')
|
|
130
135
|
.option('--re-link', 'recompute symlink strategy')
|
|
131
136
|
.option('--packs <list>', 'comma-separated packs to install: core,research,reading')
|
|
132
137
|
.option('--ide-targets <list>', 'comma-separated IDE targets')
|
|
133
|
-
.
|
|
138
|
+
.addOption(new Option('--project-name <name>', 'override auto-derived project name').hideHelp())
|
|
134
139
|
.option('--communication-language <language>', 'language agents use when talking to the user')
|
|
135
140
|
.option('--document-output-language <language>', 'language used for wiki documents')
|
|
136
141
|
.action(async (cmdOpts) => {
|
|
137
142
|
const globalOpts = program.opts();
|
|
138
|
-
const
|
|
143
|
+
const mergedDir = cmdOpts.directory ?? cmdOpts.cwd ?? globalOpts.directory ?? globalOpts.cwd ?? process.cwd();
|
|
139
144
|
const mergedYes = cmdOpts.yes ?? globalOpts.yes ?? false;
|
|
140
145
|
const mergedReLink = cmdOpts.reLink ?? globalOpts.reLink ?? false;
|
|
141
146
|
const mergedNoUpdate = cmdOpts.noUpdate ?? globalOpts.noUpdate ?? false;
|
|
142
147
|
|
|
143
148
|
try {
|
|
149
|
+
const { displayBanner } = await import('../src/installer/banner.js');
|
|
150
|
+
await displayBanner();
|
|
144
151
|
const { installCommand } = await import('../src/installer/commands.js');
|
|
145
152
|
await installCommand({
|
|
146
|
-
|
|
153
|
+
directory: resolve(mergedDir),
|
|
147
154
|
yes: Boolean(mergedYes),
|
|
148
155
|
reLink: Boolean(mergedReLink),
|
|
149
156
|
noUpdate: Boolean(mergedNoUpdate),
|
|
@@ -168,17 +175,18 @@ program
|
|
|
168
175
|
program
|
|
169
176
|
.command('uninstall')
|
|
170
177
|
.description('remove Lumina-managed files (wiki/ and raw/ are preserved)')
|
|
171
|
-
.option('--
|
|
178
|
+
.option('--directory <path>', 'installation directory')
|
|
179
|
+
.addOption(new Option('--cwd <path>', 'alias for --directory').hideHelp())
|
|
172
180
|
.option('-y, --yes', 'skip confirmation prompt')
|
|
173
181
|
.action(async (cmdOpts) => {
|
|
174
182
|
const globalOpts = program.opts();
|
|
175
|
-
const
|
|
183
|
+
const mergedDir = cmdOpts.directory ?? cmdOpts.cwd ?? globalOpts.directory ?? globalOpts.cwd ?? process.cwd();
|
|
176
184
|
const mergedYes = cmdOpts.yes ?? globalOpts.yes ?? false;
|
|
177
185
|
|
|
178
186
|
try {
|
|
179
187
|
const { uninstallCommand } = await import('../src/installer/commands.js');
|
|
180
188
|
await uninstallCommand({
|
|
181
|
-
cwd: resolve(
|
|
189
|
+
cwd: resolve(mergedDir),
|
|
182
190
|
yes: Boolean(mergedYes),
|
|
183
191
|
});
|
|
184
192
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "lumina-wiki",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "Domain-agnostic, multi-IDE wiki scaffolder — Karpathy's LLM-Wiki vision, cross-platform and pack-based.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"llm-wiki",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
},
|
|
33
33
|
"files": [
|
|
34
34
|
"bin/lumina.js",
|
|
35
|
+
"src/installer/banner.js",
|
|
35
36
|
"src/installer/commands.js",
|
|
36
37
|
"src/installer/fs.js",
|
|
37
38
|
"src/installer/manifest.js",
|
|
@@ -45,6 +46,7 @@
|
|
|
45
46
|
"src/scripts/schemas.mjs",
|
|
46
47
|
"src/skills/**/*.md",
|
|
47
48
|
"src/tools/_env.py",
|
|
49
|
+
"src/tools/extract_pdf.py",
|
|
48
50
|
"src/tools/discover.py",
|
|
49
51
|
"src/tools/init_discovery.py",
|
|
50
52
|
"src/tools/prepare_source.py",
|
|
@@ -79,7 +81,9 @@
|
|
|
79
81
|
"test:update": "node --test src/installer/update-check.test.js",
|
|
80
82
|
"ci:idempotency": "node scripts/ci-idempotency.mjs",
|
|
81
83
|
"ci:package": "node scripts/ci-package.mjs",
|
|
82
|
-
"pack:check": "npm run ci:package"
|
|
84
|
+
"pack:check": "npm run ci:package",
|
|
85
|
+
"dev:install": "node bin/lumina.js install",
|
|
86
|
+
"dev:sandbox": "node scripts/dev-sandbox.mjs"
|
|
83
87
|
},
|
|
84
88
|
"publishConfig": {
|
|
85
89
|
"access": "public"
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module installer/banner
|
|
3
|
+
* @description Render the Lumina Wiki install banner (logo + tagline + intro).
|
|
4
|
+
*
|
|
5
|
+
* Uses raw ANSI escape codes so the yellow logo renders consistently across
|
|
6
|
+
* all terminals that support colour, regardless of TTY detection quirks in
|
|
7
|
+
* subshells. Respects NO_COLOR by emitting plain text.
|
|
8
|
+
*
|
|
9
|
+
* Narrow terminals (<80 cols) get a compact fallback so lines do not wrap.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const NO_COLOR = Boolean(process.env.NO_COLOR);
|
|
13
|
+
|
|
14
|
+
const ANSI = {
|
|
15
|
+
reset: NO_COLOR ? '' : '\x1b[0m',
|
|
16
|
+
yellow: NO_COLOR ? '' : '\x1b[33m',
|
|
17
|
+
bright: NO_COLOR ? '' : '\x1b[93m',
|
|
18
|
+
dim: NO_COLOR ? '' : '\x1b[2m',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const yellow = (s) => `${ANSI.bright}${s}${ANSI.reset}`;
|
|
22
|
+
const dim = (s) => `${ANSI.dim}${s}${ANSI.reset}`;
|
|
23
|
+
|
|
24
|
+
const LOGO_WIDE = [
|
|
25
|
+
'██╗ ██╗ ██╗███╗ ███╗██╗███╗ ██╗ █████╗ ██╗ ██╗██╗██╗ ██╗██╗',
|
|
26
|
+
'██║ ██║ ██║████╗ ████║██║████╗ ██║██╔══██╗ ██║ ██║██║██║ ██╔╝██║',
|
|
27
|
+
'██║ ██║ ██║██╔████╔██║██║██╔██╗ ██║███████║ ██║ █╗ ██║██║█████╔╝ ██║',
|
|
28
|
+
'██║ ██║ ██║██║╚██╔╝██║██║██║╚██╗██║██╔══██║ ██║███╗██║██║██╔═██╗ ██║',
|
|
29
|
+
'███████╗╚██████╔╝██║ ╚═╝ ██║██║██║ ╚████║██║ ██║ ╚███╔███╔╝██║██║ ██╗██║',
|
|
30
|
+
'╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚══╝╚══╝ ╚═╝╚═╝ ╚═╝╚═╝',
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
const LOGO_NARROW = [
|
|
34
|
+
'██╗ ██╗ ██╗███╗ ███╗██╗███╗ ██╗ █████╗ ',
|
|
35
|
+
'██║ ██║ ██║████╗ ████║██║████╗ ██║██╔══██╗',
|
|
36
|
+
'██║ ██║ ██║██╔████╔██║██║██╔██╗ ██║███████║',
|
|
37
|
+
'██║ ██║ ██║██║╚██╔╝██║██║██║╚██╗██║██╔══██║',
|
|
38
|
+
'███████╗╚██████╔╝██║ ╚═╝ ██║██║██║ ╚████║██║ ██║',
|
|
39
|
+
'╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝',
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const SEPARATOR = '━'.repeat(72);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Print the install-time banner to stdout.
|
|
46
|
+
* Safe to call unconditionally; respects NO_COLOR.
|
|
47
|
+
*
|
|
48
|
+
* @returns {Promise<void>}
|
|
49
|
+
*/
|
|
50
|
+
export async function displayBanner() {
|
|
51
|
+
const termWidth = process.stdout.columns || 80;
|
|
52
|
+
const lines = termWidth >= 80 ? LOGO_WIDE : LOGO_NARROW;
|
|
53
|
+
|
|
54
|
+
process.stdout.write('\n');
|
|
55
|
+
for (const line of lines) {
|
|
56
|
+
process.stdout.write(yellow(line) + '\n');
|
|
57
|
+
}
|
|
58
|
+
process.stdout.write('\n');
|
|
59
|
+
process.stdout.write(' ' + yellow('Where Knowledge Starts to Glow') + '\n');
|
|
60
|
+
process.stdout.write(dim(' © Lumina Wiki') + '\n');
|
|
61
|
+
process.stdout.write('\n');
|
|
62
|
+
process.stdout.write(dim(SEPARATOR) + '\n\n');
|
|
63
|
+
|
|
64
|
+
process.stdout.write(
|
|
65
|
+
'A self-maintaining research wiki, scaffolded into your project in one\n' +
|
|
66
|
+
'command. Realises Karpathy\'s LLM-Wiki pattern: the agent compiles\n' +
|
|
67
|
+
'knowledge into a persistent, structured wiki instead of re-deriving it\n' +
|
|
68
|
+
'from raw chunks on every query. Cross-platform, multi-IDE, pack-based.\n\n'
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
process.stdout.write(yellow('🌟 100% free. 100% open source. Always.') + '\n');
|
|
72
|
+
process.stdout.write(' No paywalls. No gated content. Knowledge shared, not sold.\n\n');
|
|
73
|
+
|
|
74
|
+
process.stdout.write(yellow('🌐 CONNECT:') + '\n');
|
|
75
|
+
process.stdout.write(' GitHub: https://github.com/tronghieu/lumina-wiki\n');
|
|
76
|
+
process.stdout.write(' Issues: https://github.com/tronghieu/lumina-wiki/issues\n');
|
|
77
|
+
process.stdout.write(' npm: https://www.npmjs.com/package/lumina-wiki\n\n');
|
|
78
|
+
|
|
79
|
+
process.stdout.write(yellow('⭐ SUPPORT THE PROJECT:') + '\n');
|
|
80
|
+
process.stdout.write(' Star us: https://github.com/tronghieu/lumina-wiki\n\n');
|
|
81
|
+
|
|
82
|
+
process.stdout.write(dim(SEPARATOR) + '\n\n');
|
|
83
|
+
}
|
|
@@ -115,12 +115,12 @@ const LUMINA_DIRS = [
|
|
|
115
115
|
'_lumina/config',
|
|
116
116
|
'_lumina/schema',
|
|
117
117
|
'_lumina/scripts',
|
|
118
|
+
'_lumina/tools',
|
|
118
119
|
'_lumina/_state',
|
|
119
120
|
];
|
|
120
|
-
const RESEARCH_LUMINA_DIRS = ['_lumina/tools'];
|
|
121
121
|
|
|
122
122
|
const VALID_PACKS = new Set(['core', 'research', 'reading']);
|
|
123
|
-
const VALID_IDE_TARGETS = new Set(['claude_code', 'codex', 'cursor', 'gemini_cli', 'generic']);
|
|
123
|
+
const VALID_IDE_TARGETS = new Set(['claude_code', 'codex', 'cursor', 'gemini_cli', 'qwen', 'iflow', 'generic']);
|
|
124
124
|
|
|
125
125
|
// ---------------------------------------------------------------------------
|
|
126
126
|
// install command
|
|
@@ -128,22 +128,24 @@ const VALID_IDE_TARGETS = new Set(['claude_code', 'codex', 'cursor', 'gemini_cli
|
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
130
|
* @param {object} opts
|
|
131
|
-
* @param {string} opts.
|
|
131
|
+
* @param {string} [opts.directory] - Installation directory (BMAD-style canonical flag)
|
|
132
|
+
* @param {string} [opts.cwd] - Backward-compat alias for `directory`
|
|
132
133
|
* @param {boolean} opts.yes - Accept defaults (--yes)
|
|
133
134
|
* @param {boolean} opts.reLink - Force re-compute symlink strategy
|
|
134
135
|
* @param {boolean} opts.noUpdate - Skip update check
|
|
135
136
|
* @param {string|string[]} [opts.packs] - Pack override for non-interactive installs
|
|
136
137
|
* @param {string|string[]} [opts.ideTargets] - IDE target override
|
|
137
|
-
* @param {string} [opts.projectName]
|
|
138
|
+
* @param {string} [opts.projectName] - Hidden escape hatch; default = basename(directory)
|
|
138
139
|
* @param {string} [opts.communicationLang]
|
|
139
140
|
* @param {string} [opts.documentOutputLang]
|
|
140
141
|
*/
|
|
141
142
|
export async function installCommand(opts = {}) {
|
|
142
|
-
const {
|
|
143
|
-
const
|
|
143
|
+
const { yes = false, reLink = false } = opts;
|
|
144
|
+
const initialDir = opts.directory ?? opts.cwd ?? process.cwd();
|
|
145
|
+
let projectRoot = resolve(initialDir);
|
|
144
146
|
const colors = await getColorFns();
|
|
145
147
|
|
|
146
|
-
// 1. Read existing manifest
|
|
148
|
+
// 1. Read existing manifest at the initial path (upgrade detection)
|
|
147
149
|
let existingManifest = null;
|
|
148
150
|
try {
|
|
149
151
|
existingManifest = await readManifest(projectRoot);
|
|
@@ -161,6 +163,10 @@ export async function installCommand(opts = {}) {
|
|
|
161
163
|
answers = await readAnswersFromConfig(projectRoot, existingManifest);
|
|
162
164
|
} else {
|
|
163
165
|
answers = await runInstallPrompts({ acceptDefaults: yes, cwd: projectRoot });
|
|
166
|
+
// Re-resolve projectRoot from the directory the user typed.
|
|
167
|
+
if (answers.directory) {
|
|
168
|
+
projectRoot = resolve(answers.directory);
|
|
169
|
+
}
|
|
164
170
|
}
|
|
165
171
|
answers = applyInstallOverrides(answers, opts);
|
|
166
172
|
|
|
@@ -180,15 +186,13 @@ export async function installCommand(opts = {}) {
|
|
|
180
186
|
...CORE_WIKI_DIRS,
|
|
181
187
|
...CORE_RAW_DIRS,
|
|
182
188
|
...LUMINA_DIRS,
|
|
183
|
-
'.agents/skills
|
|
189
|
+
'.agents/skills',
|
|
184
190
|
];
|
|
185
191
|
if (hasResearch) {
|
|
186
|
-
dirsToCreate.push(...RESEARCH_WIKI_DIRS, ...RESEARCH_RAW_DIRS
|
|
187
|
-
dirsToCreate.push('.agents/skills/packs/research');
|
|
192
|
+
dirsToCreate.push(...RESEARCH_WIKI_DIRS, ...RESEARCH_RAW_DIRS);
|
|
188
193
|
}
|
|
189
194
|
if (hasReading) {
|
|
190
195
|
dirsToCreate.push(...READING_WIKI_DIRS);
|
|
191
|
-
dirsToCreate.push('.agents/skills/packs/reading');
|
|
192
196
|
}
|
|
193
197
|
|
|
194
198
|
for (const dir of dirsToCreate) {
|
|
@@ -222,10 +226,8 @@ export async function installCommand(opts = {}) {
|
|
|
222
226
|
// 9. Copy skills
|
|
223
227
|
const skillRows = await copySkills(projectRoot, packs);
|
|
224
228
|
|
|
225
|
-
// 10. Copy Python tools (research pack)
|
|
226
|
-
|
|
227
|
-
await copyTools(projectRoot);
|
|
228
|
-
}
|
|
229
|
+
// 10. Copy Python tools (core: extract_pdf; research pack: discovery/fetchers)
|
|
230
|
+
await copyTools(projectRoot, { research: hasResearch });
|
|
229
231
|
|
|
230
232
|
// 11. Render schema docs
|
|
231
233
|
await renderSchemaDocs(projectRoot, templateVars);
|
|
@@ -410,7 +412,8 @@ async function readAnswersFromConfig(projectRoot, existingManifest) {
|
|
|
410
412
|
.map(([k]) => k);
|
|
411
413
|
|
|
412
414
|
return {
|
|
413
|
-
|
|
415
|
+
directory: projectRoot,
|
|
416
|
+
projectName: config.project_name || basename(projectRoot),
|
|
414
417
|
researchPurpose: '',
|
|
415
418
|
ideTargets: ideTargets.length ? ideTargets : ['claude_code'],
|
|
416
419
|
packs: packs.length ? packs : ['core'],
|
|
@@ -422,7 +425,8 @@ async function readAnswersFromConfig(projectRoot, existingManifest) {
|
|
|
422
425
|
const ideTargets = existingManifest?.ideTargets ?? ['claude_code'];
|
|
423
426
|
const packs = Object.keys(existingManifest?.packs ?? { core: true });
|
|
424
427
|
return {
|
|
425
|
-
|
|
428
|
+
directory: projectRoot,
|
|
429
|
+
projectName: basename(projectRoot),
|
|
426
430
|
researchPurpose: '',
|
|
427
431
|
ideTargets,
|
|
428
432
|
packs: packs.length ? packs : ['core'],
|
|
@@ -497,6 +501,8 @@ async function renderAndWriteConfig(projectRoot, templateVars, answers) {
|
|
|
497
501
|
codex: answers.ideTargets.includes('codex'),
|
|
498
502
|
cursor: answers.ideTargets.includes('cursor'),
|
|
499
503
|
gemini_cli: answers.ideTargets.includes('gemini_cli'),
|
|
504
|
+
qwen: answers.ideTargets.includes('qwen'),
|
|
505
|
+
iflow: answers.ideTargets.includes('iflow'),
|
|
500
506
|
generic: answers.ideTargets.includes('generic'),
|
|
501
507
|
},
|
|
502
508
|
packs: {
|
|
@@ -627,6 +633,8 @@ function ideTargetFilePath(projectRoot, target) {
|
|
|
627
633
|
case 'codex': return join(projectRoot, 'AGENTS.md');
|
|
628
634
|
case 'gemini_cli': return join(projectRoot, 'GEMINI.md');
|
|
629
635
|
case 'cursor': return join(projectRoot, '.cursor', 'rules', 'lumina.mdc');
|
|
636
|
+
case 'qwen': return join(projectRoot, 'QWEN.md');
|
|
637
|
+
case 'iflow': return join(projectRoot, 'IFLOW.md');
|
|
630
638
|
case 'generic': return null; // No stub needed; README.md is the entry point
|
|
631
639
|
default: return null;
|
|
632
640
|
}
|
|
@@ -640,6 +648,8 @@ function ideTargetStubFiles(ideTargets) {
|
|
|
640
648
|
case 'codex': return 'AGENTS.md';
|
|
641
649
|
case 'gemini_cli': return 'GEMINI.md';
|
|
642
650
|
case 'cursor': return '.cursor/rules/lumina.mdc';
|
|
651
|
+
case 'qwen': return 'QWEN.md';
|
|
652
|
+
case 'iflow': return 'IFLOW.md';
|
|
643
653
|
default: return null;
|
|
644
654
|
}
|
|
645
655
|
})
|
|
@@ -652,11 +662,15 @@ function buildIdeStub(target, vars) {
|
|
|
652
662
|
case 'claude_code':
|
|
653
663
|
return `# Claude Code — Lumina Wiki\n\nYou are the wiki maintainer for **${name}**.\n\nRead \`README.md\` at the project root first — it contains the full schema, page types, link conventions, and skill list for this workspace.\n\nCommunicate with the user in **${vars.communication_language}**. Write wiki pages in **${vars.document_output_language}**.\n`;
|
|
654
664
|
case 'codex':
|
|
655
|
-
return `#
|
|
665
|
+
return `# AGENTS.md — Lumina Wiki\n\nThis file is the entry point for any CLI agent that reads \`AGENTS.md\` (Codex, Amp, Crush, Goose, Auggie, OpenCode, Kimi Code, Mistral Vibe, and other AGENTS.md-compatible tools).\n\nYou are the wiki maintainer for **${name}**.\n\nRead \`README.md\` at the project root first — it contains the full schema, page types, link conventions, and skill list for this workspace.\n\nCommunicate with the user in **${vars.communication_language}**. Write wiki pages in **${vars.document_output_language}**.\n`;
|
|
656
666
|
case 'gemini_cli':
|
|
657
667
|
return `# Gemini CLI — Lumina Wiki\n\nYou are the wiki maintainer for **${name}**.\n\nRead \`README.md\` at the project root first — it contains the full schema, page types, link conventions, and skill list for this workspace.\n\nCommunicate with the user in **${vars.communication_language}**. Write wiki pages in **${vars.document_output_language}**.\n`;
|
|
658
668
|
case 'cursor':
|
|
659
669
|
return `---\ndescription: Lumina Wiki workspace rules for Cursor\nglobs: ["**/*.md"]\nalwaysApply: true\n---\n\n# Cursor — Lumina Wiki\n\nYou are the wiki maintainer for **${name}**.\n\nRead \`README.md\` at the project root first — it contains the full schema, page types, link conventions, and skill list for this workspace.\n\nCommunicate with the user in **${vars.communication_language}**. Write wiki pages in **${vars.document_output_language}**.\n`;
|
|
670
|
+
case 'qwen':
|
|
671
|
+
return `# Qwen Code — Lumina Wiki\n\nYou are the wiki maintainer for **${name}**.\n\nRead \`README.md\` at the project root first — it contains the full schema, page types, link conventions, and skill list for this workspace.\n\nCommunicate with the user in **${vars.communication_language}**. Write wiki pages in **${vars.document_output_language}**.\n`;
|
|
672
|
+
case 'iflow':
|
|
673
|
+
return `# iFlow CLI — Lumina Wiki\n\nYou are the wiki maintainer for **${name}**.\n\nRead \`README.md\` at the project root first — it contains the full schema, page types, link conventions, and skill list for this workspace.\n\nCommunicate with the user in **${vars.communication_language}**. Write wiki pages in **${vars.document_output_language}**.\n`;
|
|
660
674
|
default:
|
|
661
675
|
return null;
|
|
662
676
|
}
|
|
@@ -681,9 +695,9 @@ async function copySkills(projectRoot, packs) {
|
|
|
681
695
|
const skillDefs = getSkillDefs(packs);
|
|
682
696
|
|
|
683
697
|
for (const skill of skillDefs) {
|
|
684
|
-
//
|
|
685
|
-
const srcDir = join(SKILLS_DIR, ...skill.
|
|
686
|
-
const destDir = join(projectRoot, '.agents', 'skills',
|
|
698
|
+
// srcPackPath uses forward slashes — join handles OS differences
|
|
699
|
+
const srcDir = join(SKILLS_DIR, ...skill.srcPackPath.split('/'), skill.name);
|
|
700
|
+
const destDir = join(projectRoot, '.agents', 'skills', skill.canonicalId);
|
|
687
701
|
|
|
688
702
|
await access(join(srcDir, 'SKILL.md'), fsConstants.F_OK);
|
|
689
703
|
await rm(destDir, { recursive: true, force: true });
|
|
@@ -695,7 +709,7 @@ async function copySkills(projectRoot, packs) {
|
|
|
695
709
|
display_name: skill.displayName,
|
|
696
710
|
pack: skill.pack,
|
|
697
711
|
source: 'built-in',
|
|
698
|
-
relative_path:
|
|
712
|
+
relative_path: `.agents/skills/${skill.canonicalId}`,
|
|
699
713
|
target_link_path: join('.claude', 'skills', skill.canonicalId),
|
|
700
714
|
version: PKG.version,
|
|
701
715
|
});
|
|
@@ -729,43 +743,45 @@ function getSkillDefs(packs) {
|
|
|
729
743
|
{ name: 'reset', canonicalId: 'lumi-reset', displayName: '/lumi-reset' },
|
|
730
744
|
];
|
|
731
745
|
for (const s of coreSkills) {
|
|
732
|
-
defs.push({ ...s, pack: 'core',
|
|
746
|
+
defs.push({ ...s, pack: 'core', srcPackPath: 'core' });
|
|
733
747
|
}
|
|
734
748
|
}
|
|
735
749
|
|
|
736
750
|
if (packs.includes('research')) {
|
|
737
751
|
const researchSkills = [
|
|
738
|
-
{ name: 'discover', canonicalId: 'lumi-discover', displayName: '/lumi-discover' },
|
|
739
|
-
{ name: 'survey', canonicalId: 'lumi-survey', displayName: '/lumi-survey' },
|
|
740
|
-
{ name: 'prefill', canonicalId: 'lumi-prefill', displayName: '/lumi-prefill' },
|
|
741
|
-
{ name: 'setup', canonicalId: 'lumi-setup', displayName: '/lumi-setup' },
|
|
752
|
+
{ name: 'discover', canonicalId: 'lumi-research-discover', displayName: '/lumi-research-discover' },
|
|
753
|
+
{ name: 'survey', canonicalId: 'lumi-research-survey', displayName: '/lumi-research-survey' },
|
|
754
|
+
{ name: 'prefill', canonicalId: 'lumi-research-prefill', displayName: '/lumi-research-prefill' },
|
|
755
|
+
{ name: 'setup', canonicalId: 'lumi-research-setup', displayName: '/lumi-research-setup' },
|
|
742
756
|
];
|
|
743
757
|
for (const s of researchSkills) {
|
|
744
|
-
defs.push({ ...s, pack: 'research',
|
|
758
|
+
defs.push({ ...s, pack: 'research', srcPackPath: 'packs/research' });
|
|
745
759
|
}
|
|
746
760
|
}
|
|
747
761
|
|
|
748
762
|
if (packs.includes('reading')) {
|
|
749
763
|
const readingSkills = [
|
|
750
|
-
{ name: 'chapter-ingest', canonicalId: 'lumi-chapter-ingest', displayName: '/lumi-chapter-ingest' },
|
|
751
|
-
{ name: 'character-track', canonicalId: 'lumi-character-track', displayName: '/lumi-character-track' },
|
|
752
|
-
{ name: 'theme-map', canonicalId: 'lumi-theme-map', displayName: '/lumi-theme-map' },
|
|
753
|
-
{ name: 'plot-recap', canonicalId: 'lumi-plot-recap', displayName: '/lumi-plot-recap' },
|
|
764
|
+
{ name: 'chapter-ingest', canonicalId: 'lumi-reading-chapter-ingest', displayName: '/lumi-reading-chapter-ingest' },
|
|
765
|
+
{ name: 'character-track', canonicalId: 'lumi-reading-character-track', displayName: '/lumi-reading-character-track' },
|
|
766
|
+
{ name: 'theme-map', canonicalId: 'lumi-reading-theme-map', displayName: '/lumi-reading-theme-map' },
|
|
767
|
+
{ name: 'plot-recap', canonicalId: 'lumi-reading-plot-recap', displayName: '/lumi-reading-plot-recap' },
|
|
754
768
|
];
|
|
755
769
|
for (const s of readingSkills) {
|
|
756
|
-
defs.push({ ...s, pack: 'reading',
|
|
770
|
+
defs.push({ ...s, pack: 'reading', srcPackPath: 'packs/reading' });
|
|
757
771
|
}
|
|
758
772
|
}
|
|
759
773
|
|
|
760
774
|
return defs;
|
|
761
775
|
}
|
|
762
776
|
|
|
763
|
-
async function copyTools(projectRoot) {
|
|
777
|
+
async function copyTools(projectRoot, { research }) {
|
|
764
778
|
const destDir = join(projectRoot, '_lumina', 'tools');
|
|
765
|
-
const
|
|
779
|
+
const coreTools = ['extract_pdf.py'];
|
|
780
|
+
const researchTools = [
|
|
766
781
|
'_env.py', 'discover.py', 'init_discovery.py', 'prepare_source.py',
|
|
767
782
|
'fetch_arxiv.py', 'fetch_wikipedia.py', 'fetch_s2.py', 'fetch_deepxiv.py',
|
|
768
783
|
];
|
|
784
|
+
const toolFiles = research ? [...coreTools, ...researchTools] : coreTools;
|
|
769
785
|
for (const file of toolFiles) {
|
|
770
786
|
const src = join(TOOLS_DIR, file);
|
|
771
787
|
const dest = join(destDir, file);
|
|
@@ -775,6 +791,11 @@ async function copyTools(projectRoot) {
|
|
|
775
791
|
// Tool not yet authored; skip
|
|
776
792
|
}
|
|
777
793
|
}
|
|
794
|
+
try {
|
|
795
|
+
await copyFile(join(TOOLS_DIR, 'requirements.txt'), join(destDir, 'requirements.txt'));
|
|
796
|
+
} catch (_) {
|
|
797
|
+
// requirements.txt missing in dev; skip
|
|
798
|
+
}
|
|
778
799
|
}
|
|
779
800
|
|
|
780
801
|
async function renderSchemaDocs(projectRoot, templateVars) {
|
|
@@ -880,6 +901,8 @@ async function buildFilesManifest(projectRoot, packs, pkgVersion) {
|
|
|
880
901
|
'CLAUDE.md',
|
|
881
902
|
'AGENTS.md',
|
|
882
903
|
'GEMINI.md',
|
|
904
|
+
'QWEN.md',
|
|
905
|
+
'IFLOW.md',
|
|
883
906
|
'.cursor/rules/lumina.mdc',
|
|
884
907
|
];
|
|
885
908
|
|