openawf

Tools / awf-cli

awf-cli

awf is a command-line tool for OpenAWF documents, licensed MIT. It validates, seals, bundles, renders, installs and exports workflows and tasks. It supports spec version 0.1.0.

Install

$ go install github.com/dwwescalelol/awf-cli/cmd/awf@latest

awf <command> --help lists every flag. Source: github.com/dwwescalelol/awf-cli.

Quick start

$ awf init                                   # create .awf in this directory
$ awf new workflow feat-dev                  # write the draft
$ $EDITOR .awf/_/workflow/feat-dev/draft.yaml # add version: 0.1.0
$ awf validate workflow feat-dev
$ awf seal workflow feat-dev                 # draft.yaml becomes 0.1.0.yaml
$ awf render workflow feat-dev@0.1.0
$ awf export workflow feat-dev@0.1.0 ./feat-dev.yaml

Documents

A workflow is a .yaml file with a top-level openawf key. A task is a .md file whose YAML frontmatter has an openawf key, with the task's instructions in the Markdown body.

Scope

A scope is an .awf directory. Each command finds one scope.

  • The project scope is the nearest .awf directory above the working directory. The search stops at the home directory.
  • The global scope is $AWF_HOME, or ~/.awf when it is unset.
  • --global selects the global scope.
  • With no project scope found, the global scope is used.

A path is in a scope when it lies inside that .awf directory. A path next to .awf, such as the project root, is outside it.

Layout

.awf/
  <namespace>/workflow/<id>/<version>.yaml
  <namespace>/task/<id>/<version>.md
  _/workflow/<id>/draft.yaml
  _/task/<id>/draft.md

Versions of one id sit side by side, so a workflow can pin any task version that exists.

Namespaces

A namespace is the first part of a document's path in a scope.

  • _ is the local namespace. It holds your own documents and is the only writable namespace.
  • Every other namespace is foreign and read-only. A foreign namespace is named after the git repository its documents came from, such as example.com/team/flows.

An id names a local document as <id>, and a foreign one as <namespace>#<id>. install is the only command that writes into a foreign namespace.

Drafts and versions

A document in a scope is a draft or a version.

Draft

A draft is an unsealed document you edit.

  • Its file is draft.yaml or draft.md, and it exists only in _.
  • Each id has at most one draft.
  • It has no sha. new and import write it with no version.
  • It may hold $ref tasks. A draft is a source, not a bundle: its tasks can live in other files, so one draft can span many files.

Version

A version is a sealed document.

  • Its file is <major.minor.patch>.yaml or .md.
  • It has a sha that matches its content.
  • Its tasks are inline. Sealing a workflow inlines every $ref.
  • It never changes. A change requires a new draft.

Sealing

seal is the only way to turn a draft into a version. It reads the version written in the draft, requires it to be newer than the latest version, writes the sha, and moves the draft to that version's file.

$ awf seal workflow feat-dev
.awf/_/workflow/feat-dev/0.1.0.yaml
sealed sha256-55ddf384c2ec93f0b2617e35b0b5ceb12b3f5514294e60e5b2a985135fa23891

File names

Every command that loads a document from a scope applies these rules, including validate, seal, bundle and the language server.

  • A file named other than <major.minor.patch> or draft is an error: a document in an awf scope is named <major.minor.patch> or draft.
  • An unsealed version file is an error: an unsealed document in an awf scope is a draft, name it draft or seal it.

Files outside a scope have no naming rule.

Outside a scope

A document outside a scope is a sealed, bundled document. The CLI never edits one.

  • validate -f and render -f read a file anywhere.
  • import -f copies a file into _ as a draft.
  • export writes a sealed document to a file outside every scope.

To keep a workflow next to your code, write and seal it in your scope, then export it.

The CLI defines intended use, and the file system cannot enforce it. Nothing stops an unbundled or unsealed file from sitting outside a scope, or a sealed version from being edited and re-hashed by hand. Neither is correct use. awf offers no command that produces either, and validate reports a sha that does not match its content.

Targets

A command that reads a document takes a target: [<namespace>#]<id>[@<version>] in the scope, or -f <path> for a file.

  • An unpinned <id> resolves to its draft, or to its latest version when it has no draft.
  • A target that is not an id fails with not an id, use -f for a file path.

A command that acts on a draft takes <id> only. It refuses @<version>, <namespace># and -f.

References

A $ref points a workflow task at a task document. Only drafts hold $ref, so references exist only in the local namespace _. The task a reference points at can live anywhere: another file in the project, or a version in any namespace.

  • A path, absolute or relative to the directory that holds the scope.
  • An id pinned to a version: [<namespace>#]<id>@<version>. A bare <id> means _.
tasks:
  review:
    $ref: tasks/review.md                     # a path
  deploy:
    $ref: example.com/team/flows#deploy@2.0.0  # a pinned id

Sealing a workflow refuses unsealed $ref tasks, then inlines them all.

Commands

Commands that act on a document take its kind, then a target: awf <command> <kind> <target>. The kind is workflow or task.

CommandReadsWrites
initA new project scope. Refuses inside an existing one.
new <kind> <id>The latest version of <id>, if any.The draft of <id> in _. A new draft of an existing id copies its latest version.
import <kind> <target> [<id>]Any target, any namespace, or -f.The draft of <id> in _, or of the source's own id. Clears version and sha, inlines $ref. Refuses an existing draft.
seal <kind> <id>The draft of <id> in _.The sealed version file. Removes the draft.
bundle workflow <id>The draft of <id> in _.Stdout, or the file given by -o. Inlines $ref.
export <kind> <target> <path>A sealed target, any namespace, or -f.<path>, a file outside every scope. Refuses a draft, an unsealed source, an existing path, and an extension that does not match the kind. --format claude writes a Claude Code plugin to the directory <path>.
install <namespace>#<id>[@<version>]A sealed document in a git repository. A document's URL also works; -b picks a branch.A version in that repository's foreign namespace.
validate <kind> <target>Any target.Nothing.
render <kind> <target>Any target.A temporary HTML page, opened in the browser. --source prints it.
ls [<kind>]The scope.Nothing.
uiThe scope.Nothing. Browses the scope's workflows in the browser.
lspOpen editor buffers.Nothing. Runs the language server over stdio.

The boundary

Inside a scope, only drafts in _ change. Every version, in every namespace, is sealed and fixed. Outside a scope, every document is sealed. import crosses the boundary inward as a draft. export crosses it outward as a sealed file.

Editors

awf lsp gives editors diagnostics, go-to-definition on $ref, and a live preview of the rendered page. The VS Code extension and Neovim setup are in editors/.

The server handles workflows (.yaml files with a top-level openawf key) and tasks (.md files whose frontmatter has an openawf key). It ignores every other file.