Skip to content

Latest commit

 

History

History
741 lines (672 loc) · 43.2 KB

cli.md

File metadata and controls

741 lines (672 loc) · 43.2 KB

CLI reference

Every command, grouped by what you are doing. Run velar --help for the same list from the toolchain you actually have installed.

Inside a project, npm scripts wrap most of these — npm run dev, npm test, npm run build. Use npx velar <command> for the rest.

velar --version

It prints two lines: the release you installed, and the surface versions it ships.

velar <release>
  core@<count>   web@<count>   node@<count>   server@<count>   desktop@<count>

The numbers are deliberately not reproduced here — run the command and read them off the toolchain you have, which is the only copy that cannot be stale. The second line names the surfaces this project has installed. A project installs the targets it declares, so a Core project — which declares none — prints core and node alone: there is no Web, Server or Desktop package present for a number to be read out of, and one written down here instead would be exactly the stale copy this whole mechanism removes.

Every package in a release steps to the same number, so the first line cannot say which of the five observable surfaces — the language itself, and the Web, Node, Server and Desktop extensions — actually moved. The second line can. A surface version is a counter: any addition, removal, or change to that surface raises it by one, so a release that leaves a number alone is saying that surface is unchanged and the code using it needs no re-reading. The N in 0.N is how many times that surface has moved since counting began, not a maturity grade: a low number beside a high one means that surface started counting later, and nothing more. Counting began for all five in the release whose changelog section introduces them, and history was not recomputed. None of these numbers is written by hand anywhere: a repository gate hashes each surface's complete vocabulary and fails the build when a surface changes without its version.

A project records what it was written against with surfaces in velar.json; see project lifecycle.

Writing code

velar check [entry.vel | project-directory]
velar graph [entry.vel | project-directory] [--focus <symbol|path>] [--depth <0-6>] [--max-nodes <count>] [--max-edges <count>] [--json]
velar format [file.vel | project-directory] [--check]
velar fix [entry.vel | project-directory]
velar repro [entry.vel | project-directory] [--out-dir <directory>]
velar lsp

check compiles and reports diagnostics without producing output. Over a project it reads every .vel file under the project directory, not only the ones the entry imports: a module nothing imports yet — a chapter mid-refactor, a file whose last importer was just deleted — is compiled as a root of its own and its diagnostics are ordinary diagnostics. build refuses on them too. An ordinary library package that declares package.json#velar.entry and exact velar.entries emits the union of those public entry graphs, with a shared module written once; every undeclared orphan remains check-only. Other projects emit only the graph their velar.json entry reaches. Naming a single file instead (velar check src/main.vel, or velar build src/main.vel --out main.js) scopes the run to that file's own graph. A named source inside a manifest-backed project must choose --out or --out-dir: omitting both cannot reinterpret the project's complete outDir as a single-entry build. Omit the source argument or name the project directory when rebuilding that declared output. A manifest-backed file may import across project directories while retaining its single-entry graph; --out-dir preserves those project-relative source paths, while a bare file has only its own directory as an import boundary. A module-resolution failure is a diagnostic like any other — path:line:col error VEL6xxx: … with the import that caused it under the caret — and the editor publishes it at the same position. It reports advisories too — the second channel, for a spelling VelarScript accepts with a meaning other than the one a Python or JavaScript reflex intended. An advisory never fails anything: check prints it, names the count in its summary line (Checked 12 modules from src/main.vel — 3 advisories), and exits 0, and build, test, CI, and release never fail because of one. Answer one by writing the spelling it names, or with a // velar-allow <CODE>: <reason> comment on that line; a suppression with no reason, and one that no longer applies, are both ordinary compile errors. The rules are in the language reference.

A .vel file with no velar.json above it is checked as a Node program: Core plus the Node capabilities, which is what velar run executes. That is the target a lone file has — Web, Server and Desktop are package-declared, so a file that declares nothing has none of them, and a Node capability written in one compiles rather than being refused by a target the file never chose.

