overlord-cli 0.2605201134.0 → 0.2605211051.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 +5 -0
- package/bin/_cli/index.mjs +7 -0
- package/bin/_cli/protocol.mjs +229 -0
- package/bin/_cli/runner.mjs +238 -0
- package/package.json +2 -1
- package/plugins/antigravity/scripts/overlord-mcp.mjs +4 -13
- package/plugins/claude/README.md +20 -4
- package/plugins/claude/commands/add-objectives.md +1 -0
- package/plugins/claude/commands/connect.md +3 -1
- package/plugins/claude/commands/create.md +3 -1
- package/plugins/claude/commands/load.md +4 -1
- package/plugins/claude/commands/prompt.md +19 -0
- package/plugins/claude/commands/record-work.md +2 -1
- package/plugins/claude/commands/spawn.md +7 -4
- package/plugins/claude/hooks/hooks.json +1 -0
- package/plugins/claude/skills/overlord-ticket/SKILL.md +30 -213
- package/plugins/claude/skills/overlord-ticket/reference/cli.md +127 -0
- package/plugins/claude/skills/overlord-ticket/reference/context.md +22 -0
- package/plugins/claude/skills/overlord-ticket/reference/devices.md +20 -0
- package/plugins/claude/skills/overlord-ticket/reference/mcp.md +10 -0
- package/plugins/claude/skills/overlord-ticket/reference/shell-escaping.md +16 -0
- package/plugins/cursor/.cursor-plugin/plugin.json +13 -1
- package/plugins/cursor/commands/add-objectives.md +1 -0
- package/plugins/cursor/commands/connect.md +9 -2
- package/plugins/cursor/commands/create.md +2 -1
- package/plugins/cursor/commands/load.md +10 -2
- package/plugins/cursor/commands/prompt.md +12 -0
- package/plugins/cursor/commands/record-work.md +12 -4
- package/plugins/cursor/commands/spawn.md +12 -2
- package/plugins/cursor/rules/overlord-local.mdc +3 -1
- package/plugins/cursor/scripts/overlord-mcp.mjs +1 -1
- package/plugins/cursor/skills/overlord-ticket/SKILL.md +33 -185
- package/plugins/cursor/skills/overlord-ticket/reference/cli.md +127 -0
- package/plugins/cursor/skills/overlord-ticket/reference/context.md +22 -0
- package/plugins/cursor/skills/overlord-ticket/reference/devices.md +20 -0
- package/plugins/cursor/skills/overlord-ticket/reference/mcp.md +10 -0
- package/plugins/cursor/skills/overlord-ticket/reference/shell-escaping.md +16 -0
- package/plugins/overlord/.codex-plugin/hooks.json +1 -0
- package/plugins/overlord/.codex-plugin/plugin.json +3 -3
- package/plugins/overlord/README.md +25 -4
- package/plugins/overlord/scripts/overlord-mcp.mjs +2 -3
- package/plugins/overlord/skills/overlord-ticket/SKILL.md +50 -187
- package/plugins/overlord/skills/overlord-ticket/reference/cli.md +127 -0
- package/plugins/overlord/skills/overlord-ticket/reference/context.md +22 -0
- package/plugins/overlord/skills/overlord-ticket/reference/devices.md +20 -0
- package/plugins/overlord/skills/overlord-ticket/reference/mcp.md +10 -0
- package/plugins/overlord/skills/overlord-ticket/reference/shell-escaping.md +16 -0
package/README.md
CHANGED
|
@@ -68,6 +68,7 @@ ovld protocol discover-project
|
|
|
68
68
|
ovld protocol attach --ticket-id <ticket_id>
|
|
69
69
|
ovld protocol search-tickets --query "auth refactor" --status next-up,execute
|
|
70
70
|
ovld protocol update --session-key <session-key> --ticket-id <ticket_id> --summary "Working on it" --phase execute
|
|
71
|
+
ovld runner start
|
|
71
72
|
ovld setup
|
|
72
73
|
ovld setup codex
|
|
73
74
|
ovld setup claude
|
|
@@ -100,6 +101,7 @@ Top-level commands (see `ovld help`):
|
|
|
100
101
|
- `tickets` - `create` or `list` (optional `--status`)
|
|
101
102
|
- `ticket` - `context <ticketId>` to print context for one ticket
|
|
102
103
|
- `launch`, `restart` - launch or resume an agent session
|
|
104
|
+
- `runner` - claim queued execution requests and launch assigned agents with `ovld launch`
|
|
103
105
|
- `connect`, `run`, `resume` - legacy aliases for `launch` and `restart`
|
|
104
106
|
- `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
|
|
105
107
|
- `update` - install the latest CLI release from npm
|
|
@@ -137,9 +139,12 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
|
|
|
137
139
|
- `attachment-upload-file` - prepare, upload, and finalize a local objective attachment in one command
|
|
138
140
|
- `get-device` / `update-device` - register the caller machine (per organization + user + fingerprint) and rename its label
|
|
139
141
|
- `list-project-resources` / `add-project-resource` / `update-project-resource` - manage filesystem directories tied to a project device
|
|
142
|
+
- `request-execution` / `claim-execution` / `complete-execution-launch` / `fail-execution-launch` - durable runner queue operations used by auto-advance and manual Run
|
|
140
143
|
|
|
141
144
|
Devices are keyed by **(organization, user, fingerprint)** so the same physical workstation can appear once per org session.
|
|
142
145
|
|
|
146
|
+
`ovld runner start` keeps a foreground runner alive for CLI-only or remote hosts. It claims compatible execution requests for the current device and launches the assigned agent/model with `ovld launch`; `ovld runner once` claims at most one request and exits.
|
|
147
|
+
|
|
143
148
|
Use `create` for future work you want to track, `prompt` for work that should start immediately, and `record-work` for work that was already completed in chat and now needs a review ticket plus feed post. Use multiple tickets when prompts represent different features or goals; use `add-objectives` or `--objectives-json '[{"objective":"Step one"},{"objective":"Step two"}]'` when prompts are sequential steps toward the same feature or goal.
|
|
144
149
|
|
|
145
150
|
`ovld protocol deliver` accepts either discrete flags like `--summary` / `--artifacts-json`, an inline full payload with `--payload-json '{"summary":"...","artifacts":[...],"changeRationales":[...]}'`, or a file/stdin payload with `--payload-file <path|->`.
|
package/bin/_cli/index.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { runAuthCommand } from './auth.mjs';
|
|
|
5
5
|
import { checkForCliUpdate, printCliUpdateNotice, runCliUpdateCommand } from './cli-update.mjs';
|
|
6
6
|
import { runLauncherCommand } from './launcher.mjs';
|
|
7
7
|
import { runProtocolCommand } from './protocol.mjs';
|
|
8
|
+
import { runRunnerCommand } from './runner.mjs';
|
|
8
9
|
import { runDoctorCommand, runSetupCommand } from './setup.mjs';
|
|
9
10
|
import { runTicketCommand } from './ticket.mjs';
|
|
10
11
|
import { runTicketsCommand } from './tickets.mjs';
|
|
@@ -35,6 +36,7 @@ Usage:
|
|
|
35
36
|
${primaryCommand} tickets <subcommand> Create or list tickets
|
|
36
37
|
${primaryCommand} ticket <subcommand> Work with a single ticket
|
|
37
38
|
${primaryCommand} protocol <subcommand> Agent workflow commands
|
|
39
|
+
${primaryCommand} runner <subcommand> Claim and launch queued execution requests
|
|
38
40
|
${primaryCommand} launch <agent> Launch an agent on a ticket
|
|
39
41
|
${primaryCommand} connect <agent> Launch an agent on a ticket (legacy alias)
|
|
40
42
|
${primaryCommand} restart <agent> Resume an agent session
|
|
@@ -127,6 +129,11 @@ export async function runCli({ primaryCommand }) {
|
|
|
127
129
|
return;
|
|
128
130
|
}
|
|
129
131
|
|
|
132
|
+
if (command === 'runner') {
|
|
133
|
+
await runRunnerCommand(rest[0], rest.slice(1), primaryCommand);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
130
137
|
if (command === 'setup') {
|
|
131
138
|
await runSetupCommand(rest);
|
|
132
139
|
return;
|
package/bin/_cli/protocol.mjs
CHANGED
|
@@ -230,6 +230,23 @@ function resolveTimeout(flags) {
|
|
|
230
230
|
return DEFAULT_TIMEOUT_MS;
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
function parseRepeatedCliFlags(args, name) {
|
|
234
|
+
const result = [];
|
|
235
|
+
const prefix = `--${name}=`;
|
|
236
|
+
for (let i = 0; i < args.length; i++) {
|
|
237
|
+
const arg = args[i];
|
|
238
|
+
if (arg === `--${name}` && i + 1 < args.length) {
|
|
239
|
+
result.push(args[i + 1]);
|
|
240
|
+
i++;
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (arg.startsWith(prefix)) {
|
|
244
|
+
result.push(arg.slice(prefix.length));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
233
250
|
function requireFlag(flags, name, envAlias) {
|
|
234
251
|
const value = flags[name] ?? (envAlias ? process.env[envAlias] : undefined);
|
|
235
252
|
if (!value) {
|
|
@@ -1821,6 +1838,155 @@ async function protocolUpdateProjectResource(args) {
|
|
|
1821
1838
|
console.log(JSON.stringify(data, null, 2));
|
|
1822
1839
|
}
|
|
1823
1840
|
|
|
1841
|
+
// ---------------------------------------------------------------------------
|
|
1842
|
+
// execution requests (durable runner queue)
|
|
1843
|
+
// ---------------------------------------------------------------------------
|
|
1844
|
+
|
|
1845
|
+
async function protocolRequestExecution(args) {
|
|
1846
|
+
const flags = parseFlags(args);
|
|
1847
|
+
const ticketId = requireFlag(flags, 'ticket-id', 'TICKET_ID');
|
|
1848
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1849
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
1850
|
+
const timeoutMs = resolveTimeout(flags);
|
|
1851
|
+
|
|
1852
|
+
const body = {
|
|
1853
|
+
ticketId,
|
|
1854
|
+
...(typeof flags['objective-id'] === 'string' ? { objectiveId: flags['objective-id'] } : {}),
|
|
1855
|
+
requestedFrom: String(flags['requested-from'] ?? 'api'),
|
|
1856
|
+
...(typeof flags['idempotency-key'] === 'string'
|
|
1857
|
+
? { idempotencyKey: flags['idempotency-key'] }
|
|
1858
|
+
: {}),
|
|
1859
|
+
...(typeof flags.agent === 'string' ? { agentIdentifier: flags.agent } : {}),
|
|
1860
|
+
...(typeof flags.model === 'string' ? { modelIdentifier: flags.model } : {}),
|
|
1861
|
+
...(typeof flags.thinking === 'string' ? { thinkingLevel: flags.thinking } : {}),
|
|
1862
|
+
launchMode: flags['launch-mode'] === 'ask' ? 'ask' : 'run',
|
|
1863
|
+
flags: parseRepeatedCliFlags(args, 'flag'),
|
|
1864
|
+
...(typeof flags['working-directory'] === 'string'
|
|
1865
|
+
? { workingDirectory: flags['working-directory'] }
|
|
1866
|
+
: {}),
|
|
1867
|
+
...(typeof flags['ssh-command'] === 'string' ? { sshCommand: flags['ssh-command'] } : {}),
|
|
1868
|
+
...(typeof flags['remote-working-directory'] === 'string'
|
|
1869
|
+
? { remoteWorkingDirectory: flags['remote-working-directory'] }
|
|
1870
|
+
: {}),
|
|
1871
|
+
...(flags['server-multiplexer'] === 'tmux' ? { serverMultiplexer: 'tmux' } : {}),
|
|
1872
|
+
...(typeof flags['tmux-command'] === 'string' ? { tmuxCommand: flags['tmux-command'] } : {}),
|
|
1873
|
+
...(typeof flags['target-kind'] === 'string' ? { targetKind: flags['target-kind'] } : {}),
|
|
1874
|
+
...(typeof flags['target-device-id'] === 'string'
|
|
1875
|
+
? { targetDeviceId: flags['target-device-id'] }
|
|
1876
|
+
: {}),
|
|
1877
|
+
...(typeof flags['target-resource-id'] === 'string'
|
|
1878
|
+
? { targetResourceId: flags['target-resource-id'] }
|
|
1879
|
+
: {})
|
|
1880
|
+
};
|
|
1881
|
+
|
|
1882
|
+
const data = await apiPost(
|
|
1883
|
+
platformUrl,
|
|
1884
|
+
bearerToken,
|
|
1885
|
+
localSecret,
|
|
1886
|
+
organizationId,
|
|
1887
|
+
'/api/protocol/request-execution',
|
|
1888
|
+
body,
|
|
1889
|
+
timeoutMs
|
|
1890
|
+
);
|
|
1891
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1892
|
+
|
|
1893
|
+
if (data.request?.id) {
|
|
1894
|
+
process.stderr.write(`\nEXECUTION_REQUEST_ID=${data.request.id}\n`);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
async function protocolClaimExecution(args) {
|
|
1899
|
+
const flags = parseFlags(args);
|
|
1900
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1901
|
+
await resolveProtocolAuthForFlags(flags);
|
|
1902
|
+
const timeoutMs = resolveTimeout(flags);
|
|
1903
|
+
const deviceFingerprint = String(
|
|
1904
|
+
flags['device-fingerprint'] ?? process.env.OVERLORD_DEVICE_FINGERPRINT ?? ''
|
|
1905
|
+
);
|
|
1906
|
+
if (!deviceFingerprint) {
|
|
1907
|
+
console.error('Error: --device-fingerprint is required (or set OVERLORD_DEVICE_FINGERPRINT)');
|
|
1908
|
+
process.exit(1);
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
const data = await apiPost(
|
|
1912
|
+
platformUrl,
|
|
1913
|
+
bearerToken,
|
|
1914
|
+
localSecret,
|
|
1915
|
+
organizationId,
|
|
1916
|
+
'/api/protocol/claim-execution',
|
|
1917
|
+
{
|
|
1918
|
+
deviceFingerprint,
|
|
1919
|
+
...(flags['device-hostname'] ? { deviceHostname: String(flags['device-hostname']) } : {}),
|
|
1920
|
+
...(flags['device-platform'] ? { devicePlatform: String(flags['device-platform']) } : {}),
|
|
1921
|
+
...(typeof flags['lease-seconds'] === 'string'
|
|
1922
|
+
? { leaseSeconds: Number.parseInt(flags['lease-seconds'], 10) }
|
|
1923
|
+
: {}),
|
|
1924
|
+
...(typeof flags['project-id'] === 'string' ? { projectId: flags['project-id'] } : {})
|
|
1925
|
+
},
|
|
1926
|
+
timeoutMs
|
|
1927
|
+
);
|
|
1928
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
async function protocolCompleteExecutionLaunch(args) {
|
|
1932
|
+
const flags = parseFlags(args);
|
|
1933
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1934
|
+
await resolveProtocolAuthForFlags(flags);
|
|
1935
|
+
const timeoutMs = resolveTimeout(flags);
|
|
1936
|
+
const requestId = requireFlag(flags, 'request-id', 'EXECUTION_REQUEST_ID');
|
|
1937
|
+
const deviceFingerprint = String(
|
|
1938
|
+
flags['device-fingerprint'] ?? process.env.OVERLORD_DEVICE_FINGERPRINT ?? ''
|
|
1939
|
+
);
|
|
1940
|
+
if (!deviceFingerprint) {
|
|
1941
|
+
console.error('Error: --device-fingerprint is required (or set OVERLORD_DEVICE_FINGERPRINT)');
|
|
1942
|
+
process.exit(1);
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
const data = await apiPost(
|
|
1946
|
+
platformUrl,
|
|
1947
|
+
bearerToken,
|
|
1948
|
+
localSecret,
|
|
1949
|
+
organizationId,
|
|
1950
|
+
'/api/protocol/complete-execution-launch',
|
|
1951
|
+
{
|
|
1952
|
+
requestId,
|
|
1953
|
+
deviceFingerprint,
|
|
1954
|
+
...(typeof flags['launched-session-id'] === 'string'
|
|
1955
|
+
? { launchedSessionId: flags['launched-session-id'] }
|
|
1956
|
+
: {})
|
|
1957
|
+
},
|
|
1958
|
+
timeoutMs
|
|
1959
|
+
);
|
|
1960
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1961
|
+
}
|
|
1962
|
+
|
|
1963
|
+
async function protocolFailExecutionLaunch(args) {
|
|
1964
|
+
const flags = parseFlags(args);
|
|
1965
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1966
|
+
await resolveProtocolAuthForFlags(flags);
|
|
1967
|
+
const timeoutMs = resolveTimeout(flags);
|
|
1968
|
+
const requestId = requireFlag(flags, 'request-id', 'EXECUTION_REQUEST_ID');
|
|
1969
|
+
const deviceFingerprint = String(
|
|
1970
|
+
flags['device-fingerprint'] ?? process.env.OVERLORD_DEVICE_FINGERPRINT ?? ''
|
|
1971
|
+
);
|
|
1972
|
+
if (!deviceFingerprint) {
|
|
1973
|
+
console.error('Error: --device-fingerprint is required (or set OVERLORD_DEVICE_FINGERPRINT)');
|
|
1974
|
+
process.exit(1);
|
|
1975
|
+
}
|
|
1976
|
+
const error = requireFlag(flags, 'error');
|
|
1977
|
+
|
|
1978
|
+
const data = await apiPost(
|
|
1979
|
+
platformUrl,
|
|
1980
|
+
bearerToken,
|
|
1981
|
+
localSecret,
|
|
1982
|
+
organizationId,
|
|
1983
|
+
'/api/protocol/fail-execution-launch',
|
|
1984
|
+
{ requestId, deviceFingerprint, error },
|
|
1985
|
+
timeoutMs
|
|
1986
|
+
);
|
|
1987
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1824
1990
|
// ---------------------------------------------------------------------------
|
|
1825
1991
|
// connect (lightweight session, no context returned)
|
|
1826
1992
|
// ---------------------------------------------------------------------------
|
|
@@ -2349,6 +2515,10 @@ Subcommands:
|
|
|
2349
2515
|
record-change-rationales Persist structured change rationales without a progress update
|
|
2350
2516
|
ask Post a blocking question and move the ticket to review
|
|
2351
2517
|
request-approval-gate Flip auto_advance=false on the next queued future objective
|
|
2518
|
+
request-execution Queue an objective for local/remote runner execution
|
|
2519
|
+
claim-execution Claim one queued execution request for this device
|
|
2520
|
+
complete-execution-launch Mark a claimed execution request launched
|
|
2521
|
+
fail-execution-launch Mark a claimed execution request failed
|
|
2352
2522
|
permission-request Notify Overlord that the agent is requesting tool permission
|
|
2353
2523
|
hook-event Record a lifecycle hook event without a session key
|
|
2354
2524
|
read-context Read shared persistent context for this ticket
|
|
@@ -2779,6 +2949,47 @@ update-project-resource:
|
|
|
2779
2949
|
--label <text> New label (pass 'null' to clear)
|
|
2780
2950
|
--is-primary <true|false>
|
|
2781
2951
|
|
|
2952
|
+
request-execution:
|
|
2953
|
+
Required:
|
|
2954
|
+
--ticket-id <ticket_id>
|
|
2955
|
+
Optional:
|
|
2956
|
+
--objective-id <uuid>
|
|
2957
|
+
--requested-from <source> manual_run | auto_advance | api | ssh
|
|
2958
|
+
--idempotency-key <key>
|
|
2959
|
+
--agent <agent>
|
|
2960
|
+
--model <model>
|
|
2961
|
+
--thinking <level>
|
|
2962
|
+
--flag <value> Extra agent flag (repeatable)
|
|
2963
|
+
--working-directory <path>
|
|
2964
|
+
--ssh-command <command>
|
|
2965
|
+
--remote-working-directory <path>
|
|
2966
|
+
--server-multiplexer <none|tmux>
|
|
2967
|
+
--target-kind <any|local|ssh>
|
|
2968
|
+
--target-device-id <uuid>
|
|
2969
|
+
--target-resource-id <uuid>
|
|
2970
|
+
|
|
2971
|
+
claim-execution:
|
|
2972
|
+
Required:
|
|
2973
|
+
--device-fingerprint <fp> (or OVERLORD_DEVICE_FINGERPRINT env var)
|
|
2974
|
+
Optional:
|
|
2975
|
+
--device-hostname <name>
|
|
2976
|
+
--device-platform <platform>
|
|
2977
|
+
--lease-seconds <n>
|
|
2978
|
+
--project-id <uuid>
|
|
2979
|
+
|
|
2980
|
+
complete-execution-launch:
|
|
2981
|
+
Required:
|
|
2982
|
+
--request-id <uuid> (or EXECUTION_REQUEST_ID env var)
|
|
2983
|
+
--device-fingerprint <fp> (or OVERLORD_DEVICE_FINGERPRINT env var)
|
|
2984
|
+
Optional:
|
|
2985
|
+
--launched-session-id <uuid>
|
|
2986
|
+
|
|
2987
|
+
fail-execution-launch:
|
|
2988
|
+
Required:
|
|
2989
|
+
--request-id <uuid> (or EXECUTION_REQUEST_ID env var)
|
|
2990
|
+
--device-fingerprint <fp> (or OVERLORD_DEVICE_FINGERPRINT env var)
|
|
2991
|
+
--error <message>
|
|
2992
|
+
|
|
2782
2993
|
Examples:
|
|
2783
2994
|
ovld protocol auth-status
|
|
2784
2995
|
ovld protocol discover-project
|
|
@@ -2808,6 +3019,8 @@ Examples:
|
|
|
2808
3019
|
ovld protocol attachment-prepare-upload --session-key <key> --ticket-id <ticket_id> --objective-id <objective-id> --file-name spec.pdf --content-type application/pdf
|
|
2809
3020
|
ovld protocol attachment-upload-file --session-key <key> --ticket-id <ticket_id> --objective-id <objective-id> --file ./spec.pdf
|
|
2810
3021
|
ovld protocol attachment-download-url --session-key <key> --ticket-id <ticket_id> --attachment-id <attachment-id>
|
|
3022
|
+
ovld protocol request-execution --ticket-id 1:899 --agent codex --requested-from manual_run
|
|
3023
|
+
ovld protocol claim-execution --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT
|
|
2811
3024
|
ovld protocol deliver --session-key <key> --ticket-id <ticket_id> --summary "Done"
|
|
2812
3025
|
ovld protocol deliver --session-key <key> --ticket-id <ticket_id> --summary "Done" --artifacts-file ./artifacts.json
|
|
2813
3026
|
ovld protocol deliver --session-key <key> --ticket-id <ticket_id> --payload-json '{"summary":"Done","artifacts":[{"type":"note","label":"Delivery","content":"..."}]}'
|
|
@@ -2915,6 +3128,22 @@ EOF
|
|
|
2915
3128
|
await protocolRequestApprovalGate(args);
|
|
2916
3129
|
return;
|
|
2917
3130
|
}
|
|
3131
|
+
if (subcommand === 'request-execution') {
|
|
3132
|
+
await protocolRequestExecution(args);
|
|
3133
|
+
return;
|
|
3134
|
+
}
|
|
3135
|
+
if (subcommand === 'claim-execution') {
|
|
3136
|
+
await protocolClaimExecution(args);
|
|
3137
|
+
return;
|
|
3138
|
+
}
|
|
3139
|
+
if (subcommand === 'complete-execution-launch') {
|
|
3140
|
+
await protocolCompleteExecutionLaunch(args);
|
|
3141
|
+
return;
|
|
3142
|
+
}
|
|
3143
|
+
if (subcommand === 'fail-execution-launch') {
|
|
3144
|
+
await protocolFailExecutionLaunch(args);
|
|
3145
|
+
return;
|
|
3146
|
+
}
|
|
2918
3147
|
if (subcommand === 'permission-request') {
|
|
2919
3148
|
await protocolPermissionRequest(args);
|
|
2920
3149
|
return;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
|
|
9
|
+
const OVLD_ENTRY = process.argv[1];
|
|
10
|
+
const DEVICE_FILE = path.join(os.homedir(), '.ovld', 'device.json');
|
|
11
|
+
|
|
12
|
+
function printRunnerHelp(primaryCommand = 'ovld') {
|
|
13
|
+
console.log(`Usage:
|
|
14
|
+
${primaryCommand} runner once [options]
|
|
15
|
+
${primaryCommand} runner start [options]
|
|
16
|
+
${primaryCommand} runner status [options]
|
|
17
|
+
|
|
18
|
+
Options:
|
|
19
|
+
--device-fingerprint <fp> Override runner device identity
|
|
20
|
+
--poll-interval-ms <ms> Poll interval for start mode (default 3000)
|
|
21
|
+
--project-id <uuid> Only claim requests for one project
|
|
22
|
+
|
|
23
|
+
The runner claims durable execution requests and launches them with \`${primaryCommand} launch\`.
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function parseFlags(args) {
|
|
28
|
+
const flags = {};
|
|
29
|
+
for (let i = 0; i < args.length; i++) {
|
|
30
|
+
const arg = args[i];
|
|
31
|
+
if (!arg.startsWith('--')) continue;
|
|
32
|
+
const eqIdx = arg.indexOf('=');
|
|
33
|
+
if (eqIdx !== -1) {
|
|
34
|
+
flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
const key = arg.slice(2);
|
|
38
|
+
if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
|
|
39
|
+
flags[key] = args[i + 1];
|
|
40
|
+
i++;
|
|
41
|
+
} else {
|
|
42
|
+
flags[key] = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return flags;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readOrCreateDeviceFingerprint(flags) {
|
|
49
|
+
const explicit =
|
|
50
|
+
typeof flags['device-fingerprint'] === 'string'
|
|
51
|
+
? flags['device-fingerprint'].trim()
|
|
52
|
+
: process.env.OVERLORD_DEVICE_FINGERPRINT?.trim();
|
|
53
|
+
if (explicit) return explicit;
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const raw = fs.readFileSync(DEVICE_FILE, 'utf8');
|
|
57
|
+
const parsed = JSON.parse(raw);
|
|
58
|
+
if (typeof parsed.deviceFingerprint === 'string' && parsed.deviceFingerprint.trim()) {
|
|
59
|
+
return parsed.deviceFingerprint.trim();
|
|
60
|
+
}
|
|
61
|
+
} catch {
|
|
62
|
+
// Create below.
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const deviceFingerprint = crypto.randomUUID();
|
|
66
|
+
fs.mkdirSync(path.dirname(DEVICE_FILE), { recursive: true });
|
|
67
|
+
fs.writeFileSync(DEVICE_FILE, `${JSON.stringify({ deviceFingerprint }, null, 2)}\n`, 'utf8');
|
|
68
|
+
return deviceFingerprint;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function runOvld(args, env = {}) {
|
|
72
|
+
return execFileSync(process.execPath, [OVLD_ENTRY, ...args], {
|
|
73
|
+
encoding: 'utf8',
|
|
74
|
+
env: { ...process.env, ...env }
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function protocolJson(subcommand, args, env) {
|
|
79
|
+
const stdout = runOvld(['protocol', subcommand, ...args], env);
|
|
80
|
+
return stdout.trim() ? JSON.parse(stdout) : {};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function claimExecution(flags, deviceFingerprint) {
|
|
84
|
+
const args = [
|
|
85
|
+
'--device-fingerprint',
|
|
86
|
+
deviceFingerprint,
|
|
87
|
+
'--device-hostname',
|
|
88
|
+
os.hostname(),
|
|
89
|
+
'--device-platform',
|
|
90
|
+
process.platform
|
|
91
|
+
];
|
|
92
|
+
if (typeof flags['project-id'] === 'string') {
|
|
93
|
+
args.push('--project-id', flags['project-id']);
|
|
94
|
+
}
|
|
95
|
+
return protocolJson('claim-execution', args, { OVERLORD_DEVICE_FINGERPRINT: deviceFingerprint });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function completeLaunch(requestId, deviceFingerprint) {
|
|
99
|
+
protocolJson(
|
|
100
|
+
'complete-execution-launch',
|
|
101
|
+
['--request-id', requestId, '--device-fingerprint', deviceFingerprint],
|
|
102
|
+
{ OVERLORD_DEVICE_FINGERPRINT: deviceFingerprint }
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function failLaunch(requestId, deviceFingerprint, error) {
|
|
107
|
+
protocolJson(
|
|
108
|
+
'fail-execution-launch',
|
|
109
|
+
['--request-id', requestId, '--device-fingerprint', deviceFingerprint, '--error', error],
|
|
110
|
+
{ OVERLORD_DEVICE_FINGERPRINT: deviceFingerprint }
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function buildLaunchArgs(launch) {
|
|
115
|
+
const args = ['launch', launch.agent, '--ticket-id', launch.ticketId];
|
|
116
|
+
if (launch.workingDirectory) args.push('--working-directory', launch.workingDirectory);
|
|
117
|
+
if (launch.launchMode === 'ask') args.push('--launch-mode', 'ask');
|
|
118
|
+
if (launch.model) args.push('--model', launch.model);
|
|
119
|
+
if (launch.thinking) args.push('--thinking', launch.thinking);
|
|
120
|
+
for (const flag of launch.flags ?? []) args.push('--flag', flag);
|
|
121
|
+
if (launch.sshCommand) args.push('--ssh-command', launch.sshCommand);
|
|
122
|
+
if (launch.remoteWorkingDirectory) {
|
|
123
|
+
args.push('--remote-working-directory', launch.remoteWorkingDirectory);
|
|
124
|
+
}
|
|
125
|
+
if (launch.serverMultiplexer === 'tmux') args.push('--server-multiplexer', 'tmux');
|
|
126
|
+
if (launch.tmuxCommand) args.push('--tmux-command', launch.tmuxCommand);
|
|
127
|
+
return args;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function launchClaimedRequest(claim, deviceFingerprint) {
|
|
131
|
+
const requestId = claim.request?.id;
|
|
132
|
+
if (!requestId || !claim.launch?.agent || !claim.launch?.ticketId) return false;
|
|
133
|
+
|
|
134
|
+
const args = buildLaunchArgs(claim.launch);
|
|
135
|
+
process.stderr.write(
|
|
136
|
+
`[runner] launching ${claim.launch.agent} for ${claim.launch.ticketId} (${requestId})\n`
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
await new Promise((resolve, reject) => {
|
|
140
|
+
const child = spawn(process.execPath, [OVLD_ENTRY, ...args], {
|
|
141
|
+
stdio: 'inherit',
|
|
142
|
+
env: { ...process.env, OVERLORD_DEVICE_FINGERPRINT: deviceFingerprint }
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
child.once('spawn', () => {
|
|
146
|
+
try {
|
|
147
|
+
completeLaunch(requestId, deviceFingerprint);
|
|
148
|
+
} catch (error) {
|
|
149
|
+
process.stderr.write(`[runner] failed to mark request launched: ${error}\n`);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
child.once('error', error => {
|
|
154
|
+
try {
|
|
155
|
+
failLaunch(requestId, deviceFingerprint, error.message);
|
|
156
|
+
} catch {
|
|
157
|
+
// Preserve the original launch error.
|
|
158
|
+
}
|
|
159
|
+
reject(error);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
child.once('close', code => {
|
|
163
|
+
if (code && code !== 0) {
|
|
164
|
+
process.stderr.write(`[runner] launch process exited with code ${code}\n`);
|
|
165
|
+
}
|
|
166
|
+
resolve();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function runOnce(flags, deviceFingerprint) {
|
|
174
|
+
const claim = claimExecution(flags, deviceFingerprint);
|
|
175
|
+
if (!claim.request) {
|
|
176
|
+
process.stderr.write('[runner] no queued execution requests\n');
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
await launchClaimedRequest(claim, deviceFingerprint);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function sleep(ms) {
|
|
184
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function runRunnerCommand(subcommand, args, primaryCommand = 'ovld') {
|
|
188
|
+
if (!subcommand || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
|
|
189
|
+
printRunnerHelp(primaryCommand);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const flags = parseFlags(args);
|
|
194
|
+
const deviceFingerprint = readOrCreateDeviceFingerprint(flags);
|
|
195
|
+
|
|
196
|
+
if (subcommand === 'status') {
|
|
197
|
+
const data = protocolJson(
|
|
198
|
+
'get-device',
|
|
199
|
+
[
|
|
200
|
+
'--device-fingerprint',
|
|
201
|
+
deviceFingerprint,
|
|
202
|
+
'--device-hostname',
|
|
203
|
+
os.hostname(),
|
|
204
|
+
'--device-platform',
|
|
205
|
+
process.platform
|
|
206
|
+
],
|
|
207
|
+
{ OVERLORD_DEVICE_FINGERPRINT: deviceFingerprint }
|
|
208
|
+
);
|
|
209
|
+
console.log(JSON.stringify({ ok: true, device: data.device }, null, 2));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (subcommand === 'once') {
|
|
214
|
+
await runOnce(flags, deviceFingerprint);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (subcommand === 'start') {
|
|
219
|
+
const pollIntervalMs =
|
|
220
|
+
typeof flags['poll-interval-ms'] === 'string'
|
|
221
|
+
? Math.max(1000, Number.parseInt(flags['poll-interval-ms'], 10) || 3000)
|
|
222
|
+
: 3000;
|
|
223
|
+
process.stderr.write(`[runner] started with device ${deviceFingerprint}\n`);
|
|
224
|
+
for (;;) {
|
|
225
|
+
try {
|
|
226
|
+
const launched = await runOnce(flags, deviceFingerprint);
|
|
227
|
+
if (!launched) await sleep(pollIntervalMs);
|
|
228
|
+
} catch (error) {
|
|
229
|
+
process.stderr.write(`[runner] ${error instanceof Error ? error.message : error}\n`);
|
|
230
|
+
await sleep(pollIntervalMs);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
console.error(`Unknown runner command: ${subcommand}\n`);
|
|
236
|
+
printRunnerHelp(primaryCommand);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overlord-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2605211051.0",
|
|
4
4
|
"description": "Overlord CLI — launch AI agents on tickets from anywhere",
|
|
5
5
|
"homepage": "https://www.ovld.ai/docs/",
|
|
6
6
|
"type": "module",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"plugins/"
|
|
17
17
|
],
|
|
18
18
|
"scripts": {
|
|
19
|
+
"prepack": "node ../../scripts/render-agent-plugins.mjs --check",
|
|
19
20
|
"postinstall": "node bin/_cli/postinstall.mjs || true"
|
|
20
21
|
},
|
|
21
22
|
"engines": {
|
|
@@ -927,20 +927,11 @@ function parseMessages(chunk) {
|
|
|
927
927
|
if (headerEnd === -1) break;
|
|
928
928
|
|
|
929
929
|
const headerText = buffer.subarray(0, headerEnd).toString('utf8');
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
const separatorIndex = line.indexOf(':');
|
|
933
|
-
return [
|
|
934
|
-
line.slice(0, separatorIndex).trim().toLowerCase(),
|
|
935
|
-
line.slice(separatorIndex + 1).trim()
|
|
936
|
-
];
|
|
937
|
-
})
|
|
938
|
-
);
|
|
939
|
-
|
|
940
|
-
const contentLength = Number(headers['content-length']);
|
|
941
|
-
if (!Number.isFinite(contentLength)) {
|
|
930
|
+
const lengthMatch = headerText.match(/Content-Length:\s*(\d+)/i);
|
|
931
|
+
if (!lengthMatch) {
|
|
942
932
|
throw new Error('Missing Content-Length header');
|
|
943
933
|
}
|
|
934
|
+
const contentLength = Number(lengthMatch[1]);
|
|
944
935
|
|
|
945
936
|
const totalLength = headerEnd + 4 + contentLength;
|
|
946
937
|
if (buffer.length < totalLength) break;
|
|
@@ -995,7 +986,7 @@ async function runProtocol(tool, args) {
|
|
|
995
986
|
cwd: process.cwd(),
|
|
996
987
|
env: {
|
|
997
988
|
...process.env,
|
|
998
|
-
AGENT_IDENTIFIER: process.env.AGENT_IDENTIFIER ?? 'antigravity
|
|
989
|
+
AGENT_IDENTIFIER: process.env.AGENT_IDENTIFIER ?? 'antigravity'
|
|
999
990
|
},
|
|
1000
991
|
maxBuffer: 20 * 1024 * 1024
|
|
1001
992
|
},
|
package/plugins/claude/README.md
CHANGED
|
@@ -4,17 +4,21 @@ Claude Code plugin that exposes the Overlord local ticket workflow to any Claude
|
|
|
4
4
|
|
|
5
5
|
## What ships
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
|
|
7
9
|
- `skills/overlord-ticket/SKILL.md` — durable attach → update → ask → deliver workflow.
|
|
8
10
|
- `commands/{connect,load,create,prompt}.md` — slash commands for session routing and ticket creation.
|
|
9
|
-
- `hooks/hooks.json` + `scripts/permission-hook.sh` — PermissionRequest notifier that
|
|
10
|
-
-
|
|
11
|
-
|
|
11
|
+
- `hooks/hooks.json` + `scripts/permission-hook.sh` — PermissionRequest notifier that posts to `/api/protocol/permission-request` on the Overlord platform.
|
|
12
|
+
- `userConfig` for `overlord_url`. Auth is expected to come from shared OAuth credentials or explicit `OVERLORD_ACCESS_TOKEN` + `OVERLORD_ORGANIZATION_ID` env vars.
|
|
13
|
+
|
|
14
|
+
|
|
12
15
|
|
|
13
16
|
## Requirements
|
|
14
17
|
|
|
15
18
|
- Overlord CLI (`ovld`) on `PATH`. See the Overlord docs / Settings → Agents & MCP.
|
|
16
19
|
- `TICKET_ID` is exported automatically when a Claude session is launched from Overlord; outside that launch, set it manually before calling the permission hook.
|
|
17
20
|
|
|
21
|
+
|
|
18
22
|
## Install (local dev)
|
|
19
23
|
|
|
20
24
|
```bash
|
|
@@ -23,14 +27,23 @@ claude --plugin-dir /absolute/path/to/overlord/plugins/claude
|
|
|
23
27
|
|
|
24
28
|
## Install (marketplace)
|
|
25
29
|
|
|
30
|
+
|
|
26
31
|
Once published:
|
|
27
32
|
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
28
36
|
```bash
|
|
29
37
|
claude plugin marketplace add cooperativ/overlord-marketplace
|
|
30
38
|
claude plugin install overlord@cooperativ
|
|
31
39
|
```
|
|
32
40
|
|
|
33
|
-
|
|
41
|
+
|
|
42
|
+
The plugin prompts for `overlord_url` at install time. Authentication should come from `ovld auth repair` first when a shared session already exists, then `ovld auth login`, Overlord Desktop, or explicit OAuth env vars in the launched shell.
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
34
47
|
|
|
35
48
|
## Namespaced components
|
|
36
49
|
|
|
@@ -40,3 +53,6 @@ Inside Claude Code the components are surfaced with the plugin prefix:
|
|
|
40
53
|
- commands → `/overlord:connect`, `/overlord:load`, `/overlord:create`, `/overlord:prompt`
|
|
41
54
|
|
|
42
55
|
Prompts generated by Overlord (see `lib/overlord/ticket-prompt.ts`) already reference these names in `bundle` instruction mode.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|