overlord-cli 0.2605101225.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.
Files changed (42) hide show
  1. package/README.md +128 -0
  2. package/bin/_cli/attach.mjs +357 -0
  3. package/bin/_cli/auth.mjs +696 -0
  4. package/bin/_cli/cli-update.mjs +119 -0
  5. package/bin/_cli/credentials.mjs +723 -0
  6. package/bin/_cli/index.mjs +170 -0
  7. package/bin/_cli/launcher.mjs +474 -0
  8. package/bin/_cli/new-ticket.mjs +306 -0
  9. package/bin/_cli/postinstall.mjs +43 -0
  10. package/bin/_cli/protocol.mjs +1799 -0
  11. package/bin/_cli/setup.mjs +1634 -0
  12. package/bin/_cli/ticket.mjs +55 -0
  13. package/bin/_cli/tickets.mjs +121 -0
  14. package/bin/_cli/version.mjs +7 -0
  15. package/bin/ovld.mjs +13 -0
  16. package/package.json +36 -0
  17. package/plugins/claude/.claude-plugin/plugin.json +34 -0
  18. package/plugins/claude/README.md +42 -0
  19. package/plugins/claude/commands/connect.md +19 -0
  20. package/plugins/claude/commands/create.md +17 -0
  21. package/plugins/claude/commands/load.md +19 -0
  22. package/plugins/claude/commands/spawn.md +17 -0
  23. package/plugins/claude/hooks/hooks.json +15 -0
  24. package/plugins/claude/scripts/permission-hook.sh +21 -0
  25. package/plugins/claude/skills/overlord-ticket/SKILL.md +164 -0
  26. package/plugins/cursor/.cursor-plugin/plugin.json +14 -0
  27. package/plugins/cursor/commands/connect.md +3 -0
  28. package/plugins/cursor/commands/create.md +11 -0
  29. package/plugins/cursor/commands/load.md +3 -0
  30. package/plugins/cursor/commands/spawn.md +3 -0
  31. package/plugins/cursor/mcp.json +8 -0
  32. package/plugins/cursor/rules/overlord-local.mdc +10 -0
  33. package/plugins/cursor/scripts/overlord-mcp.mjs +150 -0
  34. package/plugins/cursor/skills/overlord-ticket/SKILL.md +49 -0
  35. package/plugins/overlord/.codex-plugin/plugin.json +35 -0
  36. package/plugins/overlord/.mcp.json +11 -0
  37. package/plugins/overlord/README.md +42 -0
  38. package/plugins/overlord/assets/icon.png +0 -0
  39. package/plugins/overlord/assets/logo.png +0 -0
  40. package/plugins/overlord/assets/screenshot.svg +28 -0
  41. package/plugins/overlord/scripts/overlord-mcp.mjs +688 -0
  42. package/plugins/overlord/skills/overlord-ticket/SKILL.md +146 -0
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # Overlord CLI
2
+
3
+ `overlord-cli` is the packaged command-line interface for Overlord. It lets you launch agents on tickets, create new tickets, and manage the ticket lifecycle from the terminal.
4
+
5
+ Website: [ovld.ai](https://ovld.ai)
6
+
7
+ ## Install
8
+
9
+ Note: Use Node.js 20 or newer for every CLI install or update.
10
+
11
+ Install it globally so the `ovld` and `overlord` commands are available on your `PATH`:
12
+
13
+ ```bash
14
+ npm install -g overlord-cli
15
+
16
+ #After installing, run
17
+ ovld setup all #to configure every supported connector (`ovld setup` alone is interactive)
18
+
19
+ #for individual connectors, run `ovld setup <connector>`.
20
+ ovld setup cursor
21
+ ovld setup claude
22
+ ovld setup codex
23
+ ovld setup all
24
+ ```
25
+
26
+ ```bash
27
+ # To update the CLI to the latest release, run:
28
+ ovld update
29
+ ```
30
+ ## Usage
31
+
32
+ ```bash
33
+ ovld help
34
+ overlord help
35
+ ```
36
+
37
+ ### Auth
38
+ ```bash
39
+ # Repair shared credentials first when a session already exists
40
+ ovld auth repair # mirror and chmod shared Desktop/CLI credentials when possible
41
+
42
+ # Login to Overlord if repair does not restore access
43
+ ovld auth login #opens a browser when possible and also prints a verification URL/code so login can be completed from another machine over SSH.
44
+ ovld auth status # show current login status (use --verbose for redacted diagnostics)
45
+ ovld auth logout # remove stored credentials
46
+ ```
47
+
48
+ Desktop-installed wrappers default `OVERLORD_URL` to `https://www.ovld.ai` unless you override it explicitly.
49
+ For local dev against the web app on port 3000, export the override before running auth or protocol commands:
50
+ ```bash
51
+ export OVERLORD_URL=http://localhost:3000
52
+ ```
53
+
54
+ Common commands:
55
+
56
+ ```bash
57
+ ovld auth login
58
+ ovld auth status
59
+ ovld attach
60
+ ovld create "Investigate the failing build" --agent codex
61
+ ovld prompt "Draft a fix for the onboarding flow"
62
+ ovld version
63
+ ovld update
64
+ ovld protocol discover-project
65
+ ovld protocol attach --ticket-id <ticket_id>
66
+ ovld protocol search-tickets --query "auth refactor" --status next-up,execute
67
+ ovld protocol update --session-key <session-key> --ticket-id <ticket_id> --summary "Working on it" --phase execute
68
+ ovld setup
69
+ ovld setup codex
70
+ ovld setup claude
71
+ ovld setup cursor
72
+ ovld setup gemini
73
+ ovld setup all
74
+ ovld doctor
75
+ ```
76
+
77
+ ## Requirements
78
+
79
+ - Node.js 20 or newer
80
+ - Access to an Overlord instance when using authenticated commands
81
+
82
+ ## Commands for Humans
83
+
84
+ Find full CLI docs here: https://www.ovld.ai/docs/surfaces/cli
85
+
86
+ Top-level commands (see `ovld help`):
87
+
88
+ - `attach` - search tickets and launch an agent interactively (`ovld attach [ticketId] [agent]`)
89
+ - `create` - create a ticket with numbered project selection; supports `--agent`, `--model`, `--delegate` (same delegate flags as `ovld protocol create`)
90
+ - `prompt` - create a ticket, then launch an agent on it
91
+ - `auth` - `login`, `status`, `repair` (shared Desktop/CLI credentials), or `logout`
92
+ - `tickets` - `create` or `list` (optional `--status`)
93
+ - `ticket` - `context <ticketId>` to print context for one ticket
94
+ - `launch`, `restart` - launch or resume an agent session
95
+ - `connect`, `run`, `resume` - legacy aliases for `launch` and `restart`
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
+ - `update` - install the latest CLI release from npm
98
+ - `doctor` - validate installed connectors and check for CLI updates
99
+ - `version` - print the installed CLI version
100
+
101
+ ## Commands for agents
102
+ Agents can find docs here: https://www.ovld.ai/docs/for-agents
103
+
104
+ `ovld protocol <subcommand>` is the surface agents and hooks use for ticket lifecycle work. Run `ovld protocol help` for flags, env fallbacks, and examples.
105
+
106
+ - `auth-status` - return machine-readable auth status for agent runtimes
107
+ - `discover-project` - resolve a project from the current (or given) working directory
108
+ - `attach` - start a ticket session and return full working context
109
+ - `connect` - start a lightweight session without full context
110
+ - `load-context` - read ticket context without creating a session
111
+ - `search-tickets` - find tickets by keyword, status, project, creator, or update date
112
+ - `create` - create a draft ticket without attaching (standalone or follow-up)
113
+ - `prompt` - create a ticket and attach to it immediately (`spawn` is a backward-compatible alias)
114
+ - `update` - post progress, activity events, and optional change rationales
115
+ - `record-change-rationales` - persist structured change rationales without a normal progress update
116
+ - `ask` - post a blocking question and move the ticket to review
117
+ - `permission-request` - notify Overlord that the agent is requesting tool permission
118
+ - `read-context` - read shared persistent context for this ticket
119
+ - `write-context` - write shared persistent context for future sessions
120
+ - `deliver` - finish work, send artifacts, and move the ticket to review
121
+ - `attachment-prepare-upload` - get a signed upload URL for an objective attachment
122
+ - `attachment-finalize-upload` - finalize an uploaded objective attachment row after storage upload
123
+ - `attachment-download-url` - get a signed download URL for an existing objective attachment
124
+ - `attachment-upload-file` - prepare, upload, and finalize a local objective attachment in one command
125
+
126
+ ## License
127
+
128
+ Permission is granted to use this software for any purpose, free of charge. You may not modify, distribute, sublicense, or sell copies of the software without explicit permission from the author.
@@ -0,0 +1,357 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * `ovld attach` — interactive ticket search + agent launcher.
5
+ *
6
+ * Usage:
7
+ * ovld attach # interactive: search tickets, pick agent
8
+ * ovld attach <ticketId> # skip ticket search, pick agent interactively
9
+ * ovld attach <ticketId> <agent> # non-interactive: launch immediately
10
+ */
11
+
12
+ import { buildAuthHeaders, resolveAuth } from './credentials.mjs';
13
+ import { runLauncherCommand } from './launcher.mjs';
14
+
15
+ const AGENTS = ['claude', 'cursor', 'codex', 'gemini', 'opencode'];
16
+ const MAX_VISIBLE = 8;
17
+ const SEARCH_DEBOUNCE_MS = 120;
18
+
19
+ // ─── ANSI helpers ─────────────────────────────────────────────────────────────
20
+
21
+ const hide = '\x1b[?25l';
22
+ const show = '\x1b[?25h';
23
+
24
+ const saveCursor = '\x1b7';
25
+ const restoreCursor = '\x1b8';
26
+ const eraseBelow = '\x1b[J';
27
+
28
+
29
+ const dim = s => `\x1b[2m${s}\x1b[0m`;
30
+ const bold = s => `\x1b[1m${s}\x1b[0m`;
31
+ const cyan = s => `\x1b[36m${s}\x1b[0m`;
32
+ const green = s => `\x1b[32m${s}\x1b[0m`;
33
+ const gray = s => `\x1b[90m${s}\x1b[0m`;
34
+ const yellow = s => `\x1b[33m${s}\x1b[0m`;
35
+ const red = s => `\x1b[31m${s}\x1b[0m`;
36
+
37
+ function truncate(str, max) {
38
+ if (!str) return '';
39
+ return str.length > max ? str.slice(0, max - 1) + '…' : str;
40
+ }
41
+
42
+ function statusColor(status) {
43
+ switch (status) {
44
+ case 'draft':
45
+ return dim(status);
46
+ case 'execute':
47
+ return cyan(status);
48
+ case 'review':
49
+ return yellow(status);
50
+ case 'complete':
51
+ return green(status);
52
+ case 'blocked':
53
+ return red(status);
54
+ default:
55
+ return gray(status ?? '?');
56
+ }
57
+ }
58
+
59
+ // ─── API ──────────────────────────────────────────────────────────────────────
60
+
61
+ async function searchTickets(platformUrl, bearerToken, localSecret, organizationId, query) {
62
+ const res = await fetch(`${platformUrl}/api/protocol/search-tickets`, {
63
+ method: 'POST',
64
+ headers: {
65
+ ...buildAuthHeaders(bearerToken, localSecret, organizationId),
66
+ 'Content-Type': 'application/json'
67
+ },
68
+ body: JSON.stringify({
69
+ includeCompleted: false,
70
+ query,
71
+ limit: MAX_VISIBLE
72
+ })
73
+ });
74
+
75
+ if (!res.ok) {
76
+ throw new Error(`Failed to search tickets (${res.status}): ${await res.text()}`);
77
+ }
78
+
79
+ const data = await res.json();
80
+ return data.tickets ?? [];
81
+ }
82
+
83
+ // ─── Interactive prompt ───────────────────────────────────────────────────────
84
+
85
+ /**
86
+ * Run an interactive list selector.
87
+ *
88
+ * In ticket mode (search callback provided), shows a search-as-you-type menu.
89
+ * In items mode (items array provided), shows a fixed list selector.
90
+ *
91
+ * @param {object} opts
92
+ * @param {string} opts.label - Label shown before the search input
93
+ * @param {string[]} [opts.items] - Fixed list of choices (agent picker)
94
+ * @param {(query: string) => Promise<object[]>} [opts.search] - Ticket search callback
95
+ * @param {string} [opts.prefix] - Text prepended to the input line (for UX context)
96
+ * @returns {Promise<string|object|null>} - Selected item/value, or null if cancelled
97
+ */
98
+ function runInteractivePrompt({ label, items = [], search, prefix = '' }) {
99
+ return new Promise(resolve => {
100
+ const isTicketMode = typeof search === 'function';
101
+ let query = '';
102
+ let selectedIdx = 0;
103
+ let hasRendered = false;
104
+ let loading = isTicketMode;
105
+ let errorMessage = '';
106
+ let filteredItems = isTicketMode ? [] : items;
107
+ let activeRequestId = 0;
108
+ let debounceTimer = null;
109
+
110
+ function getFiltered() {
111
+ return filteredItems;
112
+ }
113
+
114
+ function renderTicketRow(t, active) {
115
+ const seq = String(t.ticket_sequence ?? '?').padStart(3, ' ');
116
+ const status = t.status ?? '?';
117
+ const title = truncate(t.title || t.objective || '(no title)', 55);
118
+ const marker = active ? cyan('▶') : ' ';
119
+ return ` ${marker} ${gray('#' + seq)} ${gray('[')}${statusColor(status)}${gray(']')} ${active ? bold(title) : title}`;
120
+ }
121
+
122
+ function renderAgentRow(agent, active) {
123
+ const marker = active ? cyan('▶') : ' ';
124
+ return ` ${marker} ${active ? bold(agent) : agent}`;
125
+ }
126
+
127
+ function render() {
128
+ const filtered = getFiltered();
129
+ const count = Math.min(filtered.length, MAX_VISIBLE);
130
+ // Clamp selected within visible range
131
+ if (selectedIdx >= count) selectedIdx = Math.max(0, count - 1);
132
+
133
+ const lines = [];
134
+
135
+ // Input line
136
+ if (prefix) {
137
+ lines.push(` ${dim(prefix)}${query}${cyan('│')}`);
138
+ } else {
139
+ lines.push(` ${gray(label + ':')} ${query}${cyan('│')}`);
140
+ }
141
+ lines.push('');
142
+
143
+ if (loading) {
144
+ lines.push(gray(' Searching tickets…'));
145
+ } else if (errorMessage) {
146
+ lines.push(red(` ${errorMessage}`));
147
+ } else if (filtered.length === 0) {
148
+ lines.push(gray(' No matches'));
149
+ } else {
150
+ for (let i = 0; i < count; i++) {
151
+ lines.push(
152
+ isTicketMode
153
+ ? renderTicketRow(filtered[i], i === selectedIdx)
154
+ : renderAgentRow(filtered[i], i === selectedIdx)
155
+ );
156
+ }
157
+ if (filtered.length > MAX_VISIBLE) {
158
+ lines.push(gray(` … ${filtered.length - MAX_VISIBLE} more — keep typing to narrow`));
159
+ }
160
+ }
161
+
162
+ lines.push('');
163
+ lines.push(dim(' ↑↓ navigate · type to filter · Enter select · Esc cancel'));
164
+
165
+ if (hasRendered) {
166
+ process.stdout.write(restoreCursor + eraseBelow);
167
+ }
168
+ process.stdout.write(saveCursor + lines.join('\n'));
169
+ hasRendered = true;
170
+ }
171
+
172
+ async function loadMatches(nextQuery) {
173
+ if (!isTicketMode) return;
174
+ const requestId = ++activeRequestId;
175
+ loading = true;
176
+ errorMessage = '';
177
+ render();
178
+
179
+ try {
180
+ const nextItems = await search(nextQuery);
181
+ if (requestId !== activeRequestId) return;
182
+ filteredItems = nextItems;
183
+ } catch (error) {
184
+ if (requestId !== activeRequestId) return;
185
+ filteredItems = [];
186
+ errorMessage = error instanceof Error ? error.message : 'Ticket search failed.';
187
+ } finally {
188
+ if (requestId !== activeRequestId) return;
189
+ loading = false;
190
+ render();
191
+ }
192
+ }
193
+
194
+ function scheduleLoad() {
195
+ if (!isTicketMode) return;
196
+ if (debounceTimer) clearTimeout(debounceTimer);
197
+ debounceTimer = setTimeout(() => {
198
+ debounceTimer = null;
199
+ void loadMatches(query);
200
+ }, SEARCH_DEBOUNCE_MS);
201
+ }
202
+
203
+ function cleanup() {
204
+ if (debounceTimer) {
205
+ clearTimeout(debounceTimer);
206
+ debounceTimer = null;
207
+ }
208
+ if (hasRendered) {
209
+ process.stdout.write(restoreCursor + eraseBelow);
210
+ }
211
+ process.stdin.setRawMode(false);
212
+ process.stdin.removeAllListeners('data');
213
+ process.stdout.write(show);
214
+ }
215
+
216
+ process.stdin.setRawMode(true);
217
+ process.stdin.resume();
218
+ process.stdin.setEncoding('utf8');
219
+ process.stdout.write(hide);
220
+ render();
221
+ scheduleLoad();
222
+
223
+ process.stdin.on('data', key => {
224
+ const filtered = getFiltered();
225
+ const count = Math.min(filtered.length, MAX_VISIBLE);
226
+
227
+ // Ctrl-C / Ctrl-D → exit
228
+ if (key === '\x03' || key === '\x04') {
229
+ cleanup();
230
+ process.exit(0);
231
+ }
232
+
233
+ // Escape (lone \x1b, not a sequence like \x1b[A)
234
+ if (key === '\x1b') {
235
+ cleanup();
236
+ resolve(null);
237
+ return;
238
+ }
239
+
240
+ // Enter → confirm selection
241
+ if (key === '\r' || key === '\n') {
242
+ if (loading || filtered.length === 0) return;
243
+ const item = filtered[selectedIdx] ?? filtered[0];
244
+ cleanup();
245
+ resolve(item);
246
+ return;
247
+ }
248
+
249
+ // Arrow up
250
+ if (key === '\x1b[A') {
251
+ selectedIdx = (selectedIdx - 1 + count) % Math.max(1, count);
252
+ render();
253
+ return;
254
+ }
255
+
256
+ // Arrow down
257
+ if (key === '\x1b[B') {
258
+ selectedIdx = (selectedIdx + 1) % Math.max(1, count);
259
+ render();
260
+ return;
261
+ }
262
+
263
+ // Backspace
264
+ if (key === '\x7f' || key === '\b') {
265
+ if (query.length > 0) {
266
+ query = query.slice(0, -1);
267
+ selectedIdx = 0;
268
+ if (isTicketMode) scheduleLoad();
269
+ else render();
270
+ }
271
+ return;
272
+ }
273
+
274
+ // Printable character → append to query
275
+ if (key.length === 1 && key >= ' ') {
276
+ query += key;
277
+ selectedIdx = 0;
278
+ if (isTicketMode) scheduleLoad();
279
+ else render();
280
+ }
281
+ });
282
+ });
283
+ }
284
+
285
+ // ─── Main ─────────────────────────────────────────────────────────────────────
286
+
287
+ export async function runAttachCommand(args) {
288
+ const [ticketIdArg, agentArg] = args;
289
+
290
+ const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
291
+
292
+ // ── Phase 1: Ticket selection ──────────────────────────────────────────────
293
+
294
+ let ticketId = ticketIdArg;
295
+ let ticketTitle = '';
296
+
297
+ if (!ticketId) {
298
+ process.stdout.write('\n');
299
+ process.stdout.write(bold(' ovld attach\n'));
300
+ process.stdout.write('\n');
301
+
302
+ const selectedTicket = await runInteractivePrompt({
303
+ label: 'Search tickets',
304
+ search: nextQuery =>
305
+ searchTickets(platformUrl, bearerToken, localSecret, organizationId, nextQuery)
306
+ });
307
+
308
+ if (!selectedTicket) {
309
+ process.stdout.write(dim('\n Cancelled.\n\n'));
310
+ process.exit(0);
311
+ }
312
+
313
+ ticketId = selectedTicket.id;
314
+ ticketTitle = selectedTicket.title || selectedTicket.objective || '';
315
+ }
316
+
317
+ // ── Phase 2: Agent selection ───────────────────────────────────────────────
318
+
319
+ const shortId = ticketId.slice(-8).toUpperCase();
320
+
321
+ let agent = agentArg;
322
+
323
+ if (!agent) {
324
+ process.stdout.write('\n');
325
+ process.stdout.write(bold(` ovld attach `) + cyan(shortId) + '\n');
326
+ if (ticketTitle) {
327
+ process.stdout.write(gray(` ${truncate(ticketTitle, 60)}\n`));
328
+ }
329
+ process.stdout.write('\n');
330
+
331
+ agent = await runInteractivePrompt({
332
+ label: 'Agent',
333
+ items: AGENTS,
334
+ prefix: `ovld attach ${shortId} `
335
+ });
336
+
337
+ if (!agent) {
338
+ process.stdout.write(dim('\n Cancelled.\n\n'));
339
+ process.exit(0);
340
+ }
341
+ }
342
+
343
+ if (!AGENTS.includes(agent)) {
344
+ console.error(`\nUnknown agent: "${agent}". Must be one of: ${AGENTS.join(', ')}`);
345
+ process.exit(1);
346
+ }
347
+
348
+ // ── Launch ─────────────────────────────────────────────────────────────────
349
+
350
+ process.stdout.write('\n');
351
+ process.stdout.write(
352
+ ` ${green('✓')} ${bold(agent)} ← ${truncate(ticketTitle || shortId, 55)}\n\n`
353
+ );
354
+
355
+ process.env.TICKET_ID = ticketId;
356
+ await runLauncherCommand('run', [agent]);
357
+ }