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
@@ -0,0 +1,306 @@
1
+ #!/usr/bin/env node
2
+
3
+ import readline from 'node:readline';
4
+ import { stdin as input, stdout as output } from 'node:process';
5
+
6
+ import { buildAuthHeaders, resolveAuth } from './credentials.mjs';
7
+ import { runLauncherCommand } from './launcher.mjs';
8
+
9
+ const PROMPT_AGENT_IDENTIFIERS = {
10
+ claude: 'claude-code',
11
+ codex: 'codex',
12
+ cursor: 'cursor',
13
+ gemini: 'gemini',
14
+ opencode: 'opencode'
15
+ };
16
+ const PROMPT_AGENTS = Object.keys(PROMPT_AGENT_IDENTIFIERS);
17
+
18
+ function parseFlags(args) {
19
+ const flags = {};
20
+ const positionals = [];
21
+
22
+ for (let i = 0; i < args.length; i++) {
23
+ const arg = args[i];
24
+
25
+ if (!arg.startsWith('--')) {
26
+ positionals.push(arg);
27
+ continue;
28
+ }
29
+
30
+ const eqIdx = arg.indexOf('=');
31
+ if (eqIdx !== -1) {
32
+ flags[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
33
+ continue;
34
+ }
35
+
36
+ const key = arg.slice(2);
37
+ if (i + 1 < args.length && !args[i + 1].startsWith('--')) {
38
+ flags[key] = args[i + 1];
39
+ i++;
40
+ } else {
41
+ flags[key] = true;
42
+ }
43
+ }
44
+
45
+ return { flags, positionals };
46
+ }
47
+
48
+ function buildUsage(commandName) {
49
+ if (commandName === 'prompt') {
50
+ return 'Usage: ovld prompt "<objective>" [--title "..."] [--acceptance-criteria "..."] [--available-tools "..."] [--execution-target agent|human] [--priority low|medium|high|urgent] [--project-id <id>] [--agent <agent>] [--model <identifier>] [--delegate <agent>]';
51
+ }
52
+
53
+ return 'Usage: ovld create "<objective>" [--title "..."] [--acceptance-criteria "..."] [--available-tools "..."] [--execution-target agent|human] [--priority low|medium|high|urgent] [--project-id <id>] [--agent <agent>] [--model <identifier>] [--delegate <agent>]';
54
+ }
55
+
56
+ function ensureObjective(commandName, objective) {
57
+ if (objective) return;
58
+
59
+ console.error(`Error: objective is required.\n`);
60
+ console.error(buildUsage(commandName));
61
+ process.exit(1);
62
+ }
63
+
64
+ export function parseNumberedSelection(rawValue, count) {
65
+ const trimmed = rawValue.trim();
66
+ if (!trimmed) return null;
67
+ if (!/^\d+$/.test(trimmed)) return null;
68
+
69
+ const selected = Number.parseInt(trimmed, 10);
70
+ if (selected < 1 || selected > count) return null;
71
+
72
+ return selected - 1;
73
+ }
74
+
75
+ export function sortProjects(projects) {
76
+ return [...projects].sort((left, right) => {
77
+ const byOrganization = String(left.organizationName ?? '').localeCompare(
78
+ String(right.organizationName ?? '')
79
+ );
80
+ if (byOrganization !== 0) return byOrganization;
81
+
82
+ const byName = String(left.name ?? '').localeCompare(String(right.name ?? ''));
83
+ if (byName !== 0) return byName;
84
+
85
+ return String(left.id ?? '').localeCompare(String(right.id ?? ''));
86
+ });
87
+ }
88
+
89
+ function projectLabel(project) {
90
+ const organizationName = String(project.organizationName ?? '').trim();
91
+ return organizationName ? `${project.name} - ${organizationName}` : project.name;
92
+ }
93
+
94
+ async function promptForSelection({ items, label, prompt, renderItem }) {
95
+ if (!items.length) {
96
+ throw new Error(`No ${label.toLowerCase()} available.`);
97
+ }
98
+
99
+ const rl = readline.createInterface({ input, output });
100
+ const question = promptText =>
101
+ new Promise((resolve, reject) => {
102
+ const handleError = error => {
103
+ rl.off('error', handleError);
104
+ reject(error);
105
+ };
106
+
107
+ rl.once('error', handleError);
108
+ rl.question(promptText, answer => {
109
+ rl.off('error', handleError);
110
+ resolve(answer);
111
+ });
112
+ });
113
+
114
+ try {
115
+ while (true) {
116
+ output.write(`\n${label}\n`);
117
+ items.forEach((item, index) => {
118
+ output.write(` ${index + 1}. ${renderItem(item, index)}\n`);
119
+ });
120
+
121
+ const answer = await question(`\n${prompt} `);
122
+ const selectedIndex = parseNumberedSelection(answer, items.length);
123
+ if (selectedIndex !== null) {
124
+ return items[selectedIndex];
125
+ }
126
+
127
+ output.write(`Enter a number between 1 and ${items.length}.\n`);
128
+ }
129
+ } finally {
130
+ rl.close();
131
+ }
132
+ }
133
+
134
+ async function fetchProjects(platformUrl, bearerToken, localSecret, organizationId) {
135
+ const res = await fetch(`${platformUrl}/api/protocol/projects`, {
136
+ headers: buildAuthHeaders(bearerToken, localSecret, organizationId)
137
+ });
138
+
139
+ const data = await res.json().catch(() => ({}));
140
+ if (!res.ok) {
141
+ throw new Error(
142
+ `Failed to list projects (${res.status}): ${data.error ?? JSON.stringify(data)}`
143
+ );
144
+ }
145
+
146
+ return Array.isArray(data.projects) ? sortProjects(data.projects) : [];
147
+ }
148
+
149
+ async function createTicket(platformUrl, bearerToken, localSecret, organizationId, body) {
150
+ const res = await fetch(`${platformUrl}/api/protocol/tickets`, {
151
+ method: 'POST',
152
+ headers: {
153
+ ...buildAuthHeaders(bearerToken, localSecret, organizationId),
154
+ 'Content-Type': 'application/json'
155
+ },
156
+ body: JSON.stringify(body)
157
+ });
158
+
159
+ const data = await res.json().catch(() => ({}));
160
+ if (!res.ok) {
161
+ throw new Error(
162
+ `Failed to create ticket (${res.status}): ${data.error ?? JSON.stringify(data)}`
163
+ );
164
+ }
165
+
166
+ return data.ticket;
167
+ }
168
+
169
+ function resolveProject(projects, projectId) {
170
+ if (!projectId) return null;
171
+
172
+ const project = projects.find(candidate => candidate.id === projectId);
173
+ if (!project) {
174
+ throw new Error(`Unknown project ID: ${projectId}`);
175
+ }
176
+
177
+ return project;
178
+ }
179
+
180
+ function resolveAgent(agent) {
181
+ if (!agent) return null;
182
+
183
+ const normalizedAgent = agent.trim().toLowerCase();
184
+ if (!PROMPT_AGENTS.includes(normalizedAgent)) {
185
+ throw new Error(`Unknown agent: "${agent}". Must be one of: ${PROMPT_AGENTS.join(', ')}`);
186
+ }
187
+
188
+ return normalizedAgent;
189
+ }
190
+
191
+ export function resolvePromptAgentIdentifier(agent) {
192
+ return PROMPT_AGENT_IDENTIFIERS[agent] ?? agent;
193
+ }
194
+
195
+ export function resolveTicketCreationModelIdentifier(flags = {}) {
196
+ const explicitModel = typeof flags.model === 'string' ? flags.model.trim() : '';
197
+ if (explicitModel) return explicitModel;
198
+
199
+ const envModel =
200
+ process.env.OVERLORD_MODEL_IDENTIFIER?.trim() ||
201
+ process.env.MODEL_IDENTIFIER?.trim() ||
202
+ process.env.AGENT_MODEL?.trim();
203
+ return envModel || null;
204
+ }
205
+
206
+ export function resolveTicketCreationDelegate(flags = {}, selectedAgent = null, modelIdentifier = '') {
207
+ const explicitDelegate = typeof flags.delegate === 'string' ? flags.delegate.trim() : '';
208
+ if (explicitDelegate) return explicitDelegate;
209
+
210
+ const resolvedModel = typeof modelIdentifier === 'string' ? modelIdentifier.trim() : '';
211
+ if (resolvedModel) return resolvedModel;
212
+
213
+ const explicitAgent = typeof flags.agent === 'string' ? flags.agent.trim() : '';
214
+ if (explicitAgent) return resolvePromptAgentIdentifier(explicitAgent.toLowerCase()) ?? explicitAgent;
215
+
216
+ if (selectedAgent) return resolvePromptAgentIdentifier(selectedAgent);
217
+
218
+ const envAgent = process.env.AGENT_IDENTIFIER?.trim();
219
+ return envAgent || null;
220
+ }
221
+
222
+ async function runTicketCreationFlow(args, { commandName, launchAgent }) {
223
+ const { flags, positionals } = parseFlags(args);
224
+ const objective = String(flags.objective ?? positionals.join(' ')).trim();
225
+ ensureObjective(commandName, objective);
226
+
227
+ const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
228
+ const projects = await fetchProjects(platformUrl, bearerToken, localSecret, organizationId);
229
+
230
+ if (!projects.length) {
231
+ throw new Error('No projects available. Create a project first.');
232
+ }
233
+
234
+ const selectedProject =
235
+ resolveProject(projects, typeof flags['project-id'] === 'string' ? flags['project-id'] : '') ??
236
+ (await promptForSelection({
237
+ items: projects,
238
+ label: 'Projects',
239
+ prompt: 'Select a project by number:',
240
+ renderItem: project => projectLabel(project)
241
+ }));
242
+
243
+ const selectedAgent = launchAgent
244
+ ? (resolveAgent(typeof flags.agent === 'string' ? flags.agent : '') ??
245
+ (await promptForSelection({
246
+ items: PROMPT_AGENTS,
247
+ label: 'Agents',
248
+ prompt: 'Select an agent by number:',
249
+ renderItem: agent => agent
250
+ })))
251
+ : null;
252
+
253
+ const modelIdentifier = resolveTicketCreationModelIdentifier(flags);
254
+ const ticketDelegate = resolveTicketCreationDelegate(flags, selectedAgent, modelIdentifier);
255
+
256
+ const ticket = await createTicket(platformUrl, bearerToken, localSecret, organizationId, {
257
+ objective,
258
+ title: String(flags.title ?? ''),
259
+ acceptanceCriteria: String(flags['acceptance-criteria'] ?? ''),
260
+ availableTools: String(flags['available-tools'] ?? ''),
261
+ executionTarget: String(flags['execution-target'] ?? 'agent'),
262
+ priority: String(flags.priority ?? 'medium'),
263
+ projectId: selectedProject.id,
264
+ ...(ticketDelegate ? { delegate: ticketDelegate } : {})
265
+ });
266
+
267
+ if (!launchAgent) {
268
+ console.log(`ticket created with id ${ticket.id}`);
269
+ return;
270
+ }
271
+
272
+ process.env.TICKET_ID = ticket.id;
273
+ await runLauncherCommand('run', [selectedAgent, '--ticket-id', ticket.id]);
274
+ }
275
+
276
+ export async function runCreateCommand(args) {
277
+ if (args[0] === '--help' || args[0] === 'help') {
278
+ console.log(`${buildUsage('create')}
279
+
280
+ Creates a ticket after interactive numbered project selection.
281
+
282
+ Examples:
283
+ ovld create "Implement login page" --agent codex
284
+ ovld create "Fix sync bug" --project-id <project-id>
285
+ `);
286
+ return;
287
+ }
288
+
289
+ await runTicketCreationFlow(args, { commandName: 'create', launchAgent: false });
290
+ }
291
+
292
+ export async function runPromptCommand(args) {
293
+ if (args[0] === '--help' || args[0] === 'help') {
294
+ console.log(`${buildUsage('prompt')}
295
+
296
+ Creates a ticket after interactive numbered project selection, then lets you pick an agent by number and launches it on the new ticket.
297
+
298
+ Examples:
299
+ ovld prompt "Implement login page"
300
+ ovld prompt "Investigate flaky tests" --agent codex --model gpt-5.4
301
+ `);
302
+ return;
303
+ }
304
+
305
+ await runTicketCreationFlow(args, { commandName: 'prompt', launchAgent: true });
306
+ }
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Post-install message for Overlord CLI
5
+ * Shown after `npm install -g overlord-cli` or `yarn global add overlord-cli`
6
+ */
7
+
8
+ import { fileURLToPath } from 'node:url';
9
+ import { dirname } from 'node:path';
10
+
11
+ const __filename = fileURLToPath(import.meta.url);
12
+ const __dirname = dirname(__filename);
13
+
14
+ // Only show message if this is a global install (not local dev)
15
+ const isGlobalInstall = process.env.npm_config_global === 'true' ||
16
+ process.env.npm_execpath?.includes('yarn') ||
17
+ !__dirname.includes('node_modules');
18
+
19
+ if (!isGlobalInstall) {
20
+ process.exit(0);
21
+ }
22
+
23
+ const green = s => `\x1b[32m${s}\x1b[0m`;
24
+ const cyan = s => `\x1b[36m${s}\x1b[0m`;
25
+ const bold = s => `\x1b[1m${s}\x1b[0m`;
26
+
27
+ console.log(`
28
+ ${green('✓')} Overlord CLI installed successfully!
29
+
30
+ ${bold('Next step:')} Prepare agent plugins and connectors
31
+
32
+ ${cyan('ovld setup')}
33
+
34
+ This will guide you through:
35
+ • Selecting which agent plugins/connectors to prepare (Claude, Cursor, etc.)
36
+ • Migrating Claude from the v3.25 connector files to the v4 plugin
37
+ • Configuring agent permissions for Overlord protocol access
38
+
39
+ You can also run ${cyan('ovld setup <agent>')} to prepare a specific agent,
40
+ or ${cyan('ovld doctor')} to check your installation status.
41
+
42
+ Run ${cyan('ovld help')} to see all available commands.
43
+ `);