overlord-cli 0.2606021930.0 → 0.2606041130.1

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.
@@ -17,6 +17,21 @@ import { runAttachCommand } from './attach.mjs';
17
17
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
18
18
  const PACKAGE_CLAUDE_PLUGIN_DIR = path.resolve(__dirname, '..', '..', 'plugins', 'claude');
19
19
  const REPO_CLAUDE_PLUGIN_DIR = path.resolve(__dirname, '..', '..', '..', '..', 'plugins', 'claude');
20
+ const CLAUDE_PLUGIN_CACHE_DIR = path.join(
21
+ os.homedir(),
22
+ '.claude',
23
+ 'plugins',
24
+ 'cache',
25
+ 'overlord-local',
26
+ 'overlord'
27
+ );
28
+ const CLAUDE_INSTALLED_PLUGINS_FILE = path.join(
29
+ os.homedir(),
30
+ '.claude',
31
+ 'plugins',
32
+ 'installed_plugins.json'
33
+ );
34
+ const CLAUDE_INSTALLED_PLUGIN_KEY = 'overlord@overlord-local';
20
35
 
21
36
  function resolveLaunchScratchDir(workingDirectory, { explicit = false } = {}) {
22
37
  const trimmed = typeof workingDirectory === 'string' ? workingDirectory.trim() : '';
@@ -41,12 +56,84 @@ function withLaunchTempEnv(baseEnv, scratchDir) {
41
56
  };
42
57
  }
43
58
 
59
+ function contextFileTag(ticketId) {
60
+ const safeTicket = String(ticketId ?? 'ticket').replace(/[^a-zA-Z0-9._-]+/g, '-');
61
+ return `overlord-${safeTicket}-${Date.now()}-${crypto.randomUUID().slice(0, 8)}`;
62
+ }
63
+
64
+ function writeLaunchContextFile(scratchDir, ticketId, context) {
65
+ const contextFile = path.join(scratchDir, `${contextFileTag(ticketId)}-context.md`);
66
+ fs.mkdirSync(path.dirname(contextFile), { recursive: true });
67
+ fs.writeFileSync(contextFile, context, 'utf-8');
68
+ setTimeout(() => {
69
+ try {
70
+ fs.unlinkSync(contextFile);
71
+ } catch {
72
+ // Best-effort cleanup only.
73
+ }
74
+ }, 30 * 60_000).unref();
75
+ return contextFile;
76
+ }
77
+
78
+ function buildContextFilePrompt(contextFile, startPrompt) {
79
+ return `${startPrompt}\n\nRead the Overlord launch context from ${contextFile} before taking action. Follow the ticket workflow and objective described in that file.`;
80
+ }
81
+
44
82
  function claudeSourcePluginDir() {
45
83
  if (fs.existsSync(PACKAGE_CLAUDE_PLUGIN_DIR)) return PACKAGE_CLAUDE_PLUGIN_DIR;
46
84
  if (fs.existsSync(REPO_CLAUDE_PLUGIN_DIR)) return REPO_CLAUDE_PLUGIN_DIR;
47
85
  return null;
48
86
  }
49
87
 
