(spec: string, originalSource: string, opts: { force?: boolean })
| 94 | } |
| 95 | |
| 96 | async function installFromNpm(spec: string, originalSource: string, opts: { force?: boolean }): Promise<InstallResult> { |
| 97 | const tmp = await mkdtempIn(os.tmpdir(), 'qodex-skill-npm-'); |
| 98 | // `npm pack` writes the tgz to cwd, so cd into tmp first |
| 99 | const packResult = spawnSync('npm', ['pack', spec], { cwd: tmp, encoding: 'utf-8' }); |
| 100 | if (packResult.status !== 0) { |
| 101 | throw new Error(`npm pack ${spec} failed: ${packResult.stderr || packResult.stdout || 'unknown error'}`); |
| 102 | } |
| 103 | const tgzName = packResult.stdout.trim().split('\n').filter(Boolean).pop(); |
| 104 | if (!tgzName) throw new Error(`npm pack produced no tarball for ${spec}.`); |
| 105 | const tgzPath = path.join(tmp, tgzName); |
| 106 | runOrThrow('tar', ['-xzf', tgzPath, '-C', tmp], 'extract npm tarball'); |
| 107 | // npm tarballs extract under a "package/" subdir |
| 108 | const pkgDir = path.join(tmp, 'package'); |
| 109 | let root: string; |
| 110 | try { |
| 111 | await fs.stat(pkgDir); |
| 112 | root = await findSkillRoot(pkgDir); |
| 113 | } catch { |
| 114 | root = await findSkillRoot(tmp); |
| 115 | } |
| 116 | return copyInto(root, originalSource, opts); |
| 117 | } |
| 118 | |
| 119 | /** Walk a freshly extracted source looking for a SKILL.md root. */ |
| 120 | async function findSkillRoot(src: string): Promise<string> { |
no test coverage detected