overlord-cli 0.2605120852.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 +6 -1
- package/bin/_cli/protocol.mjs +76 -8
- package/bin/_cli/setup.mjs +59 -3
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +4 -10
- package/plugins/claude/hooks/hooks.json +11 -0
- package/plugins/claude/scripts/user-prompt-submit-hook.sh +28 -0
- package/plugins/claude/skills/overlord-ticket/SKILL.md +17 -5
- package/plugins/cursor/.cursor-plugin/plugin.json +1 -1
- package/plugins/cursor/hooks/overlord-user-prompt-submit.sh +60 -0
- package/plugins/cursor/skills/overlord-ticket/SKILL.md +3 -3
- package/plugins/overlord/.codex-plugin/hooks.json +15 -0
- package/plugins/overlord/.codex-plugin/plugin.json +3 -2
- package/plugins/overlord/scripts/overlord-mcp.mjs +127 -33
- package/plugins/overlord/scripts/user-prompt-submit-hook.sh +28 -0
- package/plugins/overlord/skills/overlord-ticket/SKILL.md +7 -4
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Install it globally so the `ovld` and `overlord` commands are available on your
|
|
|
13
13
|
```bash
|
|
14
14
|
npm install -g overlord-cli
|
|
15
15
|
|
|
16
|
-
#After installing, run
|
|
16
|
+
#After installing, run
|
|
17
17
|
ovld setup all #to configure every supported connector (`ovld setup` alone is interactive)
|
|
18
18
|
|
|
19
19
|
#for individual connectors, run `ovld setup <connector>`.
|
|
@@ -27,6 +27,7 @@ ovld setup all
|
|
|
27
27
|
# To update the CLI to the latest release, run:
|
|
28
28
|
ovld update
|
|
29
29
|
```
|
|
30
|
+
|
|
30
31
|
## Usage
|
|
31
32
|
|
|
32
33
|
```bash
|
|
@@ -35,6 +36,7 @@ overlord help
|
|
|
35
36
|
```
|
|
36
37
|
|
|
37
38
|
### Auth
|
|
39
|
+
|
|
38
40
|
```bash
|
|
39
41
|
# Repair shared credentials first when a session already exists
|
|
40
42
|
ovld auth repair # mirror and chmod shared Desktop/CLI credentials when possible
|
|
@@ -47,6 +49,7 @@ ovld auth logout # remove stored credentials
|
|
|
47
49
|
|
|
48
50
|
Desktop-installed wrappers default `OVERLORD_URL` to `https://www.ovld.ai` unless you override it explicitly.
|
|
49
51
|
For local dev against the web app on port 3000, export the override before running auth or protocol commands:
|
|
52
|
+
|
|
50
53
|
```bash
|
|
51
54
|
export OVERLORD_URL=http://localhost:3000
|
|
52
55
|
```
|
|
@@ -101,6 +104,7 @@ Top-level commands (see `ovld help`):
|
|
|
101
104
|
- `version` - print the installed CLI version
|
|
102
105
|
|
|
103
106
|
## Commands for agents
|
|
107
|
+
|
|
104
108
|
Agents can find docs here: https://www.ovld.ai/docs/for-agents
|
|
105
109
|
|
|
106
110
|
`ovld protocol <subcommand>` is the surface agents and hooks use for ticket lifecycle work. Run `ovld protocol help` for flags, env fallbacks, and examples.
|
|
@@ -117,6 +121,7 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
|
|
|
117
121
|
- `record-change-rationales` - persist structured change rationales without a normal progress update
|
|
118
122
|
- `ask` - post a blocking question and move the ticket to review
|
|
119
123
|
- `permission-request` - notify Overlord that the agent is requesting tool permission
|
|
124
|
+
- `hook-event` - record lifecycle hook events such as `UserPromptSubmit` / Cursor `beforeSubmitPrompt` without a session key
|
|
120
125
|
- `read-context` - read shared persistent context for this ticket
|
|
121
126
|
- `write-context` - write shared persistent context for future sessions
|
|
122
127
|
- `deliver` - create a local JJ/Git checkpoint when available, send artifacts/rationales, and move the ticket to review
|
package/bin/_cli/protocol.mjs
CHANGED
|
@@ -279,6 +279,14 @@ function readTextFile(filePath, label) {
|
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
/** Like readTextFile but accepts "-" to read from stdin. */
|
|
283
|
+
async function readTextFileOrStdin(filePath, label) {
|
|
284
|
+
if (filePath === '-') {
|
|
285
|
+
return readTextFromStdin(label);
|
|
286
|
+
}
|
|
287
|
+
return readTextFile(filePath, label);
|
|
288
|
+
}
|
|
289
|
+
|
|
282
290
|
function readJsonFile(filePath, label) {
|
|
283
291
|
try {
|
|
284
292
|
return JSON.parse(readTextFile(filePath, label));
|
|
@@ -823,7 +831,7 @@ async function protocolUpdate(args) {
|
|
|
823
831
|
if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
|
|
824
832
|
if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
|
|
825
833
|
const summary = flags['summary-file']
|
|
826
|
-
?
|
|
834
|
+
? await readTextFileOrStdin(String(flags['summary-file']), '--summary-file')
|
|
827
835
|
: requireFlag(flags, 'summary', undefined);
|
|
828
836
|
|
|
829
837
|
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
@@ -894,7 +902,7 @@ async function protocolRecordChangeRationales(args) {
|
|
|
894
902
|
ticketId,
|
|
895
903
|
changeRationales,
|
|
896
904
|
...(flags['summary-file']
|
|
897
|
-
? { summary:
|
|
905
|
+
? { summary: await readTextFileOrStdin(String(flags['summary-file']), '--summary-file') }
|
|
898
906
|
: flags.summary
|
|
899
907
|
? { summary: String(flags.summary) }
|
|
900
908
|
: {}),
|
|
@@ -924,7 +932,7 @@ async function protocolAsk(args) {
|
|
|
924
932
|
if (!sessionKey) throw new Error('--session-key is required (or set SESSION_KEY)');
|
|
925
933
|
if (!ticketId) throw new Error('--ticket-id is required (or set TICKET_ID)');
|
|
926
934
|
const question = flags['question-file']
|
|
927
|
-
?
|
|
935
|
+
? await readTextFileOrStdin(String(flags['question-file']), '--question-file')
|
|
928
936
|
: requireFlag(flags, 'question', undefined);
|
|
929
937
|
|
|
930
938
|
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
@@ -980,6 +988,46 @@ async function protocolPermissionRequest(args) {
|
|
|
980
988
|
console.log(JSON.stringify(data, null, 2));
|
|
981
989
|
}
|
|
982
990
|
|
|
991
|
+
// ---------------------------------------------------------------------------
|
|
992
|
+
// hook-event
|
|
993
|
+
// ---------------------------------------------------------------------------
|
|
994
|
+
|
|
995
|
+
async function protocolHookEvent(args) {
|
|
996
|
+
const flags = parseFlags(args);
|
|
997
|
+
const ticketId = requireFlag(flags, 'ticket-id', 'TICKET_ID');
|
|
998
|
+
const hookType = requireFlag(flags, 'hook-type', undefined);
|
|
999
|
+
|
|
1000
|
+
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
1001
|
+
await resolveProtocolAuthForFlags(flags, ticketId);
|
|
1002
|
+
const timeoutMs = resolveTimeout(flags);
|
|
1003
|
+
|
|
1004
|
+
const body = {
|
|
1005
|
+
hookType,
|
|
1006
|
+
ticketId,
|
|
1007
|
+
...(flags.prompt !== undefined ? { prompt: String(flags.prompt) } : {}),
|
|
1008
|
+
...(flags['turn-index'] !== undefined
|
|
1009
|
+
? { turnIndex: parseInt(String(flags['turn-index']), 10) }
|
|
1010
|
+
: {})
|
|
1011
|
+
};
|
|
1012
|
+
|
|
1013
|
+
try {
|
|
1014
|
+
const data = await apiPost(
|
|
1015
|
+
platformUrl,
|
|
1016
|
+
bearerToken,
|
|
1017
|
+
localSecret,
|
|
1018
|
+
organizationId,
|
|
1019
|
+
'/api/protocol/hook-event',
|
|
1020
|
+
body,
|
|
1021
|
+
timeoutMs
|
|
1022
|
+
);
|
|
1023
|
+
console.log(JSON.stringify(data, null, 2));
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1026
|
+
process.stderr.write(`[protocol] hook-event skipped: ${message}\n`);
|
|
1027
|
+
console.log(JSON.stringify({ ok: false, error: message }, null, 2));
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
983
1031
|
// ---------------------------------------------------------------------------
|
|
984
1032
|
// read-context
|
|
985
1033
|
// ---------------------------------------------------------------------------
|
|
@@ -1080,7 +1128,7 @@ async function protocolDeliver(args) {
|
|
|
1080
1128
|
const summary =
|
|
1081
1129
|
deliverPayload?.summary ??
|
|
1082
1130
|
(flags['summary-file']
|
|
1083
|
-
?
|
|
1131
|
+
? await readTextFileOrStdin(String(flags['summary-file']), '--summary-file')
|
|
1084
1132
|
: requireFlag(flags, 'summary', undefined));
|
|
1085
1133
|
|
|
1086
1134
|
const { platformUrl, bearerToken, localSecret, organizationId } =
|
|
@@ -1705,6 +1753,7 @@ Subcommands:
|
|
|
1705
1753
|
record-change-rationales Persist structured change rationales without a progress update
|
|
1706
1754
|
ask Post a blocking question and move the ticket to review
|
|
1707
1755
|
permission-request Notify Overlord that the agent is requesting tool permission
|
|
1756
|
+
hook-event Record a lifecycle hook event without a session key
|
|
1708
1757
|
read-context Read shared persistent context for this ticket
|
|
1709
1758
|
write-context Write shared persistent context for future sessions
|
|
1710
1759
|
deliver Finish work, send artifacts, and move the ticket to review
|
|
@@ -1808,7 +1857,7 @@ update:
|
|
|
1808
1857
|
Required:
|
|
1809
1858
|
--session-key <key>
|
|
1810
1859
|
--ticket-id <ticket_id>
|
|
1811
|
-
--summary <text> or --summary-file <path
|
|
1860
|
+
--summary <text> or --summary-file <path|->
|
|
1812
1861
|
Optional:
|
|
1813
1862
|
--phase <status> draft | execute | review | deliver | complete | blocked | cancelled
|
|
1814
1863
|
--event-type <type> update | user_follow_up | alert
|
|
@@ -1819,6 +1868,8 @@ update:
|
|
|
1819
1868
|
--change-rationales-file <path>
|
|
1820
1869
|
Notes:
|
|
1821
1870
|
Use phase=execute while actively working. user_follow_up is for verbatim human follow-up messages.
|
|
1871
|
+
Pass --summary-file - to read the summary from stdin — avoids shell interpretation of backticks,
|
|
1872
|
+
quotes, or other special characters in the summary text.
|
|
1822
1873
|
|
|
1823
1874
|
record-change-rationales:
|
|
1824
1875
|
Purpose:
|
|
@@ -1828,7 +1879,7 @@ record-change-rationales:
|
|
|
1828
1879
|
--ticket-id <ticket_id>
|
|
1829
1880
|
--change-rationales-json <json> or --change-rationales-file <path>
|
|
1830
1881
|
Optional:
|
|
1831
|
-
--summary <text> or --summary-file <path
|
|
1882
|
+
--summary <text> or --summary-file <path|->
|
|
1832
1883
|
--phase <status>
|
|
1833
1884
|
|
|
1834
1885
|
ask:
|
|
@@ -1837,7 +1888,7 @@ ask:
|
|
|
1837
1888
|
Required:
|
|
1838
1889
|
--session-key <key>
|
|
1839
1890
|
--ticket-id <ticket_id>
|
|
1840
|
-
--question <text> or --question-file <path
|
|
1891
|
+
--question <text> or --question-file <path|->
|
|
1841
1892
|
Optional:
|
|
1842
1893
|
--phase <status>
|
|
1843
1894
|
--payload-json <json>
|
|
@@ -1853,6 +1904,17 @@ permission-request:
|
|
|
1853
1904
|
Optional:
|
|
1854
1905
|
--payload-file <path|-> Hook JSON payload, or stdin when "-"
|
|
1855
1906
|
|
|
1907
|
+
hook-event:
|
|
1908
|
+
Purpose:
|
|
1909
|
+
Record a lifecycle hook event for a ticket without requiring a session key.
|
|
1910
|
+
This is primarily used by installed UserPromptSubmit hooks. Stop is reserved for future lifecycle hooks.
|
|
1911
|
+
Required:
|
|
1912
|
+
--hook-type <UserPromptSubmit|Stop>
|
|
1913
|
+
--ticket-id <ticket_id>
|
|
1914
|
+
Optional:
|
|
1915
|
+
--prompt <text>
|
|
1916
|
+
--turn-index <n>
|
|
1917
|
+
|
|
1856
1918
|
read-context:
|
|
1857
1919
|
Purpose:
|
|
1858
1920
|
Read persistent shared context written by earlier sessions
|
|
@@ -1880,7 +1942,7 @@ deliver:
|
|
|
1880
1942
|
Required:
|
|
1881
1943
|
--session-key <key>
|
|
1882
1944
|
--ticket-id <ticket_id>
|
|
1883
|
-
--summary <text> or --summary-file <path
|
|
1945
|
+
--summary <text> or --summary-file <path|->
|
|
1884
1946
|
or: --payload-file <path|-> containing { summary, artifacts, changeRationales }
|
|
1885
1947
|
Optional:
|
|
1886
1948
|
--artifacts-json <json>
|
|
@@ -1890,6 +1952,7 @@ deliver:
|
|
|
1890
1952
|
--skip-file-change-check Bypass local git vs changeRationales validation
|
|
1891
1953
|
Notes:
|
|
1892
1954
|
Use --payload-file - to read the full delivery JSON from stdin without creating a scratch file.
|
|
1955
|
+
Use --summary-file - to pipe just the summary text via stdin (avoids shell special-char issues).
|
|
1893
1956
|
Do not combine --payload-file with --artifacts-json/--artifacts-file or change-rationale flags.
|
|
1894
1957
|
In a git workspace, deliver validates that changed files are represented by changeRationales unless skipped.
|
|
1895
1958
|
|
|
@@ -2017,6 +2080,7 @@ Examples:
|
|
|
2017
2080
|
ovld protocol update --session-key <key> --ticket-id <ticket_id> --summary-file ./update.txt --event-type user_follow_up
|
|
2018
2081
|
ovld protocol record-change-rationales --session-key <key> --ticket-id <ticket_id> --change-rationales-json '[{"label":"...","file_path":"...","summary":"...","why":"...","impact":"...","hunks":[{"header":"@@ ... @@"}]}]'
|
|
2019
2082
|
ovld protocol ask --session-key <key> --ticket-id <ticket_id> --question-file ./question.txt
|
|
2083
|
+
ovld protocol hook-event --hook-type UserPromptSubmit --ticket-id <ticket_id> --prompt "User follow-up" --turn-index 1
|
|
2020
2084
|
ovld protocol read-context --session-key <key> --ticket-id <ticket_id> --query arch --limit 5
|
|
2021
2085
|
ovld protocol write-context --session-key <key> --ticket-id <ticket_id> --key "arch" --value '"monorepo"' --tags repo,agent
|
|
2022
2086
|
ovld protocol attachment-list --session-key <key> --ticket-id <ticket_id>
|
|
@@ -2101,6 +2165,10 @@ Examples:
|
|
|
2101
2165
|
await protocolPermissionRequest(args);
|
|
2102
2166
|
return;
|
|
2103
2167
|
}
|
|
2168
|
+
if (subcommand === 'hook-event') {
|
|
2169
|
+
await protocolHookEvent(args);
|
|
2170
|
+
return;
|
|
2171
|
+
}
|
|
2104
2172
|
if (subcommand === 'read-context') {
|
|
2105
2173
|
await protocolReadContext(args);
|
|
2106
2174
|
return;
|
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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overlord",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Overlord ticket protocol workflow for Claude Code — attach/update/ask/deliver, slash commands, and
|
|
3
|
+
"version": "0.2.0",
|
|
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",
|
|
7
7
|
"url": "https://github.com/cooperativ"
|
|
@@ -9,14 +9,8 @@
|
|
|
9
9
|
"homepage": "https://www.ovld.ai",
|
|
10
10
|
"repository": "https://github.com/cooperativ/overlord",
|
|
11
11
|
"license": "UNLICENSED",
|
|
12
|
-
"keywords": [
|
|
13
|
-
|
|
14
|
-
"tickets",
|
|
15
|
-
"agents",
|
|
16
|
-
"workflow",
|
|
17
|
-
"protocol",
|
|
18
|
-
"claude-code"
|
|
19
|
-
],
|
|
12
|
+
"keywords": ["overlord", "tickets", "agents", "workflow", "protocol", "claude-code"],
|
|
13
|
+
"hooks": "./hooks/hooks.json",
|
|
20
14
|
"userConfig": {
|
|
21
15
|
"overlord_url": {
|
|
22
16
|
"type": "string",
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Overlord UserPromptSubmit hook — fires before Claude processes each user turn.
|
|
3
|
+
|
|
4
|
+
BODY=$(cat -)
|
|
5
|
+
OVERLORD_BASE_URL="${CLAUDE_PLUGIN_OPTION_OVERLORD_URL:-$OVERLORD_URL}"
|
|
6
|
+
OVERLORD_TOKEN="${CLAUDE_PLUGIN_OPTION_AGENT_TOKEN:-${CLAUDE_PLUGIN_OPTION_OVERLORD_ACCESS_TOKEN:-$OVERLORD_ACCESS_TOKEN}}"
|
|
7
|
+
OVERLORD_LOCAL_SECRET="${OVERLORD_LOCAL_SECRET:-}"
|
|
8
|
+
|
|
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
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
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 \
|
|
18
|
+
-X POST "$OVERLORD_BASE_URL/api/protocol/hook-event" \
|
|
19
|
+
-H "Authorization: Bearer $OVERLORD_TOKEN" \
|
|
20
|
+
-H "X-Overlord-Local-Secret: $OVERLORD_LOCAL_SECRET" \
|
|
21
|
+
-H "Content-Type: application/json" \
|
|
22
|
+
-d "$PAYLOAD" \
|
|
23
|
+
>/dev/null 2>&1 &
|
|
24
|
+
disown
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
exit 0
|
|
@@ -15,7 +15,7 @@ Use this mode when the prompt already contains a ticket ID or explicitly says th
|
|
|
15
15
|
2. Keep the returned `session.sessionKey` for all follow-up calls.
|
|
16
16
|
3. Treat the Overlord ticket prompt as authoritative for the objective, constraints, and delivery target.
|
|
17
17
|
4. Post updates while working with `ovld protocol update --phase execute`.
|
|
18
|
-
5.
|
|
18
|
+
5. Follow-up messages after the initial ticket are captured automatically by the installed `UserPromptSubmit` hook. Do not post `user_follow_up` manually unless the hook is unavailable.
|
|
19
19
|
6. If blocked, call `ovld protocol ask` and stop.
|
|
20
20
|
7. Deliver last with `ovld protocol deliver`, including `changeRationales` for each meaningful behavioral file change.
|
|
21
21
|
|
|
@@ -33,7 +33,16 @@ Update:
|
|
|
33
33
|
ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID --summary "What you did and why." --phase execute
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
When the summary contains backticks, quotes, or other special shell characters, pipe it via stdin to prevent shell interpretation:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID --summary-file - --phase execute <<'EOF'
|
|
40
|
+
What you did and why — including `backticks`, "quotes", and $variables are all safe here.
|
|
41
|
+
EOF
|
|
42
|
+
```
|
|
43
|
+
|
|
36
44
|
Supported `--phase` values:
|
|
45
|
+
|
|
37
46
|
- `draft`
|
|
38
47
|
- `execute`
|
|
39
48
|
- `review`
|
|
@@ -45,8 +54,9 @@ Supported `--phase` values:
|
|
|
45
54
|
These are hardcoded CLI-supported values for the `--phase` flag. They are not user-defined phase types.
|
|
46
55
|
|
|
47
56
|
Event types:
|
|
57
|
+
|
|
48
58
|
- `update` for standard progress updates
|
|
49
|
-
- `user_follow_up` for human follow-up messages after the initial ticket
|
|
59
|
+
- `user_follow_up` for human follow-up messages after the initial ticket when the automatic hook is unavailable
|
|
50
60
|
- `alert` for warnings or non-blocking issues
|
|
51
61
|
|
|
52
62
|
Ask when blocked:
|
|
@@ -65,7 +75,7 @@ ovld protocol deliver --session-key <sessionKey> \
|
|
|
65
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 @@"}]}]'
|
|
66
76
|
```
|
|
67
77
|
|
|
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.
|
|
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.
|
|
69
79
|
|
|
70
80
|
## Mode 2: Asked From Chat To Use Overlord
|
|
71
81
|
|
|
@@ -108,6 +118,7 @@ Record only meaningful behavioral changes. Skip formatting-only noise.
|
|
|
108
118
|
## Project Discovery And Ticket Creation
|
|
109
119
|
|
|
110
120
|
When creating tickets from within a repository:
|
|
121
|
+
|
|
111
122
|
- Prefer `create` by default for draft ticket creation.
|
|
112
123
|
- Use `prompt` only when the user explicitly asks to start execution immediately.
|
|
113
124
|
- Both commands can resolve the project from the current working directory; use `--working-directory` to override.
|
|
@@ -135,7 +146,7 @@ Pass `--execution-target agent` or `--execution-target human` (default: `human`)
|
|
|
135
146
|
- **`agent`** — any task an AI agent can complete in a computer environment: coding, internet research, document editing, data analysis, automated testing, etc.
|
|
136
147
|
- **`human`** — any task requiring human presence or judgment: setting credentials or tokens in a third-party UI (e.g. Vercel, AWS), sending physical mail, making a product or business decision, physical-world actions.
|
|
137
148
|
|
|
138
|
-
When in doubt, ask yourself:
|
|
149
|
+
When in doubt, ask yourself: _can this be done entirely inside a terminal or browser by an AI without human intervention?_ If yes → `agent`. If it requires a human to log in, decide, or act in the real world → `human`.
|
|
139
150
|
|
|
140
151
|
## Context And Artifacts
|
|
141
152
|
|
|
@@ -164,10 +175,11 @@ The `attach` and `load-context` responses already include `attachments` and `obj
|
|
|
164
175
|
- Use `ovld protocol` commands and the plugin's slash commands instead of ad hoc scripts.
|
|
165
176
|
- Do not invent protocol subcommands. Use `ovld protocol help` when unsure.
|
|
166
177
|
- The `summary` in deliver is what the PM reads first, so write it as a narrative, not a command list.
|
|
178
|
+
- When a summary or question contains backticks, `$vars`, or other shell-special characters, always use `--summary-file -` (or `--question-file -`) with a single-quoted heredoc (`<<'EOF'`). Never retry by stripping or escaping content — pipe stdin instead.
|
|
167
179
|
- Use `write-context` for facts a future agent session should know.
|
|
168
180
|
- 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.
|
|
169
181
|
- 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.
|
|
170
182
|
- Do not add or commit changes unless the user explicitly asks you to commit.
|
|
171
183
|
- Delivery is the concluding step. After delivering, stop unless the user follows up or the ticket is reopened.
|
|
172
184
|
|
|
173
|
-
<!-- version: 0.4.
|
|
185
|
+
<!-- version: 0.4.8 -->
|
|
@@ -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 -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overlord",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Chat-driven access to Overlord ticket protocol workflows.",
|
|
3
|
+
"version": "0.2.2",
|
|
4
|
+
"description": "Chat-driven access to Overlord ticket protocol workflows with hook-driven activity capture.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cooperativ",
|
|
7
7
|
"url": "https://github.com/cooperativ"
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"license": "UNLICENSED",
|
|
12
12
|
"keywords": ["overlord", "tickets", "agents", "workflow", "protocol", "mcp", "codex-plugin"],
|
|
13
13
|
"skills": "./skills/",
|
|
14
|
+
"hooks": "./.codex-plugin/hooks.json",
|
|
14
15
|
"mcpServers": "./.mcp.json",
|
|
15
16
|
"interface": {
|
|
16
17
|
"displayName": "Overlord",
|
|
@@ -35,7 +35,10 @@ const tools = [
|
|
|
35
35
|
inputSchema: {
|
|
36
36
|
type: 'object',
|
|
37
37
|
properties: {
|
|
38
|
-
working_directory: {
|
|
38
|
+
working_directory: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
description: 'Directory to match. Defaults to the current workspace.'
|
|
41
|
+
}
|
|
39
42
|
}
|
|
40
43
|
},
|
|
41
44
|
toCliFlags: args => ({
|
|
@@ -45,17 +48,22 @@ const tools = [
|
|
|
45
48
|
},
|
|
46
49
|
{
|
|
47
50
|
name: 'attach',
|
|
48
|
-
description:
|
|
51
|
+
description:
|
|
52
|
+
'Attach an agent session to an existing Overlord ticket and return the working context.',
|
|
49
53
|
inputSchema: {
|
|
50
54
|
type: 'object',
|
|
51
55
|
properties: {
|
|
52
|
-
ticket_id: {
|
|
56
|
+
ticket_id: {
|
|
57
|
+
type: 'string',
|
|
58
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
59
|
+
},
|
|
53
60
|
agent: { type: 'string' },
|
|
54
61
|
method: { type: 'string' },
|
|
55
62
|
external_session_id: { type: ['string', 'null'] },
|
|
56
63
|
metadata: {
|
|
57
64
|
type: 'object',
|
|
58
|
-
description:
|
|
65
|
+
description:
|
|
66
|
+
'Optional extra metadata merged into the attach request (same as --metadata-json on the CLI).'
|
|
59
67
|
}
|
|
60
68
|
},
|
|
61
69
|
required: ['ticket_id']
|
|
@@ -77,12 +85,16 @@ const tools = [
|
|
|
77
85
|
inputSchema: {
|
|
78
86
|
type: 'object',
|
|
79
87
|
properties: {
|
|
80
|
-
ticket_id: {
|
|
88
|
+
ticket_id: {
|
|
89
|
+
type: 'string',
|
|
90
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
91
|
+
},
|
|
81
92
|
agent: { type: 'string' },
|
|
82
93
|
method: { type: 'string' },
|
|
83
94
|
metadata: {
|
|
84
95
|
type: 'object',
|
|
85
|
-
description:
|
|
96
|
+
description:
|
|
97
|
+
'Optional extra metadata merged into the connect request (same as --metadata-json on the CLI).'
|
|
86
98
|
}
|
|
87
99
|
},
|
|
88
100
|
required: ['ticket_id']
|
|
@@ -104,7 +116,10 @@ const tools = [
|
|
|
104
116
|
inputSchema: {
|
|
105
117
|
type: 'object',
|
|
106
118
|
properties: {
|
|
107
|
-
ticket_id: {
|
|
119
|
+
ticket_id: {
|
|
120
|
+
type: 'string',
|
|
121
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
122
|
+
}
|
|
108
123
|
},
|
|
109
124
|
required: ['ticket_id']
|
|
110
125
|
},
|
|
@@ -124,7 +139,10 @@ const tools = [
|
|
|
124
139
|
priority: { type: 'string', enum: ['low', 'medium', 'high', 'urgent'] },
|
|
125
140
|
project_id: { type: 'string' },
|
|
126
141
|
working_directory: { type: 'string' },
|
|
127
|
-
personal: {
|
|
142
|
+
personal: {
|
|
143
|
+
type: 'boolean',
|
|
144
|
+
description: 'Create without assigning a project (private ticket).'
|
|
145
|
+
},
|
|
128
146
|
acceptance_criteria: { type: 'string' },
|
|
129
147
|
available_tools: { type: 'string' },
|
|
130
148
|
execution_target: { type: 'string', enum: ['agent', 'human'] },
|
|
@@ -161,9 +179,15 @@ const tools = [
|
|
|
161
179
|
type: 'object',
|
|
162
180
|
properties: {
|
|
163
181
|
session_key: { type: 'string' },
|
|
164
|
-
ticket_id: {
|
|
182
|
+
ticket_id: {
|
|
183
|
+
type: 'string',
|
|
184
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
185
|
+
},
|
|
165
186
|
summary: { type: 'string' },
|
|
166
|
-
phase: {
|
|
187
|
+
phase: {
|
|
188
|
+
type: 'string',
|
|
189
|
+
enum: ['draft', 'execute', 'review', 'deliver', 'complete', 'blocked', 'cancelled']
|
|
190
|
+
},
|
|
167
191
|
event_type: { type: 'string', enum: ['update', 'user_follow_up', 'alert'] },
|
|
168
192
|
external_url: { type: ['string', 'null'] },
|
|
169
193
|
external_session_id: { type: ['string', 'null'] },
|
|
@@ -192,9 +216,15 @@ const tools = [
|
|
|
192
216
|
type: 'object',
|
|
193
217
|
properties: {
|
|
194
218
|
session_key: { type: 'string' },
|
|
195
|
-
ticket_id: {
|
|
219
|
+
ticket_id: {
|
|
220
|
+
type: 'string',
|
|
221
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
222
|
+
},
|
|
196
223
|
summary: { type: 'string' },
|
|
197
|
-
phase: {
|
|
224
|
+
phase: {
|
|
225
|
+
type: 'string',
|
|
226
|
+
enum: ['draft', 'execute', 'review', 'deliver', 'complete', 'blocked', 'cancelled']
|
|
227
|
+
},
|
|
198
228
|
change_rationales: { type: 'array' }
|
|
199
229
|
},
|
|
200
230
|
required: ['session_key', 'ticket_id', 'change_rationales']
|
|
@@ -208,6 +238,31 @@ const tools = [
|
|
|
208
238
|
}),
|
|
209
239
|
subcommand: 'record-change-rationales'
|
|
210
240
|
},
|
|
241
|
+
{
|
|
242
|
+
name: 'record_hook_event',
|
|
243
|
+
description:
|
|
244
|
+
'Record a hook lifecycle event for a ticket without requiring a session key. Stop is reserved for future lifecycle hooks.',
|
|
245
|
+
inputSchema: {
|
|
246
|
+
type: 'object',
|
|
247
|
+
properties: {
|
|
248
|
+
hook_type: { type: 'string', enum: ['UserPromptSubmit', 'Stop'] },
|
|
249
|
+
ticket_id: {
|
|
250
|
+
type: 'string',
|
|
251
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
252
|
+
},
|
|
253
|
+
prompt: { type: 'string' },
|
|
254
|
+
turn_index: { type: 'number' }
|
|
255
|
+
},
|
|
256
|
+
required: ['hook_type', 'ticket_id']
|
|
257
|
+
},
|
|
258
|
+
toCliFlags: args => ({
|
|
259
|
+
'hook-type': args.hook_type,
|
|
260
|
+
'ticket-id': args.ticket_id,
|
|
261
|
+
prompt: args.prompt,
|
|
262
|
+
'turn-index': args.turn_index
|
|
263
|
+
}),
|
|
264
|
+
subcommand: 'hook-event'
|
|
265
|
+
},
|
|
211
266
|
{
|
|
212
267
|
name: 'ask',
|
|
213
268
|
description: 'Send a blocking question to the human reviewer or PM.',
|
|
@@ -215,9 +270,15 @@ const tools = [
|
|
|
215
270
|
type: 'object',
|
|
216
271
|
properties: {
|
|
217
272
|
session_key: { type: 'string' },
|
|
218
|
-
ticket_id: {
|
|
273
|
+
ticket_id: {
|
|
274
|
+
type: 'string',
|
|
275
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
276
|
+
},
|
|
219
277
|
question: { type: 'string' },
|
|
220
|
-
phase: {
|
|
278
|
+
phase: {
|
|
279
|
+
type: 'string',
|
|
280
|
+
enum: ['draft', 'execute', 'review', 'deliver', 'complete', 'blocked', 'cancelled']
|
|
281
|
+
},
|
|
221
282
|
payload: { type: 'object' }
|
|
222
283
|
},
|
|
223
284
|
required: ['session_key', 'ticket_id', 'question']
|
|
@@ -238,7 +299,10 @@ const tools = [
|
|
|
238
299
|
type: 'object',
|
|
239
300
|
properties: {
|
|
240
301
|
session_key: { type: 'string' },
|
|
241
|
-
ticket_id: {
|
|
302
|
+
ticket_id: {
|
|
303
|
+
type: 'string',
|
|
304
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
305
|
+
},
|
|
242
306
|
query: { type: 'string' },
|
|
243
307
|
limit: { type: 'number' }
|
|
244
308
|
},
|
|
@@ -259,7 +323,10 @@ const tools = [
|
|
|
259
323
|
type: 'object',
|
|
260
324
|
properties: {
|
|
261
325
|
session_key: { type: 'string' },
|
|
262
|
-
ticket_id: {
|
|
326
|
+
ticket_id: {
|
|
327
|
+
type: 'string',
|
|
328
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
329
|
+
},
|
|
263
330
|
key: { type: 'string' },
|
|
264
331
|
value: {},
|
|
265
332
|
tags: { type: 'array', items: { type: 'string' } }
|
|
@@ -283,7 +350,10 @@ const tools = [
|
|
|
283
350
|
type: 'object',
|
|
284
351
|
properties: {
|
|
285
352
|
session_key: { type: 'string' },
|
|
286
|
-
ticket_id: {
|
|
353
|
+
ticket_id: {
|
|
354
|
+
type: 'string',
|
|
355
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
356
|
+
},
|
|
287
357
|
summary: { type: 'string' },
|
|
288
358
|
artifacts: { type: 'array' },
|
|
289
359
|
change_rationales: { type: 'array' },
|
|
@@ -313,9 +383,13 @@ const tools = [
|
|
|
313
383
|
JSON.stringify({
|
|
314
384
|
summary: args.summary,
|
|
315
385
|
...(Array.isArray(args.artifacts) ? { artifacts: args.artifacts } : {}),
|
|
316
|
-
...(Array.isArray(args.change_rationales)
|
|
386
|
+
...(Array.isArray(args.change_rationales)
|
|
387
|
+
? { changeRationales: args.change_rationales }
|
|
388
|
+
: {}),
|
|
317
389
|
...(args.snapshot && typeof args.snapshot === 'object' ? { snapshot: args.snapshot } : {}),
|
|
318
|
-
...(args.checkpoint && typeof args.checkpoint === 'object'
|
|
390
|
+
...(args.checkpoint && typeof args.checkpoint === 'object'
|
|
391
|
+
? { checkpoint: args.checkpoint }
|
|
392
|
+
: {})
|
|
319
393
|
}),
|
|
320
394
|
subcommand: 'deliver'
|
|
321
395
|
},
|
|
@@ -327,7 +401,10 @@ const tools = [
|
|
|
327
401
|
type: 'object',
|
|
328
402
|
properties: {
|
|
329
403
|
session_key: { type: 'string' },
|
|
330
|
-
ticket_id: {
|
|
404
|
+
ticket_id: {
|
|
405
|
+
type: 'string',
|
|
406
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
407
|
+
},
|
|
331
408
|
objective_id: { type: 'string' }
|
|
332
409
|
},
|
|
333
410
|
required: ['session_key', 'ticket_id']
|
|
@@ -346,7 +423,10 @@ const tools = [
|
|
|
346
423
|
type: 'object',
|
|
347
424
|
properties: {
|
|
348
425
|
session_key: { type: 'string' },
|
|
349
|
-
ticket_id: {
|
|
426
|
+
ticket_id: {
|
|
427
|
+
type: 'string',
|
|
428
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
429
|
+
},
|
|
350
430
|
objective_id: { type: 'string' },
|
|
351
431
|
file_name: { type: 'string' },
|
|
352
432
|
label: { type: 'string' },
|
|
@@ -370,12 +450,16 @@ const tools = [
|
|
|
370
450
|
},
|
|
371
451
|
{
|
|
372
452
|
name: 'finalize_attachment_upload',
|
|
373
|
-
description:
|
|
453
|
+
description:
|
|
454
|
+
'Finalize an objective attachment after uploading bytes to the signed storage URL.',
|
|
374
455
|
inputSchema: {
|
|
375
456
|
type: 'object',
|
|
376
457
|
properties: {
|
|
377
458
|
session_key: { type: 'string' },
|
|
378
|
-
ticket_id: {
|
|
459
|
+
ticket_id: {
|
|
460
|
+
type: 'string',
|
|
461
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
462
|
+
},
|
|
379
463
|
objective_id: { type: 'string' },
|
|
380
464
|
storage_path: { type: 'string' },
|
|
381
465
|
label: { type: 'string' },
|
|
@@ -404,7 +488,10 @@ const tools = [
|
|
|
404
488
|
type: 'object',
|
|
405
489
|
properties: {
|
|
406
490
|
session_key: { type: 'string' },
|
|
407
|
-
ticket_id: {
|
|
491
|
+
ticket_id: {
|
|
492
|
+
type: 'string',
|
|
493
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
494
|
+
},
|
|
408
495
|
objective_id: { type: 'string' },
|
|
409
496
|
attachment_id: { type: 'string' },
|
|
410
497
|
storage_path: { type: 'string' },
|
|
@@ -424,12 +511,16 @@ const tools = [
|
|
|
424
511
|
},
|
|
425
512
|
{
|
|
426
513
|
name: 'upload_attachment_file',
|
|
427
|
-
description:
|
|
514
|
+
description:
|
|
515
|
+
'Prepare, upload, and finalize a local file as an objective attachment in one step.',
|
|
428
516
|
inputSchema: {
|
|
429
517
|
type: 'object',
|
|
430
518
|
properties: {
|
|
431
519
|
session_key: { type: 'string' },
|
|
432
|
-
ticket_id: {
|
|
520
|
+
ticket_id: {
|
|
521
|
+
type: 'string',
|
|
522
|
+
description: 'Ticket identifier (e.g. 1:899). Also accepts UUID.'
|
|
523
|
+
},
|
|
433
524
|
objective_id: { type: 'string' },
|
|
434
525
|
file: { type: 'string' },
|
|
435
526
|
file_name: { type: 'string' },
|
|
@@ -516,12 +607,13 @@ function parseMessages(chunk) {
|
|
|
516
607
|
|
|
517
608
|
const headerText = buffer.subarray(0, headerEnd).toString('utf8');
|
|
518
609
|
const headers = Object.fromEntries(
|
|
519
|
-
headerText
|
|
520
|
-
.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
610
|
+
headerText.split('\r\n').map(line => {
|
|
611
|
+
const separatorIndex = line.indexOf(':');
|
|
612
|
+
return [
|
|
613
|
+
line.slice(0, separatorIndex).trim().toLowerCase(),
|
|
614
|
+
line.slice(separatorIndex + 1).trim()
|
|
615
|
+
];
|
|
616
|
+
})
|
|
525
617
|
);
|
|
526
618
|
|
|
527
619
|
const contentLength = Number(headers['content-length']);
|
|
@@ -560,7 +652,9 @@ function cliArgsFromFlags(flags) {
|
|
|
560
652
|
}
|
|
561
653
|
|
|
562
654
|
const serialized =
|
|
563
|
-
typeof value === 'string' || typeof value === 'number'
|
|
655
|
+
typeof value === 'string' || typeof value === 'number'
|
|
656
|
+
? String(value)
|
|
657
|
+
: JSON.stringify(value);
|
|
564
658
|
args.push(`--${key}`, serialized);
|
|
565
659
|
}
|
|
566
660
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Overlord UserPromptSubmit hook — fires before Codex processes each user turn.
|
|
3
|
+
|
|
4
|
+
BODY=$(cat -)
|
|
5
|
+
OVERLORD_BASE_URL="${OVERLORD_URL:-}"
|
|
6
|
+
OVERLORD_TOKEN="${OVERLORD_ACCESS_TOKEN:-}"
|
|
7
|
+
OVERLORD_LOCAL_SECRET="${OVERLORD_LOCAL_SECRET:-}"
|
|
8
|
+
|
|
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
|
|
11
|
+
exit 0
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
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 \
|
|
18
|
+
-X POST "$OVERLORD_BASE_URL/api/protocol/hook-event" \
|
|
19
|
+
-H "Authorization: Bearer $OVERLORD_TOKEN" \
|
|
20
|
+
-H "X-Overlord-Local-Secret: $OVERLORD_LOCAL_SECRET" \
|
|
21
|
+
-H "Content-Type: application/json" \
|
|
22
|
+
-d "$PAYLOAD" \
|
|
23
|
+
>/dev/null 2>&1 &
|
|
24
|
+
disown
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
exit 0
|
|
@@ -13,7 +13,7 @@ Use this skill when Codex needs to work with Overlord, whether the session was l
|
|
|
13
13
|
2. Store the returned `SESSION_KEY` or `session.sessionKey`.
|
|
14
14
|
3. Treat the Overlord ticket prompt as authoritative for the specific objective and ticket-level constraints.
|
|
15
15
|
4. While working, publish meaningful progress with `ovld protocol update --session-key <sessionKey> --ticket-id <ticket_id> --phase execute --summary "..."`.
|
|
16
|
-
5.
|
|
16
|
+
5. Follow-up messages after the initial ticket are captured automatically by the installed `UserPromptSubmit` hook. Do not post `user_follow_up` manually unless the hook is unavailable.
|
|
17
17
|
6. If blocked on a human-only action, ask a precise blocking question with `ovld protocol ask` and stop.
|
|
18
18
|
7. Deliver last with `ovld protocol deliver`, including meaningful `changeRationales` for every behavioral git-tracked change.
|
|
19
19
|
|
|
@@ -32,6 +32,7 @@ ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID --summary
|
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
Supported `--phase` values:
|
|
35
|
+
|
|
35
36
|
- `draft`
|
|
36
37
|
- `execute`
|
|
37
38
|
- `review`
|
|
@@ -43,8 +44,9 @@ Supported `--phase` values:
|
|
|
43
44
|
These are hardcoded CLI-supported values for the `--phase` flag. They are not user-defined phase types.
|
|
44
45
|
|
|
45
46
|
Event types:
|
|
47
|
+
|
|
46
48
|
- `update` for standard progress updates
|
|
47
|
-
- `user_follow_up` for human follow-up messages after the initial ticket
|
|
49
|
+
- `user_follow_up` for human follow-up messages after the initial ticket when the automatic hook is unavailable
|
|
48
50
|
- `alert` for warnings or non-blocking issues
|
|
49
51
|
|
|
50
52
|
Ask when blocked:
|
|
@@ -81,6 +83,7 @@ ovld protocol deliver --session-key <sessionKey> \
|
|
|
81
83
|
## Project Discovery And Ticket Creation
|
|
82
84
|
|
|
83
85
|
When creating tickets from within a repository:
|
|
86
|
+
|
|
84
87
|
- Prefer `create` by default for draft ticket creation.
|
|
85
88
|
- Use `prompt` only when the user explicitly asks to start execution immediately.
|
|
86
89
|
- Both commands can resolve the project from the current working directory; use `--working-directory` to override.
|
|
@@ -100,7 +103,7 @@ Pass `--execution-target agent` or `--execution-target human` (default: `human`)
|
|
|
100
103
|
- **`agent`** — any task an AI agent can complete in a computer environment: coding, internet research, document editing, data analysis, automated testing, etc.
|
|
101
104
|
- **`human`** — any task requiring human presence or judgment: setting credentials or tokens in a third-party UI (e.g. Vercel, AWS), sending physical mail, making a product or business decision, physical-world actions.
|
|
102
105
|
|
|
103
|
-
When in doubt, ask yourself:
|
|
106
|
+
When in doubt, ask yourself: _can this be done entirely inside a terminal or browser by an AI without human intervention?_ If yes → `agent`. If it requires a human to log in, decide, or act in the real world → `human`.
|
|
104
107
|
|
|
105
108
|
## Change Rationales
|
|
106
109
|
|
|
@@ -155,4 +158,4 @@ The `attach` and `load-context` responses already include `attachments` and `obj
|
|
|
155
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.
|
|
156
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.
|
|
157
160
|
|
|
158
|
-
<!-- version: 0.4.
|
|
161
|
+
<!-- version: 0.4.8 -->
|