Skip to content
Masko logomasko
Docs
Documentation

TypeScript and Electron

Control a floating macOS desktop mascot from Electron main or Node.js. The SDK connects to the same Swift renderer as the CLI and displays the mascot in a separate window above other windows. It currently has no component for placing the mascot inside an Electron window, browser page, or React layout.

Install

Install the TypeScript SDK from npm:

npm install @masko/sdk

The standalone SDK supports Node.js 20+. The renderer requires macOS 14+. Install it using the terminal setup, or bundle it with your Electron app as described below.

Show a mascot

In Electron main, wait for the app to be ready:

import { app } from 'electron';
import { Masko } from '@masko/sdk';

await app.whenReady();
const ko = await Masko.mascot('ko', { size: 180 });
await ko.show();
await ko.state('working');
const play = await ko.play('celebrate');
// Optional: wait before continuing a completion-dependent step.
await ko.waitForPlay(play.play_id);

// When your feature or app closes:
await ko.dispose();

For Node.js, omit the Electron import and app.whenReady(). Keep the handle while the mascot is needed. Masko.mascot() starts or connects to the renderer automatically. Each call creates an independent instance, even if you use the same friendly name.

show(), state(), play() and prepare() return acceptance receipts immediately. Loading and animation continue in the host. Requests are ordered, so state/play can follow show while loading. Use ko.waitForRequest(receipt.request_id) for the first frame, state preparation/application, or full media preparation; ko.waitForPlay(receipt.play_id) waits through the whole performance and return. Public Ko requires no login. For your own hosted character in trusted local tooling, use const client = Masko.client(); const bob = await client.mascot('acme/bob', { canvas: 'gaming' }). Ordinary Masko.mascot() defaults to anonymous access; it does not borrow your terminal login. Omit canvas for the default. No release is needed: show prepares the complete canvas with your workspace login and never generates media. Use { version: 3 } instead to pin a snapshot. Dispose and recreate a handle to load later edits.

Control and cleanup

CallResult
ko.state('working')Hold a state.
ko.clearState()Release the held state; an active play finishes before returning to default.
ko.play('celebrate')Perform a reaction, then return.
ko.play('celebrate', { instant: true })Skip entry transitions; play the performance and its return path.
ko.startPlay('celebrate')Alias for play(), returning an acceptance receipt.
ko.waitForRequest(id) / ko.waitForPlay(id)Explicitly wait for preparation/application or full playback.
ko.cancel()Follow the authored return path.
ko.resize(180)Set size in screen points, from 64 to 720.
ko.position(400, 100)Move using bottom-left screen coordinates.
ko.hide() / ko.show()Pause while hidden, then resume.
ko.prepare()Download all graph media.
ko.status()Inspect current state, capabilities and rendering status.
ko.dispose()Permanently remove this instance. Safe to call again.

These methods return promises. Await cleanup before quitting Electron. Disposing a mascot does not stop the host or affect another app’s instances. A crash can leave an instance running until the user closes it.

Timeouts and errors

Control methods accept { signal, timeoutMs }. Ordinary requests default to 120 seconds; waitForRequest() and waitForPlay() wait up to five minutes. Timeout or abort stops waiting, but accepted work may continue. Inspect (await ko.status()).requests for background failures and recent request progress before retrying; use cancel() or dispose() when appropriate.

Errors are MaskoError objects with code, message, and optional details. Handle busy, timeout, aborted, disposed, instance_not_found, host_unavailable, and unsupported_platform. If the user closes the mascot, only an explicit show() recreates its window.

Bundle the renderer

Build the native app with ./scripts/package.sh. Include the whole Masko Runtime.app bundle outside app.asar:

Your App.app/Contents/Resources/Masko Runtime.app

The SDK discovers that resource automatically and uses a host isolated from the CLI and other runtime installations. A replacement runtime gets a fresh host, so an older running CLI host does not block the app. With this layout, users do not need a separate Masko installation. The bundle contains the renderer; character media still downloads from the hosted version.

Local builds use the build Mac’s architecture and ad-hoc signing. Public distribution requires signing and notarizing both the nested runtime and your app. The runtime’s optional private SkyLight pinning is not suitable for Mac App Store distribution.

Connect your app’s UI

Keep the SDK in Electron main. Expose a small, validated preload bridge for your specific actions; do not pass arbitrary shell commands or privileged SDK access to renderer content. Keep authoring credentials out of shipped apps. Use public versions for anonymous delivery, or customer mascot access for private mascots: your backend checks the customer and returns a temporary URL, then the app calls await Masko.mascot(mascotUrl). Customers need no Masko account or publishable key.

The Electron example includes two independent mascots, a typed bridge, cleanup during quit, and bundled-runtime packaging. The SDK README also covers explicit connections and custom host settings.

Account and character creation

Use Masko.client() for the same auth, account, authoring, jobs and assets available in the CLI and Swift SDK:

const client = Masko.client(); // Uses the saved login for this backend.
const balance = await client.credits();
const bob = client.character('acme/bob');
const proposal = await bob.add('celebrate', { description: 'Throws confetti' });
// Review the proposal before applying it.
await bob.graph.apply(proposal);

client.login() uses browser authorization; client.login({ apiKey }) saves an API key. For your own login UI, pass noBrowser: true and onAuthorization. Character methods cover creation, prompts, reference images, graph editing, plans, generation and release. client.jobs and client.assets cover watching, retry and downloads. client.mascot(...) uses the same delivery context.

These cloud methods use the native CLI's versioned JSON bridge to the public v1 API. The matching CLI must be installed or bundled; this SDK currently requires macOS for both runtime and cloud operations. Keep it in Electron main, and keep personal authoring credentials out of distributed apps.

Watch generation

client.generate(planID, { maxCredits: 100 }) and bob.generate('animations', { maxCredits: 100 }) return a receipt with run_id. They start paid work but do not wait for the videos. Persist that ID, then watch separately:

const run = await client.jobs.watch('RUN_ID', {
  cursor: 0,
  onEvent(event) {
    if (typeof event.message === 'string') console.log(event.message);
    // Persist event.sequence to resume from this cursor after a disconnect.
  }
});
console.log(run.status, run.readiness);
const outputs = await client.assets.list({ run: 'RUN_ID' });

Watching spends no credits and can be restarted. Events distinguish source-video generation, background removal/WebM, and HEVC export. Image-only runs can complete with awaiting_animations; only readiness: 'ready' confirms a playable result. A failed or partially failed run rejects with generation_failed; inspect it with client.jobs.show('RUN_ID') before retrying.

A new show against incomplete media fails in the background with version_not_ready. Wait for generation to finish and issue show again; use waitForRequest() to observe the loading result. Existing instances keep playing their loaded snapshot throughout generation. Dispose and recreate them to load the completed edits.