format is the single canonical layout — there are no options, because a second layout would be a second spelling, and it preserves a velar-allow comment and its reason verbatim. fix applies the rewrites that are provably equivalent, which is why it is safe to run unattended; anything requiring a judgment call stays a diagnostic for you to answer. That line runs through the advisory channel rather than around it. An advisory whose rewrite says the same thing in the checked spelling is applied — A16 turns a complete CSS filter string into the filters(...) builders that check it, imports and all — while one that would change what the program means is left to you: swapping the two names of a for header changes which name binds which value, so an editor offers that as a quick fix and fix leaves it alone. What is left is in the summary, both channels at once — applied 3 mechanical fixes in 1 file; 0 diagnostics and 1 advisory remain — so a run that fixed everything it could still says what check will have to report. An advisory is counted there, never refused: it does not change the exit code. lsp speaks the Language Server Protocol for editors, and shows an advisory as a warning rather than an error.

graph prints the compiler-owned logic view of the current project. Its compact default keeps modules, components, state, computed values, actions, functions, types, capabilities, and their exact ownership, call, read, write, and derivation edges. Its printed form locates each node the way every other line this toolchain prints does — src/app.vel:57:11 — and --json exposes the same stable IDs with byte offsets, which is what a tool splices with. --max-nodes and --max-edges bound one view; a run that reached either bound says so in its selectionLimit field. A model can ask for --focus <symbol> --depth 2 to load only that symbol's callers and dependencies when its context is bounded. The command reads the current project on every invocation. Editor hosts keep one incremental compiler session and ask velar/ownershipGraph with the previous revision; the server reuses graph fragments for compiler-reused modules and returns a node/edge patch for the unsaved document version. A truncated base or broad invalidation falls back to one bounded full snapshot, with the update strategy reported in activity.

Formatting is idempotent, and both format and the language server verify it: if formatting the result would change it again, the file keeps the bytes you wrote, format names it and exits non-zero rather than writing an unstable layout, and the language server offers no edit at all.

fix rewrites only the source the project owns. An installed frozen library loads its portable interface and never reaches the fixer; a source-fallback package compiles as part of your project, so its diagnostics are reported beside your own — on the same channel check reports them on — but fix never writes a file that came out of one: a rewrite there is invisible to git, is destroyed by the next npm ci, and makes the installed tree diverge from the tarball it was published as. The boundary is the real path, so a module reached through a symbolic link inside src/ is left alone as well when the link points into an installed tree. Inside the project, a read-only module is refused and named rather than replaced, and a module hard-linked under a second name is written in place so both names keep pointing at the same file. Every file is re-read immediately before it is written, and one that changed since the compile that computed the rewrite is left untouched and named as a file fix could not write — a save that lands mid-pass survives verbatim instead of being reverted without a word. A run that could not write a file, or that leaves a diagnostic behind, exits non-zero.

fix migrates velar.json too, and does it first, because a manifest written against a shape this compiler removed is what fails before anything else can run. A target that retires a manifest shape carries the rewrite with it, and the error the old shape raises names this command: desktop.window reports that desktop.windows replaced it, and velar fix rewrites window: {…} as windows: {"main": {…}}. The edit is surgical — the one member changes, and the rest of the manifest keeps its bytes, key order and indentation — and running it a second time changes nothing.

fix answers the project's own rules as well as the compiler's. A rule about how the project is arranged — an application entry must declare @main, a library entry must not, a manifest that declares a Server configuration file must have that file — is enforced where the project is resolved rather than where a module is compiled, and every command reads it from one place, so fix can no longer report "0 diagnostics remain" over a tree check refuses, and check can no longer call a tree clean that build will not accept. One of those rules migrates mechanically. An entry whose startup is a single non-block statement on one line at the end of the module gets @main: written in front of it, which is the rewrite the author would have made letter for letter: the inline body has the statement semantics of an indented one, and nothing else in the file moves. Two startup statements, a statement with a declaration after it, a statement spread over several lines, and a one-line statement that heads a block of its own all stay diagnostics — which statement runs first is visible in the source today and a fixer that merged them would be choosing an order rather than preserving one, and the inline body accepts one non-block statement, so wrapping if ready: start() would leave source the compiler no longer parses. Naming a single file scopes those rules out exactly as it scopes out the tree walk.

