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 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.
@@ -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
- : null;
1128
- const summary =
1129
- deliverPayload?.summary ??
1130
- (flags['summary-file']
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 --payload-file or --artifacts-file, not both');
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 --payload-file or --artifacts-json, not both');
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 --payload-file or change-rationale flags, not both');
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;
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "0.2605121205.0",
3
+ "version": "0.2605131312.0",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "homepage": "https://www.ovld.ai/docs/",
6
6
  "type": "module",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Overlord ticket protocol workflow for Claude Code — attach/update/ask/deliver, slash commands, and hook-driven activity capture.",
5
5
  "author": {
6
6
  "name": "Cooperativ",
@@ -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
- TURN=$(printf '%s' "$BODY" | python3 -c "import json,sys; print(json.load(sys.stdin).get('turn_number', 0))" 2>/dev/null || echo "0")
10
- if [ "$TURN" = "0" ]; then
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=$(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 [ -n "$PAYLOAD" ]; then
17
- curl -sf -m 5 \
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
- >/dev/null 2>&1 &
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. For larger full delivery payloads, prefer `--payload-file -` 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`.
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.8 -->
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 send a monotonic turnIndex per conversation (always >= 1)
4
- # so POST /api/protocol/hook-event records the event (the API skips only turnIndex === 0).
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
- n = int((handle.read() or '0').strip() or '0')
38
+ raw = (handle.read() or '').strip()
39
+ if raw != '':
40
+ last_posted = int(raw)
37
41
  except Exception:
38
- n = 0
39
- n += 1
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(n))
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': n}))
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.8 -->
155
+ <!-- version: 0.4.9 -->
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Chat-driven access to Overlord ticket protocol workflows with hook-driven activity capture.",
5
5
  "author": {
6
6
  "name": "Cooperativ",
@@ -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 [ -n "$PAYLOAD" ]; then
17
- curl -sf -m 5 \
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
- >/dev/null 2>&1 &
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. For larger full delivery payloads, prefer `--payload-file -` 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`.
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.8 -->
161
+ <!-- version: 0.4.9 -->