@refleet-it/runner 0.1.186 → 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 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-runner login # API URL, email, password → mints an API key and stores it locally
29
- refleet-runner run
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-runner whoami # which credentials are in effect, and whether they still work
38
- refleet-runner logout # revokes the stored key on the server and forgets it locally
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-runner run
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-runner run' to start the runner.\n");
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-runner login'.\n");
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-runner login';
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-runner login' (or set them directly) first.");
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-runner: shutting down after the current job\n');
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-runner: fleet loop died: ${errorMessage(err)}\n`);
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-runner: unknown command ${JSON.stringify(command ?? '')}\n`);
138
- process.stderr.write('Usage: refleet-runner <login|logout|whoami|run>\n');
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-runner while
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-runner: ${errorMessage(err)}\n`);
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-runner login`, read back by `whoami`/
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-runner
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
  */
@@ -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) {
@@ -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
- const mergeRequestUrl = await createMergeRequest(credentials, project.externalId, { sourceBranch: branchName, targetBranch: project.defaultBranch, title, description }, fetchFn);
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.186",
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": {