overlord-cli 0.2606021051.0 → 0.2606041130.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 +1 -1
- package/bin/_cli/add-cwd.mjs +8 -16
- package/bin/_cli/attach.mjs +11 -23
- package/bin/_cli/auth.mjs +66 -17
- package/bin/_cli/credentials.mjs +130 -12
- package/bin/_cli/launcher.mjs +66 -2
- package/bin/_cli/new-ticket.mjs +59 -4
- package/bin/_cli/protocol.mjs +12 -13
- package/bin/_cli/setup.mjs +3 -1
- package/bin/_cli/tickets.mjs +12 -27
- package/package.json +1 -1
- package/plugins/antigravity/skills/overlord-ticket/SKILL.md +2 -2
- package/plugins/claude/skills/overlord-ticket/SKILL.md +2 -2
- package/plugins/cursor/skills/overlord-ticket/SKILL.md +2 -2
- package/plugins/overlord/skills/overlord-ticket/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -89,7 +89,7 @@ ovld doctor
|
|
|
89
89
|
|
|
90
90
|
**Antigravity:** Gemini CLI is deprecated. Use `ovld setup antigravity` and `ovld launch antigravity --ticket-id <ticket_id>`. Antigravity manages model selection internally.
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
The CLI is organization-agnostic: `ovld auth login` stores your identity only and never a default organization. Your identity (OAuth session or agent token) resolves to the list of organizations you belong to — see `ovld auth status --verbose`. Each command resolves its organization from the `ticket_id` (values such as `1:899` carry the organization id), then an explicit `--organization-id`, otherwise from your membership. Commands that browse or search — `ovld add-cwd`, `ovld create`/`ovld prompt`, `ovld tickets list`, and `ovld protocol search-tickets` — span every organization you belong to. Actions against an organization you are not a member of return a clear permission error rather than silently falling back to another org.
|
|
93
93
|
|
|
94
94
|
When `ovld launch` has an explicit `--working-directory`, or when you run it from a registered project directory containing `.overlord/project.json`, it exports `TMPDIR`, `TMP`, `TEMP`, and `OVERLORD_TMPDIR` to that project's `.overlord/tmp/` directory before spawning the agent. Otherwise it falls back to the system temp directory.
|
|
95
95
|
|
package/bin/_cli/add-cwd.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { stdin as input, stdout as output } from 'node:process';
|
|
|
12
12
|
|
|
13
13
|
import { buildAuthHeaders, resolveAuth } from './credentials.mjs';
|
|
14
14
|
import { upsertLocalOverlordConfig } from './local-config.mjs';
|
|
15
|
-
import {
|
|
15
|
+
import { fetchProjectsAcrossOrganizations, parseNumberedSelection } from './new-ticket.mjs';
|
|
16
16
|
import { readOrCreateDeviceFingerprint } from './runner.mjs';
|
|
17
17
|
|
|
18
18
|
function parseFlags(args) {
|
|
@@ -41,19 +41,6 @@ function projectLabel(project) {
|
|
|
41
41
|
return organizationName ? `${project.name} — ${organizationName}` : project.name;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
async function fetchProjects(platformUrl, bearerToken, localSecret, organizationId) {
|
|
45
|
-
const res = await fetch(`${platformUrl}/api/protocol/projects`, {
|
|
46
|
-
headers: buildAuthHeaders(bearerToken, localSecret, organizationId)
|
|
47
|
-
});
|
|
48
|
-
const data = await res.json().catch(() => ({}));
|
|
49
|
-
if (!res.ok) {
|
|
50
|
-
throw new Error(
|
|
51
|
-
`Failed to list projects (${res.status}): ${data.error ?? JSON.stringify(data)}`
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
return Array.isArray(data.projects) ? sortProjects(data.projects) : [];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
44
|
async function promptForProject(projects, cwd) {
|
|
58
45
|
if (!projects.length) {
|
|
59
46
|
throw new Error('No projects available. Create a project in Overlord first.');
|
|
@@ -118,9 +105,10 @@ By default the registered resource is marked primary for this device. Pass
|
|
|
118
105
|
|
|
119
106
|
const isPrimary = !(flags.primary === 'false' || flags.primary === false);
|
|
120
107
|
|
|
121
|
-
const
|
|
108
|
+
const auth = await resolveAuth({});
|
|
109
|
+
const { platformUrl, bearerToken, localSecret } = auth;
|
|
122
110
|
|
|
123
|
-
const projects = await
|
|
111
|
+
const projects = await fetchProjectsAcrossOrganizations(auth);
|
|
124
112
|
let project = null;
|
|
125
113
|
if (typeof flags['project-id'] === 'string' && flags['project-id'].trim()) {
|
|
126
114
|
const wanted = flags['project-id'].trim();
|
|
@@ -139,6 +127,10 @@ By default the registered resource is marked primary for this device. Pass
|
|
|
139
127
|
|
|
140
128
|
const deviceFingerprint = readOrCreateDeviceFingerprint(flags);
|
|
141
129
|
|
|
130
|
+
// Register the resource in the organization that owns the chosen project,
|
|
131
|
+
// never a stored default org.
|
|
132
|
+
const organizationId = project.organizationId ?? null;
|
|
133
|
+
|
|
142
134
|
const body = {
|
|
143
135
|
projectId: project.id,
|
|
144
136
|
directoryPath,
|
package/bin/_cli/attach.mjs
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* ovld attach <ticketId> <agent> # non-interactive: launch immediately
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { resolveAuth, searchTicketsAcrossOrganizations } from './credentials.mjs';
|
|
13
13
|
import { runLauncherCommand } from './launcher.mjs';
|
|
14
14
|
|
|
15
15
|
const AGENTS = ['claude', 'cursor', 'codex', 'antigravity', 'opencode'];
|
|
@@ -58,26 +58,15 @@ function statusColor(status) {
|
|
|
58
58
|
|
|
59
59
|
// ─── API ──────────────────────────────────────────────────────────────────────
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
body: JSON.stringify({
|
|
69
|
-
includeCompleted: false,
|
|
70
|
-
query,
|
|
71
|
-
limit: MAX_VISIBLE
|
|
72
|
-
})
|
|
61
|
+
// Organization-agnostic ticket search: spans every organization the identity
|
|
62
|
+
// belongs to (no default org) by delegating to the shared fan-out helper.
|
|
63
|
+
async function searchTickets(auth, query) {
|
|
64
|
+
const { tickets } = await searchTicketsAcrossOrganizations(auth, {
|
|
65
|
+
includeCompleted: false,
|
|
66
|
+
query,
|
|
67
|
+
limit: MAX_VISIBLE
|
|
73
68
|
});
|
|
74
|
-
|
|
75
|
-
if (!res.ok) {
|
|
76
|
-
throw new Error(`Failed to search tickets (${res.status}): ${await res.text()}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const data = await res.json();
|
|
80
|
-
return data.tickets ?? [];
|
|
69
|
+
return tickets ?? [];
|
|
81
70
|
}
|
|
82
71
|
|
|
83
72
|
// ─── Interactive prompt ───────────────────────────────────────────────────────
|
|
@@ -287,7 +276,7 @@ function runInteractivePrompt({ label, items = [], search, prefix = '' }) {
|
|
|
287
276
|
export async function runAttachCommand(args) {
|
|
288
277
|
const [ticketIdArg, agentArg] = args;
|
|
289
278
|
|
|
290
|
-
const
|
|
279
|
+
const auth = await resolveAuth();
|
|
291
280
|
|
|
292
281
|
// ── Phase 1: Ticket selection ──────────────────────────────────────────────
|
|
293
282
|
|
|
@@ -301,8 +290,7 @@ export async function runAttachCommand(args) {
|
|
|
301
290
|
|
|
302
291
|
const selectedTicket = await runInteractivePrompt({
|
|
303
292
|
label: 'Search tickets',
|
|
304
|
-
search: nextQuery =>
|
|
305
|
-
searchTickets(platformUrl, bearerToken, localSecret, organizationId, nextQuery)
|
|
293
|
+
search: nextQuery => searchTickets(auth, nextQuery)
|
|
306
294
|
});
|
|
307
295
|
|
|
308
296
|
if (!selectedTicket) {
|
package/bin/_cli/auth.mjs
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
loadCredentials,
|
|
14
14
|
loadRuntime,
|
|
15
15
|
repairCredentials,
|
|
16
|
+
resolveAuth,
|
|
17
|
+
resolveOrganizations,
|
|
16
18
|
saveCredentials
|
|
17
19
|
} from './credentials.mjs';
|
|
18
20
|
|
|
@@ -558,47 +560,91 @@ export async function authLogin(args = []) {
|
|
|
558
560
|
credentials.access_token,
|
|
559
561
|
localSecret
|
|
560
562
|
);
|
|
561
|
-
|
|
563
|
+
|
|
564
|
+
// The CLI is organization-agnostic: login stores the identity only, never a
|
|
565
|
+
// default organization. `--organization-id` is still validated so a typo is
|
|
566
|
+
// caught early, but it is no longer persisted — every command resolves its org
|
|
567
|
+
// from the ticket id, an explicit --organization-id, or your membership.
|
|
568
|
+
if (preselectedOrganizationId !== null) {
|
|
569
|
+
selectLoginOrganization(organizations, preselectedOrganizationId);
|
|
570
|
+
}
|
|
562
571
|
|
|
563
572
|
saveCredentials({
|
|
564
573
|
access_token: credentials.access_token,
|
|
565
574
|
access_token_expires_at: credentials.access_token_expires_at ?? undefined,
|
|
566
575
|
refresh_token: credentials.refresh_token,
|
|
567
|
-
organization_id: selectedOrganization.id,
|
|
568
576
|
platform_url: resolvedPlatformUrl
|
|
569
577
|
});
|
|
570
578
|
|
|
571
|
-
|
|
579
|
+
console.log('Logged in successfully.');
|
|
580
|
+
printOrganizationMemberships(organizations);
|
|
581
|
+
if (preselectedOrganizationId !== null) {
|
|
572
582
|
console.log(
|
|
573
|
-
|
|
574
|
-
'
|
|
583
|
+
`\nNote: --organization-id is no longer stored as a default. ` +
|
|
584
|
+
'Commands resolve their organization from the ticket id, an explicit --organization-id, or your membership.'
|
|
575
585
|
);
|
|
576
|
-
} else {
|
|
577
|
-
console.log(`Logged in successfully. Default organization: ${describeOrganization(selectedOrganization)}.`);
|
|
578
586
|
}
|
|
579
587
|
}
|
|
580
588
|
|
|
589
|
+
function printOrganizationMemberships(organizations) {
|
|
590
|
+
if (!organizations.length) {
|
|
591
|
+
console.log(' You are not yet a member of any organization. Complete onboarding in Overlord first.');
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
if (organizations.length === 1) {
|
|
596
|
+
console.log(` Organization: ${describeOrganization(organizations[0])}`);
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
console.log(` Member of ${organizations.length} organizations:`);
|
|
601
|
+
for (const organization of organizations) {
|
|
602
|
+
console.log(` - ${describeOrganization(organization)}`);
|
|
603
|
+
}
|
|
604
|
+
console.log(' Commands resolve their organization from the ticket id or --organization-id.');
|
|
605
|
+
}
|
|
606
|
+
|
|
581
607
|
async function authLoginWithAgentToken(agentToken, args) {
|
|
582
608
|
const preselectedOrganizationId = parseOrganizationFlag(args);
|
|
583
609
|
const storedCredentials = loadCredentials();
|
|
584
610
|
const platformUrl = resolveLoginPlatformUrl(null, storedCredentials?.platform_url ?? null);
|
|
585
611
|
|
|
612
|
+
// Agent-token login stores the identity only — never a default organization.
|
|
586
613
|
saveCredentials({
|
|
587
614
|
agent_token: agentToken,
|
|
588
|
-
platform_url: platformUrl
|
|
589
|
-
...(preselectedOrganizationId !== null ? { organization_id: preselectedOrganizationId } : {})
|
|
615
|
+
platform_url: platformUrl
|
|
590
616
|
});
|
|
591
617
|
|
|
592
618
|
console.log(`Agent token saved. The CLI will use this token for all protocol commands.`);
|
|
619
|
+
console.log(` Organizations are resolved from your token's membership per command.`);
|
|
593
620
|
if (preselectedOrganizationId !== null) {
|
|
594
|
-
console.log(
|
|
595
|
-
|
|
596
|
-
|
|
621
|
+
console.log(
|
|
622
|
+
` Note: --organization-id is no longer stored as a default; pass it per command (or use a ticket id) to scope an action.`
|
|
623
|
+
);
|
|
597
624
|
}
|
|
598
625
|
console.log(` Platform URL: ${platformUrl}`);
|
|
599
626
|
console.log(`\nTo remove: ovld auth logout`);
|
|
600
627
|
}
|
|
601
628
|
|
|
629
|
+
async function printOrganizationMembershipStatus() {
|
|
630
|
+
try {
|
|
631
|
+
const auth = await resolveAuth();
|
|
632
|
+
const organizations = await resolveOrganizations(auth);
|
|
633
|
+
if (!organizations.length) {
|
|
634
|
+
console.log(' Organizations: none (not a member of any organization)');
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
console.log(` Organizations (${organizations.length}):`);
|
|
638
|
+
for (const organization of organizations) {
|
|
639
|
+
console.log(` - ${describeOrganization(organization)}`);
|
|
640
|
+
}
|
|
641
|
+
} catch (error) {
|
|
642
|
+
console.log(
|
|
643
|
+
` Organizations: unavailable (${error instanceof Error ? error.message : String(error)})`
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
602
648
|
async function printVerboseAuthStatus() {
|
|
603
649
|
const status = await getAuthStatus();
|
|
604
650
|
if (!status.isLoggedIn) {
|
|
@@ -617,7 +663,9 @@ async function printVerboseAuthStatus() {
|
|
|
617
663
|
console.log(` Token source: ${status.tokenSource}`);
|
|
618
664
|
console.log(` Token present: ${status.tokenPresent ? 'yes' : 'no'}`);
|
|
619
665
|
console.log(` Auth mode: ${status.authMode}`);
|
|
620
|
-
|
|
666
|
+
if (status.isLoggedIn) {
|
|
667
|
+
await printOrganizationMembershipStatus();
|
|
668
|
+
}
|
|
621
669
|
if (status.error) {
|
|
622
670
|
console.log(` Error: ${status.error}`);
|
|
623
671
|
}
|
|
@@ -681,10 +729,11 @@ Subcommands:
|
|
|
681
729
|
login Authorize the CLI via browser (works locally or over SSH)
|
|
682
730
|
--token <oat_…> Persist an agent token from Settings → Agents & MCP.
|
|
683
731
|
Skips the browser flow; token never expires.
|
|
684
|
-
--organization-id <id> Optional
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
732
|
+
--organization-id <id> Optional. Validated against your membership but
|
|
733
|
+
no longer stored as a default — the CLI is
|
|
734
|
+
organization-agnostic. Scope a command with a
|
|
735
|
+
ticket id (e.g. 1:899) or --organization-id.
|
|
736
|
+
status Show current login status (use --verbose to list your organizations)
|
|
688
737
|
repair Mirror and chmod shared Desktop/CLI credentials when possible
|
|
689
738
|
logout Remove stored credentials
|
|
690
739
|
`);
|
package/bin/_cli/credentials.mjs
CHANGED
|
@@ -584,12 +584,12 @@ export async function resolveAuth(options = {}) {
|
|
|
584
584
|
typeof process.env.OVERLORD_ORGANIZATION_ID === 'string'
|
|
585
585
|
? Number.parseInt(process.env.OVERLORD_ORGANIZATION_ID, 10)
|
|
586
586
|
: null;
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
587
|
+
// Organization is no longer required up front. When neither a per-action hint
|
|
588
|
+
// nor an explicit OVERLORD_ORGANIZATION_ID override is present, the platform
|
|
589
|
+
// resolves the organization from the token owner's membership, so the CLI stays
|
|
590
|
+
// org-agnostic instead of pinning a single default.
|
|
591
|
+
const resolvedOrganizationId =
|
|
592
|
+
organizationIdHint ?? (Number.isFinite(envOrganizationId) ? envOrganizationId : null);
|
|
593
593
|
|
|
594
594
|
if (isEnvAccessTokenUsable(envAccessToken)) {
|
|
595
595
|
return {
|
|
@@ -603,11 +603,14 @@ export async function resolveAuth(options = {}) {
|
|
|
603
603
|
}
|
|
604
604
|
|
|
605
605
|
if (creds?.agent_token) {
|
|
606
|
+
// Stored credentials never act as a default organization. Org is taken from the
|
|
607
|
+
// per-action hint (ticket id or --organization-id) and otherwise left to the
|
|
608
|
+
// platform's membership lookup.
|
|
606
609
|
return {
|
|
607
610
|
platformUrl,
|
|
608
611
|
bearerToken: creds.agent_token,
|
|
609
612
|
localSecret,
|
|
610
|
-
organizationId: organizationIdHint ??
|
|
613
|
+
organizationId: organizationIdHint ?? null,
|
|
611
614
|
authMode: 'agent_token'
|
|
612
615
|
};
|
|
613
616
|
}
|
|
@@ -641,19 +644,18 @@ export async function resolveAuth(options = {}) {
|
|
|
641
644
|
}
|
|
642
645
|
}
|
|
643
646
|
|
|
644
|
-
if (!Number.isFinite(nextCredentials.organization_id)) {
|
|
645
|
-
throw new Error('Overlord login is missing an organization selection. Run `ovld auth login` again.');
|
|
646
|
-
}
|
|
647
|
-
|
|
648
647
|
if (!nextCredentials.access_token) {
|
|
649
648
|
throw new Error('No OAuth access token is available. Run `ovld auth login` again.');
|
|
650
649
|
}
|
|
651
650
|
|
|
651
|
+
// OAuth sessions are not bound to a stored default organization. The platform
|
|
652
|
+
// derives the organization from the signed-in user's membership when no
|
|
653
|
+
// per-action hint is supplied, keeping the CLI org-agnostic.
|
|
652
654
|
return {
|
|
653
655
|
platformUrl,
|
|
654
656
|
bearerToken: nextCredentials.access_token,
|
|
655
657
|
localSecret,
|
|
656
|
-
organizationId: organizationIdHint ??
|
|
658
|
+
organizationId: organizationIdHint ?? null,
|
|
657
659
|
authMode: 'oauth'
|
|
658
660
|
};
|
|
659
661
|
}
|
|
@@ -667,6 +669,122 @@ export async function resolveAuth(options = {}) {
|
|
|
667
669
|
};
|
|
668
670
|
}
|
|
669
671
|
|
|
672
|
+
/**
|
|
673
|
+
* Lists every organization the authenticated identity belongs to.
|
|
674
|
+
*
|
|
675
|
+
* This is the org-agnostic replacement for a stored "default organization": the
|
|
676
|
+
* caller's identity (OAuth session or agent token) resolves to the full set of
|
|
677
|
+
* memberships, and commands fan out or prompt over that list instead of assuming
|
|
678
|
+
* a single org. Works for OAuth, agent tokens, and local-dev alike because
|
|
679
|
+
* `/api/protocol/organizations` resolves the caller through the standard
|
|
680
|
+
* protocol auth path.
|
|
681
|
+
*
|
|
682
|
+
* @param {{ platformUrl: string, bearerToken: string, localSecret?: string }} auth
|
|
683
|
+
* @returns {Promise<Array<{ id: number, name: string }>>}
|
|
684
|
+
*/
|
|
685
|
+
export async function resolveOrganizations(auth) {
|
|
686
|
+
const { platformUrl, bearerToken, localSecret } = auth;
|
|
687
|
+
let res;
|
|
688
|
+
try {
|
|
689
|
+
res = await fetch(`${platformUrl}/api/protocol/organizations`, {
|
|
690
|
+
method: 'POST',
|
|
691
|
+
headers: {
|
|
692
|
+
...buildAuthHeaders(bearerToken, localSecret),
|
|
693
|
+
'Content-Type': 'application/json'
|
|
694
|
+
},
|
|
695
|
+
body: JSON.stringify({})
|
|
696
|
+
});
|
|
697
|
+
} catch (error) {
|
|
698
|
+
throw describeNetworkError(error, `Failed to list organizations from ${platformUrl}`);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
const data = await res.json().catch(() => ({}));
|
|
702
|
+
if (!res.ok) {
|
|
703
|
+
throw new Error(
|
|
704
|
+
`Failed to list organizations (${res.status}): ${data.error ?? JSON.stringify(data)}`
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return Array.isArray(data.organizations)
|
|
709
|
+
? data.organizations
|
|
710
|
+
.map(org => ({ id: org.id, name: typeof org.name === 'string' ? org.name : '' }))
|
|
711
|
+
.filter(org => Number.isFinite(org.id))
|
|
712
|
+
: [];
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Searches tickets across every organization the identity belongs to.
|
|
717
|
+
*
|
|
718
|
+
* Organization-agnostic by default: with no explicit organization it resolves the
|
|
719
|
+
* membership list and fans out the per-org search, merging and de-duplicating by
|
|
720
|
+
* ticket id. An explicit `organizationId` (from --organization-id) scopes the
|
|
721
|
+
* search to that single org instead. There is no default-org fallback.
|
|
722
|
+
*
|
|
723
|
+
* @param {{ platformUrl: string, bearerToken: string, localSecret?: string }} auth
|
|
724
|
+
* @param {Record<string, unknown>} body search-tickets request body
|
|
725
|
+
* @param {{ organizationId?: number | null }} [options]
|
|
726
|
+
* @returns {Promise<{ tickets: unknown[], count: number }>}
|
|
727
|
+
*/
|
|
728
|
+
export async function searchTicketsAcrossOrganizations(auth, body, options = {}) {
|
|
729
|
+
const { platformUrl, bearerToken, localSecret } = auth;
|
|
730
|
+
const explicitOrganizationId =
|
|
731
|
+
Number.isFinite(options.organizationId) ? options.organizationId : null;
|
|
732
|
+
|
|
733
|
+
const post = async organizationId => {
|
|
734
|
+
const res = await fetch(`${platformUrl}/api/protocol/search-tickets`, {
|
|
735
|
+
method: 'POST',
|
|
736
|
+
headers: {
|
|
737
|
+
...buildAuthHeaders(bearerToken, localSecret, organizationId),
|
|
738
|
+
'Content-Type': 'application/json'
|
|
739
|
+
},
|
|
740
|
+
body: JSON.stringify(body)
|
|
741
|
+
});
|
|
742
|
+
const data = await res.json().catch(() => ({}));
|
|
743
|
+
if (!res.ok) {
|
|
744
|
+
throw new Error(
|
|
745
|
+
`Failed to search tickets (${res.status}): ${data.error ?? JSON.stringify(data)}`
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
return Array.isArray(data.tickets) ? data.tickets : [];
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
if (explicitOrganizationId !== null) {
|
|
752
|
+
const tickets = await post(explicitOrganizationId);
|
|
753
|
+
return { tickets, count: tickets.length };
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
const organizations = await resolveOrganizations(auth);
|
|
757
|
+
if (!organizations.length) {
|
|
758
|
+
const tickets = await post(null);
|
|
759
|
+
return { tickets, count: tickets.length };
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const results = await Promise.allSettled(organizations.map(org => post(org.id)));
|
|
763
|
+
const failures = results
|
|
764
|
+
.map((result, index) => ({ result, organization: organizations[index] }))
|
|
765
|
+
.filter(({ result }) => result.status === 'rejected');
|
|
766
|
+
if (failures.length > 0) {
|
|
767
|
+
const detail = failures
|
|
768
|
+
.map(({ result, organization }) => {
|
|
769
|
+
const reason = result.status === 'rejected' ? result.reason : null;
|
|
770
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
771
|
+
return `${organization.name || `organization ${organization.id}`} (${organization.id}): ${message}`;
|
|
772
|
+
})
|
|
773
|
+
.join('; ');
|
|
774
|
+
throw new Error(`Ticket search returned partial failures: ${detail}`);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const perOrg = results.map(result => (result.status === 'fulfilled' ? result.value : []));
|
|
778
|
+
const byId = new Map();
|
|
779
|
+
for (const ticket of perOrg.flat()) {
|
|
780
|
+
if (ticket && ticket.id && !byId.has(ticket.id)) {
|
|
781
|
+
byId.set(ticket.id, ticket);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
const tickets = [...byId.values()];
|
|
785
|
+
return { tickets, count: tickets.length };
|
|
786
|
+
}
|
|
787
|
+
|
|
670
788
|
export async function getAuthStatus() {
|
|
671
789
|
const creds = loadCredentials();
|
|
672
790
|
let resolved;
|
package/bin/_cli/launcher.mjs
CHANGED
|
@@ -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() : '';
|
|
@@ -47,6 +62,55 @@ function claudeSourcePluginDir() {
|
|
|
47
62
|
return null;
|
|
48
63
|
}
|
|
49
64
|
|
|
65
|
+
function readJsonFile(filePath) {
|
|
66
|
+
try {
|
|
67
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function claudeInstalledPluginDir() {
|
|
74
|
+
const installed = readJsonFile(CLAUDE_INSTALLED_PLUGINS_FILE);
|
|
75
|
+
const pluginEntries = installed?.plugins?.[CLAUDE_INSTALLED_PLUGIN_KEY];
|
|
76
|
+
|
|
77
|
+
if (Array.isArray(pluginEntries)) {
|
|
78
|
+
for (const entry of pluginEntries) {
|
|
79
|
+
const installPath = typeof entry?.installPath === 'string' ? entry.installPath.trim() : '';
|
|
80
|
+
if (installPath && fs.existsSync(path.join(installPath, '.claude-plugin', 'plugin.json'))) {
|
|
81
|
+
return installPath;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (!fs.existsSync(CLAUDE_PLUGIN_CACHE_DIR)) return null;
|
|
87
|
+
|
|
88
|
+
const versionDirs = fs
|
|
89
|
+
.readdirSync(CLAUDE_PLUGIN_CACHE_DIR, { withFileTypes: true })
|
|
90
|
+
.filter(entry => entry.isDirectory())
|
|
91
|
+
.map(entry => path.join(CLAUDE_PLUGIN_CACHE_DIR, entry.name))
|
|
92
|
+
.filter(candidate => fs.existsSync(path.join(candidate, '.claude-plugin', 'plugin.json')))
|
|
93
|
+
.sort()
|
|
94
|
+
.reverse();
|
|
95
|
+
|
|
96
|
+
return versionDirs[0] ?? null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function envTruthy(value) {
|
|
100
|
+
return /^(1|true|yes|on)$/i.test(String(value ?? '').trim());
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function claudeLaunchPluginDir() {
|
|
104
|
+
const explicitPluginDir = process.env.OVERLORD_CLAUDE_PLUGIN_DIR?.trim();
|
|
105
|
+
if (explicitPluginDir) return explicitPluginDir;
|
|
106
|
+
|
|
107
|
+
if (envTruthy(process.env.OVERLORD_USE_CLAUDE_SOURCE_PLUGIN)) {
|
|
108
|
+
return claudeSourcePluginDir();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
50
114
|
function antigravityBundleInstalled() {
|
|
51
115
|
return fs.existsSync(
|
|
52
116
|
path.join(os.homedir(), '.gemini', 'antigravity-cli', 'plugins', 'plugin.json')
|
|
@@ -55,7 +119,7 @@ function antigravityBundleInstalled() {
|
|
|
55
119
|
|
|
56
120
|
function getInstructionMode(agent) {
|
|
57
121
|
if (agent === 'claude') {
|
|
58
|
-
return
|
|
122
|
+
return claudeInstalledPluginDir() || claudeLaunchPluginDir() ? 'bundle' : 'legacy';
|
|
59
123
|
}
|
|
60
124
|
|
|
61
125
|
if (agent === 'codex') {
|
|
@@ -474,7 +538,7 @@ async function runAgent(agent, mode = 'run', options = {}) {
|
|
|
474
538
|
|
|
475
539
|
try {
|
|
476
540
|
if (agent === 'claude') {
|
|
477
|
-
const pluginDir =
|
|
541
|
+
const pluginDir = claudeLaunchPluginDir();
|
|
478
542
|
if (mode === 'resume') {
|
|
479
543
|
const claudeSessionId = process.env.CLAUDE_SESSION_ID?.trim();
|
|
480
544
|
const args = claudeSessionId
|
package/bin/_cli/new-ticket.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import readline from 'node:readline';
|
|
|
4
4
|
import { stdin as input, stdout as output } from 'node:process';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
|
|
7
|
-
import { buildAuthHeaders, resolveAuth } from './credentials.mjs';
|
|
7
|
+
import { buildAuthHeaders, resolveAuth, resolveOrganizations } from './credentials.mjs';
|
|
8
8
|
import { runLauncherCommand } from './launcher.mjs';
|
|
9
9
|
|
|
10
10
|
const PROMPT_AGENT_IDENTIFIERS = {
|
|
@@ -232,7 +232,57 @@ async function fetchProjects(platformUrl, bearerToken, localSecret, organization
|
|
|
232
232
|
);
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
return Array.isArray(data.projects) ?
|
|
235
|
+
return Array.isArray(data.projects) ? data.projects : [];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Lists projects across every organization the identity belongs to.
|
|
240
|
+
*
|
|
241
|
+
* The CLI is organization-agnostic: instead of listing only a single default
|
|
242
|
+
* org's projects, it resolves the membership list and fans out the per-org
|
|
243
|
+
* projects query, then merges and de-duplicates by project id. Each project
|
|
244
|
+
* carries its own `organizationId`, so the caller scopes follow-up writes (ticket
|
|
245
|
+
* creation, resource registration) to the chosen project's org — no default org.
|
|
246
|
+
*
|
|
247
|
+
* @param {{ platformUrl: string, bearerToken: string, localSecret?: string }} auth
|
|
248
|
+
*/
|
|
249
|
+
export async function fetchProjectsAcrossOrganizations(auth) {
|
|
250
|
+
const { platformUrl, bearerToken, localSecret } = auth;
|
|
251
|
+
const organizations = await resolveOrganizations(auth);
|
|
252
|
+
|
|
253
|
+
// No memberships resolved (or a backend that scopes by membership anyway): fall
|
|
254
|
+
// back to a single unscoped query so the command still works.
|
|
255
|
+
if (!organizations.length) {
|
|
256
|
+
return sortProjects(await fetchProjects(platformUrl, bearerToken, localSecret, null));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const results = await Promise.allSettled(
|
|
260
|
+
organizations.map(org => fetchProjects(platformUrl, bearerToken, localSecret, org.id))
|
|
261
|
+
);
|
|
262
|
+
const failures = results
|
|
263
|
+
.map((result, index) => ({ result, organization: organizations[index] }))
|
|
264
|
+
.filter(({ result }) => result.status === 'rejected');
|
|
265
|
+
if (failures.length > 0) {
|
|
266
|
+
const detail = failures
|
|
267
|
+
.map(({ result, organization }) => {
|
|
268
|
+
const reason = result.status === 'rejected' ? result.reason : null;
|
|
269
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
270
|
+
return `${organization.name || `organization ${organization.id}`} (${organization.id}): ${message}`;
|
|
271
|
+
})
|
|
272
|
+
.join('; ');
|
|
273
|
+
throw new Error(`Project listing returned partial failures: ${detail}`);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const perOrg = results.map(result => (result.status === 'fulfilled' ? result.value : []));
|
|
277
|
+
|
|
278
|
+
const byId = new Map();
|
|
279
|
+
for (const project of perOrg.flat()) {
|
|
280
|
+
if (project && project.id && !byId.has(project.id)) {
|
|
281
|
+
byId.set(project.id, project);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return sortProjects([...byId.values()]);
|
|
236
286
|
}
|
|
237
287
|
|
|
238
288
|
async function createTicket(platformUrl, bearerToken, localSecret, organizationId, body) {
|
|
@@ -312,8 +362,9 @@ async function runTicketCreationFlow(args, { commandName, launchAgent }) {
|
|
|
312
362
|
const { flags } = parseFlags(args);
|
|
313
363
|
const objectiveInput = await resolveObjectiveInput(flags);
|
|
314
364
|
|
|
315
|
-
const
|
|
316
|
-
const
|
|
365
|
+
const auth = await resolveAuth();
|
|
366
|
+
const { platformUrl, bearerToken, localSecret } = auth;
|
|
367
|
+
const projects = await fetchProjectsAcrossOrganizations(auth);
|
|
317
368
|
|
|
318
369
|
if (!projects.length) {
|
|
319
370
|
throw new Error('No projects available. Create a project first.');
|
|
@@ -328,6 +379,10 @@ async function runTicketCreationFlow(args, { commandName, launchAgent }) {
|
|
|
328
379
|
renderItem: project => projectLabel(project)
|
|
329
380
|
}));
|
|
330
381
|
|
|
382
|
+
// The ticket is created in the organization that owns the chosen project,
|
|
383
|
+
// never a stored default org.
|
|
384
|
+
const organizationId = selectedProject.organizationId ?? null;
|
|
385
|
+
|
|
331
386
|
const selectedAgent = launchAgent
|
|
332
387
|
? (resolveAgent(typeof flags.agent === 'string' ? flags.agent : '') ??
|
|
333
388
|
(await promptForSelection({
|
package/bin/_cli/protocol.mjs
CHANGED
|
@@ -5,7 +5,12 @@ import fs from 'node:fs';
|
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
buildAuthHeaders,
|
|
10
|
+
getAuthStatus,
|
|
11
|
+
resolveAuth,
|
|
12
|
+
searchTicketsAcrossOrganizations
|
|
13
|
+
} from './credentials.mjs';
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
16
|
* Parse simple CLI flags: --key value or --key=value
|
|
@@ -2619,9 +2624,7 @@ async function protocolAddObjectives(args) {
|
|
|
2619
2624
|
|
|
2620
2625
|
async function protocolSearchTickets(args) {
|
|
2621
2626
|
const flags = parseFlags(args);
|
|
2622
|
-
const
|
|
2623
|
-
await resolveProtocolAuthForFlags(flags);
|
|
2624
|
-
const timeoutMs = resolveTimeout(flags);
|
|
2627
|
+
const auth = await resolveProtocolAuthForFlags(flags);
|
|
2625
2628
|
|
|
2626
2629
|
const statuses = flags.status
|
|
2627
2630
|
? String(flags.status)
|
|
@@ -2646,15 +2649,11 @@ async function protocolSearchTickets(args) {
|
|
|
2646
2649
|
...(flags['updated-before'] ? { updatedBefore: String(flags['updated-before']) } : {})
|
|
2647
2650
|
};
|
|
2648
2651
|
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
'/api/protocol/search-tickets',
|
|
2655
|
-
body,
|
|
2656
|
-
timeoutMs
|
|
2657
|
-
);
|
|
2652
|
+
// Organization-agnostic: with no --organization-id this fans out across every
|
|
2653
|
+
// organization you belong to and merges the results; an explicit org scopes it.
|
|
2654
|
+
const data = await searchTicketsAcrossOrganizations(auth, body, {
|
|
2655
|
+
organizationId: auth.organizationId ?? null
|
|
2656
|
+
});
|
|
2658
2657
|
console.log(JSON.stringify(data, null, 2));
|
|
2659
2658
|
}
|
|
2660
2659
|
|
package/bin/_cli/setup.mjs
CHANGED
|
@@ -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(
|
|
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/bin/_cli/tickets.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { resolveAuth, searchTicketsAcrossOrganizations } from './credentials.mjs';
|
|
4
4
|
import { runCreateCommand } from './new-ticket.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -33,23 +33,10 @@ function parseFlags(args, knownFlags) {
|
|
|
33
33
|
return result;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
...buildAuthHeaders(token, localSecret, organizationId),
|
|
41
|
-
'Content-Type': 'application/json'
|
|
42
|
-
},
|
|
43
|
-
body: JSON.stringify(body)
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const data = await res.json().catch(() => ({}));
|
|
47
|
-
|
|
48
|
-
if (!res.ok) {
|
|
49
|
-
throw new Error(`API error (${res.status}): ${data.error ?? JSON.stringify(data)}`);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return data;
|
|
36
|
+
function parseOrganizationId(value) {
|
|
37
|
+
if (typeof value !== 'string' && typeof value !== 'number') return null;
|
|
38
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
39
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
53
40
|
}
|
|
54
41
|
|
|
55
42
|
export async function ticketsCreate(args) {
|
|
@@ -57,22 +44,20 @@ export async function ticketsCreate(args) {
|
|
|
57
44
|
}
|
|
58
45
|
|
|
59
46
|
export async function ticketsList(args) {
|
|
60
|
-
const flags = parseFlags(args, ['status', 'include-completed']);
|
|
47
|
+
const flags = parseFlags(args, ['status', 'include-completed', 'organization-id']);
|
|
61
48
|
|
|
62
|
-
const
|
|
49
|
+
const auth = await resolveAuth();
|
|
63
50
|
|
|
64
51
|
const body = {
|
|
65
52
|
includeCompleted: flags['include-completed'] !== false,
|
|
66
53
|
...(flags.status ? { statuses: [String(flags.status)] } : {})
|
|
67
54
|
};
|
|
68
55
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
body
|
|
75
|
-
);
|
|
56
|
+
// Organization-agnostic: searches every organization you belong to unless
|
|
57
|
+
// --organization-id scopes it to one.
|
|
58
|
+
const data = await searchTicketsAcrossOrganizations(auth, body, {
|
|
59
|
+
organizationId: parseOrganizationId(flags['organization-id'])
|
|
60
|
+
});
|
|
76
61
|
|
|
77
62
|
if (!data.tickets?.length) {
|
|
78
63
|
console.log('No tickets found.');
|
package/package.json
CHANGED
|
@@ -272,8 +272,8 @@ This keeps the ticket feed readable while preserving the full document in versio
|
|
|
272
272
|
- 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.
|
|
273
273
|
- Use `write-context` for facts a future agent session should know.
|
|
274
274
|
- 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.
|
|
275
|
-
- If you must run `ovld auth login`, `--organization-id <id>` is optional —
|
|
275
|
+
- If you must run `ovld auth login`, `--organization-id <id>` is optional — it validates/scopes that login or command but does not create a stored default organization.
|
|
276
276
|
- Do not add or commit changes unless the user explicitly asks you to commit.
|
|
277
277
|
- Delivery is the concluding step. After delivering, stop implementation work unless the user explicitly asks for follow-up execution; once follow-up execution is complete, deliver again.
|
|
278
278
|
|
|
279
|
-
<!-- version: 0.1.
|
|
279
|
+
<!-- version: 0.1.8 -->
|
|
@@ -102,7 +102,7 @@ For the `record-change-rationales` command and full payload shape with optional
|
|
|
102
102
|
- 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. See [reference/shell-escaping.md](reference/shell-escaping.md).
|
|
103
103
|
- Use `write-context` for facts a future agent session should know.
|
|
104
104
|
- 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.
|
|
105
|
-
- If you must run `ovld auth login`, `--organization-id <id>` is optional —
|
|
105
|
+
- If you must run `ovld auth login`, `--organization-id <id>` is optional — it validates/scopes that login or command but does not create a stored default organization.
|
|
106
106
|
- Do not add or commit changes unless the user explicitly asks you to commit.
|
|
107
107
|
- Delivery is the concluding step. After delivering, stop implementation work unless the user explicitly asks for follow-up execution; once follow-up execution is complete, deliver again.
|
|
108
108
|
|
|
@@ -114,5 +114,5 @@ For the `record-change-rationales` command and full payload shape with optional
|
|
|
114
114
|
- [reference/context.md](reference/context.md) — Shared state, attachments, and large artifact policy
|
|
115
115
|
- [reference/shell-escaping.md](reference/shell-escaping.md) — Heredoc stdin piping for special characters in summaries and payloads
|
|
116
116
|
|
|
117
|
-
<!-- version: 0.5.
|
|
117
|
+
<!-- version: 0.5.11 -->
|
|
118
118
|
|
|
@@ -102,7 +102,7 @@ For the `record-change-rationales` command and full payload shape with optional
|
|
|
102
102
|
- 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. See [reference/shell-escaping.md](reference/shell-escaping.md).
|
|
103
103
|
- Use `write-context` for facts a future agent session should know.
|
|
104
104
|
- 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.
|
|
105
|
-
- If you must run `ovld auth login`, `--organization-id <id>` is optional —
|
|
105
|
+
- If you must run `ovld auth login`, `--organization-id <id>` is optional — it validates/scopes that login or command but does not create a stored default organization.
|
|
106
106
|
- Do not add or commit changes unless the user explicitly asks you to commit.
|
|
107
107
|
- Delivery is the concluding step. After delivering, stop implementation work unless the user explicitly asks for follow-up execution; once follow-up execution is complete, deliver again.
|
|
108
108
|
|
|
@@ -114,5 +114,5 @@ For the `record-change-rationales` command and full payload shape with optional
|
|
|
114
114
|
- [reference/context.md](reference/context.md) — Shared state, attachments, and large artifact policy
|
|
115
115
|
- [reference/shell-escaping.md](reference/shell-escaping.md) — Heredoc stdin piping for special characters in summaries and payloads
|
|
116
116
|
|
|
117
|
-
<!-- version: 0.5.
|
|
117
|
+
<!-- version: 0.5.11 -->
|
|
118
118
|
|
|
@@ -102,7 +102,7 @@ For the `record-change-rationales` command and full payload shape with optional
|
|
|
102
102
|
- 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. See [reference/shell-escaping.md](reference/shell-escaping.md).
|
|
103
103
|
- Use `write-context` for facts a future agent session should know.
|
|
104
104
|
- 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.
|
|
105
|
-
- If you must run `ovld auth login`, `--organization-id <id>` is optional —
|
|
105
|
+
- If you must run `ovld auth login`, `--organization-id <id>` is optional — it validates/scopes that login or command but does not create a stored default organization.
|
|
106
106
|
- Do not add or commit changes unless the user explicitly asks you to commit.
|
|
107
107
|
- Do not create or rely on a local Codex `AGENTS.md` bundle for Overlord.
|
|
108
108
|
- Delivery is the concluding step. After delivering, stop implementation work unless the user explicitly asks for follow-up execution; once follow-up execution is complete, deliver again.
|
|
@@ -115,5 +115,5 @@ For the `record-change-rationales` command and full payload shape with optional
|
|
|
115
115
|
- [reference/context.md](reference/context.md) — Shared state, attachments, and large artifact policy
|
|
116
116
|
- [reference/shell-escaping.md](reference/shell-escaping.md) — Heredoc stdin piping for special characters in summaries and payloads
|
|
117
117
|
|
|
118
|
-
<!-- version: 0.5.
|
|
118
|
+
<!-- version: 0.5.11 -->
|
|
119
119
|
|