-
Notifications
You must be signed in to change notification settings - Fork 9
feat(cli): forge try — instant-gratification onboarding (#350) #353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
initializ-mk
wants to merge
13
commits into
main
Choose a base branch
from
feat/forge-try-350
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
3edc391
feat(cli): forge try — Phase 1 skeleton + demo preset + ephemeral sca…
initializ-mk 8f0dd02
feat(cli): forge try — Phase 2 model credential auto-resolution (#350)
initializ-mk 91705d0
feat(cli): forge try — Phase 3 in-process REPL driving the executor (…
initializ-mk 93db524
feat(cli): forge try — Phase 4 visible-loop renderer (#350)
initializ-mk 328c724
feat(cli): forge try — Phase 5 first-run polish + graduation hook (#350)
initializ-mk 177bea5
test(cli): forge try — Phase 7 command tests (#350)
initializ-mk 435d688
docs(getting-started): lead with forge try; move pipeline to ship-to-…
initializ-mk a8807bb
fix(cli): forge try — address #353 review (fail-closed egress, Ctrl-C…
initializ-mk e549d90
fix(cli): forge try — don't scaffold placeholder credential; surface …
initializ-mk dbf949d
feat(cli): forge try — colored FORGE logo + styled credential picker …
initializ-mk 820bb08
feat(cli): guarded 'forge auth logout' — clear LLM OAuth creds, refus…
initializ-mk c316de2
feat(cli): forge try — colored provider shortnames + masked paste echo
initializ-mk d544a5f
fix(cli): validate auth logout provider; clear corrupt creds (#353 re…
initializ-mk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,51 +1,83 @@ | ||
| --- | ||
| title: "Quick Start" | ||
| description: "Get a Forge agent running in under 60 seconds." | ||
| description: "Talk to a working agent in under 60 seconds with forge try." | ||
| order: 2 | ||
| --- | ||
|
|
||
| Get a Forge agent running in under 60 seconds. | ||
| Talk to a working agent, and watch it use a tool, in under 60 seconds. One command: | ||
|
|
||
| ## Why Forge? | ||
| ```bash | ||
| brew install initializ/tap/forge && forge try | ||
| ``` | ||
|
|
||
| **Instant Agent From a Single Command** | ||
| No build, no cluster, no config. `forge try` scaffolds a keyless demo agent into | ||
| a throwaway workspace, finds whatever model credential you already have (an env | ||
| key, an OpenAI sign-in, or a local Ollama), and drops you into a chat whose every | ||
| tool call and egress check renders inline. | ||
|
|
||
| Write a SKILL.md. Run `forge init`. Your agent is live. | ||
| ## What it looks like | ||
|
|
||
| The wizard configures your model provider, validates your API key, | ||
| connects Slack or Telegram, picks skills, and starts your agent. | ||
| Zero to running in under 60 seconds. | ||
| ``` | ||
| $ forge try | ||
|
|
||
| **Secure by Default** | ||
| forge try: talking to a live agent in your terminal. | ||
| No build, no cluster. Ctrl-D or /exit to quit. | ||
|
|
||
| Forge is designed for safe execution: | ||
| Using OpenAI (signed in). | ||
| Agent: quickstart · skills: weather · tools: http_request, datetime_now, math_calculate | ||
|
|
||
| * Does NOT create public tunnels | ||
| * Does NOT expose webhooks automatically | ||
| * Uses outbound-only connections (Slack Socket Mode, Telegram polling) | ||
| * Enforces outbound domain allowlists at both build-time and runtime, including subprocess HTTP via a local egress proxy | ||
| * Encrypts secrets at rest (AES-256-GCM) with per-agent isolation | ||
| * Signs build artifacts (Ed25519) for supply chain integrity | ||
| * Supports restricted network profiles with audit logging | ||
| Try: what's the weather in Tokyo? | ||
| what's 17% of 4,200? | ||
| what time is it in UTC? | ||
|
|
||
| No accidental exposure. No hidden listeners. | ||
| you › what's the weather in Tokyo, should I pack an umbrella? | ||
|
|
||
| ## Get Started in 60 Seconds | ||
| ▸ tool weather_current(location=Tokyo) | ||
| ▸ egress wttr.in ✓ allowed | ||
| ◂ 18C, light rain this evening | ||
|
|
||
| ```bash | ||
| # Install | ||
| curl -sSL https://raw.githubusercontent.com/initializ/forge/main/install.sh | bash | ||
| agent › 18C in Tokyo with light rain expected tonight. Yes, take the umbrella. | ||
|
|
||
| # Initialize a new agent (interactive wizard) | ||
| forge init my-agent | ||
| audit {"tools":["weather_current"],"egress":["wttr.in:allow"]} | ||
|
|
||
| # Run locally | ||
| cd my-agent && forge run | ||
| you › ^D | ||
|
|
||
| # Run with Telegram | ||
| forge run --with telegram | ||
| You just ran an agent whose every tool call and egress you can see and audit. | ||
| Want to keep it and make it yours? -> forge try --keep (writes ./forge-quickstart) | ||
| Then edit skills/, run it as a service with forge serve, or deploy with forge package. | ||
| ``` | ||
|
|
||
| The `forge init` wizard walks you through model provider, API key, fallback providers, tools, skills, and channel setup. Use `--non-interactive` with flags for scripted setups. | ||
| The inline `▸ tool` / `▸ egress` lines are the agent's own audit stream, the same | ||
| signal the enterprise story rests on, surfaced as the first-run view. Add `--audit` | ||
| to see the full NDJSON, or `--quiet` to hide the loop. | ||
|
|
||
| ## One-shot mode | ||
|
|
||
| For a non-interactive taste (CI, docs, a quick check): | ||
|
|
||
| ```bash | ||
| forge try --once "what's 2 + 2?" | ||
| ``` | ||
|
|
||
| ## No credential yet? | ||
|
|
||
| `forge try` resolves, in order: an explicit `--provider`/`--model`, then an env | ||
| key (`ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GEMINI_API_KEY`), then a saved | ||
| OpenAI sign-in, then a local [Ollama](https://ollama.com) daemon. With none of | ||
| those, it offers a one-time picker (sign in with OpenAI, paste a key, or use | ||
| Ollama). Nothing is written to disk unless you pass `--keep`. | ||
|
|
||
| ## The ladder | ||
|
|
||
| `forge try` is rung one. Each step adds one capability: | ||
|
|
||
| 1. **60s** — `forge try`: talk to an agent, watch it use a tool. | ||
| 2. **5 min** — `forge try --keep`, then edit `skills/<name>/SKILL.md` and add a skill. See [Your First Skill](your-first-skill.md). | ||
| 3. **15 min** — `forge serve`, tail the audit log, add a guardrail and watch it block. See [Ship to Production](ship-to-production.md). | ||
| 4. **Ship** — `forge package`: an egress-enforced container into your own cluster. See [Ship to Production](ship-to-production.md). | ||
|
|
||
| ## Next steps | ||
|
|
||
| See [Installation](installation.md) for all installation methods. | ||
| - [Your First Skill](your-first-skill.md) — teach the agent something new. | ||
| - [Ship to Production](ship-to-production.md) — the full init -> build -> package -> deploy pipeline. | ||
| - [Installation](installation.md) — every install method. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| --- | ||
| title: "Ship to Production" | ||
| description: "The full pipeline: init, skills, secrets, validate, build, package, deploy." | ||
| order: 3 | ||
| --- | ||
|
|
||
| Once [`forge try`](quick-start.md) has shown you a working agent, this is the | ||
| path to a deployable one you own: scaffold it, give it skills and secrets, | ||
| validate, build, and package it into an egress-enforced container for your own | ||
| cluster. Each step is independently runnable. | ||
|
|
||
| ## 1. Scaffold | ||
|
|
||
| ```bash | ||
| forge init my-agent | ||
| cd my-agent | ||
| ``` | ||
|
|
||
| The interactive wizard configures the model provider, validates the API key, | ||
| optionally connects a channel (Slack / Telegram), picks skills, and sets the | ||
| egress allowlist. For scripted setups, use flags with `--non-interactive`: | ||
|
|
||
| ```bash | ||
| forge init my-agent --model-provider anthropic --non-interactive | ||
| ``` | ||
|
|
||
| `forge try --keep` writes the same layout to `./forge-quickstart`, so you can | ||
| graduate the demo agent instead of starting from scratch. | ||
|
|
||
| ## 2. Add skills | ||
|
|
||
| Skills are the agent's capabilities. Install from the registry or write your own: | ||
|
|
||
| ```bash | ||
| forge skills add tavily-research # registry skill | ||
| # or author skills/<name>/SKILL.md by hand | ||
| ``` | ||
|
|
||
| See [Your First Skill](your-first-skill.md) for the SKILL.md format. | ||
|
|
||
| ## 3. Configure secrets | ||
|
|
||
| Secrets are encrypted at rest (AES-256-GCM), per-agent: | ||
|
|
||
| ```bash | ||
| forge secret set ANTHROPIC_API_KEY sk-... | ||
| forge secret set SLACK_BOT_TOKEN xoxb-... | ||
| ``` | ||
|
|
||
| ## 4. Validate | ||
|
|
||
| Catch config, egress, and policy problems before building: | ||
|
|
||
| ```bash | ||
| forge validate | ||
| ``` | ||
|
|
||
| ## 5. Run locally | ||
|
|
||
| ```bash | ||
| forge run # A2A server on :8080 | ||
| forge serve # long-running service | ||
| forge run --with slack # attach a channel | ||
| ``` | ||
|
|
||
| Tail the audit log while it runs to see tool calls, egress decisions, and | ||
| guardrail blocks, the same stream `forge try` renders inline. | ||
|
|
||
| ## 6. Build | ||
|
|
||
| Compile the agent and its dependencies into a runnable artifact. Build-time | ||
| egress allowlisting and Ed25519 artifact signing happen here: | ||
|
|
||
| ```bash | ||
| forge build | ||
| ``` | ||
|
|
||
| ## 7. Package and deploy | ||
|
|
||
| Produce an egress-enforced container image and the deployment manifests for your | ||
| cluster: | ||
|
|
||
| ```bash | ||
| forge package | ||
| ``` | ||
|
|
||
| The generated image enforces the outbound domain allowlist at runtime (including | ||
| subprocess HTTP via the local egress proxy), so the deployed agent has the same | ||
| network posture you validated locally. Deploy it with your own pipeline. | ||
|
|
||
| ## Related | ||
|
|
||
| - [Quick Start](quick-start.md) — the 60-second `forge try` on-ramp. | ||
| - [Your First Skill](your-first-skill.md) — author a SKILL.md. | ||
| - [Installation](installation.md) — every install method. |
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| package cmd | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "os" | ||
| "strings" | ||
|
|
||
| "github.com/spf13/cobra" | ||
|
|
||
| "github.com/initializ/forge/forge-core/llm/oauth" | ||
| "github.com/initializ/forge/forge-core/security" | ||
| ) | ||
|
|
||
| // authLogoutCmd removes a stored LLM OAuth credential so the next sign-in | ||
| // prompts again (e.g. to re-show the `forge try` credential picker). | ||
| // | ||
| // It is deliberately an operator/laptop command and REFUSES to run inside an | ||
| // agent runtime (a container, or when FORGE_PLATFORM_TOKEN is injected). A | ||
| // deployed agent authenticates with an injected API key or platform token, not | ||
| // this OAuth credential store, so there is nothing here for the runtime to log | ||
| // out of — and Forge should not be the tool an agent shells out to in order to | ||
| // wipe an operator's credential from inside a sandbox. This is defense in | ||
| // depth, not the primary control: an agent that can run arbitrary commands | ||
| // could `rm` the file directly, so the real mitigation is denying the agent | ||
| // write access to the operator's ~/.forge. See PR #353 discussion. | ||
| var authLogoutCmd = &cobra.Command{ | ||
| Use: "logout [provider]", | ||
| Short: "Remove a stored LLM OAuth credential (laptop/dev only)", | ||
| Long: `Delete the stored OAuth credential for an LLM provider (default: openai) | ||
| from ~/.forge/credentials and the encrypted store, so the next | ||
| 'forge init' / 'forge try' prompts you to sign in again. | ||
|
|
||
| This is an operator/laptop command. It refuses to run inside an agent | ||
| runtime (a container, or when FORGE_PLATFORM_TOKEN is set): a deployed | ||
| agent authenticates with an injected API key or platform token, not | ||
| this OAuth file, so there is nothing there to log out of.`, | ||
| Args: cobra.MaximumNArgs(1), | ||
| RunE: runAuthLogout, | ||
| SilenceUsage: true, | ||
| } | ||
|
|
||
| func runAuthLogout(cmd *cobra.Command, args []string) error { | ||
| provider := "openai" | ||
| if len(args) == 1 && strings.TrimSpace(args[0]) != "" { | ||
| provider = strings.ToLower(strings.TrimSpace(args[0])) | ||
| } | ||
|
|
||
| // Validate against the known providers BEFORE the value reaches the | ||
| // credential store, whose provider->path mapping is | ||
| // filepath.Join(dir, provider+".json") with no sanitization — so an | ||
| // unchecked arg like "../../../../tmp/x" would delete /tmp/x.json. Same | ||
| // whitelist the paste-key path uses. | ||
| if _, ok := providerKeyEnv[provider]; !ok { | ||
| return fmt.Errorf("unknown provider %q (use openai, anthropic, or gemini)", provider) | ||
| } | ||
|
|
||
| if reason, denied := deniedInAgentRuntime(); denied { | ||
| return fmt.Errorf("refusing to log out %s: %s. "+ | ||
| "`auth logout` is a laptop/dev command; a deployed agent uses an injected "+ | ||
| "key or platform token, not the OAuth credential store", provider, reason) | ||
| } | ||
|
|
||
| out := cmd.OutOrStdout() | ||
| // Only report "nothing to do" when the store is DEFINITIVELY empty. On a | ||
| // read error (e.g. a corrupt token file) fall through to delete so logout | ||
| // still clears it, rather than silently leaving it in place. | ||
| if tok, err := oauth.LoadCredentials(provider); err == nil && tok == nil { | ||
| _, _ = fmt.Fprintf(out, "No %s credential stored; nothing to do.\n", provider) | ||
| return nil | ||
| } | ||
| if err := oauth.DeleteCredentials(provider); err != nil { | ||
| return fmt.Errorf("removing %s credentials: %w", provider, err) | ||
| } | ||
| _, _ = fmt.Fprintf(out, "Logged out of %s. The next sign-in will prompt again.\n", provider) | ||
| return nil | ||
| } | ||
|
|
||
| // deniedInAgentRuntime reports whether the process looks like a deployed agent | ||
| // runtime (a container, or a managed deployment with a platform token) rather | ||
| // than an operator's laptop, along with a human reason. Sensitive operator-only | ||
| // auth commands refuse there. | ||
| func deniedInAgentRuntime() (reason string, denied bool) { | ||
| if os.Getenv("FORGE_PLATFORM_TOKEN") != "" { | ||
| return "FORGE_PLATFORM_TOKEN is set (managed deployment)", true | ||
| } | ||
| if security.InContainer() { | ||
| return "running inside a container", true | ||
| } | ||
| return "", false | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Low (path traversal via unvalidated provider).
provideris taken raw fromargs[0]and passed tooauth.LoadCredentials(L55) andoauth.DeleteCredentials(L59), both of which map it tofilepath.Join(dir, provider+".json")with no sanitization (store.go:311,removePlaintextFileis always reached). So:Severity is Low — laptop/dev-only (the
deniedInAgentRuntimeguard blocks the container/platform case, so this isn't agent-reachable), operator-invoked, only.json-suffixed files the user already owns, no priv-esc. But it's a real validation gap and inconsistent with the paste-key path this same PR just hardened withproviderFromInput. Close it at this entry point:Defense-in-depth, the
store.goprovider→path helpers should also reject path separators (they're the pre-existing traversal-capable code shared by save/load/remove), but validating here closes the newly-reachable CLI vector. ATestAuthLogout_RejectsTraversalProvidercase would pin it.