@aiconnect/nexus-forge
v0.2.6
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.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 --profile <name>CLI flagFORGE_S3CLI_PROFILEenv varconfig.json→s3cli.profile- Default:
forge-data
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>] [--profile <profile>]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>] [--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>]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)
│ ├── lib/ Core logic — S3 delegation, bundle loader, frontmatter parser, line ending normalization
│ ├── types/ TypeScript type definitions and Zod schemas
│ └── integration/ Integration tests
├── dist/ Compiled output published to npm (generated; not committed)
├── docs/
│ └── prd.md Full Product Requirements Document
├── 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.
