overlord-cli 0.2605121205.0 → 0.2605131312.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 +2 -0
- package/bin/_cli/protocol.mjs +71 -9
- package/bin/_cli/setup.mjs +2 -2
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/plugins/claude/scripts/user-prompt-submit-hook.sh +75 -8
- package/plugins/claude/skills/overlord-ticket/SKILL.md +3 -3
- package/plugins/cursor/hooks/overlord-user-prompt-submit.sh +12 -7
- package/plugins/cursor/skills/overlord-ticket/SKILL.md +2 -2
- package/plugins/overlord/.codex-plugin/plugin.json +1 -1
- package/plugins/overlord/scripts/user-prompt-submit-hook.sh +36 -5
- package/plugins/overlord/skills/overlord-ticket/SKILL.md +3 -3
package/README.md
CHANGED
|
@@ -130,6 +130,8 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
|
|
|
130
130
|
- `attachment-download-url` - get a signed download URL for an existing objective attachment
|
|
131
131
|
- `attachment-upload-file` - prepare, upload, and finalize a local objective attachment in one command
|
|
132
132
|
|
|
133
|
+
`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|->`.
|
|
134
|
+
|
|
133
135
|
## License
|
|
134
136
|
|
|
135
137
|
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.
|
package/bin/_cli/protocol.mjs
CHANGED
|
@@ -1122,14 +1122,28 @@ async function protocolDeliver(args) {
|
|
|
1122
1122
|
const { sessionKey, ticketId } = resolveSessionFlags(flags);
|
|
1123
1123
|
if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
|
|
1124
1124
|
if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
|
|
1125
|
+
if (flags['payload-file'] && flags['payload-json']) {
|
|
1126
|
+
throw new Error('Use either --payload-file or --payload-json, not both');
|
|
1127
|
+
}
|
|
1125
1128
|
const deliverPayload = flags['payload-file']
|
|
1126
1129
|
? await readJsonFileOrStdin(String(flags['payload-file']), '--payload-file')
|
|
1127
|
-
:
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1130
|
+
: flags['payload-json']
|
|
1131
|
+
? parseJsonFlag('--payload-json', flags['payload-json'])
|
|
1132
|
+
: null;
|
|
1133
|
+
if (deliverPayload && (flags.summary || flags['summary-file'])) {
|
|
1134
|
+
throw new Error('Use either payload input or --summary/--summary-file, not both');
|
|
1135
|
+
}
|
|
1136
|
+
const summary = deliverPayload
|
|
1137
|
+
? (() => {
|
|
1138
|
+
const value = deliverPayload.summary;
|
|
1139
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
1140
|
+
throw new Error('payload input must include a non-empty summary');
|
|
1141
|
+
}
|
|
1142
|
+
return value;
|
|
1143
|
+
})()
|
|
1144
|
+
: flags['summary-file']
|
|
1131
1145
|
? await readTextFileOrStdin(String(flags['summary-file']), '--summary-file')
|
|
1132
|
-
: requireFlag(flags, 'summary', undefined)
|
|
1146
|
+
: requireFlag(flags, 'summary', undefined);
|
|
1133
1147
|
|
|
1134
1148
|
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1135
1149
|
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
@@ -1137,10 +1151,10 @@ async function protocolDeliver(args) {
|
|
|
1137
1151
|
|
|
1138
1152
|
let artifacts = deliverPayload?.artifacts ?? [];
|
|
1139
1153
|
if (deliverPayload && flags['artifacts-file']) {
|
|
1140
|
-
throw new Error('Use either
|
|
1154
|
+
throw new Error('Use either payload input or --artifacts-file, not both');
|
|
1141
1155
|
}
|
|
1142
1156
|
if (deliverPayload && flags['artifacts-json']) {
|
|
1143
|
-
throw new Error('Use either
|
|
1157
|
+
throw new Error('Use either payload input or --artifacts-json, not both');
|
|
1144
1158
|
}
|
|
1145
1159
|
if (flags['artifacts-file']) {
|
|
1146
1160
|
artifacts = await readJsonFileOrStdin(String(flags['artifacts-file']), '--artifacts-file');
|
|
@@ -1149,7 +1163,7 @@ async function protocolDeliver(args) {
|
|
|
1149
1163
|
}
|
|
1150
1164
|
|
|
1151
1165
|
if (deliverPayload && (flags['change-rationales-file'] || flags['change-rationales-json'])) {
|
|
1152
|
-
throw new Error('Use either
|
|
1166
|
+
throw new Error('Use either payload input or change-rationale flags, not both');
|
|
1153
1167
|
}
|
|
1154
1168
|
|
|
1155
1169
|
const changeRationales =
|
|
@@ -1556,6 +1570,34 @@ async function protocolPrompt(args) {
|
|
|
1556
1570
|
}
|
|
1557
1571
|
}
|
|
1558
1572
|
|
|
1573
|
+
// ---------------------------------------------------------------------------
|
|
1574
|
+
// discuss-objective (mark a draft objective as submitted)
|
|
1575
|
+
// ---------------------------------------------------------------------------
|
|
1576
|
+
|
|
1577
|
+
async function protocolDiscussObjective(args) {
|
|
1578
|
+
const flags = parseFlags(args);
|
|
1579
|
+
const ticketId = requireFlag(flags, 'ticket-id', process.env.TICKET_ID);
|
|
1580
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1581
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
1582
|
+
const timeoutMs = resolveTimeout(flags);
|
|
1583
|
+
|
|
1584
|
+
const body = {
|
|
1585
|
+
ticketId,
|
|
1586
|
+
...(flags['objective-id'] ? { objectiveId: String(flags['objective-id']) } : {})
|
|
1587
|
+
};
|
|
1588
|
+
|
|
1589
|
+
const data = await apiPost(
|
|
1590
|
+
platformUrl,
|
|
1591
|
+
bearerToken,
|
|
1592
|
+
localSecret,
|
|
1593
|
+
organizationId,
|
|
1594
|
+
'/api/protocol/discuss-objective',
|
|
1595
|
+
body,
|
|
1596
|
+
timeoutMs
|
|
1597
|
+
);
|
|
1598
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1559
1601
|
// ---------------------------------------------------------------------------
|
|
1560
1602
|
// create (create follow-up ticket draft only)
|
|
1561
1603
|
// ---------------------------------------------------------------------------
|
|
@@ -1747,6 +1789,7 @@ Subcommands:
|
|
|
1747
1789
|
connect Start a lightweight session without full context
|
|
1748
1790
|
load-context Read ticket context without creating a session
|
|
1749
1791
|
search-tickets Find tickets by keyword, status, project, creator, or update date
|
|
1792
|
+
discuss-objective Mark a draft objective as submitted (ready for review/execution)
|
|
1750
1793
|
create Create a draft ticket without attaching (follow-up or standalone)
|
|
1751
1794
|
prompt Create a ticket and attach to it immediately
|
|
1752
1795
|
update Post progress, activity events, and optional change rationales
|
|
@@ -1943,6 +1986,7 @@ deliver:
|
|
|
1943
1986
|
--session-key <key>
|
|
1944
1987
|
--ticket-id <ticket_id>
|
|
1945
1988
|
--summary <text> or --summary-file <path|->
|
|
1989
|
+
or: --payload-json <json>
|
|
1946
1990
|
or: --payload-file <path|-> containing { summary, artifacts, changeRationales }
|
|
1947
1991
|
Optional:
|
|
1948
1992
|
--artifacts-json <json>
|
|
@@ -1951,9 +1995,10 @@ deliver:
|
|
|
1951
1995
|
--change-rationales-file <path|->
|
|
1952
1996
|
--skip-file-change-check Bypass local git vs changeRationales validation
|
|
1953
1997
|
Notes:
|
|
1998
|
+
Use --payload-json when the full delivery JSON fits comfortably inline.
|
|
1954
1999
|
Use --payload-file - to read the full delivery JSON from stdin without creating a scratch file.
|
|
1955
2000
|
Use --summary-file - to pipe just the summary text via stdin (avoids shell special-char issues).
|
|
1956
|
-
Do not combine --payload-file with --artifacts-json/--artifacts-file or change-rationale flags.
|
|
2001
|
+
Do not combine --payload-json/--payload-file with --summary/--summary-file, --artifacts-json/--artifacts-file, or change-rationale flags.
|
|
1957
2002
|
In a git workspace, deliver validates that changed files are represented by changeRationales unless skipped.
|
|
1958
2003
|
|
|
1959
2004
|
prompt:
|
|
@@ -1982,6 +2027,16 @@ prompt:
|
|
|
1982
2027
|
Returns:
|
|
1983
2028
|
New ticket/session JSON plus SESSION_KEY and TICKET_ID on stderr when available
|
|
1984
2029
|
|
|
2030
|
+
discuss-objective:
|
|
2031
|
+
Purpose:
|
|
2032
|
+
Mark the latest draft objective on a ticket as "submitted", indicating the objective
|
|
2033
|
+
has been discussed and is ready for review or execution. Does NOT start execution —
|
|
2034
|
+
use \`attach\` to begin execution.
|
|
2035
|
+
Required:
|
|
2036
|
+
--ticket-id <ticket_id>
|
|
2037
|
+
Optional:
|
|
2038
|
+
--objective-id <id> Target a specific draft objective by UUID
|
|
2039
|
+
|
|
1985
2040
|
create:
|
|
1986
2041
|
Purpose:
|
|
1987
2042
|
Create a draft ticket without attaching to it.
|
|
@@ -2073,6 +2128,8 @@ Examples:
|
|
|
2073
2128
|
ovld protocol connect --ticket-id 1:899
|
|
2074
2129
|
ovld protocol load-context --ticket-id 1:899
|
|
2075
2130
|
ovld protocol search-tickets --query "auth refactor" --status next-up,execute --limit 10
|
|
2131
|
+
ovld protocol discuss-objective --ticket-id 1:899
|
|
2132
|
+
ovld protocol discuss-objective --ticket-id 1:899 --objective-id <objective-uuid>
|
|
2076
2133
|
ovld protocol create --agent codex --objective "Capture follow-up work from this repo"
|
|
2077
2134
|
ovld protocol create --agent codex --session-key <key> --ticket-id <ticket_id> --objective "Capture follow-up work"
|
|
2078
2135
|
ovld protocol prompt --agent codex --objective "Implement user auth" --priority high
|
|
@@ -2089,6 +2146,7 @@ Examples:
|
|
|
2089
2146
|
ovld protocol attachment-download-url --session-key <key> --ticket-id <ticket_id> --attachment-id <attachment-id>
|
|
2090
2147
|
ovld protocol deliver --session-key <key> --ticket-id <ticket_id> --summary "Done"
|
|
2091
2148
|
ovld protocol deliver --session-key <key> --ticket-id <ticket_id> --summary "Done" --artifacts-file ./artifacts.json
|
|
2149
|
+
ovld protocol deliver --session-key <key> --ticket-id <ticket_id> --payload-json '{"summary":"Done","artifacts":[{"type":"note","label":"Delivery","content":"..."}]}'
|
|
2092
2150
|
ovld protocol deliver --session-key <key> --ticket-id <ticket_id> --payload-file ./deliver.json
|
|
2093
2151
|
ovld protocol deliver --session-key <key> --ticket-id <ticket_id> --payload-file -
|
|
2094
2152
|
ovld protocol deliver --session-key <key> --ticket-id <ticket_id> --summary "Done" --skip-file-change-check
|
|
@@ -2121,6 +2179,10 @@ Examples:
|
|
|
2121
2179
|
await protocolSearchTickets(args);
|
|
2122
2180
|
return;
|
|
2123
2181
|
}
|
|
2182
|
+
if (subcommand === 'discuss-objective') {
|
|
2183
|
+
await protocolDiscussObjective(args);
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2124
2186
|
if (subcommand === 'create' || subcommand === 'create-ticket') {
|
|
2125
2187
|
await protocolCreateTicket(args);
|
|
2126
2188
|
return;
|
package/bin/_cli/setup.mjs
CHANGED
|
@@ -88,7 +88,7 @@ If you receive a prompt with a specified ticket ID, adhere to the following. If
|
|
|
88
88
|
--change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
|
|
89
89
|
\`\`\`
|
|
90
90
|
|
|
91
|
-
For larger or quote-sensitive deliveries, prefer a single JSON payload on stdin:
|
|
91
|
+
Use \`--payload-json\` when the full delivery object fits comfortably inline. For larger or quote-sensitive deliveries, prefer a single JSON payload on stdin:
|
|
92
92
|
\`\`\`bash
|
|
93
93
|
ovld protocol deliver --session-key <sessionKey> --ticket-id $TICKET_ID --payload-file -
|
|
94
94
|
\`\`\`
|
|
@@ -261,7 +261,7 @@ When done, deliver with artifacts and change rationales:
|
|
|
261
261
|
\`\`\`bash
|
|
262
262
|
ovld protocol deliver --session-key <sessionKey> --ticket-id $TICKET_ID --summary "Narrative: what you did, next steps." --artifacts-json '[{"type":"next_steps","label":"Next steps","content":"..."}]' --change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
|
|
263
263
|
\`\`\`
|
|
264
|
-
For larger delivery JSON, prefer \`--payload-file -\` with stdin so no scratch file needs to be created or removed. If you use a JSON file for delivery transport, keep it ephemeral scratch data outside the repository and remove it after the protocol call.
|
|
264
|
+
Use \`--payload-json\` for compact inline delivery objects. For larger delivery JSON, prefer \`--payload-file -\` with stdin so no scratch file needs to be created or removed. If you use a JSON file for delivery transport, keep it ephemeral scratch data outside the repository and remove it after the protocol call.
|
|
265
265
|
|
|
266
266
|
Rules:
|
|
267
267
|
- Always attach first and deliver last.
|
package/package.json
CHANGED
|
@@ -1,28 +1,95 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Overlord UserPromptSubmit hook — fires before Claude processes each user turn.
|
|
3
|
+
# Claude Code does not expose turn_number in the hook body; we persist the last
|
|
4
|
+
# posted turnIndex per session_id and send (last + 1). The first submit is
|
|
5
|
+
# turnIndex 0 (initial injected ticket/objective prompt), which
|
|
6
|
+
# POST /api/protocol/hook-event skips — same contract as the Cursor hook.
|
|
3
7
|
|
|
4
8
|
BODY=$(cat -)
|
|
5
9
|
OVERLORD_BASE_URL="${CLAUDE_PLUGIN_OPTION_OVERLORD_URL:-$OVERLORD_URL}"
|
|
6
10
|
OVERLORD_TOKEN="${CLAUDE_PLUGIN_OPTION_AGENT_TOKEN:-${CLAUDE_PLUGIN_OPTION_OVERLORD_ACCESS_TOKEN:-$OVERLORD_ACCESS_TOKEN}}"
|
|
7
11
|
OVERLORD_LOCAL_SECRET="${OVERLORD_LOCAL_SECRET:-}"
|
|
12
|
+
HOOK_NAME="claude"
|
|
13
|
+
LOG_DIR="${HOME:-}/.ovld/logs"
|
|
14
|
+
LOG_FILE="${LOG_DIR}/user-prompt-submit-hook.log"
|
|
8
15
|
|
|
9
|
-
|
|
10
|
-
|
|
16
|
+
log_hook() {
|
|
17
|
+
mkdir -p "$LOG_DIR" 2>/dev/null || return 0
|
|
18
|
+
printf '%s [%s] %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$HOOK_NAME" "$1" >>"$LOG_FILE" 2>/dev/null || true
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
PROMPT_LEN=$(printf '%s' "$BODY" | python3 -c "import json,sys; print(len((json.load(sys.stdin).get('prompt') or '').strip()))" 2>/dev/null || echo "0")
|
|
22
|
+
SESSION_ID=$(printf '%s' "$BODY" | python3 -c "import json,sys; print(json.load(sys.stdin).get('session_id') or 'unknown')" 2>/dev/null || echo "unknown")
|
|
23
|
+
log_hook "received submit session_id=$SESSION_ID prompt_len=$PROMPT_LEN ticket_present=$([ -n "${TICKET_ID:-}" ] && echo yes || echo no)"
|
|
24
|
+
|
|
25
|
+
if [ -z "$OVERLORD_BASE_URL" ] || [ -z "$OVERLORD_TOKEN" ] || [ -z "${TICKET_ID:-}" ]; then
|
|
26
|
+
log_hook "missing required env base_url=$([ -n "$OVERLORD_BASE_URL" ] && echo yes || echo no) token=$([ -n "$OVERLORD_TOKEN" ] && echo yes || echo no) ticket=$([ -n "${TICKET_ID:-}" ] && echo yes || echo no)"
|
|
11
27
|
exit 0
|
|
12
28
|
fi
|
|
13
29
|
|
|
14
30
|
if [ -n "$OVERLORD_BASE_URL" ] && [ -n "$OVERLORD_TOKEN" ] && [ -n "$TICKET_ID" ]; then
|
|
15
|
-
PAYLOAD=$(
|
|
16
|
-
|
|
17
|
-
|
|
31
|
+
PAYLOAD=$(
|
|
32
|
+
printf '%s' "$BODY" | python3 -c "
|
|
33
|
+
import hashlib
|
|
34
|
+
import json
|
|
35
|
+
import os
|
|
36
|
+
import sys
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
body = json.load(sys.stdin)
|
|
40
|
+
except Exception:
|
|
41
|
+
sys.exit(0)
|
|
42
|
+
|
|
43
|
+
text = (body.get('prompt') or '').strip()
|
|
44
|
+
if not text:
|
|
45
|
+
sys.exit(0)
|
|
46
|
+
|
|
47
|
+
sid = body.get('session_id') or 'unknown'
|
|
48
|
+
state_dir = os.path.join(os.path.expanduser('~'), '.ovld', 'claude-user-prompt-hook')
|
|
49
|
+
os.makedirs(state_dir, exist_ok=True)
|
|
50
|
+
path = os.path.join(state_dir, hashlib.sha256(sid.encode()).hexdigest())
|
|
51
|
+
last_posted = -1
|
|
52
|
+
try:
|
|
53
|
+
with open(path, encoding='utf-8') as handle:
|
|
54
|
+
raw = (handle.read() or '').strip()
|
|
55
|
+
if raw != '':
|
|
56
|
+
last_posted = int(raw)
|
|
57
|
+
except Exception:
|
|
58
|
+
last_posted = -1
|
|
59
|
+
|
|
60
|
+
turn_index = last_posted + 1
|
|
61
|
+
with open(path, 'w', encoding='utf-8') as handle:
|
|
62
|
+
handle.write(str(turn_index))
|
|
63
|
+
|
|
64
|
+
tid = os.environ.get('TICKET_ID', '')
|
|
65
|
+
print(json.dumps({'hookType': 'UserPromptSubmit', 'ticketId': tid, 'prompt': text, 'turnIndex': turn_index}))
|
|
66
|
+
" 2>/dev/null
|
|
67
|
+
)
|
|
68
|
+
if [ -z "$PAYLOAD" ]; then
|
|
69
|
+
log_hook "failed to build hook-event payload from submit body"
|
|
70
|
+
exit 0
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
PAYLOAD_TURN=$(printf '%s' "$PAYLOAD" | python3 -c "import json,sys; print(json.load(sys.stdin).get('turnIndex', 'unknown'))" 2>/dev/null || echo "unknown")
|
|
74
|
+
log_hook "built payload turn=$PAYLOAD_TURN posting hook-event"
|
|
75
|
+
(
|
|
76
|
+
RESPONSE_FILE=$(mktemp "${TMPDIR:-/tmp}/overlord-hook-event.XXXXXX")
|
|
77
|
+
HTTP_CODE=$(curl -sS -m 5 -o "$RESPONSE_FILE" -w "%{http_code}" \
|
|
18
78
|
-X POST "$OVERLORD_BASE_URL/api/protocol/hook-event" \
|
|
19
79
|
-H "Authorization: Bearer $OVERLORD_TOKEN" \
|
|
20
80
|
-H "X-Overlord-Local-Secret: $OVERLORD_LOCAL_SECRET" \
|
|
21
81
|
-H "Content-Type: application/json" \
|
|
22
|
-
-d "$PAYLOAD"
|
|
23
|
-
|
|
82
|
+
-d "$PAYLOAD")
|
|
83
|
+
CURL_EXIT=$?
|
|
84
|
+
RESPONSE_PREVIEW=$(python3 -c "from pathlib import Path; import sys; p=Path(sys.argv[1]); text=p.read_text(encoding='utf-8', errors='replace') if p.exists() else ''; text=' '.join(text.split()); print(text[:200])" "$RESPONSE_FILE" 2>/dev/null || echo '')
|
|
85
|
+
rm -f "$RESPONSE_FILE"
|
|
86
|
+
if [ "$CURL_EXIT" -eq 0 ]; then
|
|
87
|
+
log_hook "hook-event POST finished exit=0 http_status=$HTTP_CODE response_preview=$RESPONSE_PREVIEW"
|
|
88
|
+
else
|
|
89
|
+
log_hook "hook-event POST failed exit=$CURL_EXIT http_status=${HTTP_CODE:-none} response_preview=$RESPONSE_PREVIEW"
|
|
90
|
+
fi
|
|
91
|
+
) >/dev/null 2>&1 &
|
|
24
92
|
disown
|
|
25
|
-
fi
|
|
26
93
|
fi
|
|
27
94
|
|
|
28
95
|
exit 0
|
|
@@ -75,7 +75,7 @@ ovld protocol deliver --session-key <sessionKey> \
|
|
|
75
75
|
--change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
-
`ovld protocol deliver` automatically creates a local checkpoint before the API request when the workspace is JJ- or Git-managed; use `--skip-checkpoint` only when intentionally bypassing local provenance. For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed. If the summary contains special characters, use `--summary-file -` and pipe via a single-quoted heredoc (`<<'EOF'`) to prevent shell expansion. If you use `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that file as ephemeral scratch data outside the repository and remove it after delivery.
|
|
78
|
+
`ovld protocol deliver` automatically creates a local checkpoint before the API request when the workspace is JJ- or Git-managed; use `--skip-checkpoint` only when intentionally bypassing local provenance. Use `--payload-json` when the full delivery object fits comfortably inline. For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed. If the summary contains special characters, use `--summary-file -` and pipe via a single-quoted heredoc (`<<'EOF'`) to prevent shell expansion. If you use `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that file as ephemeral scratch data outside the repository and remove it after delivery.
|
|
79
79
|
|
|
80
80
|
## Mode 2: Asked From Chat To Use Overlord
|
|
81
81
|
|
|
@@ -99,7 +99,7 @@ Always include `changeRationales` when delivering. Optionally include them on up
|
|
|
99
99
|
|
|
100
100
|
Before delivering, make sure every meaningful git-tracked file change is represented in `changeRationales`; do not send `file_changes` as an artifact.
|
|
101
101
|
|
|
102
|
-
These are structured protocol payloads that Overlord stores as first-class rows in the `file_changes` table. Prefer inline JSON or the dedicated command below.
|
|
102
|
+
These are structured protocol payloads that Overlord stores as first-class rows in the `file_changes` table. Prefer inline JSON or the dedicated command below. Use `--payload-json` for compact full delivery payloads, or `--payload-file -` when the JSON is larger or quote-sensitive so summary, artifacts, and change rationales stay in one JSON document without creating a temporary file. Ordinary deliver artifacts should use `next_steps`, `test_results`, `migration`, `note`, `url`, or `decision`.
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
105
|
ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \
|
|
@@ -182,4 +182,4 @@ The `attach` and `load-context` responses already include `attachments` and `obj
|
|
|
182
182
|
- Do not add or commit changes unless the user explicitly asks you to commit.
|
|
183
183
|
- Delivery is the concluding step. After delivering, stop unless the user follows up or the ticket is reopened.
|
|
184
184
|
|
|
185
|
-
<!-- version: 0.4.
|
|
185
|
+
<!-- version: 0.4.9 -->
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Overlord Cursor beforeSubmitPrompt hook — posts user composer prompts to the ticket activity feed.
|
|
3
|
-
# Cursor does not expose Claude's turn_number; we
|
|
4
|
-
#
|
|
3
|
+
# Cursor does not expose Claude's turn_number; we persist the last posted turnIndex per conversation_id
|
|
4
|
+
# and send (last + 1). The first submit is turnIndex 0 (initial injected ticket/objective prompt), which
|
|
5
|
+
# POST /api/protocol/hook-event skips — same contract as Claude's turn_number 0.
|
|
5
6
|
|
|
6
7
|
BODY=$(cat -)
|
|
7
8
|
|
|
@@ -31,17 +32,21 @@ cid = body.get('conversation_id') or 'unknown'
|
|
|
31
32
|
state_dir = os.path.join(os.path.expanduser('~'), '.ovld', 'cursor-user-prompt-hook')
|
|
32
33
|
os.makedirs(state_dir, exist_ok=True)
|
|
33
34
|
path = os.path.join(state_dir, hashlib.sha256(cid.encode()).hexdigest())
|
|
35
|
+
last_posted = -1
|
|
34
36
|
try:
|
|
35
37
|
with open(path, encoding='utf-8') as handle:
|
|
36
|
-
|
|
38
|
+
raw = (handle.read() or '').strip()
|
|
39
|
+
if raw != '':
|
|
40
|
+
last_posted = int(raw)
|
|
37
41
|
except Exception:
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
last_posted = -1
|
|
43
|
+
|
|
44
|
+
turn_index = last_posted + 1
|
|
40
45
|
with open(path, 'w', encoding='utf-8') as handle:
|
|
41
|
-
handle.write(str(
|
|
46
|
+
handle.write(str(turn_index))
|
|
42
47
|
|
|
43
48
|
tid = os.environ.get('TICKET_ID', '')
|
|
44
|
-
print(json.dumps({'hookType': 'UserPromptSubmit', 'ticketId': tid, 'prompt': text, 'turnIndex':
|
|
49
|
+
print(json.dumps({'hookType': 'UserPromptSubmit', 'ticketId': tid, 'prompt': text, 'turnIndex': turn_index}))
|
|
45
50
|
" 2>/dev/null
|
|
46
51
|
)
|
|
47
52
|
if [ -n "$PAYLOAD" ]; then
|
|
@@ -61,7 +61,7 @@ ovld protocol deliver --session-key <sessionKey> \
|
|
|
61
61
|
--change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
`ovld protocol deliver` automatically creates a local checkpoint before the API request when the workspace is JJ- or Git-managed; use `--skip-checkpoint` only when intentionally bypassing local provenance. For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed.
|
|
64
|
+
`ovld protocol deliver` automatically creates a local checkpoint before the API request when the workspace is JJ- or Git-managed; use `--skip-checkpoint` only when intentionally bypassing local provenance. Use `--payload-json` when the full delivery object fits comfortably inline. For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed.
|
|
65
65
|
|
|
66
66
|
## Mode 2: Asked From Chat To Use Overlord
|
|
67
67
|
|
|
@@ -152,4 +152,4 @@ Objective attachment uploads also expose two-step variants — `attachment-prepa
|
|
|
152
152
|
- If you must run `ovld auth login`, always include `--organization-id <id>` — use the organization ID from the ticket prompt context to select the organization non-interactively and avoid a blocking TTY prompt.
|
|
153
153
|
- Delivery is the concluding step. After delivering, stop unless the user follows up or the ticket is reopened.
|
|
154
154
|
|
|
155
|
-
<!-- version: 0.4.
|
|
155
|
+
<!-- version: 0.4.9 -->
|
|
@@ -5,24 +5,55 @@ BODY=$(cat -)
|
|
|
5
5
|
OVERLORD_BASE_URL="${OVERLORD_URL:-}"
|
|
6
6
|
OVERLORD_TOKEN="${OVERLORD_ACCESS_TOKEN:-}"
|
|
7
7
|
OVERLORD_LOCAL_SECRET="${OVERLORD_LOCAL_SECRET:-}"
|
|
8
|
+
HOOK_NAME="codex"
|
|
9
|
+
LOG_DIR="${HOME:-}/.ovld/logs"
|
|
10
|
+
LOG_FILE="${LOG_DIR}/user-prompt-submit-hook.log"
|
|
11
|
+
|
|
12
|
+
log_hook() {
|
|
13
|
+
mkdir -p "$LOG_DIR" 2>/dev/null || return 0
|
|
14
|
+
printf '%s [%s] %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')" "$HOOK_NAME" "$1" >>"$LOG_FILE" 2>/dev/null || true
|
|
15
|
+
}
|
|
8
16
|
|
|
9
17
|
TURN=$(printf '%s' "$BODY" | python3 -c "import json,sys; print(json.load(sys.stdin).get('turn_number', 0))" 2>/dev/null || echo "0")
|
|
18
|
+
PROMPT_LEN=$(printf '%s' "$BODY" | python3 -c "import json,sys; print(len((json.load(sys.stdin).get('prompt') or '').strip()))" 2>/dev/null || echo "0")
|
|
19
|
+
log_hook "received submit turn=$TURN prompt_len=$PROMPT_LEN ticket_present=$([ -n "${TICKET_ID:-}" ] && echo yes || echo no)"
|
|
10
20
|
if [ "$TURN" = "0" ]; then
|
|
21
|
+
log_hook "skipping initial submit turn=0"
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
if [ -z "$OVERLORD_BASE_URL" ] || [ -z "$OVERLORD_TOKEN" ] || [ -z "${TICKET_ID:-}" ]; then
|
|
26
|
+
log_hook "missing required env base_url=$([ -n "$OVERLORD_BASE_URL" ] && echo yes || echo no) token=$([ -n "$OVERLORD_TOKEN" ] && echo yes || echo no) ticket=$([ -n "${TICKET_ID:-}" ] && echo yes || echo no)"
|
|
11
27
|
exit 0
|
|
12
28
|
fi
|
|
13
29
|
|
|
14
30
|
if [ -n "$OVERLORD_BASE_URL" ] && [ -n "$OVERLORD_TOKEN" ] && [ -n "$TICKET_ID" ]; then
|
|
15
31
|
PAYLOAD=$(printf '%s' "$BODY" | python3 -c "import json,sys; body=json.load(sys.stdin); print(json.dumps({'hookType': 'UserPromptSubmit', 'ticketId': __import__('os').environ['TICKET_ID'], 'prompt': body.get('prompt', ''), 'turnIndex': body.get('turn_number', 0)}))" 2>/dev/null || echo '')
|
|
16
|
-
if [ -
|
|
17
|
-
|
|
32
|
+
if [ -z "$PAYLOAD" ]; then
|
|
33
|
+
log_hook "failed to build hook-event payload from submit body"
|
|
34
|
+
exit 0
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
PAYLOAD_TURN=$(printf '%s' "$PAYLOAD" | python3 -c "import json,sys; print(json.load(sys.stdin).get('turnIndex', 'unknown'))" 2>/dev/null || echo "unknown")
|
|
38
|
+
log_hook "built payload turn=$PAYLOAD_TURN posting hook-event"
|
|
39
|
+
(
|
|
40
|
+
RESPONSE_FILE=$(mktemp "${TMPDIR:-/tmp}/overlord-hook-event.XXXXXX")
|
|
41
|
+
HTTP_CODE=$(curl -sS -m 5 -o "$RESPONSE_FILE" -w "%{http_code}" \
|
|
18
42
|
-X POST "$OVERLORD_BASE_URL/api/protocol/hook-event" \
|
|
19
43
|
-H "Authorization: Bearer $OVERLORD_TOKEN" \
|
|
20
44
|
-H "X-Overlord-Local-Secret: $OVERLORD_LOCAL_SECRET" \
|
|
21
45
|
-H "Content-Type: application/json" \
|
|
22
|
-
-d "$PAYLOAD"
|
|
23
|
-
|
|
46
|
+
-d "$PAYLOAD")
|
|
47
|
+
CURL_EXIT=$?
|
|
48
|
+
RESPONSE_PREVIEW=$(python3 -c "from pathlib import Path; import sys; p=Path(sys.argv[1]); text=p.read_text(encoding='utf-8', errors='replace') if p.exists() else ''; text=' '.join(text.split()); print(text[:200])" "$RESPONSE_FILE" 2>/dev/null || echo '')
|
|
49
|
+
rm -f "$RESPONSE_FILE"
|
|
50
|
+
if [ "$CURL_EXIT" -eq 0 ]; then
|
|
51
|
+
log_hook "hook-event POST finished exit=0 http_status=$HTTP_CODE response_preview=$RESPONSE_PREVIEW"
|
|
52
|
+
else
|
|
53
|
+
log_hook "hook-event POST failed exit=$CURL_EXIT http_status=${HTTP_CODE:-none} response_preview=$RESPONSE_PREVIEW"
|
|
54
|
+
fi
|
|
55
|
+
) >/dev/null 2>&1 &
|
|
24
56
|
disown
|
|
25
|
-
fi
|
|
26
57
|
fi
|
|
27
58
|
|
|
28
59
|
exit 0
|
|
@@ -65,7 +65,7 @@ ovld protocol deliver --session-key <sessionKey> \
|
|
|
65
65
|
--change-rationales-json '[{"label":"Short reviewer title","file_path":"path/to/file.ts","summary":"What changed.","why":"Why it changed.","impact":"Behavioral impact.","hunks":[{"header":"@@ -10,6 +10,14 @@"}]}]'
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
`ovld protocol deliver` automatically creates a local checkpoint before the API request when the workspace is JJ- or Git-managed; use `--skip-checkpoint` only when intentionally bypassing local provenance. For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed. If you use `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that file as ephemeral scratch data outside the repository and remove it after delivery.
|
|
68
|
+
`ovld protocol deliver` automatically creates a local checkpoint before the API request when the workspace is JJ- or Git-managed; use `--skip-checkpoint` only when intentionally bypassing local provenance. Use `--payload-json` when the full delivery object fits comfortably inline. For larger delivery payloads, prefer `--payload-file -` and stream the full JSON on stdin so no scratch file needs to be created or removed. If you use `--payload-file`, `--artifacts-file`, or `--change-rationales-file` with a real path, treat that file as ephemeral scratch data outside the repository and remove it after delivery.
|
|
69
69
|
|
|
70
70
|
## Mode 2: Asked From Chat To Use Overlord
|
|
71
71
|
|
|
@@ -111,7 +111,7 @@ Always include `changeRationales` when delivering. Optionally include them on up
|
|
|
111
111
|
|
|
112
112
|
Before delivering, make sure every meaningful git-tracked file change is represented in `changeRationales`; do not send `file_changes` as an artifact.
|
|
113
113
|
|
|
114
|
-
These are structured protocol payloads that Overlord stores as first-class rows in the `file_changes` table. Prefer inline JSON or the dedicated command below.
|
|
114
|
+
These are structured protocol payloads that Overlord stores as first-class rows in the `file_changes` table. Prefer inline JSON or the dedicated command below. Use `--payload-json` for compact full delivery payloads, or `--payload-file -` when the JSON is larger or quote-sensitive so summary, artifacts, and change rationales stay in one JSON document without creating a temporary file. Ordinary deliver artifacts should use `next_steps`, `test_results`, `migration`, `note`, `url`, or `decision`.
|
|
115
115
|
|
|
116
116
|
```bash
|
|
117
117
|
ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \
|
|
@@ -158,4 +158,4 @@ The `attach` and `load-context` responses already include `attachments` and `obj
|
|
|
158
158
|
- If a protocol or MCP call fails with auth/session errors, run `ovld auth repair` yourself before asking the user to log in again or proceed without Overlord updates.
|
|
159
159
|
- If you must run `ovld auth login`, always include `--organization-id <id>` — use the organization ID from the ticket prompt context to select the organization non-interactively and avoid a blocking TTY prompt.
|
|
160
160
|
|
|
161
|
-
<!-- version: 0.4.
|
|
161
|
+
<!-- version: 0.4.9 -->
|