Arcane OS Docs Development only

Reference

Arcane.terminal.start()

Starts one of at most eight app-owned native terminal sessions. Requires terminal.execute, app id terminal, and a Core or Android host.

  • Reference

This focused page is derived from the mechanically checked full member inventory.

Syntax

Arcane.terminal.start(options?)

Parameters

options?: {shell="auto", cwd="", columns=120, rows=32}; shell is auto, powershell, cmd, bash, or sh; columns 20–500; rows 5–200

Return value

Promise<{id,shell,cwd,title,columns,rows,createdAt}>

Description

Starts one of at most eight app-owned native terminal sessions. Requires terminal.execute, app id terminal, and a Core or Android host.

Overview

Arcane.terminal.start(options?) starts one native terminal session owned by the bound Arcane Terminal application. It is a high-risk, non-idempotent process start. The host admits no more than eight concurrent sessions.

The method is available only when terminal.start is projected, the application id is terminal, the terminal.execute grant is present, and terminal.start appears in the admitted method list. A browser preview cannot start an operating system process.

Subscribe to terminal events before calling start(). The host begins forwarding process streams as part of startup, so an early output event may arrive before application code receives the resolved session object. Buffer events by sessionId until the start result identifies the desired session.

Options

The optional options object has four normalized fields:

Field Type Default Contract
shell string "auto" One of auto, powershell, cmd, bash, or sh, subject to host availability; at most 16 characters.
cwd string "" Existing accessible working directory, or the host default when empty; at most 4096 characters and subject to the host sandbox.
columns safe integer 120 From 20 through 500.
rows safe integer 32 From 5 through 200.

The JavaScript wrapper sends only these four fields and converts the supplied values to their documented string or number forms. Values outside the checked bounds are rejected; do not rely on host clamping.

Resolved session

The method resolves to:

const session = {
    id: 'term-example',
    shell: 'powershell',
    cwd: '<resolved working directory>',
    title: 'PowerShell',
    columns: 120,
    rows: 32,
    createdAt: '2026-08-15T12:00:00.000Z'
};
Property Type Description
id string Opaque session identifier matching the Arcane session-id contract and no longer than 128 characters.
shell string Resolved shell selected by the host.
cwd string Resolved working directory.
title string Host-provided display title, at most 80 characters.
columns number Accepted column count.
rows number Accepted row count.
createdAt string Host creation timestamp.

Events

After startup, observe these future-only events:

  • terminal.output carries { sessionId, stream, data } for stdout and stderr chunks. Chunks are not lines and may contain terminal control sequences.
  • terminal.exit carries { sessionId, exitCode, signal } after the process exits and the host retires the session.
  • terminal.error carries { sessionId, message } for asynchronous host stream failures. The Android provider also uses it when a session exceeds its output limit.

Store every returned unsubscribe function and call it during teardown. The Arcane event catalog defines the complete event payloads.

Platform differences

On Microsoft NT Core hosts, auto resolves to PowerShell. powershell, cmd, and an installed bash are selectable; sh is unavailable. On Linux Core hosts, auto resolves to Bash, sh selects /bin/sh, powershell requires an installed pwsh, and cmd is unavailable.

On Android, only auto and sh are accepted and both resolve to the application-sandbox /system/bin/sh. The working directory must stay inside the application's private files area. Android runs the process as the ordinary Arcane Terminal app identity and stops a session after one MiB of emitted output.

Errors and recovery

Code Meaning and recovery
METHOD_NOT_ALLOWED The bound application or grant is wrong. Open the admitted Arcane Terminal application; do not retry from another app.
ARCANE_TRANSPORT_UNAVAILABLE Open Arcane Terminal through an installed or development Arcane host.
METHOD_CONTRACT_INPUT_INVALID or TERMINAL_REQUEST_INVALID Use only the documented fields, value types, and bounds.
TERMINAL_SESSION_LIMIT Close an existing session before retrying.
TERMINAL_SHELL_INVALID Use one of the five documented shell names.
TERMINAL_SHELL_UNAVAILABLE Select a shell supported and installed on the active platform.
TERMINAL_CWD_INVALID Choose an existing accessible directory allowed by the current host sandbox.
TERMINAL_START_FAILED Verify that the resolved shell executable is installed and can start, then retry.

