@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 +15 -0
- package/dist/cli.js +118 -71
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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
|
|
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(
|
|
1558
|
-
return join3(
|
|
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(
|
|
1623
|
-
return tamsenseFolder(
|
|
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.
|
|
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.
|
|
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,
|
|
1767
|
+
async function releaseBranch2(source, branch, keep, dataFolder) {
|
|
1746
1768
|
const listed = await runGit(["worktree", "list", "--porcelain"], { cwd: source });
|
|
1747
|
-
const work2 = tamsenseFolder(
|
|
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.
|
|
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.
|
|
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(
|
|
2272
|
+
async function checkFolder(dataFolder, job) {
|
|
2251
2273
|
if (job.taskId !== null) {
|
|
2252
|
-
const step = taskFolder(
|
|
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(
|
|
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
|
-
|
|
3465
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
3868
|
-
return join8(
|
|
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
|
-
|
|
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(
|
|
4175
|
-
return join10(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
5134
|
-
|
|
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
|