claude-teammate 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Claude Teammate
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,165 @@
1
+ ```
2
+ ____ _ _ _____ _
3
+ / ___| | __ _ _ _ __| | ___ |_ _|__ __ _ _ __ ___ _ __ ___ __ _| |_ ___
4
+ | | | |/ _` | | | |/ _` |/ _ \ | |/ _ \/ _` | '_ ` _ \| '_ ` _ \ / _` | __/ _ \
5
+ | |___| | (_| | |_| | (_| | __/ | | __/ (_| | | | | | | | | | | | (_| | || __/
6
+ \____|_|\__,_|\__,_|\__,_|\___| |_|\___|\__,_|_| |_| |_|_| |_| |_|\__,_|\__\___|
7
+ ```
8
+
9
+ <p align="center">
10
+ <a href="#quickstart"><strong>Quickstart</strong></a> &middot;
11
+ <a href="#how-it-works"><strong>How It Works</strong></a> &middot;
12
+ <a href="#it-remembers"><strong>It Remembers</strong></a> &middot;
13
+ <a href="https://github.com/ignify-rd/claude-teammate"><strong>GitHub</strong></a>
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="https://github.com/ignify-rd/claude-teammate/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License" /></a>
18
+ </p>
19
+
20
+ <br/>
21
+
22
+ # An autonomous software engineer that lives in your Jira and GitHub
23
+
24
+ Claude Teammate is an AI bot that works around the clock - picking up tickets, writing plans, opening PRs, acting on feedback, and reviewing code. All without being asked.
25
+
26
+ Assign it tickets. It ships.
27
+
28
+ | | Step | What happens |
29
+ | ------ | ------------------ | ----------------------------------------------------------------------------------------------------- |
30
+ | **01** | Assign the ticket | Set the Jira assignee to the bot. |
31
+ | **02** | It plans | Reads the codebase and writes a plan. You give feedback, it revises. Nothing moves until you approve. |
32
+ | **03** | It ships | Opens a PR. Responds to feedback. Keeps going until it's merged. |
33
+
34
+ <br/>
35
+
36
+ ## Claude Teammate is right for you if
37
+
38
+ - ✅ You want Jira tickets to **turn into merged PRs** without manual handoff
39
+ - ✅ You want **PR review comments acted on**, not just acknowledged
40
+ - ✅ You need a reviewer that **always shows up** and leaves structured feedback
41
+ - ✅ You want an AI that **remembers** - which repo, what's shipped, what breaks, what to avoid
42
+ - ✅ You want this running on **your own infrastructure**
43
+
44
+ <br/>
45
+
46
+ ## How It Works
47
+
48
+
49
+ <table>
50
+ <tr>
51
+ <td align="center" width="25%">
52
+ <h3>🎫 Jira → GitHub</h3>
53
+ Picks up assigned Jira issues, asks clarifying questions until it has enough context, then creates a GitHub issue with an implementation plan.
54
+ </td>
55
+ <td align="center" width="25%">
56
+ <h3>⚙️ GitHub Issue → PR</h3>
57
+ When the plan is approved, it implements the issue and opens a pull request. No code is written until you sign off.
58
+ </td>
59
+ <td align="center" width="25%">
60
+ <h3>💬 PR Feedback → Fix</h3>
61
+ Review comments don't sit. The bot reads them, fixes the code, and replies - so the only thing left for you to do is approve.
62
+ </td>
63
+ <td align="center" width="25%">
64
+ <h3>🔍 Review Requests</h3>
65
+ Add it as a reviewer and it will show up - every time, with real feedback, never "LGTM" without reading.
66
+ </td>
67
+ </tr>
68
+ </table>
69
+
70
+ <br/>
71
+
72
+ ## It Remembers
73
+
74
+ Every epic has its own memory. The bot picks up exactly where it left off - no matter how much time has passed.
75
+
76
+ ```
77
+ memory/
78
+ └── {domain}/
79
+ └── {workspace}/
80
+ └── epic-{id}.md
81
+ ```
82
+
83
+ | Field | What it stores |
84
+ |---|---|
85
+ | `repos` | Target GitHub repositories |
86
+ | `status` | Done, in-flight, blocked |
87
+ | `notes` | Architectural decisions, reviewer preferences, conventions |
88
+ | `known issues` | Recurring bugs, fragile areas, patterns to avoid |
89
+
90
+ The bot carries context across tickets, PRs, and sessions.
91
+
92
+ <br/>
93
+
94
+ ## Quickstart
95
+
96
+ **Requirements:** Node.js 20+, [Claude CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated, a Jira API token, a GitHub PAT with repo + PR permissions.
97
+
98
+ ```bash
99
+ npx claude-teammate start
100
+ ```
101
+
102
+ The wizard asks for your Jira and GitHub credentials and writes a `.env` file in the current directory. If the directory is not writable, it exits with a clear error instead of failing silently.
103
+
104
+ ```env
105
+ # .env file generated by `npx claude-teammate start`
106
+ JIRA_BASE_URL=https://yourorg.atlassian.net
107
+ JIRA_EMAIL=you@example.com
108
+ JIRA_API_TOKEN=...
109
+ JIRA_BOT_EMAIL=bot@yourorg.com
110
+ GITHUB_PAT=ghp_...
111
+ ```
112
+
113
+ <br/>
114
+
115
+ ## FAQ
116
+
117
+ **How is this different from just prompting Claude directly?**
118
+ Claude Teammate is a persistent, scheduled process. It doesn't wait for you to type - it polls, acts, and remembers.
119
+
120
+ **What happens if a Jira issue lacks context?**
121
+ It asks. The bot comments on the Jira issue with clarifying questions and waits for answers before doing anything.
122
+
123
+ **Can it handle multiple Jira workspaces and GitHub repos?**
124
+ Yes. Each epic is scoped to its own Jira domain and workspace, and can map to one or more GitHub repositories.
125
+
126
+ **Does it understand the codebase or just generate generic code?**
127
+ It reads the actual repository before writing anything - structure, conventions, existing patterns. The plan it proposes is specific to your code, not a template.
128
+
129
+ <br/>
130
+
131
+ ## Roadmap
132
+
133
+ - 🟢 Design the workflow
134
+ - ⚪ Jira integration - read tickets, comment, detect assignee
135
+ - ⚪ GitHub integration - issues, PRs, review requests
136
+ - ⚪ Epic memory - persistent context per epic
137
+ - ⚪ End-to-end: Jira ticket → approved plan → merged PR
138
+ - ⚪ Publish to npm
139
+
140
+ <br/>
141
+
142
+ ## Contributing
143
+
144
+ Contributions are welcome. Open an issue to discuss before submitting large changes.
145
+
146
+ <br/>
147
+
148
+ ## Community
149
+
150
+ - [GitHub Issues](https://github.com/ignify-rd/claude-teammate/issues) - bugs and feature requests
151
+ - [GitHub Discussions](https://github.com/ignify-rd/claude-teammate/discussions) - ideas and questions
152
+
153
+ <br/>
154
+
155
+ ## License
156
+
157
+ MIT &copy; 2026 Claude Teammate
158
+
159
+ <br/>
160
+
161
+ ---
162
+
163
+ <p align="center">
164
+ <sub>Open source under MIT. Built for engineers who want to ship, not supervise.</sub>
165
+ </p>
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+
3
+ import process from "node:process";
4
+
5
+ import { runCli } from "../src/cli.js";
6
+
7
+ try {
8
+ await runCli(process.argv.slice(2));
9
+ } catch (error) {
10
+ const message = error instanceof Error ? error.message : String(error);
11
+ process.stderr.write(`${message}\n`);
12
+ process.exitCode = 1;
13
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "claude-teammate",
3
+ "version": "0.1.0",
4
+ "description": "CLI bootstrapper for Claude Teammate.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "claude-teammate": "bin/claude-teammate.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "src",
13
+ "README.md"
14
+ ],
15
+ "engines": {
16
+ "node": ">=20"
17
+ },
18
+ "scripts": {
19
+ "lint": "eslint .",
20
+ "start": "node ./bin/claude-teammate.js start"
21
+ },
22
+ "keywords": [
23
+ "cli",
24
+ "automation",
25
+ "jira",
26
+ "github",
27
+ "agent"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/ignify-rd/claude-teammate.git"
32
+ },
33
+ "bugs": {
34
+ "url": "https://github.com/ignify-rd/claude-teammate/issues"
35
+ },
36
+ "homepage": "https://github.com/ignify-rd/claude-teammate#readme",
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "devDependencies": {
41
+ "@eslint/js": "^9.23.0",
42
+ "eslint": "^9.23.0",
43
+ "globals": "^16.0.0"
44
+ }
45
+ }
package/src/cli.js ADDED
@@ -0,0 +1,32 @@
1
+ import process from "node:process";
2
+
3
+ import { runStartWizard } from "./commands/start.js";
4
+
5
+ const HELP_TEXT = `claude-teammate
6
+
7
+ Usage:
8
+ claude-teammate start
9
+ claude-teammate onboard
10
+ claude-teammate --help
11
+
12
+ Commands:
13
+ start Run the setup wizard and write configuration to .env
14
+ onboard Alias for start
15
+ `;
16
+
17
+ export async function runCli(args) {
18
+ const [command] = args;
19
+
20
+ if (!command || command === "--help" || command === "-h") {
21
+ process.stdout.write(`${HELP_TEXT}\n`);
22
+ return;
23
+ }
24
+
25
+ if (command === "start" || command === "onboard") {
26
+ await runStartWizard();
27
+ return;
28
+ }
29
+
30
+ process.stderr.write(`Unknown command: ${command}\n\n${HELP_TEXT}\n`);
31
+ process.exitCode = 1;
32
+ }
@@ -0,0 +1,189 @@
1
+ import { access, readFile, writeFile } from "node:fs/promises";
2
+ import { constants as fsConstants } from "node:fs";
3
+ import { createInterface } from "node:readline/promises";
4
+ import process from "node:process";
5
+
6
+ const ENV_PATH = ".env";
7
+ const REQUIRED_FIELDS = [
8
+ {
9
+ key: "JIRA_BASE_URL",
10
+ prompt: "Jira base URL",
11
+ example: "https://yourorg.atlassian.net"
12
+ },
13
+ {
14
+ key: "JIRA_EMAIL",
15
+ prompt: "Jira user email",
16
+ example: "you@example.com"
17
+ },
18
+ {
19
+ key: "JIRA_API_TOKEN",
20
+ prompt: "Jira API token",
21
+ secret: true
22
+ },
23
+ {
24
+ key: "JIRA_BOT_EMAIL",
25
+ prompt: "Bot email used for Jira assignment",
26
+ example: "bot@yourorg.com"
27
+ },
28
+ {
29
+ key: "GITHUB_PAT",
30
+ prompt: "GitHub personal access token",
31
+ secret: true
32
+ }
33
+ ];
34
+
35
+ export async function runStartWizard() {
36
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
37
+ process.stderr.write("The start wizard requires an interactive terminal.\n");
38
+ process.exitCode = 1;
39
+ return;
40
+ }
41
+
42
+ const existingEnv = await loadExistingEnv();
43
+ const rl = createInterface({
44
+ input: process.stdin,
45
+ output: process.stdout
46
+ });
47
+
48
+ try {
49
+ process.stdout.write("Claude Teammate setup\n\n");
50
+ const values = {};
51
+
52
+ for (const field of REQUIRED_FIELDS) {
53
+ const currentValue = existingEnv[field.key];
54
+ values[field.key] = await promptForValue(rl, field, currentValue);
55
+ }
56
+
57
+ const envContent = buildEnvFile(values);
58
+ await persistEnvFile(envContent);
59
+
60
+ process.stdout.write(`\nSaved configuration to ${ENV_PATH}.\n`);
61
+ process.stdout.write("Claude Teammate runtime is not implemented yet.\n");
62
+ } finally {
63
+ rl.close();
64
+ }
65
+ }
66
+
67
+ async function loadExistingEnv() {
68
+ try {
69
+ const currentFile = await readFile(ENV_PATH, "utf8");
70
+ return parseEnvFile(currentFile);
71
+ } catch (error) {
72
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
73
+ return {};
74
+ }
75
+
76
+ throw new Error(`Unable to read ${ENV_PATH}: ${formatError(error)}`);
77
+ }
78
+ }
79
+
80
+ async function promptForValue(rl, field, currentValue) {
81
+ const parts = [field.prompt];
82
+
83
+ if (field.example) {
84
+ parts.push(`example: ${field.example}`);
85
+ }
86
+
87
+ if (currentValue) {
88
+ parts.push(`current: ${field.secret ? maskSecret(currentValue) : currentValue}`);
89
+ }
90
+
91
+ const answer = await rl.question(`${parts.join(" | ")}\n> `);
92
+ const trimmed = answer.trim();
93
+
94
+ if (trimmed) {
95
+ return trimmed;
96
+ }
97
+
98
+ if (currentValue) {
99
+ return currentValue;
100
+ }
101
+
102
+ throw new Error(`Missing required value for ${field.key}.`);
103
+ }
104
+
105
+ function buildEnvFile(values) {
106
+ const lines = ["# Generated by `claude-teammate start`"];
107
+
108
+ for (const field of REQUIRED_FIELDS) {
109
+ lines.push(`${field.key}=${escapeEnvValue(values[field.key])}`);
110
+ }
111
+
112
+ return `${lines.join("\n")}\n`;
113
+ }
114
+
115
+ async function persistEnvFile(content) {
116
+ try {
117
+ try {
118
+ await access(".", fsConstants.W_OK);
119
+ } catch (error) {
120
+ throw new Error(`Current directory is not writable: ${formatError(error)}`);
121
+ }
122
+
123
+ await writeFile(ENV_PATH, content, "utf8");
124
+ } catch (error) {
125
+ if (error && typeof error === "object" && "code" in error && error.code === "EACCES") {
126
+ throw new Error(`No permission to write ${ENV_PATH}.`);
127
+ }
128
+
129
+ throw new Error(`Unable to write ${ENV_PATH}: ${formatError(error)}`);
130
+ }
131
+ }
132
+
133
+ function parseEnvFile(content) {
134
+ const entries = {};
135
+
136
+ for (const line of content.split(/\r?\n/u)) {
137
+ const trimmed = line.trim();
138
+
139
+ if (!trimmed || trimmed.startsWith("#")) {
140
+ continue;
141
+ }
142
+
143
+ const separatorIndex = trimmed.indexOf("=");
144
+ if (separatorIndex === -1) {
145
+ continue;
146
+ }
147
+
148
+ const key = trimmed.slice(0, separatorIndex).trim();
149
+ const rawValue = trimmed.slice(separatorIndex + 1).trim();
150
+ entries[key] = stripWrappedQuotes(rawValue);
151
+ }
152
+
153
+ return entries;
154
+ }
155
+
156
+ function stripWrappedQuotes(value) {
157
+ if (
158
+ (value.startsWith("\"") && value.endsWith("\"")) ||
159
+ (value.startsWith("'") && value.endsWith("'"))
160
+ ) {
161
+ return value.slice(1, -1);
162
+ }
163
+
164
+ return value;
165
+ }
166
+
167
+ function escapeEnvValue(value) {
168
+ if (/^[A-Za-z0-9_./:-]+$/u.test(value)) {
169
+ return value;
170
+ }
171
+
172
+ return JSON.stringify(value);
173
+ }
174
+
175
+ function maskSecret(value) {
176
+ if (value.length <= 4) {
177
+ return "*".repeat(value.length);
178
+ }
179
+
180
+ return `${"*".repeat(value.length - 4)}${value.slice(-4)}`;
181
+ }
182
+
183
+ function formatError(error) {
184
+ if (error instanceof Error) {
185
+ return error.message;
186
+ }
187
+
188
+ return String(error);
189
+ }