Example

This complete example subscribes before startup, buffers early output, uses the session methods, requests closure, observes process exit, and releases every listener.

const arcane = globalThis.Arcane;
const terminal = arcane?.terminal;
const events = arcane?.events;

if (!terminal?.start || !events?.on || !arcane?.capabilities?.list) {
    throw new Error('Open Arcane Terminal through an admitted Arcane host.');
}

const access = await arcane.capabilities.list();
const requiredMethods = [
    'terminal.start',
    'terminal.list',
    'terminal.write',
    'terminal.resize',
    'terminal.close'
];

if (
    access.app?.id !== 'terminal'
    || !access.grants.includes('terminal.execute')
    || !requiredMethods.every(function isRequiredTerminalMethodAdmitted(method) {
        return access.methods.includes(method);
    })
) {
    throw new Error('This application is not admitted for terminal execution.');
}

const bufferedOutput = new Map();
const observedExits = new Map();
const exitWaiters = new Map();
const maxBufferedChunksPerSession = 128;
let session = null;
let activeSessionId = null;

function render({stream, data}) {
    const write = stream === 'stderr' ? console.error : console.log;
    write(data);
}

const offOutput = events.on('terminal.output', function handleTerminalOutput(payload) {
    if (payload.sessionId === activeSessionId) {
        render(payload);
        return;
    }
    const pending = bufferedOutput.get(payload.sessionId) ?? [];
    pending.push(payload);
    // Bound pre-identification buffering; surface truncation in a real UI.
    if (pending.length > maxBufferedChunksPerSession) {
        pending.shift();
    }
    bufferedOutput.set(payload.sessionId, pending);
});

const offExit = events.on('terminal.exit', function handleTerminalExit(payload) {
    observedExits.set(payload.sessionId, payload);
    const waiter = exitWaiters.get(payload.sessionId);
    if (waiter) {
        clearTimeout(waiter.timer);
        exitWaiters.delete(payload.sessionId);
        waiter.resolve(payload);
    }
});

const offError = events.on('terminal.error', function handleTerminalError(payload) {
    console.error(`Terminal ${payload.sessionId}: ${payload.message}`);
});

function waitForExit(sessionId, timeoutMs = 5000) {
    if (observedExits.has(sessionId)) {
        return Promise.resolve(observedExits.get(sessionId));
    }
    return new Promise(function createExitWait(resolve, reject) {
        const timer = setTimeout(function rejectTimedOutExitWait() {
            exitWaiters.delete(sessionId);
            reject(new Error(`Timed out waiting for ${sessionId} to exit.`));
        }, timeoutMs);
        exitWaiters.set(sessionId, {resolve, timer});
    });
}

try {
    session = await terminal.start({
        shell: 'auto',
        cwd: '',
        columns: 120,
        rows: 32
    });
    activeSessionId = session.id;

    for (const payload of bufferedOutput.get(session.id) ?? []) {
        render(payload);
    }
    bufferedOutput.delete(session.id);

    await terminal.resize(session.id, 100, 30);

    const {sessions} = await terminal.list();
    console.log('Owned sessions', sessions);

    const lineEnding = ['powershell', 'cmd'].includes(session.shell)
        ? '\r\n'
        : '\n';
    const writeResult = await terminal.write(
        session.id,
        `echo Arcane terminal ready${lineEnding}`
    );
    console.log('Accepted input bytes', writeResult.bytes);

    const exitPromise = waitForExit(session.id);
    const closeResult = await terminal.close(session.id);
    console.log('Close request accepted', closeResult.accepted);

    const exit = await exitPromise;
    console.log('Process exit', exit.exitCode, exit.signal);
} catch (error) {
    if (error instanceof arcane.Error) {
        console.error(error.code, error.message, error.resolution);
    } else {
        throw error;
    }
} finally {
    if (session && !observedExits.has(session.id)) {
        await terminal.close(session.id).catch(function ignoreTerminalCloseFailure() {});
    }
    offOutput();
    offExit();
    offError();
    for (const waiter of exitWaiters.values()) {
        clearTimeout(waiter.timer);
    }
    exitWaiters.clear();
}

Reference group

Applications, terminal, and capabilities

Repository and reviewed source access