claude-teammate 0.1.20 → 0.1.22

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/README.md CHANGED
@@ -91,14 +91,18 @@ memory/
91
91
 
92
92
  ## Quickstart
93
93
 
94
- **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.
94
+ **Requirements:**
95
+ - Node.js 20+
96
+ - [Claude CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
97
+ - A Jira API token
98
+ - A GitHub PAT with `repo` + PR permissions
95
99
 
96
100
  ```bash
97
101
  npm install -g claude-teammate
98
102
  tm8 start
99
103
  ```
100
104
 
101
- `start` creates `.env` on the first run, runs a 5 second Claude CLI preflight check that expects a plain `OK`, then launches a background worker for the current project. If `.env` already exists with the required values, setup is skipped and your credentials are not prompted for again.
105
+ `start` creates `.env` on the first run, then launches a background worker for the current project. If `.env` already exists with the required values, setup is skipped and your credentials are not prompted for again.
102
106
 
103
107
  To upgrade to latest version
104
108
 
@@ -163,6 +167,19 @@ It reads the actual repository before writing anything - structure, conventions,
163
167
 
164
168
  <br/>
165
169
 
170
+ ## Roadmap
171
+
172
+ - GitLab support
173
+ - Bitbucket support
174
+ - Ensure no conflicts for multiple concurrent claude-teammate agents
175
+ - Slack and Teams notifications when PRs are opened or merged
176
+ - Automatic PR description generation from commit history and issue context
177
+ - Branch-naming convention enforcement per epic or project config
178
+ - Stale-issue nudges when tickets sit unactioned past a configurable threshold
179
+ - Test-coverage gating before issue closure
180
+
181
+ <br/>
182
+
166
183
  ## Contributing
167
184
 
168
185
  Contributions are welcome. Open an issue to discuss before submitting large changes.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-teammate",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "CLI bootstrapper for Claude Teammate.",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cli.js CHANGED
@@ -5,22 +5,28 @@ import { runStatusCommand } from "./commands/status.js";
5
5
  import { runStopCommand } from "./commands/stop.js";
6
6
  import { runWorkerCommand } from "./commands/worker.js";
7
7
 
8
+ const LOCAL_FLAG = "--local";
9
+ const COMMANDS_WITH_LOCAL_FLAG = new Set(["start", "stop", "status"]);
10
+
8
11
  const HELP_TEXT = `claude-teammate
9
12
 
10
13
  Usage:
11
- claude-teammate start
12
- claude-teammate stop
13
- claude-teammate status
14
+ claude-teammate start [${LOCAL_FLAG}]
15
+ claude-teammate stop [${LOCAL_FLAG}]
16
+ claude-teammate status [${LOCAL_FLAG}]
14
17
  claude-teammate --help
15
18
 
16
19
  Commands:
17
- start Start Claude Teammate in the background for this project
18
- stop Stop the background worker for this project
20
+ start Start Claude Teammate in the background (default: global mode using ~/.tm8)
21
+ stop Stop the background worker
19
22
  status Show worker status, logs, and last Jira poll state
23
+
24
+ Flags:
25
+ ${LOCAL_FLAG} Use the current project directory instead of the global ~/.tm8 directory
20
26
  `;
21
27
 
22
28
  export async function runCli(args) {
23
- const [command] = args;
29
+ const [command, ...remainingArgs] = args;
24
30
  const projectRoot = process.cwd();
25
31
  const entrypointPath = process.argv[1];
26
32
 
@@ -29,23 +35,30 @@ export async function runCli(args) {
29
35
  return;
30
36
  }
31
37
 
38
+ const invalidFlag = findInvalidFlag(command, remainingArgs);
39
+ if (invalidFlag) {
40
+ process.stderr.write(`Unknown flag for ${command}: ${invalidFlag}\n\n${HELP_TEXT}\n`);
41
+ process.exitCode = 1;
42
+ return;
43
+ }
44
+
32
45
  if (command === "start") {
33
- await runStartCommand({ projectRoot, entrypointPath });
46
+ await runStartCommand({ projectRoot, entrypointPath, args: remainingArgs });
34
47
  return;
35
48
  }
36
49
 
37
50
  if (command === "stop") {
38
- await runStopCommand({ projectRoot });
51
+ await runStopCommand({ projectRoot, args: remainingArgs });
39
52
  return;
40
53
  }
41
54
 
42
55
  if (command === "status") {
43
- await runStatusCommand({ projectRoot });
56
+ await runStatusCommand({ projectRoot, args: remainingArgs });
44
57
  return;
45
58
  }
46
59
 
47
60
  if (command === "run-worker") {
48
- const workerProjectRoot = args[1];
61
+ const workerProjectRoot = remainingArgs[0];
49
62
  if (!workerProjectRoot) {
50
63
  throw new Error("Missing project root for run-worker.");
51
64
  }
@@ -57,3 +70,17 @@ export async function runCli(args) {
57
70
  process.stderr.write(`Unknown command: ${command}\n\n${HELP_TEXT}\n`);
58
71
  process.exitCode = 1;
59
72
  }
73
+
74
+ export function findInvalidFlag(command, args) {
75
+ if (!COMMANDS_WITH_LOCAL_FLAG.has(command)) {
76
+ return null;
77
+ }
78
+
79
+ for (const arg of args) {
80
+ if (arg.startsWith("-") && arg !== LOCAL_FLAG) {
81
+ return arg;
82
+ }
83
+ }
84
+
85
+ return null;
86
+ }
@@ -6,17 +6,21 @@ import process from "node:process";
6
6
  import { ensurePlaywrightMcpAvailable, verifyClaudeCli } from "../claude.js";
7
7
  import {
8
8
  REQUIRED_FIELDS,
9
+ ensureGlobalConfigDir,
9
10
  getMissingFields,
10
11
  loadProjectEnv,
11
12
  persistEnvFile
12
13
  } from "../config.js";
13
14
  import { ensureRuntimeDir, isProcessRunning, readPid, removePid } from "../runtime.js";
14
15
 
15
- export async function runStartCommand({ projectRoot, entrypointPath }) {
16
- const { values } = await loadProjectEnv(projectRoot);
16
+ export async function runStartCommand({ projectRoot, entrypointPath, args = [] }) {
17
+ const isLocal = args.includes("--local");
18
+ const configRoot = isLocal ? projectRoot : await ensureGlobalConfigDir();
19
+
20
+ const { values } = await loadProjectEnv(configRoot);
17
21
 
18
22
  if (Object.keys(values).length === 0) {
19
- await runSetupWizard(projectRoot);
23
+ await runSetupWizard(configRoot);
20
24
  } else {
21
25
  const missingFields = getMissingFields(values);
22
26
 
@@ -25,7 +29,7 @@ export async function runStartCommand({ projectRoot, entrypointPath }) {
25
29
  }
26
30
  }
27
31
 
28
- const runtimePaths = await ensureRuntimeDir(projectRoot);
32
+ const runtimePaths = await ensureRuntimeDir(configRoot);
29
33
  const existingPid = await readPid(runtimePaths.pidFile);
30
34
 
31
35
  if (existingPid && isProcessRunning(existingPid)) {
@@ -40,12 +44,12 @@ export async function runStartCommand({ projectRoot, entrypointPath }) {
40
44
 
41
45
  process.stdout.write("Checking Claude CLI...\n");
42
46
  await verifyClaudeCli({
43
- cwd: projectRoot,
47
+ cwd: configRoot,
44
48
  timeoutMs: 15_000
45
49
  });
46
50
  process.stdout.write("Checking Playwright MCP...\n");
47
51
  const playwrightMcp = await ensurePlaywrightMcpAvailable({
48
- cwd: projectRoot,
52
+ cwd: configRoot,
49
53
  scope: "user",
50
54
  timeoutMs: 15_000,
51
55
  installTimeoutMs: 60_000
@@ -57,8 +61,8 @@ export async function runStartCommand({ projectRoot, entrypointPath }) {
57
61
  }
58
62
 
59
63
  const logFd = openSync(runtimePaths.logFile, "a");
60
- const child = spawn(process.execPath, [entrypointPath, "run-worker", projectRoot], {
61
- cwd: projectRoot,
64
+ const child = spawn(process.execPath, [entrypointPath, "run-worker", configRoot], {
65
+ cwd: configRoot,
62
66
  detached: true,
63
67
  stdio: ["ignore", logFd, logFd]
64
68
  });
@@ -70,7 +74,7 @@ export async function runStartCommand({ projectRoot, entrypointPath }) {
70
74
  process.stdout.write(`Logs: ${runtimePaths.logFile}\n`);
71
75
  }
72
76
 
73
- async function runSetupWizard(projectRoot) {
77
+ async function runSetupWizard(configRoot) {
74
78
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
75
79
  throw new Error("The start wizard requires an interactive terminal.");
76
80
  }
@@ -88,7 +92,7 @@ async function runSetupWizard(projectRoot) {
88
92
  values[field.key] = await promptForValue(rl, field);
89
93
  }
90
94
 
91
- await persistEnvFile(projectRoot, values);
95
+ await persistEnvFile(configRoot, values);
92
96
  process.stdout.write("\nSaved configuration to .env.\n");
93
97
  } finally {
94
98
  rl.close();
@@ -1,9 +1,12 @@
1
1
  import process from "node:process";
2
2
 
3
+ import { ensureGlobalConfigDir } from "../config.js";
3
4
  import { ensureRuntimeDir, isProcessRunning, readPid, readState, removePid } from "../runtime.js";
4
5
 
5
- export async function runStatusCommand({ projectRoot }) {
6
- const runtimePaths = await ensureRuntimeDir(projectRoot);
6
+ export async function runStatusCommand({ projectRoot, args = [] }) {
7
+ const isLocal = args.includes("--local");
8
+ const configRoot = isLocal ? projectRoot : await ensureGlobalConfigDir();
9
+ const runtimePaths = await ensureRuntimeDir(configRoot);
7
10
  const pid = await readPid(runtimePaths.pidFile);
8
11
  const state = await readState(runtimePaths.stateFile);
9
12
 
@@ -1,9 +1,12 @@
1
1
  import process from "node:process";
2
2
 
3
+ import { ensureGlobalConfigDir } from "../config.js";
3
4
  import { ensureRuntimeDir, isProcessRunning, readPid, removePid } from "../runtime.js";
4
5
 
5
- export async function runStopCommand({ projectRoot }) {
6
- const runtimePaths = await ensureRuntimeDir(projectRoot);
6
+ export async function runStopCommand({ projectRoot, args = [] }) {
7
+ const isLocal = args.includes("--local");
8
+ const configRoot = isLocal ? projectRoot : await ensureGlobalConfigDir();
9
+ const runtimePaths = await ensureRuntimeDir(configRoot);
7
10
  const pid = await readPid(runtimePaths.pidFile);
8
11
 
9
12
  if (!pid) {
package/src/config.js CHANGED
@@ -1,8 +1,20 @@
1
- import { access, readFile, writeFile } from "node:fs/promises";
1
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
2
  import { constants as fsConstants } from "node:fs";
3
+ import os from "node:os";
3
4
  import path from "node:path";
4
5
 
5
6
  export const ENV_FILE_NAME = ".env";
7
+
8
+ export function getGlobalConfigDir() {
9
+ return path.join(os.homedir(), ".tm8");
10
+ }
11
+
12
+ export async function ensureGlobalConfigDir() {
13
+ const dir = getGlobalConfigDir();
14
+ await mkdir(dir, { recursive: true });
15
+ return dir;
16
+ }
17
+
6
18
  export const REQUIRED_FIELDS = [
7
19
  {
8
20
  key: "JIRA_BASE_URL",