overlord-cli 5.8.0 → 5.11.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 +2 -2
- package/bin/_cli/index.mjs +4 -2
- package/bin/_cli/launcher.mjs +201 -16
- package/bin/_cli/setup.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,8 +91,8 @@ Top-level commands (see `ovld help`):
|
|
|
91
91
|
- `auth` - `login`, `status`, `repair` (shared Desktop/CLI credentials), or `logout`
|
|
92
92
|
- `tickets` - `create` or `list` (optional `--status`)
|
|
93
93
|
- `ticket` - `context <ticketId>` to print context for one ticket
|
|
94
|
-
- `
|
|
95
|
-
- `run`, `resume` - legacy aliases for `
|
|
94
|
+
- `launch`, `restart` - launch or resume an agent session
|
|
95
|
+
- `connect`, `run`, `resume` - legacy aliases for `launch` and `restart`
|
|
96
96
|
- `setup` - install the Overlord connector for an agent; `ovld setup [agent|all]` (interactive with no args). `ovld setup claude` also performs the one-time v3.25.0 to v4 Claude plugin migration
|
|
97
97
|
- `update` - install the latest CLI release from npm
|
|
98
98
|
- `doctor` - validate installed connectors and check for CLI updates
|
package/bin/_cli/index.mjs
CHANGED
|
@@ -35,7 +35,8 @@ Usage:
|
|
|
35
35
|
${primaryCommand} tickets <subcommand> Create or list tickets
|
|
36
36
|
${primaryCommand} ticket <subcommand> Work with a single ticket
|
|
37
37
|
${primaryCommand} protocol <subcommand> Agent workflow commands
|
|
38
|
-
${primaryCommand}
|
|
38
|
+
${primaryCommand} launch <agent> Launch an agent on a ticket
|
|
39
|
+
${primaryCommand} connect <agent> Launch an agent on a ticket (legacy alias)
|
|
39
40
|
${primaryCommand} restart <agent> Resume an agent session
|
|
40
41
|
${primaryCommand} setup [agent|all] Install Overlord agent connector (interactive if no args)
|
|
41
42
|
${primaryCommand} update Install the latest CLI version from npm
|
|
@@ -151,8 +152,9 @@ export async function runCli({ primaryCommand }) {
|
|
|
151
152
|
return;
|
|
152
153
|
}
|
|
153
154
|
|
|
154
|
-
// Launcher commands (`run
|
|
155
|
+
// Launcher commands (`connect`, `run`, and `resume` kept as legacy aliases)
|
|
155
156
|
if (
|
|
157
|
+
command === 'launch' ||
|
|
156
158
|
command === 'connect' ||
|
|
157
159
|
command === 'restart' ||
|
|
158
160
|
command === 'run' ||
|
package/bin/_cli/launcher.mjs
CHANGED
|
@@ -73,6 +73,124 @@ const agentIdentifierMap = {
|
|
|
73
73
|
|
|
74
74
|
const supportedAgents = ['claude', 'codex', 'cursor', 'gemini', 'opencode'];
|
|
75
75
|
|
|
76
|
+
function shellQuote(value) {
|
|
77
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function toTomlString(value) {
|
|
81
|
+
return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function buildExtraArgs(agent, options = {}) {
|
|
85
|
+
const args = [];
|
|
86
|
+
const extraFlags = Array.isArray(options.flags)
|
|
87
|
+
? options.flags.map(flag => String(flag).trim()).filter(Boolean)
|
|
88
|
+
: [];
|
|
89
|
+
|
|
90
|
+
if (options.model) {
|
|
91
|
+
args.push('--model', options.model);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (options.thinking) {
|
|
95
|
+
if (agent === 'claude') {
|
|
96
|
+
args.push('--effort', options.thinking);
|
|
97
|
+
} else if (agent === 'codex') {
|
|
98
|
+
args.push('-c', `model_reasoning_effort=${toTomlString(options.thinking)}`);
|
|
99
|
+
} else if (agent === 'gemini') {
|
|
100
|
+
args.push('--thinking-level', options.thinking);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return [...args, ...extraFlags];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildRemoteLaunchCommand(agent, options) {
|
|
108
|
+
const envParts = [];
|
|
109
|
+
for (const [key, value] of Object.entries({
|
|
110
|
+
OVERLORD_URL: process.env.OVERLORD_URL,
|
|
111
|
+
OVERLORD_ACCESS_TOKEN: process.env.OVERLORD_ACCESS_TOKEN,
|
|
112
|
+
OVERLORD_ORGANIZATION_ID: process.env.OVERLORD_ORGANIZATION_ID,
|
|
113
|
+
OVERLORD_LOCAL_SECRET: process.env.OVERLORD_LOCAL_SECRET,
|
|
114
|
+
TICKET_ID: process.env.TICKET_ID,
|
|
115
|
+
AGENT_IDENTIFIER: agentIdentifierMap[agent],
|
|
116
|
+
OVERLORD_MODEL_IDENTIFIER: options.model ?? '',
|
|
117
|
+
MODEL_IDENTIFIER: options.model ?? ''
|
|
118
|
+
})) {
|
|
119
|
+
if (typeof value === 'string') {
|
|
120
|
+
envParts.push(`export ${key}=${shellQuote(value)}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const nestedParts = ['ovld', 'launch', agent, '--ticket-id', shellQuote(process.env.TICKET_ID ?? '')];
|
|
125
|
+
if (options.launchMode === 'ask') {
|
|
126
|
+
nestedParts.push('--launch-mode', 'ask');
|
|
127
|
+
}
|
|
128
|
+
if (options.model) {
|
|
129
|
+
nestedParts.push('--model', shellQuote(options.model));
|
|
130
|
+
}
|
|
131
|
+
if (options.thinking) {
|
|
132
|
+
nestedParts.push('--thinking', shellQuote(options.thinking));
|
|
133
|
+
}
|
|
134
|
+
for (const flag of options.flags ?? []) {
|
|
135
|
+
nestedParts.push('--flag', shellQuote(flag));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const remoteCwd = typeof options.remoteWorkingDirectory === 'string'
|
|
139
|
+
? options.remoteWorkingDirectory.trim()
|
|
140
|
+
: '';
|
|
141
|
+
const remotePrelude = [
|
|
142
|
+
'[ -f "$HOME/.bashrc" ] && . "$HOME/.bashrc"',
|
|
143
|
+
'[ -f "$HOME/.zshrc" ] && . "$HOME/.zshrc"',
|
|
144
|
+
'[ -f "$HOME/.profile" ] && . "$HOME/.profile"',
|
|
145
|
+
'export NVM_DIR="${NVM_DIR:-$HOME/.nvm}"',
|
|
146
|
+
'[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"',
|
|
147
|
+
'export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$HOME/.cargo/bin:$PATH"',
|
|
148
|
+
...envParts,
|
|
149
|
+
remoteCwd ? `cd ${shellQuote(remoteCwd)}` : null,
|
|
150
|
+
nestedParts.join(' ')
|
|
151
|
+
].filter(Boolean);
|
|
152
|
+
|
|
153
|
+
const innerCommand = remotePrelude.join('; ');
|
|
154
|
+
const remoteCommand =
|
|
155
|
+
options.serverMultiplexer === 'tmux'
|
|
156
|
+
? buildTmuxWrappedCommand(innerCommand, options.tmuxCommand)
|
|
157
|
+
: innerCommand;
|
|
158
|
+
|
|
159
|
+
return `${options.sshCommand.trim()} ${shellQuote(remoteCommand)}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function buildTmuxWrappedCommand(innerCommand, tmuxCommand) {
|
|
163
|
+
const template = typeof tmuxCommand === 'string' && tmuxCommand.trim().includes('{script}')
|
|
164
|
+
? tmuxCommand.trim()
|
|
165
|
+
: 'tmux new-session bash {script}';
|
|
166
|
+
const scriptCommand = `bash -lc ${shellQuote(innerCommand)}`;
|
|
167
|
+
return template.replaceAll('{script}', shellQuote(scriptCommand));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function printLauncherHelp() {
|
|
171
|
+
console.log(`Usage:
|
|
172
|
+
ovld launch <agent> --ticket-id <id> [options]
|
|
173
|
+
ovld connect <agent> --ticket-id <id> [options] # alias for ovld launch
|
|
174
|
+
ovld restart <agent> --ticket-id <id> [options]
|
|
175
|
+
|
|
176
|
+
Options:
|
|
177
|
+
--ticket-id <id> Ticket to launch or resume
|
|
178
|
+
--working-directory <path> Change to a local working directory before launch
|
|
179
|
+
--launch-mode <run|ask> Ask mode adjusts the fetched prompt context
|
|
180
|
+
--model <identifier> Preferred model identifier
|
|
181
|
+
--thinking <level> Agent reasoning/effort level
|
|
182
|
+
--flag <value> Extra agent flag (repeatable)
|
|
183
|
+
--ssh-command <command> Launch remotely over SSH by running ovld on the target host
|
|
184
|
+
--remote-working-directory <path> Change to this path on the remote host before launch
|
|
185
|
+
--server-multiplexer <none|tmux> Wrap remote launches in tmux
|
|
186
|
+
--tmux-command <template> Remote tmux template; use {script} as the placeholder
|
|
187
|
+
|
|
188
|
+
Notes:
|
|
189
|
+
- ovld launch is the primary user-facing launcher.
|
|
190
|
+
- ovld connect remains available as a compatibility alias.
|
|
191
|
+
- ovld restart keeps using the native resume flow when the target agent supports it.`);
|
|
192
|
+
}
|
|
193
|
+
|
|
76
194
|
function parseLauncherArgs(args) {
|
|
77
195
|
const positionals = [];
|
|
78
196
|
const flags = {};
|
|
@@ -102,11 +220,26 @@ function parseLauncherArgs(args) {
|
|
|
102
220
|
return { positionals, flags };
|
|
103
221
|
}
|
|
104
222
|
|
|
105
|
-
|
|
223
|
+
function parseRepeatedFlags(args) {
|
|
224
|
+
const result = [];
|
|
225
|
+
for (let i = 0; i < args.length; i++) {
|
|
226
|
+
const arg = args[i];
|
|
227
|
+
if (arg === '--flag' && i + 1 < args.length) {
|
|
228
|
+
result.push(args[i + 1]);
|
|
229
|
+
i++;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
if (arg.startsWith('--flag=')) {
|
|
233
|
+
result.push(arg.slice('--flag='.length));
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async function runAgent(agent, mode = 'run', options = {}) {
|
|
106
240
|
if (!agent || !supportedAgents.includes(agent)) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
);
|
|
241
|
+
printLauncherHelp();
|
|
242
|
+
console.error(`\nAgent must be one of: ${supportedAgents.join(', ')}`);
|
|
110
243
|
process.exit(1);
|
|
111
244
|
}
|
|
112
245
|
|
|
@@ -116,7 +249,23 @@ async function runAgent(agent, mode = 'run') {
|
|
|
116
249
|
process.exit(1);
|
|
117
250
|
}
|
|
118
251
|
|
|
252
|
+
if (options.workingDirectory) {
|
|
253
|
+
process.chdir(options.workingDirectory);
|
|
254
|
+
}
|
|
255
|
+
|
|
119
256
|
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
257
|
+
if (options.sshCommand?.trim()) {
|
|
258
|
+
const remoteCommand = buildRemoteLaunchCommand(agent, options);
|
|
259
|
+
try {
|
|
260
|
+
execFileSync('sh', ['-lc', remoteCommand], { stdio: 'inherit', env: process.env });
|
|
261
|
+
return;
|
|
262
|
+
} catch (error) {
|
|
263
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
264
|
+
console.error(message);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
120
269
|
const context = await fetchContext(
|
|
121
270
|
platformUrl,
|
|
122
271
|
bearerToken,
|
|
@@ -127,6 +276,7 @@ async function runAgent(agent, mode = 'run') {
|
|
|
127
276
|
);
|
|
128
277
|
|
|
129
278
|
const childEnv = { ...process.env, AGENT_IDENTIFIER: agentIdentifierMap[agent] };
|
|
279
|
+
const extraArgs = buildExtraArgs(agent, options);
|
|
130
280
|
|
|
131
281
|
try {
|
|
132
282
|
if (agent === 'claude') {
|
|
@@ -136,12 +286,14 @@ async function runAgent(agent, mode = 'run') {
|
|
|
136
286
|
const args = claudeSessionId
|
|
137
287
|
? ['--resume', claudeSessionId, context]
|
|
138
288
|
: ['--continue', context];
|
|
289
|
+
args.unshift(...extraArgs);
|
|
139
290
|
if (pluginDir) args.unshift('--plugin-dir', pluginDir);
|
|
140
291
|
execFileSync('claude', args, { stdio: 'inherit', env: childEnv });
|
|
141
292
|
} else {
|
|
142
293
|
const args = [
|
|
143
294
|
'--append-system-prompt',
|
|
144
295
|
context,
|
|
296
|
+
...extraArgs,
|
|
145
297
|
'Begin working on this ticket. Start by calling the attach endpoint, then proceed with the objective described in your system prompt.'
|
|
146
298
|
];
|
|
147
299
|
if (pluginDir) args.unshift('--plugin-dir', pluginDir);
|
|
@@ -157,21 +309,26 @@ async function runAgent(agent, mode = 'run') {
|
|
|
157
309
|
const args = codexSessionId
|
|
158
310
|
? ['resume', codexSessionId, context]
|
|
159
311
|
: ['resume', '--last', context];
|
|
312
|
+
args.splice(1, 0, ...extraArgs);
|
|
160
313
|
execFileSync('codex', args, { stdio: 'inherit', env: childEnv });
|
|
161
314
|
} else {
|
|
162
|
-
execFileSync('codex', [context], { stdio: 'inherit', env: childEnv });
|
|
315
|
+
execFileSync('codex', [...extraArgs, context], { stdio: 'inherit', env: childEnv });
|
|
163
316
|
}
|
|
164
317
|
} else if (agent === 'cursor') {
|
|
165
|
-
execFileSync('agent', [context], { stdio: 'inherit', env: childEnv });
|
|
318
|
+
execFileSync('agent', [...extraArgs, context], { stdio: 'inherit', env: childEnv });
|
|
166
319
|
} else if (agent === 'opencode') {
|
|
167
320
|
if (mode === 'resume') {
|
|
168
321
|
const openCodeSessionId = process.env.OPENCODE_SESSION_ID?.trim();
|
|
169
322
|
const args = openCodeSessionId
|
|
170
323
|
? ['--continue', '--session', openCodeSessionId, '--prompt', context]
|
|
171
324
|
: ['--continue', '--prompt', context];
|
|
325
|
+
args.unshift(...extraArgs);
|
|
172
326
|
execFileSync('opencode', args, { stdio: 'inherit', env: childEnv });
|
|
173
327
|
} else {
|
|
174
|
-
execFileSync('opencode', ['--prompt', context], {
|
|
328
|
+
execFileSync('opencode', [...extraArgs, '--prompt', context], {
|
|
329
|
+
stdio: 'inherit',
|
|
330
|
+
env: childEnv
|
|
331
|
+
});
|
|
175
332
|
}
|
|
176
333
|
} else if (agent === 'gemini') {
|
|
177
334
|
// Write context to a temp file. Passing inline content as a positional arg
|
|
@@ -188,13 +345,13 @@ async function runAgent(agent, mode = 'run') {
|
|
|
188
345
|
const resumeTarget = geminiSessionId ?? 'latest';
|
|
189
346
|
execFileSync(
|
|
190
347
|
'gemini',
|
|
191
|
-
['--resume', resumeTarget, '--include-directories', os.tmpdir(), `@${contextFile}`],
|
|
348
|
+
[...extraArgs, '--resume', resumeTarget, '--include-directories', os.tmpdir(), `@${contextFile}`],
|
|
192
349
|
{ stdio: 'inherit', env: childEnv }
|
|
193
350
|
);
|
|
194
351
|
} else {
|
|
195
352
|
execFileSync(
|
|
196
353
|
'gemini',
|
|
197
|
-
['--include-directories', os.tmpdir(), `@${contextFile}`],
|
|
354
|
+
[...extraArgs, '--include-directories', os.tmpdir(), `@${contextFile}`],
|
|
198
355
|
{ stdio: 'inherit', env: childEnv }
|
|
199
356
|
);
|
|
200
357
|
}
|
|
@@ -203,12 +360,12 @@ async function runAgent(agent, mode = 'run') {
|
|
|
203
360
|
const isResume = mode === 'resume';
|
|
204
361
|
const noSessionHint =
|
|
205
362
|
agent === 'claude'
|
|
206
|
-
? `No prior Claude session was found. Start one with \`ovld
|
|
363
|
+
? `No prior Claude session was found. Start one with \`ovld launch claude --ticket-id <ticket-id>\` first.`
|
|
207
364
|
: agent === 'codex'
|
|
208
|
-
? `No prior Codex session was found. Start one with \`ovld
|
|
365
|
+
? `No prior Codex session was found. Start one with \`ovld launch codex --ticket-id <ticket-id>\` first.`
|
|
209
366
|
: agent === 'opencode'
|
|
210
|
-
? `No prior OpenCode session was found. Start one with \`ovld
|
|
211
|
-
|
|
367
|
+
? `No prior OpenCode session was found. Start one with \`ovld launch opencode --ticket-id <ticket-id>\` first.`
|
|
368
|
+
: '';
|
|
212
369
|
const message = error instanceof Error ? error.message : String(error);
|
|
213
370
|
|
|
214
371
|
if (isResume && noSessionHint) {
|
|
@@ -221,26 +378,54 @@ async function runAgent(agent, mode = 'run') {
|
|
|
221
378
|
}
|
|
222
379
|
|
|
223
380
|
export async function runLauncherCommand(command, args) {
|
|
224
|
-
|
|
381
|
+
if (args[0] === '--help' || args[0] === '-h' || args[0] === 'help') {
|
|
382
|
+
printLauncherHelp();
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const normalizedCommand =
|
|
387
|
+
command === 'launch' || command === 'connect'
|
|
388
|
+
? 'run'
|
|
389
|
+
: command === 'restart'
|
|
390
|
+
? 'resume'
|
|
391
|
+
: command;
|
|
225
392
|
const { positionals, flags } = parseLauncherArgs(args);
|
|
393
|
+
const repeatedFlags = parseRepeatedFlags(args);
|
|
226
394
|
const ticketId = typeof flags['ticket-id'] === 'string' ? flags['ticket-id'].trim() : '';
|
|
227
395
|
|
|
228
396
|
if (ticketId) {
|
|
229
397
|
process.env.TICKET_ID = ticketId;
|
|
230
398
|
}
|
|
231
399
|
|
|
400
|
+
const launchOptions = {
|
|
401
|
+
workingDirectory:
|
|
402
|
+
typeof flags['working-directory'] === 'string' ? flags['working-directory'].trim() : '',
|
|
403
|
+
launchMode: flags['launch-mode'] === 'ask' ? 'ask' : 'run',
|
|
404
|
+
model: typeof flags.model === 'string' ? flags.model.trim() : '',
|
|
405
|
+
thinking: typeof flags.thinking === 'string' ? flags.thinking.trim() : '',
|
|
406
|
+
flags: repeatedFlags,
|
|
407
|
+
sshCommand: typeof flags['ssh-command'] === 'string' ? flags['ssh-command'].trim() : '',
|
|
408
|
+
remoteWorkingDirectory:
|
|
409
|
+
typeof flags['remote-working-directory'] === 'string'
|
|
410
|
+
? flags['remote-working-directory'].trim()
|
|
411
|
+
: '',
|
|
412
|
+
serverMultiplexer:
|
|
413
|
+
flags['server-multiplexer'] === 'tmux' ? 'tmux' : 'none',
|
|
414
|
+
tmuxCommand: typeof flags['tmux-command'] === 'string' ? flags['tmux-command'].trim() : ''
|
|
415
|
+
};
|
|
416
|
+
|
|
232
417
|
if (normalizedCommand === 'run') {
|
|
233
418
|
// If no ticket-id flag and no TICKET_ID env var, present interactive ticket search
|
|
234
419
|
if (!ticketId && !process.env.TICKET_ID) {
|
|
235
420
|
await runAttachCommand([undefined, positionals[0]]);
|
|
236
421
|
return;
|
|
237
422
|
}
|
|
238
|
-
await runAgent(positionals[0]);
|
|
423
|
+
await runAgent(positionals[0], 'run', launchOptions);
|
|
239
424
|
return;
|
|
240
425
|
}
|
|
241
426
|
|
|
242
427
|
if (normalizedCommand === 'resume') {
|
|
243
|
-
await runAgent(positionals[0], 'resume');
|
|
428
|
+
await runAgent(positionals[0], 'resume', launchOptions);
|
|
244
429
|
return;
|
|
245
430
|
}
|
|
246
431
|
|
package/bin/_cli/setup.mjs
CHANGED
|
@@ -822,7 +822,7 @@ function installClaude() {
|
|
|
822
822
|
} else {
|
|
823
823
|
console.log(' ✓ No v3.25 Claude connector files needed migration.');
|
|
824
824
|
}
|
|
825
|
-
console.log(' ✓ `ovld
|
|
825
|
+
console.log(' ✓ `ovld launch claude` now loads this plugin with `claude --plugin-dir`.');
|
|
826
826
|
|
|
827
827
|
const manifest = readManifest();
|
|
828
828
|
manifest.claude = {
|