88
+ function readJsonFile(filePath) {
89
+ try {
90
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+
96
+ function claudeInstalledPluginDir() {
97
+ const installed = readJsonFile(CLAUDE_INSTALLED_PLUGINS_FILE);
98
+ const pluginEntries = installed?.plugins?.[CLAUDE_INSTALLED_PLUGIN_KEY];
99
+
100
+ if (Array.isArray(pluginEntries)) {
101
+ for (const entry of pluginEntries) {
102
+ const installPath = typeof entry?.installPath === 'string' ? entry.installPath.trim() : '';
103
+ if (installPath && fs.existsSync(path.join(installPath, '.claude-plugin', 'plugin.json'))) {
104
+ return installPath;
105
+ }
106
+ }
107
+ }
108
+
109
+ if (!fs.existsSync(CLAUDE_PLUGIN_CACHE_DIR)) return null;
110
+
111
+ const versionDirs = fs
112
+ .readdirSync(CLAUDE_PLUGIN_CACHE_DIR, { withFileTypes: true })
113
+ .filter(entry => entry.isDirectory())
114
+ .map(entry => path.join(CLAUDE_PLUGIN_CACHE_DIR, entry.name))
115
+ .filter(candidate => fs.existsSync(path.join(candidate, '.claude-plugin', 'plugin.json')))
116
+ .sort()
117
+ .reverse();
118
+
119
+ return versionDirs[0] ?? null;
120
+ }
121
+
122
+ function envTruthy(value) {
123
+ return /^(1|true|yes|on)$/i.test(String(value ?? '').trim());
124
+ }
125
+
126
+ function claudeLaunchPluginDir() {
127
+ const explicitPluginDir = process.env.OVERLORD_CLAUDE_PLUGIN_DIR?.trim();
128
+ if (explicitPluginDir) return explicitPluginDir;
129
+
130
+ if (envTruthy(process.env.OVERLORD_USE_CLAUDE_SOURCE_PLUGIN)) {
131
+ return claudeSourcePluginDir();
132
+ }
133
+
134
+ return null;
135
+ }
136
+
50
137
  function antigravityBundleInstalled() {
51
138
  return fs.existsSync(
52
139
  path.join(os.homedir(), '.gemini', 'antigravity-cli', 'plugins', 'plugin.json')
@@ -55,7 +142,7 @@ function antigravityBundleInstalled() {
55
142
 
56
143
  function getInstructionMode(agent) {
57
144
  if (agent === 'claude') {
58
- return claudeSourcePluginDir() ? 'bundle' : 'legacy';
145
+ return claudeInstalledPluginDir() || claudeLaunchPluginDir() ? 'bundle' : 'legacy';
59
146
  }
60
147
 
61
148
  if (agent === 'codex') {
@@ -397,8 +484,9 @@ async function runAgent(agent, mode = 'run', options = {}) {
397
484
  process.chdir(options.workingDirectory);
398
485
  }
399
486
 
487
+ const useMountedProjectScratch = Boolean(options.workingDirectory || options.preCommand?.trim());
400
488
  const scratchDir = resolveLaunchScratchDir(options.workingDirectory, {
401
- explicit: Boolean(options.workingDirectory)
489
+ explicit: useMountedProjectScratch
402
490
  });
403
491
 
404
492
  const launchOrganizationId = resolveLaunchOrganizationId(
@@ -471,24 +559,38 @@ async function runAgent(agent, mode = 'run', options = {}) {
471
559
  : {})
472
560
  }, scratchDir);
473
561
  const extraArgs = buildExtraArgs(agent, options);
562
+ const useContextFilePrompt = Boolean(options.preCommand?.trim());
563
+ const launchContextFile = useContextFilePrompt
564
+ ? writeLaunchContextFile(scratchDir, ticketId, context)
565
+ : null;
474
566
 
475
567
  try {
476
568
  if (agent === 'claude') {
477
- const pluginDir = claudeSourcePluginDir();
569
+ const pluginDir = claudeLaunchPluginDir();
478
570
  if (mode === 'resume') {
479
571
  const claudeSessionId = process.env.CLAUDE_SESSION_ID?.trim();
572
+ const resumePrompt = launchContextFile
573
+ ? buildContextFilePrompt(launchContextFile, 'Resume working on this Overlord ticket.')
574
+ : context;
480
575
  const args = claudeSessionId
481
- ? ['--resume', claudeSessionId, context]
482
- : ['--continue', context];
576
+ ? ['--resume', claudeSessionId, resumePrompt]
577
+ : ['--continue', resumePrompt];
578
+ if (launchContextFile) args.unshift('--append-system-prompt-file', launchContextFile);
483
579
  args.unshift(...extraArgs);
484
580
  if (pluginDir) args.unshift('--plugin-dir', pluginDir);
485
581
  execAgentBinary('claude', args, { stdio: 'inherit', env: childEnv }, options.preCommand);
486
582
  } else {
583
+ const startPrompt = launchContextFile
584
+ ? buildContextFilePrompt(
585
+ launchContextFile,
586
+ 'Begin working on this ticket.'
587
+ )
588
+ : 'Begin working on this ticket. Start by calling the attach endpoint, then proceed with the objective described in your system prompt.';
487
589
  const args = [
488
- '--append-system-prompt',
489
- context,
590
+ launchContextFile ? '--append-system-prompt-file' : '--append-system-prompt',
591
+ launchContextFile ?? context,
490
592
  ...extraArgs,
491
- 'Begin working on this ticket. Start by calling the attach endpoint, then proceed with the objective described in your system prompt.'
593
+ startPrompt
492
594
  ];
493
595
  if (pluginDir) args.unshift('--plugin-dir', pluginDir);
494
596
  execAgentBinary('claude', args, { stdio: 'inherit', env: childEnv }, options.preCommand);
@@ -496,15 +598,23 @@ async function runAgent(agent, mode = 'run', options = {}) {
496
598
  } else if (agent === 'codex') {
497
599
  if (mode === 'resume') {
498
600
  const codexSessionId = process.env.CODEX_SESSION_ID?.trim();
601
+ const resumePrompt = launchContextFile
602
+ ? buildContextFilePrompt(launchContextFile, 'Resume working on this Overlord ticket.')
603
+ : context;
499
604
  const args = codexSessionId
500
- ? ['resume', codexSessionId, context]
501
- : ['resume', '--last', context];
605
+ ? ['resume', codexSessionId, resumePrompt]
606
+ : ['resume', '--last', resumePrompt];
502
607
  args.splice(1, 0, ...extraArgs);
503
608
  execAgentBinary('codex', args, { stdio: 'inherit', env: childEnv }, options.preCommand);
504
609
  } else {
505
610
  execAgentBinary(
506
611
  'codex',
507
- [...extraArgs, context],
612
+ [
613
+ ...extraArgs,
614
+ launchContextFile
615
+ ? buildContextFilePrompt(launchContextFile, 'Begin working on this Overlord ticket.')
616
+ : context
617
+ ],
508
618
  { stdio: 'inherit', env: childEnv },
509
619
  options.preCommand
510
620
  );
@@ -512,22 +622,36 @@ async function runAgent(agent, mode = 'run', options = {}) {
512
622
  } else if (agent === 'cursor') {
513
623
  execAgentBinary(
514
624
  'agent',
515
- [...extraArgs, context],
625
+ [
626
+ ...extraArgs,
627
+ launchContextFile
628
+ ? buildContextFilePrompt(launchContextFile, 'Begin working on this Overlord ticket.')
629
+ : context
630
+ ],
516
631
  { stdio: 'inherit', env: childEnv },
517
632
  options.preCommand
518
633
  );
519
634
  } else if (agent === 'opencode') {
520
635
  if (mode === 'resume') {
521
636
  const openCodeSessionId = process.env.OPENCODE_SESSION_ID?.trim();
637
+ const resumePrompt = launchContextFile
638
+ ? buildContextFilePrompt(launchContextFile, 'Resume working on this Overlord ticket.')
639
+ : context;
522
640
  const args = openCodeSessionId
523
- ? ['--continue', '--session', openCodeSessionId, '--prompt', context]
524
- : ['--continue', '--prompt', context];
641
+ ? ['--continue', '--session', openCodeSessionId, '--prompt', resumePrompt]
642
+ : ['--continue', '--prompt', resumePrompt];
525
643
  args.unshift(...extraArgs);
526
644
  execAgentBinary('opencode', args, { stdio: 'inherit', env: childEnv }, options.preCommand);
527
645
  } else {
528
646
  execAgentBinary(
529
647
  'opencode',
530
- [...extraArgs, '--prompt', context],
648
+ [
649
+ ...extraArgs,
650
+ '--prompt',
651
+ launchContextFile
652
+ ? buildContextFilePrompt(launchContextFile, 'Begin working on this Overlord ticket.')
653
+ : context
654
+ ],
531
655
  { stdio: 'inherit', env: childEnv },
532
656
  options.preCommand
533
657
  );
@@ -535,24 +659,34 @@ async function runAgent(agent, mode = 'run', options = {}) {
535
659
  } else if (agent === 'pi') {
536
660
  if (mode === 'resume') {
537
661
  const piSessionId = process.env.PI_SESSION_ID?.trim();
662
+ const resumePrompt = launchContextFile
663
+ ? buildContextFilePrompt(launchContextFile, 'Resume working on this Overlord ticket.')
664
+ : context;
538
665
  const args = piSessionId
539
- ? ['--session', piSessionId, context]
540
- : ['--continue', context];
666
+ ? ['--session', piSessionId, resumePrompt]
667
+ : ['--continue', resumePrompt];
541
668
  args.unshift(...extraArgs);
542
669
  execAgentBinary('pi', args, { stdio: 'inherit', env: childEnv }, options.preCommand);
543
670
  } else {
544
671
  execAgentBinary(
545
672
  'pi',
546
- [...extraArgs, context],
673
+ [
674
+ ...extraArgs,
675
+ launchContextFile
676
+ ? buildContextFilePrompt(launchContextFile, 'Begin working on this Overlord ticket.')
677
+ : context
678
+ ],
547
679
  { stdio: 'inherit', env: childEnv },
548
680
  options.preCommand
549
681
  );
550
682
  }
551
683
  } else if (agent === 'antigravity') {
552
684
  const tag = `overlord-${ticketId.slice(-8)}-${Date.now()}`;
553
- const contextFile = path.join(scratchDir, `${tag}-ctx.md`);
554
- fs.writeFileSync(contextFile, context, 'utf-8');
555
- setTimeout(() => { try { fs.unlinkSync(contextFile); } catch { /* already gone */ } }, 30 * 60_000).unref();
685
+ const contextFile = launchContextFile ?? path.join(scratchDir, `${tag}-ctx.md`);
686
+ if (!launchContextFile) {
687
+ fs.writeFileSync(contextFile, context, 'utf-8');
688
+ setTimeout(() => { try { fs.unlinkSync(contextFile); } catch { /* already gone */ } }, 30 * 60_000).unref();
689
+ }
556
690
 
557
691
  const agySessionId =
558
692
  process.env.AGY_SESSION_ID?.trim() ?? process.env.GEMINI_SESSION_ID?.trim();
@@ -1138,7 +1138,9 @@ function installClaude() {
1138
1138
  } else {
1139
1139
  console.log(' ✓ No v3.25 Claude connector files needed migration.');
1140
1140
  }
1141
- console.log(' ✓ `ovld launch claude` now loads this plugin with `claude --plugin-dir`.');
1141
+ console.log(
1142
+ ' ✓ `ovld launch claude` now uses the installed Claude local marketplace plugin by default.'
1143
+ );
1142
1144
 
1143
1145
  const manifest = readManifest();
1144
1146
  manifest.claude = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "0.2606021930.0",
3
+ "version": "0.2606041130.1",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "homepage": "https://www.ovld.ai/docs/",
6
6
  "type": "module",