overlord-cli 0.2605181821.0 → 0.2605201134.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 +6 -2
- package/bin/_cli/attach.mjs +1 -1
- package/bin/_cli/launcher.mjs +45 -26
- package/bin/_cli/new-ticket.mjs +84 -11
- package/bin/_cli/protocol.mjs +99 -19
- package/bin/_cli/setup.mjs +250 -56
- package/package.json +1 -1
- package/plugins/antigravity/README.md +53 -0
- package/plugins/antigravity/commands/add-objectives.md +13 -0
- package/plugins/antigravity/commands/attach.md +17 -0
- package/plugins/antigravity/commands/connect.md +17 -0
- package/plugins/antigravity/commands/create.md +15 -0
- package/plugins/antigravity/commands/discuss-objective.md +17 -0
- package/plugins/antigravity/commands/load.md +17 -0
- package/plugins/antigravity/commands/prompt.md +15 -0
- package/plugins/antigravity/commands/record-work.md +26 -0
- package/plugins/antigravity/hooks/hooks.json +15 -0
- package/plugins/antigravity/plugin.json +22 -0
- package/plugins/antigravity/scripts/overlord-mcp.mjs +1126 -0
- package/plugins/antigravity/scripts/user-prompt-submit-hook.sh +105 -0
- package/plugins/antigravity/skills/overlord-ticket/SKILL.md +264 -0
- package/plugins/claude/commands/add-objectives.md +14 -0
- package/plugins/claude/skills/overlord-ticket/SKILL.md +15 -7
- package/plugins/cursor/commands/add-objectives.md +8 -0
- package/plugins/cursor/skills/overlord-ticket/SKILL.md +12 -7
- package/plugins/overlord/.codex-plugin/hooks.json +12 -1
- package/plugins/overlord/.codex-plugin/plugin.json +1 -1
- package/plugins/overlord/scripts/overlord-mcp.mjs +290 -45
- package/plugins/overlord/scripts/permission-hook.sh +26 -0
- package/plugins/overlord/skills/overlord-ticket/SKILL.md +14 -6
package/README.md
CHANGED
|
@@ -72,11 +72,14 @@ ovld setup
|
|
|
72
72
|
ovld setup codex
|
|
73
73
|
ovld setup claude
|
|
74
74
|
ovld setup cursor
|
|
75
|
-
ovld setup
|
|
75
|
+
ovld setup antigravity
|
|
76
76
|
ovld setup all
|
|
77
|
+
ovld launch antigravity --ticket-id <ticket_id>
|
|
77
78
|
ovld doctor
|
|
78
79
|
```
|
|
79
80
|
|
|
81
|
+
**Antigravity:** Gemini CLI is deprecated. Use `ovld setup antigravity` and `ovld launch antigravity --ticket-id <ticket_id>`. Antigravity manages model selection internally.
|
|
82
|
+
|
|
80
83
|
For ticket-scoped protocol and launch commands, `ticket_id` values such as `1:899` carry the organization id. The CLI uses that first, then `--organization-id` for UUID compatibility, then stored auth.
|
|
81
84
|
|
|
82
85
|
## Requirements
|
|
@@ -116,6 +119,7 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
|
|
|
116
119
|
- `load-context` - read ticket context without creating a session
|
|
117
120
|
- `revert` - restore the local working tree to an objective checkpoint after fetching its checkpoint row
|
|
118
121
|
- `search-tickets` - find tickets by keyword, status, project, creator, or update date
|
|
122
|
+
- `add-objectives` - append ordered objectives to an existing ticket (`--objectives-json` / `--objectives-file`)
|
|
119
123
|
- `create` - create a draft ticket without attaching (standalone or follow-up)
|
|
120
124
|
- `prompt` - create a ticket and attach to it immediately (`spawn` is a backward-compatible alias)
|
|
121
125
|
- `record-work` - record already-completed chat work as a ticket in review with a completed objective and trigger feed-post generation
|
|
@@ -136,7 +140,7 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
|
|
|
136
140
|
|
|
137
141
|
Devices are keyed by **(organization, user, fingerprint)** so the same physical workstation can appear once per org session.
|
|
138
142
|
|
|
139
|
-
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.
|
|
143
|
+
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.
|
|
140
144
|
|
|
141
145
|
`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|->`.
|
|
142
146
|
|
package/bin/_cli/attach.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { buildAuthHeaders, resolveAuth } from './credentials.mjs';
|
|
13
13
|
import { runLauncherCommand } from './launcher.mjs';
|
|
14
14
|
|
|
15
|
-
const AGENTS = ['claude', 'cursor', 'codex', '
|
|
15
|
+
const AGENTS = ['claude', 'cursor', 'codex', 'antigravity', 'opencode'];
|
|
16
16
|
const MAX_VISIBLE = 8;
|
|
17
17
|
const SEARCH_DEBOUNCE_MS = 120;
|
|
18
18
|
|
package/bin/_cli/launcher.mjs
CHANGED
|
@@ -23,6 +23,12 @@ function claudeSourcePluginDir() {
|
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function antigravityBundleInstalled() {
|
|
27
|
+
return fs.existsSync(
|
|
28
|
+
path.join(os.homedir(), '.gemini', 'antigravity-cli', 'plugins', 'plugin.json')
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
function getInstructionMode(agent) {
|
|
27
33
|
if (agent === 'claude') {
|
|
28
34
|
return claudeSourcePluginDir() ? 'bundle' : 'legacy';
|
|
@@ -40,9 +46,27 @@ function getInstructionMode(agent) {
|
|
|
40
46
|
return fs.existsSync(pluginManifest) ? 'bundle' : 'legacy';
|
|
41
47
|
}
|
|
42
48
|
|
|
49
|
+
if (agent === 'antigravity') {
|
|
50
|
+
return antigravityBundleInstalled() ? 'bundle' : 'legacy';
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
return 'legacy';
|
|
44
54
|
}
|
|
45
55
|
|
|
56
|
+
function buildAgyLaunchArgv({ contextFile, mode, sessionId, extraArgs }) {
|
|
57
|
+
const argv = [...extraArgs, '--add-dir', os.tmpdir()];
|
|
58
|
+
if (mode === 'resume') {
|
|
59
|
+
const agySessionId = sessionId?.trim();
|
|
60
|
+
if (agySessionId) {
|
|
61
|
+
argv.push('--conversation', agySessionId);
|
|
62
|
+
} else {
|
|
63
|
+
argv.push('--continue');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
argv.push('--prompt-interactive', `@${contextFile}`);
|
|
67
|
+
return argv;
|
|
68
|
+
}
|
|
69
|
+
|
|
46
70
|
async function fetchContext(platformUrl, bearerToken, localSecret, organizationId, ticketId, agent) {
|
|
47
71
|
const params = new URLSearchParams({
|
|
48
72
|
context: 'cli',
|
|
@@ -67,12 +91,12 @@ const agentIdentifierMap = {
|
|
|
67
91
|
claude: 'claude-code',
|
|
68
92
|
codex: 'codex',
|
|
69
93
|
cursor: 'cursor',
|
|
70
|
-
|
|
94
|
+
antigravity: 'antigravity',
|
|
71
95
|
opencode: 'opencode',
|
|
72
96
|
pi: 'pi'
|
|
73
97
|
};
|
|
74
98
|
|
|
75
|
-
const supportedAgents = ['claude', 'codex', 'cursor', '
|
|
99
|
+
const supportedAgents = ['claude', 'codex', 'cursor', 'antigravity', 'opencode', 'pi'];
|
|
76
100
|
|
|
77
101
|
function shellQuote(value) {
|
|
78
102
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
@@ -88,7 +112,7 @@ function buildExtraArgs(agent, options = {}) {
|
|
|
88
112
|
? options.flags.map(flag => String(flag).trim()).filter(Boolean)
|
|
89
113
|
: [];
|
|
90
114
|
|
|
91
|
-
if (options.model) {
|
|
115
|
+
if (options.model && agent !== 'antigravity') {
|
|
92
116
|
args.push('--model', options.model);
|
|
93
117
|
}
|
|
94
118
|
|
|
@@ -97,8 +121,8 @@ function buildExtraArgs(agent, options = {}) {
|
|
|
97
121
|
args.push('--effort', options.thinking);
|
|
98
122
|
} else if (agent === 'codex') {
|
|
99
123
|
args.push('-c', `model_reasoning_effort=${toTomlString(options.thinking)}`);
|
|
100
|
-
} else if (agent === '
|
|
101
|
-
|
|
124
|
+
} else if (agent === 'antigravity') {
|
|
125
|
+
// Antigravity manages thinking levels internally — no launch flag.
|
|
102
126
|
} else if (agent === 'pi') {
|
|
103
127
|
args.push('--thinking', options.thinking);
|
|
104
128
|
}
|
|
@@ -392,31 +416,24 @@ async function runAgent(agent, mode = 'run', options = {}) {
|
|
|
392
416
|
} else {
|
|
393
417
|
execFileSync('pi', [...extraArgs, context], { stdio: 'inherit', env: childEnv });
|
|
394
418
|
}
|
|
395
|
-
} else if (agent === '
|
|
396
|
-
// Write context to a temp file. Passing inline content as a positional arg
|
|
397
|
-
// causes Gemini's @-reference parser to lstat(cwd + content) when it encounters
|
|
398
|
-
// @ symbols in the markdown (e.g. "@@ -10,6 +10,14 @@" in JSON hunk examples),
|
|
399
|
-
// producing an ENAMETOOLONG crash. Using @file keeps the path short.
|
|
419
|
+
} else if (agent === 'antigravity') {
|
|
400
420
|
const tag = `overlord-${ticketId.slice(-8)}-${Date.now()}`;
|
|
401
421
|
const contextFile = path.join(os.tmpdir(), `${tag}-ctx.md`);
|
|
402
422
|
fs.writeFileSync(contextFile, context, 'utf-8');
|
|
403
423
|
setTimeout(() => { try { fs.unlinkSync(contextFile); } catch { /* already gone */ } }, 30 * 60_000).unref();
|
|
404
424
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
{ stdio: 'inherit', env: childEnv }
|
|
418
|
-
);
|
|
419
|
-
}
|
|
425
|
+
const agySessionId =
|
|
426
|
+
process.env.AGY_SESSION_ID?.trim() ?? process.env.GEMINI_SESSION_ID?.trim();
|
|
427
|
+
execFileSync(
|
|
428
|
+
'agy',
|
|
429
|
+
buildAgyLaunchArgv({
|
|
430
|
+
contextFile,
|
|
431
|
+
mode,
|
|
432
|
+
sessionId: agySessionId,
|
|
433
|
+
extraArgs
|
|
434
|
+
}),
|
|
435
|
+
{ stdio: 'inherit', env: childEnv }
|
|
436
|
+
);
|
|
420
437
|
}
|
|
421
438
|
} catch (error) {
|
|
422
439
|
const isResume = mode === 'resume';
|
|
@@ -429,7 +446,9 @@ async function runAgent(agent, mode = 'run', options = {}) {
|
|
|
429
446
|
? `No prior OpenCode session was found. Start one with \`ovld launch opencode --ticket-id <ticket_id>\` first.`
|
|
430
447
|
: agent === 'pi'
|
|
431
448
|
? `No prior Pi session was found. Start one with \`ovld launch pi --ticket-id <ticket_id>\` first.`
|
|
432
|
-
: ''
|
|
449
|
+
: agent === 'antigravity'
|
|
450
|
+
? `No prior Antigravity session was found. Start one with \`ovld launch antigravity --ticket-id <ticket_id>\` first.`
|
|
451
|
+
: '';
|
|
433
452
|
const message = error instanceof Error ? error.message : String(error);
|
|
434
453
|
|
|
435
454
|
if (isResume && noSessionHint) {
|
package/bin/_cli/new-ticket.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import readline from 'node:readline';
|
|
4
4
|
import { stdin as input, stdout as output } from 'node:process';
|
|
5
|
+
import fs from 'node:fs';
|
|
5
6
|
|
|
6
7
|
import { buildAuthHeaders, resolveAuth } from './credentials.mjs';
|
|
7
8
|
import { runLauncherCommand } from './launcher.mjs';
|
|
@@ -10,7 +11,7 @@ const PROMPT_AGENT_IDENTIFIERS = {
|
|
|
10
11
|
claude: 'claude-code',
|
|
11
12
|
codex: 'codex',
|
|
12
13
|
cursor: 'cursor',
|
|
13
|
-
|
|
14
|
+
antigravity: 'antigravity',
|
|
14
15
|
opencode: 'opencode'
|
|
15
16
|
};
|
|
16
17
|
const PROMPT_AGENTS = Object.keys(PROMPT_AGENT_IDENTIFIERS);
|
|
@@ -47,10 +48,10 @@ function parseFlags(args) {
|
|
|
47
48
|
|
|
48
49
|
function buildUsage(commandName) {
|
|
49
50
|
if (commandName === 'prompt') {
|
|
50
|
-
return 'Usage: ovld prompt "
|
|
51
|
+
return 'Usage: ovld prompt --objectives-json \'[{"objective":"..."}]\' [--objectives-file <path>] [--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
|
}
|
|
52
53
|
|
|
53
|
-
return 'Usage: ovld create "
|
|
54
|
+
return 'Usage: ovld create --objectives-json \'[{"objective":"..."}]\' [--objectives-file <path>] [--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
|
}
|
|
55
56
|
|
|
56
57
|
function ensureObjective(commandName, objective) {
|
|
@@ -61,6 +62,79 @@ function ensureObjective(commandName, objective) {
|
|
|
61
62
|
process.exit(1);
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
function parseJsonFlag(flagName, rawValue) {
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(String(rawValue));
|
|
68
|
+
} catch (err) {
|
|
69
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
70
|
+
throw new Error(`${flagName} must be valid JSON: ${detail}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function readJsonFile(filePath, label) {
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
77
|
+
} catch (err) {
|
|
78
|
+
throw new Error(
|
|
79
|
+
`${label}: could not read or parse "${filePath}": ${
|
|
80
|
+
err instanceof Error ? err.message : String(err)
|
|
81
|
+
}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function readTextFromStdin(label) {
|
|
87
|
+
const chunks = [];
|
|
88
|
+
try {
|
|
89
|
+
for await (const chunk of process.stdin) {
|
|
90
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`${label}: could not read stdin: ${err instanceof Error ? err.message : String(err)}`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function readJsonFileOrStdin(filePath, label) {
|
|
101
|
+
if (filePath !== '-') return readJsonFile(filePath, label);
|
|
102
|
+
try {
|
|
103
|
+
return JSON.parse(await readTextFromStdin(label));
|
|
104
|
+
} catch (err) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`${label}: could not parse stdin: ${err instanceof Error ? err.message : String(err)}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function resolveObjectiveInput(flags) {
|
|
112
|
+
if (flags['objectives-json'] && flags['objectives-file']) {
|
|
113
|
+
throw new Error('Use either --objectives-json or --objectives-file, not both');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (flags['objectives-json']) {
|
|
117
|
+
const objectives = parseJsonFlag('--objectives-json', flags['objectives-json']);
|
|
118
|
+
if (!Array.isArray(objectives) || objectives.length === 0) {
|
|
119
|
+
throw new Error('--objectives-json must be a non-empty JSON array');
|
|
120
|
+
}
|
|
121
|
+
return { objectives };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (flags['objectives-file']) {
|
|
125
|
+
const objectives = await readJsonFileOrStdin(
|
|
126
|
+
String(flags['objectives-file']),
|
|
127
|
+
'--objectives-file'
|
|
128
|
+
);
|
|
129
|
+
if (!Array.isArray(objectives) || objectives.length === 0) {
|
|
130
|
+
throw new Error('--objectives-file must contain a non-empty JSON array');
|
|
131
|
+
}
|
|
132
|
+
return { objectives };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
throw new Error('Provide --objectives-json or --objectives-file');
|
|
136
|
+
}
|
|
137
|
+
|
|
64
138
|
export function parseNumberedSelection(rawValue, count) {
|
|
65
139
|
const trimmed = rawValue.trim();
|
|
66
140
|
if (!trimmed) return null;
|
|
@@ -220,9 +294,8 @@ export function resolveTicketCreationDelegate(flags = {}, selectedAgent = null,
|
|
|
220
294
|
}
|
|
221
295
|
|
|
222
296
|
async function runTicketCreationFlow(args, { commandName, launchAgent }) {
|
|
223
|
-
const { flags
|
|
224
|
-
const
|
|
225
|
-
ensureObjective(commandName, objective);
|
|
297
|
+
const { flags } = parseFlags(args);
|
|
298
|
+
const objectiveInput = await resolveObjectiveInput(flags);
|
|
226
299
|
|
|
227
300
|
const { platformUrl, bearerToken, localSecret, organizationId } = await resolveAuth();
|
|
228
301
|
const projects = await fetchProjects(platformUrl, bearerToken, localSecret, organizationId);
|
|
@@ -254,7 +327,7 @@ async function runTicketCreationFlow(args, { commandName, launchAgent }) {
|
|
|
254
327
|
const ticketDelegate = resolveTicketCreationDelegate(flags, selectedAgent, modelIdentifier);
|
|
255
328
|
|
|
256
329
|
const ticket = await createTicket(platformUrl, bearerToken, localSecret, organizationId, {
|
|
257
|
-
|
|
330
|
+
...objectiveInput,
|
|
258
331
|
title: String(flags.title ?? ''),
|
|
259
332
|
acceptanceCriteria: String(flags['acceptance-criteria'] ?? ''),
|
|
260
333
|
availableTools: String(flags['available-tools'] ?? ''),
|
|
@@ -280,8 +353,8 @@ export async function runCreateCommand(args) {
|
|
|
280
353
|
Creates a ticket after interactive numbered project selection.
|
|
281
354
|
|
|
282
355
|
Examples:
|
|
283
|
-
ovld create "Implement login page"
|
|
284
|
-
ovld create "Fix sync bug" --project-id <project-id>
|
|
356
|
+
ovld create --objectives-json '[{"objective":"Implement login page"}]'
|
|
357
|
+
ovld create --objectives-json '[{"objective":"Fix sync bug"}]' --project-id <project-id>
|
|
285
358
|
`);
|
|
286
359
|
return;
|
|
287
360
|
}
|
|
@@ -296,8 +369,8 @@ export async function runPromptCommand(args) {
|
|
|
296
369
|
Creates a ticket after interactive numbered project selection, then lets you pick an agent by number and launches it on the new ticket.
|
|
297
370
|
|
|
298
371
|
Examples:
|
|
299
|
-
ovld prompt "Implement login page"
|
|
300
|
-
ovld prompt "Investigate flaky tests" --agent codex --model gpt-5.4
|
|
372
|
+
ovld prompt --objectives-json '[{"objective":"Implement login page"}]'
|
|
373
|
+
ovld prompt --objectives-json '[{"objective":"Investigate flaky tests"}]' --agent codex --model gpt-5.4
|
|
301
374
|
`);
|
|
302
375
|
return;
|
|
303
376
|
}
|
package/bin/_cli/protocol.mjs
CHANGED
|
@@ -331,6 +331,44 @@ async function readJsonFileOrStdin(filePath, label) {
|
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
async function resolveObjectivesInput(flags, { payload = null, requireSingleObjective = true } = {}) {
|
|
335
|
+
if (flags['objectives-json'] && flags['objectives-file']) {
|
|
336
|
+
throw new Error('Use either --objectives-json or --objectives-file, not both');
|
|
337
|
+
}
|
|
338
|
+
if (payload && (flags['objectives-json'] || flags['objectives-file'])) {
|
|
339
|
+
throw new Error('Use either payload input or --objectives-json/--objectives-file, not both');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (payload?.objectives !== undefined) {
|
|
343
|
+
if (!Array.isArray(payload.objectives) || payload.objectives.length === 0) {
|
|
344
|
+
throw new Error('payload objectives must be a non-empty array');
|
|
345
|
+
}
|
|
346
|
+
return { objectives: payload.objectives };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (flags['objectives-file']) {
|
|
350
|
+
const objectives = await readJsonFileOrStdin(String(flags['objectives-file']), '--objectives-file');
|
|
351
|
+
if (!Array.isArray(objectives) || objectives.length === 0) {
|
|
352
|
+
throw new Error('--objectives-file must contain a non-empty JSON array');
|
|
353
|
+
}
|
|
354
|
+
return { objectives };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (flags['objectives-json']) {
|
|
358
|
+
const objectives = parseJsonFlag('--objectives-json', flags['objectives-json']);
|
|
359
|
+
if (!Array.isArray(objectives) || objectives.length === 0) {
|
|
360
|
+
throw new Error('--objectives-json must be a non-empty JSON array');
|
|
361
|
+
}
|
|
362
|
+
return { objectives };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (requireSingleObjective) {
|
|
366
|
+
throw new Error('Provide --objectives-json or --objectives-file');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
throw new Error('Provide --objectives-json or --objectives-file');
|
|
370
|
+
}
|
|
371
|
+
|
|
334
372
|
// ---------------------------------------------------------------------------
|
|
335
373
|
// changeRationales helper
|
|
336
374
|
// ---------------------------------------------------------------------------
|
|
@@ -1886,7 +1924,7 @@ async function protocolRevert(args) {
|
|
|
1886
1924
|
|
|
1887
1925
|
async function protocolPrompt(args) {
|
|
1888
1926
|
const flags = parseFlags(args);
|
|
1889
|
-
const
|
|
1927
|
+
const objectiveInput = await resolveObjectivesInput(flags);
|
|
1890
1928
|
const parentTicketId =
|
|
1891
1929
|
flags['parent-ticket-id'] !== undefined
|
|
1892
1930
|
? String(flags['parent-ticket-id'] ?? process.env.TICKET_ID ?? '')
|
|
@@ -1905,7 +1943,7 @@ async function protocolPrompt(args) {
|
|
|
1905
1943
|
flags['working-directory'] ?? (!flags['project-id'] && !personal ? process.cwd() : undefined);
|
|
1906
1944
|
|
|
1907
1945
|
const body = {
|
|
1908
|
-
|
|
1946
|
+
...objectiveInput,
|
|
1909
1947
|
agentIdentifier,
|
|
1910
1948
|
connectionMethod: String(flags.method ?? 'cli'),
|
|
1911
1949
|
metadata: await resolveProtocolMetadata(flags, { cwd: process.cwd() }),
|
|
@@ -1983,7 +2021,7 @@ async function protocolDiscussObjective(args) {
|
|
|
1983
2021
|
async function protocolCreateTicket(args) {
|
|
1984
2022
|
const flags = parseFlags(args);
|
|
1985
2023
|
const { sessionKey, ticketId } = resolveSessionFlags(flags);
|
|
1986
|
-
const
|
|
2024
|
+
const objectiveInput = await resolveObjectivesInput(flags);
|
|
1987
2025
|
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1988
2026
|
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
1989
2027
|
const timeoutMs = resolveTimeout(flags);
|
|
@@ -1997,7 +2035,7 @@ async function protocolCreateTicket(args) {
|
|
|
1997
2035
|
const body = {
|
|
1998
2036
|
sessionKey,
|
|
1999
2037
|
ticketId,
|
|
2000
|
-
|
|
2038
|
+
...objectiveInput,
|
|
2001
2039
|
...(flags.title ? { title: String(flags.title) } : {}),
|
|
2002
2040
|
...(flags.priority ? { priority: String(flags.priority) } : {}),
|
|
2003
2041
|
...(flags['acceptance-criteria']
|
|
@@ -2034,7 +2072,7 @@ async function protocolCreateTicket(args) {
|
|
|
2034
2072
|
: undefined;
|
|
2035
2073
|
|
|
2036
2074
|
const standaloneBody = {
|
|
2037
|
-
|
|
2075
|
+
...objectiveInput,
|
|
2038
2076
|
...(flags.personal ? { personal: true } : {}),
|
|
2039
2077
|
...(!flags.personal && flags['project-id'] ? { projectId: String(flags['project-id']) } : {}),
|
|
2040
2078
|
...(standaloneWorkingDirectory ? { workingDirectory: standaloneWorkingDirectory } : {}),
|
|
@@ -2079,21 +2117,11 @@ async function protocolRecordWork(args) {
|
|
|
2079
2117
|
if (payload && (flags.summary || flags['summary-file'])) {
|
|
2080
2118
|
throw new Error('Use either payload input or --summary/--summary-file, not both');
|
|
2081
2119
|
}
|
|
2082
|
-
if (payload && (flags
|
|
2083
|
-
throw new Error('Use either payload input or --
|
|
2120
|
+
if (payload && (flags['objectives-json'] || flags['objectives-file'])) {
|
|
2121
|
+
throw new Error('Use either payload input or --objectives-json/--objectives-file, not both');
|
|
2084
2122
|
}
|
|
2085
2123
|
|
|
2086
|
-
const
|
|
2087
|
-
? (() => {
|
|
2088
|
-
const value = payload.objective;
|
|
2089
|
-
if (typeof value !== 'string' || !value.trim()) {
|
|
2090
|
-
throw new Error('payload input must include a non-empty objective');
|
|
2091
|
-
}
|
|
2092
|
-
return value;
|
|
2093
|
-
})()
|
|
2094
|
-
: flags['objective-file']
|
|
2095
|
-
? await readTextFileOrStdin(String(flags['objective-file']), '--objective-file')
|
|
2096
|
-
: requireFlag(flags, 'objective', undefined);
|
|
2124
|
+
const objectiveInput = await resolveObjectivesInput(flags, { payload });
|
|
2097
2125
|
|
|
2098
2126
|
const summary = payload
|
|
2099
2127
|
? (() => {
|
|
@@ -2134,7 +2162,7 @@ async function protocolRecordWork(args) {
|
|
|
2134
2162
|
const modelIdentifier = resolveProtocolModelIdentifier(flags);
|
|
2135
2163
|
|
|
2136
2164
|
const body = {
|
|
2137
|
-
|
|
2165
|
+
...objectiveInput,
|
|
2138
2166
|
summary,
|
|
2139
2167
|
artifacts,
|
|
2140
2168
|
changeRationales,
|
|
@@ -2174,6 +2202,36 @@ async function protocolRecordWork(args) {
|
|
|
2174
2202
|
}
|
|
2175
2203
|
}
|
|
2176
2204
|
|
|
2205
|
+
// ---------------------------------------------------------------------------
|
|
2206
|
+
// add-objectives (append ordered objectives to an existing ticket)
|
|
2207
|
+
// ---------------------------------------------------------------------------
|
|
2208
|
+
|
|
2209
|
+
async function protocolAddObjectives(args) {
|
|
2210
|
+
const flags = parseFlags(args);
|
|
2211
|
+
const ticketId = requireFlag(flags, 'ticket-id', process.env.TICKET_ID);
|
|
2212
|
+
const objectiveInput = await resolveObjectivesInput(flags, { requireSingleObjective: false });
|
|
2213
|
+
if (!objectiveInput.objectives) {
|
|
2214
|
+
throw new Error('add-objectives requires --objectives-json or --objectives-file');
|
|
2215
|
+
}
|
|
2216
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
2217
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
2218
|
+
const timeoutMs = resolveTimeout(flags);
|
|
2219
|
+
|
|
2220
|
+
const data = await apiPost(
|
|
2221
|
+
platformUrl,
|
|
2222
|
+
bearerToken,
|
|
2223
|
+
localSecret,
|
|
2224
|
+
organizationId,
|
|
2225
|
+
'/api/protocol/add-objectives',
|
|
2226
|
+
{
|
|
2227
|
+
ticketId,
|
|
2228
|
+
objectives: objectiveInput.objectives
|
|
2229
|
+
},
|
|
2230
|
+
timeoutMs
|
|
2231
|
+
);
|
|
2232
|
+
console.log(JSON.stringify(data, null, 2));
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2177
2235
|
// ---------------------------------------------------------------------------
|
|
2178
2236
|
// search-tickets (find tickets by query/status/project/created_by/dates)
|
|
2179
2237
|
// ---------------------------------------------------------------------------
|
|
@@ -2283,6 +2341,7 @@ Subcommands:
|
|
|
2283
2341
|
revert Restore the local working tree to an objective checkpoint
|
|
2284
2342
|
search-tickets Find tickets by keyword, status, project, creator, or update date
|
|
2285
2343
|
discuss-objective Mark a draft objective as submitted (ready for review/execution)
|
|
2344
|
+
add-objectives Append ordered objectives to an existing ticket
|
|
2286
2345
|
create Create a draft ticket without attaching (follow-up or standalone)
|
|
2287
2346
|
prompt Create a ticket and attach to it immediately
|
|
2288
2347
|
record-work Record completed-from-chat work as a ticket in review + feed post (no attach)
|
|
@@ -2518,6 +2577,7 @@ prompt:
|
|
|
2518
2577
|
current working directory (matching against the caller's project_user.local_working_directory).
|
|
2519
2578
|
Required:
|
|
2520
2579
|
--objective <text>
|
|
2580
|
+
or: --objectives-json <json> / --objectives-file <path|-> with [{ "objective": "...", "title": "...", "autoAdvance": true }]
|
|
2521
2581
|
Optional:
|
|
2522
2582
|
--title <text>
|
|
2523
2583
|
--priority <level> low | medium | high | urgent
|
|
@@ -2554,6 +2614,7 @@ create:
|
|
|
2554
2614
|
If session flags are omitted, resolves project by working directory and creates a standalone draft.
|
|
2555
2615
|
Required:
|
|
2556
2616
|
--objective <text>
|
|
2617
|
+
or: --objectives-json <json> / --objectives-file <path|-> with [{ "objective": "...", "title": "...", "autoAdvance": true }]
|
|
2557
2618
|
Optional:
|
|
2558
2619
|
--session-key <key>
|
|
2559
2620
|
--ticket-id <ticket_id>
|
|
@@ -2576,6 +2637,18 @@ create:
|
|
|
2576
2637
|
Use create for future work. If the work is already complete and only needs to be recorded,
|
|
2577
2638
|
use \`record-work\` instead.
|
|
2578
2639
|
|
|
2640
|
+
add-objectives:
|
|
2641
|
+
Purpose:
|
|
2642
|
+
Append ordered objectives to an existing ticket. Index 0 is the first newly
|
|
2643
|
+
added objective to execute; later indexes queue after it.
|
|
2644
|
+
Required:
|
|
2645
|
+
--ticket-id <ticket_id>
|
|
2646
|
+
--objectives-json <json> or --objectives-file <path|->
|
|
2647
|
+
Notes:
|
|
2648
|
+
Use this when each prompt is a sequential step toward the same feature or
|
|
2649
|
+
goal. Create multiple tickets instead when prompts describe different
|
|
2650
|
+
features or goals.
|
|
2651
|
+
|
|
2579
2652
|
record-work:
|
|
2580
2653
|
Purpose:
|
|
2581
2654
|
Record work the agent already completed in a chat as a ticket in \`review\` status
|
|
@@ -2584,6 +2657,7 @@ record-work:
|
|
|
2584
2657
|
just did" flows. Do NOT use this for in-progress work; use \`prompt\` for that.
|
|
2585
2658
|
Required:
|
|
2586
2659
|
--objective <text> or --objective-file <path|-> What was asked / what was done
|
|
2660
|
+
or: --objectives-json <json> / --objectives-file <path|->
|
|
2587
2661
|
--summary <text> or --summary-file <path|-> Narrative for feed post + reviewer
|
|
2588
2662
|
or: --payload-json <json>
|
|
2589
2663
|
or: --payload-file <path|-> containing { objective, summary, artifacts, changeRationales }
|
|
@@ -2718,7 +2792,9 @@ Examples:
|
|
|
2718
2792
|
ovld protocol search-tickets --query "auth refactor" --status next-up,execute --limit 10
|
|
2719
2793
|
ovld protocol discuss-objective --ticket-id 1:899
|
|
2720
2794
|
ovld protocol discuss-objective --ticket-id 1:899 --objective-id <objective-uuid>
|
|
2795
|
+
ovld protocol add-objectives --ticket-id 1:899 --objectives-json '[{"objective":"Step one"},{"objective":"Step two"}]'
|
|
2721
2796
|
ovld protocol create --agent codex --objective "Capture follow-up work from this repo"
|
|
2797
|
+
ovld protocol create --agent codex --objectives-json '[{"objective":"Step one"},{"objective":"Step two"}]'
|
|
2722
2798
|
ovld protocol create --agent codex --session-key <key> --ticket-id <ticket_id> --objective "Capture follow-up work"
|
|
2723
2799
|
ovld protocol prompt --agent codex --objective "Implement user auth" --priority high
|
|
2724
2800
|
ovld protocol update --session-key <key> --ticket-id <ticket_id> --summary "Did X" --phase execute
|
|
@@ -2787,6 +2863,10 @@ EOF
|
|
|
2787
2863
|
await protocolDiscussObjective(args);
|
|
2788
2864
|
return;
|
|
2789
2865
|
}
|
|
2866
|
+
if (subcommand === 'add-objectives') {
|
|
2867
|
+
await protocolAddObjectives(args);
|
|
2868
|
+
return;
|
|
2869
|
+
}
|
|
2790
2870
|
if (subcommand === 'create' || subcommand === 'create-ticket') {
|
|
2791
2871
|
await protocolCreateTicket(args);
|
|
2792
2872
|
return;
|