← back to Cli Printing Press
feat(cli): named-profile system for repeatable agent contexts
7140c3e72a63bcaf6057e07149e6817c3c30e267 · 2026-04-17 23:11:45 -0400 · Matt Van Horn
Every printed CLI ships a profile subcommand (save/use/list/show/delete)
backed by a JSON store at ~/.<cli>-pp-cli/profiles.json. --profile <name>
is a persistent root flag that overlays the profile's values onto flags
the caller has not set explicitly.
Precedence: explicit flag > env var > profile > default.
agent-context surfaces available_profiles so introspecting agents
discover saved personas at runtime.
Mapped from HeyGen's "Beacon" pattern (2026-04-13): same configuration
invoked day after day, different input each run.
Files touched
A docs/plans/2026-04-17-002-fix-private-library-registry-auth-plan.mdM internal/generator/generator.goM internal/generator/generator_test.goM internal/generator/templates/agent_context.go.tmplA internal/generator/templates/profile.go.tmplM internal/generator/templates/root.go.tmplM internal/generator/templates/skill.md.tmpl
Diff
commit 7140c3e72a63bcaf6057e07149e6817c3c30e267
Author: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Date: Fri Apr 17 23:11:45 2026 -0400
feat(cli): named-profile system for repeatable agent contexts
Every printed CLI ships a profile subcommand (save/use/list/show/delete)
backed by a JSON store at ~/.<cli>-pp-cli/profiles.json. --profile <name>
is a persistent root flag that overlays the profile's values onto flags
the caller has not set explicitly.
Precedence: explicit flag > env var > profile > default.
agent-context surfaces available_profiles so introspecting agents
discover saved personas at runtime.
Mapped from HeyGen's "Beacon" pattern (2026-04-13): same configuration
invoked day after day, different input each run.
---
...7-002-fix-private-library-registry-auth-plan.md | 72 +++++
internal/generator/generator.go | 1 +
internal/generator/generator_test.go | 7 +-
internal/generator/templates/agent_context.go.tmpl | 16 +-
internal/generator/templates/profile.go.tmpl | 328 +++++++++++++++++++++
internal/generator/templates/root.go.tmpl | 19 ++
internal/generator/templates/skill.md.tmpl | 14 +
7 files changed, 449 insertions(+), 8 deletions(-)
diff --git a/docs/plans/2026-04-17-002-fix-private-library-registry-auth-plan.md b/docs/plans/2026-04-17-002-fix-private-library-registry-auth-plan.md
new file mode 100644
index 00000000..01033548
--- /dev/null
+++ b/docs/plans/2026-04-17-002-fix-private-library-registry-auth-plan.md
@@ -0,0 +1,72 @@
+---
+title: "fix: Private library registry auth for mega MCP"
+type: fix
+status: active
+date: 2026-04-17
+---
+
+# fix: Private library registry auth for mega MCP
+
+## Overview
+
+The `printing-press-mcp` binary fetches `registry.json` and per-API `tools-manifest.json` files from `raw.githubusercontent.com/mvanhorn/printing-press-library/main/...` via unauthenticated HTTP. The library repo is private, so every fetch returns 404 and the mega MCP ships with an empty tool catalog. Fix: attach `Authorization: token $GITHUB_TOKEN` when the env var is set. Same URL, same response shape, just with auth.
+
+## Problem Frame
+
+Two `http.Get` calls need the header:
+
+- `FetchRegistry` in `internal/megamcp/registry.go:18`
+- `fetchManifestData` in `internal/megamcp/manifest.go:283`
+
+Single user (the maintainer), single private repo, token read from `GITHUB_TOKEN` the same way `internal/crowdsniff/github.go` already does it.
+
+## Scope Boundaries
+
+- **In scope:** Add `Authorization` header to the two fetch calls. One test that the header gets set when `GITHUB_TOKEN` is present.
+- **Out of scope:** Shared auth helper, upgraded error diagnostics, doc reconciliation, tests that the token never appears in error strings, a `library_status` meta-tool, flipping repo visibility.
+
+## Key Technical Decisions
+
+- **Env var is `GITHUB_TOKEN`.** Matches `internal/crowdsniff/github.go:19,43` convention. No new env var.
+- **Inline at each call site.** Two fetches, ~5 lines each. A shared helper adds more surface than it saves.
+- **Keep existing error shapes.** `HTTP 404` on 404 is fine. Maintainer reading their own repo knows what that means.
+
+## Implementation Units
+
+- [ ] **Unit 1: Attach GITHUB_TOKEN auth header to registry and manifest fetches**
+
+**Goal:** Both outbound GETs carry `Authorization: token <value>` when `GITHUB_TOKEN` is set. Nothing else changes.
+
+**Files:**
+- Modify: `internal/megamcp/registry.go`
+- Modify: `internal/megamcp/manifest.go`
+- Modify: `internal/megamcp/registry_test.go` (add one auth-header assertion)
+
+**Approach:**
+- In both files, replace `http.Get(url)` with a `http.NewRequest("GET", url, nil)` + `http.DefaultClient.Do(req)` pair, and set the `Authorization` header when `os.Getenv("GITHUB_TOKEN")` is non-empty.
+- Use `token <value>` format (matches the crowdsniff package, not `Bearer`).
+- Don't wrap in a helper - two call sites isn't enough to justify the indirection.
+
+**Patterns to follow:**
+- `internal/crowdsniff/github.go` for the token-read + header-set pattern.
+
+**Test scenarios:**
+- Happy path: with `GITHUB_TOKEN` set via `t.Setenv`, the `httptest.NewServer` handler observes `Authorization: token <value>` on the inbound request. (Extend an existing `registry_test.go` case - don't add a new file.)
+
+**Verification:**
+- `go test ./internal/megamcp/...` passes.
+- Rebuild (`go install ./cmd/printing-press-mcp`), restart Claude Code, `library_info` returns a non-empty API list.
+
+## Risks & Dependencies
+
+| Risk | Mitigation |
+|------|------------|
+| Token missing or scope-insufficient still returns 404, with no hint why | Accepted. Maintainer context - you know what 404 means on a private repo read. |
+| Future contributor adds a third fetch and forgets the header | Accepted for now. Revisit if a third fetch path lands. |
+
+## Sources & References
+
+- `internal/megamcp/registry.go:15-39`
+- `internal/megamcp/manifest.go:282-299`
+- `internal/crowdsniff/github.go:17-44`
+- Repo visibility: `gh repo view mvanhorn/printing-press-library --json visibility` -> `PRIVATE` as of 2026-04-17.
diff --git a/internal/generator/generator.go b/internal/generator/generator.go
index 37d2b3ec..fcb1c1c1 100644
--- a/internal/generator/generator.go
+++ b/internal/generator/generator.go
@@ -659,6 +659,7 @@ func (g *Generator) Generate() error {
"helpers.go.tmpl": filepath.Join("internal", "cli", "helpers.go"),
"doctor.go.tmpl": filepath.Join("internal", "cli", "doctor.go"),
"agent_context.go.tmpl": filepath.Join("internal", "cli", "agent_context.go"),
+ "profile.go.tmpl": filepath.Join("internal", "cli", "profile.go"),
"config.go.tmpl": filepath.Join("internal", "config", "config.go"),
"cache.go.tmpl": filepath.Join("internal", "cache", "cache.go"),
"client.go.tmpl": filepath.Join("internal", "client", "client.go"),
diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go
index 4e98b587..bdbaed21 100644
--- a/internal/generator/generator_test.go
+++ b/internal/generator/generator_test.go
@@ -26,9 +26,10 @@ func TestGenerateProjectsCompile(t *testing.T) {
}{
// +3 for cliutil package: fanout.go, text.go, cliutil_test.go
// +1 for internal/cli/agent_context.go (Cloudflare-style runtime introspection)
- {name: "stytch", specPath: filepath.Join("..", "..", "testdata", "stytch.yaml"), expectedFiles: 36},
- {name: "clerk", specPath: filepath.Join("..", "..", "testdata", "clerk.yaml"), expectedFiles: 40},
- {name: "loops", specPath: filepath.Join("..", "..", "testdata", "loops.yaml"), expectedFiles: 40},
+ // +1 for internal/cli/profile.go (HeyGen-style named-profile system)
+ {name: "stytch", specPath: filepath.Join("..", "..", "testdata", "stytch.yaml"), expectedFiles: 37},
+ {name: "clerk", specPath: filepath.Join("..", "..", "testdata", "clerk.yaml"), expectedFiles: 41},
+ {name: "loops", specPath: filepath.Join("..", "..", "testdata", "loops.yaml"), expectedFiles: 41},
}
for _, tt := range tests {
diff --git a/internal/generator/templates/agent_context.go.tmpl b/internal/generator/templates/agent_context.go.tmpl
index 4fb2b14b..97f691b3 100644
--- a/internal/generator/templates/agent_context.go.tmpl
+++ b/internal/generator/templates/agent_context.go.tmpl
@@ -22,10 +22,11 @@ const agentContextSchemaVersion = "1"
// (2026-04-13 Wrangler post): agents can introspect the live CLI without
// parsing --help or reading source.
type agentContext struct {
- SchemaVersion string `json:"schema_version"`
- CLI agentContextCLI `json:"cli"`
- Auth agentContextAuth `json:"auth"`
- Commands []agentContextCommand `json:"commands"`
+ SchemaVersion string `json:"schema_version"`
+ CLI agentContextCLI `json:"cli"`
+ Auth agentContextAuth `json:"auth"`
+ Commands []agentContextCommand `json:"commands"`
+ AvailableProfiles []string `json:"available_profiles"`
}
type agentContextCLI struct {
@@ -85,6 +86,10 @@ func buildAgentContext(rootCmd *cobra.Command) agentContext {
if authMode == "" {
authMode = "none"
}
+ profiles := ListProfileNames()
+ if profiles == nil {
+ profiles = []string{}
+ }
return agentContext{
SchemaVersion: agentContextSchemaVersion,
CLI: agentContextCLI{
@@ -96,7 +101,8 @@ func buildAgentContext(rootCmd *cobra.Command) agentContext {
Mode: authMode,
EnvVars: envVars,
},
- Commands: collectAgentCommands(rootCmd),
+ Commands: collectAgentCommands(rootCmd),
+ AvailableProfiles: profiles,
}
}
diff --git a/internal/generator/templates/profile.go.tmpl b/internal/generator/templates/profile.go.tmpl
new file mode 100644
index 00000000..7a785e46
--- /dev/null
+++ b/internal/generator/templates/profile.go.tmpl
@@ -0,0 +1,328 @@
+// Copyright {{currentYear}} {{.Owner}}. Licensed under Apache-2.0. See LICENSE.
+// Generated by CLI Printing Press (https://github.com/mvanhorn/cli-printing-press). DO NOT EDIT.
+
+package cli
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "github.com/spf13/cobra"
+ "github.com/spf13/pflag"
+)
+
+// Profile is a named set of flag values saved for reuse across invocations.
+// HeyGen's "Beacon" pattern: one named context that a scheduled agent reuses
+// day after day with the same voice/format but different input each run.
+type Profile struct {
+ Name string `json:"name"`
+ Description string `json:"description,omitempty"`
+ Values map[string]string `json:"values"`
+}
+
+type profileStore struct {
+ Profiles map[string]Profile `json:"profiles"`
+}
+
+func profileStorePath() (string, error) {
+ home, err := os.UserHomeDir()
+ if err != nil {
+ return "", fmt.Errorf("resolving home dir: %w", err)
+ }
+ dir := filepath.Join(home, ".{{.Name}}-pp-cli")
+ if err := os.MkdirAll(dir, 0o700); err != nil {
+ return "", fmt.Errorf("creating state dir: %w", err)
+ }
+ return filepath.Join(dir, "profiles.json"), nil
+}
+
+func loadProfileStore() (*profileStore, error) {
+ p, err := profileStorePath()
+ if err != nil {
+ return nil, err
+ }
+ data, err := os.ReadFile(p)
+ if err != nil {
+ if os.IsNotExist(err) {
+ return &profileStore{Profiles: map[string]Profile{}}, nil
+ }
+ return nil, fmt.Errorf("reading profiles: %w", err)
+ }
+ var s profileStore
+ if err := json.Unmarshal(data, &s); err != nil {
+ return nil, fmt.Errorf("parsing profiles: %w", err)
+ }
+ if s.Profiles == nil {
+ s.Profiles = map[string]Profile{}
+ }
+ return &s, nil
+}
+
+func saveProfileStore(s *profileStore) error {
+ p, err := profileStorePath()
+ if err != nil {
+ return err
+ }
+ data, err := json.MarshalIndent(s, "", " ")
+ if err != nil {
+ return fmt.Errorf("marshaling profiles: %w", err)
+ }
+ tmp := p + ".tmp"
+ if err := os.WriteFile(tmp, data, 0o600); err != nil {
+ return fmt.Errorf("writing profiles: %w", err)
+ }
+ return os.Rename(tmp, p)
+}
+
+// GetProfile returns a profile by name, or (nil, nil) if not found.
+func GetProfile(name string) (*Profile, error) {
+ s, err := loadProfileStore()
+ if err != nil {
+ return nil, err
+ }
+ if p, ok := s.Profiles[name]; ok {
+ return &p, nil
+ }
+ return nil, nil
+}
+
+// ApplyProfileToFlags overlays profile values onto flags that the user has
+// not set explicitly on the command line. Used from root.go's
+// PersistentPreRunE so profile values feed the whole command tree.
+func ApplyProfileToFlags(cmd *cobra.Command, profile *Profile) error {
+ if profile == nil || len(profile.Values) == 0 {
+ return nil
+ }
+ // Reserved flags that never come from a profile - they control profile
+ // resolution itself or are dangerous to overlay.
+ reserved := map[string]bool{
+ "profile": true, "config": true, "help": true,
+ }
+ for name, value := range profile.Values {
+ if reserved[name] {
+ continue
+ }
+ flag := cmd.Flags().Lookup(name)
+ if flag == nil {
+ flag = cmd.InheritedFlags().Lookup(name)
+ }
+ if flag == nil {
+ continue
+ }
+ if flag.Changed {
+ continue
+ }
+ if err := flag.Value.Set(value); err != nil {
+ return fmt.Errorf("applying profile value %s=%q: %w", name, value, err)
+ }
+ }
+ return nil
+}
+
+// ListProfileNames returns profile names sorted alphabetically. Used by the
+// agent-context subcommand to expose available_profiles at runtime.
+func ListProfileNames() []string {
+ s, err := loadProfileStore()
+ if err != nil {
+ return nil
+ }
+ names := make([]string, 0, len(s.Profiles))
+ for name := range s.Profiles {
+ names = append(names, name)
+ }
+ sort.Strings(names)
+ return names
+}
+
+func newProfileCmd(flags *rootFlags) *cobra.Command {
+ cmd := &cobra.Command{
+ Use: "profile",
+ Short: "Named sets of flags saved for reuse",
+ Long: `Profiles capture a set of flag values under a name so a scheduled
+agent can invoke the same command with the same configuration each run.
+
+ profile save <name> captures the current invocation's set flags
+ profile use <name> prints the values (for inspection)
+ profile list lists all saved profiles
+ profile show <name> shows the values of one profile
+ profile delete <name> removes a profile
+
+Use --profile <name> on any command to apply that profile's values.
+Explicit flags override profile values.`,
+ }
+ cmd.AddCommand(newProfileSaveCmd(flags))
+ cmd.AddCommand(newProfileUseCmd(flags))
+ cmd.AddCommand(newProfileListCmd(flags))
+ cmd.AddCommand(newProfileShowCmd(flags))
+ cmd.AddCommand(newProfileDeleteCmd(flags))
+ return cmd
+}
+
+func newProfileSaveCmd(flags *rootFlags) *cobra.Command {
+ var description string
+ cmd := &cobra.Command{
+ Use: "save <name> [--<flag> <value> ...]",
+ Short: "Save the current invocation's non-default flags as a named profile",
+ Long: `Captures every flag explicitly set on the invocation and stores
+them under <name>. To update an existing profile, run save again; the
+entry is replaced.
+
+To avoid creating empty profiles, at least one non-default flag must be
+present (other than --profile and --config).`,
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ name := args[0]
+ if strings.ContainsAny(name, `/\: `) {
+ return fmt.Errorf("profile name %q contains reserved characters", name)
+ }
+ values := map[string]string{}
+ // Walk inherited + local flags, capture only those the user set.
+ skip := map[string]bool{"profile": true, "config": true, "help": true, "description": true}
+ visit := func(fl *pflag.Flag) {
+ if fl.Changed && !skip[fl.Name] {
+ values[fl.Name] = fl.Value.String()
+ }
+ }
+ cmd.InheritedFlags().VisitAll(visit)
+ cmd.Flags().VisitAll(visit)
+ if len(values) == 0 {
+ return fmt.Errorf("no non-default flags set - pass at least one flag to save into %q", name)
+ }
+ s, err := loadProfileStore()
+ if err != nil {
+ return err
+ }
+ s.Profiles[name] = Profile{Name: name, Description: description, Values: values}
+ if err := saveProfileStore(s); err != nil {
+ return err
+ }
+ if flags.asJSON {
+ return flags.printJSON(cmd, s.Profiles[name])
+ }
+ fmt.Fprintf(cmd.OutOrStdout(), "saved profile %q with %d values\n", name, len(values))
+ return nil
+ },
+ }
+ cmd.Flags().StringVar(&description, "description", "", "Short description shown in 'profile list'")
+ return cmd
+}
+
+func newProfileUseCmd(flags *rootFlags) *cobra.Command {
+ return &cobra.Command{
+ Use: "use <name>",
+ Short: "Print the flag values a profile will apply (does not execute anything)",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ p, err := GetProfile(args[0])
+ if err != nil {
+ return err
+ }
+ if p == nil {
+ return fmt.Errorf("profile %q not found", args[0])
+ }
+ if flags.asJSON {
+ return flags.printJSON(cmd, p)
+ }
+ fmt.Fprintf(cmd.OutOrStdout(), "profile %q:\n", p.Name)
+ if p.Description != "" {
+ fmt.Fprintf(cmd.OutOrStdout(), " description: %s\n", p.Description)
+ }
+ keys := make([]string, 0, len(p.Values))
+ for k := range p.Values {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, k := range keys {
+ fmt.Fprintf(cmd.OutOrStdout(), " --%s %s\n", k, p.Values[k])
+ }
+ return nil
+ },
+ }
+}
+
+func newProfileListCmd(flags *rootFlags) *cobra.Command {
+ return &cobra.Command{
+ Use: "list",
+ Short: "List saved profiles",
+ RunE: func(cmd *cobra.Command, _ []string) error {
+ s, err := loadProfileStore()
+ if err != nil {
+ return err
+ }
+ names := make([]string, 0, len(s.Profiles))
+ for n := range s.Profiles {
+ names = append(names, n)
+ }
+ sort.Strings(names)
+ if flags.asJSON {
+ out := make([]map[string]any, 0, len(names))
+ for _, n := range names {
+ p := s.Profiles[n]
+ out = append(out, map[string]any{
+ "name": p.Name,
+ "description": p.Description,
+ "field_count": len(p.Values),
+ })
+ }
+ return flags.printJSON(cmd, out)
+ }
+ headers := []string{"NAME", "FIELDS", "DESCRIPTION"}
+ rows := make([][]string, 0, len(names))
+ for _, n := range names {
+ p := s.Profiles[n]
+ rows = append(rows, []string{p.Name, fmt.Sprintf("%d", len(p.Values)), p.Description})
+ }
+ return flags.printTable(cmd, headers, rows)
+ },
+ }
+}
+
+func newProfileShowCmd(flags *rootFlags) *cobra.Command {
+ return &cobra.Command{
+ Use: "show <name>",
+ Short: "Show a profile's values as JSON",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ p, err := GetProfile(args[0])
+ if err != nil {
+ return err
+ }
+ if p == nil {
+ return fmt.Errorf("profile %q not found", args[0])
+ }
+ return flags.printJSON(cmd, p)
+ },
+ }
+}
+
+func newProfileDeleteCmd(flags *rootFlags) *cobra.Command {
+ return &cobra.Command{
+ Use: "delete <name>",
+ Short: "Remove a profile",
+ Args: cobra.ExactArgs(1),
+ RunE: func(cmd *cobra.Command, args []string) error {
+ name := args[0]
+ s, err := loadProfileStore()
+ if err != nil {
+ return err
+ }
+ if _, ok := s.Profiles[name]; !ok {
+ return fmt.Errorf("profile %q not found", name)
+ }
+ if !flags.yes {
+ fmt.Fprintf(cmd.ErrOrStderr(), "refusing to delete %q without --yes\n", name)
+ return fmt.Errorf("confirmation required: pass --yes")
+ }
+ delete(s.Profiles, name)
+ if err := saveProfileStore(s); err != nil {
+ return err
+ }
+ fmt.Fprintf(cmd.OutOrStdout(), "deleted profile %q\n", name)
+ return nil
+ },
+ }
+}
diff --git a/internal/generator/templates/root.go.tmpl b/internal/generator/templates/root.go.tmpl
index 2353582c..6dbe0fb6 100644
--- a/internal/generator/templates/root.go.tmpl
+++ b/internal/generator/templates/root.go.tmpl
@@ -30,6 +30,7 @@ type rootFlags struct {
agent bool
selectFields string
configPath string
+ profileName string
timeout time.Duration
rateLimit float64
dataSource string
@@ -100,6 +101,7 @@ Run '{{.Name}}-pp-cli doctor' to verify auth and connectivity.{{backtick}},
rootCmd.PersistentFlags().BoolVar(&humanFriendly, "human-friendly", false, "Enable colored output and rich formatting")
rootCmd.PersistentFlags().BoolVar(&flags.agent, "agent", false, "Set all agent-friendly defaults (--json --compact --no-input --no-color --yes)")
rootCmd.PersistentFlags().StringVar(&flags.dataSource, "data-source", "auto", "Data source for read commands: auto (live with local fallback), live (API only), local (synced data only)")
+ rootCmd.PersistentFlags().StringVar(&flags.profileName, "profile", "", "Apply values from a saved profile (see '{{.Name}}-pp-cli profile list')")
{{- if eq .SpecSource "sniffed"}}
rootCmd.PersistentFlags().Float64Var(&flags.rateLimit, "rate-limit", 2, "Max requests per second (0 to disable, default 2 for sniffed APIs)")
{{- else}}
@@ -107,6 +109,22 @@ Run '{{.Name}}-pp-cli doctor' to verify auth and connectivity.{{backtick}},
{{- end}}
rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
+ if flags.profileName != "" {
+ profile, err := GetProfile(flags.profileName)
+ if err != nil {
+ return err
+ }
+ if profile == nil {
+ available := ListProfileNames()
+ if len(available) == 0 {
+ return fmt.Errorf("profile %q not found (no profiles saved yet; run '%s profile save <name> --<flag> <value>')", flags.profileName, cmd.Root().Name())
+ }
+ return fmt.Errorf("profile %q not found; available: %s", flags.profileName, strings.Join(available, ", "))
+ }
+ if err := ApplyProfileToFlags(cmd, profile); err != nil {
+ return err
+ }
+ }
if flags.agent {
if !cmd.Flags().Changed("json") {
flags.asJSON = true
@@ -141,6 +159,7 @@ Run '{{.Name}}-pp-cli doctor' to verify auth and connectivity.{{backtick}},
rootCmd.AddCommand(newDoctorCmd(&flags))
rootCmd.AddCommand(newAuthCmd(&flags))
rootCmd.AddCommand(newAgentContextCmd(rootCmd))
+ rootCmd.AddCommand(newProfileCmd(&flags))
{{- if .HasAsyncJobs}}
rootCmd.AddCommand(newJobsCmd(&flags))
{{- end}}
diff --git a/internal/generator/templates/skill.md.tmpl b/internal/generator/templates/skill.md.tmpl
index 0969497c..ccf56bc6 100644
--- a/internal/generator/templates/skill.md.tmpl
+++ b/internal/generator/templates/skill.md.tmpl
@@ -146,6 +146,20 @@ Add `--agent` to any command. Expands to: `--json --compact --no-input --no-colo
- **Cacheable** — GET responses cached for 5 minutes, bypass with `--no-cache`
- **Non-interactive** — never prompts, every input is a flag
+## Named Profiles
+
+A profile is a saved set of flag values, reused across invocations. Use it when a scheduled agent calls the same command every run with the same configuration - HeyGen's "Beacon" pattern.
+
+```
+<cli>-pp-cli profile save briefing --<flag> <value> ...
+<cli>-pp-cli --profile briefing <command> ...
+<cli>-pp-cli profile list [--json]
+<cli>-pp-cli profile show briefing
+<cli>-pp-cli profile delete briefing --yes
+```
+
+Explicit flags always win over profile values; profile values win over defaults. `agent-context` lists all available profiles under `available_profiles` so introspecting agents discover them at runtime.
+
## Async Jobs
For endpoints that submit long-running work, the generator detects the submit-then-poll pattern (a `job_id`/`task_id`/`operation_id` field in the response plus a sibling status endpoint) and wires up three extra flags on the submitting command:
← 1c172850 feat(cli): async-job detection, --wait flag, jobs command
·
back to Cli Printing Press
·
feat(cli): --deliver routes command output to file or webhoo f6e74931 →