A project-level rule reports in the shape every diagnostic has — file:line:column error VELxxxx, with a source frame — because each of them is about a line somebody wrote. VEL6001 names an entry file the manifest declares and the disk does not have, on the entry line of velar.json; VEL6011 and VEL6012 are the two @main rules, on the entry module's first line; VEL6013 is the Server configuration rule, on the server.configuration value that declared the file; and VEL6014 is a Desktop capability the manifest never granted, on the import that reached for it with the caret under the specifier. Source-module diagnostics retain these codes and positions in editor sessions; manifest source frames are part of the CLI report.

project/velar.json:5:13 error VEL6001: Entry module "src/main.vel" does not exist
  "entry": "src/main.vel",
            ^^^^^^^^^^^^

lsp orders workspace symbols by match quality first, then by name ignoring case, then by path. Ignoring case is the Unicode default case mapping rather than a locale-tailored one, so apple comes before Banana on every machine and no editor's picker reorders itself because one developer's LC_ALL differs from a colleague's.

repro is for the case where the compiler itself looks wrong. It writes a self-contained minimal reproduction — the source the diagnostic touches, velar.json, the verbatim output, and the toolchain, Node, and platform versions — into .velar/repro, then prints the path. Its README.md arrives already laid out in the three sections a defect report carries, with What the compiler said filled in for you. It writes to disk and does nothing else: no upload, no network call, nothing collected about your machine, and every absolute path rewritten to a project-relative one. Before it finishes it extracts the bundle to a temporary directory and re-checks it there, and if the copy stops reproducing it says so rather than handing you a false lead. A failing velar check ends with the one line that names it. Over a project that checks there is nothing to bundle: it says so and exits 0, because being asked to reproduce a failure that is not there is an answer rather than a failure of its own. The doctrine it mechanizes is escape hatches.

Running and testing

velar dev [entry.vel | project-directory] [--port <port>]
velar serve [project-directory]
velar run [entry.vel | project-directory] [--stack] [-- <program-arguments>...]
velar test [project-directory | file.test.vel] [--stack]
velar test [project-directory] --browser [chromium|firefox|webkit|all] [--stack]

dev rebuilds on save and serves a Web/Desktop application or restarts the last-good Server/Node entry module. Every application target executes the selected source's @main. Web defaults to port 5173, --port 0 binds any free port, and the address printed at startup names the port that was bound rather than the one that was asked for. Server host, port, and request limits come only from the file named by server.configuration in velar.json; Node --port and serve --host are not parallel configuration channels. serve checks and runs the same entry with production runtime behavior and no file watcher. run executes a framework-free CLI program; --stack keeps the full trace instead of hiding internal frames. It compiles into a sandbox at <project>/.velar/run-XXXX/ and enters the program through a launcher, so a relative static or upload root still means the project root it was written against, and so does the server.configuration path — velar run, velar dev, velar serve and velar test all read it from beside velar.json, whatever directory the command was typed in, and a program whose @main never finishes — the event loop drained with an awaited value unsettled — is named on stderr and exits 13, the code Node.js gives that same program run as its own main module, instead of exiting 0 with its output cut short. On that exit-13 report --stack adds only the launcher's own frames: the loop drained, so there is no .vel frame left to show, and what the report can prove is what it already says. The program is also watched by the launcher rather than merely signalled by it — if the velar run process is killed outright, the program ends with it instead of being reparented and left holding its port. run has nothing to do in a kind: "library" project — a library publishes declarations and exports and has no entry to run — and says so rather than exiting 0 with no output; naming a single .vel source still runs that file. test runs *.test.vel modules in Node; --browser runs *.browser.test.vel modules in a real browser, which requires the matching Playwright browsers to be installed. A failing test reads the way an uncaught velar run failure does: the thrown value's own line, the .vel line that raised it with a caret under the column, the frames you own, and a count of the frames outside your program that were hidden. An expect mismatch is located at the assertion that failed; a Type.parse failure points to its call, not the generated validator. test --stack restores runtime and host frames while keeping available source snippets. The same flag applies with --browser: both the Node test body and the page's error channels receive it. Page traces retain the locations the browser supplies; a production bundle may report JavaScript locations instead of a .vel source snippet. A browser test that does not finish within its bound ends the run: the supervisor names the test and the bound it outlived, writes the counts up to it, and exits, so the browser tests after it are neither run nor reported. A .test.vel file resumes past a wedged test instead; see project lifecycle.

