
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
await client.terminal.setMachine('my-server');
const { output, exitCode } = await client.terminal.execute('echo hello');
console.log(output, exitCode);
// Get the currently active session
const active = await client.terminal.getActiveSession({ hostname: 'my-server' });
// Stream output in real-time (polling-based)
const stream = client.terminal.stream(session.sessionId);
stream.on((event) => {
if (event.type === 'output') process.stdout.write(event.data.toString());
});
await stream.connect();
// Attach to session (bidirectional gRPC stream, SSH-like)
const attach = client.terminal.attach(session.sessionId, { cols: 120, rows: 40 });
attach.on((event) => {
if (event.type === 'sessionReady') console.log('Connected!');
if (event.type === 'output') process.stdout.write(event.data);
if (event.type === 'closed') console.log('Disconnected:', event.reason);
});
await attach.connect();
attach.sendInput('ls -la\n');
attach.sendResize(200, 50);
attach.close();
// List / close
const { sessions } = await client.terminal.list();
await client.terminal.close(session.sessionId);
High-level interactive terminal (like ssh). Handles raw mode, stdin/stdout piping, resize, and Ctrl+D to disconnect.
import { CMDOPClient, sshConnect } from '@cmdop/node';
const client = CMDOPClient.remote('cmdop_live_xxx');
const exitCode = await sshConnect({
client,
hostname: 'my-server',
debug: false, // optional: log gRPC messages to stderr
sessionId: '...', // optional: skip session discovery
});
process.exit(exitCode);
// 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'
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' } } })
);
For cloud relay connections, set the session ID before using files, agent, or extract.
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,
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);
}
No code needed? Use the standalone binary:
curl -fsSL cmdop.com/install-cli.sh | bash
cmdok ssh

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.