@tamsensedev/runner 0.18.0 → 0.19.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/README.md CHANGED
@@ -50,6 +50,21 @@ Options:
50
50
  - `--server <url>` TAMsense address, default https://my.tamsense.com (login)
51
51
  - `--parallel <n>` how many tasks to work on at once (start)
52
52
  - `--no-browser` do not install the headless browser for interface checks (start)
53
+ - `-p <name>`, also `--p` or `--profile`: which connection to use (login, start, status, logout)
54
+
55
+ ## Several projects on one computer
56
+
57
+ One connection works for one account in one project. For another project, or another account, use a profile: a separate connection with its own name.
58
+
59
+ ```
60
+ cd C:\work\client-a
61
+ npx @tamsensedev/runner@latest login -p client-a
62
+ npx @tamsensedev/runner@latest start -p client-a
63
+ ```
64
+
65
+ Confirm the code in the browser while the project of that profile is open in TAMsense. Each profile keeps its own login, folders and task copies in `~/.tamsense/profiles/<name>`, and its computer is called `<computer name> (<profile>)` on the site. Run each profile in its own terminal, at the same time if you like. Claude Code and the headless browser are shared. Without `-p` the program uses the main connection, as before.
66
+
67
+ A profile name may have letters, digits, `-` and `_`, up to 32 characters, and is case-insensitive.
53
68
 
54
69
  ## What it does on your computer
55
70
 
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { existsSync as existsSync8 } from "fs";
5
- import { homedir as homedir3 } from "os";
5
+ import { homedir as homedir2 } from "os";
6
6
  import process8 from "process";
7
7
 
8
8
  // src/api.ts
@@ -252,6 +252,7 @@ var START_COMMAND = "npx @tamsensedev/runner@latest start";
252
252
  var BROWSER_COMMAND = "npx @tamsensedev/runner@latest browser";
253
253
  var DEFAULT_PARALLEL = 2;
254
254
  var MAX_PARALLEL = 4;
255
+ var PROFILE_NAME = /^[a-z0-9][\w-]{0,31}$/i;
255
256
  var HELP_TEXT = `Connects this computer to TAMsense so it can work on your tasks.
256
257
 
257
258
  Usage:
@@ -263,6 +264,12 @@ Usage:
263
264
  npx @tamsensedev/runner@latest status show the connection
264
265
  npx @tamsensedev/runner@latest logout disconnect this computer
265
266
 
267
+ Profiles, for several projects or accounts on one computer:
268
+ -p, --profile <name> use a separate connection with its own folders, for example:
269
+ ${LOGIN_COMMAND} -p client-a
270
+ ${START_COMMAND} -p client-a
271
+ Works with login, start, status and logout. Without it, the main connection is used.
272
+
266
273
  Login options:
267
274
  --name <name> how this computer is called in TAMsense (default: its network name)
268
275
  --server <url> TAMsense address (default: ${DEFAULT_SERVER})
@@ -270,6 +277,18 @@ Login options:
270
277
  Start options:
271
278
  --parallel <n> how many tasks to work on at once, 1 to ${MAX_PARALLEL} (default: ${DEFAULT_PARALLEL}, or TAMSENSE_PARALLEL)
272
279
  --no-browser do not install the headless browser for interface checks (start installs it when it is missing)`;