The dev server a Web or Desktop project starts, and preview, bind the loopback interface and answer only requests that address them as one. A request is refused with 403 unless its Host header names 127.0.0.1, localhost or [::1]; unless its Sec-Fetch-Site header is absent, none or same-origin; and unless its Origin header, when it sends one, is an http origin on a loopback host and on the same port the request was addressed to. The refusal body names the header it refused and the value it saw. Binding the interface is not the whole defence: a page whose own hostname has been pointed at 127.0.0.1 is otherwise same-origin with the development server and can read /<module>.js.map, whose sourcesContent carries your verbatim .vel source and whose sources carries its absolute on-disk path. A second local server on another port is a different origin, and the Origin rule refuses it like any other; a tunnel that forwards its own hostname fails the Host rule instead — run it with host-header rewriting, ngrok http --host-header=rewrite 5173, so the request reaches the server addressed to localhost.

dev's watcher ignores node_modules/, .git/, .velar/ and the project's outDir, at every depth and on every platform, for a Node project as well as a framework one. A velar build or velar test running in a second terminal writes into those directories, and without the exclusion each write rebuilt the application and pushed a full-page reload that discarded the page state being debugged. Request paths are percent-decoded before a public asset is resolved, so public/my file.txt and a file with a non-ASCII name are reachable in development exactly as they are after deployment; a malformed percent escape is answered with 400.

Building and shipping

velar build [entry.vel | project-directory] [--out-dir <directory>] [--mode <production|readable>] [--source-maps|--no-source-maps] [--force]
velar build <single.vel> --out <file.js> [--mode <production|readable>] [--source-maps|--no-source-maps]
velar build-library [project-directory] [--mode <production|readable>]
velar verify [project-directory | build-directory]
velar preview [project-directory | build-directory] [--port <port>]
velar verify-deployment [project-directory | build-directory] --url <https-origin> [--json]
velar package [project-directory]

build defaults to production: generated modules, target runtimes and bundled applications are minified and safely tree-shaken. Use --mode readable for one inspectable handover build, or make it the project default in velar.json:

{
  "formatVersion": 2,
  "kind": "application",
  "entry": "src/main.vel",
  "build": { "mode": "readable", "sourceMaps": true }
}

JavaScript mode and Source Map are independent. build.sourceMaps defaults to false for distributable builds and can be overridden for one invocation with --source-maps or --no-source-maps. Development and test execution retain their own enabled mappings regardless of the production build setting.

That default decides what a failure in the built program can show. A build with no source maps has no .vel frames to print: an uncaught failure under node dist/main.js carries the sentence and, where its frames name only generated JavaScript, the sentence alone — the emitted velar/serve drops such a trace rather than making Node print a minified line thousands of columns wide. Build with --source-maps to get the author's frames back, or reproduce the failure under velar run, which enables them and prints the code frame beside the first .vel frame. Either way the sentence is the same one and names what it is about: a worker readiness deadline names the worker family that missed it, and the velar run launcher's header names the program entry it was running.

Every build holds a process-shared claim for its complete output mutation set. Directory claims cover their whole tree, so an overlapping directory build and standalone --out build cannot run concurrently. Recovery, ownership checks, same-filesystem staging, atomic replacement, and cleanup all run inside that claim. A framework-free build written to an alternate --out-dir carries the path-bound .velar-build-output.json ownership receipt, allowing later builds to replace that generated directory without --force.

An ordinary source library can use build, regardless of its execution target, including a source project without package.json. For a library that declares velar.artifacts, or whose output directory already contains a frozen library receipt, build refuses before writing and names build-library. An unreadable or malformed package manifest cannot authorize replacement. Naming a .vel file does not bypass this protection: only an explicit single-file --out resolving outside the protected directory is allowed. verify authenticates the declared artifact set when given its project, output directory, or receipt file.

