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 CHANGED
@@ -4,7 +4,9 @@
4
4
 
5
5
  # Lumina-Wiki
6
6
 
7
- > **The LLM-maintained Knowledge Artifact for Technical Research.**
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. Contributing & License
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. Đóng góp & Giấy phép
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
- * --cwd <path> operate against a different project root
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
- --cwd <path> project root (defaults to current directory)
90
- --yes, -y accept all defaults; non-interactive (CI use)
91
- --no-update skip npm registry version check
92
- --re-link recompute symlink/junction/copy strategy from platform
93
- --packs <list> install packs: core,research,reading
94
- --ide-targets <list> target IDEs: claude_code,codex,cursor,gemini_cli,generic
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 --cwd /path/to/project
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('--cwd <path>', 'project root directory', process.cwd())
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('--cwd <path>', 'project root directory')
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
- .option('--project-name <name>', 'project name for non-interactive install')
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 mergedCwd = cmdOpts.cwd ?? globalOpts.cwd ?? process.cwd();
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
- cwd: resolve(mergedCwd),
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('--cwd <path>', 'project root directory')
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 mergedCwd = cmdOpts.cwd ?? globalOpts.cwd ?? process.cwd();
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(mergedCwd),
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.1.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.cwd - Project root
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 { cwd = process.cwd(), yes = false, reLink = false } = opts;
143
- const projectRoot = resolve(cwd);
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/core',
189
+ '.agents/skills',
184
190
  ];
185
191
  if (hasResearch) {
186
- dirsToCreate.push(...RESEARCH_WIKI_DIRS, ...RESEARCH_RAW_DIRS, ...RESEARCH_LUMINA_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
- if (hasResearch) {
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
- projectName: config.project_name || 'My Wiki',
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
- projectName: 'My Wiki',
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 `# Codex — 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`;
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
- // packPath uses forward slashes — join handles OS differences
685
- const srcDir = join(SKILLS_DIR, ...skill.packPath.split('/'), skill.name);
686
- const destDir = join(projectRoot, '.agents', 'skills', ...skill.packPath.split('/'), skill.name);
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: ['.agents', 'skills', skill.packPath, skill.name].join('/'),
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', packPath: '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', packPath: 'packs/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', packPath: 'packs/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 toolFiles = [
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