@arkhera30/cli 0.8.4 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2580,6 +2580,92 @@ async function syncSkills(runtime) {
2580
2580
  }
2581
2581
  }
2582
2582
  }
2583
+ var GLOBAL_SKILLS = ["horus-anvil", "horus-vault", "horus-forge", "horus-context", "capture", "triage"];
2584
+ async function fetchLatestSkillBody(controlPlaneUrl, id) {
2585
+ const base = controlPlaneUrl.replace(/\/+$/, "");
2586
+ const versionsRes = await fetch(`${base}/api/v1/forge/artifacts/skill/${id}/versions`);
2587
+ if (!versionsRes.ok) return null;
2588
+ const versionsJson = await versionsRes.json();
2589
+ const versions = versionsJson.versions ?? [];
2590
+ if (versions.length === 0) return null;
2591
+ const latest = versions.slice().sort((a, b) => {
2592
+ const pa = a.split(".").map(Number);
2593
+ const pb = b.split(".").map(Number);
2594
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
2595
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
2596
+ if (diff !== 0) return diff;
2597
+ }
2598
+ return 0;
2599
+ }).pop();
2600
+ const artifactRes = await fetch(`${base}/api/v1/forge/artifacts/skill/${id}/${latest}`);
2601
+ if (!artifactRes.ok) return null;
2602
+ const artifact = await artifactRes.json();
2603
+ const b64 = artifact.files?.["SKILL.md"];
2604
+ if (!b64) return null;
2605
+ return Buffer.from(b64, "base64").toString("utf-8");
2606
+ }
2607
+ async function syncSkillsFromRegistry(controlPlaneUrl) {
2608
+ const home = homedir3();
2609
+ const skillsBase = join2(home, ".claude", "skills");
2610
+ let synced = 0;
2611
+ const failed = [];
2612
+ for (const id of GLOBAL_SKILLS) {
2613
+ try {
2614
+ const body = await fetchLatestSkillBody(controlPlaneUrl, id);
2615
+ if (!body) {
2616
+ failed.push(id);
2617
+ continue;
2618
+ }
2619
+ const destDir = join2(skillsBase, id);
2620
+ mkdirSync2(destDir, { recursive: true });
2621
+ writeFileSync3(join2(destDir, "SKILL.md"), body, "utf-8");
2622
+ synced++;
2623
+ } catch {
2624
+ failed.push(id);
2625
+ }
2626
+ }
2627
+ if (failed.length === 0) {
2628
+ console.log(chalk.green(`\u2714 Synced ${synced} global skills from the control plane`));
2629
+ } else {
2630
+ console.log(chalk.yellow(`\u2714 Synced ${synced} global skills from the control plane`) + chalk.dim(` (failed: ${failed.join(", ")})`));
2631
+ }
2632
+ }
2633
+ async function syncSkillsForCursorFromRegistry(controlPlaneUrl) {
2634
+ const home = homedir3();
2635
+ const rulesDir = join2(home, ".cursor", "rules");
2636
+ const skillsBase = join2(home, ".cursor", "skills-cursor");
2637
+ mkdirSync2(rulesDir, { recursive: true });
2638
+ let synced = 0;
2639
+ const failed = [];
2640
+ for (const id of GLOBAL_SKILLS) {
2641
+ try {
2642
+ const body = await fetchLatestSkillBody(controlPlaneUrl, id);
2643
+ if (!body) {
2644
+ failed.push(id);
2645
+ continue;
2646
+ }
2647
+ const ruleDest = join2(rulesDir, `${id}.mdc`);
2648
+ const frontmatter = `---
2649
+ description: Horus ${id} reference
2650
+ alwaysApply: true
2651
+ ---
2652
+
2653
+ `;
2654
+ writeFileSync3(ruleDest, frontmatter + body, "utf-8");
2655
+ const skillDir = join2(skillsBase, id);
2656
+ mkdirSync2(skillDir, { recursive: true });
2657
+ writeFileSync3(join2(skillDir, "SKILL.md"), body, "utf-8");
2658
+ synced++;
2659
+ } catch {
2660
+ failed.push(id);
2661
+ }
2662
+ }
2663
+ if (failed.length === 0) {
2664
+ console.log(chalk.green(`\u2714 Synced ${synced} global skills for Cursor from the control plane`));
2665
+ } else {
2666
+ console.log(chalk.yellow(`\u2714 Synced ${synced} global skills for Cursor from the control plane`) + chalk.dim(` (failed: ${failed.join(", ")})`));
2667
+ }
2668
+ }
2583
2669
  async function syncSkillsForCursor(runtime) {
2584
2670
  const home = homedir3();
2585
2671
  const rulesDir = join2(home, ".cursor", "rules");
@@ -2692,8 +2778,14 @@ async function runConnect(config, runtime, targets, host = "localhost") {
2692
2778
  }
2693
2779
  if (targets.includes("claude-code")) {
2694
2780
  if (connectedMode) {
2695
- console.warn(chalk.yellow("[connect] Skipping skills sync \u2014 connected mode has no local forge container."));
2696
- console.log(chalk.dim(" TODO: fetch skills via the control plane when the skills API is available."));
2781
+ const skillsSpinner = ora("Syncing horus-core skills from the control plane...").start();
2782
+ try {
2783
+ await syncSkillsFromRegistry(config.control_plane_url);
2784
+ skillsSpinner.succeed("horus-core skills synced to ~/.claude/skills/");
2785
+ } catch (error) {
2786
+ skillsSpinner.warn("Could not sync skills from the control plane");
2787
+ console.log(chalk.dim(error.message));
2788
+ }
2697
2789
  } else {
2698
2790
  const skillsSpinner = ora("Syncing horus-core skills...").start();
2699
2791
  try {
@@ -2707,7 +2799,14 @@ async function runConnect(config, runtime, targets, host = "localhost") {
2707
2799
  }
2708
2800
  if (targets.includes("cursor")) {
2709
2801
  if (connectedMode) {
2710
- console.warn(chalk.yellow("[connect] Skipping Cursor rules sync \u2014 connected mode has no local forge container."));
2802
+ const cursorRulesSpinner = ora("Syncing horus-core rules for Cursor from the control plane...").start();
2803
+ try {
2804
+ await syncSkillsForCursorFromRegistry(config.control_plane_url);
2805
+ cursorRulesSpinner.succeed("horus-core rules synced to ~/.cursor/rules/ and skills to ~/.cursor/skills-cursor/");
2806
+ } catch (error) {
2807
+ cursorRulesSpinner.warn("Could not sync Cursor rules from the control plane");
2808
+ console.log(chalk.dim(error.message));
2809
+ }
2711
2810
  } else {
2712
2811
  const cursorRulesSpinner = ora("Syncing horus-core rules for Cursor...").start();
2713
2812
  try {
package/guides/index.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schema_version": 1,
3
- "built_at": "2026-05-31T17:23:45.012Z",
3
+ "built_at": "2026-06-01T09:10:29.830Z",
4
4
  "guide_count": 5,
5
5
  "guides": [
6
6
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arkhera30/cli",
3
- "version": "0.8.4",
3
+ "version": "0.8.6",
4
4
  "description": "CLI for managing the Horus AI development stack",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,111 +0,0 @@
1
- ---
2
- title: Horus Core Concepts
3
- description: The mental model for Anvil, Vault, Forge, and the supporting services Horus depends on.
4
- slug: core-concepts
5
- tags: [concepts, architecture, onboarding, anvil, vault, forge]
6
- schema_version: 1
7
- keywords: [concepts, architecture, mental-model, anvil, vault, forge, neo4j, typesense, mcp, services, how-it-works, overview]
8
- related_commands: [horus status, horus config]
9
- sidebar_position: 2
10
- ---
11
-
12
- # Horus Core Concepts
13
-
14
- Horus is three primary systems (Anvil, Vault, Forge) backed by supporting services (Typesense, Neo4j, a web UI). This guide explains what each one does, why it exists as a separate service, and how they connect.
15
-
16
- ## The three primary systems
17
-
18
- ### Anvil — live state
19
-
20
- Anvil stores your structured, **changing** data: tasks, stories, projects, journals, notes, conversation state. Every entity is a markdown file with YAML front-matter, indexed by an embedded SQLite database for fast full-text search.
21
-
22
- Anvil has a **dynamic type system** — type definitions are YAML files, not hardcoded in the server. That means you can add new note types or extend existing ones without changing code. Claude always queries the current type schema before creating a note, so your types stay authoritative.
23
-
24
- **Use Anvil for:** tasks, projects, meeting notes, daily journals, research notes, anything you'd put in a personal knowledge-management app.
25
-
26
- **MCP endpoint:** `http://localhost:8100`
27
-
28
- ### Vault — durable knowledge
29
-
30
- Vault stores your **long-lived**, structured documentation: repo profiles, architecture decisions, how-to guides, procedures, and learnings. Unlike Anvil (which tracks changing state), Vault is the place for knowledge you want to reference across sessions, weeks, and projects.
31
-
32
- Vault is a FastAPI knowledge service backed by a git repository per vault. You can run **multiple vaults** (e.g. `personal`, `work`, `client-acme`) — each is a separate repo with its own access controls and git history. A separate `vault-router` service fans out read queries to all vaults and routes writes to the correct one by UUID.
33
-
34
- Search is powered by both Typesense (full-text, typo-tolerant) and Neo4j (graph traversal for "what's related to what"). You get semantic-ish retrieval without a GPU or an external API.
35
-
36
- **Use Vault for:** how does this codebase work, what conventions does that team follow, what's the decision history for this architecture choice.
37
-
38
- **MCP endpoint:** `http://localhost:8300` (via the `vault-mcp` adapter)
39
-
40
- ### Forge — execution environment
41
-
42
- Forge is the **workspace and session manager**. It does three distinct things:
43
-
44
- 1. **Workspaces** — isolated contexts (MCP configs, skills, permissions) for a particular line of work. A workspace doesn't clone any code; it's just the agent environment.
45
- 2. **Sessions** — git worktrees tied to a work item, created on demand via `forge_develop`. A session is where code changes actually happen.
46
- 3. **Repo index** — a scanned index of your local Git repositories, made available to Claude so it can resolve and search across them.
47
-
48
- Forge also manages **plugins and skills** — reusable packages that install into the registry and add capabilities to your workspaces.
49
-
50
- **Use Forge for:** starting coded work, managing isolated contexts per project, discovering which repos exist on your machine.
51
-
52
- **MCP endpoint:** `http://localhost:8200`
53
-
54
- ## Supporting services
55
-
56
- ### Typesense
57
-
58
- A full-text and vector search engine that runs inside the Horus stack. Both Anvil and Vault use Typesense under the hood for fast, typo-tolerant search. You don't interact with Typesense directly — it's internal plumbing exposed only inside the Docker network.
59
-
60
- ### Neo4j
61
-
62
- A graph database for relationship-aware queries. Vault uses Neo4j to store and traverse the graph of entities and edges between knowledge pages — useful for questions like "what's related to this decision" or "walk me from this ADR to the procedures that implement it". Runs on ports **7474** (browser / HTTP) and **7687** (Bolt protocol).
63
-
64
- ### Horus UI
65
-
66
- A React web interface at **`http://localhost:8400`** that lets you browse Anvil notes, Vault pages, and Forge workspaces without going through Claude. It's served by an Express proxy that fans requests out to Anvil, Vault MCP, and Forge over the internal Docker network. Optional — Claude works fine without it — but handy for a visual sanity check.
67
-
68
- ## How they connect
69
-
70
- Everything runs in a single Docker Compose stack on a bridge network named `horus-net`. Claude (Desktop, Code, or Cursor) connects via the Model Context Protocol (MCP) to three endpoints on your host:
71
-
72
- | Service | Host endpoint | Who talks to it |
73
- |---|---|---|
74
- | Anvil | `http://localhost:8100` | Claude, Horus UI |
75
- | Vault MCP | `http://localhost:8300` | Claude, Horus UI |
76
- | Forge | `http://localhost:8200` | Claude, Horus UI |
77
-
78
- Internally, Vault MCP proxies to the `vault-router`, which fans out to each `vault` instance on ports `8001`, `8002`, etc. Anvil and Vault both query Typesense over the internal network. Forge reads repo metadata from the host filesystem via a read-only bind mount.
79
-
80
- ## Data layout
81
-
82
- All durable state lives under your data directory (default `~/Horus/data`). On a fresh install you'll see:
83
-
84
- ```
85
- ~/Horus/data/
86
- notes/ Anvil notes (git repo)
87
- vaults/
88
- <name>/ Each vault is a separate git repo
89
- registry/ Forge plugin registry (git repo)
90
- workspaces/ Forge workspace directories
91
- sessions/ Forge session git worktrees
92
- repos/ Managed clone pool (created by forge_repo_clone)
93
- config/ Forge-managed config (forge.yaml, repos.json, workspaces.json)
94
- typesense-data/ Typesense index (internal)
95
- ```
96
-
97
- User config lives at `~/Horus/config.yaml` and `~/Horus/.env`. The Docker Compose file is installed at `~/Horus/docker-compose.yml` — edit it only if you know what you're doing; `horus setup` regenerates it from the config.
98
-
99
- ## Key principles
100
-
101
- 1. **Everything is local.** All data stays on your machine. Search runs in-process. The only network traffic is to Anthropic (when Claude talks to the API) and to your own Git remotes for sync.
102
- 2. **Data is durable.** Notes, knowledge, and workspaces are files on disk backed by git. Stopping the stack or removing containers never deletes your data.
103
- 3. **Routing is automatic.** You don't pick Anvil vs Vault vs Forge — Claude reads the routing rules from the `horus-*` skills and picks the right system based on intent.
104
- 4. **Types are dynamic.** Anvil's type system is runtime-defined. New types and fields land without a code release.
105
-
106
- ## What's next
107
-
108
- - **`horus guide getting-started`** if you haven't installed yet
109
- - **`horus guide first-note`** to create your first Anvil entity
110
- - **`horus guide first-workspace`** to set up your first isolated working context
111
- - **`horus guide first-session`** to start your first isolated code session
@@ -1,127 +0,0 @@
1
- ---
2
- title: Your First Anvil Note
3
- description: How Anvil's dynamic type system works and how to create your first entity — project, task, or note.
4
- slug: first-note
5
- tags: [anvil, notes, onboarding, types]
6
- schema_version: 1
7
- keywords: [note, create, anvil, type, project, task, story, journal, list-types, dynamic-types]
8
- related_commands: [horus status]
9
- sidebar_position: 5
10
- ---
11
-
12
- # Your First Anvil Note
13
-
14
- Anvil is Horus's live-state layer. It stores tasks, projects, journals, stories, meeting notes, and every other kind of structured entry you might want to track. Every entity is a markdown file with YAML front-matter, indexed by SQLite for fast search.
15
-
16
- The one thing that surprises most first-time users: **types are not hardcoded**. Anvil has a dynamic type system — you (or a plugin) define types as YAML files, and Anvil discovers them at runtime. That's why the first thing any Anvil-aware tool does is call `anvil_list_types` to ask what's currently available.
17
-
18
- ## Step 1 — See what types exist
19
-
20
- Ask Claude:
21
-
22
- > "What note types exist in Anvil?"
23
-
24
- Claude will call the `anvil_list_types` tool and return a list of types with their fields, required fields, and defaults. On a fresh install you'll see core types like:
25
-
26
- - **`project`** — top-level container for related work
27
- - **`story`** — a feature, task, or work item (extends `task`)
28
- - **`task`** — a tracked to-do with status
29
- - **`note`** — a generic unstructured entry
30
- - **`journal`** — append-only chronological record for standups, session logs, decisions
31
- - **`meeting`** — attendees + agenda + action items
32
-
33
- Each type has:
34
-
35
- - **Required fields** (e.g. every `story` needs a `status`)
36
- - **Optional fields** (`priority`, `due`, `story_points`, etc.)
37
- - **Enum fields** with allowed values (e.g. `status: open | in-progress | blocked | done`)
38
-
39
- Don't memorize these — the tool returns them every call.
40
-
41
- ## Step 2 — Create a project
42
-
43
- > "Create a project called 'My First Horus Project' with status `active`."
44
-
45
- Claude will pick the right type, populate the required fields, and call `anvil_create_entity`. Under the hood that looks like:
46
-
47
- ```
48
- anvil_create_entity({
49
- type: "project",
50
- title: "My First Horus Project",
51
- fields: { status: "active" },
52
- body: "Brief description of what this project is for."
53
- })
54
- ```
55
-
56
- The response gives you a UUID (`entityId`) and the file path where Anvil wrote the note on disk — something like `~/Horus/data/notes/my-first-horus-project.md`.
57
-
58
- ## Step 3 — Add a task to the project
59
-
60
- Projects are just containers — the actual work lives as `task` or `story` entities that reference them. Ask Claude:
61
-
62
- > "Create a task in My First Horus Project called 'Try out `horus help`' with priority P2-medium."
63
-
64
- Anvil will create a new task and add the project reference via a typed edge. A lightly-edited version of the tool call:
65
-
66
- ```
67
- anvil_create_entity({
68
- type: "task",
69
- title: "Try out horus help",
70
- fields: {
71
- project: "[[My First Horus Project]]",
72
- priority: "P2-medium",
73
- status: "open"
74
- }
75
- })
76
- ```
77
-
78
- Two things worth noting:
79
-
80
- - **References are wiki-links.** Anvil uses `[[Title]]` format for cross-entity references, not UUIDs. The ingestion pipeline resolves the link to a UUID under the hood.
81
- - **Edges are real.** Fields like `project` aren't just strings — they create typed edges in Anvil's graph, visible via `anvil_get_edges` and `anvil_get_related`.
82
-
83
- ## Step 4 — Search for what you just created
84
-
85
- > "What's pending in My First Horus Project?"
86
-
87
- Claude calls `anvil_query_view` with a filter on project + status, or `anvil_search` with a free-text query. Both hit Anvil's SQLite index and return the matching entities. You'll see the task you just created.
88
-
89
- You can also:
90
-
91
- - **Group results as a board:** `anvil_query_view({ type: "task", project: "[[My First Horus Project]]", format: "board", groupBy: "status" })`
92
- - **Filter by tags:** `anvil_search({ query: "onboarding", tags: ["experimental"] })`
93
- - **Get a subtree** starting from a project: `anvil_get_subtree({ rootId: "..." })`
94
-
95
- ## Step 5 — Update without overwriting
96
-
97
- Anvil uses **PATCH semantics** for updates. When you call `anvil_update_entity`, only the fields you pass are changed — everything else is preserved. That means you can safely update a single field without worrying about clobbering unrelated metadata.
98
-
99
- > "Mark that task as in-progress."
100
-
101
- ```
102
- anvil_update_entity({
103
- noteId: "abc-...",
104
- fields: { status: "in-progress" }
105
- })
106
- ```
107
-
108
- **Journals are a special case:** they're append-only. Updating a journal's body *appends* the new content instead of replacing it, which is useful for daily standups and decision logs.
109
-
110
- ## Common starter flows
111
-
112
- A few things you'll probably try in your first week:
113
-
114
- - **Daily journal** — create a `journal` type each morning; Claude appends new entries throughout the day
115
- - **Project kanban** — one `project`, many `task`s, `anvil_query_view` with `format: "board"` gives you a live kanban
116
- - **Conversation state** — Claude auto-creates a `conversation-state` entity to track open questions and decisions for a session
117
- - **SDLC workflow** — `project` → `story` → `task` with typed edges, driven by the `sdlc-*` skills
118
-
119
- ## Where the data lives
120
-
121
- All Anvil entities are plain markdown files under `<data_dir>/notes/`, tracked in git. You can browse them, edit them in your text editor, commit them, and push them to your remote — Anvil picks up file-system changes via its file watcher. Anvil also periodically pulls the remote and pushes local changes, so your data sync across machines works the same way as git.
122
-
123
- ## What's next
124
-
125
- - **`horus guide first-workspace`** — set up an isolated agent context so your notes, skills, and MCP configs stay organized
126
- - **`horus guide first-session`** — tie a code session to a story or task you just created
127
- - **`horus guide core-concepts`** — how Anvil relates to Vault and Forge in the bigger picture
@@ -1,125 +0,0 @@
1
- ---
2
- title: Your First Forge Code Session
3
- description: How to start an isolated code session on a repo for a specific work item, using forge_develop.
4
- slug: first-session
5
- tags: [forge, session, onboarding, git, worktree]
6
- schema_version: 1
7
- keywords: [session, worktree, forge-develop, code, branch, git, isolate, coding, start-work]
8
- related_commands: [horus status]
9
- sidebar_position: 4
10
- ---
11
-
12
- # Your First Forge Code Session
13
-
14
- A **Forge session** is a git worktree on disk, tied to a single work item and a single repository. When you're ready to actually write code for a task — not just plan it, not just take notes — you start a session. The session gives you an isolated, feature-branched workspace that doesn't interfere with whatever else is checked out in your main clone of the repo.
15
-
16
- Sessions are created by the **`forge_develop`** MCP tool. One call does everything: finds the repo, creates the worktree, checks out a feature branch, wires up the commit scripts, and hands you back a path to work in.
17
-
18
- ## Before you start
19
-
20
- You need two things:
21
-
22
- 1. **A repo registered with Forge.** Run `forge_repo_list()` to see what Forge has indexed, or `forge_repo_resolve({ name: "..." })` to look up a specific one. Forge scans the `host_repos_path` you set during `horus setup` and auto-adds repos it finds.
23
- 2. **A work item in Anvil.** Sessions are namespaced by work item ID, so you need one before `forge_develop` will do anything. See `first-note` for how to create one.
24
-
25
- ## Create your first session
26
-
27
- Ask Claude:
28
-
29
- > "Start a code session for work item `<your-work-item-id>` on repo `<your-repo>`."
30
-
31
- Or call the MCP tool directly:
32
-
33
- ```
34
- forge_develop({
35
- repo: "my-project",
36
- workItem: "abc12345",
37
- branch: "feature/abc12345-initial-work"
38
- })
39
- ```
40
-
41
- Forge returns something like:
42
-
43
- ```json
44
- {
45
- "status": "created",
46
- "sessionId": "sess-...",
47
- "sessionPath": "/Users/you/Horus/data/sessions/abc12345-my-project",
48
- "branch": "feature/abc12345-initial-work",
49
- "baseBranch": "main",
50
- "repo": "my-project",
51
- "repoSource": "managed"
52
- }
53
- ```
54
-
55
- The **`sessionPath`** is where you do all your work. It's a real git worktree — a separate working directory that shares the underlying git object store with your main clone of the repo. You can `cd` into it, edit files, run tests, and commit like you would in any other clone.
56
-
57
- ## What's in a session directory
58
-
59
- When you open the session path, you'll see your repo checked out at the feature branch, plus one extra directory:
60
-
61
- ```
62
- <sessionPath>/
63
- <your repo contents>
64
- .forge/
65
- scripts/
66
- commit.sh # stage-aware conventional commit wrapper
67
- push.sh # push to the right remote for this repo's workflow
68
- create-pr.sh # create a PR against the correct target (handles fork vs owner vs contributor)
69
- ```
70
-
71
- Always commit through `.forge/scripts/commit.sh <type> <scope> <description>`. It handles the conventional-commit formatting and picks up per-repo workflow metadata so you don't have to remember which remote to push to or what the target branch is. Same applies to `push.sh` and `create-pr.sh`.
72
-
73
- ## Resume a session
74
-
75
- `forge_develop` is **idempotent** for the same work item + repo pair. If the session already exists, Forge resumes it instead of creating a new one:
76
-
77
- ```json
78
- {
79
- "status": "resumed",
80
- "sessionId": "sess-...",
81
- "sessionPath": "...",
82
- "branch": "feature/abc12345-initial-work"
83
- }
84
- ```
85
-
86
- That means you can re-invoke `forge_develop` any time you restart an agent session or switch machines — it picks up where you left off without polluting your git state.
87
-
88
- ## Workflow confirmation on first use
89
-
90
- The first time you create a session for a new repo, Forge may return:
91
-
92
- ```json
93
- { "status": "needs_workflow_confirmation", "detected": { ... } }
94
- ```
95
-
96
- This is Forge asking: "I see a `fork` / `owner` / `contributor` workflow here — is that right?" Re-call `forge_develop` with the `workflow` parameter set to confirm. Forge saves the choice so future sessions on the same repo don't need the confirmation again.
97
-
98
- ## Clean up when the work is done
99
-
100
- Sessions hang around until the linked work item transitions to `done` or `cancelled`. Once that happens, you can bulk-clean:
101
-
102
- ```
103
- forge_session_cleanup({ auto: true })
104
- ```
105
-
106
- This removes every session whose work item is complete. Or you can check for stale sessions manually:
107
-
108
- ```
109
- forge_session_list()
110
- ```
111
-
112
- ## Sessions vs. workspaces
113
-
114
- This trips up everyone the first time, so here it is one more time:
115
-
116
- - A **workspace** is an agent *environment* — MCP configs, skills, CLAUDE.md, permissions. It doesn't touch any repo.
117
- - A **session** is a git *worktree* — an actual checkout of a repo, tied to a work item.
118
-
119
- You can have one workspace and ten sessions inside it, each working on a different story or repo. Or you can skip workspaces and create sessions directly — they work fine on their own.
120
-
121
- ## What's next
122
-
123
- - **`horus guide first-note`** — create a work item in Anvil to link your session to
124
- - **`horus guide first-workspace`** — set up an isolated agent context that wraps multiple sessions
125
- - **`horus guide core-concepts`** — the mental model behind the workspace / session / repo split
@@ -1,108 +0,0 @@
1
- ---
2
- title: Your First Forge Workspace
3
- description: What a Forge workspace is, how it differs from a code session, and how to create your first one.
4
- slug: first-workspace
5
- tags: [forge, workspace, onboarding]
6
- schema_version: 1
7
- keywords: [workspace, forge, context, isolation, isolated, create, mcp, skills, plugins, environment, setup, initialize]
8
- related_commands: [horus status]
9
- sidebar_position: 3
10
- ---
11
-
12
- # Your First Forge Workspace
13
-
14
- A **Forge workspace** is an isolated agent context — a bundle of MCP configs, installed skills, environment variables, and permission settings — that Claude loads when you're working on something specific. Workspaces do **not** clone code. That's what sessions are for (see `first-session`).
15
-
16
- Think of a workspace as "the agent environment for working on project X", and a session as "the git worktree where I'm actually editing code for story Y inside project X". One workspace, many sessions.
17
-
18
- ## Why workspaces exist
19
-
20
- Without workspaces, every Claude session shares the same MCP tools, skills, and environment. That's fine at first, but as you start using Horus for real work you'll notice:
21
-
22
- - Different projects want different skills loaded (one project uses `horus-anvil`, another needs a custom `client-conventions` skill)
23
- - Sensitive projects shouldn't share environment variables with throwaway experiments
24
- - You want a clean context when you switch from personal work to client work
25
- - MCP server configs can drift per-project (e.g. different vault instances)
26
-
27
- Workspaces solve this by giving each line of work its own agent context.
28
-
29
- ## What's in a workspace
30
-
31
- When Forge creates a workspace, it writes:
32
-
33
- - **`.claude/settings.local.json`** — MCP server URLs for Anvil, Vault, and Forge, resolved to host-accessible endpoints (not Docker-internal ones)
34
- - **`.claude/skills/`** — a subset of skills from the Forge registry, installed for this workspace
35
- - **`workspace.env`** — environment variables sourced by shells and child processes inside the workspace
36
- - **`CLAUDE.md`** — a workspace-level instruction file that gets loaded into Claude's context on every session
37
- - **Permission hooks** — optional pre/post hooks that enforce policies
38
-
39
- The workspace directory lives under `<data_dir>/workspaces/<workspace-name>/` on your host and is also mounted into the Forge container so the MCP server can read and update it.
40
-
41
- ## Create your first workspace
42
-
43
- Open Claude and ask it to create a workspace for you:
44
-
45
- > "Create a workspace called `draft` for general experimentation with the `sdlc-*` skills loaded."
46
-
47
- Claude will call `forge_workspace_create` under the hood. If you want to do it in a single MCP tool call directly, the arguments look like:
48
-
49
- ```
50
- forge_workspace_create({
51
- name: "draft",
52
- description: "General experimentation workspace",
53
- skills: ["sdlc-planner", "sdlc-developer", "sdlc-reviewer", "sdlc-story"],
54
- mcp_servers: ["anvil", "vault", "forge"]
55
- })
56
- ```
57
-
58
- Forge returns the workspace path (e.g. `~/Horus/data/workspaces/draft/`) and writes all the config files mentioned above.
59
-
60
- ## Use the workspace
61
-
62
- To actually *work* inside a workspace, open Claude Code or Cursor in the workspace directory:
63
-
64
- ```bash
65
- cd ~/Horus/data/workspaces/draft
66
- claude
67
- ```
68
-
69
- Claude Code picks up `.claude/settings.local.json` automatically and loads the configured MCP servers and skills. The `CLAUDE.md` in the workspace root becomes your project-level instructions. From that point on, every tool call Claude makes is scoped to this workspace's configuration.
70
-
71
- ## Workspaces vs. code sessions
72
-
73
- Here's the easiest way to keep them straight:
74
-
75
- | Workspace | Session |
76
- |---|---|
77
- | Agent environment (MCP, skills, permissions) | Git worktree on disk |
78
- | One per project or line of work | One per work item (story / task) |
79
- | Does NOT clone any repo | Is a git worktree checked out to a feature branch |
80
- | Persists until you explicitly delete it | Auto-cleans when the linked work item is done |
81
- | Created via `forge_workspace_create` | Created via `forge_develop` |
82
-
83
- You typically have one workspace per project and many sessions inside that workspace over time as you pick up different work items.
84
-
85
- ## Listing and inspecting workspaces
86
-
87
- ```
88
- forge_workspace_list()
89
- forge_workspace_status({ name: "draft" })
90
- ```
91
-
92
- The list tool returns every workspace Forge knows about. The status tool tells you its configuration and whether any sessions are linked to it.
93
-
94
- ## Cleaning up
95
-
96
- When you're done with a workspace:
97
-
98
- ```
99
- forge_workspace_delete({ name: "draft" })
100
- ```
101
-
102
- This removes the workspace directory and deregisters it from Forge. If the workspace has active sessions, you'll need to clean those up first (see `first-session`).
103
-
104
- ## What's next
105
-
106
- - **`horus guide first-session`** — create a git worktree tied to a work item so you can actually edit code
107
- - **`horus guide first-note`** — create the work item in Anvil that a session links to
108
- - **`horus guide core-concepts`** — the mental model for why workspace, session, and repo are three separate concepts