Kryon for Dart
Powerful terminal execution, everywhere.
Run operating-system commands, stream their output, and manage the processes behind them — with an API designed so the dangerous thing is the one you have to ask for by name.
This is the Dart SDK of Kryon. Zero runtime
dependencies — it orchestrates dart:io and nothing else.
1.0.0. Command execution and process streaming are implemented and pass the cross-language conformance corpus on Linux, macOS and Windows. PTY, terminal emulation and remote transports are specified but not implemented.
Install
dependencies:
kryon: ^1.0.0
dart pub add kryon
Requires Dart 3.0 or newer.
Run something
import 'dart:convert';
import 'package:kryon/kryon.dart';
final runtime = Runtime(const ExecutionOptions(
encoding: utf8,
timeout: Duration(seconds: 30),
));
final result = await runtime.execute('git', ['status', '--porcelain']);
print(result.stdoutText);
print('${result.exitCode} ${result.ok} ${result.duration}');
Talk to something
final proc = await runtime.spawn('dart', ['run', 'worker.dart']);
try {
proc.write('job-1\n');
await proc.closeStdin();
await for (final chunk in proc.output) {
stdout.write(utf8.decode(chunk.data, allowMalformed: true));
}
final result = await proc.wait();
print('exit ${result.exitCode}');
} finally {
// Leaving without this leaks the child. Always close in a finally.
await proc.close();
}
Two things worth knowing
Arguments are never interpreted
await runtime.execute('wc', ['-l', userInput]); // safe, whatever userInput is
await runtime.executeShell('wc -l $userInput'); // command injection
execute passes an argument vector to the operating system. No shell is involved, so nothing in
an argument can expand, glob, chain or substitute. Shell semantics live behind executeShell — a
separate method name, not a runInShell: true flag, because a boolean among a dozen options
is easy to set by accident and easy to miss in review.
Kryon is not a sandbox
Timeouts and output caps manage resources. They do not contain a hostile program. Isolation is a container, a VM, or an unprivileged account. See the threat model.
API
Runtime([ExecutionOptions defaults])
Holds default options; safe to share. Every call may override them with a third positional
ExecutionOptions. env merges with the runtime's env; everything else is replaced, and a
boolean set explicitly to false really does turn a default off.
| Option | Default | Meaning |
|---|---|---|
cwd |
inherited | Working directory. A path that is not a directory is an error, never a silent fallback. |
env |
{} |
Variables merged over the inherited environment. null removes one. |
clearEnv |
false |
Start from an empty environment. With env, this is an allowlist. |
stdin |
— | Data written to stdin, after which stdin is closed. |
timeout |
— | A Duration. On expiry: terminate, wait killGrace, kill. |
maxOutputBytes |
— | Per-stream cap, enforced during the flood. |
encoding |
— | Set it for text output, leave it for bytes. |
check |
false |
Throw on an unsuccessful result. |
killGrace |
5s |
Between the polite stop and the forced kill. |
ExecutionResult
executable, arguments, exitCode, signal, stdout, stderr, duration, termination,
pid, stdoutTruncated, stderrTruncated, plus ok, stdoutText, stderrText and
checked().
termination is exited, signaled, timeout, cancelled or outputLimit. The
Kryon-initiated reasons win over the kernel's account: a process killed for exceeding its timeout
reports timeout, because that is what you need in order to decide whether to retry.
KryonProcess
pid, running, exitCode, write(), closeStdin(), output, signal(), terminate(),
kill(), wait(), close().
output is a single-subscription Stream<OutputChunk> with real backpressure: pause it and
Kryon pauses the underlying pipes, so the child blocks instead of your heap growing.
Errors
The rule: failing to start is an error, failing while running is a result.
CommandNotFoundException, PermissionDeniedException, ProcessStartFailedException and
InvalidArgumentsException throw — no process ran. ProcessFailedException,
ProcessTimeoutException, ProcessCancelledException and ResourceLimitExceededException throw
only under check: true, and each carries the ExecutionResult it came from. All implement
KryonException.
A naming note
The output-stream enum is OutputStream, not Stream. A package that exports a type called
Stream shadows dart:async's Stream for everyone who imports it, which is a hostile thing to
do to your users. The specification fixes semantics, not spelling.
Platform notes
| Linux | macOS | Windows | Android | iOS | |
|---|---|---|---|---|---|
execute / spawn |
Yes | Yes | Yes | Planned | Not possible |
signal() |
Yes | Yes | UnsupportedPlatformException |
Planned | — |
terminate() |
SIGTERM |
SIGTERM |
TerminateProcess — no graceful stop |
Planned | — |
iOS does not permit an application to spawn arbitrary child processes. That is a platform rule, not a missing feature; the correct architecture there is a remote transport to a server that does the executing.
Flutter
This package is pure Dart and has no Flutter dependency. A kryon_flutter package for terminal
rendering does not exist and will not be created until it has a clear purpose beyond existing.
Develop
cd dart
dart pub get
dart analyze
dart test
dart run example/kryon_example.dart
Tests drive a small helper program rather than real system commands, so they behave the same on every platform and touch nothing outside a temporary directory.
License
Apache-2.0. See LICENSE.
If Kryon saves you time, you can buy me a coffee.
Libraries
- kryon
- Kryon -- powerful terminal execution, everywhere.