overlord-cli 0.2605311843.0 → 0.2605312007.1

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
@@ -149,10 +149,11 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
149
149
  - `get-device` / `update-device` - register the caller machine (per organization + user + fingerprint) and rename its label
150
150
  - `list-project-resources` / `add-project-resource` / `update-project-resource` - manage filesystem directories tied to a project device
151
151
  - `request-execution` / `claim-execution` / `complete-execution-launch` / `fail-execution-launch` - durable runner queue operations used by auto-advance and manual Run
152
+ - `list-organizations` - list every organization the authenticated user (or agent token) belongs to; the runner uses this to poll all of your organizations, not just the one stored at login
152
153
 
153
154
  Devices are keyed by **(organization, user, fingerprint)** so the same physical workstation can appear once per org session.
154
155
 
155
- `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.
156
+ `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. By default a single runner drains queued requests from **every organization you belong to**, so you do not need one runner per org. Pin it to a single org with `--organization-id <id>` or `OVERLORD_ORGANIZATION_ID`.
156
157
 
157
158
  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.
158
159
 
@@ -2049,6 +2049,24 @@ async function protocolClaimExecution(args) {
2049
2049
  console.log(JSON.stringify(data, null, 2));
2050
2050
  }
2051
2051
 
2052
+ async function protocolListOrganizations(args) {
2053
+ const flags = parseFlags(args);
2054
+ const { platformUrl, bearerToken, localSecret, organizationId } =
2055
+ await resolveProtocolAuthForFlags(flags);
2056
+ const timeoutMs = resolveTimeout(flags);
2057
+
2058
+ const data = await apiPost(
2059
+ platformUrl,
2060
+ bearerToken,
2061
+ localSecret,
2062
+ organizationId,
2063
+ '/api/protocol/organizations',
2064
+ {},
2065
+ timeoutMs
2066
+ );
2067
+ console.log(JSON.stringify(data, null, 2));
2068
+ }
2069
+
2052
2070
  async function protocolCompleteExecutionLaunch(args) {
2053
2071
  const flags = parseFlags(args);
2054
2072
  const { platformUrl, bearerToken, localSecret, organizationId } =
@@ -2225,7 +2243,7 @@ async function protocolPrompt(args) {
2225
2243
 
2226
2244
  // When --project-id is not provided, auto-send cwd as workingDirectory so
2227
2245
  // the server can resolve the project from the caller's project_user
2228
- // local_working_directory setting.
2246
+ // project resource directory setting.
2229
2247
  const personal = Boolean(flags.personal);
2230
2248
  const workingDirectory =
2231
2249
  flags['working-directory'] ?? (!flags['project-id'] && !personal ? process.cwd() : undefined);
@@ -2644,6 +2662,7 @@ Subcommands:
2644
2662
  request-approval-gate Flip auto_advance=false on the next queued future objective
2645
2663
  request-execution Queue an objective for local/remote runner execution
2646
2664
  claim-execution Claim one queued execution request for this device
2665
+ list-organizations List organizations the authenticated user belongs to
2647
2666
  complete-execution-launch Mark a claimed execution request launched
2648
2667
  fail-execution-launch Mark a claimed execution request failed
2649
2668
  permission-request Notify Overlord that the agent is requesting tool permission
@@ -2901,7 +2920,7 @@ prompt:
2901
2920
  Purpose:
2902
2921
  Create a ticket and attach to it in one call.
2903
2922
  When --project-id is omitted, automatically resolves the project from the
2904
- current working directory (matching against the caller's project_user.local_working_directory).
2923
+ current working directory (matching against the caller's project resource directories).
2905
2924
  Required:
2906
2925
  --objective <text>
2907
2926
  or: --objectives-json <json> / --objectives-file <path|-> with [{ "objective": "...", "title": "...", "autoAdvance": true }]
@@ -3009,7 +3028,7 @@ record-work:
3009
3028
  Notes:
3010
3029
  Project resolution mirrors \`prompt\`: if --project-id is not set and --personal is not used,
3011
3030
  the CLI sends cwd as workingDirectory and the API matches it against your configured
3012
- project_user.local_working_directory rows. If no match, the API returns 400 — re-run with
3031
+ project_resource_directories rows. If no match, the API returns 400 — re-run with
3013
3032
  --project-id <id> or --personal.
3014
3033
  Use this for completed-from-chat work. If you still need to execute the work, use \`create\`
3015
3034
  for a draft ticket or \`prompt\` to create and start execution immediately.
@@ -3139,6 +3158,12 @@ claim-execution:
3139
3158
  --lease-seconds <n>
3140
3159
  --project-id <uuid>
3141
3160
 
3161
+ list-organizations:
3162
+ Purpose:
3163
+ Print every organization the authenticated user (or agent token) belongs to.
3164
+ The runner uses this to poll all of your organizations, not just the one
3165
+ stored at login. Takes no required flags.
3166
+
3142
3167
  complete-execution-launch:
3143
3168
  Required:
3144
3169
  --request-id <uuid> (or EXECUTION_REQUEST_ID env var)
@@ -3184,6 +3209,7 @@ Examples:
3184
3209
  ovld protocol attachment-download-url --session-key <key> --attachment-id <attachment-id>
3185
3210
  ovld protocol request-execution --ticket-id 1:899 --agent codex --requested-from manual_run
3186
3211
  ovld protocol claim-execution --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT
3212
+ ovld protocol list-organizations
3187
3213
  ovld protocol deliver --session-key <key> --ticket-id <ticket_id> --summary "Done"
3188
3214
  ovld protocol deliver --session-key <key> --ticket-id <ticket_id> --summary "Done" --artifacts-file ./artifacts.json
3189
3215
  ovld protocol deliver --session-key <key> --ticket-id <ticket_id> --payload-json '{"summary":"Done","artifacts":[{"type":"note","label":"Delivery","content":"..."}]}'
@@ -3303,6 +3329,10 @@ EOF
3303
3329
  await protocolClaimExecution(args);
3304
3330
  return;
3305
3331
  }
3332
+ if (subcommand === 'list-organizations') {
3333
+ await protocolListOrganizations(args);
3334
+ return;
3335
+ }
3306
3336
  if (subcommand === 'complete-execution-launch') {
3307
3337
  await protocolCompleteExecutionLaunch(args);
3308
3338
  return;
@@ -27,8 +27,12 @@ Options:
27
27
  --device-fingerprint <fp> Override runner device identity
28
28
  --poll-interval-ms <ms> Poll interval for start mode (default 3000)
29
29
  --project-id <uuid> Only claim requests for one project
30
+ --organization-id <id> Only poll one organization (also OVERLORD_ORGANIZATION_ID).
31
+ Default: poll every organization you belong to.
30
32
 
31
33
  The runner claims durable execution requests and launches them with \`${primaryCommand} launch\`.
34
+ By default it drains queued requests from all of your organizations, so a single
35
+ runner serves every org without needing one instance per org.
32
36
  `);
33
37
  }
34
38
 
@@ -89,7 +93,7 @@ function protocolJson(subcommand, args, env) {
89
93
  return stdout.trim() ? JSON.parse(stdout) : {};
90
94
  }
91
95
 
92
- function claimExecution(flags, deviceFingerprint) {
96
+ function claimExecution(flags, deviceFingerprint, organizationId) {
93
97
  const args = [
94
98
  '--device-fingerprint',
95
99
  deviceFingerprint,
@@ -98,12 +102,82 @@ function claimExecution(flags, deviceFingerprint) {
98
102
  '--device-platform',
99
103
  process.platform
100
104
  ];
105
+ // An explicit organization id scopes this claim to one org. The CLI turns it
106
+ // into the `x-organization-id` header, which OAuth-authenticated protocol
107
+ // requests require and the server validates against the user's membership.
108
+ if (organizationId) {
109
+ args.push('--organization-id', String(organizationId));
110
+ }
101
111
  if (typeof flags['project-id'] === 'string') {
102
112
  args.push('--project-id', flags['project-id']);
103
113
  }
104
114
  return protocolJson('claim-execution', args, { OVERLORD_DEVICE_FINGERPRINT: deviceFingerprint });
105
115
  }
106
116
 
117
+ function listOrganizationIds(deviceFingerprint) {
118
+ const data = protocolJson('list-organizations', [], {
119
+ OVERLORD_DEVICE_FINGERPRINT: deviceFingerprint
120
+ });
121
+ const organizations = Array.isArray(data.organizations) ? data.organizations : [];
122
+ return organizations
123
+ .map(organization => organization?.id)
124
+ .filter(id => Number.isInteger(id) && id > 0)
125
+ .map(id => String(id));
126
+ }
127
+
128
+ function resolvePinnedOrganizationId(flags) {
129
+ const fromFlag =
130
+ typeof flags['organization-id'] === 'string' ? flags['organization-id'].trim() : '';
131
+ if (fromFlag) return fromFlag;
132
+ const fromEnv = process.env.OVERLORD_ORGANIZATION_ID?.trim();
133
+ return fromEnv || '';
134
+ }
135
+
136
+ // Re-discover the user's organizations periodically so a runner picks up newly
137
+ // joined orgs without a restart, without hitting the API on every poll.
138
+ const ORGANIZATION_REFRESH_MS = 60_000;
139
+
140
+ /**
141
+ * Resolves which organizations a poll pass should claim from.
142
+ *
143
+ * - When the user pins an org (`--organization-id` flag or
144
+ * `OVERLORD_ORGANIZATION_ID`), only that org is polled.
145
+ * - Otherwise the runner discovers every org the authenticated user belongs to
146
+ * and polls each one. If discovery fails or returns nothing, it falls back to
147
+ * the credential default (an empty scope id) so behavior never regresses.
148
+ */
149
+ export function createOrganizationScope(flags, deviceFingerprint) {
150
+ const pinned = resolvePinnedOrganizationId(flags);
151
+ if (pinned) {
152
+ return { pinned: true, resolve: () => [pinned] };
153
+ }
154
+
155
+ let cache = [];
156
+ let fetchedAt = 0;
157
+ return {
158
+ pinned: false,
159
+ resolve() {
160
+ const now = Date.now();
161
+ if (cache.length === 0 || now - fetchedAt >= ORGANIZATION_REFRESH_MS) {
162
+ try {
163
+ const ids = listOrganizationIds(deviceFingerprint);
164
+ fetchedAt = now;
165
+ // Keep the last known good list on a transient empty result.
166
+ if (ids.length > 0) cache = ids;
167
+ } catch (error) {
168
+ process.stderr.write(
169
+ `[runner] could not list organizations (${
170
+ error instanceof Error ? error.message : error
171
+ }); using the default organization\n`
172
+ );
173
+ }
174
+ }
175
+ // Empty string = "no override", i.e. the org stored in credentials.
176
+ return cache.length > 0 ? cache : [''];
177
+ }
178
+ };
179
+ }
180
+
107
181
  function completeLaunch(requestId, deviceFingerprint) {
108
182
  protocolJson(
109
183
  'complete-execution-launch',
@@ -358,14 +432,34 @@ export async function launchClaimedRequest(claim, deviceFingerprint) {
358
432
  return true;
359
433
  }
360
434
 
361
- export async function runOnce(flags, deviceFingerprint) {
362
- const claim = claimExecution(flags, deviceFingerprint);
363
- if (!claim.request) {
435
+ export async function runOnce(flags, deviceFingerprint, organizationScope) {
436
+ const scope = organizationScope ?? createOrganizationScope(flags, deviceFingerprint);
437
+ const organizationIds = scope.resolve();
438
+
439
+ let launchedAny = false;
440
+ for (const organizationId of organizationIds) {
441
+ let claim;
442
+ try {
443
+ claim = claimExecution(flags, deviceFingerprint, organizationId);
444
+ } catch (error) {
445
+ // A single organization failing (transient API/auth error) must not stop
446
+ // the runner from servicing the others.
447
+ process.stderr.write(
448
+ `[runner] claim failed for organization ${organizationId || 'default'}: ${
449
+ error instanceof Error ? error.message : error
450
+ }\n`
451
+ );
452
+ continue;
453
+ }
454
+ if (!claim.request) continue;
455
+ await launchClaimedRequest(claim, deviceFingerprint);
456
+ launchedAny = true;
457
+ }
458
+
459
+ if (!launchedAny) {
364
460
  process.stderr.write('[runner] no queued execution requests\n');
365
- return false;
366
461
  }
367
- await launchClaimedRequest(claim, deviceFingerprint);
368
- return true;
462
+ return launchedAny;
369
463
  }
370
464
 
371
465
  function sleep(ms) {
@@ -408,10 +502,18 @@ export async function runRunnerCommand(subcommand, args, primaryCommand = 'ovld'
408
502
  typeof flags['poll-interval-ms'] === 'string'
409
503
  ? Math.max(1000, Number.parseInt(flags['poll-interval-ms'], 10) || 3000)
410
504
  : 3000;
505
+ // Build the organization scope once so its discovery cache persists across
506
+ // polls instead of refetching the org list every iteration.
507
+ const organizationScope = createOrganizationScope(flags, deviceFingerprint);
411
508
  process.stderr.write(`[runner] started with device ${deviceFingerprint}\n`);
509
+ process.stderr.write(
510
+ organizationScope.pinned
511
+ ? `[runner] polling a single organization\n`
512
+ : `[runner] polling every organization you belong to\n`
513
+ );
412
514
  for (;;) {
413
515
  try {
414
- const launched = await runOnce(flags, deviceFingerprint);
516
+ const launched = await runOnce(flags, deviceFingerprint, organizationScope);
415
517
  if (!launched) await sleep(pollIntervalMs);
416
518
  } catch (error) {
417
519
  process.stderr.write(`[runner] ${error instanceof Error ? error.message : error}\n`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "0.2605311843.0",
3
+ "version": "0.2605312007.1",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "homepage": "https://www.ovld.ai/docs/",
6
6
  "type": "module",
@@ -123,7 +123,7 @@ Use this mode when the conversation starts normally and the user asks you to cre
123
123
 
124
124
  1. If the user wants to create tickets (and does not ask to start execution), run `ovld protocol create --agent antigravity --objectives-json '[{"objective":"..."}]'`.
125
125
  - When `--session-key` and `--ticket-id` are provided, it creates a follow-up draft.
126
- - When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to Overlord `local_working_directory`, then creates a standalone draft.
126
+ - When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to Overlord project resource directories, then creates a standalone draft.
127
127
  - Pass multiple items in `--objectives-json` when creating ordered steps for the same feature or goal.
128
128
  2. Default to `create` for new tickets. Only use `/overlord:prompt` or `ovld protocol prompt --agent antigravity --objectives-json '[{"objective":"..."}]'` when the user explicitly asks to create and execute immediately.
129
129
  `prompt` creates the ticket in `execute` status and attaches immediately.
@@ -276,4 +276,4 @@ This keeps the ticket feed readable while preserving the full document in versio
276
276
  - Do not add or commit changes unless the user explicitly asks you to commit.
277
277
  - Delivery is the concluding step. After delivering, stop implementation work unless the user explicitly asks for follow-up execution; once follow-up execution is complete, deliver again.
278
278
 
279
- <!-- version: 0.1.5 -->
279
+ <!-- version: 0.1.6 -->
@@ -60,7 +60,7 @@ Use this mode when the conversation starts normally and the user asks Claude to
60
60
 
61
61
  1. If the user wants to create tickets (and does not ask to start execution), use `/overlord:create` or run `ovld protocol create --agent claude-code --objectives-json '[{"objective":"..."}]'`.
62
62
  - When `--session-key` and `--ticket-id` are provided, it creates a follow-up draft.
63
- - When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to Overlord `local_working_directory`, then creates a standalone draft.
63
+ - When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to Overlord project resource directories, then creates a standalone draft.
64
64
  - Pass multiple items in `--objectives-json` when creating ordered steps for the same feature or goal.
65
65
  - If the user wants to **add more objectives to an existing ticket** (not create a new ticket), use `ovld protocol add-objectives --ticket-id <ticket_id> --objectives-json '[{"objective":"..."}]'` instead.
66
66
  2. Default to `create` for new tickets. Only use `/overlord:prompt` or `ovld protocol prompt --agent claude-code --objectives-json '[{"objective":"..."}]'` when the user explicitly asks to create and execute immediately.
@@ -114,5 +114,5 @@ For the `record-change-rationales` command and full payload shape with optional
114
114
  - [reference/context.md](reference/context.md) — Shared state, attachments, and large artifact policy
115
115
  - [reference/shell-escaping.md](reference/shell-escaping.md) — Heredoc stdin piping for special characters in summaries and payloads
116
116
 
117
- <!-- version: 0.5.9 -->
117
+ <!-- version: 0.5.10 -->
118
118
 
@@ -60,7 +60,7 @@ Use this mode when the conversation starts normally and the user asks Cursor to
60
60
 
61
61
  1. If the user wants to create tickets (and does not ask to start execution), use `/create` or run `ovld protocol create --agent cursor --objectives-json '[{"objective":"..."}]'`.
62
62
  - When `--session-key` and `--ticket-id` are provided, it creates a follow-up draft.
63
- - When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to Overlord `local_working_directory`, then creates a standalone draft.
63
+ - When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to Overlord project resource directories, then creates a standalone draft.
64
64
  - Pass multiple items in `--objectives-json` when creating ordered steps for the same feature or goal.
65
65
  - If the user wants to **add more objectives to an existing ticket** (not create a new ticket), use `ovld protocol add-objectives --ticket-id <ticket_id> --objectives-json '[{"objective":"..."}]'` instead.
66
66
  2. Default to `create` for new tickets. Only use `/prompt` or `ovld protocol prompt --agent cursor --objectives-json '[{"objective":"..."}]'` when the user explicitly asks to create and execute immediately.
@@ -114,5 +114,5 @@ For the `record-change-rationales` command and full payload shape with optional
114
114
  - [reference/context.md](reference/context.md) — Shared state, attachments, and large artifact policy
115
115
  - [reference/shell-escaping.md](reference/shell-escaping.md) — Heredoc stdin piping for special characters in summaries and payloads
116
116
 
117
- <!-- version: 0.5.9 -->
117
+ <!-- version: 0.5.10 -->
118
118
 
@@ -60,7 +60,7 @@ Use this mode when the conversation starts normally and the user asks Codex to c
60
60
 
61
61
  1. If the user wants to create tickets (and does not ask to start execution), run `ovld protocol create --agent codex --objectives-json '[{"objective":"..."}]'`.
62
62
  - When `--session-key` and `--ticket-id` are provided, it creates a follow-up draft.
63
- - When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to Overlord `local_working_directory`, then creates a standalone draft.
63
+ - When session flags are omitted, it resolves the project by matching current working directory (or `--working-directory`) to Overlord project resource directories, then creates a standalone draft.
64
64
  - Pass multiple items in `--objectives-json` when creating ordered steps for the same feature or goal.
65
65
  - If the user wants to **add more objectives to an existing ticket** (not create a new ticket), use `ovld protocol add-objectives --ticket-id <ticket_id> --objectives-json '[{"objective":"..."}]'` instead.
66
66
  2. Default to `create` for new tickets. Only use `ovld protocol prompt --agent codex --objectives-json '[{"objective":"..."}]'` when the user explicitly asks to create and execute immediately.
@@ -115,5 +115,5 @@ For the `record-change-rationales` command and full payload shape with optional
115
115
  - [reference/context.md](reference/context.md) — Shared state, attachments, and large artifact policy
116
116
  - [reference/shell-escaping.md](reference/shell-escaping.md) — Heredoc stdin piping for special characters in summaries and payloads
117
117
 
118
- <!-- version: 0.5.9 -->
118
+ <!-- version: 0.5.10 -->
119
119