diff --git a/.gitignore b/.gitignore index a459041..e977020 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ venv/ *.log logs/ .omc/ + +# ─── ResearchStudio installer output (skills links, runtime settings) ─── +.claude/ +.codex/ diff --git a/README.md b/README.md index 91d1fa8..2bcc4b6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ [![Status: Active](https://img.shields.io/badge/Status-Active-brightgreen)](https://aka.ms/ResearchStudio) ResearchStudio is a collection of skills covering the **entire research lifecycle** — from an under-specified research direction to a published paper. -The skills run on [Claude Code](https://docs.claude.com/en/docs/claude-code/overview) and [Codex](https://developers.openai.com/codex). +The skills run on [Claude Code](https://docs.claude.com/en/docs/claude-code/overview), [Codex](https://developers.openai.com/codex), and [QwenPaw](https://github.com/agentscope-ai/QwenPaw). --- diff --git a/ResearchStudio-Idea/README.md b/ResearchStudio-Idea/README.md index be35636..02ed56a 100644 --- a/ResearchStudio-Idea/README.md +++ b/ResearchStudio-Idea/README.md @@ -89,7 +89,7 @@ Full protocol, IdeaSpark vs the baselines, and the scoring skill are in [`evalua ## Acknowledgements -- [Claude Code](https://docs.claude.com/en/docs/claude-code/overview) and [Codex](https://developers.openai.com/codex) — the agent runtime that drives every skill. +- [Claude Code](https://docs.claude.com/en/docs/claude-code/overview), [Codex](https://developers.openai.com/codex), and [QwenPaw](https://github.com/agentscope-ai/QwenPaw) — the agent runtimes that drive every skill. - [arXiv](https://arxiv.org/), [OpenAlex](https://openalex.org/), [Semantic Scholar](https://www.semanticscholar.org/product/api), [OpenReview](https://openreview.net/) — the Phase 0 / 3.1 literature connectors. - [PyMuPDF](https://pymupdf.readthedocs.io/) + [BeautifulSoup](https://www.crummy.com/software/BeautifulSoup/) — full-text fetch and parsing. - [tectonic](https://tectonic-typesetting.github.io/) / XeLaTeX — optional PDF compilation of the idea cards. diff --git a/ResearchStudio-Reel/README.md b/ResearchStudio-Reel/README.md index 690a4f4..e72ee6c 100644 --- a/ResearchStudio-Reel/README.md +++ b/ResearchStudio-Reel/README.md @@ -36,7 +36,7 @@ | **[Paper2Blog](skills/paper2blog/README.md)** | paper PDF or assets | two blogs (DOCX) | publicity push after acceptance — bilingual outreach in one pass | | **[Paper2Reel](skills/paper2reel/README.md)** | poster + deck artifacts | an interactive reel viewer (HTML) | one scrollable view that aligns the poster with slide / video frames | -Each skill is shipped as a [Claude Code](https://docs.claude.com/en/docs/claude-code/overview) and [Codex](https://developers.openai.com/codex) **skill**. +Each skill is shipped as a [Claude Code](https://docs.claude.com/en/docs/claude-code/overview) / [Codex](https://developers.openai.com/codex) / [QwenPaw](https://github.com/agentscope-ai/QwenPaw) **skill**. --- @@ -210,7 +210,7 @@ For the full bootstrap behavior, alignment map, section-modal UI contract, brows ## Acknowledgements -- [Claude Code](https://docs.claude.com/en/docs/claude-code/overview) and [Codex](https://developers.openai.com/codex) — the agent runtime that drives every skill. +- [Claude Code](https://docs.claude.com/en/docs/claude-code/overview), [Codex](https://developers.openai.com/codex), and [QwenPaw](https://github.com/agentscope-ai/QwenPaw) — the agent runtimes that drive every skill. - [PyMuPDF](https://pymupdf.readthedocs.io/), [Poppler](https://poppler.freedesktop.org/), [Pillow](https://python-pillow.org/) — PDF + image processing. - [Playwright](https://playwright.dev/) + Chromium — HTML → PDF / PNG rendering for posters. - [LibreOffice](https://www.libreoffice.org/) + [FFmpeg](https://ffmpeg.org/) — slide rasterization and video muxing. diff --git a/bin/install.mjs b/bin/install.mjs index c909d73..35aec6f 100755 --- a/bin/install.mjs +++ b/bin/install.mjs @@ -15,7 +15,7 @@ * up from each skill dir and that is the nearest common ancestor they check. * * Non-interactive (--yes or no TTY) takes env answers: - * RS_PLUGINS=idea,reel RS_SCOPE=global|project RS_AGENTS=claude,codex + * RS_PLUGINS=idea,reel RS_SCOPE=global|project RS_AGENTS=claude,codex,qwenpaw * RS_PIP=0|1 + the key env vars. */ import fs from 'node:fs'; @@ -25,9 +25,45 @@ import { fileURLToPath } from 'node:url'; import { execFileSync } from 'node:child_process'; const PKG_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); -const AGENTS = { claude: '.claude', codex: '.codex' }; const PYTHON = process.env.PYTHON || (process.platform === 'win32' ? 'python' : 'python3'); +const expandHome = (p) => (p.startsWith('~') ? path.join(os.homedir(), p.slice(1)) : p); + +// Agent registry — every runtime that can host ResearchStudio skills. Adding +// support for a new agent host is one entry here. `skillsDir(base)` returns +// where SKILL.md folders go for a scope base (home dir for global, cwd for +// project). `pool: true` marks agents without a project-scoped skills dir: +// they install into one shared location regardless of scope. +const AGENTS = { + claude: { label: 'Claude Code', skillsDir: (base) => path.join(base, '.claude', 'skills') }, + codex: { label: 'Codex CLI', skillsDir: (base) => path.join(base, '.codex', 'skills') }, + qwenpaw: { label: 'QwenPaw', skillsDir: () => qwenpawDirs().pool, pool: true }, +}; + +// QwenPaw keeps one shared skill pool under its working dir; pool entries are +// then broadcast into per-agent workspaces from the Console. The working dir +// resolution mirrors QwenPaw's constant.py: QWENPAW_WORKING_DIR env var first, +// then the legacy ~/.copaw layout, then ~/.qwenpaw. +function qwenpawDirs() { + const home = process.env.QWENPAW_WORKING_DIR + ? path.resolve(expandHome(process.env.QWENPAW_WORKING_DIR)) + : [path.join(os.homedir(), '.copaw'), path.join(os.homedir(), '.qwenpaw')] + .find((d) => fs.existsSync(d)) || path.join(os.homedir(), '.qwenpaw'); + return { home, pool: path.join(home, 'skill_pool') }; +} + +// QwenPaw picks up manually placed pool skills on its next manifest reconcile. +// If its CLI is on PATH, nudge one now so the skills show up immediately; +// otherwise the next Console/CLI skill operation reconciles the manifest. +function qwenpawReconcile() { + try { + execFileSync('qwenpaw', ['skills', 'list', '--pool'], { stdio: 'ignore', timeout: 60_000 }); + return true; + } catch { + return false; + } +} + // Preserve an explicit editor choice while giving installer subprocesses a // predictable fallback on machines where EDITOR is unset or empty. process.env.EDITOR ||= 'vim'; @@ -153,33 +189,34 @@ function installSkills(skillsDir, srcDir, names) { // pptx2video. Fetch both skills from their upstream repos via `npx skills add`, // installing into skillsDir. Returns the names that were added. function fetchDependencySkills(skillsDir) { - fs.mkdirSync(skillsDir, { recursive: true }); const DEPS = [ { repo: 'hugohe3/ppt-master', name: 'ppt-master' }, { repo: 'ai-nuts/pptx2video', name: 'pptx2video' }, ]; const added = []; + // The skills CLI refuses non-interactive runs without --agent, and the agent + // choice only decides where content lands. Fetch into a throwaway dir, then + // copy the skill content into skillsDir. Both CLI layouts are accepted: the + // current direct .claude/skills/ and the legacy .agents/skills/. + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'rs-deps-')); for (const d of DEPS) { try { - execFileSync('npx', ['-y', 'skills', 'add', d.repo, '--skill', d.name], - { cwd: skillsDir, stdio: 'inherit' }); - // `skills add` drops content under /.agents/skills/ - // rather than the top level where the Reel/Idea skills live. Move it up - // to the top level so the agent's top-level scan finds it. - const src = path.join(skillsDir, '.agents', 'skills', d.name); - const dst = path.join(skillsDir, d.name); - if (fs.existsSync(src)) { - fs.rmSync(dst, { recursive: true, force: true }); - fs.renameSync(src, dst); - } + execFileSync('npx', ['-y', 'skills', 'add', d.repo, '--skill', d.name, '-y', '--agent', 'claude-code'], + { cwd: tmp, stdio: 'inherit' }); + const src = [ + path.join(tmp, '.claude', 'skills', d.name), + path.join(tmp, '.agents', 'skills', d.name), + ].find((c) => fs.existsSync(c)); + if (!src) throw new Error(`no skill content found for ${d.name}`); + fs.rmSync(path.join(skillsDir, d.name), { recursive: true, force: true }); + fs.cpSync(src, path.join(skillsDir, d.name), { recursive: true }); added.push(d.name); } catch { say(` ${C.y}! failed to fetch ${d.name} via npx skills add — install it manually:${C.r}`); - say(` ${C.c}npx skills add ${d.repo} --skill ${d.name}${C.r}`); + say(` ${C.c}npx skills add ${d.repo} --skill ${d.name} -y --agent claude-code${C.r}`); } } - // Remove the now-empty .agents scaffold left by `skills add`. - fs.rmSync(path.join(skillsDir, '.agents'), { recursive: true, force: true }); + fs.rmSync(tmp, { recursive: true, force: true }); return added; } @@ -255,13 +292,24 @@ async function main() { const a = (await ask('Install globally (all projects) or into this project? g/p', 'g')).toLowerCase(); scope = a.startsWith('p') ? 'project' : 'global'; } + const base = scope === 'global' ? os.homedir() : process.cwd(); + const agentList = Object.entries(AGENTS); let agents = (process.env.RS_AGENTS || 'claude').split(',').map((s) => s.trim()).filter(Boolean); if (!NONINTERACTIVE) { - agents = ['claude']; - if (await askYN('Also install for Codex (in addition to Claude Code)?', false)) agents.push('codex'); + say(`\n${C.b}Agents${C.r}`); + agentList.forEach(([key, a], i) => say( + ` ${i + 1}) ${a.label} ${C.d}(→ ${a.skillsDir(base)})${C.r}`)); + const ans = (await ask('Install for which agents? numbers e.g. 1 or 1,3', '1')).toLowerCase(); + const picked = ans === 'all' ? agentList.map(([k]) => k) + : ans.split(/[\s,]+/).map((n) => agentList[parseInt(n, 10) - 1]?.[0]).filter(Boolean); + agents = picked.length ? picked : ['claude']; } - const base = scope === 'global' ? os.homedir() : process.cwd(); - const targets = agents.filter((a) => AGENTS[a]).map((a) => path.join(base, AGENTS[a], 'skills')); + agents = agents.filter((a) => { + if (AGENTS[a]) return true; + say(`${C.y} skipping unknown agent '${a}' — known: ${Object.keys(AGENTS).join(', ')}${C.r}`); + return false; + }); + if (!agents.length) { say(`${C.y}No agent selected.${C.r}`); process.exit(1); } // union of keys from the selected plugins' templates (dedup by name) const keySpec = []; const seen = new Set(); @@ -283,7 +331,12 @@ async function main() { // install say(''); const reelSelected = selected.some((p) => p.key === 'reel'); - for (const dir of targets) { + for (const key of agents) { + const agent = AGENTS[key]; + const dir = agent.skillsDir(base); + if (agent.pool && scope === 'project') { + say(`${C.d} ${agent.label} has no project-scoped skills dir — installing into its shared pool instead${C.r}`); + } for (const p of selected) installSkills(dir, p.src, p.skills); const total = selected.reduce((n, p) => n + p.skills.length, 0); say(`${C.g}✓${C.r} installed ${total} skills (${selected.map((p) => p.key).join(', ')}) → ${C.c}${dir}${C.r}`); @@ -293,8 +346,19 @@ async function main() { const deps = fetchDependencySkills(dir); if (deps.length) say(` ${C.g}✓${C.r} provisioned ${deps.length} Paper2Video dep skill(s) (${deps.join(', ')}) → ${C.c}${dir}${C.r}`); } - const w = writeEnv(dir, values); + // QwenPaw's runtime loads /.env into the process env its + // skills run in, and the skills' own .env walker reaches that file from + // the pool — so one merged .env there covers pool skills wherever they run. + // Other agents keep the skills-dir .env (the nearest common ancestor their + // loaders check). + const envDir = key === 'qwenpaw' ? qwenpawDirs().home : dir; + const w = writeEnv(envDir, values); if (w) say(` ${C.g}✓${C.r} wrote ${Object.keys(values).length} key(s) → ${w.envPath}${w.backedUp ? ` ${C.d}(backup: .env.bak)${C.r}` : ''}`); + if (key === 'qwenpaw') { + const ok = qwenpawReconcile(); + say(` ${C.d}pool skills are shared — load them into a workspace from QwenPaw Console → Workspace → Skills` + + `${ok ? '' : ' (they appear after the next manifest reconcile)'}`); + } } // python deps for whichever selected plugins declare them diff --git a/install.sh b/install.sh index c817aa7..bb8ae94 100755 --- a/install.sh +++ b/install.sh @@ -4,24 +4,27 @@ # # Lets you pick: # • which skill bundles to install — Idea, Reel, or both -# • which agent runtimes to link into — Claude Code, Codex, or both +# • which agent runtimes to link into — Claude Code, Codex, and/or QwenPaw # # Auto-detects OS + Python, installs each bundle's native and Python deps, # and symlinks the selected skills into /.claude/skills/ and/or -# /.codex/skills/ (both git-ignored). Idempotent — safe to re-run. +# /.codex/skills/ (both git-ignored). QwenPaw gets real copies in +# its shared skill pool instead of links — its scanner rejects symlinked skill +# dirs. Idempotent — safe to re-run. # # Usage: # bash install.sh # interactive prompts # bash install.sh --yes # non-interactive (idea+reel, claude only) # bash install.sh --idea --claude # explicit selection # bash install.sh --reel --codex --with-pdf # explicit selection + LaTeX -# bash install.sh --idea --reel --claude --codex +# bash install.sh --idea --reel --claude --codex --qwenpaw # # Flags (anything you don't pass falls back to a prompt, or the --yes defaults): # --idea / --no-idea include / skip Idea skills # --reel / --no-reel include / skip Reel skills # --claude / --no-claude link into /.claude/skills/ # --codex / --no-codex link into /.codex/skills/ +# --qwenpaw / --no-qwenpaw copy into the QwenPaw shared skill pool # --with-pdf also install a LaTeX engine (Idea PDF idea cards) # --yes, -y non-interactive; default selection = idea+reel for Claude # --help, -h this help @@ -31,6 +34,8 @@ # EDITOR=code use a specific editor (defaults to vim) # CLAUDE_SKILLS_DIR=/custom/path use a non-default Claude skills dir # CODEX_SKILLS_DIR=/custom/path use a non-default Codex skills dir +# QWENPAW_WORKING_DIR=/custom/path use a non-default QwenPaw working dir +# QWENPAW_POOL_DIR=/custom/path use a non-default QwenPaw skill pool # set -euo pipefail @@ -45,6 +50,17 @@ REEL_REPO="${REPO_ROOT}/ResearchStudio-Reel" CLAUDE_SKILLS_DIR="${CLAUDE_SKILLS_DIR:-$REPO_ROOT/.claude/skills}" CODEX_SKILLS_DIR="${CODEX_SKILLS_DIR:-$REPO_ROOT/.codex/skills}" +# QwenPaw working-dir resolution mirrors its own constant.py: explicit +# QWENPAW_WORKING_DIR first, then the legacy ~/.copaw layout, then ~/.qwenpaw. +if [ -n "${QWENPAW_WORKING_DIR:-}" ]; then + QWENPAW_HOME="${QWENPAW_WORKING_DIR}" +elif [ -d "$HOME/.copaw" ]; then + QWENPAW_HOME="$HOME/.copaw" +else + QWENPAW_HOME="$HOME/.qwenpaw" +fi +QWENPAW_POOL_DIR="${QWENPAW_POOL_DIR:-$QWENPAW_HOME/skill_pool}" + # --------------------------------------------------------------------------- # pretty-print helpers # --------------------------------------------------------------------------- @@ -57,7 +73,7 @@ die() { printf '\033[1;31m[error]\033[0m %s\n' "$*" >&2; exit 1; } # CLI parsing # --------------------------------------------------------------------------- USE_IDEA=""; USE_REEL="" -USE_CLAUDE=""; USE_CODEX="" +USE_CLAUDE=""; USE_CODEX=""; USE_QWENPAW="" WITH_PDF=0 NONINTERACTIVE=0 @@ -75,6 +91,8 @@ while [ $# -gt 0 ]; do --no-claude) USE_CLAUDE=0 ;; --codex) USE_CODEX=1 ;; --no-codex) USE_CODEX=0 ;; + --qwenpaw) USE_QWENPAW=1 ;; + --no-qwenpaw) USE_QWENPAW=0 ;; --with-pdf) WITH_PDF=1 ;; --yes|-y) NONINTERACTIVE=1 ;; --help|-h) print_help; exit 0 ;; @@ -117,14 +135,15 @@ fi # Runtime selection if [ -z "$USE_CLAUDE" ]; then ask_yn "Link skills for Claude Code (-> $CLAUDE_SKILLS_DIR)?" Y && USE_CLAUDE=1 || USE_CLAUDE=0; fi if [ -z "$USE_CODEX" ]; then ask_yn "Link skills for Codex (-> $CODEX_SKILLS_DIR)?" N && USE_CODEX=1 || USE_CODEX=0; fi +if [ -z "$USE_QWENPAW" ]; then ask_yn "Copy skills into the QwenPaw pool (-> $QWENPAW_POOL_DIR)?" N && USE_QWENPAW=1 || USE_QWENPAW=0; fi -if [ "$USE_CLAUDE" = 0 ] && [ "$USE_CODEX" = 0 ]; then - die "No runtime selected — pick --claude and/or --codex." +if [ "$USE_CLAUDE" = 0 ] && [ "$USE_CODEX" = 0 ] && [ "$USE_QWENPAW" = 0 ]; then + die "No runtime selected — pick --claude, --codex and/or --qwenpaw." fi echo echo " bundles: $([ "$USE_IDEA" = 1 ] && echo -n "Idea ")$([ "$USE_REEL" = 1 ] && echo -n "Reel")" -echo " runtimes: $([ "$USE_CLAUDE" = 1 ] && echo -n "Claude($CLAUDE_SKILLS_DIR) ")$([ "$USE_CODEX" = 1 ] && echo -n "Codex($CODEX_SKILLS_DIR)")" +echo " runtimes: $([ "$USE_CLAUDE" = 1 ] && echo -n "Claude($CLAUDE_SKILLS_DIR) ")$([ "$USE_CODEX" = 1 ] && echo -n "Codex($CODEX_SKILLS_DIR) ")$([ "$USE_QWENPAW" = 1 ] && echo -n "QwenPaw($QWENPAW_POOL_DIR)")" echo " optional pdf: $([ "$WITH_PDF" = 1 ] && echo yes || echo no)" # Sanity: required bundle repos must exist @@ -207,6 +226,18 @@ pip_install() { "$PY" -m pip install --user --upgrade "$@" } +# copy_skill_qwenpaw — real copy into the QwenPaw shared +# pool. QwenPaw's scanner resolves skill dirs and rejects anything outside the +# pool root, so symlinked skills would be dropped — pool entries must be copies. +copy_skill_qwenpaw() { + local src="$1" name="$2" + [ -d "$src" ] || { warn "missing $src — skipped"; return; } + mkdir -p "$QWENPAW_POOL_DIR" + rm -rf "$QWENPAW_POOL_DIR/$name" + cp -R "$src" "$QWENPAW_POOL_DIR/$name" + printf ' • qwenpaw %s → %s\n' "$name" "$QWENPAW_POOL_DIR/$name" +} + # link_skill — into every selected runtime's skills dir. link_skill() { local src="$1" name="$2" @@ -223,6 +254,9 @@ link_skill() { rm -rf "$dst"; ln -s "$src" "$dst" printf ' • codex %s → %s\n' "$name" "$src" fi + if [ "$USE_QWENPAW" = 1 ]; then + copy_skill_qwenpaw "$src" "$name" + fi } # seed_config — write the embedded settings.json into @@ -308,6 +342,7 @@ if [ "$USE_IDEA" = 1 ]; then for rt_dir in "$CLAUDE_SKILLS_DIR" "$CODEX_SKILLS_DIR"; do rm -rf "$rt_dir/idea_spark" 2>/dev/null || true done + rm -rf "$QWENPAW_POOL_DIR/idea_spark" 2>/dev/null || true link_skill "$IDEA_REPO/skills/idea_spark" idea-spark link_skill "$IDEA_REPO/skills/paper_search" paper-search link_skill "$IDEA_REPO/skills/scoop_check" scoop-check @@ -402,35 +437,39 @@ if [ "$USE_REEL" = 1 ]; then # Paper2Video delegates deck authoring to ppt-master and rendering/QA to # pptx2video. Fetch both skills from their upstream repos via `npx skills add`. + # The skills CLI refuses non-interactive runs without --agent, and the agent + # choice only decides where content lands, so fetch into a throwaway dir and + # copy from there into every selected runtime. if command -v npx >/dev/null 2>&1; then log "Fetching Paper2Video dependency skills via npx skills add (ppt-master, pptx2video)" + dep_tmp="$(mktemp -d)" + npx -y skills add hugohe3/ppt-master --skill ppt-master -y --agent claude-code \ + || warn "npx skills add failed for ppt-master — install manually; paper2video will not work until then" + npx -y skills add ai-nuts/pptx2video --skill pptx2video -y --agent claude-code \ + || warn "npx skills add failed for pptx2video — install manually; paper2video will not work until then" + for target_dir in \ "$([ "$USE_CLAUDE" = 1 ] && echo "$CLAUDE_SKILLS_DIR")" \ - "$([ "$USE_CODEX" = 1 ] && echo "$CODEX_SKILLS_DIR")"; do + "$([ "$USE_CODEX" = 1 ] && echo "$CODEX_SKILLS_DIR")" \ + "$([ "$USE_QWENPAW" = 1 ] && echo "$QWENPAW_POOL_DIR")"; do [ -n "$target_dir" ] || continue mkdir -p "$target_dir" - ( cd "$target_dir" \ - && npx -y skills add hugohe3/ppt-master --skill ppt-master \ - && npx -y skills add ai-nuts/pptx2video --skill pptx2video ) \ - || warn "npx skills add failed for ppt-master/pptx2video — install them manually; paper2video will not work until then" - - # `skills add` drops the skill content under /.agents/skills/ - # (its own project layout) rather than the skills-dir top level where the - # Reel/Idea skills live. Move each up to the top level so the agent's - # top-level scan finds them next to the other skills. for dep_name in ppt-master pptx2video; do - dep_src="$target_dir/.agents/skills/$dep_name" + # Accept both CLI layouts: the current direct .claude/skills/ + # and the legacy .agents/skills/ scaffold. + dep_src="$dep_tmp/.claude/skills/$dep_name" + [ -d "$dep_src" ] || dep_src="$dep_tmp/.agents/skills/$dep_name" if [ -d "$dep_src" ]; then rm -rf "$target_dir/$dep_name" - mv "$dep_src" "$target_dir/$dep_name" + cp -R "$dep_src" "$target_dir/$dep_name" fi done - rmdir "$target_dir/.agents/skills" "$target_dir/.agents" 2>/dev/null || true done + rm -rf "$dep_tmp" else warn "npx not found — install ppt-master + pptx2video manually:" - warn " npx skills add hugohe3/ppt-master --skill ppt-master" - warn " npx skills add ai-nuts/pptx2video --skill pptx2video" + warn " npx skills add hugohe3/ppt-master --skill ppt-master -y --agent claude-code" + warn " npx skills add ai-nuts/pptx2video --skill pptx2video -y --agent claude-code" fi echo fi @@ -447,6 +486,25 @@ if [ "$USE_CODEX" = 1 ]; then seed_config codex fi +# QwenPaw: seed the user-level .env its runtime loads (keys reach skills run +# inside QwenPaw), and nudge a pool manifest reconcile so the new skills show +# up right away. Both best-effort — re-runs never overwrite an existing .env. +if [ "$USE_QWENPAW" = 1 ]; then + bold "Seeding QwenPaw ($QWENPAW_HOME)" + if [ -f "$QWENPAW_HOME/.env" ]; then + echo " $QWENPAW_HOME/.env already exists — left untouched" + elif [ -f "$IDEA_REPO/.env.template" ]; then + mkdir -p "$QWENPAW_HOME" + cp "$IDEA_REPO/.env.template" "$QWENPAW_HOME/.env" + echo " created $QWENPAW_HOME/.env from .env.template — fill in the connector keys" + fi + if command -v qwenpaw >/dev/null 2>&1; then + qwenpaw skills list --pool >/dev/null 2>&1 \ + && echo " skill pool reconciled — new skills are visible" \ + || echo " (qwenpaw CLI reconcile failed — skills appear after the next pool refresh)" + fi +fi + bold "Done." if [ "$USE_CLAUDE" = 1 ]; then echo " Claude Code:" @@ -458,6 +516,13 @@ if [ "$USE_CODEX" = 1 ]; then echo " skills linked into $CODEX_SKILLS_DIR" echo " settings: $REPO_ROOT/.codex/settings.json" fi +if [ "$USE_QWENPAW" = 1 ]; then + echo " QwenPaw:" + echo " skills copied into the shared pool: $QWENPAW_POOL_DIR" + echo " load them into a workspace: Console → Workspace → Skills → \"Load from Skill Pool\"" + echo " (or copy a skill into $QWENPAW_HOME/workspaces//skills/ and run:" + echo " qwenpaw skills enable --agent-id )" +fi if [ "$USE_REEL" = 1 ]; then echo echo " Reel notes:"