diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index 4b49127..efa0c4c 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -8,6 +8,7 @@ on:
- "install.sh"
- "install.ps1"
- "docs/**"
+ - "internal-docs/pages/**"
- "cmd/gendocs/**"
- "internal/cli/**"
- "scripts/prepare-pages-site.py"
@@ -54,7 +55,7 @@ jobs:
run: sudo chown -R "$(id -u):$(id -g)" _site
- name: Prepare human and agent docs
- run: python3 scripts/prepare-pages-site.py --source docs --site _site --env-file docs/site.env
+ run: python3 scripts/prepare-pages-site.py --source docs --site _site --env-file internal-docs/pages/site.env
- name: Verify Pages installer freshness
shell: bash
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8244145..881a0d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -135,7 +135,7 @@ Set `AGORA_ALLOW_UPGRADE_IN_CI=1` only when a CI job intentionally needs to muta
### Added
- Add GitHub Pages publishing for generated CLI docs and route `agora open --target docs` to the human CLI docs site, `agora open --target docs-md` to the agent-facing raw Markdown docs under `/md/`, and `product-docs` to Agora product docs.
-- Add `docs/site.env` and Pages build-time URL injection so staging docs can publish with different `CLI_DOCS_BASE_URL` / `CLI_DOCS_MD_BASE_URL` values while keeping predictable human and Markdown paths.
+- Add `internal-docs/pages/site.env` and Pages build-time URL injection so staging docs can publish with different `CLI_DOCS_BASE_URL` / `CLI_DOCS_MD_BASE_URL` values while keeping predictable human and Markdown paths.
- Add a custom GitHub Pages theme for the human docs with responsive layout, system light/dark mode via `prefers-color-scheme`, and no manual theme toggle.
- Add `make docs-preview` for a Ruby/Jekyll local docs preview that builds with localhost-friendly paths, injects localhost docs URLs, and serves both the human site and `/md/` Markdown tree.
- Add global `--yes` / `-y` and `AGORA_NO_INPUT=1` support to accept defaults and suppress prompts.
@@ -164,7 +164,7 @@ Set `AGORA_ALLOW_UPGRADE_IN_CI=1` only when a CI job intentionally needs to muta
- `agora doctor` now suggests the exact shell-aware command for fixing a missing PATH entry. The `path_resolution` failure suggestion now reads `echo 'export PATH="
:$PATH"' >> ~/.zshrc && source ~/.zshrc` for zsh, the equivalent for bash and `fish_add_path` for fish, the `setx PATH ...` form on Windows, and a generic `~/.profile` fallback for unknown shells. The doctor derives `` from the running binary's location, so the command is always copy-pasteable.
- Add a curated `Requirements` and `Verifying release artifacts` section to the README, plus links to `SECURITY.md`, `SUPPORT.md`, the new troubleshooting doc, and `docs/schema/envelope.v1.json` from the Docs index.
- Add a telemetry stub (`internal/cli/telemetry.go`) with the `telemetryClient` interface, default no-op sink, redaction helper, and `sentryClient` placeholder so the next release wires Sentry by adding the SDK + replacing one constant. The on/off contract (`agora telemetry`, `AGORA_SENTRY_ENABLED`, `DO_NOT_TRACK`) is fully wired; transport is a no-op until Sentry is connected.
-- Add the proposal documents `docs/proposals/supply-chain-hardening.md`, `docs/proposals/ci-matrix-expansion.md`, and `docs/proposals/telemetry-sentry-wireup.md` for the next release.
+- Add the proposal documents `internal-docs/proposals/supply-chain-hardening.md`, `internal-docs/proposals/ci-matrix-expansion.md`, and `internal-docs/proposals/telemetry-sentry-wireup.md` for the next release.
### Changed
@@ -208,7 +208,7 @@ Set `AGORA_ALLOW_UPGRADE_IN_CI=1` only when a CI job intentionally needs to muta
- README updates land in logical sections (Install requirements, Verifying releases, Docs index, Command Model additions, Troubleshooting redirect) without disturbing the marketing-first opening.
- `CONTRIBUTING.md` documents the branching model (`main` is releasable, topic branches off `main`), the commit-message convention, the optional DCO sign-off path, and the per-command example requirement for new commands.
- `docs/automation.md` adds a section documenting the `agora project env` raw-stdout exception and links the new envelope JSON Schema.
-- `docs/proposals/` introduces a new directory for deferred-implementation proposals (supply-chain hardening, CI matrix expansion, Sentry wire-up). Each proposal carries a `status: proposed`, `target-release: next` front-matter so contributors can see what's planned without bisecting branches.
+- `internal-docs/proposals/` introduces a new directory for deferred-implementation proposals (supply-chain hardening, CI matrix expansion, Sentry wire-up). Each proposal carries a `status: proposed`, `target-release: next` front-matter so contributors can see what's planned without bisecting branches.
## [0.1.9] - 2026-04-30
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index dd732e5..91d81ad 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -72,7 +72,7 @@ make docs-preview # optional: local Jekyll site + /md
Documentation work:
- Run `make docs-commands` after command-tree changes; CI uses `go run ./cmd/gendocs -check`.
-- For GitHub Pages content, use `make docs-preview` (see `scripts/preview-pages-site.sh`). Published docs resolve `@@CLI_DOCS_*@@` and `@@CLI_INSTALL_*@@` tokens via `scripts/prepare-pages-site.py` and `docs/site.env` as documented in `docs/automation.md`.
+- For GitHub Pages content, use `make docs-preview` (see `scripts/preview-pages-site.sh`). Published docs resolve `@@CLI_DOCS_*@@` and `@@CLI_INSTALL_*@@` tokens via `scripts/prepare-pages-site.py` and `internal-docs/pages/site.env` as documented in `docs/automation.md`.
Install `golangci-lint` **v1.64.8** (matches CI). CI builds it with `go install` against your toolchain; locally prefer:
diff --git a/docs/_config.yml b/docs/_config.yml
index afc1eb8..dc5f2b5 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -11,6 +11,3 @@ defaults:
path: ""
values:
layout: default
-
-exclude:
- - site.env
diff --git a/docs/automation.md b/docs/automation.md
index d81deb8..0306d68 100644
--- a/docs/automation.md
+++ b/docs/automation.md
@@ -46,7 +46,7 @@ Use this guide for:
- Set `AGORA_AGENT=` in automated environments to explicitly label agent traffic in the API `User-Agent`. When unset, the CLI may infer a coarse label such as `cursor`, `claude-code`, `cline`, `windsurf`, `codex`, or `aider` from known agent environment markers. Set `AGORA_AGENT_DISABLE_INFER=1` to disable inference.
- Use `agora mcp serve` to expose local Agora CLI tools to MCP-capable agents. The full surface is exposed: `agora.version`, `agora.introspect`, `agora.auth.{status,logout}`, `agora.config.{path,get}`, `agora.telemetry.status`, `agora.upgrade.check`, `agora.project.{list,show,use,create,doctor,env,env_write}`, `agora.project.feature.{list,status,enable}`, `agora.project.webhook.{events,list,show,create,update,delete}`, `agora.quickstart.{list,create,env_write}`, and `agora.init`. Authentication is intentionally **not** exposed via MCP because OAuth requires an interactive browser; run `agora login` once on the host first.
- Use `agora open --target docs` for the human GitHub Pages docs and `agora open --target docs-md` for the agent-facing raw Markdown index. In CI/non-TTY runs the command defaults to URL-only output unless `--browser` is set. The Markdown tree is published under predictable `/md/` URLs, for example `/md/commands.md`, `/md/automation.md`, and `/md/error-codes.md`.
-- Docs publishing reads `docs/site.env` for `CLI_DOCS_*` and `CLI_INSTALL_*` URL defaults; staging Pages builds can override those environment variables at workflow time without changing docs content. The resolved values are published as `/docs.env` for transparency.
+- Docs publishing reads `internal-docs/pages/site.env` for `CLI_DOCS_*` and `CLI_INSTALL_*` URL defaults; staging Pages builds can override those environment variables at workflow time without changing docs content. The resolved values are published as `/docs.env` for transparency.
- The CLI maintains a short-lived on-disk completion cache for `agora project use ` under `/cache/projects.json`. The cache is only used for completions when a **local unexpired session exists** (`session.json` with a non-empty access token and a future `expiresAt`, when present), so Tab does not suggest stale project names after logout or local session expiry. The cache TTL is 5 minutes by default; override with `AGORA_PROJECT_CACHE_TTL_SECONDS=` (set to `0` to disable). Cache files older than 24 h are pruned at every CLI startup. Set `AGORA_DISABLE_CACHE=1` to drop the cache on the next startup. The cache is invalidated automatically by `agora logout` and `agora project create` (the latter clears the file; it does not embed the new project until the next successful list fetch). To **force-refresh** the cached completion page, run `agora project list --refresh-cache` while authenticated; that command fetches the unfiltered first page used by completion and rewrites `projects.json` when it succeeds.
### CI auto-detect
diff --git a/docs/telemetry.md b/docs/telemetry.md
index f3665f3..c1604c8 100644
--- a/docs/telemetry.md
+++ b/docs/telemetry.md
@@ -16,9 +16,7 @@ key pattern (case-insensitive) and replaces the value with the literal
> **Status (current release):** the on/off contract below is fully
> wired. The transport (Sentry SDK) is not yet linked into the binary,
> so all telemetry calls are no-ops at runtime. The next release will
-> wire Sentry per
-> [`docs/proposals/telemetry-sentry-wireup.md`](proposals/telemetry-sentry-wireup.md);
-> the surface and field schema will not change.
+> wire Sentry; the surface and field schema will not change.
## Inspect or change the setting
diff --git a/docs/site.env b/internal-docs/pages/site.env
similarity index 100%
rename from docs/site.env
rename to internal-docs/pages/site.env
diff --git a/docs/superpowers/plans/2026-06-07-project-webhook-implementation.md b/internal-docs/plans/2026-06-07-project-webhook-implementation.md
similarity index 96%
rename from docs/superpowers/plans/2026-06-07-project-webhook-implementation.md
rename to internal-docs/plans/2026-06-07-project-webhook-implementation.md
index eeaaded..8a381af 100644
--- a/docs/superpowers/plans/2026-06-07-project-webhook-implementation.md
+++ b/internal-docs/plans/2026-06-07-project-webhook-implementation.md
@@ -27,7 +27,7 @@
## Task 0: Backend Confirmation Check
**Files:**
-- Modify: `docs/superpowers/specs/2026-06-07-project-webhook-design.md`
+- Modify: `internal-docs/specs/2026-06-07-project-webhook-design.md`
- [ ] **Step 1: Confirm event ID mapping before production behavior is wired**
@@ -45,7 +45,7 @@ eventIds uses ncs-events.items[].eventId.
- [ ] **Step 2: Record the confirmation in the design spec**
-Edit `docs/superpowers/specs/2026-06-07-project-webhook-design.md` and replace the final sentence in the event API paragraph with:
+Edit `internal-docs/specs/2026-06-07-project-webhook-design.md` and replace the final sentence in the event API paragraph with:
```markdown
Backend owner confirmation: config `eventIds` are populated from event `eventId`, not `eventType`.
@@ -56,7 +56,7 @@ Backend owner confirmation: config `eventIds` are populated from event `eventId`
Run:
```bash
-git add docs/superpowers/specs/2026-06-07-project-webhook-design.md
+git add internal-docs/specs/2026-06-07-project-webhook-design.md
git commit -m "docs: confirm webhook event id mapping"
```
@@ -360,7 +360,9 @@ Append these tests:
```go
func TestNormalizeWebhookEventsIgnoresChineseDisplayName(t *testing.T) {
- resp := ncsEventListResponse{Items: []ncsEvent{{EventID: 1001, DisplayName: "Channel Created", DisplayNameCn: "频道创建", EventType: 7, Payload: `{"x":1}`}}}
+ resp := ncsEventListResponse{Items: []ncsEvent{
+ {EventID: 1001, DisplayName: "Channel Created", DisplayNameCn: "频道创建", EventType: 7, Payload: `{"x":1}`},
+ }}
got := normalizeWebhookEvents(resp)
if len(got) != 1 || got[0].Key != "channel-created" || got[0].ID != 1001 || got[0].EventType != 7 || got[0].Payload != `{"x":1}` {
t.Fatalf("unexpected normalized events: %#v", got)
@@ -724,7 +726,9 @@ func TestProjectWebhookUpdateReadMergePut(t *testing.T) {
defer api.server.Close()
project := buildFakeProject("demo", "prj_0001", "app_0001", "global")
api.projects[project.ProjectID] = &project
- api.ncsConfigs["prj_0001/rtc"] = []fakeNCSConfig{{ConfigID: 42, URL: "https://old.example/webhook", URLRegion: "eu", Enabled: true, EventIDs: []int{1001}, UseIPWhitelist: false, Secret: "secret_123"}}
+ api.ncsConfigs["prj_0001/rtc"] = []fakeNCSConfig{
+ {ConfigID: 42, URL: "https://old.example/webhook", URLRegion: "eu", Enabled: true, EventIDs: []int{1001}, UseIPWhitelist: false, Secret: "secret_123"},
+ }
configHome := t.TempDir()
persistSessionForIntegration(t, configHome)
result := runCLI(t, bin, map[string]string{"XDG_CONFIG_HOME": configHome, "AGORA_CLI_BASE_URL": api.baseURL}, "project", "webhook", "update", "42", "--project", "demo", "--feature", "rtc", "--url", "https://new.example/webhook", "--json")
@@ -795,7 +799,9 @@ func TestProjectWebhookListRedactsAndShowWithSecretReveals(t *testing.T) {
defer api.server.Close()
project := buildFakeProject("demo", "prj_0001", "app_0001", "global")
api.projects[project.ProjectID] = &project
- api.ncsConfigs["prj_0001/rtc"] = []fakeNCSConfig{{ConfigID: 42, URL: "https://example.com/webhook", URLRegion: "na", Enabled: true, EventIDs: []int{1001}, UseIPWhitelist: false, Secret: "secret_123"}}
+ api.ncsConfigs["prj_0001/rtc"] = []fakeNCSConfig{
+ {ConfigID: 42, URL: "https://example.com/webhook", URLRegion: "na", Enabled: true, EventIDs: []int{1001}, UseIPWhitelist: false, Secret: "secret_123"},
+ }
configHome := t.TempDir()
persistSessionForIntegration(t, configHome)
@@ -1062,7 +1068,10 @@ case "project webhook events":
}
case "project webhook list":
m := data.(map[string]any)
- printBlock(out, "Webhooks", [][2]string{{"Project", asString(m["projectName"])}, {"Feature", asString(m["feature"])}})
+ printBlock(out, "Webhooks", [][2]string{
+ {"Project", asString(m["projectName"])},
+ {"Feature", asString(m["feature"])},
+ })
if items, ok := m["items"].([]webhookConfig); ok {
for _, item := range items {
fmt.Fprintf(out, "- %d %s %s enabled=%v\n", item.ConfigID, item.URL, item.URLRegion, item.Enabled)
@@ -1077,7 +1086,12 @@ case "project webhook show", "project webhook create", "project webhook update":
}
case "project webhook delete":
m := data.(map[string]any)
- printBlock(out, "Webhook", [][2]string{{"Project", asString(m["projectName"])}, {"Feature", asString(m["feature"])}, {"Config ID", asString(m["configId"])}, {"Deleted", "true"}})
+ printBlock(out, "Webhook", [][2]string{
+ {"Project", asString(m["projectName"])},
+ {"Feature", asString(m["feature"])},
+ {"Config ID", asString(m["configId"])},
+ {"Deleted", "true"},
+ })
```
Add helper:
@@ -1093,7 +1107,17 @@ func printWebhookBlock(out io.Writer, m map[string]any) {
if len(keys) > 0 {
events = strings.Join(keys, ", ")
}
- printBlock(out, "Webhook", [][2]string{{"Project", asString(m["projectName"])}, {"Feature", asString(m["feature"])}, {"Config ID", asString(cfg.ConfigID)}, {"URL", cfg.URL}, {"Events", events}, {"Delivery Region", renderWebhookDeliveryRegion(cfg.URLRegion)}, {"Enabled", asString(cfg.Enabled)}, {"Retry", asString(cfg.Retry)}, {"Secret", cfg.Secret}})
+ printBlock(out, "Webhook", [][2]string{
+ {"Project", asString(m["projectName"])},
+ {"Feature", asString(m["feature"])},
+ {"Config ID", asString(cfg.ConfigID)},
+ {"URL", cfg.URL},
+ {"Events", events},
+ {"Delivery Region", renderWebhookDeliveryRegion(cfg.URLRegion)},
+ {"Enabled", asString(cfg.Enabled)},
+ {"Retry", asString(cfg.Retry)},
+ {"Secret", cfg.Secret},
+ })
}
}
```
diff --git a/docs/proposals/ci-matrix-expansion.md b/internal-docs/proposals/ci-matrix-expansion.md
similarity index 100%
rename from docs/proposals/ci-matrix-expansion.md
rename to internal-docs/proposals/ci-matrix-expansion.md
diff --git a/docs/proposals/supply-chain-hardening.md b/internal-docs/proposals/supply-chain-hardening.md
similarity index 100%
rename from docs/proposals/supply-chain-hardening.md
rename to internal-docs/proposals/supply-chain-hardening.md
diff --git a/docs/proposals/telemetry-sentry-wireup.md b/internal-docs/proposals/telemetry-sentry-wireup.md
similarity index 100%
rename from docs/proposals/telemetry-sentry-wireup.md
rename to internal-docs/proposals/telemetry-sentry-wireup.md
diff --git a/docs/superpowers/specs/2026-06-07-project-webhook-design.md b/internal-docs/specs/2026-06-07-project-webhook-design.md
similarity index 100%
rename from docs/superpowers/specs/2026-06-07-project-webhook-design.md
rename to internal-docs/specs/2026-06-07-project-webhook-design.md
diff --git a/internal/cli/open_targets_test.go b/internal/cli/open_targets_test.go
index c0c9c1d..b296471 100644
--- a/internal/cli/open_targets_test.go
+++ b/internal/cli/open_targets_test.go
@@ -129,24 +129,24 @@ func TestCLIDocsURLMatchesPagesWorkflow(t *testing.T) {
}
if !strings.Contains(string(body), "Prepare human and agent docs") ||
!strings.Contains(string(body), "scripts/prepare-pages-site.py") ||
- !strings.Contains(string(body), "docs/site.env") {
+ !strings.Contains(string(body), "internal-docs/pages/site.env") {
t.Fatal("pages.yml does not prepare docs with the env-file driven Pages script")
}
}
func TestPagesSiteEnvDefaultsMatchOpenTargets(t *testing.T) {
repoRoot := findRepoRootForTest(t)
- siteEnv := filepath.Join(repoRoot, "docs", "site.env")
+ siteEnv := filepath.Join(repoRoot, "internal-docs", "pages", "site.env")
body, err := os.ReadFile(siteEnv)
if err != nil {
t.Fatalf("could not read %s: %v", siteEnv, err)
}
content := string(body)
if !strings.Contains(content, "CLI_DOCS_BASE_URL="+strings.TrimSuffix(cliDocsURL, "/")) {
- t.Fatalf("docs/site.env does not match cliDocsURL %q:\n%s", cliDocsURL, content)
+ t.Fatalf("internal-docs/pages/site.env does not match cliDocsURL %q:\n%s", cliDocsURL, content)
}
if !strings.Contains(content, "CLI_DOCS_MD_BASE_URL="+strings.TrimSuffix(cliDocsURL, "/")+"/md") {
- t.Fatalf("docs/site.env does not match cliDocsMarkdownURL base %q:\n%s", cliDocsMarkdownURL, content)
+ t.Fatalf("internal-docs/pages/site.env does not match cliDocsMarkdownURL base %q:\n%s", cliDocsMarkdownURL, content)
}
}
diff --git a/internal/cli/telemetry.go b/internal/cli/telemetry.go
index f4bfbcd..6d54051 100644
--- a/internal/cli/telemetry.go
+++ b/internal/cli/telemetry.go
@@ -25,7 +25,7 @@ import (
// Today this file ships a no-op default. The Sentry-backed sink is
// scaffolded as `sentryClient` below so the next release can flip the
// constructor over without changing the public API. See
-// docs/proposals/telemetry-sentry-wireup.md for the wire-up plan.
+// internal-docs/proposals/telemetry-sentry-wireup.md for the wire-up plan.
type telemetryClient interface {
// Enabled reports whether the underlying sink will actually emit.
Enabled() bool
@@ -46,7 +46,7 @@ type telemetryClient interface {
// agoraSentryDSN is the Sentry project DSN that the CLI ships with.
// Empty string disables Sentry transport entirely (the default until the
// Sentry SDK is wired in for the next release). When set, events go to the
-// Agora CLI Sentry project (see docs/proposals/telemetry-sentry-wireup.md).
+// Agora CLI Sentry project (see internal-docs/proposals/telemetry-sentry-wireup.md).
const agoraSentryDSN = ""
// initTelemetry returns the telemetry client appropriate for the current
@@ -73,7 +73,7 @@ func initTelemetry(configEnabled bool, env map[string]string, _ versionInformati
if agoraSentryDSN == "" {
// Sentry SDK not compiled in for this build. The wire-up is
// scheduled for the next release; until then this is the
- // expected path. See docs/proposals/telemetry-sentry-wireup.md.
+ // expected path. See internal-docs/proposals/telemetry-sentry-wireup.md.
return noopTelemetry{}
}
return newSentryClient(agoraSentryDSN, env)
diff --git a/scripts/prepare-pages-site.py b/scripts/prepare-pages-site.py
index f9776f0..c2a1a62 100644
--- a/scripts/prepare-pages-site.py
+++ b/scripts/prepare-pages-site.py
@@ -3,7 +3,7 @@
The Pages workflow renders the human site with Jekyll, then this script:
-1. Reads docs/site.env for default published URLs.
+1. Reads internal-docs/pages/site.env for default published URLs.
2. Lets workflow/job env vars override those defaults for staging builds.
3. Replaces URL placeholders in rendered site files.
4. Copies raw Markdown and MDC files to _site/md/ with the same replacements.
@@ -128,7 +128,7 @@ def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source", default="docs", type=Path)
parser.add_argument("--site", default="_site", type=Path)
- parser.add_argument("--env-file", default=Path("docs/site.env"), type=Path)
+ parser.add_argument("--env-file", default=Path("internal-docs/pages/site.env"), type=Path)
parser.add_argument("--repo-root", default=Path("."), type=Path)
args = parser.parse_args()
diff --git a/scripts/preview-pages-site.sh b/scripts/preview-pages-site.sh
index 492801e..34c35e0 100755
--- a/scripts/preview-pages-site.sh
+++ b/scripts/preview-pages-site.sh
@@ -43,7 +43,7 @@ CLI_DOCS_BASE_URL="http://localhost:${PORT}" \
CLI_DOCS_MD_BASE_URL="http://localhost:${PORT}/md" \
CLI_INSTALL_SH_URL="http://localhost:${PORT}/install.sh" \
CLI_INSTALL_PS1_URL="http://localhost:${PORT}/install.ps1" \
- python3 scripts/prepare-pages-site.py --source docs --site "$SITE_DIR" --env-file docs/site.env
+ python3 scripts/prepare-pages-site.py --source docs --site "$SITE_DIR" --env-file internal-docs/pages/site.env
cat <