@aiconnect/nexus-forge
v0.7.2
Published
Agent-native CLI for managing Skills and Instructions lifecycle for Nexus AI agents
Maintainers
Readme
Nexus Forge
Agent-native CLI for managing the lifecycle of Skills and Instructions for Nexus AI agents. All S3 operations are delegated to s3cli via --profile forge-data.
Every command outputs structured JSON with _apiVersion, ok, errorCode, and remediation fields, designed for LLM/agent consumption rather than human readability.
What's new in v0.3
If you're upgrading from v0.2, read this section first — it covers the web distribution pipeline shipped across four cycles.
Web distribution 🚀
The Forge Web Distribution layer lets you share instructions publicly and generate ephemeral download links — all served through a read-only, hardened web app.
forge instruction share <name> --vanity <slug>— publish an instruction to a public URL at/<domain>/i/<slug>. Useunshareto take it down.forge instruction share skill|instruction <name>— generate a time-limited download link with a cryptographically strong token (--expires-indefault 24h). Token hash stored in S3, never the raw token. Userevokefor immediate invalidation.forge share list— enumerate all active ephemeral links and perennial publications in one command.GET /i/:vanity— public, unauthenticated web endpoint serving rendered markdown (HTML with inline styling) or raw text (?raw=1).GET /d/:slug?t=<token>— ephemeral download: skills streamed as.skillzip bundles, instructions as plain text.GET /healthz— S3-backed readiness probe (realheadObject), not a stub. Returns 503NOT_READYif S3 is unreachable.- Size guard (NFR-7) — 50 MB served-content ceiling (configurable via
FORGE_WEB_MAX_BYTES). Over-cap → HTTP 413PAYLOAD_TOO_LARGE. - Token-safe request logging (NFR-9) — structured JSON lines per request with method, route, result, status, bytes — never logs tokens, URLs, or auth headers.
- Dockerfile — multi-stage
node:22-alpine, non-root user,HEALTHCHECKpolling/healthz, ready for production. - Operational docs — read-only IAM policy (
docs/web-read-only-iam-policy.json), DNS/TLS guidance, credential rotation procedure (docs/web-deployment.md).
What's new in v0.2
If you're upgrading from v0.1, read this section.
- Dual-format bundles — publish skills in either Forge-native (
manifest.yaml+SKILL.md) or ClawHub-native (SKILL.mdwith YAML frontmatter) format. Existing ClawHub skills publish via forge with zero conversion. forge skill init/instruction init— scaffold a new bundle from the CLI. Default format is ClawHub.forge bucket init+bucket show+bucket validate— provision a new S3 bucket as a forge-data store from the CLI. Replaces hand-crafting_forge/manifest.jsonvia console.forge config init— bootstrap your local config and (optionally) the s3cli profile template.forge schema show <type>— inspect the contract for any document type from the CLI.- Examples in every
--help— first-run path is discoverable without reading source or PRD. - Reason-aware bucket remediation —
BUCKET_MANIFEST_INVALIDnow returns a different remediation per failure reason (actionable for empty buckets, hybrid for populated buckets needing adoption, protective for genuinely-wrong buckets). v0.1 returned the same generic "do not mutate" string for all reasons. bundleFormatechoed across the lifecycle — every bundle-touching response (publish,download,verify,list,rollback) carriesbundleFormat: "forge" | "clawhub".- Line ending normalization — bundles are normalized to LF on store;
bundleChecksumis computed over the normalized bytes. Cross-platform identity preserved. - Breaking:
update→publish.forge skill updateandforge instruction updateare removed. Invoking them returns a structuredCOMMAND_RENAMEDJSON error envelope pointing topublish(so LLM-driven callers can self-correct on first try).
See openspec/archive/v02-onboarding-ux/ for the full design rationale.
What's new in v0.2.1
v0.2.1 is a stability release addressing crashes, contract drifts, and rough edges found during agent-native integration testing.
Crashes fixed (3)
forge skill listreturns 0 items — S3 delimiter issue caused empty results on aiconnect.direct API. Fixed by removing--delimiter /from listing calls and adding object-key fallback extraction. Also affectsinstruction list,namespace list, andbucket show.forge skill update/instruction updatestubs crash — Stub handlers used 2-arg(_opts, command)signature but Commander 13 calls with 3 args(name, opts, command). Fixed to match the already-correctedpublishhandler pattern.forge schema showcrash — Same Commander 13 handler signature issue.
Contract drifts fixed (6)
namespacedefaults togeneral— Reversal of MISSING_NAMESPACE error; aligns with the agent-native principle that namespace should be a convenience, not a gate.rollbackresponse shape — AddedbundleFormatandbundleMetadatato rollback responses, matching the rest of the lifecycle.publishaction unified topublished— Was sometimespublished, sometimesupdated. Now alwayspublished.- Frontmatter errors use
INVALID_FRONTMATTER— Instead ofINVALID_SKILL_BUNDLEfor YAML parse failures. frontmatterMismatchmoved todetails.frontmatterMismatch— Previously leaked into top-level response.- Line endings warning moved to
details.lineEndingsNormalized— Always present when normalization occurred, never in the top-levelwarningsarray.
Polish (4)
config showresolution chain — AddedresolutionChainblock showing how each setting was resolved (cli-flag → env-var → config-file → default).- Duplicate warning suppression — Same warning category no longer repeats across the response.
verifyflag parity —--fromreplaces--localonskill verifyandinstruction verify(matchingpublish).--localkept as hidden alias.bucket initorg-id is an option, not a required argument — Better error envelope with remediation.
Tests
477 tests (29 suites), TypeScript compiles clean.
Get started in 60 seconds
# Admin step — skip if your org admin already provisioned the bucket:
forge bucket init --org-id my-org # one-time, per org
# User flow:
forge config init # one-time setup
forge skill init my-first-skill # scaffold a bundle (ClawHub format)
forge skill publish my-first-skill \
--from ./my-first-skill \
--namespace my-team \
--dry-run # validate without publishing
forge skill publish my-first-skill \
--from ./my-first-skill \
--namespace my-team # publish for realTip: forge schema show clawhub-frontmatter to see the bundle contract.
Quick start
# Run without installing — requires Node.js 22+ and a configured s3cli profile
npx @aiconnect/nexus-forge --help
npx @aiconnect/nexus-forge config show
npx @aiconnect/nexus-forge skill list --all-namespacesPrerequisites
- Node.js 22+
- s3cli v1.4.1+ installed and configured with
--profile forge-data
s3cli configuration
The fastest path is forge config init, which writes a profile template at ~/.config/s3cli/forge-data.env if one doesn't exist (does not populate credentials — leaves placeholders you fill in).
To configure manually:
mkdir -p ~/.config/s3cli
cat > ~/.config/s3cli/forge-data.env << 'EOF'
S3_ENDPOINT=<your-s3-endpoint>
S3_ACCESS_KEY=<your-access-key>
S3_SECRET_KEY=<your-secret-key>
S3_BUCKET=<your-bucket>
S3_REGION=auto
S3_USE_SSL=true
S3_PATH_STYLE=true # set false if using AWS S3 native
EOFVerify:
s3cli --profile forge-data lsForge resolves the s3cli profile from (in order):
FORGE_S3CLI_PROFILEenv var.env(or<profile>.env) →FORGE_S3CLI_PROFILE- Default:
forge-data
Note: the global
--profileflag selects the forge config profile (which.envto read), not the s3cli profile. Seeforge config show.
Installation
Run on demand with npx (recommended)
No installation required. npx downloads the package on first use and caches it locally:
npx @aiconnect/nexus-forge <command> [options]Install globally (optional)
For frequent use, install globally to expose the forge binary on your PATH:
npm install -g @aiconnect/nexus-forge
forge --helpPin a version (CI / scripts)
For reproducible automation, pin to an explicit version:
npx @aiconnect/[email protected] skill list --namespace my-nsBundle formats
Forge accepts two equivalent bundle formats. Forge stores what you publish — no normalization, no rewriting between formats.
Forge-native
my-skill/
├── manifest.yaml (required — name, namespace, description, kind: skill)
├── SKILL.md (required — the skill content)
└── <other files> (optional — scripts/, fixtures, etc.)forge skill init my-skill --format forge scaffolds this layout.
ClawHub-native
my-skill/
├── SKILL.md (required — content with YAML frontmatter at top)
└── <other files> (optional)The frontmatter at the top of SKILL.md:
---
name: my-skill
description: One-liner about what the skill does and when to use it.
version: 1.0.0
metadata:
openclaw:
requires:
bins:
- bash
envVars:
- name: MY_API_KEY
required: true
---
# My skill
...content...Forge extracts only name and description from the frontmatter for its own metadata indexing. Everything else — version, the entire metadata.openclaw block, any custom keys — is preserved bit-perfect in S3 for the OpenClaw runtime to consume on load. Forge does not validate or interpret the OpenClaw runtime metadata; that's OpenClaw's job.
forge skill init my-skill (default format) scaffolds this layout.
See forge schema show clawhub-frontmatter for the full contract Forge requires from the frontmatter.
Detection precedence
When forge resolves a bundle at --from <path>:
- If
<path>/manifest.yamlexists → forge-native format. - Else if
<path>/SKILL.mdhas YAML frontmatter with at leastnameanddescription→ ClawHub format. - Else →
INVALID_SKILL_BUNDLE.
If both manifest.yaml and frontmatter are present, manifest.yaml wins. Disagreement between the two surfaces as a details.frontmatterMismatch warning in the publish response, but does not fail the publish.
Usage
forge [global-options] <command> [subcommand] [options]forge in the examples below stands for any of the three invocation forms above (npx @aiconnect/nexus-forge, the globally installed forge binary, or ./dist/index.js when running from a local build).
Global Options
| Option | Description |
|---|---|
| --format <json\|human> | Output format (default: TTY-aware — json if stdout is not a TTY, human otherwise) |
| --quiet | Suppress stdout output |
--skip-bucket-validation exists as a hidden power-user flag for debugging; it does not appear in --help. The BUCKET_MANIFEST_INVALID remediation will reference it explicitly when relevant.
bucket
Bucket activation and inspection. Required once per org before any publish can happen.
forge bucket init --org-id <id> [--bucket-name <name>] [--adopt-existing] [--dry-run] [--force]
forge bucket show
forge bucket validateNotes:
--bucket-nameis auto-inferred from the s3cli profile'sS3_BUCKETif omitted.--adopt-existingis required when initializing a bucket that already contains skills/instructions but lacks_forge/manifest.json(e.g., migrating from v0.1, or claiming a bucket that pre-dates the manifest schema).--forcere-initializes a bucket that already has a manifest (rare; only for org-id migrations).
config
Configuration management.
forge config init [--namespace <ns>] [--no-s3cli-template] [--force] [--interactive]
forge config show [--org <org>] [--namespace <ns>]
# Both subcommands honor the global --profile <name> flag (selects <profile>.env).config init resolves the namespace from --namespace flag → FORGE_NAMESPACE env var → omit. There is no silent default to general — namespace is identity, not a credential, and config init never picks one for you. The --interactive flag enables a minimal readline prompt for first-run setup; without it, the command is fully flag-driven (agent-friendly).
namespace
Namespace listing.
forge namespace list [--with-counts]skill
Skill bundle management — scaffold, publish, download, list, verify, rollback.
forge skill init <name> [--format <forge|clawhub>] [--namespace <ns>] [--dest <path>] [--force]
forge skill publish <name> --from <path> [--namespace <ns>] [--dry-run] [--allow-secrets]
forge skill download <name> [--namespace <ns>] [--dest <dir>] [--target-version <N|current|previous>] [--force] [--no-fallback]
forge skill list [--namespace <ns>] [--all-namespaces] [--filter <substr>] [--limit <N>]
forge skill verify <name> [--namespace <ns>] [--local <path>]
forge skill rollback <name> [--namespace <ns>] [--dry-run]skill init defaults to --format clawhub (the recommended interop path). Use --format forge for the native two-file layout if you have a specific reason.
instruction
Instruction bundle management — same shape as skill, minus the format choice (instructions are forge-native only in v0.2).
forge instruction init <name> [--namespace <ns>] [--dest <path>] [--force]
forge instruction publish <name> --from <path> [--namespace <ns>] [--dry-run] [--allow-secrets]
forge instruction download <name> [--namespace <ns>] [--dest <dir>] [--version <N|current|previous>] [--force] [--no-fallback]
forge instruction list [--namespace <ns>] [--all-namespaces] [--filter <substr>] [--limit <N>]
forge instruction verify <name> [--namespace <ns>] [--local <path>]
forge instruction rollback <name> [--namespace <ns>] [--dry-run]secret-scanning
Before publishing, forge scans all bundle files for potential secrets. Detection has two severity tiers:
| Severity | Patterns | Behavior |
|----------|----------|----------|
| block | AWS keys, GitHub PATs, OpenAI/Anthropic API keys | ❌ Rejects publish (exit 60 SECRET_DETECTED) |
| warn | JWT tokens, high-entropy strings ≥ 4.0 Shannon entropy near password/secret/token/key labels | ⚠️ Emits warning, proceeds with publish |
If any block-severity match is found, the publish is rejected. If only warn-severity matches exist, the publish proceeds with a structured warning listing each match.
Suppression (forge-allow-secret)
Add a comment on the line immediately above the flagged line to suppress detection. Forge recognizes four comment prefixes:
# forge-allow-secret: reason (shell, YAML, .env)
// forge-allow-secret: reason (JS, TS, MJS)
/* forge-allow-secret: reason (JS/TS block comment)
<!-- forge-allow-secret: reason (HTML)Override (--allow-secrets)
Pass --allow-secrets to publish to proceed despite any detected secret (block or warn). All matches are listed as warnings; the publish succeeds normally.
forge skill publish my-skill --from ./skills/my-skill --allow-secretsschema
Schema introspection — see the contract for any document type without leaving the CLI.
forge schema show <type> # supported types: skill-manifest, clawhub-frontmatter, instruction-meta, meta-json, bucket-manifest
forge schema show # list available typesOutput is a JSON Schema (Draft 2020-12) you can pipe to jq, feed to a validator, or read directly.
logs
Event log management.
forge logs export --since <YYYY-MM-DD> [--until <YYYY-MM-DD>] [--event <type>] [--format <json|csv>]web
Web distribution configuration.
forge web config set --domain <url> # set the public domain for share URLs
forge web config show # show current web configThe domain set here is used by forge instruction share and forge share list to produce correct public URLs (<domain>/i/<slug>).
share
Shared artifact inspection.
forge share list # list active ephemeral links and perennial publicationsOutput includes two arrays: links[] (ephemeral download links with slug, kind, name, namespace, createdAt, expiresAt, expired) and publications[] (perennial published instructions with vanity, name, namespace, createdAt, url). Token-derived fields are never surfaced.
Web App
The Forge web distribution server (web/) is a read-only ExpressJS app that serves public content directly from S3 via the AWS SDK. It is a separate deployable from the CLI.
Quick start
cd web
npm ci
npm run build
# Required env vars:
export S3_BUCKET=<forge-data-bucket>
export S3_REGION=<region> # or AWS_REGION
export S3_ENDPOINT=<optional-endpoint> # for non-AWS S3 (MinIO, R2, etc.)
node start.mjsRoutes
| Route | Method | Auth | Purpose |
|-------|--------|------|---------|
| /healthz | GET | None | Readiness probe — real S3 headObject on _forge/web/config.json |
| /i/:vanity | GET | None | Public instruction view — markdown rendered as styled HTML |
| /d/:slug?t=<token> | GET | Token | Ephemeral download — skill (.skill zip) or instruction (text) |
Environment variables
| Variable | Required | Default | Purpose |
|---|---|---|---|
| S3_BUCKET | yes | — | Forge data bucket name |
| S3_REGION / AWS_REGION | yes | — | AWS region of the bucket |
| S3_ENDPOINT | no | AWS default | Custom endpoint (MinIO, R2, LocalStack) |
| S3_PATH_STYLE | no | false | Set "true" for path-style addressing |
| PORT | no | 3000 | HTTP listen port |
| FORGE_WEB_MAX_BYTES | no | 52428800 (50 MB) | Served-content size ceiling (NFR-7) |
Docker
web/package.json ships build shortcuts (run from web/):
cd web
# Local dev image (host arch), loaded into docker:
npm run docker:build
docker run --rm -p 3000:3000 \
-e S3_BUCKET=<bucket> \
-e AWS_REGION=<region> \
-e AWS_ACCESS_KEY_ID=<key> \
-e AWS_SECRET_ACCESS_KEY=<secret> \
aiconnectcloud/nexus-forge-web:dev
# Universal multi-arch image (linux/amd64 + linux/arm64) pushed to a registry:
docker login
npm run docker:build:universal # -> aiconnectcloud/nexus-forge-web:latestA multi-arch image can't be --loaded locally, so docker:build:universal
pushes. Override the ref/tag with FORGE_WEB_IMAGE / FORGE_WEB_TAG. For a
one-command local run, cp .env.example .env and docker compose up --build
(see web/docker-compose.yml).
See docs/web-deployment.md for IAM policy, DNS/TLS guidance, and credential rotation.
Output format
Every forge command returns a structured JSON envelope. Parse it before doing anything else.
Success
{
"_apiVersion": "1.0",
"ok": true,
"<command-specific fields>": "..."
}For commands that touch a bundle (publish, download, verify, list, rollback), the response includes bundleFormat: "forge" | "clawhub". ClawHub bundles also get a bundleMetadata block with at minimum frontmatterVersion (the author's semver, if present), openclawMetadataPresent, and frontmatterKeys.
Error
{
"_apiVersion": "1.0",
"ok": false,
"errorCode": "STRING_ENUM",
"message": "human-readable description",
"remediation": "what to do next",
"details": { "...": "..." }
}The errorCode is your primary signal — match against it. The remediation is a literal next step for the agent or human. Many remediation strings reference other forge commands (e.g., INVALID_FRONTMATTER points to forge schema show clawhub-frontmatter; the manifest_missing reason of BUCKET_MANIFEST_INVALID points to forge bucket init).
Versioning
Forge uses a monotonic counter (revision 1, 2, 3, ...) for publication versioning. This is independent of any author-assigned semver in a ClawHub frontmatter version field. Both numbers appear side-by-side in publish/rollback responses:
version(top-level) — the forge revision (lifecycle, controlled by S3 state).bundleMetadata.frontmatterVersion(when present) — the author's semver (intent, controlled by the bundle's frontmatter).
forge skill rollback reverts the forge revision (lifecycle), not the semver. Both numbers appear in the rollback response so audit logs and human messaging can use the appropriate identifier.
Concurrency and idempotency
- Advisory lock — concurrent
publish/rollbackoperations on the same skill serialize via a_lockedfield in the remotemeta.jsonwith a 60-second TTL. If another agent holds the lock, you getCONCURRENT_UPDATE; wait at least 60s before retrying. - Checksum-based idempotency — re-publishing a bundle with identical content (same SHA256 of normalized files) is a
noop: no version bump, no S3 write. This makespublishsafe to retry. - Line ending normalization — files in the bundle are normalized to LF before checksum and storage. A bundle published from Windows (CRLF) and macOS (LF) yields the same
bundleChecksum. Binary files (detected by null bytes in the first 8KB) are passed through unmodified.
Repository Structure
forge/
├── src/
│ ├── index.ts CLI entry point (Commander.js program)
│ ├── commands/ Command handlers (bucket, config, namespace, skill, instruction, schema, logs, web, share)
│ ├── lib/ Core logic — S3 delegation, bundle loader, frontmatter parser, line ending normalization, share list
│ ├── types/ TypeScript type definitions and Zod schemas
│ └── integration/ Integration tests
├── skills/ Agent skills for operating forge (forge-ops)
├── web/ Web distribution server (separate deployable)
│ ├── src/ ExpressJS server, S3 wrapper, descriptor parser
│ ├── test/ supertest-based integration tests
│ ├── Dockerfile Multi-stage production image
│ └── start.mjs Entry point
├── dist/ Compiled output published to npm (generated; not committed)
├── docs/
│ ├── prd.md Full Product Requirements Document
│ ├── prd-web-distribution.md Web Distribution PRD (587 lines)
│ ├── web-deployment.md Deployment guide (IAM, DNS/TLS, credential rotation)
│ └── web-read-only-iam-policy.json Read-only S3 IAM policy
├── openspec/
│ ├── config.yaml OpenSpec project configuration
│ ├── changes/ Active changes (spec-driven workflow)
│ └── archive/ Archived/completed changes
├── AGENTS.md Agent-facing documentation and coding guidelines
└── README.md This fileDevelopment
For contributors working on Forge itself:
git clone <repo-url> forge && cd forge
npm install
npm run dev -- --help # run from sources via tsx (no build needed)
npm run build # compile TypeScript to dist/
npm run build:watch # recompile on change
npm test # run tests via Vitest
npm run lint # lint with ESLintnpm run dev invokes src/index.ts directly through tsx, so edits take effect on the next invocation — no rebuild required. Arguments after -- are forwarded to the CLI:
npm run dev -- skill list --namespace my-ns
npm run dev -- config showFor the full product specification, see docs/prd.md.
