ccteams 0.1.0

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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +227 -0
  3. package/bin/ccteams.js +174 -0
  4. package/lib/manifest.js +59 -0
  5. package/lib/teams.js +64 -0
  6. package/lib/use.js +230 -0
  7. package/package.json +25 -0
  8. package/teams/debug/agents/bug-fixer.md +52 -0
  9. package/teams/debug/agents/bug-reproducer.md +53 -0
  10. package/teams/debug/orchestration.md +19 -0
  11. package/teams/debug/team.json +6 -0
  12. package/teams/frontend/agents/ui-builder.md +47 -0
  13. package/teams/frontend/agents/ux-reviewer.md +60 -0
  14. package/teams/frontend/orchestration.md +19 -0
  15. package/teams/frontend/team.json +6 -0
  16. package/teams/generalist/agents/architect.md +48 -0
  17. package/teams/generalist/agents/builder.md +43 -0
  18. package/teams/generalist/agents/qa-reviewer.md +59 -0
  19. package/teams/generalist/agents/scope-planner.md +42 -0
  20. package/teams/generalist/agents/shipper.md +54 -0
  21. package/teams/generalist/orchestration.md +32 -0
  22. package/teams/generalist/team.json +6 -0
  23. package/teams/go-api/agents/go-builder.md +61 -0
  24. package/teams/go-api/agents/go-reviewer.md +54 -0
  25. package/teams/go-api/orchestration.md +19 -0
  26. package/teams/go-api/team.json +6 -0
  27. package/teams/next-ts/agents/next-builder.md +49 -0
  28. package/teams/next-ts/agents/next-reviewer.md +39 -0
  29. package/teams/next-ts/orchestration.md +20 -0
  30. package/teams/next-ts/team.json +6 -0
  31. package/teams/python-fastapi/agents/fastapi-builder.md +74 -0
  32. package/teams/python-fastapi/agents/fastapi-reviewer.md +60 -0
  33. package/teams/python-fastapi/orchestration.md +19 -0
  34. package/teams/python-fastapi/team.json +6 -0
  35. package/teams/rails/agents/rails-builder.md +55 -0
  36. package/teams/rails/agents/rails-reviewer.md +55 -0
  37. package/teams/rails/orchestration.md +19 -0
  38. package/teams/rails/team.json +6 -0
  39. package/teams/research/agents/tech-researcher.md +67 -0
  40. package/teams/research/orchestration.md +18 -0
  41. package/teams/research/team.json +6 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 toffyui
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,227 @@
1
+ # ccteams — Agent-Team Package Manager for Claude Code
2
+
3
+ Apply a pre-built team of Claude Code subagents to your project with one command, and switch teams whenever the work changes. An **agent team** is a bundle of subagents (with specific roles, expertise, and behaviors) plus orchestration rules that control how they collaborate — managed as a single unit in your project's `.claude/` directory.
4
+
5
+ ## Two ways to use it
6
+
7
+ Use ccteams from the terminal, from inside Claude Code, or both — whichever fits your flow.
8
+
9
+ https://github.com/user-attachments/assets/d6c89e7f-3945-484f-95a5-c69eb73cf035
10
+
11
+ https://github.com/user-attachments/assets/ff71a9cf-a981-440b-8c35-c783dd559ad1
12
+
13
+ ```bash
14
+ ccteams list # see the teams
15
+ ccteams use <team> # apply one (e.g. ccteams use go-api) to the current project
16
+ ```
17
+
18
+ | | How you drive it | |
19
+ | ------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
20
+ | **CLI** (`ccteams`) | From your terminal | `ccteams list`, `ccteams use <team>`, `ccteams current` |
21
+ | **Plugin** (`/ccteams:*`) | From inside Claude Code | `/ccteams:list-teams`, `/ccteams:use-team`, and `/ccteams:choose-team` — describe what you need and it picks the team for you |
22
+
23
+ The CLI is the engine, so it's always installed; the plugin adds the in-Claude-Code slash
24
+ commands (its skills call the CLI under the hood). Install one or both.
25
+
26
+ ## Install
27
+
28
+ ### 1. Install the CLI
29
+
30
+ ```bash
31
+ npm install -g ccteams
32
+ ccteams list
33
+ ```
34
+
35
+ Verify it prints the available teams. You can already use ccteams now — apply a team with
36
+ `ccteams use <team>` and restart Claude Code.
37
+
38
+ ### 2. Add the Claude Code plugin
39
+
40
+ For slash commands inside Claude Code, add the marketplace and install the plugin:
41
+
42
+ ```
43
+ /plugin marketplace add toffyui/ccteams
44
+ /plugin install ccteams@ccteams
45
+ /reload-plugins
46
+ ```
47
+
48
+ Or restart Claude Code. The slash commands `/ccteams:list-teams`, `/ccteams:use-team`, and `/ccteams:choose-team` will then be available. (The plugin's skills call the `ccteams` CLI under the hood, so the CLI must be installed too.)
49
+
50
+ ## Updating
51
+
52
+ ```bash
53
+ # CLI (new commands, new bundled teams)
54
+ npm install -g ccteams@latest
55
+
56
+ # Plugin (new or changed slash commands)
57
+ /plugin marketplace update ccteams # re-pull the latest from the repo
58
+ /reload-plugins # or restart Claude Code
59
+ ```
60
+
61
+ A full uninstall/reinstall is **not** needed. New slash commands reach users when the
62
+ plugin's `version` is bumped (the plugin is versioned via `plugin.json`); a marketplace
63
+ update followed by `/reload-plugins` picks them up.
64
+
65
+ ## Usage
66
+
67
+ ### Command Line (CLI)
68
+
69
+ ```bash
70
+ ccteams list # All teams (compact, one line each)
71
+ ccteams list --details # Full descriptions and tags
72
+ ccteams list --json # Machine-readable JSON
73
+ ccteams use <team> # Apply a team to the current project
74
+ ccteams use <team> --agent-teams # Apply it AND enable agent-teams mode (optional)
75
+ ccteams current # Show the currently active team
76
+ ccteams --version # Print the version
77
+ ```
78
+
79
+ After `ccteams use`, **restart Claude Code** so the team loads (see below).
80
+
81
+ ### Claude Code (slash commands — via the plugin)
82
+
83
+ ```
84
+ /ccteams:list-teams # List available teams
85
+ /ccteams:use-team <team-name> # Apply a team
86
+ /ccteams:choose-team <natural-language> # Find and apply a team by description ("for backend work", "frontend-focused", etc.)
87
+ ```
88
+
89
+ ## Available teams
90
+
91
+ ccteams ships with these teams out of the box. Each is a builder + reviewer pair (except `research`, which is a single read-only researcher).
92
+
93
+ | Team | What it's for |
94
+ | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
95
+ | `generalist` | Stack-agnostic, end-to-end feature team: scope → design → build → QA → ship. Use when no stack-specific team fits or for general cross-stack work. |
96
+ | `next-ts` | Next.js (App Router) + TypeScript + Tailwind — RSC, Server Actions, type-safe data fetching, accessible UI. |
97
+ | `frontend` | Framework-agnostic UI/UX and accessibility — UI work that isn't Next.js-specific, or focused on a11y/responsive/UX quality. |
98
+ | `go-api` | Go HTTP API backend — idiomatic services with `net/http` and `database/sql`. |
99
+ | `python-fastapi` | Python FastAPI + Pydantic v2 — async HTTP APIs with full type coverage and validation. |
100
+ | `rails` | Ruby on Rails — ActiveRecord, convention-over-configuration, the full Rails stack. |
101
+ | `debug` | Stack-agnostic bug hunting — reproduce → root-cause → minimal fix → regression test. |
102
+ | `research` | Stack-agnostic technical research — compare options and produce a written recommendation. Writes no code. |
103
+
104
+ Run `ccteams list` for the full descriptions and tags, or `/ccteams:choose-team <what you need>` to let Claude pick one for you.
105
+
106
+ ## One team per session (and monorepos)
107
+
108
+ ccteams applies **one team per project at a time**. `ccteams use <team>` is an exclusive switch: applying a new team cleanly replaces the previous one.
109
+
110
+ This is partly a Claude Code constraint: subagents in `.claude/agents/` are **global to the project** and cannot be scoped to a subdirectory. You can't, for example, have the `next-ts` team active only in `apps/web/` and `go-api` only in `apps/api/` at the same time with isolation.
111
+
112
+ **Monorepo workaround:** pick the team that matches the area you're actively working on. Claude Code loads `CLAUDE.md` files along the path to the files you're editing, so launching `claude` from the subdirectory you're working in gives you that subtree's `CLAUDE.md` context — but the applied team's agents themselves remain available repo-wide.
113
+
114
+ ## IMPORTANT: Session restart required
115
+
116
+ After running `ccteams use`, `/ccteams:use-team`, or `/ccteams:choose-team`, **you must restart Claude Code** for the new agent team to load. The agents are instantiated at session start, not mid-session.
117
+
118
+ **To restart:** type `/exit` (or close Claude Code) and start a new session.
119
+
120
+ ## How teams are applied to your project
121
+
122
+ When you apply a team with `ccteams use <team>` or `/ccteams:use-team <team>`:
123
+
124
+ 1. The team's agent definitions are copied into `.claude/agents/`.
125
+ 2. A `.claude/active-team.md` file is created, documenting the active team and its purpose.
126
+ 3. Your project's `.claude/CLAUDE.md` is updated with an import statement (`@.claude/active-team.md`) to include the team's orchestration rules.
127
+ 4. A `.claude/.ccteams-manifest.json` is written to track which team is active and allow clean switching.
128
+ 5. If you pass `--agent-teams` (or the team opts in via `"requiresAgentTeams": true`), `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` is set in `.claude/settings.json`. This is optional — without it, the team runs in the normal orchestrated mode.
129
+
130
+ ccteams includes a **collision guard**: it will refuse to apply a team if any of its agents share a name with agents you've written by hand in `.claude/agents/`. This prevents accidental overwrites.
131
+
132
+ ## Committing `.claude/` — your choice
133
+
134
+ You have two options:
135
+
136
+ **Option A (shared teams):** Commit `.claude/agents/`, `.claude/active-team.md`, and `.claude/.ccteams-manifest.json` to git. Teammates pulling the repo will automatically have the same team active.
137
+
138
+ **Option B (local teams):** Add `.claude/agents/`, `.claude/active-team.md`, and `.claude/.ccteams-manifest.json` to `.gitignore`. Each developer can run `ccteams use` locally to activate their preferred team.
139
+
140
+ **Recommendation:** If your project benefits from consistent team composition (e.g., a shared code style or mandatory QA agents), commit the team. Otherwise, keep it local.
141
+
142
+ ## Contributing a team
143
+
144
+ ccteams applies the teams bundled in this repo's `teams/` directory. To add a new team,
145
+ contribute it here (open a PR) — there's no separate user-local team registry. A team lives
146
+ in `teams/<name>/`:
147
+
148
+ ```
149
+ teams/<name>/
150
+ ├── team.json # Metadata: name, description, tags, optional flags
151
+ ├── orchestration.md # The CLAUDE.md rules to import (defines roles, goals, behavior)
152
+ └── agents/
153
+ ├── agent1.md # YAML frontmatter + agent system prompt
154
+ ├── agent2.md
155
+ └── ...
156
+ ```
157
+
158
+ ### `team.json` schema
159
+
160
+ ```json
161
+ {
162
+ "name": "my-team",
163
+ "description": "A short pitch of what this team does",
164
+ "tags": ["backend", "api", "performance"],
165
+ "requiresAgentTeams": false
166
+ }
167
+ ```
168
+
169
+ Set `"requiresAgentTeams": true` if your team uses agent-to-agent messaging or collaborative member features.
170
+
171
+ ### Agent files (`.md`)
172
+
173
+ Each agent file is a standard Claude Code subagent: YAML frontmatter (`name`, `description`, and optional `tools`) followed by its system prompt:
174
+
175
+ ```markdown
176
+ ---
177
+ name: my-agent
178
+ description: Backend API specialist. Use for building and reviewing REST/GraphQL endpoints, data layers, and integrations.
179
+ tools: Read, Write, Edit, Bash, Glob, Grep
180
+ ---
181
+
182
+ You are a Python backend expert. Your job is to...
183
+ ```
184
+
185
+ The `description` is what Claude uses to decide when to delegate to this agent, so make it specific. Omit `tools` to inherit all available tools.
186
+
187
+ For examples to copy from, see `teams/next-ts/` (a stack-specific team) and `teams/debug/` (a stack-agnostic team). `next-ts/` is the cleanest reference for the builder + reviewer shape.
188
+
189
+ ### Orchestrated vs. collaborative teams
190
+
191
+ All teams that ship today are **orchestrated**: one lead delegates to specialized subagents that report back independently. This is the simple, predictable default.
192
+
193
+ ccteams also supports **collaborative** teams — where subagents message each other directly — via Claude Code's experimental agent-teams feature (`CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1`). ccteams writes that env key into `.claude/settings.json` for you in two cases:
194
+
195
+ - The team declares `"requiresAgentTeams": true` in its `team.json` — agent-teams mode is enabled automatically whenever you apply it.
196
+ - You pass the `--agent-teams` flag to `ccteams use`, which opts any team into agent-teams mode for that project:
197
+
198
+ ```bash
199
+ ccteams use <team> --agent-teams
200
+ ```
201
+
202
+ The flag is position-agnostic, so `ccteams use --agent-teams <team>` works too.
203
+
204
+ When ccteams added the env key (either way), it removes it again the next time you switch to a team that doesn't need it. No collaborative team ships by default, but the format supports authoring one.
205
+
206
+ ## Development / local testing
207
+
208
+ ### Test the plugin locally (session-only)
209
+
210
+ ```bash
211
+ claude --plugin-dir ./plugins/ccteams
212
+ ```
213
+
214
+ This loads the plugin for the current session only — no permanent install. Useful for development.
215
+
216
+ ### Test the CLI locally
217
+
218
+ ```bash
219
+ npm install -g .
220
+ ccteams list
221
+ ```
222
+
223
+ Installs the CLI from the repo's current source.
224
+
225
+ ## License
226
+
227
+ MIT © toffyui. See [LICENSE](./LICENSE) for the full text.
package/bin/ccteams.js ADDED
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ccteams — Claude Code agent-team package manager
4
+ *
5
+ * Commands:
6
+ * list [--json] List available teams
7
+ * use <team> Apply a team to the current project
8
+ * current Show the currently-applied team
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+ import { fileURLToPath } from 'url';
14
+ import { listTeams } from '../lib/teams.js';
15
+ import { readManifest } from '../lib/manifest.js';
16
+ import { useTeam } from '../lib/use.js';
17
+
18
+ const args = process.argv.slice(2);
19
+ const command = args[0];
20
+
21
+ // Read the version from package.json (single source of truth), resolved relative
22
+ // to this file so it works regardless of where ccteams is installed.
23
+ function getVersion() {
24
+ try {
25
+ const pkgPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
26
+ return JSON.parse(fs.readFileSync(pkgPath, 'utf8')).version ?? 'unknown';
27
+ } catch {
28
+ return 'unknown';
29
+ }
30
+ }
31
+
32
+ // ── version ───────────────────────────────────────────────────────────────────
33
+ if (command === '--version' || command === '-V' || command === 'version') {
34
+ console.log(`ccteams ${getVersion()}`);
35
+ process.exit(0);
36
+ }
37
+
38
+ // ── list ────────────────────────────────────────────────────────────────────
39
+ if (command === 'list') {
40
+ const teams = listTeams();
41
+ const jsonFlag = args.includes('--json');
42
+
43
+ if (jsonFlag) {
44
+ // Print ONLY valid JSON — no decorative text, nothing else.
45
+ const output = teams.map(({ name, summary, description, tags, requiresAgentTeams }) => ({
46
+ name,
47
+ summary,
48
+ description,
49
+ tags,
50
+ requiresAgentTeams,
51
+ }));
52
+ process.stdout.write(JSON.stringify(output, null, 2) + '\n');
53
+ process.exit(0);
54
+ }
55
+
56
+ if (teams.length === 0) {
57
+ console.log('No teams found.');
58
+ process.exit(0);
59
+ }
60
+
61
+ // Color follows the NO_COLOR / FORCE_COLOR conventions, then falls back to TTY
62
+ // detection (so piped/captured output stays plain).
63
+ const color = !process.env.NO_COLOR && (process.env.FORCE_COLOR ? true : !!process.stdout.isTTY);
64
+ const bold = (s) => (color ? `\x1b[1;36m${s}\x1b[0m` : s); // bold cyan (team names)
65
+ const dim = (s) => (color ? `\x1b[2m${s}\x1b[0m` : s); // dim (secondary text)
66
+
67
+ const details = args.includes('--details') || args.includes('-d');
68
+
69
+ if (details) {
70
+ // Full view: name + complete description + tags, one block per team.
71
+ console.log('Available teams:\n');
72
+ for (const t of teams) {
73
+ console.log(` ${bold(t.name)}`);
74
+ console.log(` ${t.description}`);
75
+ if (t.tags.length > 0) {
76
+ console.log(` ${dim('tags:')} ${dim(t.tags.join(', '))}`);
77
+ }
78
+ console.log();
79
+ }
80
+ console.log(dim(' Apply: ccteams use <team> Optional: add --agent-teams to run members in parallel.'));
81
+ process.exit(0);
82
+ }
83
+
84
+ // Compact view (default): name (bold cyan) + a short one-line summary. One row
85
+ // per team, no wrapping, so the list stays scannable. `--details` shows the full
86
+ // descriptions and tags.
87
+ const nameWidth = Math.max(...teams.map((t) => t.name.length));
88
+
89
+ console.log('Available teams:\n');
90
+ for (const t of teams) {
91
+ console.log(` ${bold(t.name.padEnd(nameWidth))} ${t.summary}`);
92
+ }
93
+ console.log(dim('\n Apply: ccteams use <team> Full details: ccteams list --details'));
94
+ console.log(dim(' Optional: add --agent-teams to run a team’s members in parallel.'));
95
+ process.exit(0);
96
+ }
97
+
98
+ // ── current ──────────────────────────────────────────────────────────────────
99
+ if (command === 'current') {
100
+ const manifest = readManifest(process.cwd());
101
+ if (!manifest?.appliedTeam) {
102
+ console.log('No team currently applied. Run: ccteams use <team>');
103
+ process.exit(0);
104
+ }
105
+ console.log(`Current team: ${manifest.appliedTeam}`);
106
+ console.log(`Applied at : ${manifest.appliedAt}`);
107
+ console.log(`Files placed: ${manifest.placedFiles?.length ?? 0}`);
108
+ process.exit(0);
109
+ }
110
+
111
+ // ── use ──────────────────────────────────────────────────────────────────────
112
+ if (command === 'use') {
113
+ // Parse position-agnostic: strip --agent-teams from the args list, whatever
114
+ // position it appears in, then take the first remaining non-flag word as the
115
+ // team name.
116
+ const agentTeamsFlag = args.includes('--agent-teams');
117
+ const useArgs = args.slice(1).filter((a) => a !== '--agent-teams');
118
+ const teamName = useArgs[0];
119
+
120
+ if (!teamName) {
121
+ console.error('Usage: ccteams use [--agent-teams] <team-name>');
122
+ process.exit(1);
123
+ }
124
+
125
+ const result = useTeam(teamName, process.cwd(), { agentTeams: agentTeamsFlag });
126
+ if (!result.success) {
127
+ console.error(`Error: ${result.message}`);
128
+ process.exit(1);
129
+ }
130
+ console.log(result.message);
131
+ process.exit(0);
132
+ }
133
+
134
+ // ── no args / unknown command ────────────────────────────────────────────────
135
+ const usageText = `
136
+ ccteams — Claude Code agent-team package manager
137
+
138
+ Usage:
139
+ ccteams list List all available teams (compact)
140
+ ccteams list --details List teams with full descriptions and tags
141
+ ccteams list --json Machine-readable JSON list (for scripts/slash commands)
142
+ ccteams use <team> Apply a team to the current project
143
+ ccteams use <team> --agent-teams Apply a team AND enable agent-teams mode
144
+ ccteams current Show the currently-applied team
145
+ ccteams --version Print the ccteams version
146
+
147
+ Flags:
148
+ --agent-teams Enable CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 in .claude/settings.json
149
+ for the applied team. Position-agnostic: works before or after <team>.
150
+ Teams that declare "requiresAgentTeams" set this automatically.
151
+
152
+ Agent teams mode:
153
+ By default a team is orchestrated: one lead delegates to members one at a time,
154
+ and they report back. With --agent-teams, members become parallel teammates that
155
+ run at once and message each other directly — better for big, splittable work.
156
+ It uses Claude Code's experimental agent-teams feature and takes effect after you
157
+ restart the session.
158
+
159
+ Examples:
160
+ ccteams list
161
+ ccteams use frontend
162
+ ccteams use go-api --agent-teams
163
+ ccteams use --agent-teams rails
164
+ ccteams current
165
+ `.trimStart();
166
+
167
+ if (command === undefined || command === '--help' || command === '-h') {
168
+ console.log(usageText);
169
+ process.exit(0);
170
+ }
171
+
172
+ console.error(`Unknown command: "${command}"\n`);
173
+ console.error(usageText);
174
+ process.exit(1);
@@ -0,0 +1,59 @@
1
+ /**
2
+ * manifest.js — read/write .claude/.ccteams-manifest.json in the user's project.
3
+ *
4
+ * The manifest tracks what ccteams placed so we can clean up on team switches
5
+ * without touching hand-written agent files.
6
+ *
7
+ * Schema:
8
+ * {
9
+ * "version": "1", // schema version, bump if format changes
10
+ * "appliedTeam": "<team-name>", // name of the currently-applied team
11
+ * "appliedAt": "<ISO-8601>", // timestamp for diagnostics
12
+ * "placedFiles": ["<abs-path>", ...],// every file ccteams wrote, for clean removal
13
+ * "agentTeamsFlagSet": <boolean> // true if ccteams wrote the experimental env key
14
+ * }
15
+ */
16
+
17
+ import fs from 'fs';
18
+ import path from 'path';
19
+
20
+ const MANIFEST_VERSION = '1';
21
+
22
+ /**
23
+ * Resolve the manifest path inside the given project root.
24
+ */
25
+ export function manifestPath(projectRoot) {
26
+ return path.join(projectRoot, '.claude', '.ccteams-manifest.json');
27
+ }
28
+
29
+ /**
30
+ * Read the manifest from disk. Returns null if it does not exist or is invalid.
31
+ */
32
+ export function readManifest(projectRoot) {
33
+ const mPath = manifestPath(projectRoot);
34
+ if (!fs.existsSync(mPath)) return null;
35
+ try {
36
+ return JSON.parse(fs.readFileSync(mPath, 'utf8'));
37
+ } catch {
38
+ // Corrupted manifest — treat as absent so we start fresh.
39
+ return null;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Write a new manifest to disk.
45
+ * agentTeamsFlagSet tracks whether ccteams injected the experimental env key so
46
+ * we can remove it on switch without clobbering a user's pre-existing value.
47
+ */
48
+ export function writeManifest(projectRoot, { appliedTeam, placedFiles, agentTeamsFlagSet = false }) {
49
+ const mPath = manifestPath(projectRoot);
50
+ const data = {
51
+ version: MANIFEST_VERSION,
52
+ appliedTeam,
53
+ appliedAt: new Date().toISOString(),
54
+ placedFiles,
55
+ agentTeamsFlagSet,
56
+ };
57
+ fs.mkdirSync(path.dirname(mPath), { recursive: true });
58
+ fs.writeFileSync(mPath, JSON.stringify(data, null, 2) + '\n', 'utf8');
59
+ }
package/lib/teams.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * teams.js — discover and read team packages from the ccteams source repo.
3
+ *
4
+ * Teams live at <ccteams-repo-root>/teams/<team-name>/ and are resolved relative
5
+ * to this file's location (import.meta.url), so the path works regardless of
6
+ * where the user runs ccteams from (process.cwd is the user's project, not ours).
7
+ */
8
+
9
+ import fs from 'fs';
10
+ import path from 'path';
11
+ import { fileURLToPath } from 'url';
12
+
13
+ // Resolve the ccteams source root from this file's location.
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+ const TEAMS_DIR = path.resolve(__dirname, '..', 'teams');
17
+
18
+ /**
19
+ * Return an array of all available team descriptors.
20
+ * Each element: { name, description, tags, teamDir }
21
+ */
22
+ export function listTeams() {
23
+ if (!fs.existsSync(TEAMS_DIR)) {
24
+ return [];
25
+ }
26
+
27
+ const entries = fs.readdirSync(TEAMS_DIR, { withFileTypes: true });
28
+ const teams = [];
29
+
30
+ for (const entry of entries) {
31
+ if (!entry.isDirectory()) continue;
32
+ const teamDir = path.join(TEAMS_DIR, entry.name);
33
+ const jsonPath = path.join(teamDir, 'team.json');
34
+ if (!fs.existsSync(jsonPath)) continue;
35
+
36
+ let meta;
37
+ try {
38
+ meta = JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
39
+ } catch {
40
+ // Skip malformed team packages silently — user-visible error would be noise.
41
+ continue;
42
+ }
43
+
44
+ teams.push({
45
+ name: meta.name ?? entry.name,
46
+ // Short one-line label for `list`; falls back to the description.
47
+ summary: meta.summary ?? meta.description ?? '',
48
+ description: meta.description ?? '',
49
+ tags: meta.tags ?? [],
50
+ // Optional: signals that CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS is required.
51
+ requiresAgentTeams: meta.requiresAgentTeams === true,
52
+ teamDir,
53
+ });
54
+ }
55
+
56
+ return teams;
57
+ }
58
+
59
+ /**
60
+ * Return a single team descriptor by name, or null if not found.
61
+ */
62
+ export function findTeam(name) {
63
+ return listTeams().find((t) => t.name === name) ?? null;
64
+ }