@refleet-it/runner 0.1.196 → 0.1.197
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 +8 -5
- package/dist/cli.js +10 -10
- package/dist/config.js +1 -1
- package/dist/detect.js +1 -1
- package/dist/fleet/gitlab.js +30 -0
- package/dist/fleet/workspace.js +8 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -22,11 +22,14 @@ twice in Refleet.
|
|
|
22
22
|
npm install -g @refleet-it/runner
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
The command is `refleet`; `refleet-runner` still works as an alias for scripts written
|
|
26
|
+
against earlier releases.
|
|
27
|
+
|
|
25
28
|
## Connect and run
|
|
26
29
|
|
|
27
30
|
```bash
|
|
28
|
-
refleet
|
|
29
|
-
refleet
|
|
31
|
+
refleet login # API URL, email, password → mints an API key and stores it locally
|
|
32
|
+
refleet run
|
|
30
33
|
```
|
|
31
34
|
|
|
32
35
|
`login` keeps its API key in `~/.config/refleet/runner.json` (`XDG_CONFIG_HOME` is honoured).
|
|
@@ -34,8 +37,8 @@ Anything headless — a server, a container, CI — should instead set `REFLEET_
|
|
|
34
37
|
`REFLEET_API_KEY` directly; when both are set they take precedence over the stored login.
|
|
35
38
|
|
|
36
39
|
```bash
|
|
37
|
-
refleet
|
|
38
|
-
refleet
|
|
40
|
+
refleet whoami # which credentials are in effect, and whether they still work
|
|
41
|
+
refleet logout # revokes the stored key on the server and forgets it locally
|
|
39
42
|
```
|
|
40
43
|
|
|
41
44
|
`run` has no idle mode: without credentials it exits immediately, and a job in progress is
|
|
@@ -48,7 +51,7 @@ Description=Refleet runner
|
|
|
48
51
|
After=network-online.target
|
|
49
52
|
|
|
50
53
|
[Service]
|
|
51
|
-
ExecStart=/usr/bin/env refleet
|
|
54
|
+
ExecStart=/usr/bin/env refleet run
|
|
52
55
|
Environment=REFLEET_API_URL=https://refleet.it/api
|
|
53
56
|
Environment=REFLEET_API_KEY=…
|
|
54
57
|
Environment=ANTHROPIC_API_KEY=…
|
package/dist/cli.js
CHANGED
|
@@ -48,7 +48,7 @@ async function runLogin() {
|
|
|
48
48
|
createdAt: created.createdAt,
|
|
49
49
|
});
|
|
50
50
|
process.stdout.write(`Logged in as ${email}. API key ${created.prefix}… saved to your local config.\n`);
|
|
51
|
-
process.stdout.write("Run 'refleet
|
|
51
|
+
process.stdout.write("Run 'refleet run' to start the runner.\n");
|
|
52
52
|
}
|
|
53
53
|
/** Revokes the locally saved key on the backend (best-effort) and clears the local config. */
|
|
54
54
|
async function runLogout() {
|
|
@@ -70,11 +70,11 @@ async function runLogout() {
|
|
|
70
70
|
async function runWhoami() {
|
|
71
71
|
const resolved = resolveFleetConfig(process.env);
|
|
72
72
|
if (!resolved) {
|
|
73
|
-
process.stdout.write("Not logged in, and REFLEET_API_URL/REFLEET_API_KEY aren't set. Run 'refleet
|
|
73
|
+
process.stdout.write("Not logged in, and REFLEET_API_URL/REFLEET_API_KEY aren't set. Run 'refleet login'.\n");
|
|
74
74
|
process.exitCode = 1;
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
|
-
const sourceLabel = 'env' === resolved.source ? 'environment variables (automatic mode)' : 'refleet
|
|
77
|
+
const sourceLabel = 'env' === resolved.source ? 'environment variables (automatic mode)' : 'refleet login';
|
|
78
78
|
process.stdout.write(`API URL: ${resolved.apiUrl}\n`);
|
|
79
79
|
process.stdout.write(`API key: ${resolved.apiKey.slice(0, 12)}…\n`);
|
|
80
80
|
process.stdout.write(`Source: ${sourceLabel}\n`);
|
|
@@ -96,11 +96,11 @@ async function runWhoami() {
|
|
|
96
96
|
async function runRunner() {
|
|
97
97
|
const credentials = resolveFleetConfig(process.env);
|
|
98
98
|
if (!credentials) {
|
|
99
|
-
throw new Error("REFLEET_API_URL and REFLEET_API_KEY are both required — this runner only supports fleet mode. Run 'refleet
|
|
99
|
+
throw new Error("REFLEET_API_URL and REFLEET_API_KEY are both required — this runner only supports fleet mode. Run 'refleet login' (or set them directly) first.");
|
|
100
100
|
}
|
|
101
101
|
const controller = new AbortController();
|
|
102
102
|
const stop = () => {
|
|
103
|
-
process.stdout.write('refleet
|
|
103
|
+
process.stdout.write('refleet: shutting down after the current job\n');
|
|
104
104
|
controller.abort();
|
|
105
105
|
};
|
|
106
106
|
process.once('SIGINT', stop);
|
|
@@ -114,7 +114,7 @@ async function runRunner() {
|
|
|
114
114
|
}
|
|
115
115
|
// Reached only through an unexpected exception inside a poll loop: exit non-zero so
|
|
116
116
|
// the supervisor (Docker restart policy, systemd) restarts the whole runner.
|
|
117
|
-
process.stderr.write(`refleet
|
|
117
|
+
process.stderr.write(`refleet: fleet loop died: ${errorMessage(err)}\n`);
|
|
118
118
|
process.exitCode = 1;
|
|
119
119
|
}
|
|
120
120
|
}
|
|
@@ -134,13 +134,13 @@ async function main() {
|
|
|
134
134
|
await runRunner();
|
|
135
135
|
return;
|
|
136
136
|
default:
|
|
137
|
-
process.stderr.write(`refleet
|
|
138
|
-
process.stderr.write('Usage: refleet
|
|
137
|
+
process.stderr.write(`refleet: unknown command ${JSON.stringify(command ?? '')}\n`);
|
|
138
|
+
process.stderr.write('Usage: refleet <login|logout|whoami|run>\n');
|
|
139
139
|
process.exitCode = 1;
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
/**
|
|
143
|
-
* npm links bin entries as symlinks, so argv[1] is node_modules/.bin/refleet
|
|
143
|
+
* npm links bin entries as symlinks, so argv[1] is node_modules/.bin/refleet while
|
|
144
144
|
* import.meta.url is the resolved dist/cli.js — comparing them raw made the installed CLI a
|
|
145
145
|
* silent no-op. Resolve the symlink, and build the URL rather than concatenating it so paths
|
|
146
146
|
* with spaces compare correctly too.
|
|
@@ -159,7 +159,7 @@ function isMainModule() {
|
|
|
159
159
|
}
|
|
160
160
|
if (isMainModule()) {
|
|
161
161
|
main().catch((err) => {
|
|
162
|
-
process.stderr.write(`refleet
|
|
162
|
+
process.stderr.write(`refleet: ${errorMessage(err)}\n`);
|
|
163
163
|
process.exitCode = 1;
|
|
164
164
|
});
|
|
165
165
|
}
|
package/dist/config.js
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node
|
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
/**
|
|
5
|
-
* Local login credentials saved by `refleet
|
|
5
|
+
* Local login credentials saved by `refleet login`, read back by `whoami`/
|
|
6
6
|
* `logout` and used by `run` when REFLEET_API_URL/REFLEET_API_KEY aren't set.
|
|
7
7
|
* Lives under XDG_CONFIG_HOME (falling back to ~/.config, the convention
|
|
8
8
|
* most CLI tools on the host already follow) rather than inside this repo, since
|
package/dist/detect.js
CHANGED
|
@@ -46,7 +46,7 @@ export function resolveExecutable(command, env, isExecutable = defaultIsExecutab
|
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* Detects which of the supported agent CLIs are available on this host — the
|
|
49
|
-
* auto-detection this whole runner redesign is for. Called once when `refleet
|
|
49
|
+
* auto-detection this whole runner redesign is for. Called once when `refleet
|
|
50
50
|
* run` starts (see fleet/loop.ts); a CLI installed after that requires a restart, not
|
|
51
51
|
* a live re-probe.
|
|
52
52
|
*/
|
package/dist/fleet/gitlab.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every merge request a runner opens carries this label so customers can tell Refleet's
|
|
3
|
+
* work from their own. The backend creates it in the group at sync time; the runner only
|
|
4
|
+
* falls back to a project label when it is missing, because GitLab would otherwise invent
|
|
5
|
+
* one in a random colour the moment the MR is created.
|
|
6
|
+
*/
|
|
7
|
+
export const REFLEET_LABEL = { name: 'refleet', color: '#000000', description: 'Merge requests opened by Refleet' };
|
|
1
8
|
export class GitLabRequestError extends Error {
|
|
2
9
|
status;
|
|
3
10
|
constructor(message, status) {
|
|
@@ -31,6 +38,28 @@ async function findOpenMergeRequest(credentials, projectId, spec, fetchFn) {
|
|
|
31
38
|
const list = (await response.json());
|
|
32
39
|
return list[0]?.web_url ?? null;
|
|
33
40
|
}
|
|
41
|
+
/**
|
|
42
|
+
* Returns true when the label is available to the project (its own or inherited from an
|
|
43
|
+
* ancestor group) by the time it returns. Never throws: a missing label is worth a warning,
|
|
44
|
+
* not a failed job — the MR itself is what the shift is about.
|
|
45
|
+
*/
|
|
46
|
+
export async function ensureRefleetLabel(credentials, projectId, fetchFn = fetch) {
|
|
47
|
+
try {
|
|
48
|
+
const query = new URLSearchParams({ search: REFLEET_LABEL.name, include_ancestor_groups: 'true', per_page: '100' });
|
|
49
|
+
const listed = await gitlabRequest(credentials, fetchFn, 'GET', `/projects/${projectId}/labels?${query.toString()}`);
|
|
50
|
+
if (listed.ok) {
|
|
51
|
+
const labels = (await listed.json());
|
|
52
|
+
if (labels.some(label => label.name === REFLEET_LABEL.name)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const created = await gitlabRequest(credentials, fetchFn, 'POST', `/projects/${projectId}/labels`, REFLEET_LABEL);
|
|
57
|
+
return created.ok || 409 === created.status;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
34
63
|
/**
|
|
35
64
|
* Opens a merge request straight through the GitLab REST API — the branch has already
|
|
36
65
|
* been pushed by publishChange, so unlike `glab mr create --push` this never needs
|
|
@@ -46,6 +75,7 @@ export async function createMergeRequest(credentials, projectId, spec, fetchFn =
|
|
|
46
75
|
target_branch: spec.targetBranch,
|
|
47
76
|
title: spec.title,
|
|
48
77
|
description: spec.description,
|
|
78
|
+
...(spec.labels ? { labels: spec.labels.join(',') } : {}),
|
|
49
79
|
remove_source_branch: true,
|
|
50
80
|
});
|
|
51
81
|
if (409 === response.status) {
|
package/dist/fleet/workspace.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, utimesSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { gitAuthConfig } from './git.js';
|
|
4
|
-
import { createMergeRequest } from './gitlab.js';
|
|
4
|
+
import { createMergeRequest, ensureRefleetLabel, REFLEET_LABEL } from './gitlab.js';
|
|
5
5
|
const LAST_USED_MARKER = '.slipway-last-used';
|
|
6
6
|
function directorySizeBytes(dir) {
|
|
7
7
|
let total = 0;
|
|
@@ -119,6 +119,12 @@ export async function publishChange(workspace, credentials, repoDir, project, sh
|
|
|
119
119
|
}
|
|
120
120
|
await workspace.git(['-c', 'user.name=Slipway', '-c', 'user.email=slipway@localhost', 'commit', '-m', title], repoDir);
|
|
121
121
|
await workspace.git([...gitAuthConfig(credentials.accessToken), 'push', '--force', 'origin', `HEAD:refs/heads/${branchName}`], repoDir);
|
|
122
|
-
|
|
122
|
+
// Without the label in place GitLab would create it in a random colour, so the MR
|
|
123
|
+
// rather goes out unlabelled than off-brand.
|
|
124
|
+
const labelled = await ensureRefleetLabel(credentials, project.externalId, fetchFn);
|
|
125
|
+
if (!labelled) {
|
|
126
|
+
workspace.log(`could not ensure the "${REFLEET_LABEL.name}" label on ${project.path}; opening the merge request without it`);
|
|
127
|
+
}
|
|
128
|
+
const mergeRequestUrl = await createMergeRequest(credentials, project.externalId, { sourceBranch: branchName, targetBranch: project.defaultBranch, title, description, labels: labelled ? [REFLEET_LABEL.name] : undefined }, fetchFn);
|
|
123
129
|
return { branchName, mergeRequestUrl };
|
|
124
130
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@refleet-it/runner",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.197",
|
|
4
4
|
"description": "Refleet fleet runner: claims code-modernisation jobs from the Refleet API and runs them through Claude Code or Kiro on your own machine.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"refleet",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"license": "MIT",
|
|
23
23
|
"type": "module",
|
|
24
24
|
"bin": {
|
|
25
|
+
"refleet": "dist/cli.js",
|
|
25
26
|
"refleet-runner": "dist/cli.js"
|
|
26
27
|
},
|
|
27
28
|
"engines": {
|