280
+ function profileFor(flag) {
281
+ if (flag === void 0) {
282
+ return null;
283
+ }
284
+ if (!PROFILE_NAME.test(flag)) {
285
+ throw new Error(`The profile name may have letters, digits, - and _, up to 32 characters, got "${flag}".`);
286
+ }
287
+ return flag.toLowerCase();
288
+ }
289
+ function withProfile(command, profile) {
290
+ return profile === null ? command : `${command} -p ${profile}`;
291
+ }
273
292
  function parallelFor(flag, env) {
274
293
  const [source, value] = flag !== void 0 ? ["--parallel", flag] : ["TAMSENSE_PARALLEL", env?.trim() || void 0];
275
294
  if (value === void 0) {
@@ -287,6 +306,8 @@ function parseCommand(argv, env = {}) {
287
306
  allowPositionals: true,
288
307
  strict: true,
289
308
  options: {
309
+ "profile": { type: "string", short: "p" },
310
+ "p": { type: "string" },
290
311
  "server": { type: "string" },
291
312
  "name": { type: "string" },
292
313
  "parallel": { type: "string" },
@@ -304,14 +325,15 @@ function parseCommand(argv, env = {}) {
304
325
  if (positionals.length !== 1) {
305
326
  return { name: "help" };
306
327
  }
328
+ const profile = profileFor(values.profile ?? values.p);
307
329
  if (name === "login") {
308
- return { name: "login", server: values.server ?? null, deviceName: values.name ?? null };
330
+ return { name: "login", profile, server: values.server ?? null, deviceName: values.name ?? null };
309
331
  }
310
332
  if (name === "start") {
311
- return { name: "start", parallel: parallelFor(values.parallel, env.TAMSENSE_PARALLEL), browser: !values["no-browser"] };
333
+ return { name: "start", profile, parallel: parallelFor(values.parallel, env.TAMSENSE_PARALLEL), browser: !values["no-browser"] };
312
334
  }
313
335
  if (name === "status" || name === "logout") {
314
- return { name };
336
+ return { name, profile };
315
337
  }
316
338
  return { name: "help" };
317
339
  }
@@ -1554,8 +1576,8 @@ var cacheLock = keyedLock();
1554
1576
  function githubRemoteUrl(fullName) {
1555
1577
  return `https://github.com/${fullName}.git`;
1556
1578
  }
1557
- function tamsenseFolder(home, ...parts) {
1558
- return join3(home, ".tamsense", ...parts);
1579
+ function tamsenseFolder(dataFolder, ...parts) {
1580
+ return join3(dataFolder, ...parts);
1559
1581
  }
1560
1582
  function folderNames(repositories) {
1561
1583
  const shortName = (fullName) => fullName.split("/").pop() ?? fullName;
@@ -1619,15 +1641,15 @@ async function keptBase(cache, path, job, repository) {
1619
1641
  }
1620
1642
  return git(["rev-parse", await baseRef(cache, job, repository)], { cwd: cache });
1621
1643
  }
1622
- function taskFolder(home, job) {
1623
- return tamsenseFolder(home, "work", ...stepFolder(job));
1644
+ function taskFolder(dataFolder, job) {
1645
+ return tamsenseFolder(dataFolder, "work", ...stepFolder(job));
1624
1646
  }
1625
1647
  async function prepareWorkspace(job, token, tools) {
1626
- const root = taskFolder(tools.home, job);
1648
+ const root = taskFolder(tools.dataFolder, job);
1627
1649
  const folders = folderNames(job.repositories);
1628
1650
  const repositories = [];
1629
1651
  for (const [index, repository] of job.repositories.entries()) {
1630
- const cache = tamsenseFolder(tools.home, "repos", repository.fullName.replace("/", "__"));
1652
+ const cache = tamsenseFolder(tools.dataFolder, "repos", repository.fullName.replace("/", "__"));
1631
1653
  const folder = folders[index] ?? repository.fullName;
1632
1654
  const path = join3(root, folder);
1633
1655
  const baseSha = await cacheLock.run(repository.fullName, async () => {
@@ -1742,9 +1764,9 @@ async function startRef(source, job, local) {
1742
1764
  const where = local.remote ? `${source} or on its remote ${local.remote}` : source;
1743
1765
  throw new Error(`Branch ${job.branch} is not in ${where}. It was made on another computer: push it to a remote this folder can fetch, or run this check on that computer.`);
1744
1766
  }
1745
- async function releaseBranch2(source, branch, keep, home) {
1767
+ async function releaseBranch2(source, branch, keep, dataFolder) {
1746
1768
  const listed = await runGit(["worktree", "list", "--porcelain"], { cwd: source });
1747
- const work2 = tamsenseFolder(home, "work");
1769
+ const work2 = tamsenseFolder(dataFolder, "work");
1748
1770
  for (const holder of branchHolders(listed.stdout, branch).filter((path) => resolve3(path) !== resolve3(keep))) {
1749
1771
  if (!isInside(holder, work2)) {
1750
1772
  throw new Error(`Branch ${branch} is checked out in ${resolve3(holder)}. Switch that folder to another branch, then start the step again.`);
@@ -1789,13 +1811,13 @@ async function prepareRepository(job, repository, path, tools) {
1789
1811
  if (readsOnly(job)) {
1790
1812
  await git(["worktree", "add", "--detach", path, start], { cwd: source });
1791
1813
  } else {
1792
- await releaseBranch2(source, job.branch, path, tools.home);
1814
+ await releaseBranch2(source, job.branch, path, tools.dataFolder);
1793
1815
  await git(["worktree", "add", "-B", job.branch, path, start], { cwd: source });
1794
1816
  }
1795
1817
  return { baseSha: await git(["rev-parse", "HEAD"], { cwd: path }), local };
1796
1818
  }
1797
1819
  async function prepareLocalWorkspace(job, tools) {
1798
- const root = taskFolder(tools.home, job);
1820
+ const root = taskFolder(tools.dataFolder, job);
1799
1821
  const folders = folderNames(job.repositories);
1800
1822
  const repositories = [];
1801
1823
  for (const [index, repository] of job.repositories.entries()) {
@@ -2247,14 +2269,14 @@ function wakeUpLaterTool(tools) {
2247
2269
  }
2248
2270
  );
2249
2271
  }
2250
- async function checkFolder(home, job) {
2272
+ async function checkFolder(dataFolder, job) {
2251
2273
  if (job.taskId !== null) {
2252
- const step = taskFolder(home, { taskId: job.taskId, stage: { columnId: job.stepColumnId } });
2274
+ const step = taskFolder(dataFolder, { taskId: job.taskId, stage: { columnId: job.stepColumnId } });
2253
2275
  if (existsSync5(step)) {
2254
2276
  return step;
2255
2277
  }
2256
2278
  }
2257
- const own = tamsenseFolder(home, "schedules", `schedule-${job.scheduleId}`);
2279
+ const own = tamsenseFolder(dataFolder, "schedules", `schedule-${job.scheduleId}`);
2258
2280
  await mkdir3(own, { recursive: true });
2259
2281
  return own;
2260
2282
  }
@@ -3457,12 +3479,16 @@ var runClaude = claudeRunner(query3);
3457
3479
 
3458
3480
  // src/config.ts
3459
3481
  import { mkdir as mkdir4, readFile as readFile3, rm as rm4, writeFile as writeFile3 } from "fs/promises";
3460
- import { homedir as homedir2 } from "os";
3461
3482
  import { dirname as dirname2, join as join6 } from "path";
3462
3483
  var OWNER_ONLY_FILE = 384;
3463
3484
  var OWNER_ONLY_FOLDER = 448;
3464
- function configPath(home = homedir2()) {
3465
- return join6(home, ".tamsense", "runner.json");
3485
+ var PROFILES_FOLDER = "profiles";
3486
+ function dataFolderFor(home, profile) {
3487
+ const shared = join6(home, ".tamsense");
3488
+ return profile === null ? shared : join6(shared, PROFILES_FOLDER, profile);
3489
+ }
3490
+ function configPath(dataFolder) {
3491
+ return join6(dataFolder, "runner.json");
3466
3492
  }
3467
3493
  function isRecord2(value) {
3468
3494
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -3478,7 +3504,7 @@ function parseConfig(value) {
3478
3504
  function isMissingFile(error) {
3479
3505
  return error instanceof Error && "code" in error && error.code === "ENOENT";
3480
3506
  }
3481
- async function readConfig(path = configPath()) {
3507
+ async function readConfig(path) {
3482
3508
  try {
3483
3509
  return parseConfig(JSON.parse(await readFile3(path, "utf8")));
3484
3510
  } catch (error) {
@@ -3488,18 +3514,18 @@ async function readConfig(path = configPath()) {
3488
3514
  throw error;
3489
3515
  }
3490
3516
  }
3491
- async function writeConfig(config, path = configPath()) {
3517
+ async function writeConfig(config, path) {
3492
3518
  await mkdir4(dirname2(path), { recursive: true, mode: OWNER_ONLY_FOLDER });
3493
3519
  await writeFile3(path, `${JSON.stringify(config, null, 4)}
3494
3520
  `, { mode: OWNER_ONLY_FILE });
3495
3521
  }
3496
- async function removeConfig(path = configPath()) {
3522
+ async function removeConfig(path) {
3497
3523
  await rm4(path, { force: true });
3498
3524
  }
3499
3525
 
3500
3526
  // src/login.ts
3501
3527
  var MS_PER_SECOND = 1e3;
3502
- var EXPIRED_TEXT = `The code expired before it was confirmed. Run ${LOGIN_COMMAND} again.`;
3528
+ var EXPIRED_TEXT = "The code expired before it was confirmed. Run the same command again.";
3503
3529
  var DENIED_TEXT = "The connection was cancelled in the browser. This computer was not connected.";
3504
3530
  async function login(tools, request) {
3505
3531
  const pairing = await tools.api.request("POST", PAIRINGS_PATH, request);
@@ -3864,8 +3890,8 @@ function bundledRecorder() {
3864
3890
  const path = fileURLToPath(new URL("./recorder.cjs", import.meta.url));
3865
3891
  return existsSync6(path) ? path : null;
3866
3892
  }
3867
- function recordingsFolder(home, runId) {
3868
- return join8(home, ".tamsense", "recordings", `run-${runId}`);
3893
+ function recordingsFolder(dataFolder, runId) {
3894
+ return join8(dataFolder, "recordings", `run-${runId}`);
3869
3895
  }
3870
3896
  function nodeOptionsWithRecorder(recorder, nodeOptions) {
3871
3897
  return [nodeOptions?.trim(), `--require "${recorder.replaceAll("\\", "/")}"`].filter(Boolean).join(" ");
@@ -3912,8 +3938,9 @@ var MAX_NAME_LENGTH2 = 80;
3912
3938
  function platformLabel(platform = process.platform) {
3913
3939
  return PLATFORM_LABELS[platform] ?? platform;
3914
3940
  }
3915
- function defaultDeviceName() {
3916
- return hostname().slice(0, MAX_NAME_LENGTH2) || "My computer";
3941
+ function defaultDeviceName(profile = null) {
3942
+ const name = hostname() || "My computer";
3943
+ return (profile === null ? name : `${name} (${profile})`).slice(0, MAX_NAME_LENGTH2);
3917
3944
  }
3918
3945
  function browserCommand(url, platform) {
3919
3946
  if (platform === "win32") {
@@ -4171,8 +4198,8 @@ function allowedSkills(stage) {
4171
4198
  const skills = stage.role?.skills ?? null;
4172
4199
  return skills && [.../* @__PURE__ */ new Set([...skills, ...STAGE_SKILLS[stage.kind] ?? []])].filter((name) => SKILL_NAME.test(name));
4173
4200
  }
4174
- function roleLibraryFolder(home, runId) {
4175
- return join10(home, ".tamsense", "libraries", `run-${runId}`);
4201
+ function roleLibraryFolder(dataFolder, runId) {
4202
+ return join10(dataFolder, "libraries", `run-${runId}`);
4176
4203
  }
4177
4204
  async function prepareLibrary(folder, skills, source = LIBRARY_PATH) {
4178
4205
  if (skills === null) {
@@ -4655,7 +4682,7 @@ function skillsLine(skills) {
4655
4682
  async function prepare(job, tools) {
4656
4683
  if (isFolderJob(job)) {
4657
4684
  tools.print("Preparing the task folder\u2026");
4658
- const { results, ...workspace2 } = await prepareFolder(job, taskFolder(tools.home, job), tools.api);
4685
+ const { results, ...workspace2 } = await prepareFolder(job, taskFolder(tools.dataFolder, job), tools.api);
4659
4686
  return { workspace: workspace2, results, identity: {} };
4660
4687
  }
4661
4688
  const taskResults = (workspace2) => writeResults(join12(workspace2.root, RESULTS_FOLDER), job, tools.api);
@@ -4678,12 +4705,12 @@ async function executeJob(job, tools, signal, input, record) {
4678
4705
  if (skills) {
4679
4706
  tools.print(skillsLine(skills));
4680
4707
  }
4681
- const library = await prepareLibrary(roleLibraryFolder(tools.home, job.runId), skills);
4708
+ const library = await prepareLibrary(roleLibraryFolder(tools.dataFolder, job.runId), skills);
4682
4709
  signal.throwIfAborted();
4683
4710
  tools.print("Claude is working\u2026");
4684
4711
  const attached = { count: 0 };
4685
4712
  const session = sessionReporter(tools.api, job, tools.print);
4686
- const recordings = recordingsFolder(tools.home, job.runId);
4713
+ const recordings = recordingsFolder(tools.dataFolder, job.runId);
4687
4714
  const baseEnv = withNodeOnPath(process7.env, dirname6(process7.execPath));
4688
4715
  const recording = tools.recorder ? recorderEnv({ recorder: tools.recorder, folder: recordings, server: tools.api.server, job, nodeOptions: baseEnv.NODE_OPTIONS }) : {};
4689
4716
  const outcome = await tools.runAgent({
@@ -4770,7 +4797,7 @@ function resultLine(finish2, workspace = "repositories") {
4770
4797
  return `Done. Pushed ${finish2.pushed.length} branch(es), the pull requests open on TAMsense.`;
4771
4798
  }
4772
4799
  async function sendRecordings(job, tools) {
4773
- const folder = recordingsFolder(tools.home, job.runId);
4800
+ const folder = recordingsFolder(tools.dataFolder, job.runId);
4774
4801
  try {
4775
4802
  const report = await uploadPendingRecordings(tools.api, job.runId, folder);
4776
4803
  if (report.uploaded > 0) {
@@ -4811,7 +4838,7 @@ async function runJob(job, tools, stop) {
4811
4838
  } catch (error) {
4812
4839
  finish2 = { outcome: "failed", report: failureReport(error, run2.signal), pushed: [], model: null, usage: null, verdict: null, handoff: null, sessionId: null, pausedUntil: null, questions: null, spec: null, waitingFor: null };
4813
4840
  } finally {
4814
- await rm7(roleLibraryFolder(tools.home, job.runId), { recursive: true, force: true });
4841
+ await rm7(roleLibraryFolder(tools.dataFolder, job.runId), { recursive: true, force: true });
4815
4842
  clearInterval(polling);
4816
4843
  clearInterval(feeding);
4817
4844
  stop.removeEventListener("abort", stopRun);
@@ -4823,7 +4850,7 @@ async function runJob(job, tools, stop) {
4823
4850
  }
4824
4851
  await feed.flush();
4825
4852
  if (run2.signal.reason === CLOSED_ON_SITE2) {
4826
- await rm7(recordingsFolder(tools.home, job.runId), { recursive: true, force: true });
4853
+ await rm7(recordingsFolder(tools.dataFolder, job.runId), { recursive: true, force: true });
4827
4854
  tools.print("TAMsense closed this run while it was in progress. The result was not sent.");
4828
4855
  return;
4829
4856
  }
@@ -4854,7 +4881,7 @@ async function runCheckJob(check, runCheck, tools, stop) {
4854
4881
  const beating = setInterval(() => void beat(), CHECK_HEARTBEAT_MS);
4855
4882
  let body;
4856
4883
  try {
4857
- const folder = await checkFolder(tools.home, check);
4884
+ const folder = await checkFolder(tools.dataFolder, check);
4858
4885
  const env = withNodeOnPath(process7.env, dirname6(process7.execPath));
4859
4886
  const prompt = routine ? buildRoutinePrompt(check, folder) : buildCheckPrompt(check, folder);
4860
4887
  body = checkFinishBody(await runCheck({ cwd: folder, prompt, model: check.model, env, signal: run2.signal, sources: sourceTools(check.sourceTools, tools.api, checkPath(check.checkId, "sources")), routine }));
@@ -4994,11 +5021,18 @@ async function work(tools, stop, parallel = 1) {
4994
5021
  }
4995
5022
 
4996
5023
  // src/cli.ts
4997
- var NOT_CONNECTED_TEXT = `This computer is not connected. Run ${LOGIN_COMMAND}`;
4998
- var DISCONNECTED_TEXT = `This computer was disconnected on the website. Run ${LOGIN_COMMAND}`;
4999
5024
  var NO_TASKS_ACCESS_TEXT = "Your account no longer has access to Tasks. Ask the project owner.";
5000
- var ACCESS_PROBLEMS = { 401: DISCONNECTED_TEXT, 403: NO_TASKS_ACCESS_TEXT };
5025
+ var UNAUTHORIZED_STATUS = 401;
5026
+ var FORBIDDEN_STATUS = 403;
5001
5027
  var INTERRUPTED_EXIT_CODE = 130;
5028
+ function connectionFor(profile) {
5029
+ const dataFolder = dataFolderFor(homedir2(), profile);
5030
+ return { profile, dataFolder, configFile: configPath(dataFolder) };
5031
+ }
5032
+ function notConnectedText(profile) {
5033
+ const which = profile === null ? "" : ` in the profile ${profile}`;
5034
+ return `This computer is not connected${which}. Run ${withProfile(LOGIN_COMMAND, profile)}`;
5035
+ }
5002
5036
  function print(line) {
5003
5037
  process8.stdout.write(`${line}
5004
5038
  `);
@@ -5006,27 +5040,33 @@ function print(line) {
5006
5040
  function serverFor(flag, saved) {
5007
5041
  return serverOrigin(flag ?? process8.env.TAMSENSE_URL ?? saved?.server ?? DEFAULT_SERVER);
5008
5042
  }
5009
- function accessProblem(error) {
5010
- return error instanceof ApiError ? ACCESS_PROBLEMS[error.status] ?? null : null;
5043
+ function accessProblem(error, profile) {
5044
+ if (!(error instanceof ApiError)) {
5045
+ return null;
5046
+ }
5047
+ if (error.status === UNAUTHORIZED_STATUS) {
5048
+ return `This computer was disconnected on the website. Run ${withProfile(LOGIN_COMMAND, profile)}`;
5049
+ }
5050
+ return error.status === FORBIDDEN_STATUS ? NO_TASKS_ACCESS_TEXT : null;
5011
5051
  }
5012
- async function forgetDevice(config) {
5052
+ async function forgetDevice(config, connection) {
5013
5053
  await createApi(config.server, config.token).request("DELETE", IDENTITY_PATH).catch(() => {
5014
5054
  });
5015
- await removeConfig();
5055
+ await removeConfig(connection.configFile);
5016
5056
  }
5017
- async function runLogin(server, deviceName) {
5018
- const saved = await readConfig();
5057
+ async function runLogin(connection, server, deviceName) {
5058
+ const saved = await readConfig(connection.configFile);
5019
5059
  const api = createApi(serverFor(server, saved));
5020
5060
  if (saved) {
5021
- await forgetDevice(saved);
5061
+ await forgetDevice(saved, connection);
5022
5062
  }
5023
- const name = deviceName?.trim() || defaultDeviceName();
5063
+ const name = deviceName?.trim() || defaultDeviceName(connection.profile);
5024
5064
  const result = await login(
5025
5065
  { api, openBrowser, print, sleep, now: Date.now },
5026
5066
  { name, platform: platformLabel() }
5027
5067
  );
5028
- await writeConfig({ server: api.server, token: result.token, deviceId: result.device.id, deviceName: result.device.name });
5029
- print(`Connected: ${result.device.name}. To take your tasks, go to your code folder and run ${START_COMMAND}`);
5068
+ await writeConfig({ server: api.server, token: result.token, deviceId: result.device.id, deviceName: result.device.name }, connection.configFile);
5069
+ print(`Connected: ${result.device.name}. To take your tasks, go to your code folder and run ${withProfile(START_COMMAND, connection.profile)}`);
5030
5070
  }
5031
5071
  function stopOnInterrupt() {
5032
5072
  const stop = new AbortController();
@@ -5061,13 +5101,13 @@ async function startedNewVersion() {
5061
5101
  return false;
5062
5102
  }
5063
5103
  }
5064
- async function runStart(parallel, installsBrowser) {
5104
+ async function runStart(connection, parallel, installsBrowser) {
5065
5105
  if (await startedNewVersion()) {
5066
5106
  return;
5067
5107
  }
5068
- const saved = await readConfig();
5108
+ const saved = await readConfig(connection.configFile);
5069
5109
  if (!saved) {
5070
- print(NOT_CONNECTED_TEXT);
5110
+ print(notConnectedText(connection.profile));
5071
5111
  return;
5072
5112
  }
5073
5113
  const identity = await createApi(saved.server, saved.token).request("GET", IDENTITY_PATH);
@@ -5076,13 +5116,13 @@ async function runStart(parallel, installsBrowser) {
5076
5116
  throw new Error(problem2);
5077
5117
  }
5078
5118
  print(`${identity.device.name} is connected to ${saved.server} as ${describeDevice(identity)}.`);
5079
- const home = homedir3();
5119
+ const home = homedir2();
5080
5120
  const browser = await browserForStart(home, installsBrowser);
5081
5121
  const api = createApi(saved.server, saved.token, { watch: connectionWatch(print) });
5082
- await work({ api, runAgent: runClaude, runCheck: runClaudeCheck, runSense: runClaudeSense, version: currentVersion() ?? void 0, capabilities: probeCapabilities, print, sleep, home, remoteUrl: githubRemoteUrl, browser, recorder: bundledRecorder(), inspectFolder: (path) => inspectFolder(path, home), startFolder: process8.cwd() }, stopOnInterrupt(), parallel);
5122
+ await work({ api, runAgent: runClaude, runCheck: runClaudeCheck, runSense: runClaudeSense, version: currentVersion() ?? void 0, capabilities: probeCapabilities, print, sleep, home, dataFolder: connection.dataFolder, remoteUrl: githubRemoteUrl, browser, recorder: bundledRecorder(), inspectFolder: (path) => inspectFolder(path, home), startFolder: process8.cwd() }, stopOnInterrupt(), parallel);
5083
5123
  }
5084
5124
  async function runBrowser(remove) {
5085
- const home = homedir3();
5125
+ const home = homedir2();
5086
5126
  if (remove) {
5087
5127
  print(await removeBrowser(home) ? "Removed the headless browser. The next start installs it again, unless you add --no-browser." : "No headless browser is installed.");
5088
5128
  return;
@@ -5095,46 +5135,53 @@ async function runBrowser(remove) {
5095
5135
  await installBrowser(installTools(home));
5096
5136
  print("Claude uses it for interface checks from the next start.");
5097
5137
  }
5098
- async function runStatus() {
5099
- const saved = await readConfig();
5138
+ async function runStatus(connection) {
5139
+ const saved = await readConfig(connection.configFile);
5100
5140
  if (!saved) {
5101
- print(NOT_CONNECTED_TEXT);
5141
+ print(notConnectedText(connection.profile));
5102
5142
  return;
5103
5143
  }
5104
5144
  const identity = await createApi(saved.server, saved.token).request("GET", IDENTITY_PATH);
5105
5145
  print(`Connected: ${identity.device.name}, ${describeDevice(identity)}`);
5106
5146
  print(`Account: ${identity.email ?? "unknown"}, project ${identity.projectName}`);
5107
5147
  print(`Server: ${saved.server}`);
5148
+ if (connection.profile !== null) {
5149
+ print(`Profile: ${connection.profile}, folders in ${connection.dataFolder}`);
5150
+ }
5108
5151
  }
5109
- async function runLogout() {
5110
- const saved = await readConfig();
5152
+ async function runLogout(connection) {
5153
+ const saved = await readConfig(connection.configFile);
5111
5154
  if (!saved) {
5112
- print(NOT_CONNECTED_TEXT);
5155
+ print(notConnectedText(connection.profile));
5113
5156
  return;
5114
5157
  }
5115
- await forgetDevice(saved);
5158
+ await forgetDevice(saved, connection);
5116
5159
  print("Disconnected. This computer no longer takes your tasks.");
5117
5160
  }
5118
5161
  async function run(command) {
5119
5162
  if (command.name === "login") {
5120
- await runLogin(command.server, command.deviceName);
5163
+ await runLogin(connectionFor(command.profile), command.server, command.deviceName);
5121
5164
  } else if (command.name === "start") {
5122
- await runStart(command.parallel, command.browser);
5165
+ await runStart(connectionFor(command.profile), command.parallel, command.browser);
5123
5166
  } else if (command.name === "browser") {
5124
5167
  await runBrowser(command.remove);
5125
5168
  } else if (command.name === "status") {
5126
- await runStatus();
5169
+ await runStatus(connectionFor(command.profile));
5127
5170
  } else if (command.name === "logout") {
5128
- await runLogout();
5171
+ await runLogout(connectionFor(command.profile));
5129
5172
  } else {
5130
5173
  print(HELP_TEXT);
5131
5174
  }
5132
5175
  }
5133
- try {
5134
- await run(parseCommand(process8.argv.slice(2), process8.env));
5135
- } catch (error) {
5136
- process8.stderr.write(`${accessProblem(error) ?? (error instanceof Error ? error.message : String(error))}
5176
+ function fail(error, profile) {
5177
+ process8.stderr.write(`${accessProblem(error, profile) ?? (error instanceof Error ? error.message : String(error))}
5137
5178
  `);
5138
5179
  process8.exitCode = 1;
5139
5180
  }
5181
+ try {
5182
+ const command = parseCommand(process8.argv.slice(2), process8.env);
5183
+ await run(command).catch((error) => fail(error, "profile" in command ? command.profile : null));
5184
+ } catch (error) {
5185
+ fail(error, null);
5186
+ }
5140
5187
  //# sourceMappingURL=cli.js.map