paperclipai/paperclip · error · Error
TypeScript CLI not found at
Error message
TypeScript CLI not found at ${tscCliPath} What it means
This bootstrap script builds @paperclipai/shared and @paperclipai/plugin-sdk from source before plugins can load. It shells out to the TypeScript compiler located at node_modules/typescript/bin/tsc. If that file does not exist, dependencies were not installed (or TypeScript was removed/hoisted elsewhere), so it throws immediately before doing any work.
Solutions
- Run `pnpm install` at the repo root so node_modules/typescript/bin/tsc exists.
- If install ran, check that typescript is a (dev)Dependency of the root workspace and re-add it if a lockfile update removed it.
- If node_modules is corrupted or pruned, delete node_modules and reinstall (`rm -rf node_modules && pnpm install`).
- As a last resort, verify the path expectation in scripts/ensure-plugin-build-deps.mjs:12 matches your package manager's layout (e.g. pnpm's virtual store) and adjust tscCliPath.
Example fix
// before $ node scripts/ensure-plugin-build-deps.mjs Error: TypeScript CLI not found at /repo/node_modules/typescript/bin/tsc // after $ pnpm install $ node scripts/ensure-plugin-build-deps.mjs [paperclip] plugin build deps ready
Defensive patterns
Strategy: validation
Validate before calling
import fs from "node:fs";
const tscCliPath = "node_modules/typescript/bin/tsc";
if (!fs.existsSync(tscCliPath)) {
console.error("Run `pnpm install` first: TypeScript CLI missing at " + tscCliPath);
process.exit(1);
} Type guard
const hasTsc = (p) => fs.existsSync(p) && fs.statSync(p).isFile();
Prevention
- Always run `pnpm install` after cloning or pulling lockfile changes before plugin builds.
- Never install with --production/--prod in environments that build the SDK from source.
- Verify node_modules/typescript exists in CI before invoking the script.
- Keep typescript pinned as a root devDependency so hoisting keeps bin/tsc at the expected path.
When it happens
Trigger: Running scripts/ensure-plugin-build-deps.mjs (directly or via the plugin build path) when node_modules/typescript/bin/tsc is absent — e.g. `pnpm install` was never run, node_modules was partially deleted, TypeScript is not in the root dependency tree, or a pnpm version/hoisting change moved the binary.
Common situations: Fresh clone without `pnpm install`; CI checkout missing the install step; stale node_modules after a lockfile change that dropped typescript; running the script from a pruned/production install (`--prod`) where devDependencies like typescript are omitted.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
- ACPX provider package manifest must be an explicit…
- ACPX provider package root must be an explicit normalized…
- Canonical Evalbook generator not found. Set…
- bundle retained a non-builtin import
- Evalbook requires the chat viewer. Run pnpm --filter…
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/661090a7c041018f.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/ensure-plugin-build-deps.mjs:37
name: "@paperclipai/shared",
output: path.join(rootDir, "packages/shared/dist/index.js"),
completion: path.join(rootDir, "packages/shared/dist/.paperclip-build-complete"),
sourceDir: path.join(rootDir, "packages/shared/src"),
tsconfig: path.join(rootDir, "packages/shared/tsconfig.json"),
dependencies: [],
},
{
name: "@paperclipai/plugin-sdk",
output: path.join(rootDir, "packages/plugins/sdk/dist/index.js"),
completion: path.join(rootDir, "packages/plugins/sdk/dist/.paperclip-build-complete"),
sourceDir: path.join(rootDir, "packages/plugins/sdk/src"),
tsconfig: path.join(rootDir, "packages/plugins/sdk/tsconfig.json"),
dependencies: [0],
},
];
if (!fs.existsSync(tscCliPath)) {
throw new Error(`TypeScript CLI not found at ${tscCliPath}`);
}
function directoryFingerprint(directory, exclude) {
const hash = createHash("sha256");
function visit(dir) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
const entryPath = path.join(dir, entry.name);
if (entryPath === exclude) continue;
if (entry.isDirectory()) {
visit(entryPath);
} else if (entry.isFile()) {
const content = fs.readFileSync(entryPath);
hash.update(JSON.stringify([path.relative(directory, entryPath), content.length]));
hash.update(content);
}
}
}
visit(directory);View on GitHub (pinned to 3f1d897a7c)