build-library is the release build for a kind: "library" Core or Node library whose package.json declares the root velar.entry, optional exact velar.entries, one velar.artifacts receipt, and a matching exact npm export for every entry. It checks every entry independently, then emits all distinct public entries through one ESM splitting build. Receipt format 2 records . and every exact subpath in entries, the union of checked sources in sources, and every shared JavaScript/source-map pair in chunks. Entry files and shared chunks are hashed as one package artifact set. Consumers authenticate the full set once per package and target. Commands that materialize a selected artifact use its verified in-memory JavaScript/source-map snapshots; build-library keeps an imported frozen package as its original bare npm specifier so that package continues to own its dependency graph. The set must be a closed local ESM graph; computed dynamic imports and relative edges outside its receipt fail closed. Direct public imports, internal static imports, and literal dynamic imports share one instance of each source module, including a lazy path back to the root entry. Generated links name the final public output paths, including mixed .js and .mjs entries; rewritten links preserve authored string values and source-map locations in both production and readable modes. Library artifacts are host-independent intermediate output and never contain a CommonJS host bridge: opaque require, createRequire, and process.getBuiltinModule remain publication errors, including for a Node artifact. A standalone, frozen-package, or Node directory linker may add the Node builtin bridge only after it has authenticated and closed the complete statically visible package graph. Root-only packages retain receipt format 1 and the original index.* layout. The .vel sources remain separate published inputs; consumers use one compatible artifact set first and compile source only as a fallback. ABI 1 does not build Web/Desktop component packages. Exact subpath and conditional-export rules are in package distribution.

The Source Map switch belongs to application/module build output. A frozen entry map and every format-2 shared-chunk map remain mandatory because ABI 1 hashes and verifies them as part of the released artifact set.

For frozen dependencies, run and test preserve resolution from the installed artifact owner, revalidate its bytes immediately before launch, and serve the authenticated JavaScript/source-map snapshot through an exact module loader. The current invocation therefore never reopens artifact entries or chunks after validation; ordinary bare npm dependencies remain anchored to the installed owner and must remain unchanged while the command runs. Compiler-owned Standard imports retained by a verified artifact are part of its loaded runtime snapshot. The CLI merges them with source and lowering runtime requirements, closes the target implementation graph once, and uses that same set for sandbox execution and deployable output. Framework-free and Node directory build close compiled modules, embedded JavaScript, and checked JSON wrappers through one shared ESM graph when the project uses JavaScript dependencies. Static npm imports and literal require calls resolve from their original physical importer, preserving nested package versions and shared module state. The output includes an ESM package.json, so it runs independently of the source project's package type. Bundled inputs are captured and checked against the output boundary before directory recovery or staging starts; materialization writes the captured bytes. Generated embedded JavaScript follows the same static-loader rules as ordinary JavaScript inputs. Framework-free and Node build also bundle each frozen artifact's statically discoverable JavaScript dependency graph from that artifact's physical package owner. Nested package versions therefore keep their own identities instead of being flattened to the application. Production rejects computed dynamic imports, indirect CommonJS loaders, createRequire, process.getBuiltinModule, and unresolved non-builtin edges. Literal CommonJS loads of Node builtins execute through a generated ESM compatibility bridge after that graph proof. Packages that discover native modules or runtime assets outside the static JavaScript graph are not currently deployable through this build mode; a future explicit deployment contract is required. Today's package resource contract covers checked static JSON only.

verify checks that a build is actually deployable rather than merely present. For a Node application, build instead writes a standalone ESM directory with the compiled @main entry, copied public assets, the manifest-declared Server configuration, and velar-node.json; run the recorded entry with Node from that output directory. A WebSocket entry uses the same host, port, body limit, and shared HTTP/WebSocket listener after build; its pinned transport dependency is copied into the output. Top-level build.sourceMaps controls whether source-map files are retained. All commands read the same checked JSON-resource graph: dev watches and serves it, test reconstructs used package resource exports in its sandbox, browser builds bundle it, and framework-free builds copy its exact bytes and portable ESM wrapper. Package resources must follow the manifest contract in package distribution. preview serves only what verify accepted; its default port is 4173, and --port 0 binds any free port, on the same rule dev follows — the address printed at startup names the port that was bound. verify-deployment runs the same checks against a live origin. package builds the target-owned native application package for a project whose framework host implements that operation. Reusable Core/Node libraries publish source and frozen artifacts through npm; Web/Desktop component packages remain source packages. Their generated validate scripts run the appropriate build and npm pack --dry-run --json so the package contents are checked before publication. See package distribution and static deployment.

