tan is the standalone build CLI for Alp Lab E1M and E1M-X projects. It reads
hardware metadata from an alp-sdk
checkout, creates build plans, and runs the tools needed to build, inspect,
flash, and debug firmware. VS Code is optional. The implementation is Python.
Prerequisites. On Linux and macOS the installer needs a downloader --
curl or wget, either one -- plus tar and sha256sum (macOS:
shasum). Nothing else. tar and the digest tool are already present on a
stock Debian/Ubuntu and on macOS; a downloader is not. A pristine
ubuntu:24.04 has neither curl nor wget, so the command below fails there
with bash: curl: command not found until you install one:
sudo apt-get update && sudo apt-get install -y curl # or: wgetThat also pulls ca-certificates, which ubuntu:24.04 does not ship either
and which the download needs. On Windows, install.ps1 uses only PowerShell
built-ins, so there is nothing to install first.
Linux and macOS:
curl -fsSL https://raw.githubusercontent.com/alplabai/tan-cli/main/install.sh | shThe same install with wget, if that is the downloader the host has:
wget -qO- https://raw.githubusercontent.com/alplabai/tan-cli/main/install.sh | shWindows PowerShell:
irm https://raw.githubusercontent.com/alplabai/tan-cli/main/install.ps1 | iexThe installers download the release for your platform, verify its SHA-256
digest, and install it for the current user. Open a new terminal if tan is not
immediately on PATH.
That prerequisite list is the whole of it, and it is deliberately shorter than
the one a build needs: the release asset is a self-contained freeze, so the
installed tan runs on a host with no python3, no git and no compiler --
tan --version and tan doctor both work there. Building firmware needs more;
see What a build needs below.
For a system-wide install, pass --system (Unix) or -System (Windows)
through to the script -- piping straight into sh or iex swallows a bare
--system/-System before the installer ever sees it:
curl -fsSL https://raw.githubusercontent.com/alplabai/tan-cli/main/install.sh | sh -s -- --system&([scriptblock]::Create((irm https://raw.githubusercontent.com/alplabai/tan-cli/main/install.ps1))) -SystemSee docs/release-contract.md for asset names,
manual verification, and OS support.
Every release from v0.5.0 on publishes four archives:
- Windows x64
- Linux x64 with glibc
- macOS x64
- macOS arm64
Linux arm64, Linux musl, and Windows arm64 do not have prebuilt archives. Install from source on those hosts.
Python 3.12 or newer is required. Install into a virtual environment, not the
system interpreter: on a PEP 668 host (Debian/Ubuntu, including stock
ubuntu:24.04) a bare python3 -m pip install ./python refuses with
error: externally-managed-environment instead of installing anywhere.
Debian/Ubuntu's python3 package also does not include venv itself --
python3 -m venv fails there until python3-venv is installed:
sudo apt-get install -y python3-venv # Debian/Ubuntu only
git clone https://github.com/alplabai/tan-cli
cd tan-cli
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1
python3 -m pip install ./python
tan --versionFor serial monitoring, install the optional dependency from the checkout:
python3 -m pip install "./python[monitor]"tan is not on PyPI yet: release.yml's publish_pypi job exists and is
opt-in, off until the repository variable TAN_PYPI_PUBLISH is true and a
pending publisher is configured on pypi.org (tan-cli#1054). PyPI is the
intended standalone channel; the npm shim that used to be advertised here is
retired, and the alp-tan-cli crate on crates.io is a stale v0.4-era Rust CLI,
no longer built from this repository and not the current program. Until the
first upload lands, use a GitHub release or a source checkout.
Getting tan onto the host and building firmware with it are different sets
of tools, and only the first one is short. A build needs, on PATH:
- Linux:
git,cmake,python3,ninja,xz,wget. - macOS:
git,cmake,python3,ninja. - Windows:
git,cmake,python,ninja.
Beyond that list: on Debian/Ubuntu tan bootstrap cannot create its workspace
virtual environment until python3-venv is installed. On native Windows, west sdk install needs a 7-Zip-compatible archive tool on PATH instead -- west
delegates .7z extraction to patoolib, which shells out to an external
7z/7za/7zr/7zz/7zzs/unar binary and has no pure-Python fallback
(winget install -e --id 7zip.7zip). tan doctor has a dedicated sevenZip
check for this, on every native-Windows host: it warns when none of those
binaries is on PATH and names the same winget command. It does not wait for
the Zephyr SDK to be missing first — a host that already has the SDK and no
7-Zip is exactly the host whose next west sdk install dies with
Zephyr SDK setup requires '7z' (tan-cli#736). tan bootstrap's own
cross-toolchain phase (below) extracts the Windows .7z archives itself and
checks for a 7-Zip before downloading anything, refusing cleanly instead.
Do not assemble any of these lists by hand. tan doctor reads its checks from
the SDK's own metadata/bootstrap.json, so it stays correct when the SDK
changes it, and it names what is missing on this host -- Windows included --
together with the command that fixes it:
bootstrap.prerequisites-missing: missing from PATH: cmake, ninja
doctor.zephyr-sdk: Zephyr SDK toolchain not detected (ZEPHYR_SDK_INSTALL_DIR unset)
-- from an initialised west workspace, run `west sdk install`
doctor.west-resolved: west resolved neither through the workspace venv nor PATH
-- no build slice can be executed. Run `tan bootstrap`
tan doctor needs none of those tools itself, so run it first, on the bare
host, rather than guessing -- this holds on Windows exactly as it does on
Linux and macOS.
Start in an empty working directory:
git clone https://github.com/alplabai/alp-sdk
tan bootstrap --sdk-root ./alp-sdk
tan init --name my-app
cd my-app
tan validate
tan build
tan size
tan run --flash --confirmWhat those commands do:
-
bootstrapprepares west, Zephyr, the Python environment and SDK dependencies into a workspace venv next to the SDK checkout -- which for the Quickstart's own invocation means./.venv, notalp-workspace/.venv:west init -l ./alp-sdkforces the west topdir to be the checkout's PARENT, and starting in an empty directory makes that parent the current directory.alp-workspace/appears only in the two cases further down: you pass--workspace, or the directory held something besides the checkout andbootstraprelocated it there for you.bootstrapthen, as its final phase (ADR 0021 Lane 1 P1), acquires the Zephyr SDK cross-toolchain (arm-zephyr-eabi) thattan buildneeds for real silicon, into the artifact-keyed store~/.alp/toolchains/zephyr-sdk-<version>-arm-zephyr-eabi/(or$ALP_TOOLCHAIN_ROOT, shared across every project pinning that same version). It reads the version to install from the SDK checkout's ownmetadata/toolchains.json, so a pin bump there reaches you on your nexttan bootstrapwith notanupgrade needed. A secondtan bootstrapagainst the same pin is near-instant: it probes the existing install's compiler and re-verifies its stamp rather than reinstalling. Pass--no-toolchainto skip this phase (the rest ofbootstrapis unaffected, andnative_simbuilds never need a cross toolchain at all). What the phase verifies (tan-cli#1496, tan-cli#1556):tandownloads both pinned archives itself -- the SDK's minimal bundle and thearm-zephyr-eabitoolchain -- and hashes each against alp-sdk's sha256 pin (metadata/toolchains.json) while it streams. Nothing is re-hashed from disk afterwards. A hash mismatch refuses withbootstrap.toolchain-pin-mismatch-- and only then doestanread the release's ownsha256.sum, to say what it is consistent with (an upstream republish, or bytes changed in transit) -- as evidence to check on the release page, never as a reason to move the pin (tan-cli#1559); the sum never blocks an install on its own. An archive that cannot be fetched (offline, a SOCKS proxy) refuses withbootstrap.toolchain-pin-unverified; both stamp nothing; a pinned archive upstream does not serve (HTTP 404/410) refuses withbootstrap.toolchain-installand is not retried. The stamp records what was checked: an install by atanbefore tan-cli#1556 is reported as having had only its toolchain archive pin-checked, and one before tan-cli#1496 as never compared with the pin.tanno longer runswest sdk installfor this. West ignored--install-dirwhenever CMake could find the pinned SDK version anywhere it searches (your home directory, abindirectory onPATH, the CMake package registry), and it registered tan's temporary install directory in that registry. It also listed releases through the rate-limited GitHub API. None of that happens now: the phase needs no GitHub token, writes no CMake package-registry entry, and removes the dead entries oldertanversions left (tan-cli#1553). It also installs nothing from the SDK'shosttools/bundle, so it needs nofileon PATH; Zephyr treatsdtcandopenocdas optional and a real-silicon build needs neither, but if you want them, run the manual command below (and then you do needfile). -
If you skip the toolchain phase, or need to point at a different pin, run
west sdk installby hand from inside the workspace venv:source .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1 export ZEPHYR_BASE="$PWD/zephyr" west sdk install --version 1.0.1 -t arm-zephyr-eabi
the exact command
tan doctor'szephyrSdkcheck also names, so it stays correct if that pin ever moves. On a minimal Linux host this also needsfileon PATH (Debian/Ubuntu:sudo apt-get install -y file); without it the SDK's own host-tools step fails with "Host tools installation failed" and names nothing. alp-sdk'smetadata/bootstrap.json(manualInstallHints.posix.note[2]) calls a missingfile"WARN-only, not a bootstrap.sh prerequisite", and both statements are true: that note is written for the--no-hosttoolsinvocation in its ownnote[0], which never runs the host-tools step. The command above installs host tools, so it needsfile. Add--no-hosttoolsand it does not.Those two paths are the Quickstart's own layout -- an empty starting directory and no
--workspace, so the west topdir is the current directory. Do not hand-adjust them for a different layout: asktan.tan bootstrapprints the activate andZEPHYR_BASElines for the workspace it actually created, andtan bootstrap --print-envreprints them at any time, on stdout so the block can be redirected into a file. Note that the venv-activation line it emits is a comment: sourcing that output setsZEPHYR_BASEandZEPHYR_TOOLCHAIN_VARIANTbut does NOT activate the venv, so run thesource ...line yourself as well. Reach for it if you passed--workspace <path>, whose venv is<path>/.venvand Zephyr<path>/zephyr, or ifbootstraprelocated the checkout intoalp-workspace/because the starting directory held something besides the checkout, whose venv is thenalp-workspace/.venv.tan buildandtan doctoralways use the resolved west workspace'szephyr/; anenv ZEPHYR_BASE=<other tree>override is ignored. When it names a different tree they say so (build.zephyr-base-ignored, info; thezephyrBasedoctor check). To build against another tree, runtanfrom that workspace. -
initcreates a Zephyr application and pins the SDK checkout in.alp/sdk-path. -
validatechecksboard.yamland related metadata. -
buildplans, materialises, and builds every core slice. -
sizereports firmware use against the SoM memory budget. -
run --flashbuilds and then runs or programs the selected target. On a hardware target (a native_sim/host target always just runs),--flashalone only previews the write: every slice comes backplanned, nothing reaches the device, and the run exits non-zero naming the remedy. Add--confirm(as above) to actually arm the write, or setALP_FLASH_FORCE=1in the environment, orflash_args.confirm: truein the manifest -- the same three-way gatetan flash --confirmalready has. This is deliberate, not a bug: a fresh checkout must not silently reprogram an attached module. On an Alif Flow D slice (alif_mram_jlink, the AEN MRAM path) the write also needs--atoc-unqueryable, a separate acknowledgement that it replaces the board's entire ATOC. A Flow A slice (zephyr_west_flashon thealif_flashrunner, over the SE-UART) is checked by the runner itself, which refuses to delist a resident entry;--replace-atocoverrides that check, and is never the same flag -- see SETOOLS setup.
Run tan doctor if setup or toolchain discovery fails; its toolchain check
reports stamp-vs-pin for the resolved project (a version-skewed or
never-verified install is a fail naming tan bootstrap, never a silent
"looks present"), and its zephyrSdk check answers the broader "does any
toolchain exist on this host at all" question, unconditionally. tan doctor --fix installs missing prerequisites, but only at a real, interactive
terminal -- it is a no-op (exit 4) under a pipe, a redirect, or CI, so it is
not a scripted-onboarding remedy. It never spawns sudo itself: it runs a
prerequisite's manifest install command directly when already root, and
otherwise prints the exact command to run by hand.
If you do not want the west workspace next to the SDK checkout, choose it explicitly:
tan bootstrap --sdk-root ./alp-sdk --workspace /path/to/alp-workspace--workspace does not simply relocate where the workspace metadata is
written: the west topdir is always the checkout's parent, so this moves
the alp-sdk checkout itself to /path/to/alp-workspace/alp-sdk and updates
the machine-global ~/.alp/sdk-default pointer to it. Run this before
anything else that references --sdk-root ./alp-sdk by its old path, or
those calls stop resolving; if a project's .alp/sdk-path already pins the
old location, re-run tan init/tan bootstrap from that project after the
move.
| Task | Command |
|---|---|
| Create a project | tan init --name my-app |
| Check a project | tan validate |
| Build firmware | tan build |
| Build and run or flash | tan run --flash --confirm (--confirm arms the write on a hardware target; see the quickstart) |
| Flash an existing build | tan flash --confirm (an Alif Flow D slice also needs --atoc-unqueryable, which acknowledges the whole-ATOC replacement rather than arming the write; a Flow A slice refused by the alif_flash runner's ATOC guard takes --replace-atoc instead — see SETOOLS setup) |
| RAM-run an AEN image over J-Link (no MRAM write) | tan flash --ram --core m55_he --confirm (see the note below on DEMCR.TRCENA) |
| Read the attached SW-DP / core, or a few words of memory (read-only J-Link) | tan probe identify, tan probe read 0x80010000 4 (see tan probe) |
| Pulse nRESET once through J-Link (no flash, no halt) | tan reset --probe-usb-path 3-4.2 (see tan reset) |
| Inspect firmware size | tan size |
| Create an image | tan image |
| Remove build output | tan clean |
| Generate configuration files | tan generate |
| Check the host setup | tan doctor |
| Start a serial monitor, or record one headlessly with input and baud change | tan monitor, tan monitor --capture --port rfc2217://gw:4001 --send v --reopen-at 23040 --on STOP (see monitor capture) |
| Generate debugger settings | tan debug-config |
| List examples and presets | tan examples, tan presets |
| Explain resolved project settings | tan inspect |
| Explain a template or generation target | tan explain |
| Show help | tan <command> --help |
When a project's build/system-manifest.yaml names more than one debug
target class (e.g. a yocto A-cluster slice beside a zephyr M-core slice),
debug-config exits 2 with debug-config.target-kind-ambiguous; pass
--target-kind <zephyr-mcu|baremetal-mcu|yocto-userspace|native-host>, or
--core <core_id> to narrow to one slice. A multi-core SoM whose cores all
share one target class (e.g. two Zephyr cores) is unaffected -- the classes
fold to one and no flag is needed.
tan --help is the authoritative list. Beyond the commands above, the surface
includes bootstrap, doctor, validate, generate, examples, sdk,
monitor, probe, reset, scaffold, completion, diff, pinmux,
inspect, trace, support-bundle, kconfig, faultdecode, model, and
new-som. tan probe identify / tan probe read ADDR [WORDS] is a read-only
J-Link inspector (it never writes, erases, halts, resets or runs). migrate, lock, and quality forward to their
corresponding west alp-* commands: migrate requires --check,
--preview, or --apply; quality requires --profile. The other
commands run directly in tan.
tan model has nine subcommands: build (compile board.yaml models: into
.alpmodel packages), doctor (NPU-compiler toolchain availability), check,
list (declared models next to what is already built), zoo (the SDK's model
zoo; --sku filters to a SoM), add <id> (add a zoo model to the project),
prep <model.onnx> (INT8-quantize with an accuracy report), and run <model.onnx> / ab <model.onnx> (time a host reference run, or compare two
models). run and ab take --device to also report the on-device tier,
from --capture FILE or live (a RAM-run of the built diagnostics.link: itcm
project; the live form resets the device and needs --confirm). Run tan model --help for every flag.
tan clean --build-root PATH takes the same PATH as tan build --build-root (the
project tree, relative to the current directory) and removes PATH/build; it
refuses a target that holds a board.yaml.
tan flash, tan size and tan image accept --build-root PATH to read the
system-manifest.yaml from a build root other than <project>/build, the same
root tan build --build-root wrote to.
diagnostics.console: in board.yaml accepts auto, ram, uart and alp;
alp is an alias of uart and produces identical output.
tan model check statically screens a declared model's NPU eligibility with
no NPU toolchain installed. It reports npu-eligible | cpu-certain |
undetermined -- deliberately never fits, which is reserved for a real
compile or a bench measurement. For what those words do and do not claim, and
for the MAC-weighted computeOnNpuPctMax figure, see
docs/model-check-static-screen.md.
tan flash --ram (AEN Flow C) loads an ITCM-linked image over J-Link and runs it
without touching MRAM. When tan's J-Link session ends it clears DEMCR.TRCENA
(bit 24), which stops the DWT cycle counter (CYCCNT) and ITM trace in the
running image about 10 ms after start, while the envelope still reports
ok: true. Firmware that reads CYCCNT or emits trace must set TRCENA again
after start, or the cycle counts it prints are wrong. Every successful --ram
run (and its --dry-run) carries the info issue
flash.ram-debugger-detach-clears-trcena as a reminder.
Experimental (tan-cli#1372, off by default, pending a bench A/B): with --ram-console,
setting TAN_FLASH_RAM_HOLD=1 keeps the load session open for the --wait window
(... go; Sleep <wait_ms>; exit, no halt after go, no DEMCR/DWT write) and the
following ram_console_buf reread session does not wait again. The session timeout
covers the wait, the envelope reports ram.holdSession: true, and the TRCENA info
issue then says the clear happens at session close, after the wait.
--watch <addr>[:<words>][@<period-ms>] (repeatable, --ram only) samples target
memory in the same J-Link session that starts the image, for the --wait window,
for example tan flash --ram --core m55_he --confirm --wait 5 --watch 0x42002000@50.
Reads are mem32 only, spaced by J-Link Sleep <ms> (read-only on memory, but a register read -- FIFO, clear-on-read status, clock-gated block -- may have side effects); words defaults to 1 (max 64)
and the period to 100 ms (10..60000). The samples land in data.watch[] as
{address, words, index, elapsedMs, values}. J-Link prints no timestamps, so
elapsedMs is the scheduled offset after go, not a measurement (each read adds SWD
latency). A watched run does not sleep again before --ram-console. Refused before
any spawn, --dry-run included: an unaligned address, zero or more than 64 words, a
period outside 10..60000 ms, more than 8 watches or 2000 samples
(flash.ram-watch-invalid), and the HP TCM windows 0x50000000..0x57FFFFFF (ITCM + DTCM),
whose read from an HE attach leaves the core unhaltable until a PIN reset
(flash.ram-watch-unsafe-address). Samples missing from the transcript come back
with values: null and a flash.ram-watch-incomplete warning (a distinct message when NO sample returned data). ram.watch.spawnMs is the wall-clock of the whole load J-Link spawn, including connect/halt/loadbin and any --jlink wrapper overhead (the board-farm shim adds about 45 s): an upper bound on the watch span, NOT a drift measure (J-Link prints no timestamps).
For Alif Ensemble MRAM flashing with SETOOLS, see
docs/setools.md.
Most project commands find alp-sdk in this order:
--sdk-root <path>- the project's
.alp/sdk-pathpin - the user's default SDK pointer
- a nearby
alp-sdkcheckout
Step 3 is one pointer shared across every project on the host and is
last-writer-wins: another project's tan bootstrap can repoint it. tan
warns (sdk.global-default-foreign-project) rather than resolving silently;
use --sdk-root or the project pin to be explicit.
Use --sdk-root when more than one checkout is nearby or when you want a
one-off override:
tan build --sdk-root /path/to/alp-sdktan sdk list, tan sdk current and tan sdk remove <version|path> work
today (remove refuses the active or pinned install, or a path outside the
cache root, unless --force). tan sdk install and
tan sdk switch are not implemented yet, so clone the SDK yourself and use
--sdk-root or let tan init write the project pin.
An Alp Platform release (Alp Platform 26.11, full version 26.11.0) is a
dated, bench-validated set of component versions: alp-sdk, tan-cli,
alp-sdk-vscode and the GD32 and CC3501E bridge firmware. Each is defined by one
platform.yaml (schema alp-platform-v1) published as a release asset of
alplabai/alp-platform. Versions are YY.M.MICRO[-rcN] with the month never
zero-padded: 27.2.0 is accepted, 27.02.0 is refused and the message names
27.2.0. The first line is 26.11 (November 2026); a new line ships every one
to two months. Pinning is optional; a workspace without a pin behaves as before.
tan platform use 26.11 # latest GA MICRO of the 26.11 line
tan platform use 26.11.1 # an exact release (an -rcN is named in full)
tan platform use --manifest ./platform.yaml # a local file, no network
tan platform list # published releases, newest first (--include-rc for pre-releases)
tan platform show # the pinned platform and each component's status
tan platform clear # remove the pinlist prints the published releases of alplabai/alp-platform, newest first and
grouped by line, marking the pinned one; with --format json, data is
{"releases": [{"version", "line", "prerelease", "published"}], "pinned": <version or null>}.
A 403 or 404 while that repository is private means a GITHUB_TOKEN with read
access is needed.
use validates the manifest, then writes it byte-for-byte to
<west topdir>/.alp/platform.yaml (<west topdir> is the directory holding
.west/). It refuses an unknown schema, unknown top-level keys, and a
CC3501E gpe_stamp at or below its gpe_floor (flashing such a stamp
permanently bricks the bridge). GITHUB_TOKEN, when set, is sent with the
GitHub requests. use never writes firmware to a board, and never updates tan
itself: tan has no SDK-switching mechanism yet, so when the pinned alp-sdk
differs from the checkout, it reports the version to check out
(git checkout v<version>, then tan bootstrap) as a next step, and reports a
tan mismatch as a warning (platform.tan-off-manifest).
tan doctor reports the pin as data.platform (null when there is no pin)
and a platform check: warn when a component whose installed version tan can
read (alp-sdk, tan-cli) differs from the pin, fail when the pin is
unreadable or invalid. The other components report status: "unknown".
Add --format json for machine-readable output:
tan build --format jsonThe stable top-level envelope is:
{command, ok, exitCode, project, data, issues}
plus an optional sdk object, present only on commands that resolved an
alp-sdk checkout -- absent, never null, when none was resolved.
You do not need a flag for this. tan already treats a run as
non-interactive when stdin or stderr is not a terminal — piped, redirected,
or a CI runner — and --format json settles it on its own, whatever the
terminal looks like. That rule is applied unasked (tan/core/consent.py's
can_prompt, whose not json_mode is an unconditional term). It reads stdin
and stderr and deliberately never stdout — which matters more than it
sounds, because in text mode nothing is written to stdout at all
(tan/env.py). stderr is therefore both the report channel and half the
consent gate: tan doctor --fix 2> log.txt from a real terminal is treated as
unattended and the fix is suppressed (doctor.fix-suppressed), while
> log.txt leaves consent untouched and captures an empty file. Where a
command has a documented default it takes it; where it has none it fails rather
than asking.
--ci and --non-interactive exist as explicit "do not ask me" signals on top
of that, and every registered command parses them:
tan/core/global_flags.py holds the shared spec and injects it into any
command that does not already declare the flag itself. What keeps that true is
split in two. tests/gates/test_global_flags_gate.py fails the build for the
30 commands that reject an unknown option; it cannot speak for lock,
migrate and quality, which register ignore_unknown_options
(west_forward_cmd.py) and would swallow an undeclared flag into the west
passthrough rather than reject it — those three are held by
test_west_forward_command.py's
test_a_leading_global_flag_is_consumed_not_forwarded. They are not ROOT
options either: a leading one is relocated across the subcommand boundary, so
tan --ci doctor and tan doctor --ci are the same run, while a bare
tan --ci with no subcommand is No such option: --ci.
tan build accepts all ten of the shared flags (global_flags.py's
_GLOBAL_FLAG_SPECS) — zero of them refuse the invocation. Three —
--project, --board-yaml, --sdk-root — are already declared by build
itself and pass through untouched. The other seven — --target, --all,
--verbose, --quiet, --no-color, --non-interactive, --ci — are
accepted and dropped, the SAME accept_global_flags mechanism 17 other
commands already use: the oracle's own cli.rs declares them ONLY on the
shared GlobalArgs struct and build's Rust handler never read them either,
so this is not a narrower stand-in for refusing them, it is the identical
oracle behaviour.
Separately, four flags LOCAL to build — never part of the shared set —
still refuse the invocation outright (tan-cli#427): --plan, --manifest,
--manifest-from and --no-auto-bootstrap. Each is RETIRED rather than
deferred or accepted — parsed (never a Click typo error) but refused with
build.flag-retired (exit 2), naming what to do instead directly in the
message: --plan-from (with --materialise/--execute) for --plan, a
native tan build's own build/system-manifest.yaml for --manifest,
opening the file directly for --manifest-from, and running tan bootstrap
yourself for --no-auto-bootstrap — this port has no implicit "run
tan bootstrap on a missing Zephyr workspace" trigger for that one to
disable, and is not building one just to give the flag something to switch
off, so it is retired outright rather than left pending.
--plan-from, --materialise, --native, --execute, --build-root,
--format and --pristine are unaffected by either bucket:
$ tan build --ci --plan-from plan.json --format json
{"command":"build","ok":true,"exitCode":0,"project":{...},
"data":{"schemaVersion":1,...},"issues":[]}
$ tan build --no-auto-bootstrap --format json
{"command":"build","ok":false,"exitCode":2,...,"issues":[{"code":"build.flag-retired",
"severity":"error","message":"`--no-auto-bootstrap` is retired: `tan build` never
bootstraps implicitly, so there is nothing for it to disable -- run `tan bootstrap`
yourself when a workspace needs preparing."}]}So a script that adds --ci to every tan invocation no longer breaks on
build — none of the ten shared flags refuse. Of the four BUILD-local flags
that do refuse, each names itself and says what to run instead:
--no-auto-bootstrap is retired rather than pending (tan-cli#427), because
tan build never bootstraps implicitly. Elsewhere --ci does three separate
things, and only the first is consent:
- Consent, on the two commands that read the flag for it —
doctor --fix(doctor_cmd.py'sfix_allowed) andscaffold(scaffold_cmd.py'sinteractive), both throughcan_prompt. This is the half worth reaching for: it is what stopstan doctor --fixrunning unattendedwinget installs under a pty-allocating runner. It is NOT the whole prompting surface, though —new-somprompts too, gates on stdio alone (stdin_is_tty() and stderr_is_tty(),new_som_cmd.py) anddels both flags unread, so under that same pty-allocating runnertan new-som --ciblocks onNew SoM SKU (E1M-<UPPERCASE> shaped)forever. Pass its required flags there instead. - Colour, which has nothing to do with prompting. Inside
tan/env.py'suse_color(if no_color or ci or no_color_requested())--ciis a second spelling of--no-color, so ondoctorandsize— that helper's only two callers — it changes text output where no prompt was ever possible. It is not a synonym outside it:faultdecodecolours through its own_use_color(no_color)and ignores--ci.--non-interactivechanges colour nowhere. - The refusal message. Where stdio is already non-tty the rule above has
decided first, but
--cistill names itself indoctor'sdoctor.fix-suppressedreason list — anissues[]difference a JSON consumer sees.
Rely on the stdio rule for the consent half; reach for --ci on doctor and
scaffold when you want that refusal regardless of what stdio looks like —
build accepts and drops --ci (and --verbose, --quiet, --no-color,
--target, --all, --non-interactive) identically, doing nothing with any
of them.
alp-sdk-vscode -> tan -> alp-sdk
VS Code UI CLI metadata, schemas, examples, west extensions
alp-sdk: hardware metadata, schemas, examples, and the remainingwest alp-*extensions.tan: SDK selection, planning, build execution, and the manifest thatflash/size/imageread.alp-sdk-vscode: an optional UI that invokestan.
A successful build writes build/system-manifest.yaml, which records the
per-core artifacts and is reused by downstream commands.
New implementation work belongs under python/:
python3.12 -m venv .venv
.venv/bin/python -m pip install -e "./python[monitor]"
.venv/bin/python -m pip install pytest
(cd python && ../.venv/bin/python -m pytest tests -q)
python3 python/scripts/version_check.py --selftest --selfAlways install into a venv you create — never a bare pip install -e ./python
or pip install --user -e ./python. Run without an active venv, that writes
an editable install into your OS user site-packages, and from that moment
every bare python3 process on the machine resolves import tan to
whichever checkout was installed last, regardless of which worktree it is
actually running in — including another developer's or another agent's
checkout, on a shared box (tan-cli#665). It costs nothing to notice while it
is happening: tan --version and which tan keep answering normally, so a
full pytest tests -q run can report hundreds of misleading failures (or,
worse, a false green) with no other symptom. tests/conftest.py's
tan_under_test fixture refuses loudly at session start if import tan
resolves to anything outside this checkout's own python/ — that is the
backstop, not a substitute for using a venv in the first place.
That run means the same thing on every machine, including a bench host with
real debug tooling installed. The suite neutralises the debug/flash probe
identities — JLinkExe, openocd, pyocd, west and friends —
for its own duration (python/tests/conftest.py, PROBE_TOOLS), so a
which()-gated branch answers the way it answers on a CI runner rather than the
way this host happens to be provisioned. Before that (tan-cli#603) seven
test_flow_d_preflight_* cases passed locally and failed on ubuntu, windows
and macos at once. Ordinary host tooling — git, python3, sleep, the
coreutils the installer-script tests execute — is left alone. A test that
needs a probe tool present seeds its own and points PATH at it, which is
what makes the inventory readable in the test.
Useful directories:
python/tan/commands/ command orchestration, filesystem and subprocess work
python/tan/core/ domain logic and wire models
python/tan/planner/ in-process planner, mirrored from alp-sdk
python/tan/templates/ project templates included in the package
python/tests/ unit, conformance, parity, and repository gates
contract/ the JSON envelope goldens shared with alp-sdk-vscode
Release assets are PyInstaller freezes of python/. The Rust implementation
this program was ported from is deleted; the behaviour it was measured against
survives as the frozen captures under python/tests/fixtures/oracle_captures/.
- Release assets and verification
tan model checkand the static NPU-eligibility screen- SETOOLS setup
- Development roadmap
- Changelog
- License
tan is licensed under Apache-2.0.