Project Management with acctl
acctl is the command-line tool for managing AutoCore projects. It handles project creation, deployment, monitoring, and sending commands to the server and its modules.
Configuration
acctl reads server connection settings from acctl.toml in the project directory (created by acctl clone or acctl set-target), falling back to ~/.acctl.toml for global defaults.
[server]
host = "192.168.1.100"
port = 11969
[build]
release = true
Global flags --host and --port override all config files for a single invocation:
acctl --host 192.168.1.200 status
acctl Command Reference
Project Creation
| Command | Description |
|---|---|
acctl new <name> | Create a new project from the standard template (Rust control program + React web UI) |
acctl new-tis-project <name> | Create a new project pre-wired with TIS (<TisProvider> + tick_with_autostart). |
acctl clone <host> [project] [-P port] [-d dir] | Clone a project from a remote server |
acctl clone <host> --list | List available projects on a server |
acctl new my_machine
acctl clone 192.168.1.100 my_machine
acctl clone 192.168.1.100 --list
Subsystem Retrofit
Two idempotent commands flip subsystems on for an existing project:
| Command | Description |
|---|---|
acctl add-tis | Adds an empty test_methods: {} block to project.json so Project::normalize() injects the nine tis_* GM scalars next time codegen runs. See Chapter 15. |
acctl add-ams | Adds an empty asset_types: {} block to project.json so the three baseline ams_* GM scalars are injected and <AmsProvider> has something to mount against. See Chapter 16. |
acctl add-axis --name <N> [--link <slave>] [--type pp] [--backend ethercat|virtual] | Adds a CiA-402 axis to project.json. EtherCAT axes (--link <slave>) go in modules.ethercat.config.axes; --backend virtual creates a fieldbus-less simulated axis in modules.motion.config.axes. Idempotent on --name. Run acctl codegen afterward. See Chapter 8b. |
Run them in any order on a project that started without those subsystems; re-running on a project that already has them is a no-op that prints “already enabled” and exits 0.
AMS Backup / Restore
| Command | Description |
|---|---|
acctl ams export --output <file.json> | Pull the full Asset Management System dataset (registry + per-asset history + usage) from the server into a single JSON document. |
acctl ams import --input <file.json> [--dry-run] | Apply an exported document to the current server. Default behaviour merges (preserves IDs, appends calibrations, takes max-of usage counters). --dry-run previews changes. |
See Chapter 16 for the export shape and merge semantics.
Project Inspection
| Command | Description |
|---|---|
acctl info | Show a human-readable project summary (modules, variables, control program, www status) |
acctl validate | Check project.json for errors. Runs both the local cheap checks (syntax, variable types, duplicate names, broken links) and — if a server is reachable — the full server-side validator (AMS placeholder resolution, AMS registry/asset integrity, module schema). |
acctl status | Show server status, control program state, and project list (requires server connection) |
acctl info # Local — reads project.json, no server needed
Offline Code Generation
The autocore_server executable can generate the gm.rs and results.ts files directly without needing to start the full system (useful for CI/CD or development machines lacking physical hardware like EtherCAT).
# Generate gm.rs and results.ts offline
cargo run --bin autocore_server -- --generate /path/to/project.json
This bypasses the background servelets and exits immediately with code 0 on success.
acctl validate # Local — checks for errors before deploying
acctl status # Remote — queries the running server
`acctl validate` runs two passes:
**Local pass** (always runs):
- JSON syntax
- Required fields on variables (`type`)
- Valid type values (`f64`, `bool`, `u64`, etc.)
- Duplicate variable names
- Variable `link` targets reference configured module domains
**Server pass** (when a server is reachable — `system.validate_project` IPC command):
- `project_schema` — the file deserialises as a `Project`
- `module_schema` — each module entry has valid `enabled` / `args` / `executable` shape
- `ams.placeholder` — every `${ams.*}` placeholder anywhere in the project resolves against the server's AMS registry. **Warning-severity** (see "Severities" below).
- `ams.registry`, `ams.asset`, `ams.calibration` — the on-disk AMS state itself is well-formed
- `ams.asset_type`, `ams.asset_ref` — project asset_type entries don't shadow built-ins and asset_refs target known types
- `variable_duplicate` — two or more variables share the same hardware `link` (mirrors `acctl dedup-vars`)
- `variable_link_shape` — non-empty `link` values look like a dotted FQDN
If no server is reachable, the server pass is skipped with a `Note:` line and only the local pass counts. Connect to a server (`acctl set-target …`) before relying on this for AMS-aware checks.
The same server-side validator runs automatically as a pre-flight on:
- `acctl sync` when you push your local file to the server — error-severity findings block the push; warnings are shown and the push proceeds.
- `acctl codegen` before any code is generated — broken AMS placeholders don't get baked into `gm.rs`.
#### Severities
Each finding carries one of two severities:
- **`error`** (default for nearly every category) — blocks `acctl sync push` and `acctl codegen`. Indicates a structural problem (malformed JSON, broken schema, missing AMS registry, duplicate IDs, cross-module link typos).
- **`warning`** — surfaces in acctl output but does not block. Currently only `ams.placeholder` emits at this severity. The reasoning is that an unresolved AMS placeholder reflects asset-record state that the operator fixes through the AIS UI; blocking sync would leave them with no way to land the project the UI needs.
The `acctl sync` push output partitions findings:
Project validation warnings (not blocking):
ams.placeholder (18)
/modules/ni/config/tasks/0/channels/0/create_args/max_val
asset LC-... at location tsdr1 has no field capacity (looked under custom.capacity)
…
Project validation: OK (18 warning(s); fix in the AIS UI when convenient)
A mix of warnings + errors prints warnings first in yellow, errors below in red, and exits non-zero on the errors.
> **Why warnings exist.** Before this split, a stale asset (missing
> field, wrong type) blocked the project from landing at all. The
> operator would need to edit asset.json by hand on the server to
> recover. Now the project lands, the AIS UI on the (now-up) server
> exposes the bad asset, the operator fixes it there. The module
> supervisor still refuses to spawn modules whose configs contain
> unresolved placeholders at runtime — `UnresolvedAmsPlaceholders` —
> so the eventual hardware path is still gated on the asset being
> correct. The warning surfaces the same information, just earlier
> and without locking the operator out of the UI.
#### Server Configuration
| Command | Description |
|---|---|
| `acctl set-target <host> [--port PORT]` | Save the server address to `acctl.toml` |
| `acctl switch <project> [--restart]` | Switch the active project on the server |
#### Deployment
| Command | Flags | Description |
|---|---|---|
| `acctl push project` | `--restart` | Upload project.json to the server |
| `acctl push www` | `--no-build`, `--source` | Build (`npm run build`) and upload web HMI. `--no-build` skips the build. `--source` pushes full `www/` instead of `www/dist/`. |
| `acctl push control` | `--start`, `--no-build`, `--source`, `--force` | Build (`cargo build`) and upload the control binary. `--start` starts it after upload. `--source` pushes full source for remote build. `--force` skips project.json sync check. |
| `acctl push doc` | `--no-build` | Build (`acctl doc build`) and upload the generated documentation. `--no-build` uploads an existing `doc/book/` without rebuilding (fails if missing). |
| `acctl push assets` | `--no-reinit` | Publish local AMS data (`datastore/assets/` — registry, per-asset records, calibration history, usage) to the server. AMS records are machine-local, so `acctl sync` never auto-pushes them; this is the deliberate publish path. Calls `ams.reinitialize` after upload so the running server reloads from disk; `--no-reinit` skips that (restart the server yourself). |
| `acctl pull` | `--extract` | Download the active project as a zip |
| `acctl upload <file>` | `--dest PATH` | Upload an arbitrary file to the project directory (default: `lib/<filename>`) |
```bash
# Typical deploy workflow
acctl push project
acctl push www
acctl push control --start
# Or individually with options
acctl push www --no-build # Skip npm build, push existing dist/
acctl push control --no-build # Skip cargo build, push existing binary
Control Program Lifecycle
| Command | Description |
|---|---|
acctl control start | Start the control program |
acctl control stop | Stop the control program |
acctl control restart | Restart the control program |
acctl control status | Show control program state and PID |
Monitoring
| Command | Description |
|---|---|
acctl status | Server status, control program state, project list |
acctl logs | Show recent control program log output |
acctl logs --follow | Stream logs in real time (colorized by level) |
Log levels are colorized: ERROR (red), WARN (yellow), INFO (green), DEBUG (blue), TRACE (dimmed).
Code Generation and Sync
| Command | Description |
|---|---|
acctl codegen | Regenerate control/src/gm.rs from the server’s project.json (shared memory bindings). Requires a running server. Also scaffolds axis output/option variables (see Chapter 8b) and, before generating, checks that control/Cargo.lock’s autocore-std is new enough for the codegen output — aborting with the exact cargo update --precise to run if it’s too old, rather than letting you hit a cryptic Rust error. |
acctl codegen-tags [--force] | Regenerate www/src/AutoCoreTags.ts from the local project.json. Pure local operation — no server connection needed. |
acctl sync | Compare local vs server project.json interactively (pull, push, or skip per-section; auto-runs codegen after), then pull the critical datastore files: autocore_gnv.ini and assets/. |
acctl sync all | Same project.json reconcile, then a full mtime-wins sync of the entire datastore/ tree (excluding results/). datastore is accepted as an alias for all. |
acctl diff | (Planned) Show what would change on push |
After adding or removing variables in project.json, always run acctl codegen to update the Rust shared memory bindings before rebuilding the control program.
The plain acctl sync deliberately skips the bulk of the datastore
(captures/, scripts/, …): on long-running projects those grow to
hundreds of files and make every sync slow — especially over remote
tailscale links — when usually you only want the project file and the
machine-critical state backed up. Run acctl sync all when you do want
the whole tree; transfers are batched into ~8 MiB requests so large
datastores no longer overflow a single websocket message.
acctl sync all reconciles the datastore/ directory mtime-wins, but
two kinds of file are pull-only — sync brings a fresher server
copy down but never pushes the local copy up:
datastore/autocore_gnv.ini— non-volatile values written by the running control program. Restore deliberately withacctl push gnv.datastore/assets/— AMS asset and calibration records. These are machine-local (the transducer actually installed in this machine, its cert history, usage counters); the sharedproject.jsonis the same on every machine but this data is not. Auto-pushing would let one machine overwrite another’s assets on the shared server. Publish deliberately withacctl push assets.
results/ is excluded from sync entirely (pull deliberately with
acctl pull-results). Everything else under datastore/ (e.g.
scripts/, captures/) syncs both ways under acctl sync all.
acctl codegen-tags — web UI tag generation
Each variable in project.json supports an optional boolean field ux. When "ux": true, acctl codegen-tags emits a record for that variable into the generated block of www/src/AutoCoreTags.ts, giving the React web UI a typed handle (tagName, fqdn, valueType) for subscriptions and controls. Variables without ux: true are ignored.
"variables": {
"lift_axis_position": { "type": "f64", "ux": true, "description": "Lift axis position (mm)" },
"internal_watchdog": { "type": "u32", "ux": false, "description": "Never shown in HMI" },
"req_start_auto": { "type": "bool", "ux": true }
}
Type mapping from project.json to TypeScript’s valueType:
project.json type | TS valueType |
|---|---|
bool | "boolean" |
u8–u64, i8–i64, f32, f64 | "number" |
string | "string" |
| anything else | (warns and skips) |
Tag names are derived from the variable name by snake_case → camelCase conversion (lift_axis_position → liftAxisPosition). The FQDN is always gm.<variable_name>.
Output file layout
The generated file contains two arrays combined into the exported acTagSpec:
// autocore-codegen:generated-start
// DO NOT EDIT: this block is regenerated by `acctl codegen-tags`.
export const acTagSpecGenerated = [
{ "tagName": "liftAxisPosition", "fqdn": "gm.lift_axis_position", "valueType": "number" },
{ "tagName": "reqStartAuto", "fqdn": "gm.req_start_auto", "valueType": "boolean" },
// ... one record per variable with ux: true ...
] as const satisfies readonly TagConfig[];
// autocore-codegen:generated-end
// Hand-written tags and per-tag overrides — safe to edit.
export const acTagSpecCustom = [
{ tagName: "liftPosition", fqdn: "gm.lift_axis_position", valueType: "number",
subscriptionOptions: { sampling_interval_ms: 300 }, scale: "position" },
// ...
] as const satisfies readonly TagConfig[];
export const acTagSpec = [
...acTagSpecGenerated,
...acTagSpecCustom,
] as const satisfies readonly TagConfig[];
Put anything that needs subscriptionOptions, scale, or any other TagConfig property into acTagSpecCustom — the generated block only carries the three basic fields. The sentinel comments // autocore-codegen:generated-start and // autocore-codegen:generated-end delimit the replaceable region.
Regeneration behavior
On each run, acctl codegen-tags decides whether to replace only the generated block or rewrite the whole file:
| Situation | Action |
|---|---|
www/src/AutoCoreTags.ts doesn’t exist | Write full file from template. |
File exists, has both sentinel comments and acTagSpecCustom | Replace only the generated block; acTagSpecCustom is preserved. |
File exists but missing a sentinel or acTagSpecCustom | Full rewrite from template. Old file is saved to AutoCoreTags.ts.bak. |
--force is passed | Full rewrite regardless. Old file saved to .bak. |
A .bak sibling is only ever produced when the tool actually overwrites a hand-edited file — routine in-place updates leave nothing on disk besides the new AutoCoreTags.ts.
Workflow
# Mark variables for the UI in project.json (editor or acctl import-vars)
# Then:
acctl codegen-tags # → www/src/AutoCoreTags.ts
cd www && npm run dev # React picks up the updated tag list immediately
Run codegen-tags any time you flip ux on/off or add/rename variables. The React side has no caching — refreshing the dev server or the built app picks up the new list on next load.
Variable Management
| Command | Description |
|---|---|
acctl export-vars [--output FILE] | Export variables to CSV (default: variables.csv) |
acctl import-vars [--input FILE] | Import variables from CSV (default: variables.csv) |
acctl dedup-vars | Find and interactively resolve variables with duplicate hardware links |
CSV columns: name, type, link, description, initial.
acctl export-vars --output variables.csv
# Edit in spreadsheet...
acctl import-vars --input variables.csv
acctl dedup-vars # Check for conflicts
Project Documentation
Every project created by acctl new includes a doc/ directory with an mdBook-based user manual. The acctl doc subcommands build, serve, and keep that manual in sync with project.json and the control program source.
| Command | Flags | Description |
|---|---|---|
acctl doc init | --force | Scaffold doc/ (book.toml + the five starter Markdown files) in an existing project. Skips files that already exist; --force overwrites. Use this to add the doc directory to projects created before acctl doc support. |
acctl doc build | — | Build static HTML output at doc/book/. Runs generate-vars and cargo doc automatically. |
acctl doc serve | --port PORT (default 4444) | Serve the book locally with live reload. |
acctl doc generate-vars | — | Regenerate doc/src/variables.md from project.json (hardware-linked / bit-mapped / plain tables). |
acctl doc clean | — | Remove doc/book/ and doc/src/rustdoc/. |
acctl doc init # Scaffold doc/ if the project doesn't have one yet
acctl doc serve # http://localhost:4444 with live reload
acctl doc serve --port 8080 # Custom port
acctl doc build # One-shot build → doc/book/index.html
New projects created by acctl new already contain a scaffolded doc/, so you only need acctl doc init when retrofitting an older project or when you’ve deleted doc/ and want to start over. The command reads the project name from project.json to populate the book title and introduction page, and never overwrites files by default — safe to run repeatedly.
On first use, if mdbook is not on your PATH, acctl installs it automatically via cargo install mdbook --locked (one-time, ~60s). cargo doc ships with every Rust toolchain, so no additional installation is needed for the Rustdoc section.
Distribution
The output at doc/book/ is a self-contained static site. You have three distribution options:
-
Push to the server —
acctl push docbuilds the book and uploads it to the active project on the server. autocore-server automatically serves the active project’s documentation on its documentation port (default4444, configurable inconfig.ini):http://<server-ip>:4444/Operators get up-to-date docs as a side effect of deployment — no separate hosting required. When no documentation has been pushed, the port serves a placeholder page explaining how to run
acctl push doc. Switching the active project on the server automatically swaps the served docs to the new project’s book. -
Zip and share —
doc/book/is fully self-contained. Zip it, email it, or drop it on a shared drive. Recipients unzip and openindex.htmldirectly from the filesystem. -
Host elsewhere — Push
doc/book/to any static web host (GitHub Pages, S3, internal nginx, etc.). All links are relative.
Configuring the documentation port
The server reads the doc port from config.ini:
[general]
port = 80 # Main HMI port
doc_port = 4444 # Documentation port (this)
Omit doc_port to use the default of 4444. Set it to a different value if 4444 is already in use on the target.
Note: As of this writing, autocore-server’s HTTP endpoints — including port
4444— are unauthenticated. If your project documentation contains sensitive information, keep the server on a trusted network until the forthcoming authentication gate ships.
Sending Commands to Modules
| Command | Description |
|---|---|
acctl cmd <topic> [args...] | Send a command to the server (same as the AutoCore console) |
The topic format is domain.command. Arguments are parsed as --key value pairs. Values are auto-detected as numbers, booleans, JSON objects/arrays, or strings.
# System commands
acctl cmd system.get_domains
acctl cmd system.list_modules
acctl cmd system.load_module --name ni
acctl cmd system.new_project --project_name my_machine
# Global Memory (read/write variables)
acctl cmd gm.read --name motor_speed
acctl cmd gm.write --name motor_speed_setpoint --value 1500
# Module commands (NI example)
acctl cmd ni.status
acctl cmd ni.describe
acctl cmd ni.add_channel --task AnalogInput --name ai0 --physical_channel Dev1/ai0 --type voltage
acctl cmd ni.save_config --generate_variables true
# Any module that implements CommandRegistry
acctl cmd modbus.status
acctl cmd labelit.camera_start --ip 192.168.1.50
Managing Tools and Editors
Tools and editors (such as labelit-studio) are registered with the server through the tool registry. acctl can list them and trigger a live rescan — see Tools and Editors for the full picture.
| Command | Description |
|---|---|
acctl tools list | List registered tools with running state, URL, and the module domains each edits |
acctl tools rescan | Re-read the registry and start/stop service tools without a server restart |
acctl tools list
acctl tools rescan # package install/uninstall scripts call this automatically
Working with Multiple Projects
Each AutoCore server can host multiple projects, but only one is active at a time.
acctl status # See all projects and which is active
acctl switch other_project --restart # Switch to a different project
acctl cmd system.new_project --project_name new_machine # Create a new project on the server
Deploying to a Remote Server
# 1. Set the target server (saved to acctl.toml)
acctl set-target 192.168.1.100
# 2. Verify the connection
acctl status
# 3. Validate before deploying
acctl validate
# 4. Deploy
acctl push project
acctl push www
acctl push control --start
# 5. Monitor remotely
acctl logs --follow
Importing and Exporting Variables
For large projects, manage variables in a spreadsheet and import them:
acctl export-vars --output variables.csv
# Edit the CSV in your spreadsheet application...
acctl import-vars --input variables.csv
acctl dedup-vars # Resolve any duplicate links
The CSV format has these columns: name, type, link, description, initial.
Writing Project Documentation
The project’s doc/ directory is an mdBook — the same tool used for this manual. Source files live in doc/src/ as Markdown; the table of contents is doc/src/SUMMARY.md.
Default layout (created by acctl new, or by acctl doc init on an existing project):
doc/
├── book.toml # mdBook configuration
└── src/
├── SUMMARY.md # Table of contents
├── introduction.md # Edit this — your project overview
├── variables.md # Auto-generated — do not edit
└── control_api.md # Links to the Rustdoc section
Retrofitting older projects. If your project was created before
acctl docsupport anddoc/book.tomldoesn’t exist, runacctl doc initfrom the project root. It scaffolds the same five files thatacctl newwould have produced, pulling the project name fromproject.jsonfor the book title. Existing files are left untouched; pass--forceto overwrite.
What Gets Auto-Generated
Two parts of the book are regenerated every time you run acctl doc build or acctl doc serve — do not edit them by hand:
doc/src/variables.md— a grouped FQDN table of every entry inproject.json’svariablesmap. Three sections are emitted when non-empty: Hardware-Linked (entries with alinkfield), Bit-Mapped (entries withsource+bit), and Other. Columns include FQDN, type, description, and the relevant linkage fields.doc/src/rustdoc/— a copy ofcargo doc --no-depsoutput fromcontrol/. The defaultcontrol_api.mdchapter links intorustdoc/index.html. Doc comments (///) in your control program source become a browsable API reference.
Typical Authoring Workflow
# Start the live-reload server while you write
acctl doc serve
# In another shell, edit doc/src/introduction.md and any other chapters
# Add new chapters by creating new .md files and listing them in SUMMARY.md
# Once happy, produce a static build to hand off
acctl doc build
# → doc/book/index.html
Because generate-vars and cargo doc run on every build, changes to project.json variables or doc comments in control/ appear in the book without any extra step.
Distribution
doc/book/ is a standalone static site — no runtime dependencies, all links relative. Typical distribution options:
- Zip
doc/book/and email or share the archive; recipients unzip and openindex.htmldirectly. - Push
doc/book/to any static web host (GitHub Pages, S3, an internal nginx, etc.). - Commit
doc/book/to a docs branch for versioned online access.
Add doc/book/ and doc/src/rustdoc/ to your .gitignore if you prefer to keep only sources in version control — both are fully reproducible from the sources plus project.json and control/.