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.
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.outputcarries{ sessionId, stream, data }for stdout and stderr chunks. Chunks are not lines and may contain terminal control sequences.terminal.exitcarries{ sessionId, exitCode, signal }after the process exits and the host retires the session.terminal.errorcarries{ 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();
}