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
|
package/bin/_cli/setup.mjs
CHANGED
|
@@ -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
|
@@ -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.
|
|
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`
|
|
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.
|
|
155
|
+
<!-- version: 0.4.8 -->
|