@just-every/manager 0.1.65 → 0.1.67

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
@@ -17,6 +17,29 @@ justevery-manager
17
17
  Use `--download-only` to cache the installer without launching it, and
18
18
  `--print-path` to print the cached path.
19
19
 
20
+ ### Queue commands
21
+
22
+ ```bash
23
+ justevery-manager queue devices
24
+
25
+ justevery-manager queue start \
26
+ --device dev_123 \
27
+ --message "Start a new session"
28
+
29
+ justevery-manager queue followup \
30
+ --session sess_123 \
31
+ --message "Add tests"
32
+
33
+ justevery-manager queue state --session sess_123
34
+ ```
35
+
36
+ Queue commands require a Manager org id and session cookie:
37
+
38
+ ```bash
39
+ export ORG_ID=acct-...
40
+ export SESSION_COOKIE=better-auth.session_token_value
41
+ ```
42
+
20
43
  On headless Linux, the wrapper will download and run the headless daemon
21
44
  (`Every.Manager_*_linux_amd64_headless`) when available.
22
45
 
@@ -30,3 +53,6 @@ On headless Linux, the wrapper will download and run the headless daemon
30
53
  - `JE_AGENT_GITHUB_TOKEN` – optional GitHub token for private releases (also supports `GH_TOKEN` / `GITHUB_TOKEN`).
31
54
  - `JE_AGENT_SKIP_DOWNLOAD=1` – skip the postinstall download/caching step.
32
55
  - `JE_AGENT_SKIP_LAUNCH=1` – skip launching the installer after download (also supports `JE_MANAGER_SKIP_LAUNCH`).
