npm package: @instantlycreative/pulse
Description: # Initializing
Latest version: 0.4.3

# Pulse

# Initializing

```sh
npm add @instantlycreative/pulse
```

## Importing in browser

```ts
import { PulseClient } from '@instantlycreative/pulse'

const client = new PulseClient()
```

## Importing in node

```ts
import { PulseClient } from '@instantlycreative/pulse'
import crossFetch from 'cross-fetch'
import WebSocket from 'ws'

const client = new PulseClient({
  fetch: crossFetch,
  ws: WebSocket,
})
```

# Usage

Before running any command, you must login:

```ts
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.

```ts
const token = fs.readFileSync('~/.pulse.token', 'utf8')
await client.login({ token })
```

## API

Runtime:

- `docker`
- `node14.x`
- `node16.x`
- `node18.x`
- `node20.x`
- `python3.8`
- `python3.9`
- `python3.10`
- `python3.11`
- `python3.12`
- `ruby3.2`
- `ruby3.3`

### `login`

```ts
async login({
  email: string,
  password: string
}): Promise<{
  success: boolean;
  token?: string;
  error?: string
}>

async login({
  token: string
}): Promise<{
  success: boolean;
  token?: string;
  error?: string
}>
```

### `get`

```ts
async get({
  pulseId
}: {
  pulseId: string
}): Promise<{
  success: boolean
  team?: Team
  pulse?: Pulse
  statuses?: Status[]
  error?: string
}>
```

```ts
interface Team {
  id: string
  name: string
  createdByUser: string
  createdAt: Date
  updatedAt: Date
}
```

```ts
interface Pulse {
  id: string
  name: string
  teamId: string
  controllerId: string
  createdAt: Date
  updatedAt: Date
  deletedAt: Date | null
}
```

```ts
interface Status {
  id: string
  pulseId: string
  sequence: number
  status: PulseStatus
  isPaused: boolean
  data: any
  createdAt: Date
  updatedAt: Date
}
```

```ts
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',
}
```

### `create`

```ts
async 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
}>
```

### `fork`

```ts
async fork({
  pulseId,
  newPulseId,   // if not provided, a random id will be generated
}: {
  pulseId: string
  newPulseId?: string
}): Promise<{
  success: boolean;
  pulseId?: string
  error?: string;
}>
```

### `update`

```ts
async 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
}>
```

```ts
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.

```ts
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".

```ts
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:

```ts
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:

```ts
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:

- "source" is the path to the file on disk.
- "content" is the content of the file.
- "mode" is the file mode.
- "source" and "content" are mutually exclusive.

```ts
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:

```ts
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:

```ts
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:

```ts
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.

### `warmUp`

```ts
async warmUp({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>
```

### `run`

```ts
async run({ pulseId }: { pulseId: string }): Promise<{ success: boolean; accessToken?: string; error?: string }>
```

### `pause`

```ts
async pause({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>
```

### `resume`

```ts
async resume({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>
```

### `shutdown`

```ts
async shutdown({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>
```

### `delete`

```ts
async delete({ pulseId }: { pulseId: string }): Promise<{ success: boolean; error?: string }>
```

### `balloon`

```ts
async balloon({
  pulseId,
  balloon, // change balloon size to this (in MB)
}: {
  pulseId: string
  balloon: number
}): Promise<{
  success: boolean;
  error?: string
}>
```

### `history`

```ts
async history({
  pulseId,
}: {
  pulseId: string
}): Promise<{
  success: boolean;
  outputs?: Output[];
  problems?: Problem[];
  code?: number | null;
  error?: string
}>
```

```ts
interface Output {
  pulseId: string
  time: Date
  source: string // "manager", "controller", "vm", "agent", "app"
  stream: string // "stdout" or "stderr"
  data: string
}
```

```ts
interface Problem {
  pulseId: string
  time: Date
  source: string // "manager", "controller", "vm", "agent", "app"
  message: string
  stack?: string
}
```

### `stats`

```ts
async stats({
  pulseId,
}: {
  pulseId: string
}): Promise<{
  success: boolean;
  stats?: AgentStats | null;
  error?: string
}>
```

```ts
interface AgentStats {
  time: number
  cpuAverage: number
  memAverage: number
  memTotal: number
  memFree: number
  memAvailable: number
  vsz: number
  rss: number
  user: number
  system: number
  guest: number
}
```

### `teamList`

```ts
async teamList(): Promise<{ success: boolean; teams?: Team[]; error?: string }>
```

```ts
interface Team {
  id: string
  name: string
  createdByUser: string
  createdAt: number
  updatedAt: number
}
```

### `teamGet`

```ts
async teamGet({ teamId }: { teamId: string }): Promise<{ success: boolean; team?: Team; error?: string }>
```

```ts
interface Team {
  id: string
  name: string
  createdByUser: string
  createdAt: number
  updatedAt: number
}
```

### `teamCreate`

```ts
async teamCreate({ name }: { name: string }): Promise<{ success: boolean; team?: Team; error?: string }>
```

```ts
interface Team {
  id: string
  name: string
  createdByUser: string
  createdAt: number
  updatedAt: number
}
```

### `teamUpdate`

```ts
async teamUpdate({ teamId, name }: { teamId: string; name: string }): Promise<{ success: boolean; team?: Team; error?: string }>
```

```ts
interface Team {
  id: string
  name: string
  createdByUser: string
  createdAt: number
  updatedAt: number
}
```

### `teamDelete`

```ts
async teamDelete({ teamId }: { teamId: string }): Promise<{ success: boolean; team?: Team; error?: string }>
```

```ts
interface Team {
  id: string
  name: string
  createdByUser: string
  createdAt: number
  updatedAt: number
}
```

### `teamUserList`

```ts
async teamUserList({ teamId }: { teamId: string }): Promise<{ success: boolean; users?: User[]; error?: string }>
```

```ts
interface User {
  id: string
  email: string
  name: string
  createdAt: number
  updatedAt: number
}
```

### `teamUserAdd`

```ts
async teamUserAdd({ teamId, userId }: { teamId: string; userId: string }): Promise<{ success: boolean; error?: string }>
```

```ts
interface User {
  id: string
  email: string
  name: string
  createdAt: number
  updatedAt: number
}
```

### `teamUserRemove`

```ts
async teamUserRemove({ teamId, userId }: { teamId: string; userId: string }): Promise<{ success: boolean; error?: string }>
```

### `run`

This is a do-all method that will:

- `create`
- `update`
- `warmUp`
- `run`
- `delete`

```ts
async 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()`.

### `stream`

```ts
async stream({
  accessToken,
  onOutput,
  onProblem,
  onFinish,
}: {
  accessToken: string
  onOutput?: (data: Output) => void
  onProblem?: (data: Problem) => void
  onFinish?: (data: Finished) => void
}): Promise<void>
```

```ts
type ManagerMsgOutput = {
  type: 'OUTPUT'
  pulseId: string
  output: Output
}
```

```ts
interface Output {
  pulseId: string
  time: Date
  source: string // "manager", "controller", "vm", "agent", "app"
  stream: string // "stdout" or "stderr"
  data: string
}
```

```ts
interface Problem {
  pulseId: string
  time: Date
  source: string // "manager", "controller", "vm", "agent", "app"
  message: string
  stack?: string
}
```

```ts
export interface Finished {
  pulseId: string
  code: number | null
  stats: AgentStats | null
}
```

```ts
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:

```ts
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)}%`)
    }
  },
})
```