@groupchatai/claude-runner 0.4.12 → 0.4.13

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 (2) hide show
  1. package/dist/index.js +122 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,8 +2,18 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { spawn, execFileSync } from "child_process";
5
- import { readFileSync, readdirSync, statSync, writeFileSync, existsSync, rmSync } from "fs";
5
+ import {
6
+ readFileSync,
7
+ readdirSync,
8
+ statSync,
9
+ writeFileSync,
10
+ existsSync,
11
+ rmSync,
12
+ mkdirSync,
13
+ unlinkSync
14
+ } from "fs";
6
15
  import path from "path";
16
+ import { homedir } from "os";
7
17
  import { fileURLToPath } from "url";
8
18
  var API_URL = "https://groupchat.ai";
9
19
  var CONVEX_URL = "https://fantastic-jay-464.convex.cloud";
@@ -707,12 +717,22 @@ function createWorktree(repoDir, taskId, existingBranch) {
707
717
  return worktreePath;
708
718
  }
709
719
  const baseBranch = getDefaultBranch(repoDir);
710
- const branchName = `agent/${name}-${Date.now()}`;
720
+ const branchName = `agent/${name}`;
711
721
  try {
712
722
  execGit(["fetch", "origin", baseBranch, "--quiet"], repoDir);
713
723
  } catch {
714
724
  }
715
- execGit(["worktree", "add", "-b", branchName, worktreePath, `origin/${baseBranch}`], repoDir);
725
+ let branchExists = false;
726
+ try {
727
+ execGit(["rev-parse", "--verify", branchName], repoDir);
728
+ branchExists = true;
729
+ } catch {
730
+ }
731
+ if (branchExists) {
732
+ execGit(["worktree", "add", worktreePath, branchName], repoDir);
733
+ } else {
734
+ execGit(["worktree", "add", "-b", branchName, worktreePath, `origin/${baseBranch}`], repoDir);
735
+ }
716
736
  writeFileSync(path.join(worktreePath, ".agent-branch"), branchName, "utf-8");
717
737
  return worktreePath;
718
738
  }
@@ -823,11 +843,6 @@ async function removeWorktree(workDir, info) {
823
843
  }
824
844
  }
825
845
  function removeWorktreeSimple(repoDir, worktreePath) {
826
- let branchName;
827
- try {
828
- branchName = readFileSync(path.join(worktreePath, ".agent-branch"), "utf-8").trim();
829
- } catch {
830
- }
831
846
  try {
832
847
  execGit(["worktree", "remove", worktreePath, "--force"], repoDir);
833
848
  } catch {
@@ -837,12 +852,6 @@ function removeWorktreeSimple(repoDir, worktreePath) {
837
852
  } catch {
838
853
  }
839
854
  }
840
- if (branchName) {
841
- try {
842
- execGit(["branch", "-D", branchName], repoDir);
843
- } catch {
844
- }
845
- }
846
855
  }
847
856
  async function startupSweep(workDir) {
848
857
  const worktrees = await listOurWorktrees(workDir);
@@ -1217,6 +1226,76 @@ ${message.slice(0, 2e3)}
1217
1226
  log(`${C.red}\u274C Error: ${message}${C.reset}`);
1218
1227
  }
1219
1228
  }