A Desktop project declares the windows it may open in desktop.windows, keyed by window kind. main is required and is the window the host opens at launch; every other kind waits for openWindow, and a kind that is not declared here is refused at the call. A kind name is lowercase words joined by single hyphens, and one application declares at most 32 of them. Every field has a default, and each one is a closed vocabulary — an unknown field is named with its exact path:

{
  "formatVersion": 2,
  "entry": "src/main.vel",
  "extensions": ["@velarscript/desktop"],
  "desktop": {
    "productName": "Example",
    "identifier": "com.example.app",
    "windows": {
      "main": {
        "title": "Example",
        "width": 1280, "height": 820,
        "minWidth": 720, "minHeight": 520,
        "titleBar": "standard",
        "material": "none"
      },
      "note-preview": {
        "style": "panel",
        "frame": false,
        "level": "floating",
        "visibleOnAllWorkspaces": true,
        "aspectRatio": 1.6,
        "resizable": false,
        "width": 512, "height": 320
      }
    },
    "services": {
      "core": { "payload": "dist/service-core", "entry": "main.js", "restart": "always" }
    },
    "permissions": {
      "files": ["project", "dropped"],
      "processes": ["git"],
      "network": ["https://api.example.com"],
      "environment": [],
      "secrets": [],
      "links": ["https", "mailto"],
      "notifications": true,
      "secureStorage": ["CLOUD_SESSION"]
    }
  }
}

title defaults to productName. titleBar is standard or hidden-inset; material is none or sidebar (macOS vibrancy, which implies the page paints no background of its own); style is window or panel, where a panel is non-activating, floats, and stays out of the window cycle; frame, resizable and visibleOnAllWorkspaces are booleans defaulting to true, true and false; level is normal or floating, and a panel defaults to floating because that is what a panel is; aspectRatio locks the width-to-height ratio when present. The older singular desktop.window was removed — velar fix migrates it.

desktop.services declares the long-running processes the product owns — at most eight, keyed by a name that follows a window kind's rule. payload is a project directory velar package copies whole into Contents/Resources/services/<name>/, and entry is a JavaScript file inside it. The only runtime is the Node.js executable the bundle already carries: no other executable is declarable, and a short-lived process is velar/process with a processes grant instead. restart is always — an exponential backoff from one second to a thirty-second cap, with five consecutive failures reaching the terminal failed — or never.

The host allocates a loopback endpoint and a 128-bit token per service, hands both to the process in VELAR_SERVICE_ENDPOINT and VELAR_SERVICE_TOKEN, and expects a WebSocket server there; the handshake frames are pinned in packages/desktop/README.md. A third variable, VELAR_SERVICE_APP_DATA, carries the application's own data directory — the path velar/desktop.appDataDirectory() answers — because a service's data root is the application's identity resolved on this machine and cannot be written into a payload at build time. Those three are the whole of what the host adds: desktop.services has no env. Services start before the renderer loads and are not awaited, and quitting sends SIGTERM and then SIGKILL thirty seconds later. A service payload is application code, so its bytes are inside desktop.build.sizeBudgetBytes and any .node or .dylib it carries is signed with the rest of the bundle. velar dev runs the same services on the system Node, in the same three variables with the same values, and converges them when the dev server closes; it does not watch or rebuild them.

desktop.permissions is eight finite allowlists and one flag, and every one of them defaults to no authority at all. A capability the manifest never declared is refused where the program calls it, with the manifest line that would grant it named in the error, and the native host asks the same question again on its own side:

Field Grants Vocabulary
files velar/fs, velar/path, and the paths a drag gesture brings in app-data, project, dropped
processes velar/process exact executable names, never paths or shell text
network velar/http and renderer navigation to an outside origin exact HTTPS origins, or exact loopback HTTP origins
environment velar/env uppercase variable names
secrets velar/http secret headers uppercase variable names
links openExternal http, https, mailto
notifications velar/notification true or false
secureStorage velar/secure-storage uppercase variable names

