
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
@instantlycreative/pulse
Advanced tools
npm add @instantlycreative/pulse
import { PulseClient } from '@instantlycreative/pulse'
const client = new PulseClient()
import { PulseClient } from '@instantlycreative/pulse'
import crossFetch from 'cross-fetch'
import WebSocket from 'ws'
const client = new PulseClient({
fetch: crossFetch,
ws: WebSocket,
})
Before running any command, you must login:
const { token } = await client.login({ email: 'user@example.com', password: 'mypasswd' })
if (token) {
fs.writeFileSync('~/.pulse.token', token)
}
Alternatively, you can just pass a saved token.
const token = fs.readFileSync('~/.pulse.token', 'utf8')
await client.login({ token })
Runtime:
dockernode14.xnode16.xnode18.xnode20.xpython3.8python3.9python3.10python3.11python3.12ruby3.2ruby3.3loginasync login({
email: string,
password: string
}): Promise<{
success: boolean;
token?: string;
error?: string
}>
async login({
token: string
}): Promise<{
success: boolean;
token?: string;
error?: string
}>
getasync get({
pulseId
}: {
pulseId: string
}): Promise<{
success: boolean
team?: Team
pulse?: Pulse
statuses?: Status[]
error?: string
}>
interface Team {
id: string
name: string
createdByUser: string
createdAt: Date
updatedAt: Date
}
interface Pulse {
id: string
name: string
teamId: string
controllerId: string
createdAt: Date
updatedAt: Date
deletedAt: Date | null
}
interface Status {
id: string
pulseId: string
sequence: number
status: PulseStatus
isPaused: boolean
data: any
createdAt: Date
updatedAt: Date
}
export enum PulseStatus {
CREATED = 'created',
INITIALIZING = 'initializing',
INITIALIZED = 'initialized',
UPDATING = 'updating',
UPDATED = 'updated',
WARMING = 'warming',
READY = 'ready',
EXECUTING = 'executing',
STOPPING = 'stopping',
STOPPED = 'stopped',
DELETED = 'deleted',
}
createasync create({
pulseId, // if not provided, a random id will be generated
name,
teamId,
runtime,
}: {
pulseId?: string
name?: string
teamId: string
runtime: string
}): Promise<{
success: boolean;
pulseId?: string;
error?: string
}>
forkasync fork({
pulseId,
newPulseId, // if not provided, a random id will be generated
}: {
pulseId: string
newPulseId?: string
}): Promise<{
success: boolean;
pulseId?: string
error?: string;
}>
updateasync update({
pulseId,
name,
buildOptions, // if runtime supports it, i.e, docker build step args
execOptions, // runtime exec options, e.g., "--param" in "node --param /app/script.js"
execPath, // entry point, e.g., "/script.js" in "node /script.js", or "/Dockerfile" for "docker" runtime.
files, // files to be uploaded to the app directory. cannot be used with "zipFile"
zipFile, // zip with the app. will be extracted to /app/ directory. cannot be used with "files"
initFile, // initialization script
envFile, // .env file
stdinFile, // a file that will be used as stdin for the app
vcpu, // number of virtual CPUs
memory, // memory in MB
balloon, // initial balloon size in MB
autoPause, // pause the app after it's done executing the user app
autoShutdown, // shutdown the app after it's done executing the user app
autoDelete, // delete the app on shutdown
}: {
pulseId: string
name?: string
buildOptions?: string
execOptions?: string
execPath: string
files?: { [path: string]: string | Buffer | File }
zipFile?: string | Buffer | File
initFile?: string | Buffer | File
envFile?: string | Buffer | File
stdinFile?: string | Buffer | File
vcpu?: string
memory?: string
balloon?: string
autoPause?: boolean
autoShutdown?: boolean
autoDelete?: boolean
}): Promise<{
success: boolean;
accessToken?: string;
error?: string
}>
export interface File {
source?: string // path on disk
content?: string | Buffer
mode?: number // like 0o755
}
Use accessToken to stream the output using client.stream().
Notes on build and exec options:
buildOptions is only used for the docker runtime.execOptions is used for all runtimes.const { accessToken } = await client.update({
pulseId,
execPath: '/script.js',
files: {
'/script.js': 'console.log("Hello, world!")',
},
execOptions: '--max-old-space-size=4096', // node param to set memory to 4GB
})
This is will result in executing "node --max-old-space-size=4096 /script.js".
const { accessToken } = await client.update({
pulseId,
execPath: '/script.js',
files: {
'/script.js': 'console.log("Hello, world!")',
},
buildOptions: '--build-arg=HELLO=WORLD --build-arg=ANOTHER=ARG', // docker build arg
})
This is will result in adding "--build-arg=HELLO=WORLD" and "--build-arg=ANOTHER=ARG" params to the "docker build" command.
Notes on files:
files is an object where the key is the path and the value is the content. The path must start with a slash, but the resulting file will be created in the app directory, which is /run/app.zipFile is a zip file that will be extracted to the app directory (/run/app).initFile is a script that will be executed before the user script.envFile is a .env file that will be used to set environment variables.stdinFile is a file that will be used as stdin for the app.When any file is a string, it will be treated as utf-8 encoded content:
const { accessToken } = await client.update({
pulseId,
execPath: '/script.js',
files: {
'/script.js': 'console.log("Hello, world!")',
},
})
When any file is a buffer, it will be treated as binary content:
import fs from 'fs'
const { accessToken } = await client.update({
pulseId,
execPath: '/script.js',
zipFile: fs.readFileSync('/some/path/to/local/script.zip'), // returns Buffer
})
Otherwise, you can use the File interface:
Note:
const { accessToken } = await client.update({
pulseId,
execPath: '/script.js',
files: {
'/script.js': {
source: '/some/path/to/local/script.js',
mode: 0o755,
},
'/other/script.js': {
content: 'console.log("Hello, world!")',
mode: 0o644,
},
'/another/script.js': {
content: Buffer.from('console.log("Hello, world!")'),
mode: 0o644,
},
},
})
Optional initFile is a bash script that will be executed before the user script:
const { accessToken } = await client.update({
pulseId,
execPath: '/script.js',
zipFile: fs.readFileSync('/some/path/to/local/script.zip'),
initFile: `#!/bin/bash
echo "Hello, world!"
echo "Goodbye, world!"
`,
})
Optional envFile is a .env file that will be used to set environment variables:
const { accessToken } = await client.update({
pulseId,
execPath: '/script.js',
zipFile: fs.readFileSync('/some/path/to/local/script.zip'),
envFile: `VAR1="value 1"
VAR2="value 2"
ANOTHER_VAR="another value"
# ... you get the idea
`,
})
Optional stdinFile is a file that will be used as stdin for the app:
const { accessToken } = await client.update({
pulseId,
execPath: '/script.js',
zipFile: fs.readFileSync('/some/path/to/local/script.zip'),
stdinFile: fs.readFileSync('/some/path/to/local/stdin.txt'),
})
If the app reads STDIN, it will read from that file.
warmUpasync warmUp({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>
runasync run({ pulseId }: { pulseId: string }): Promise<{ success: boolean; accessToken?: string; error?: string }>
pauseasync pause({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>
resumeasync resume({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>
shutdownasync shutdown({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>
deleteasync delete({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>
balloonasync balloon({
pulseId,
balloon, // change balloon size to this (in MB)
}: {
pulseId: string
balloon: number
}): Promise<{
success: boolean;
error?: string
}>
historyasync history({
pulseId,
}: {
pulseId: string
}): Promise<{
success: boolean;
outputs?: Output[];
problems?: Problem[];
code?: number | null;
error?: string
}>
interface Output {
pulseId: string
time: Date
source: string // "manager", "controller", "vm", "agent", "app"
stream: string // "stdout" or "stderr"
data: string
}
interface Problem {
pulseId: string
time: Date
source: string // "manager", "controller", "vm", "agent", "app"
message: string
stack?: string
}
statsasync stats({
pulseId,
}: {
pulseId: string
}): Promise<{
success: boolean;
stats?: AgentStats | null;
error?: string
}>
interface AgentStats {
time: number
cpuAverage: number
memAverage: number
memTotal: number
memFree: number
memAvailable: number
vsz: number
rss: number
user: number
system: number
guest: number
}
teamListasync teamList(): Promise<{ success: boolean; teams?: Team[]; error?: string }>
interface Team {
id: string
name: string
createdByUser: string
createdAt: number
updatedAt: number
}
teamGetasync teamGet({ teamId }: { teamId: string }): Promise<{ success: boolean; team?: Team; error?: string }>
interface Team {
id: string
name: string
createdByUser: string
createdAt: number
updatedAt: number
}
teamCreateasync teamCreate({ name }: { name: string }): Promise<{ success: boolean; team?: Team; error?: string }>
interface Team {
id: string
name: string
createdByUser: string
createdAt: number
updatedAt: number
}
teamUpdateasync teamUpdate({ teamId, name }: { teamId: string; name: string }): Promise<{ success: boolean; team?: Team; error?: string }>
interface Team {
id: string
name: string
createdByUser: string
createdAt: number
updatedAt: number
}
teamDeleteasync teamDelete({ teamId }: { teamId: string }): Promise<{ success: boolean; team?: Team; error?: string }>
interface Team {
id: string
name: string
createdByUser: string
createdAt: number
updatedAt: number
}
teamUserListasync teamUserList({ teamId }: { teamId: string }): Promise<{ success: boolean; users?: User[]; error?: string }>
interface User {
id: string
email: string
name: string
createdAt: number
updatedAt: number
}
teamUserAddasync teamUserAdd({ teamId, userId }: { teamId: string; userId: string }): Promise<{ success: boolean; error?: string }>
interface User {
id: string
email: string
name: string
createdAt: number
updatedAt: number
}
teamUserRemoveasync teamUserRemove({ teamId, userId }: { teamId: string; userId: string }): Promise<{ success: boolean; error?: string }>
runThis is a do-all method that will:
createupdatewarmUprundeleteasync run(({
pulseId,
name,
buildOptions, // if runtime supports it, i.e, docker build step args
execOptions, // runtime exec options, e.g., "--param" in "node --param /app/script.js"
execPath, // entry point, e.g., "/script.js" in "node /script.js", or "/Dockerfile" for "docker" runtime.
files, // files to be uploaded to the app directory. cannot be used with "zipFile"
zipFile, // zip with the app. will be extracted to /app/ directory. cannot be used with "files"
initFile, // initialization script
envFile, // .env file
stdinFile, // a file that will be used as stdin for the app
vcpu, // number of virtual CPUs
memory, // memory in MB
balloon, // initial balloon size in MB
autoPause, // pause the app after it's done executing the user app
autoShutdown, // shutdown the app after it's done executing the user app
autoDelete, // delete the app on shutdown
}: {
pulseId: string
name?: string
buildOptions?: string
execOptions?: string
execPath: string
files?: { [path: string]: string | Buffer | File }
zipFile?: string | Buffer | File
initFile?: string | Buffer | File
envFile?: string | Buffer | File
stdinFile?: string | Buffer | File
vcpu?: string
memory?: string
balloon?: string
autoPause?: boolean
autoShutdown?: boolean
autoDelete?: boolean
}): Promise<{
success: boolean;
accessToken?: string;
error?: string
}>
Use accessToken to stream the output using client.stream().
streamasync stream({
accessToken,
onOutput,
onProblem,
onFinish,
}: {
accessToken: string
onOutput?: (data: Output) => void
onProblem?: (data: Problem) => void
onFinish?: (data: Finished) => void
}): Promise<void>
type ManagerMsgOutput = {
type: 'OUTPUT'
pulseId: string
output: Output
}
interface Output {
pulseId: string
time: Date
source: string // "manager", "controller", "vm", "agent", "app"
stream: string // "stdout" or "stderr"
data: string
}
interface Problem {
pulseId: string
time: Date
source: string // "manager", "controller", "vm", "agent", "app"
message: string
stack?: string
}
export interface Finished {
pulseId: string
code: number | null
stats: AgentStats | null
}
interface AgentStats {
time: number
cpuAverage: number
memAverage: number
memTotal: number
memFree: number
memAvailable: number
vsz: number
rss: number
user: number
system: number
guest: number
}
An example:
await manager.stream({
accessToken,
onOutput: (data) => {
process.stdout.write(data.output.data)
},
onProblem: (data) => {
console.error(data.error.message)
},
onFinish: (data) => {
console.log('--------------------')
console.log(`Exit code: ${data.code}`)
if (data.stats) {
console.log(`Time: ${stats.time}ms`)
console.log(`CPU average: ${stats.cpuAverage.toFixed(2)}%`)
console.log(`Memory average: ${stats.memAverage.toFixed(2)}%`)
console.log(`Memory total: ${(stats.memTotal / 1024).toFixed(2)}MB`)
console.log(`Memory free: ${(stats.memFree / 1024).toFixed(2)}MB`)
console.log(`Memory available: ${(stats.memAvailable / 1024).toFixed(2)}MB`)
console.log(`VSZ: ${stats.vsz}`)
console.log(`RSS: ${stats.rss}`)
console.log(`User: ${stats.user.toFixed(2)}%`)
console.log(`System: ${stats.system.toFixed(2)}%`)
console.log(`Guest: ${stats.guest.toFixed(2)}%`)
}
},
})
FAQs
# Initializing
We found that @instantlycreative/pulse demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers 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.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.