overlord-cli 3.24.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/bin/_cli/auth.mjs +12 -6
- package/bin/_cli/credentials.mjs +36 -20
- package/bin/_cli/launcher.mjs +24 -5
- package/bin/_cli/postinstall.mjs +4 -3
- package/bin/_cli/setup.mjs +114 -80
- package/package.json +1 -1
- package/plugins/claude/.claude-plugin/plugin.json +34 -0
- package/plugins/claude/README.md +41 -0
- package/plugins/claude/commands/connect.md +18 -0
- package/plugins/claude/commands/load.md +18 -0
- package/plugins/claude/commands/spawn.md +16 -0
- package/plugins/claude/hooks/hooks.json +15 -0
- package/plugins/claude/scripts/permission-hook.sh +21 -0
- package/plugins/claude/skills/overlord-ticket-workflow/SKILL.md +104 -0
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ ovld protocol discover-project
|
|
|
37
37
|
ovld protocol attach --ticket-id <ticket-id>
|
|
38
38
|
ovld protocol update --session-key <session-key> --ticket-id <ticket-id> --summary "Working on it" --phase execute
|
|
39
39
|
ovld setup codex
|
|
40
|
+
ovld setup claude
|
|
40
41
|
ovld setup cursor
|
|
41
42
|
ovld setup gemini
|
|
42
43
|
ovld setup all
|
|
@@ -59,7 +60,7 @@ ovld doctor
|
|
|
59
60
|
- `protocol` - run ticket lifecycle commands such as `discover-project`, `attach`, `connect`, `load-context`, `spawn`, `update`, `record-change-rationales`, `ask`, `read-context`, `write-context`, `deliver`, and artifact upload/download helpers
|
|
60
61
|
- `connect`, `restart`, `context` - launch or resume an agent session or print ticket context
|
|
61
62
|
- `run`, `resume` - legacy aliases for `connect` and `restart`
|
|
62
|
-
- `setup` -
|
|
63
|
+
- `setup` - prepare the Overlord plugin or connector for a supported agent; `ovld setup claude` performs the one-time v3.25.0 to v4 Claude plugin migration
|
|
63
64
|
- `update` - install the latest CLI release from npm
|
|
64
65
|
- `doctor` - verify installed agent connectors and check whether a newer CLI version is available
|
|
65
66
|
|
package/bin/_cli/auth.mjs
CHANGED
|
@@ -5,9 +5,15 @@ import { execFileSync } from 'node:child_process';
|
|
|
5
5
|
import crypto from 'node:crypto';
|
|
6
6
|
import http from 'node:http';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
buildAuthHeaders,
|
|
10
|
+
clearCredentials,
|
|
11
|
+
getDefaultOverlordUrl,
|
|
12
|
+
loadCredentials,
|
|
13
|
+
loadRuntime,
|
|
14
|
+
saveCredentials
|
|
15
|
+
} from './credentials.mjs';
|
|
9
16
|
|
|
10
|
-
const DEFAULT_OVERLORD_URL = process.env.OVERLORD_URL ?? 'https://ovld.ai';
|
|
11
17
|
const DEFAULT_CLI_REDIRECT_URI = 'http://127.0.0.1:45619/callback';
|
|
12
18
|
const DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;
|
|
13
19
|
|
|
@@ -412,13 +418,13 @@ export async function authLoginViaOAuthLoopback(platformUrl, localSecret) {
|
|
|
412
418
|
// Public auth commands
|
|
413
419
|
// ---------------------------------------------------------------------------
|
|
414
420
|
|
|
415
|
-
export function resolveLoginPlatformUrl(runtime =
|
|
416
|
-
return process.env.OVERLORD_URL ?? runtime?.platform_url ??
|
|
421
|
+
export function resolveLoginPlatformUrl(runtime = null) {
|
|
422
|
+
return process.env.OVERLORD_URL ?? runtime?.platform_url ?? getDefaultOverlordUrl();
|
|
417
423
|
}
|
|
418
424
|
|
|
419
425
|
export async function authLogin() {
|
|
420
|
-
const
|
|
421
|
-
const
|
|
426
|
+
const platformUrl = resolveLoginPlatformUrl();
|
|
427
|
+
const runtime = loadRuntime(platformUrl);
|
|
422
428
|
const localSecret = runtime?.local_secret ?? process.env.OVERLORD_LOCAL_SECRET ?? '';
|
|
423
429
|
|
|
424
430
|
console.log('Starting Overlord CLI authorization...\n');
|
package/bin/_cli/credentials.mjs
CHANGED
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import os from 'node:os';
|
|
7
7
|
import path from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
8
9
|
|
|
9
10
|
const CREDENTIALS_DIR = path.join(os.homedir(), '.ovld');
|
|
10
11
|
const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, 'credentials.json');
|
|
11
12
|
const RUNTIME_FILE_PATTERN = /^runtime\..+\.json$/;
|
|
12
|
-
const
|
|
13
|
+
const HOSTED_OVERLORD_URL = 'https://www.ovld.ai';
|
|
14
|
+
const LOCAL_DEV_OVERLORD_URL = 'http://localhost:3000';
|
|
13
15
|
const LOCAL_SECRET_HEADER = 'X-Overlord-Local-Secret';
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -139,6 +141,14 @@ function isLocalhostUrl(value) {
|
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
|
|
144
|
+
function isLocalDevOverlordUrl(value) {
|
|
145
|
+
try {
|
|
146
|
+
return new URL(value).origin === LOCAL_DEV_OVERLORD_URL;
|
|
147
|
+
} catch {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
142
152
|
function isSupportedPlatformUrl(value) {
|
|
143
153
|
try {
|
|
144
154
|
const parsed = new URL(value);
|
|
@@ -198,37 +208,33 @@ export function buildAuthHeaders(token, localSecret) {
|
|
|
198
208
|
return headers;
|
|
199
209
|
}
|
|
200
210
|
|
|
211
|
+
export function getDefaultOverlordUrl() {
|
|
212
|
+
return isLocalDevCli() ? LOCAL_DEV_OVERLORD_URL : HOSTED_OVERLORD_URL;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function isLocalDevCli() {
|
|
216
|
+
const sourcePath = fileURLToPath(import.meta.url);
|
|
217
|
+
return !sourcePath.split(path.sep).includes('node_modules');
|
|
218
|
+
}
|
|
219
|
+
|
|
201
220
|
/**
|
|
202
221
|
* Resolve the overlord URL and agent token from credentials file or env vars.
|
|
203
222
|
* @returns {{ platformUrl: string, agentToken: string }}
|
|
204
223
|
*/
|
|
205
224
|
export function resolveAuth() {
|
|
206
225
|
const creds = loadCredentials();
|
|
207
|
-
const connectorUrlFromEnv = normalizePlatformUrl(process.env.OVERLORD_CONNECTOR_URL);
|
|
208
226
|
const overlordUrlFromEnv = normalizePlatformUrl(process.env.OVERLORD_URL);
|
|
209
|
-
const overlordUrlFromCreds =
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
: null;
|
|
215
|
-
const targetedRuntime = loadRuntime(runtimeTarget ?? null);
|
|
216
|
-
const fallbackRuntime = targetedRuntime ?? loadRuntime(null);
|
|
217
|
-
const runtime =
|
|
218
|
-
targetedRuntime && isLocalhostUrl(targetedRuntime.platform_url)
|
|
219
|
-
? targetedRuntime
|
|
220
|
-
: fallbackRuntime && isLocalhostUrl(fallbackRuntime.platform_url)
|
|
221
|
-
? fallbackRuntime
|
|
222
|
-
: targetedRuntime;
|
|
227
|
+
const overlordUrlFromCreds = normalizeStoredPlatformUrl(creds?.platform_url);
|
|
228
|
+
|
|
229
|
+
const runtime = overlordUrlFromEnv && isLocalhostUrl(overlordUrlFromEnv)
|
|
230
|
+
? loadRuntime(overlordUrlFromEnv)
|
|
231
|
+
: null;
|
|
223
232
|
const runtimeOverlordUrl = runtime?.platform_url;
|
|
224
233
|
|
|
225
234
|
const platformUrl =
|
|
226
|
-
connectorUrlFromEnv ??
|
|
227
|
-
(overlordUrlFromEnv && isLocalhostUrl(overlordUrlFromEnv) ? overlordUrlFromEnv : undefined) ??
|
|
228
|
-
runtimeOverlordUrl ??
|
|
229
235
|
overlordUrlFromEnv ??
|
|
230
236
|
overlordUrlFromCreds ??
|
|
231
|
-
|
|
237
|
+
getDefaultOverlordUrl();
|
|
232
238
|
const localSecret =
|
|
233
239
|
runtime &&
|
|
234
240
|
runtime.local_secret &&
|
|
@@ -260,8 +266,18 @@ function normalizePlatformUrl(value) {
|
|
|
260
266
|
try {
|
|
261
267
|
const parsed = new URL(trimmed);
|
|
262
268
|
if (!isSupportedPlatformUrl(parsed.toString())) return undefined;
|
|
269
|
+
if (parsed.protocol === 'https:' && parsed.hostname === 'ovld.ai') {
|
|
270
|
+
return HOSTED_OVERLORD_URL;
|
|
271
|
+
}
|
|
263
272
|
return parsed.origin;
|
|
264
273
|
} catch {
|
|
265
274
|
return undefined;
|
|
266
275
|
}
|
|
267
276
|
}
|
|
277
|
+
|
|
278
|
+
function normalizeStoredPlatformUrl(value) {
|
|
279
|
+
const normalized = normalizePlatformUrl(value);
|
|
280
|
+
if (!normalized) return undefined;
|
|
281
|
+
if (isLocalhostUrl(normalized) && !isLocalDevOverlordUrl(normalized)) return undefined;
|
|
282
|
+
return normalized;
|
|
283
|
+
}
|
package/bin/_cli/launcher.mjs
CHANGED
|
@@ -9,10 +9,25 @@ import { execFileSync } from 'node:child_process';
|
|
|
9
9
|
import fs from 'node:fs';
|
|
10
10
|
import os from 'node:os';
|
|
11
11
|
import path from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
12
13
|
import { buildAuthHeaders, resolveAuth } from './credentials.mjs';
|
|
13
14
|
import { runAttachCommand } from './attach.mjs';
|
|
14
15
|
|
|
16
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const PACKAGE_CLAUDE_PLUGIN_DIR = path.resolve(__dirname, '..', '..', 'plugins', 'claude');
|
|
18
|
+
const REPO_CLAUDE_PLUGIN_DIR = path.resolve(__dirname, '..', '..', '..', '..', 'plugins', 'claude');
|
|
19
|
+
|
|
20
|
+
function claudeSourcePluginDir() {
|
|
21
|
+
if (fs.existsSync(PACKAGE_CLAUDE_PLUGIN_DIR)) return PACKAGE_CLAUDE_PLUGIN_DIR;
|
|
22
|
+
if (fs.existsSync(REPO_CLAUDE_PLUGIN_DIR)) return REPO_CLAUDE_PLUGIN_DIR;
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
function getInstructionMode(agent) {
|
|
27
|
+
if (agent === 'claude') {
|
|
28
|
+
return claudeSourcePluginDir() ? 'bundle' : 'legacy';
|
|
29
|
+
}
|
|
30
|
+
|
|
16
31
|
if (agent === 'codex') {
|
|
17
32
|
const pluginManifest = path.join(
|
|
18
33
|
os.homedir(),
|
|
@@ -108,20 +123,24 @@ async function runAgent(agent, mode = 'run') {
|
|
|
108
123
|
|
|
109
124
|
try {
|
|
110
125
|
if (agent === 'claude') {
|
|
126
|
+
const pluginDir = claudeSourcePluginDir();
|
|
111
127
|
if (mode === 'resume') {
|
|
112
128
|
const claudeSessionId = process.env.CLAUDE_SESSION_ID?.trim();
|
|
113
129
|
const args = claudeSessionId
|
|
114
130
|
? ['--resume', claudeSessionId, context]
|
|
115
131
|
: ['--continue', context];
|
|
132
|
+
if (pluginDir) args.unshift('--plugin-dir', pluginDir);
|
|
116
133
|
execFileSync('claude', args, { stdio: 'inherit', env: childEnv });
|
|
117
134
|
} else {
|
|
135
|
+
const args = [
|
|
136
|
+
'--append-system-prompt',
|
|
137
|
+
context,
|
|
138
|
+
'Begin working on this ticket. Start by calling the attach endpoint, then proceed with the objective described in your system prompt.'
|
|
139
|
+
];
|
|
140
|
+
if (pluginDir) args.unshift('--plugin-dir', pluginDir);
|
|
118
141
|
execFileSync(
|
|
119
142
|
'claude',
|
|
120
|
-
|
|
121
|
-
'--append-system-prompt',
|
|
122
|
-
context,
|
|
123
|
-
'Begin working on this ticket. Start by calling the attach endpoint, then proceed with the objective described in your system prompt.'
|
|
124
|
-
],
|
|
143
|
+
args,
|
|
125
144
|
{ stdio: 'inherit', env: childEnv }
|
|
126
145
|
);
|
|
127
146
|
}
|
package/bin/_cli/postinstall.mjs
CHANGED
|
@@ -27,15 +27,16 @@ const bold = s => `\x1b[1m${s}\x1b[0m`;
|
|
|
27
27
|
console.log(`
|
|
28
28
|
${green('✓')} Overlord CLI installed successfully!
|
|
29
29
|
|
|
30
|
-
${bold('Next step:')}
|
|
30
|
+
${bold('Next step:')} Prepare agent plugins and connectors
|
|
31
31
|
|
|
32
32
|
${cyan('ovld setup')}
|
|
33
33
|
|
|
34
34
|
This will guide you through:
|
|
35
|
-
• Selecting which agent connectors to
|
|
35
|
+
• Selecting which agent plugins/connectors to prepare (Claude, Cursor, etc.)
|
|
36
|
+
• Migrating Claude from the v3.25 connector files to the v4 plugin
|
|
36
37
|
• Configuring agent permissions for Overlord protocol access
|
|
37
38
|
|
|
38
|
-
You can also run ${cyan('ovld setup <agent>')} to
|
|
39
|
+
You can also run ${cyan('ovld setup <agent>')} to prepare a specific agent,
|
|
39
40
|
or ${cyan('ovld doctor')} to check your installation status.
|
|
40
41
|
|
|
41
42
|
Run ${cyan('ovld help')} to see all available commands.
|
package/bin/_cli/setup.mjs
CHANGED
|
@@ -22,6 +22,8 @@ const MANIFEST_FILE = path.join(MANIFEST_DIR, 'bundle-manifest.json');
|
|
|
22
22
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
23
|
const PACKAGE_PLUGIN_DIR = path.resolve(__dirname, '..', '..', 'plugins', 'overlord');
|
|
24
24
|
const REPO_PLUGIN_DIR = path.resolve(__dirname, '..', '..', '..', '..', 'plugins', 'overlord');
|
|
25
|
+
const PACKAGE_CLAUDE_PLUGIN_DIR = path.resolve(__dirname, '..', '..', 'plugins', 'claude');
|
|
26
|
+
const REPO_CLAUDE_PLUGIN_DIR = path.resolve(__dirname, '..', '..', '..', '..', 'plugins', 'claude');
|
|
25
27
|
const CODEX_TARGET_PLUGIN_DIR = path.join(os.homedir(), '.codex', 'plugins', 'overlord');
|
|
26
28
|
const CODEX_TARGET_PLUGIN_MANIFEST = path.join(
|
|
27
29
|
CODEX_TARGET_PLUGIN_DIR,
|
|
@@ -459,11 +461,22 @@ function installSlashCommands(agent) {
|
|
|
459
461
|
};
|
|
460
462
|
}
|
|
461
463
|
|
|
464
|
+
function uninstallSlashCommands(agent) {
|
|
465
|
+
const removedFiles = [];
|
|
466
|
+
const files = slashCommandFiles(agent);
|
|
467
|
+
for (const file of files) {
|
|
468
|
+
if (!fs.existsSync(file.path)) continue;
|
|
469
|
+
const existing = readTextFile(file.path);
|
|
470
|
+
if (existing.trim() !== file.content.trim()) continue;
|
|
471
|
+
fs.rmSync(file.path, { force: true });
|
|
472
|
+
removedFiles.push(file.path);
|
|
473
|
+
}
|
|
474
|
+
return { removedFiles };
|
|
475
|
+
}
|
|
476
|
+
|
|
462
477
|
function currentContentHashForAgent(agent) {
|
|
463
478
|
if (agent === 'claude') {
|
|
464
|
-
return
|
|
465
|
-
[CLAUDE_SKILL_CONTENT, PERMISSION_HOOK_SCRIPT, ...slashCommandFiles('claude').map(file => file.content)].join('\n')
|
|
466
|
-
);
|
|
479
|
+
return claudeContentHash();
|
|
467
480
|
}
|
|
468
481
|
if (agent === 'cursor') {
|
|
469
482
|
return contentHash(
|
|
@@ -487,6 +500,14 @@ function codexSourcePluginDir() {
|
|
|
487
500
|
);
|
|
488
501
|
}
|
|
489
502
|
|
|
503
|
+
function claudeSourcePluginDir() {
|
|
504
|
+
if (fs.existsSync(PACKAGE_CLAUDE_PLUGIN_DIR)) return PACKAGE_CLAUDE_PLUGIN_DIR;
|
|
505
|
+
if (fs.existsSync(REPO_CLAUDE_PLUGIN_DIR)) return REPO_CLAUDE_PLUGIN_DIR;
|
|
506
|
+
throw new Error(
|
|
507
|
+
`Claude plugin bundle not found. Checked ${PACKAGE_CLAUDE_PLUGIN_DIR} and ${REPO_CLAUDE_PLUGIN_DIR}.`
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
490
511
|
function listFilesRecursive(dir) {
|
|
491
512
|
if (!fs.existsSync(dir)) return [];
|
|
492
513
|
return fs.readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
|
|
@@ -496,8 +517,7 @@ function listFilesRecursive(dir) {
|
|
|
496
517
|
});
|
|
497
518
|
}
|
|
498
519
|
|
|
499
|
-
function
|
|
500
|
-
const sourceDir = codexSourcePluginDir();
|
|
520
|
+
function contentHashForDirectory(sourceDir) {
|
|
501
521
|
const hash = crypto.createHash('sha256');
|
|
502
522
|
|
|
503
523
|
for (const filePath of listFilesRecursive(sourceDir).sort()) {
|
|
@@ -510,6 +530,14 @@ function codexContentHash() {
|
|
|
510
530
|
return hash.digest('hex').slice(0, 16);
|
|
511
531
|
}
|
|
512
532
|
|
|
533
|
+
function codexContentHash() {
|
|
534
|
+
return contentHashForDirectory(codexSourcePluginDir());
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function claudeContentHash() {
|
|
538
|
+
return contentHashForDirectory(claudeSourcePluginDir());
|
|
539
|
+
}
|
|
540
|
+
|
|
513
541
|
function mergeCodexRules(existingContent) {
|
|
514
542
|
const managedBlock = [
|
|
515
543
|
CODEX_RULES_START,
|
|
@@ -621,6 +649,62 @@ function removeLegacyCodexBundle() {
|
|
|
621
649
|
writeManifest(manifest);
|
|
622
650
|
}
|
|
623
651
|
|
|
652
|
+
function removeLegacyClaudeBundle() {
|
|
653
|
+
const paths = claudePaths();
|
|
654
|
+
const removed = [];
|
|
655
|
+
|
|
656
|
+
if (fs.existsSync(paths.skillDir)) {
|
|
657
|
+
fs.rmSync(paths.skillDir, { recursive: true, force: true });
|
|
658
|
+
removed.push(paths.skillDir);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
if (fs.existsSync(paths.hookScript)) {
|
|
662
|
+
fs.rmSync(paths.hookScript, { force: true });
|
|
663
|
+
removed.push(paths.hookScript);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
const slashResult = uninstallSlashCommands('claude');
|
|
667
|
+
removed.push(...slashResult.removedFiles);
|
|
668
|
+
|
|
669
|
+
if (fs.existsSync(paths.settingsFile)) {
|
|
670
|
+
const settings = readJsonFile(paths.settingsFile);
|
|
671
|
+
let changed = false;
|
|
672
|
+
|
|
673
|
+
if (settings.__overlord_managed) {
|
|
674
|
+
delete settings.__overlord_managed;
|
|
675
|
+
changed = true;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const hooks = settings.hooks && typeof settings.hooks === 'object' ? settings.hooks : {};
|
|
679
|
+
if (Array.isArray(hooks.PermissionRequest)) {
|
|
680
|
+
const nextPermissionHooks = hooks.PermissionRequest.filter(hook => {
|
|
681
|
+
if (hook && typeof hook === 'object' && Array.isArray(hook.hooks)) {
|
|
682
|
+
return !hook.hooks.some(
|
|
683
|
+
inner =>
|
|
684
|
+
typeof inner?.command === 'string' &&
|
|
685
|
+
inner.command.includes('overlord-permission-hook')
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
return true;
|
|
689
|
+
});
|
|
690
|
+
if (nextPermissionHooks.length !== hooks.PermissionRequest.length) {
|
|
691
|
+
changed = true;
|
|
692
|
+
if (nextPermissionHooks.length > 0) hooks.PermissionRequest = nextPermissionHooks;
|
|
693
|
+
else delete hooks.PermissionRequest;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
if (changed) {
|
|
698
|
+
if (Object.keys(hooks).length > 0) settings.hooks = hooks;
|
|
699
|
+
else delete settings.hooks;
|
|
700
|
+
writeJsonFile(paths.settingsFile, settings);
|
|
701
|
+
removed.push(paths.settingsFile);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
return removed;
|
|
706
|
+
}
|
|
707
|
+
|
|
624
708
|
// ---------------------------------------------------------------------------
|
|
625
709
|
// Install
|
|
626
710
|
// ---------------------------------------------------------------------------
|
|
@@ -660,82 +744,30 @@ function geminiPaths() {
|
|
|
660
744
|
}
|
|
661
745
|
|
|
662
746
|
function installClaude() {
|
|
663
|
-
const
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
console.log(` ✓
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
// 3. Merge hook into settings.json
|
|
675
|
-
const backup = backupFile(paths.settingsFile);
|
|
676
|
-
if (backup) {
|
|
677
|
-
backups.push(backup);
|
|
678
|
-
console.log(` ✓ Backed up: ${paths.settingsFile} → ${path.basename(backup)}`);
|
|
747
|
+
const sourceDir = claudeSourcePluginDir();
|
|
748
|
+
const sourceManifest = path.join(sourceDir, '.claude-plugin', 'plugin.json');
|
|
749
|
+
const sourceVersion = pluginVersion(sourceManifest) ?? '0.0.0';
|
|
750
|
+
const removed = removeLegacyClaudeBundle();
|
|
751
|
+
|
|
752
|
+
console.log(` ✓ Found Claude plugin source: ${sourceDir}`);
|
|
753
|
+
if (removed.length > 0) {
|
|
754
|
+
console.log(' ✓ Migrated v3.25 Claude connector files:');
|
|
755
|
+
for (const filePath of removed) console.log(` ${filePath}`);
|
|
756
|
+
} else {
|
|
757
|
+
console.log(' ✓ No v3.25 Claude connector files needed migration.');
|
|
679
758
|
}
|
|
759
|
+
console.log(' ✓ `ovld connect claude` now loads this plugin with `claude --plugin-dir`.');
|
|
680
760
|
|
|
681
|
-
const existingSettings = readJsonFile(paths.settingsFile);
|
|
682
|
-
const overlordHook = {
|
|
683
|
-
matcher: '.*',
|
|
684
|
-
hooks: [{ type: 'command', command: paths.hookScript }]
|
|
685
|
-
};
|
|
686
|
-
|
|
687
|
-
const existingHooks = existingSettings.hooks ?? {};
|
|
688
|
-
const existingPermHooks = Array.isArray(existingHooks.PermissionRequest)
|
|
689
|
-
? existingHooks.PermissionRequest
|
|
690
|
-
: [];
|
|
691
|
-
const existingPermissions =
|
|
692
|
-
existingSettings.permissions && typeof existingSettings.permissions === 'object'
|
|
693
|
-
? existingSettings.permissions
|
|
694
|
-
: {};
|
|
695
|
-
const mergedAllow = Array.from(
|
|
696
|
-
new Set([
|
|
697
|
-
...asStringArray(existingPermissions.allow),
|
|
698
|
-
'Bash(ovld protocol:*)',
|
|
699
|
-
'Bash(curl -sS -X POST:*)'
|
|
700
|
-
])
|
|
701
|
-
);
|
|
702
|
-
|
|
703
|
-
// Remove existing Overlord hooks
|
|
704
|
-
const filteredPermHooks = existingPermHooks.filter(hook => {
|
|
705
|
-
if (hook && typeof hook === 'object' && hook.hooks) {
|
|
706
|
-
return !hook.hooks.some(
|
|
707
|
-
inner =>
|
|
708
|
-
typeof inner.command === 'string' && inner.command.includes('overlord-permission-hook')
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
return true;
|
|
712
|
-
});
|
|
713
|
-
|
|
714
|
-
const merged = deepClone(existingSettings);
|
|
715
|
-
merged.hooks = { ...existingHooks, PermissionRequest: [...filteredPermHooks, overlordHook] };
|
|
716
|
-
merged.permissions = { ...existingPermissions, allow: mergedAllow };
|
|
717
|
-
merged.__overlord_managed = {
|
|
718
|
-
version: BUNDLE_VERSION,
|
|
719
|
-
paths: ['hooks.PermissionRequest', 'permissions.allow'],
|
|
720
|
-
updatedAt: new Date().toISOString()
|
|
721
|
-
};
|
|
722
|
-
writeJsonFile(paths.settingsFile, merged);
|
|
723
|
-
console.log(` ✓ Merged hook into: ${paths.settingsFile}`);
|
|
724
|
-
|
|
725
|
-
const slashResult = installSlashCommands('claude');
|
|
726
|
-
backups.push(...slashResult.backups);
|
|
727
|
-
|
|
728
|
-
// 4. Update manifest
|
|
729
761
|
const manifest = readManifest();
|
|
730
762
|
manifest.claude = {
|
|
731
|
-
version:
|
|
763
|
+
version: sourceVersion,
|
|
732
764
|
contentHash: currentContentHashForAgent('claude'),
|
|
733
765
|
installedAt: new Date().toISOString(),
|
|
734
|
-
files:
|
|
766
|
+
files: listFilesRecursive(sourceDir)
|
|
735
767
|
};
|
|
736
768
|
writeManifest(manifest);
|
|
737
769
|
|
|
738
|
-
return { ok: true, backups };
|
|
770
|
+
return { ok: true, backups: [] };
|
|
739
771
|
}
|
|
740
772
|
|
|
741
773
|
function installOpenCode() {
|
|
@@ -923,7 +955,9 @@ function doctorAgent(agent) {
|
|
|
923
955
|
}
|
|
924
956
|
|
|
925
957
|
const currentVersion =
|
|
926
|
-
agent === '
|
|
958
|
+
agent === 'claude'
|
|
959
|
+
? pluginVersion(path.join(claudeSourcePluginDir(), '.claude-plugin', 'plugin.json'))
|
|
960
|
+
: agent === 'codex'
|
|
927
961
|
? pluginVersion(path.join(codexSourcePluginDir(), '.codex-plugin', 'plugin.json'))
|
|
928
962
|
: BUNDLE_VERSION;
|
|
929
963
|
const currentHash = currentContentHashForAgent(agent);
|
|
@@ -1346,12 +1380,12 @@ export async function runSetupCommand(args) {
|
|
|
1346
1380
|
if (agent === '--help' || agent === '-h' || agent === 'help') {
|
|
1347
1381
|
console.log(`Usage:
|
|
1348
1382
|
ovld setup Interactive setup (select agents and configure permissions)
|
|
1349
|
-
ovld setup claude
|
|
1383
|
+
ovld setup claude Prepare the Overlord Claude plugin and migrate v3.25 connector files
|
|
1350
1384
|
ovld setup codex Install Overlord Codex plugin bundle
|
|
1351
1385
|
ovld setup cursor Install Overlord rules, slash commands, and permissions for Cursor
|
|
1352
1386
|
ovld setup gemini Install Overlord slash commands and policy rules for Gemini CLI
|
|
1353
1387
|
ovld setup opencode Install Overlord connector for OpenCode
|
|
1354
|
-
ovld setup all
|
|
1388
|
+
ovld setup all Prepare all supported agents
|
|
1355
1389
|
ovld doctor Validate installed connectors and check for CLI updates`);
|
|
1356
1390
|
return;
|
|
1357
1391
|
}
|
|
@@ -1373,7 +1407,7 @@ export async function runSetupCommand(args) {
|
|
|
1373
1407
|
});
|
|
1374
1408
|
|
|
1375
1409
|
const selectedLabels = await runCheckboxPrompt({
|
|
1376
|
-
message: 'Select agent connectors to
|
|
1410
|
+
message: 'Select agent plugins/connectors to prepare (Space to toggle, Enter to confirm):',
|
|
1377
1411
|
choices: agentLabels,
|
|
1378
1412
|
defaults: []
|
|
1379
1413
|
});
|
|
@@ -1387,7 +1421,7 @@ export async function runSetupCommand(args) {
|
|
|
1387
1421
|
const selectedAgents = selectedLabels.map(label => label.split('-')[0].trim());
|
|
1388
1422
|
|
|
1389
1423
|
// Step 2: Install selected agents
|
|
1390
|
-
console.log(`\
|
|
1424
|
+
console.log(`\nPreparing Overlord agent plugins/connectors for: ${selectedAgents.join(', ')}...\n`);
|
|
1391
1425
|
|
|
1392
1426
|
const installedAgents = [];
|
|
1393
1427
|
for (const a of selectedAgents) {
|
|
@@ -1416,7 +1450,7 @@ export async function runSetupCommand(args) {
|
|
|
1416
1450
|
);
|
|
1417
1451
|
|
|
1418
1452
|
if (agentsThatNeedPermissions.length > 0) {
|
|
1419
|
-
console.log('Agent connectors
|
|
1453
|
+
console.log('Agent plugins/connectors prepared successfully!\n');
|
|
1420
1454
|
|
|
1421
1455
|
const shouldInstallPermissions = await askYesNo(
|
|
1422
1456
|
'Would you like to configure agent permissions for Overlord protocol access?',
|
|
@@ -1439,7 +1473,7 @@ export async function runSetupCommand(args) {
|
|
|
1439
1473
|
}
|
|
1440
1474
|
|
|
1441
1475
|
if (agent === 'all') {
|
|
1442
|
-
console.log('
|
|
1476
|
+
console.log('Preparing Overlord agent plugins/connectors for all supported agents...\n');
|
|
1443
1477
|
const installedAgents = [];
|
|
1444
1478
|
|
|
1445
1479
|
for (const a of supportedAgents) {
|
|
@@ -1485,7 +1519,7 @@ export async function runSetupCommand(args) {
|
|
|
1485
1519
|
process.exit(1);
|
|
1486
1520
|
}
|
|
1487
1521
|
|
|
1488
|
-
console.log(`
|
|
1522
|
+
console.log(`Preparing Overlord agent plugin/connector for ${agent}...\n`);
|
|
1489
1523
|
try {
|
|
1490
1524
|
if (agent === 'claude') installClaude();
|
|
1491
1525
|
else if (agent === 'codex') installCodex();
|
package/package.json
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "overlord",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Overlord ticket protocol workflow for Claude Code — attach/update/ask/deliver, slash commands, and a PermissionRequest notifier.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Cooperativ",
|
|
7
|
+
"url": "https://github.com/cooperativ"
|
|
8
|
+
},
|
|
9
|
+
"homepage": "https://www.ovld.ai",
|
|
10
|
+
"repository": "https://github.com/cooperativ/overlord",
|
|
11
|
+
"license": "UNLICENSED",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"overlord",
|
|
14
|
+
"tickets",
|
|
15
|
+
"agents",
|
|
16
|
+
"workflow",
|
|
17
|
+
"protocol",
|
|
18
|
+
"claude-code"
|
|
19
|
+
],
|
|
20
|
+
"userConfig": {
|
|
21
|
+
"overlord_url": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"title": "Overlord URL",
|
|
24
|
+
"description": "Overlord platform URL (e.g. https://www.ovld.ai or http://localhost:3000).",
|
|
25
|
+
"sensitive": false
|
|
26
|
+
},
|
|
27
|
+
"agent_token": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"title": "Agent token",
|
|
30
|
+
"description": "Overlord agent token. Pull from Overlord Settings → Agents & MCP or from ~/.ovld/credentials.json.",
|
|
31
|
+
"sensitive": true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Overlord — Claude Code plugin
|
|
2
|
+
|
|
3
|
+
Claude Code plugin that exposes the Overlord local ticket workflow to any Claude Code surface (CLI, VS Code extension, Claude Code desktop app).
|
|
4
|
+
|
|
5
|
+
## What ships
|
|
6
|
+
|
|
7
|
+
- `skills/overlord-ticket-workflow/SKILL.md` — durable attach → update → ask → deliver workflow.
|
|
8
|
+
- `commands/{connect,load,spawn}.md` — slash commands for session routing and ticket creation.
|
|
9
|
+
- `hooks/hooks.json` + `scripts/permission-hook.sh` — PermissionRequest notifier that posts to `/api/protocol/permission-request` on the Overlord platform.
|
|
10
|
+
- `userConfig` for `overlord_url` (non-sensitive) and `agent_token` (sensitive → OS keychain) so the hook and CLI know where to talk.
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
- Overlord CLI (`ovld`) on `PATH`. See the Overlord docs / Settings → Agents & MCP.
|
|
15
|
+
- `TICKET_ID` is exported automatically when a Claude session is launched from Overlord; outside that launch, set it manually before calling the permission hook.
|
|
16
|
+
|
|
17
|
+
## Install (local dev)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
claude --plugin-dir /absolute/path/to/overlord/plugins/claude
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Install (marketplace)
|
|
24
|
+
|
|
25
|
+
Once published:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
claude plugin marketplace add cooperativ/overlord-marketplace
|
|
29
|
+
claude plugin install overlord@cooperativ
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The plugin prompts for `overlord_url` and `agent_token` at install time. The token is persisted to the OS keychain; the URL is stored in `~/.claude/settings.json` under `pluginConfigs["overlord"].options`.
|
|
33
|
+
|
|
34
|
+
## Namespaced components
|
|
35
|
+
|
|
36
|
+
Inside Claude Code the components are surfaced with the plugin prefix:
|
|
37
|
+
|
|
38
|
+
- skill → `overlord:overlord-ticket-workflow`
|
|
39
|
+
- commands → `/overlord:connect`, `/overlord:load`, `/overlord:spawn`
|
|
40
|
+
|
|
41
|
+
Prompts generated by Overlord (see `lib/overlord/ticket-prompt.ts`) already reference these names in `bundle` instruction mode.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Connect this session to another Overlord ticket by ticket ID
|
|
3
|
+
argument-hint: <ticket-id>
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Connect this session to another Overlord ticket.
|
|
8
|
+
|
|
9
|
+
Treat `$ARGUMENTS` as the target ticket ID.
|
|
10
|
+
If no ticket ID was provided, ask the user for one and stop.
|
|
11
|
+
|
|
12
|
+
Run:
|
|
13
|
+
`ovld protocol connect --ticket-id <ticketId>`
|
|
14
|
+
|
|
15
|
+
Rules:
|
|
16
|
+
- Use `connect`, not `attach`.
|
|
17
|
+
- Do not load extra ticket context unless the user explicitly asks for it.
|
|
18
|
+
- After the command succeeds, report the returned `SESSION_KEY` and confirm that future updates should use that ticket.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Load Overlord ticket context without creating a new session
|
|
3
|
+
argument-hint: <ticket-id>
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Load Overlord ticket context without attaching to the ticket.
|
|
8
|
+
|
|
9
|
+
Treat `$ARGUMENTS` as the target ticket ID.
|
|
10
|
+
If no ticket ID was provided, ask the user for one and stop.
|
|
11
|
+
|
|
12
|
+
Run:
|
|
13
|
+
`ovld protocol load-context --ticket-id <ticketId>`
|
|
14
|
+
|
|
15
|
+
Rules:
|
|
16
|
+
- Use `load-context`, not `attach`.
|
|
17
|
+
- Do not create or switch sessions.
|
|
18
|
+
- Summarize the returned ticket details, history, artifacts, and shared context for the user.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Create a new Overlord ticket from the current conversation
|
|
3
|
+
argument-hint: <objective or raw flags>
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Create a new Overlord ticket from the user's request.
|
|
8
|
+
|
|
9
|
+
Use `$ARGUMENTS` as the input.
|
|
10
|
+
If it already contains flags such as `--title`, `--priority`, `--project-id`, or `--execution-target`, pass those flags through after `ovld protocol spawn`.
|
|
11
|
+
Otherwise, treat `$ARGUMENTS` as the objective text and run:
|
|
12
|
+
`ovld protocol spawn --objective "<objective>"`
|
|
13
|
+
|
|
14
|
+
If no objective was provided, ask the user for one and stop.
|
|
15
|
+
|
|
16
|
+
After the command succeeds, report the new `TICKET_ID` and `SESSION_KEY`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Overlord PermissionRequest notification hook (plugin-managed).
|
|
3
|
+
#
|
|
4
|
+
# Prefers plugin userConfig values (CLAUDE_PLUGIN_OPTION_*) when set, and falls
|
|
5
|
+
# back to the raw OVERLORD_URL / AGENT_TOKEN env vars Overlord-launched shells
|
|
6
|
+
# already export. Silently no-ops if we can't authenticate — the hook must
|
|
7
|
+
# never block the user or leak errors into the Claude session.
|
|
8
|
+
BODY=$(cat -)
|
|
9
|
+
OVERLORD_BASE_URL="${CLAUDE_PLUGIN_OPTION_OVERLORD_URL:-$OVERLORD_URL}"
|
|
10
|
+
OVERLORD_TOKEN="${CLAUDE_PLUGIN_OPTION_AGENT_TOKEN:-$AGENT_TOKEN}"
|
|
11
|
+
if [ -n "$OVERLORD_BASE_URL" ] && [ -n "$OVERLORD_TOKEN" ] && [ -n "$TICKET_ID" ]; then
|
|
12
|
+
curl -sf -m 5 \
|
|
13
|
+
-X POST "$OVERLORD_BASE_URL/api/protocol/permission-request?ticketId=$TICKET_ID" \
|
|
14
|
+
-H "Authorization: Bearer $OVERLORD_TOKEN" \
|
|
15
|
+
-H "X-Overlord-Local-Secret: $OVERLORD_LOCAL_SECRET" \
|
|
16
|
+
-H "Content-Type: application/json" \
|
|
17
|
+
-d "$BODY" \
|
|
18
|
+
>/dev/null 2>&1 &
|
|
19
|
+
disown
|
|
20
|
+
fi
|
|
21
|
+
exit 0
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: overlord-ticket-workflow
|
|
3
|
+
description: Overlord local workflow protocol — attach, update, deliver lifecycle for ticket-driven work from Claude Code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Overlord Local Workflow
|
|
7
|
+
|
|
8
|
+
If you receive a prompt with a specified ticket ID, adhere to the following. If the prompt does not have a ticket ID, the user may choose to add one later, but otherwise, proceed without it.
|
|
9
|
+
|
|
10
|
+
## Lifecycle
|
|
11
|
+
|
|
12
|
+
1. **Attach first** — If there is a TICKET_ID, always call attach before doing any work:
|
|
13
|
+
```bash
|
|
14
|
+
ovld protocol attach --ticket-id $TICKET_ID
|
|
15
|
+
```
|
|
16
|
+
Store `session.sessionKey` from the response — it is required for all subsequent calls.
|
|
17
|
+
|
|
18
|
+
2. **Update during work** — Post at least one progress update before delivering:
|
|
19
|
+
```bash
|
|
20
|
+
ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID --summary "What you did and why." --phase execute
|
|
21
|
+
```
|
|
22
|
+
Phases: `draft`, `execute`, `review`, `deliver`, `complete`, `blocked`, `cancelled`.
|
|
23
|
+
Use `execute` while working.
|
|
24
|
+
|
|
25
|
+
Pass `--event-type <type>` to publish a specific activity event (default: `update`):
|
|
26
|
+
- `update` — standard progress update (default)
|
|
27
|
+
- `user_follow_up` — a message or question from the human user (EXCLUDING THE INITIAL TICKET)
|
|
28
|
+
- `alert` — surface a warning or non-blocking alert
|
|
29
|
+
|
|
30
|
+
3. **Ask when blocked** — Stop working after calling:
|
|
31
|
+
```bash
|
|
32
|
+
ovld protocol ask --session-key <sessionKey> --ticket-id $TICKET_ID --question "Specific question for the PM."
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
4. **Deliver last** — Always deliver when done:
|
|
36
|
+
```bash
|
|
37
|
+
ovld protocol deliver --session-key <sessionKey> \
|
|
38
|
+
--ticket-id $TICKET_ID \
|
|
39
|
+
--summary "Narrative: what you did, next steps." \
|
|
40
|
+
--artifacts-json '[{"type":"next_steps","label":"Next steps","content":"..."}]' \
|
|
41
|
+
--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 @@"}]}]'
|
|
42
|
+
```
|
|
43
|
+
For larger delivery JSON, prefer `--payload-file -` and stream the full payload 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. Do not leave delivery JSON checked into the worktree.
|
|
44
|
+
|
|
45
|
+
## Change Rationales
|
|
46
|
+
|
|
47
|
+
Always include `changeRationales` when delivering. Optionally include them on updates during long-running work.
|
|
48
|
+
|
|
49
|
+
Before delivering, make sure every meaningful git-tracked file change is represented in `changeRationales`; do not send `file_changes` as an artifact.
|
|
50
|
+
|
|
51
|
+
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`.
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
ovld protocol record-change-rationales --session-key <sessionKey> --ticket-id $TICKET_ID \
|
|
55
|
+
--summary "Recorded rationale details for the latest code changes." --phase execute \
|
|
56
|
+
--change-rationales-json '[{"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x.","hunks":[{"header":"@@ -22,4 +22,18 @@"}]}]'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
ovld protocol update --session-key <sessionKey> --ticket-id $TICKET_ID \
|
|
61
|
+
--summary "Added retry logic." --phase execute \
|
|
62
|
+
--change-rationales-json '[{"label":"Add backoff","file_path":"lib/api.ts","summary":"Added retry.","why":"Transient failures.","impact":"Retries 3x.","hunks":[{"header":"@@ -22,4 +22,18 @@"}]}]'
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Record only meaningful behavioral changes — skip formatting-only noise. Prefer 1–5 concise rationales per ticket, each tied to a specific file and diff hunk.
|
|
66
|
+
|
|
67
|
+
## Project Discovery & Ticket Spawning
|
|
68
|
+
|
|
69
|
+
When creating tickets from within a repository, `spawn` automatically resolves the
|
|
70
|
+
correct project by matching your current working directory against each project's
|
|
71
|
+
configured "Local working directory". No `--project-id` flag is needed:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ovld protocol spawn --objective "Implement feature X" --priority medium
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
To discover which project maps to the current directory:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
ovld protocol discover-project
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
You can override with `--project-id` or `--working-directory` if needed.
|
|
84
|
+
|
|
85
|
+
## Context & Artifacts
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
ovld protocol read-context --session-key <sessionKey> --ticket-id $TICKET_ID
|
|
89
|
+
ovld protocol write-context --session-key <sessionKey> --ticket-id $TICKET_ID --key "key" --value '"json-value"'
|
|
90
|
+
ovld protocol artifact-upload-file --session-key <sessionKey> --ticket-id $TICKET_ID --file ./spec.pdf --content-type application/pdf
|
|
91
|
+
ovld protocol artifact-download-url --session-key <sessionKey> --ticket-id $TICKET_ID --artifact-id <artifact-id>
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Rules
|
|
95
|
+
|
|
96
|
+
- Always attach first; always deliver when done.
|
|
97
|
+
- Always communicate with Overlord using the ovld protocol cli commands.
|
|
98
|
+
- Post any substantial updates about your decisions or progress.
|
|
99
|
+
- If blocked on human-only work, call `ask` and request a follow-up human ticket.
|
|
100
|
+
- The `summary` in deliver is what the PM reads first — write it as a narrative, not a command list.
|
|
101
|
+
- Use `write-context` for facts a future agent session should know.
|
|
102
|
+
- **If the user sends you a message during your session, immediately publish a `user_follow_up` activity event with the user's message recorded verbatim in the summary before doing anything else. This DOES NOT apply to the initial ticket.**
|
|
103
|
+
- **Do not add or commit changes (git commit) unless the user explicitly asks you to commit.**
|
|
104
|
+
- **Delivery is the concluding step.** After delivering, stop working. Do not continue unless the user follows up or the ticket is reopened.
|