1229
+ function globalConfigDir() {
1230
+ return path.join(homedir(), ".config", "groupchat");
1231
+ }
1232
+ function globalConfigPath() {
1233
+ return path.join(globalConfigDir(), "config");
1234
+ }
1235
+ function readGlobalConfig() {
1236
+ try {
1237
+ const contents = readFileSync(globalConfigPath(), "utf-8");
1238
+ const config = {};
1239
+ for (const line of contents.split("\n")) {
1240
+ const trimmed = line.trim();
1241
+ if (!trimmed || trimmed.startsWith("#")) continue;
1242
+ const eqIdx = trimmed.indexOf("=");
1243
+ if (eqIdx === -1) continue;
1244
+ const key = trimmed.slice(0, eqIdx).trim();
1245
+ let value = trimmed.slice(eqIdx + 1).trim();
1246
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1247
+ value = value.slice(1, -1);
1248
+ }
1249
+ config[key] = value;
1250
+ }
1251
+ return config;
1252
+ } catch {
1253
+ return {};
1254
+ }
1255
+ }
1256
+ function writeGlobalConfig(config) {
1257
+ const dir = globalConfigDir();
1258
+ mkdirSync(dir, { recursive: true });
1259
+ const lines = Object.entries(config).map(([k, v]) => `${k}=${v}`);
1260
+ writeFileSync(globalConfigPath(), lines.join("\n") + "\n", "utf-8");
1261
+ }
1262
+ function handleLogin(token) {
1263
+ if (!token) {
1264
+ console.error("Usage: npx @groupchatai/claude-runner login <gca_token>");
1265
+ process.exit(1);
1266
+ }
1267
+ if (!token.startsWith("gca_")) {
1268
+ console.error(`Error: Token must start with gca_ (got "${token.slice(0, 8)}\u2026")`);
1269
+ process.exit(1);
1270
+ }
1271
+ const config = readGlobalConfig();
1272
+ config.GCA_TOKEN = token;
1273
+ writeGlobalConfig(config);
1274
+ console.log(`\u2705 Token saved to ${globalConfigPath()}`);
1275
+ console.log(` You can now run the agent from any directory.`);
1276
+ }
1277
+ function handleLogout() {
1278
+ const configFile = globalConfigPath();
1279
+ if (!existsSync(configFile)) {
1280
+ console.log("No saved token found.");
1281
+ return;
1282
+ }
1283
+ const config = readGlobalConfig();
1284
+ if (!config.GCA_TOKEN) {
1285
+ console.log("No saved token found.");
1286
+ return;
1287
+ }
1288
+ delete config.GCA_TOKEN;
1289
+ if (Object.keys(config).length === 0) {
1290
+ try {
1291
+ unlinkSync(configFile);
1292
+ } catch {
1293
+ }
1294
+ } else {
1295
+ writeGlobalConfig(config);
1296
+ }
1297
+ console.log("\u2705 Token removed.");
1298
+ }
1220
1299
  function loadEnvFile() {
1221
1300
  const candidates = [".env.local", ".env"];
1222
1301
  for (const file of candidates) {
@@ -1325,8 +1404,10 @@ function showHelp() {
1325
1404
  Usage: npx @groupchatai/claude-runner [command] [options]
1326
1405
 
1327
1406
  Commands:
1328
- (default) Start the agent runner
1329
- cleanup Interactively review and remove stale worktrees
1407
+ (default) Start the agent runner
1408
+ login <gca_token> Save your agent token for use from any directory
1409
+ logout Remove saved agent token
1410
+ cleanup Interactively review and remove stale worktrees
1330
1411
 
1331
1412
  Options:
1332
1413
  --work-dir <path> Repo directory for Claude Code to work in (default: cwd)
@@ -1342,6 +1423,12 @@ Options:
1342
1423
  -h, --help Show this help message
1343
1424
  -v, -version, --version Print version and exit
1344
1425
 
1426
+ Token resolution order:
1427
+ 1. --token flag
1428
+ 2. GCA_TOKEN env var
1429
+ 3. .env.local / .env in current directory
1430
+ 4. ~/.config/groupchat/config (saved via 'login' command)
1431
+
1345
1432
  Environment variables:
1346
1433
  GCA_TOKEN Agent token (gca_...)
1347
1434
  GCA_API_URL API URL override
@@ -1351,9 +1438,20 @@ Environment variables:
1351
1438
  }
1352
1439
  function parseArgs() {
1353
1440
  loadEnvFile();
1354
- const args = process.argv.slice(2);
1441
+ const rawArgs = process.argv.slice(2);
1442
+ if (rawArgs[0] === "login") {
1443
+ handleLogin(rawArgs[1] ?? "");
1444
+ process.exit(0);
1445
+ }
1446
+ if (rawArgs[0] === "logout") {
1447
+ handleLogout();
1448
+ process.exit(0);
1449
+ }
1450
+ const globalConfig = readGlobalConfig();
1451
+ const resolvedToken = process.env.GCA_TOKEN || globalConfig.GCA_TOKEN || "";
1452
+ const args = rawArgs;
1355
1453
  const config = {
1356
- token: process.env.GCA_TOKEN ?? "",
1454
+ token: resolvedToken,
1357
1455
  apiUrl: process.env.GCA_API_URL ?? API_URL,
1358
1456
  convexUrl: process.env.GCA_CONVEX_URL ?? CONVEX_URL,
1359
1457
  workDir: process.cwd(),
@@ -1419,7 +1517,12 @@ function parseArgs() {
1419
1517
  if (config.command !== "cleanup") {
1420
1518
  if (!config.token) {
1421
1519
  console.error("Error: No agent token found.");
1422
- console.error(" Add GCA_TOKEN=gca_... to .env.local, or pass --token gca_...");
1520
+ console.error("");
1521
+ console.error(" To save your token globally (recommended):");
1522
+ console.error(" npx @groupchatai/claude-runner login gca_...");
1523
+ console.error("");
1524
+ console.error(" Or pass it directly:");
1525
+ console.error(" --token gca_... or GCA_TOKEN=gca_... in .env.local");
1423
1526
  process.exit(1);
1424
1527
  }
1425
1528
  if (!config.token.startsWith("gca_")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@groupchatai/claude-runner",
3
- "version": "0.4.12",
3
+ "version": "0.4.13",
4
4
  "description": "Run GroupChat AI agent tasks locally with Claude Code",
5
5
  "type": "module",
6
6
  "bin": {