Repository navigation
feat(context): nan context ls|show|create|push|pull|edit|rm (agent contexts C1) - #62
Merged
Merged
Conversation
…ntexts C1) The CLI side of agent contexts (helmcode/nan-devops AGENT-CONTEXT.md §6, C1), against the /v1/contexts API merged in cloud-api (#391, #395). - internal/contexts/path.go is a verbatim copy of cloud-api's validator, with its parity table (path_test.go) and a test of the shared pins. - push: walks DIR, NFC-normalises names (macOS gives NFD), skips dotfiles with a notice, prints every refused file with its reason and exits 65 before uploading unless --skip-invalid (CLAUDE.md gets a rename hint), checks the version limits, hashes, asks blobs/check, uploads only missing blobs (4 at a time; single PUT up to 16 MiB, multipart above, resumed within a push via the upload status and across pushes via a remembered upload id), publishes with base_version and an Idempotency-Key. Handles blob_missing (re-check, re-upload), version_conflict (exit 1, or --force shows the diff and rebases), context_secret_detected (--allow-secrets), per-file refusals and Retry-After (up to 2 minutes). --dry-run prints the add/change/remove list and the bytes to upload. - pull: refuses a non-empty DIR (--force overrides), validates every server path with the shared validator before writing anything, writes through os.OpenRoot, pins downloads to the version read and verifies sha256/size. - golang.org/x/text becomes a direct dependency at v0.39.0. CI gains a job (scripts/check-cloud-api-pins.sh, secret CLOUD_API_READ_TOKEN) comparing x/text, the Go toolchain's Unicode tables and path.go with cloud-api. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nan-cli is public and cloud-api private: cloud-api's CI reads this repository's go.mod and path.go without a token, so the check lives there and no secret is added here. The unit test keeps pinning golang.org/x/text v0.39.0 and the Unicode tables (15.0.0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Security: - A DIR that is a symlink is resolved once and its target walked; WalkDir alone saw one non-directory and push published an empty tree. - push refuses to publish no files over a version that has some unless --allow-empty (also after a --force rebase). - Files are hashed and uploaded through os.OpenRoot on DIR and refused if they stopped being regular files. - pull downloads into a temporary name, verifies, sets the mode both ways and renames into place: a failed download never deletes a member's file, and a link or folder at a file's path is never replaced. The total of a version is capped at 250 MiB; a partial pull says how many files were written and points at --force. - NAME|id is checked locally (context name or id) before any request. - Remembered multipart uploads are keyed by API host and credential hash. QA: - Tests for a symlinked folder inside DIR and a symlinked file on pull, the blob_missing re-check body and single PUT, the version limits (31 skills, 1001 files, 250 MiB, 100 MiB per file, SKILL.md 64 KiB), aborting an unusable remembered upload, forgetting an upload dropped mid-parts, exit mappings (entitlement_required and others), the inherited executable bit, pull --force modes, the total cap and local ref checks. - Unchanged files keep the remote executable bit where the file system has none (Windows). - Dead ContextVersions/ContextVersionInfo removed. - The fake follows cloud-api's publish check order, decodes strictly and answers 411 without Content-Length. - Help says API keys are not used here; pins comment wrapped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The CLI side of agent contexts (helmcode/nan-devops
AGENT-CONTEXT.md§6, row C1), against the/v1/contextsAPI merged in cloud-api (#391 uploads, #395 CRUD/publish/download; stillCONTEXTS_ENABLED=falsein prod, so these commands answer "not available for your account yet" there).internal/contexts/path.go: verbatim copy of cloud-api's validator (cloud-api30af3381), plus its test table (path_test.go, the middleware test left out) and a pins test.internal/api/contexts.go: client for every route;ContextErrorkeeps the top-level extras (current_version,sha256,files,findings,used/quota,automations) andRetry-After, and unwraps toAPIError.cmd/context.go:--skip-invalid(CLAUDE.md gets a rename hint; local reasonssymlink,too_large,skill_invalidadded), checks version limits, hashes,blobs/check, uploads only missing blobs (4 at a time; single PUT up to 16 MiB, multipart above with resume via upload status in a push and via a remembered upload id in the user cache dir across pushes), publishes withbase_version+Idempotency-Key.blob_missing-> re-check and re-upload;version_conflict-> exit 1, or--forceprints the diff and rebases;context_secret_detected-> findings + exit 65,--allow-secretssendsallow_secret_like; 429/upload_in_progress/blob_busy/503 wait outRetry-Afterup to 2 min (3 tries).--forceoverrides) before any request; validates every server path with the shared validator (.., absolute, case/file-folder collisions, reserved names, dotfiles, non-NFC) and writes nothing if one fails; writes viaos.OpenRoot; downloads pinned to the version read; sha256 + size verified, bad files removed.Tests (spec first, httptest fake of the real API:
cmd/context_fake_test.go)--skip-invalid, NFC from NFD input (and NFD/NFC collision on Linux), symlinks, AGENTS.md limit.--create.version_conflictmessage and--forcerebase;--allow-secrets;blob_missingre-upload;Retry-Afterhonoured (and not slept past 2 min); server per-file refusals; quota.context_in_use), surface off, name validation, help.--jsoncontract cases added tojson_output_test.go.Gates:
go build ./... && go vet ./... && go test ./...green;go test -race ./...green; gofmt clean; govulncheck: no vulnerabilities; cross-compile linux/windows/darwin OK.Review round 1 (QA + Security), fixed in 4cefc87
--allow-empty.os.OpenRoot. pull writes a temporary file, verifies it, sets the mode both ways and renames it. A failed download never removes the member's file, and an existing link or folder at a path is never replaced. Total is capped at 250 MiB. A partial pull says how many files were written.NAME|idis validated locally. The resume cache is keyed by API host + credential hash. Windows keeps the remote executable bit for unchanged files. DeadContextVersionsremoved.cmd/context_hardening_test.go+ blob_missing re-check assertions):filesdir and symlinked file on pull;upload_not_found/upload_expiredmid-parts;--forcemodes, total cap, local ref checks.path.godrift check) is handled in cloud-api #397 (Lead).Deviations / deploy notes
unicode/norm, reached by the path rules). The tables differ between the two, so exact parity matters: depends on helmcode/nan-cloud-api#397 (x/text to v0.39.0, Go stays 1.25.0), merge it first.internal/contexts/pins_test.go, which pins x/text v0.39.0 and Unicode tables 15.0.0. Go 1.25 (cloud-api) and 1.26.10 (nan-cli, GO-2026-6809) both carry Unicode 15.0.0.pull --version Nis left out: the API has no route that lists the files of an older version.nan run --contextcomes later).🤖 Generated with Claude Code