dropped is not a directory: it authorizes reading the files a user's own drag gesture brings in and learning their real filesystem paths — the gesture is the grant, and it lasts the session. links governs openExternal, which hands a URL to the system default handler; network separately governs what the renderer itself may reach, so a link the application opens and an origin it fetches from are two grants rather than one. secrets and secureStorage share a spelling rule and may not share a name: the first is an opaque value the environment injects, the second is a credential slot the application writes and reads in the macOS keychain, and one name naming both would be two authorities wearing one label. notifications declares intent only — the operating system still asks the user, and requestPermission() is how the application learns that answer.

Packaging a Desktop application

velar package produces a self-contained .app: the user needs nothing installed. The bundle carries one bare Node.js executable at Contents/MacOS/node, and the version is the toolchain generation's rather than the project's — velar.json has no field for it, and every application this toolchain builds carries the same interpreter. The first velar package on a machine downloads the official archive from nodejs.org, checks it against the SHASUMS256 digest this toolchain pins, and caches the executable under ~/Library/Caches/velarscript/desktop-runtimes/<version>/<platform>-<arch> (VELAR_DESKTOP_RUNTIME_CACHE moves that directory and nothing else). Later builds are offline. A cache entry whose bytes no longer match its receipt is treated as absent rather than trusted, and an offline build with nothing cached names the version and the directory to prime.

desktop.build.sizeBudgetBytes (32 MiB by default) governs the application's components — native host, renderer, capability host, metadata. The runtime is not one of them: no project change removes or shrinks it, so it is reported separately and held to a fixed 200 MiB integrity ceiling the toolchain owns.

The bundle is always signed, because an arm64 Mach-O with no signature cannot be executed. desktop.build.signing carries the three answers that belong to the product, and nothing about the mechanics:

{
  "desktop": {
    "build": {
      "signing": {
        "identity": "Developer ID Application: Example Inc (TEAMID1234)",
        "entitlements": "build/app.entitlements",
        "notarization": { "keychainProfile": "example-notary" }
      }
    }
  }
}
Field Meaning
identity The codesign identity. Omit it for an ad-hoc signature, which is what a local build gets; writing "-" is refused, because absence already means ad-hoc.
entitlements A project-relative entitlements plist applied to the host and the bundle. The embedded runtime is signed separately with the language's own minimal file.
notarization.keychainProfile A profile xcrun notarytool store-credentials already stored. It is a reference the local keychain resolves, so no credential reaches the manifest, the build log, or the build receipt. Requires identity: Apple does not notarize an ad-hoc signature.

Signing runs inside-out — the runtime first, then the host, then the bundle — because macOS seals a bundle from its leaves inward and re-signing the bundle invalidates anything signed after it. The runtime's own entitlements are supplied by the language and contain exactly one key, com.apple.security.cs.allow-jit: without it the hardened runtime refuses V8 its code range, and the application dies on its first capability call rather than at build time. The file is written beside the build manifest as velar-desktop-runtime.entitlements so the signature is auditable.

velar-desktop-build.json is formatVersion 4. Its runtime is {"kind": "embedded-node", "version", "embedded": true, "bytes", "sha256"}, where sha256 is the official archive digest that was verified — provenance, not a hash of the shipped bytes, which this build's own signature has already changed. services lists each declared service with its entry, restart policy, byte count and the digest of the entry file the host will run. sizes reports applicationBytes and runtimeBytes beside the component breakdown — including servicesBytes, which is inside the application's budget — and signing records the mode and whether the build was notarized, never by whom. There is no reader for version 3.

The packaged host accepts --headless-smoke: it starts, launches the capability worker on whichever runtime it resolved, completes one real capability round-trip, starts every declared service and waits for each to answer the authenticated handshake, converges them, prints what answered, and exits 0. That is the packaging gate's acceptance, and it is the only thing this host calls a smoke. --verify-bundle is the static check beside it: the bundle is complete and a runtime resolves. It cannot be an acceptance, because resolving a runtime means asking node --version, which returns before V8 has created an isolate — a bundle whose interpreter cannot execute JavaScript passes it. The check was once called --smoke, which is why it was believed; the flag now says what it does and there is no alias for the old spelling.

