overlord-cli 0.2605121033.0 → 0.2605121205.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
@@ -121,7 +121,7 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
121
121
  - `record-change-rationales` - persist structured change rationales without a normal progress update
122
122
  - `ask` - post a blocking question and move the ticket to review
123
123
  - `permission-request` - notify Overlord that the agent is requesting tool permission
124
- - `hook-event` - record lifecycle hook events such as `UserPromptSubmit` without a session key
124
+ - `hook-event` - record lifecycle hook events such as `UserPromptSubmit` / Cursor `beforeSubmitPrompt` without a session key
125
125
  - `read-context` - read shared persistent context for this ticket
126
126
  - `write-context` - write shared persistent context for future sessions
127
127
  - `deliver` - create a local JJ/Git checkpoint when available, send artifacts/rationales, and move the ticket to review
@@ -809,10 +809,57 @@ function cursorPaths() {
809
809
  'plugin.json'
810
810
  ),
811
811
  rulesFile: path.join(base, 'rules', 'overlord-local.mdc'),
812
- settingsFile: path.join(base, 'settings.json')
812
+ settingsFile: path.join(base, 'settings.json'),
813
+ hooksFile: path.join(base, 'hooks.json')
813
814
  };
814
815
  }
815
816
 
817
+ const CURSOR_USER_PROMPT_HOOK_RELATIVE = 'plugins/local/overlord/hooks/overlord-user-prompt-submit.sh';
818
+
819
+ function isOverlordCursorBeforeSubmitHook(entry) {
820
+ if (!entry || typeof entry !== 'object') return false;
821
+ const cmd = typeof entry.command === 'string' ? entry.command : '';
822
+ return cmd.includes('overlord-user-prompt-submit');
823
+ }
824
+
825
+ function mergeCursorBeforeSubmitHook(paths) {
826
+ const hooksFile = paths.hooksFile;
827
+ const base = fs.existsSync(hooksFile)
828
+ ? readJsonFile(hooksFile)
829
+ : { version: 1, hooks: {} };
830
+ const hooks = base.hooks && typeof base.hooks === 'object' && !Array.isArray(base.hooks) ? base.hooks : {};
831
+ const existing = Array.isArray(hooks.beforeSubmitPrompt) ? hooks.beforeSubmitPrompt : [];
832
+ hooks.beforeSubmitPrompt = [
833
+ ...existing.filter(entry => !isOverlordCursorBeforeSubmitHook(entry)),
834
+ { command: CURSOR_USER_PROMPT_HOOK_RELATIVE }
835
+ ];
836
+ writeJsonFile(hooksFile, {
837
+ ...base,
838
+ version: typeof base.version === 'number' ? base.version : 1,
839
+ hooks
840
+ });
841
+ }
842
+
843
+ function removeCursorBeforeSubmitHook(paths) {
844
+ const hooksFile = paths.hooksFile;
845
+ if (!fs.existsSync(hooksFile)) return;
846
+ const base = readJsonFile(hooksFile);
847
+ const hooks = base.hooks && typeof base.hooks === 'object' && !Array.isArray(base.hooks) ? base.hooks : {};
848
+ if (!Array.isArray(hooks.beforeSubmitPrompt)) return;
849
+ hooks.beforeSubmitPrompt = hooks.beforeSubmitPrompt.filter(
850
+ entry => !isOverlordCursorBeforeSubmitHook(entry)
851
+ );
852
+ if (hooks.beforeSubmitPrompt.length === 0) {
853
+ delete hooks.beforeSubmitPrompt;
854
+ }
855
+ if (Object.keys(hooks).length === 0) {
856
+ delete base.hooks;
857
+ } else {
858
+ base.hooks = hooks;
859
+ }
860
+ writeJsonFile(hooksFile, base);
861
+ }
862
+
816
863
  function geminiPaths() {
817
864
  const base = path.join(os.homedir(), '.gemini');
818
865
  return {
@@ -945,6 +992,7 @@ function installCodex() {
945
992
 
946
993
  function installCursor() {
947
994
  const paths = cursorPaths();
995
+ const backups = [];
948
996
  const sourceDir = cursorSourcePluginDir();
949
997
  fs.mkdirSync(path.dirname(paths.pluginDir), { recursive: true });
950
998
  fs.rmSync(paths.pluginDir, { recursive: true, force: true });
@@ -961,6 +1009,14 @@ function installCursor() {
961
1009
  for (const filePath of removedLegacySlash.removedFiles) console.log(` ${filePath}`);
962
1010
  }
963
1011
 
1012
+ const hooksBackup = backupFile(paths.hooksFile);
1013
+ if (hooksBackup) {
1014
+ backups.push(hooksBackup);
1015
+ console.log(` ✓ Backed up: ${paths.hooksFile} → ${path.basename(hooksBackup)}`);
1016
+ }
1017
+ mergeCursorBeforeSubmitHook(paths);
1018
+ console.log(` ✓ Registered Cursor beforeSubmitPrompt hook: ${paths.hooksFile}`);
1019
+
964
1020
  const existingSettings = readJsonFile(paths.settingsFile);
965
1021
  const permissions =
966
1022
  existingSettings.permissions && typeof existingSettings.permissions === 'object'
@@ -983,11 +1039,11 @@ function installCursor() {
983
1039
  version: pluginVersion(paths.pluginManifest) ?? '0.0.0',
984
1040
  contentHash: currentContentHashForAgent('cursor'),
985
1041
  installedAt: new Date().toISOString(),
986
- files: [...listFilesRecursive(paths.pluginDir), paths.settingsFile]
1042
+ files: [...listFilesRecursive(paths.pluginDir), paths.settingsFile, paths.hooksFile]
987
1043
  };
988
1044
  writeManifest(manifest);
989
1045
 
990
- return { ok: true };
1046
+ return { ok: true, backups };
991
1047
  }
992
1048
 
993
1049
  function installGemini() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "0.2605121033.0",
3
+ "version": "0.2605121205.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.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Local Overlord workflow plugin for Cursor terminal agents.",
5
5
  "author": {
6
6
  "name": "Cooperativ",
@@ -0,0 +1,60 @@
1
+ #!/bin/bash
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).
5
+
6
+ BODY=$(cat -)
7
+
8
+ OVERLORD_BASE_URL="${OVERLORD_URL:-${OVERLORD_CONNECTOR_URL:-}}"
9
+ OVERLORD_TOKEN="${OVERLORD_ACCESS_TOKEN:-}"
10
+ TICKET_ID="${TICKET_ID:-}"
11
+ OVERLORD_LOCAL_SECRET="${OVERLORD_LOCAL_SECRET:-}"
12
+
13
+ if [ -n "$OVERLORD_BASE_URL" ] && [ -n "$OVERLORD_TOKEN" ] && [ -n "$TICKET_ID" ]; then
14
+ PAYLOAD=$(
15
+ printf '%s' "$BODY" | python3 -c "
16
+ import hashlib
17
+ import json
18
+ import os
19
+ import sys
20
+
21
+ try:
22
+ body = json.load(sys.stdin)
23
+ except Exception:
24
+ sys.exit(0)
25
+
26
+ text = (body.get('prompt') or body.get('message') or '').strip()
27
+ if not text:
28
+ sys.exit(0)
29
+
30
+ cid = body.get('conversation_id') or 'unknown'
31
+ state_dir = os.path.join(os.path.expanduser('~'), '.ovld', 'cursor-user-prompt-hook')
32
+ os.makedirs(state_dir, exist_ok=True)
33
+ path = os.path.join(state_dir, hashlib.sha256(cid.encode()).hexdigest())
34
+ try:
35
+ with open(path, encoding='utf-8') as handle:
36
+ n = int((handle.read() or '0').strip() or '0')
37
+ except Exception:
38
+ n = 0
39
+ n += 1
40
+ with open(path, 'w', encoding='utf-8') as handle:
41
+ handle.write(str(n))
42
+
43
+ tid = os.environ.get('TICKET_ID', '')
44
+ print(json.dumps({'hookType': 'UserPromptSubmit', 'ticketId': tid, 'prompt': text, 'turnIndex': n}))
45
+ " 2>/dev/null
46
+ )
47
+ if [ -n "$PAYLOAD" ]; then
48
+ curl -sf -m 5 \
49
+ -X POST "$OVERLORD_BASE_URL/api/protocol/hook-event" \
50
+ -H "Authorization: Bearer $OVERLORD_TOKEN" \
51
+ -H "X-Overlord-Local-Secret: $OVERLORD_LOCAL_SECRET" \
52
+ -H "Content-Type: application/json" \
53
+ -d "$PAYLOAD" \
54
+ >/dev/null 2>&1 &
55
+ disown
56
+ fi
57
+ fi
58
+
59
+ printf '%s\n' '{"continue":true}'
60
+ exit 0
@@ -13,7 +13,7 @@ Use this skill whenever Cursor needs to work with Overlord, whether the session
13
13
  2. Keep the returned `session.sessionKey` for all follow-up calls.
14
14
  3. Treat the Overlord ticket prompt as authoritative for the objective, constraints, and delivery target.
15
15
  4. Post updates while working with `ovld protocol update --phase execute`.
16
- 5. If the user sends a follow-up message after the initial ticket, publish it immediately with `--event-type user_follow_up`.
16
+ 5. Follow-up messages after the initial ticket are captured automatically by the installed Cursor `beforeSubmitPrompt` hook (see `~/.cursor/hooks.json`). Do not post `user_follow_up` manually unless the hook is unavailable.
17
17
  6. If blocked, call `ovld protocol ask` and stop.
18
18
  7. Deliver last with `ovld protocol deliver`, including `changeRationales` for each meaningful behavioral file change.
19
19
 
@@ -42,7 +42,7 @@ Supported `--phase` values:
42
42
 
43
43
  Event types:
44
44
  - `update` for standard progress updates
45
- - `user_follow_up` for human follow-up messages after the initial ticket
45
+ - `user_follow_up` — only when the Cursor `beforeSubmitPrompt` hook is unavailable; the hook normally posts follow-ups to the activity feed
46
46
  - `alert` for warnings or non-blocking issues
47
47
 
48
48
  Ask when blocked:
@@ -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.7 -->
155
+ <!-- version: 0.4.8 -->