overlord-cli 3.25.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 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` - install the Overlord connector or plugin bundle for a supported agent
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
 
@@ -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
  }
@@ -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:')} Configure agent connectors
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 install (Claude, Cursor, etc.)
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 install a specific agent connector,
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.
@@ -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 contentHash(
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 codexContentHash() {
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 paths = claudePaths();
664
- const backups = [];
665
-
666
- // 1. Install skill file
667
- writeTextFile(paths.skillFile, CLAUDE_SKILL_CONTENT);
668
- console.log(` ✓ Installed skill: ${paths.skillFile}`);
669
-
670
- // 2. Install permission hook
671
- writeTextFile(paths.hookScript, PERMISSION_HOOK_SCRIPT, 0o755);
672
- console.log(` ✓ Installed hook: ${paths.hookScript}`);
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: BUNDLE_VERSION,
763
+ version: sourceVersion,
732
764
  contentHash: currentContentHashForAgent('claude'),
733
765
  installedAt: new Date().toISOString(),
734
- files: [paths.skillFile, paths.hookScript, paths.settingsFile, ...slashResult.managedFiles]
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 === 'codex'
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 Install Overlord bundle for Claude Code
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 Install for all supported agents
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 install (Space to toggle, Enter to confirm):',
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(`\nInstalling Overlord agent bundles for: ${selectedAgents.join(', ')}...\n`);
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 installed successfully!\n');
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('Installing Overlord agent bundle for all supported agents...\n');
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(`Installing Overlord agent bundle for ${agent}...\n`);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "3.25.0",
3
+ "version": "4.0.0",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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,15 @@
1
+ {
2
+ "hooks": {
3
+ "PermissionRequest": [
4
+ {
5
+ "matcher": ".*",
6
+ "hooks": [
7
+ {
8
+ "type": "command",
9
+ "command": "${CLAUDE_PLUGIN_ROOT}/scripts/permission-hook.sh"
10
+ }
11
+ ]
12
+ }
13
+ ]
14
+ }
15
+ }
@@ -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.