
Research
6 Malicious Packagist Themes Ship Trojanized jQuery and FUNNULL Redirect Payloads
Six malicious Packagist packages posing as OphimCMS themes contain trojanized jQuery that exfiltrates URLs, injects ads, and loads FUNNULL-linked redirects.
@cmdop/node
Advanced tools
Node.js SDK for CMDOP agent interaction via gRPC.
npm install @cmdop/node
# or
pnpm add @cmdop/node
Requirements: Node.js >= 18.0.0
import { CMDOPClient } from '@cmdop/node';
// Local connection (auto-discover agent via ~/.cmdop/agent.info)
const client = CMDOPClient.local();
// Remote connection via cloud relay
const client = CMDOPClient.remote('cmdop_live_xxx');
// Auto-detect: local first, falls back to CMDOP_API_KEY env var
const client = await CMDOPClient.discover();
// Automatic cleanup via Symbol.asyncDispose (TypeScript 5.2+)
await using client = CMDOPClient.local();
// Create a session
const session = await client.terminal.create({ cols: 120, rows: 40 });
// Send input / resize / signal
await client.terminal.sendInput(session.sessionId, 'ls -la\n');
await client.terminal.resize(session.sessionId, 200, 50);
await client.terminal.signal(session.sessionId, 'SIGINT');
// Run command and wait for output
const output = await client.terminal.execute(session.sessionId, 'echo hello');
console.log(output.stdout, output.exitCode);
// Get the currently active session
const active = await client.terminal.getActiveSession();
// Stream output in real-time
const stream = client.terminal.stream(session.sessionId);
stream.on((event) => {
if (event.type === 'output') process.stdout.write(event.data.toString());
});
await stream.start();
// List / close
const { sessions } = await client.terminal.list();
await client.terminal.close(session.sessionId);
// List directory
const result = await client.files.list('/tmp', { pageSize: 50 });
for (const entry of result.entries) {
console.log(entry.name, entry.type, entry.size);
}
// Read / Write
const file = await client.files.read('/tmp/file.txt');
console.log(file.content.toString('utf-8'));
await client.files.write('/tmp/new.txt', 'Hello World');
// Stat / Mkdir / Move / Copy / Delete
await client.files.stat('/tmp/file.txt');
await client.files.mkdir('/tmp/newdir');
await client.files.move('/tmp/a.txt', '/tmp/b.txt');
await client.files.copy('/tmp/src.txt', '/tmp/dst.txt');
await client.files.delete('/tmp/file.txt');
// Search / Archive
const matches = await client.files.search('/tmp', { pattern: '*.log' });
await client.files.archive(['/tmp/dir'], '/tmp/out.zip');
Transfer files from the remote agent to local disk.
// Download a remote file
const result = await client.download.downloadFile('/remote/data.csv', '/local/data.csv');
console.log(`Saved ${result.size} bytes in ${result.metrics?.durationMs}ms`);
// Download a URL via the agent (respects agent cookies/auth)
const result = await client.download.downloadUrl(
'https://example.com/report.pdf',
'/local/report.pdf'
);
// One-shot execution
const result = await client.agent.run('List files in /tmp', {
mode: 'terminal', // 'chat' | 'terminal' | 'command' | 'router' | 'planner' | 'browser' | 'scraper' | 'form_filler'
timeoutSeconds: 60,
maxTurns: 10,
maxRetries: 2,
model: 'claude-opus-4-6',
});
console.log(result.text);
console.log(result.usage?.totalTokens);
console.log(result.toolResults);
// Streaming
const stream = client.agent.stream('Explain what ls -la does');
stream.on((event) => {
if (event.type === 'token') process.stdout.write(event.token);
if (event.type === 'tool_start') console.log(`\n[tool: ${event.payload}]`);
if (event.type === 'thinking') console.log(`[thinking: ${event.payload}]`);
});
const result = await stream.start();
stream.cancel(); // cancel if needed
// Structured output
import { z, zodToJsonSchema } from '@cmdop/node';
const FileListSchema = z.object({
files: z.array(z.string()),
total: z.number(),
});
const data = await client.agent.extract<z.infer<typeof FileListSchema>>(
'List files in /tmp',
zodToJsonSchema(FileListSchema)
);
console.log(data.files, data.total);
// List all installed skills
const skills = await client.skills.list();
for (const skill of skills) {
console.log(`${skill.name} (${skill.origin}) — ${skill.description}`);
}
// Show skill details
const detail = await client.skills.show('code-review');
if (detail.found) {
console.log(detail.info?.name, detail.info?.version);
console.log(detail.content); // SKILL.md body
console.log(detail.source); // file path on agent
}
// Run a skill
const result = await client.skills.run('code-review', 'Review my PR changes');
console.log(result.text);
console.log(result.durationMs);
// Structured output
import { z, zodToJsonSchema } from '@cmdop/node';
const ReviewSchema = z.object({
issues: z.array(z.object({ file: z.string(), line: z.number(), message: z.string() })),
summary: z.string(),
});
const review = await client.skills.extract<z.infer<typeof ReviewSchema>>(
'code-review',
'Review the changes',
zodToJsonSchema(ReviewSchema)
);
console.log(review.issues, review.summary);
Dedicated structured data extraction RPC (more reliable than agent.extract()).
import { z } from '@cmdop/node';
const ConfigSchema = z.object({
host: z.string(),
port: z.number(),
database: z.string(),
});
// Zod schema — result is validated and fully typed
const result = await client.extract.runSchema(
'Find the database config in config files',
ConfigSchema
);
console.log(result.data.host);
console.log(result.reasoning);
console.log(result.metrics?.durationMs);
// Raw JSON Schema
const raw = await client.extract.run<{ host: string }>(
'Find the database host',
JSON.stringify({ type: 'object', properties: { host: { type: 'string' } } })
);
// Create a session
const browser = await client.browser.createSession({
startUrl: 'https://example.com',
headless: true,
});
// Navigation
await browser.navigate('https://github.com');
await browser.reload();
await browser.goBack();
await browser.goForward();
// Interaction
await browser.click({ selector: 'button.submit' });
await browser.type('hello world', 'input[name="q"]');
await browser.key('Enter');
await browser.hover('nav a.menu');
await browser.mouseMove(100, 200);
// Scroll
await browser.scrollDown(500);
await browser.scrollUp(500);
await browser.scrollToBottom();
await browser.scrollToTop();
// Wait
await browser.wait({ selector: '.loaded', timeoutMs: 5000 });
await browser.wait({ timeMs: 1000 });
// Read content
const html = await browser.getHTML('main');
const text = await browser.getText('h1');
const state = await browser.getState(); // url, title, scrollY, ...
const info = await browser.getPageInfo(); // url, title, pageHeight, isHttps, cloudflareDetected, ...
// Extract structured data
const values = await browser.extract('a', { attribute: 'href' });
const data = await browser.extractData({
fields: [
{ name: 'title', selector: 'h1', type: 'text' },
{ name: 'price', selector: '.price', type: 'text' },
],
});
// Screenshot (returns Buffer)
const screenshot = await browser.screenshot({ fullPage: true });
// Fetch (runs inside browser context, respects cookies)
const json = await browser.fetchJson<{ id: number }>('/api/me');
const html = await browser.fetchText('https://example.com/page');
// Cookies
const cookies = await browser.getCookies({ domain: 'example.com' });
await browser.setCookies([{ name: 'session', value: 'abc', domain: 'example.com' }]);
// Validate selectors
const valid = await browser.validateSelectors({ selectors: ['h1', '.missing'] });
// Network capture
await browser.networkEnable();
const exchanges = await browser.networkGetExchanges({ urlPattern: '/api/' });
const last = await browser.networkGetLast('/api/user');
const stats = await browser.networkStats();
const har = await browser.networkExportHAR();
await browser.networkClear();
await browser.networkDisable();
// Cleanup
await browser.close();
// Or with Symbol.asyncDispose
await using browser = await client.browser.createSession({ startUrl: 'https://example.com' });
For cloud relay connections, set the session ID before using files, agent, extract, or browser.
const client = CMDOPClient.remote('cmdop_live_xxx');
// Discover available agents
const agents = await CMDOPClient.listAgents('cmdop_live_xxx');
const online = await CMDOPClient.getOnlineAgents('cmdop_live_xxx');
// Route all services to a specific agent
client.setSessionId(online[0].agentId);
// Or route each service to a different machine
await client.files.setMachine('storage-01');
await client.agent.setMachine('gpu-box');
await client.skills.setMachine('gpu-box');
await client.terminal.setMachine('prod-01');
const client = CMDOPClient.remote('cmdop_live_xxx');
client.mode // 'local' | 'remote'
client.address // 'grpc.cmdop.com:443'
client.isConnected // false (lazy connection)
client.transport // underlying BaseTransport instance
// Build client from a pre-configured transport
import { RemoteTransport } from '@cmdop/node';
const transport = new RemoteTransport({ apiKey: '...', server: 'custom.host:443' });
const client = CMDOPClient.fromTransport(transport);
import { configure } from '@cmdop/node';
configure({
connectTimeoutMs: 10_000,
requestTimeoutMs: 30_000,
retryAttempts: 5,
retryTimeoutMs: 30_000,
keepaliveIntervalMs: 25_000,
circuitBreakerFailMax: 5,
circuitBreakerResetMs: 30_000,
maxMessageSize: 64 * 1024 * 1024,
grpcServer: 'grpc.cmdop.com:443',
apiBaseUrl: 'https://api.cmdop.com',
logLevel: 'info',
logJson: false,
});
Environment variables:
| Variable | Default | Description |
|---|---|---|
CMDOP_GRPC_SERVER | grpc.cmdop.com:443 | gRPC server address |
CMDOP_API_BASE_URL | https://api.cmdop.com | REST API base URL |
CMDOP_CONNECT_TIMEOUT_MS | 10000 | Connection timeout (ms) |
CMDOP_REQUEST_TIMEOUT_MS | 30000 | Per-request timeout (ms) |
CMDOP_RETRY_ATTEMPTS | 5 | Max retry attempts |
CMDOP_RETRY_TIMEOUT_MS | 30000 | Total retry window (ms) |
CMDOP_KEEPALIVE_INTERVAL_MS | 25000 | Keepalive ping interval (ms) |
CMDOP_QUEUE_MAX_SIZE | 1000 | Max streaming queue size |
CMDOP_CIRCUIT_FAIL_MAX | 5 | Circuit breaker failure threshold |
CMDOP_CIRCUIT_RESET_TIMEOUT_MS | 30000 | Circuit breaker reset time (ms) |
CMDOP_MAX_MESSAGE_SIZE | 33554432 | Max gRPC message size (bytes) |
CMDOP_LOG_LEVEL | info | debug|info|warn|error|silent |
CMDOP_LOG_JSON | false | Structured JSON logging |
CMDOP_API_KEY | — | API key (used by CMDOPClient.discover()) |
import {
CMDOPError,
// Core
ConnectionError,
AuthenticationError,
SessionError,
TimeoutError,
NotFoundError,
PermissionError,
// Extended (Node SDK)
AgentNotRunningError,
StalePortFileError,
ConnectionLostError,
InvalidAPIKeyError,
TokenExpiredError,
AgentError,
AgentOfflineError,
AgentBusyError,
FeatureNotAvailableError,
SessionInterruptedError,
FileTooLargeError,
BrowserError,
BrowserSessionClosedError,
BrowserNavigationError,
BrowserElementNotFoundError,
RateLimitError,
} from '@cmdop/node';
try {
await client.files.read('/etc/shadow');
} catch (error) {
if (error instanceof PermissionError) console.log('Permission denied');
else if (error instanceof NotFoundError) console.log('File not found');
else if (error instanceof AgentOfflineError) console.log('Agent is offline');
else if (error instanceof RateLimitError) console.log('Rate limit exceeded');
else if (error instanceof CMDOPError) console.log(error.message);
}
MIT
FAQs
CMDOP SDK for Node.js - Server-side agent interaction via gRPC
The npm package @cmdop/node receives a total of 1,140 weekly downloads. As such, @cmdop/node popularity was classified as popular.
We found that @cmdop/node 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
Six malicious Packagist packages posing as OphimCMS themes contain trojanized jQuery that exfiltrates URLs, injects ads, and loads FUNNULL-linked redirects.

Security News
The GCVE initiative operated by CIRCL has officially opened its publishing ecosystem, letting organizations issue and share vulnerability identifiers without routing through a central authority.

Security News
The project is retiring its odd/even release model in favor of a simpler annual cadence where every major version becomes LTS.