56
+ - `ORG_ID` – Manager org id for queue commands.
57
+ - `SESSION_COOKIE` – `better-auth.session_token` value for queue commands.
58
+ - `BASE_URL` – Manager base URL (defaults to https://manager.justevery.com).
@@ -3,31 +3,40 @@ import { ensureInstaller, launchInstaller } from '../lib/installer.js';
3
3
  import { platform as osPlatform } from 'os';
4
4
 
5
5
  const args = process.argv.slice(2);
6
- const wantsHelp = args.includes('--help') || args.includes('-h');
7
- const skipLaunchEnv = process.env.JE_AGENT_SKIP_LAUNCH === '1' || process.env.JE_MANAGER_SKIP_LAUNCH === '1';
8
- const forceForeground = args.includes('--foreground') || args.includes('--no-daemon');
9
- const forceDaemon = args.includes('--daemon');
10
- const sessionType = (process.env.XDG_SESSION_TYPE || '').toLowerCase();
11
- const hasDisplay = Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
12
- const hasGuiSession = hasDisplay || (Boolean(sessionType) && sessionType !== 'tty');
13
- const isHeadlessLinux = osPlatform() === 'linux' && !hasGuiSession;
14
- const downloadOnly = args.includes('--download-only') || args.includes('--no-open') || skipLaunchEnv;
15
- const printPath = args.includes('--print-path');
16
- const launchMode = forceForeground ? 'foreground' : forceDaemon ? 'daemon' : 'auto';
17
-
18
- if (wantsHelp) {
19
- console.log(`Every Manager installer helper
6
+
7
+ const runInstaller = async () => {
8
+ const wantsHelp = args.includes('--help') || args.includes('-h');
9
+ const skipLaunchEnv = process.env.JE_AGENT_SKIP_LAUNCH === '1' || process.env.JE_MANAGER_SKIP_LAUNCH === '1';
10
+ const forceForeground = args.includes('--foreground') || args.includes('--no-daemon');
11
+ const forceDaemon = args.includes('--daemon');
12
+ const sessionType = (process.env.XDG_SESSION_TYPE || '').toLowerCase();
13
+ const hasDisplay = Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
14
+ const hasGuiSession = hasDisplay || (Boolean(sessionType) && sessionType !== 'tty');
15
+ const isHeadlessLinux = osPlatform() === 'linux' && !hasGuiSession;
16
+ const downloadOnly = args.includes('--download-only') || args.includes('--no-open') || skipLaunchEnv;
17
+ const printPath = args.includes('--print-path');
18
+ const launchMode = forceForeground ? 'foreground' : forceDaemon ? 'daemon' : 'auto';
19
+
20
+ if (wantsHelp) {
21
+ console.log(`Every Manager CLI
20
22
 
21
23
  Usage:
22
24
  justevery-manager [--download-only] [--print-path]
25
+ justevery-manager queue <command>
23
26
 
24
- Options:
27
+ Installer Options:
25
28
  --download-only Download the installer but do not launch it.
26
29
  --print-path Print the installer path after download.
27
30
  --foreground Run the Linux headless daemon in the foreground.
28
31
  --daemon Force the Linux headless daemon to run in the background.
29
32
  -h, --help Show this help message.
30
33
 
34
+ Queue Commands:
35
+ queue start Start a new session on a device.
36
+ queue followup Enqueue a follow-up message for a session.
37
+ queue state Show queue state for a session.
38
+ queue devices List available devices.
39
+
31
40
  Environment:
32
41
  JE_AGENT_VERSION Override the agent version.
33
42
  JE_AGENT_RELEASE_TAG Override the GitHub release tag.
@@ -35,21 +44,36 @@ Environment:
35
44
  JE_AGENT_FALLBACK_BASE_URL Add fallback base URLs (comma-separated).
36
45
  JE_AGENT_ASSET Override the installer filename.
37
46
  JE_AGENT_GITHUB_TOKEN Optional GitHub token for private releases.
47
+ ORG_ID Manager org id for queue commands.
48
+ SESSION_COOKIE better-auth.session_token value for queue commands.
49
+ BASE_URL Manager base URL (default https://manager.justevery.com).
38
50
  `);
39
- process.exit(0);
40
- }
51
+ process.exit(0);
52
+ }
41
53
 
42
- try {
43
- const result = await ensureInstaller({ allowDownload: true });
44
- if (printPath || downloadOnly) {
45
- console.log(result.path);
54
+ try {
55
+ const result = await ensureInstaller({ allowDownload: true });
56
+ if (printPath || downloadOnly) {
57
+ console.log(result.path);
58
+ }
59
+ if (!downloadOnly) {
60
+ await launchInstaller(result.path, { mode: launchMode });
61
+ } else if (isHeadlessLinux) {
62
+ console.log('No desktop session detected. Run the installer on a machine with a GUI.');
63
+ }
64
+ } catch (error) {
65
+ console.error(`Failed to prepare installer: ${error.message}`);
66
+ process.exit(1);
46
67
  }
47
- if (!downloadOnly) {
48
- await launchInstaller(result.path, { mode: launchMode });
49
- } else if (isHeadlessLinux) {
50
- console.log('No desktop session detected. Run the installer on a machine with a GUI.');
68
+ };
69
+
70
+ const main = async () => {
71
+ if (args[0] === 'queue') {
72
+ const { runQueueCommand } = await import('../lib/queue-cli.js');
73
+ await runQueueCommand(args.slice(1));
74
+ return;
51
75
  }
52
- } catch (error) {
53
- console.error(`Failed to prepare installer: ${error.message}`);
54
- process.exit(1);
55
- }
76
+ await runInstaller();
77
+ };
78
+
79
+ await main();
@@ -0,0 +1,260 @@
1
+ import { parseArgs } from 'node:util';
2
+
3
+ const DEFAULT_BASE_URL = 'https://manager.justevery.com';
4
+
5
+ const USAGE = `
6
+ Every Manager queue commands
7
+
8
+ Usage:
9
+ justevery-manager queue <command> [options]
10
+
11
+ Commands:
12
+ start Start a new session on a device
13
+ followup Enqueue a follow-up message for a session
14
+ state Show queue state for a session
15
+ devices List devices (id, name, run status)
16
+
17
+ Options:
18
+ --org Manager org id (or ORG_ID env)
19
+ --cookie better-auth.session_token value (or SESSION_COOKIE env)
20
+ --base-url Manager base URL (default: https://manager.justevery.com)
21
+ --device Device id
22
+ --session Session id
23
+ --message Message/prompt
24
+ --prompt Alias for --message
25
+ --project Project id
26
+ --cli CLI name (code|codex|claude|gemini|qwen)
27
+ --cwd Working directory
28
+ --action followup action override (resume|new)
29
+ -h, --help Show help
30
+ `;
31
+
32
+ const normalizeCookie = (value) => {
33
+ if (!value) return '';
34
+ if (value.includes('better-auth.session_token=')) return value;
35
+ return `better-auth.session_token=${value}`;
36
+ };
37
+
38
+ const resolveBaseUrl = (value) => value || process.env.BASE_URL || process.env.MANAGER_API_BASE || DEFAULT_BASE_URL;
39
+
40
+ const resolveOrgId = (value) => value || process.env.ORG_ID || process.env.MANAGER_ORG_ID || '';
41
+
42
+ const resolveCookie = (value) => value || process.env.SESSION_COOKIE || process.env.MANAGER_SESSION_COOKIE || '';
43
+
44
+ const buildAuthHeaders = (orgId, cookie) => {
45
+ if (!orgId) throw new Error('ORG_ID is required (--org or ORG_ID env)');
46
+ if (!cookie) throw new Error('SESSION_COOKIE is required (--cookie or SESSION_COOKIE env)');
47
+ return {
48
+ 'Content-Type': 'application/json',
49
+ 'x-manager-org': orgId,
50
+ Cookie: normalizeCookie(cookie),
51
+ };
52
+ };
53
+
54
+ const parseResponse = async (response) => {
55
+ const text = await response.text();
56
+ if (!text) return null;
57
+ try {
58
+ return JSON.parse(text);
59
+ } catch {
60
+ return null;
61
+ }
62
+ };
63
+
64
+ const request = async (baseUrl, path, options = {}) => {
65
+ const url = `${baseUrl}${path}`;
66
+ const response = await fetch(url, options);
67
+ const data = await parseResponse(response);
68
+ if (!response.ok) {
69
+ const detail = data?.error || (data?.message ?? null) || response.statusText || 'Request failed';
70
+ throw new Error(detail);
71
+ }
72
+ return data;
73
+ };
74
+
75
+ const listDevices = async (baseUrl, headers) => {
76
+ const data = await request(baseUrl, '/api/manager/devices', {
77
+ method: 'GET',
78
+ headers,
79
+ });
80
+ return Array.isArray(data?.devices) ? data.devices : [];
81
+ };
82
+
83
+ const printQueueState = (queueState) => {
84
+ if (!queueState) {
85
+ console.log('Queue state unavailable.');
86
+ return;
87
+ }
88
+ const queued = queueState.queuedCount ?? 0;
89
+ const running = queueState.runningCount ?? 0;
90
+ const status = queueState.status || 'unknown';
91
+ console.log(`Queue: ${queued} queued, ${running} running (status=${status}).`);
92
+ if (queueState.error) {
93
+ console.log(`Last error: ${queueState.error}`);
94
+ }
95
+ };
96
+
97
+ const handleStart = async (baseUrl, headers, values) => {
98
+ const deviceId = values.device || '';
99
+ const message = (values.message || values.prompt || '').trim();
100
+ const projectId = values.project || '';
101
+ const cliNameInput = (values.cli || '').trim();
102
+ const cwd = (values.cwd || '').trim();
103
+
104
+ if (!deviceId) throw new Error('device id required (--device)');
105
+ if (!message) throw new Error('message required (--message)');
106
+
107
+ let cliName = cliNameInput;
108
+ let device = null;
109
+ if (!cliName) {
110
+ const devices = await listDevices(baseUrl, headers);
111
+ device = devices.find((entry) => entry.id === deviceId) || null;
112
+ cliName = (device?.cliName || '').trim();
113
+ if (!cliName) {
114
+ throw new Error('CLI name required. Provide --cli or update device metadata.');
115
+ }
116
+ }
117
+
118
+ const payload = {
119
+ deviceId,
120
+ message,
121
+ cliName,
122
+ ...(projectId ? { projectId } : {}),
123
+ ...(cwd ? { workingDirectory: cwd } : {}),
124
+ };
125
+
126
+ const response = await request(baseUrl, '/api/manager/sessions/start', {
127
+ method: 'POST',
128
+ headers,
129
+ body: JSON.stringify(payload),
130
+ });
131
+
132
+ console.log(`Queued start: ${response?.queueItem?.id ?? 'unknown'}`);
133
+ if (!device) {
134
+ const devices = await listDevices(baseUrl, headers);
135
+ device = devices.find((entry) => entry.id === deviceId) || null;
136
+ }
137
+ if (device) {
138
+ console.log(`Device ${device.name || device.deviceName || device.prefix}: ${device.runStatus || 'idle'}`);
139
+ if (device.lastError) {
140
+ console.log(`Device last error: ${device.lastError}`);
141
+ }
142
+ }
143
+ };
144
+
145
+ const handleFollowup = async (baseUrl, headers, values) => {
146
+ const sessionId = values.session || '';
147
+ const message = (values.message || values.prompt || '').trim();
148
+ const deviceId = (values.device || '').trim();
149
+ const cliName = (values.cli || '').trim();
150
+ const cwd = (values.cwd || '').trim();
151
+ const action = (values.action || '').trim();
152
+
153
+ if (!sessionId) throw new Error('session id required (--session)');
154
+ if (!message) throw new Error('message required (--message)');
155
+
156
+ const metadata = {};
157
+ if (action) metadata.action = action;
158
+ if (cliName) metadata.cliName = cliName;
159
+ if (cwd) metadata.workingDirectory = cwd;
160
+
161
+ const payload = {
162
+ message,
163
+ ...(deviceId ? { deviceId } : {}),
164
+ ...(Object.keys(metadata).length ? { metadata } : {}),
165
+ };
166
+
167
+ const response = await request(baseUrl, `/api/manager/sessions/${sessionId}/queue`, {
168
+ method: 'POST',
169
+ headers,
170
+ body: JSON.stringify(payload),
171
+ });
172
+
173
+ console.log(`Queued follow-up: ${response?.queueItem?.id ?? 'unknown'}`);
174
+ printQueueState(response?.queueState);
175
+ };
176
+
177
+ const handleState = async (baseUrl, headers, values) => {
178
+ const sessionId = values.session || '';
179
+ if (!sessionId) throw new Error('session id required (--session)');
180
+ const response = await request(baseUrl, `/api/manager/sessions/${sessionId}/queue`, {
181
+ method: 'GET',
182
+ headers,
183
+ });
184
+ printQueueState(response?.queueState);
185
+ };
186
+
187
+ const handleDevices = async (baseUrl, headers) => {
188
+ const devices = await listDevices(baseUrl, headers);
189
+ if (devices.length === 0) {
190
+ console.log('No devices found.');
191
+ return;
192
+ }
193
+ for (const device of devices) {
194
+ const name = device.name || device.deviceName || device.prefix;
195
+ const cliName = device.cliName ? ` cli=${device.cliName}` : '';
196
+ const status = device.runStatus || 'idle';
197
+ console.log(`${device.id} ${name} ${status}${cliName}`);
198
+ }
199
+ };
200
+
201
+ const printHelp = () => {
202
+ console.log(USAGE.trim());
203
+ };
204
+
205
+ export const runQueueCommand = async (argv = process.argv.slice(2)) => {
206
+ const { values, positionals } = parseArgs({
207
+ args: argv,
208
+ options: {
209
+ help: { type: 'boolean', short: 'h' },
210
+ org: { type: 'string' },
211
+ cookie: { type: 'string' },
212
+ 'base-url': { type: 'string' },
213
+ device: { type: 'string' },
214
+ session: { type: 'string' },
215
+ message: { type: 'string' },
216
+ prompt: { type: 'string' },
217
+ project: { type: 'string' },
218
+ cli: { type: 'string' },
219
+ cwd: { type: 'string' },
220
+ action: { type: 'string' },
221
+ },
222
+ allowPositionals: true,
223
+ });
224
+
225
+ const command = positionals[0];
226
+ if (values.help || !command || command === 'help') {
227
+ printHelp();
228
+ return;
229
+ }
230
+
231
+ try {
232
+ const baseUrl = resolveBaseUrl(values['base-url']);
233
+ const orgId = resolveOrgId(values.org);
234
+ const cookie = resolveCookie(values.cookie);
235
+ const headers = buildAuthHeaders(orgId, cookie);
236
+
237
+ if (command === 'start') {
238
+ await handleStart(baseUrl, headers, values);
239
+ return;
240
+ }
241
+ if (command === 'followup' || command === 'queue' || command === 'continue') {
242
+ await handleFollowup(baseUrl, headers, values);
243
+ return;
244
+ }
245
+ if (command === 'state') {
246
+ await handleState(baseUrl, headers, values);
247
+ return;
248
+ }
249
+ if (command === 'devices') {
250
+ await handleDevices(baseUrl, headers);
251
+ return;
252
+ }
253
+
254
+ printHelp();
255
+ process.exit(1);
256
+ } catch (error) {
257
+ console.error(`Error: ${error instanceof Error ? error.message : error}`);
258
+ process.exit(1);
259
+ }
260
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@just-every/manager",
3
- "version": "0.1.65",
3
+ "version": "0.1.67",
4
4
  "description": "Installer wrapper for Every Manager",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -11,11 +11,13 @@
11
11
  "files": [
12
12
  "bin/justevery-manager.js",
13
13
  "lib/installer.js",
14
+ "lib/queue-cli.js",
14
15
  "postinstall.js",
15
16
  "README.md"
16
17
  ],
17
18
  "scripts": {
18
- "postinstall": "node postinstall.js"
19
+ "postinstall": "node postinstall.js",
20
+ "test": "node --test test/cli-routing.test.mjs"
19
21
  },
20
22
  "engines": {
21
23
  "node": ">=18"