
Research
Supply Chain Attack on Axios Pulls Malicious Dependency from npm
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.
@coderule/clients
Advanced tools
TypeScript HTTP clients for core Coderule microservices (Auth, Sync, Retrieval, AST)
TypeScript HTTP clients for core Coderule microservices (Auth, Sync, Retrieval, AST) with automatic fetch polyfill support.
node-fetch to be installed:
npm install node-fetch@2
The library automatically detects your Node.js version and uses the appropriate fetch implementation.
npm install
npm run build
import { CoderuleClients, consoleLogger } from '@coderule/clients';
const clients = new CoderuleClients({
token: process.env.CODERULE_TOKEN!,
auth: { baseUrl: 'https://r.coderule.ai:16803' }, // optional, defaults to this host
logger: consoleLogger, // optional, defaults to nullLogger (silent)
});
const snapshotStatus = await clients.sync.checkSnapshotStatus(snapshotHash);
const retrieval = await clients.retrieval.query(snapshotHash, 'Find the entrypoint');
const visitorRules = await clients.ast.getVisitorRulesV2();
clients.close();
The unified client automatically exchanges your long-lived token for short-lived
JWTs using the Auth service. Tokens are cached and refreshed halfway through
their lifetime. Whenever the Auth service returns a JWT with a different
server_url, the AST, Retrieval, and Sync clients update their base URLs on
the fly.
You can override individual service hosts or timeouts:
const clients = new CoderuleClients({
token,
auth: { baseUrl: 'https://internal-auth.example.com' },
retrieval: { timeout: 90_000 },
sync: { baseUrl: 'http://localhost:8002' }, // static override keeps sync on localhost
});
If you prefer to manage dependency injection yourself, wire the Auth client and JWT factory and pass the provider into the other clients.
import {
AuthHttpClient,
JWTFactory,
RetrievalHttpClient,
SyncHttpClient,
ASTHttpClient,
} from '@coderule/clients';
const authClient = new AuthHttpClient('http://localhost:8001');
const jwtFactory = new JWTFactory(authClient, longLivedToken);
const syncClient = new SyncHttpClient({
baseUrl: 'http://localhost:8002',
jwtProvider: jwtFactory,
});
const astClient = new ASTHttpClient({
baseUrl: 'http://localhost:8003',
jwtProvider: jwtFactory,
});
const retrievalClient = new RetrievalHttpClient({
baseUrl: 'http://localhost:8004',
jwtProvider: jwtFactory,
});
const status = await syncClient.checkSnapshotStatus(snapshotHash);
const queryResult = await retrievalClient.query(snapshotHash, 'Find the entrypoint');
const rules = await astClient.getVisitorRulesV2();
const authClient = new AuthHttpClient('http://localhost:8001');
try {
const authResponse = await authClient.authenticate('your-token');
console.log(`JWT expires at: ${authResponse.expires_at}`);
const health = await authClient.health();
console.log(`Auth service status: ${health.status}`);
} finally {
authClient.close();
}
const syncClient = new SyncHttpClient({
baseUrl: 'http://localhost:8002',
jwtProvider: jwtFactory,
});
const files = [
{ file_path: 'src/main.ts', content: 'const x = 1;' },
{ file_path: 'src/utils.ts', content: 'export function util() {}' },
{ file_path: 'package.json', content: '{"name": "test"}' },
];
const filesInfo = [];
const fileHashes = [];
const fileContents = new Map();
for (const file of files) {
const fileHash = SyncHttpClient.calculateFileHash(file.file_path, file.content);
filesInfo.push({ file_path: file.file_path, file_hash: fileHash });
fileContents.set(fileHash, {
path: file.file_path,
content: Buffer.from(file.content),
});
fileHashes.push(fileHash);
}
const snapshotHash = SyncHttpClient.calculateSnapshotHash(fileHashes);
const status = await syncClient.checkSnapshotStatus(snapshotHash);
if (status.status === 'NOT_FOUND') {
const result = await syncClient.createSnapshot(snapshotHash, filesInfo);
if (result.status === 'MISSING_CONTENT' && result.missing_files) {
const missingContent = new Map();
for (const missingFile of result.missing_files) {
if (fileContents.has(missingFile.file_hash)) {
missingContent.set(
missingFile.file_hash,
fileContents.get(missingFile.file_hash)!,
);
}
}
if (missingContent.size > 0) {
await syncClient.uploadFileContent(missingContent);
await syncClient.createSnapshot(snapshotHash, filesInfo);
}
}
}
// Download a self-contained HDF5 bush file for offline use
const bushBuffer = await syncClient.downloadSnapshotBush(snapshotHash);
fs.writeFileSync('snapshot.h5', Buffer.from(bushBuffer));
// Fetch the HDF5 bush reader skill (public, no auth required)
const skillMarkdown = await syncClient.getHdf5BushReaderSkill();
const astClient = new ASTHttpClient({
baseUrl: 'http://localhost:8003',
jwtProvider: jwtFactory,
});
const health = await astClient.health();
console.log(`AST service status: ${health.status}`);
const rules = await astClient.getVisitorRulesV2();
console.log(`Rules format: ${rules.format}`);
const compiled = ASTHttpClient.compileRulesV2(rules);
const ignoredPredicate = ASTHttpClient.buildIgnoredPredicate(compiled);
const retrievalClient = new RetrievalHttpClient({
baseUrl: 'http://localhost:8004',
jwtProvider: jwtFactory,
});
const result = await retrievalClient.query(
snapshotHash,
'Find the main authentication logic',
3000,
{
formatter: 'compact',
flow_strength: 1.5,
blend_alpha: 0.8,
},
);
console.log(result.formatted_output);
const status = await retrievalClient.checkSnapshotStatus(snapshotHash);
console.log(`Snapshot indexing status: ${status.status}`);
const cacheStats = await retrievalClient.getCacheStats();
console.log(`Cached snapshots: ${cacheStats.cached_snapshots}`);
The SyncHttpClient provides static utility methods for hash calculation:
import { SyncHttpClient } from '@coderule/clients';
// Calculate file hash from path and content
const fileHash = SyncHttpClient.calculateFileHash('src/main.ts', 'const x = 1;');
console.log(`File hash: ${fileHash}`);
// Calculate snapshot hash from file hashes
const fileHashes = ['hash1', 'hash2', 'hash3'];
const snapshotHash = SyncHttpClient.calculateSnapshotHash(fileHashes);
console.log(`Snapshot hash: ${snapshotHash}`);
All clients throw errors with descriptive messages on failures:
try {
const result = await client.someMethod();
} catch (error) {
if (error.message.includes('timeout')) {
console.error('Request timed out');
} else if (error.message.includes('401')) {
console.error('Authentication failed');
} else {
console.error('Unexpected error:', error.message);
}
}
All clients support timeout configuration in milliseconds:
// Custom timeout of 30 seconds
const retrievalClient = new RetrievalHttpClient({
baseUrl: 'http://localhost:8004',
timeout: 30_000,
jwtProvider: jwtFactory,
logger: consoleLogger, // optional
});
CoderuleClients also accepts per-service overrides via the auth, ast, retrieval,
and sync options. JWTFactory exposes an onTokenRefreshed callback if you need to
observe token rotations (for example, to log the current server_url).
The SDK includes a flexible logging interface that allows you to integrate with any logging library:
import { Logger, nullLogger, consoleLogger } from '@coderule/clients';
// Use the built-in silent logger (default)
const silentClients = new CoderuleClients({
token,
logger: nullLogger, // or omit for default
});
// Use the built-in console logger for debugging
const debugClients = new CoderuleClients({
token,
logger: consoleLogger,
});
// Implement your own logger
const customLogger: Logger = {
error: (message, ...meta) => myLogger.error(message, meta),
warn: (message, ...meta) => myLogger.warn(message, meta),
info: (message, ...meta) => myLogger.info(message, meta),
debug: (message, ...meta) => myLogger.debug(message, meta),
};
const clients = new CoderuleClients({
token,
logger: customLogger,
});
All individual clients also accept an optional logger parameter:
const authClient = new AuthHttpClient('http://localhost:8001', 30000, consoleLogger);
logger parameternullLogger which produces no outputSyncHttpClient, RetrievalHttpClient, and
ASTHttpClient no longer accept JWT strings per method. Inject a JWTFactory or any
JWTProvider implementation through the constructor instead.CoderuleClients centralises token exchange. Auth defaults to
https://r.coderule.ai:16803, and other clients automatically adopt the server_url
provided by the Auth service unless explicitly overridden.node-fetch is only required for Node.js versions < 18JWTProvider (JWTFactory or CoderuleClients)server_url (unless a manual override is configured)close() method for consistency, though fetch doesn't maintain persistent connections# Install dependencies
npm install
# Build TypeScript to JavaScript
npm run build
# Output will be in dist/ directory
ISC
FAQs
TypeScript HTTP clients for core Coderule microservices (Auth, Sync, Retrieval, AST)
We found that @coderule/clients demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.

Research
Malicious versions of the Telnyx Python SDK on PyPI delivered credential-stealing malware via a multi-stage supply chain attack.

Security News
TeamPCP is partnering with ransomware group Vect to turn open source supply chain attacks on tools like Trivy and LiteLLM into large-scale ransomware operations.