Build output is fixed by the toolchain, not by the machine that runs it. Project modules are ordered by UTF-16 code unit over their POSIX-normalized project-relative path, and that order decides the concatenated stylesheet's bytes, its content hash, and buildId, so two builds of the same source agree whatever LANG or LC_ALL the build machine has set. The manifest inventory, the package lists in velar-build.json, and every other list the toolchain promises to reproduce use that same order — the order a plain Array.prototype.sort gives, which is the order verify checks against.

velar-deploy.json states its headers rules in order, and they resolve last match wins. The <base>* rule carries the security headers and Cache-Control: no-cache; the later <base>assets/* rule overrides that with public, max-age=31536000, immutable for the content-hashed output, and the enumerated document paths restate no-cache. A provider that applies first-match-wins instead would give the hashed assets no-cache, so project the rules in the order the manifest states them. Because <base>* states the document rule itself rather than preview supplying it out of band, every file outside assets/ — public JSON, images, and fonts included — answers no-cache, a provider that projects the manifest literally is indistinguishable from preview, and verify-deployment checks the resolved value for every file it fetches.

Projects and dependencies

velar create <project-directory> [--template <web|node|desktop|docs|library|component>]
velar install
velar add <package[@version]>... [--dev]
velar remove <package>...
velar update [package...]

npm still owns dependency resolution and the lockfile. These commands add a project-aware surface on top of it and keep extension activation in velar.json synchronized — they do not replace npm, and they do not introduce a second registry. Details in project lifecycle.

create writes the directory's own name into velar.json as name, the field a deployed Node or Server output is identified by. A manifest name is text a person reads, not an npm package name: any non-empty string of at most 100 characters (UTF-16 code units), with no control characters and no leading or trailing whitespace, and a manifest that breaks that rule is refused by the rule rather than by the clause that failed. What create derives is the directory name with control characters removed and the ends trimmed, then cut to 100 code units — nothing else: spaces, dots and non-ASCII letters are kept as written, so a directory called my app gets "name": "my app" and one called ... gets "name": "...". Only a directory name that is empty after that — whitespace and control characters alone — falls back to velar-app. The name in the package.json beside it is a separate field under npm's rules, and the two are not required to match.

A manifest this toolchain cannot read is reported in the direction it is wrong in. unsupported formatVersion 3: newer than this toolchain supports (2); upgrade @velarscript/cli means a later toolchain wrote the project. unsupported formatVersion 1: no longer supported by this toolchain (2) means the format is one this compiler no longer reads. There is no migration command, and there is deliberately no reader for a legacy format.

A VelarScript toolchain is one generation. @velarscript/cli pins every official target extension to its own version, and a project resolves its extensions before the toolchain does, so a target extension installed from another release would otherwise load its own nested compiler with nothing in the load path able to tell — protocolVersion has never moved, and apiVersion is compared against the extension's own manifest. Every command that loads a project therefore compares the version the project resolves against the version this CLI was published against, and refuses a mismatch by name and version:

node_modules/@velarscript/node/package.json: this project resolves
@velarscript/node 0.99.0, but @velarscript/cli 0.14.2 is built against
@velarscript/node 0.14.2; a VelarScript toolchain is one generation, so install
@velarscript/node 0.14.2 or @velarscript/cli 0.99.0

The path is the manifest the resolution actually read, and the running command prefixes its own name to the line. Install either version the message names. create writes exact versions for this reason: a caret range lets npm install pair a newer target extension with the pinned CLI.

Handing work to a model

velar skill [core|web|node|server|desktop]
velar skill core topics
velar skill core <topic>
velar graph [entry.vel | project-directory] [--focus <symbol|path>] [--depth <0-6>] [--max-nodes <count>] [--max-edges <count>] [--json]

Prints one owner-specific language brief, version-locked to the installed compiler. Core is the default; framework projects load Core plus the briefs named by their generated AGENTS.md: Web, Node, Server, or Desktop. topics lists the installed Core reference sections. Read a topic such as types, collections, control, or validation before choosing a language form or introducing a helper. Each topic is derived from the canonical documents and ships with the same CLI version, so it is available offline. Use velar graph before a project-wide edit to obtain the current global semantic view; use --focus for a smaller caller/dependency neighborhood.