overlord-cli 0.2605211850.0 → 0.2605211850.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.
- package/bin/_cli/runner.mjs +14 -6
- package/bin/_cli/setup.mjs +80 -4
- package/package.json +1 -1
package/bin/_cli/runner.mjs
CHANGED
|
@@ -9,6 +9,12 @@ import path from 'node:path';
|
|
|
9
9
|
const OVLD_ENTRY = process.argv[1];
|
|
10
10
|
const DEVICE_FILE = path.join(os.homedir(), '.ovld', 'device.json');
|
|
11
11
|
|
|
12
|
+
/** @internal Test-only overrides for protocol/launch subprocess calls. */
|
|
13
|
+
export const runnerTestHooks = {
|
|
14
|
+
execFileSync: null,
|
|
15
|
+
spawn: null
|
|
16
|
+
};
|
|
17
|
+
|
|
12
18
|
function printRunnerHelp(primaryCommand = 'ovld') {
|
|
13
19
|
console.log(`Usage:
|
|
14
20
|
${primaryCommand} runner once [options]
|
|
@@ -45,7 +51,7 @@ function parseFlags(args) {
|
|
|
45
51
|
return flags;
|
|
46
52
|
}
|
|
47
53
|
|
|
48
|
-
function readOrCreateDeviceFingerprint(flags) {
|
|
54
|
+
export function readOrCreateDeviceFingerprint(flags) {
|
|
49
55
|
const explicit =
|
|
50
56
|
typeof flags['device-fingerprint'] === 'string'
|
|
51
57
|
? flags['device-fingerprint'].trim()
|
|
@@ -69,7 +75,8 @@ function readOrCreateDeviceFingerprint(flags) {
|
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
function runOvld(args, env = {}) {
|
|
72
|
-
|
|
78
|
+
const exec = runnerTestHooks.execFileSync ?? execFileSync;
|
|
79
|
+
return exec(process.execPath, [OVLD_ENTRY, ...args], {
|
|
73
80
|
encoding: 'utf8',
|
|
74
81
|
env: { ...process.env, ...env }
|
|
75
82
|
});
|
|
@@ -111,7 +118,7 @@ function failLaunch(requestId, deviceFingerprint, error) {
|
|
|
111
118
|
);
|
|
112
119
|
}
|
|
113
120
|
|
|
114
|
-
function buildLaunchArgs(launch) {
|
|
121
|
+
export function buildLaunchArgs(launch) {
|
|
115
122
|
const args = ['launch', launch.agent, '--ticket-id', launch.ticketId];
|
|
116
123
|
if (launch.workingDirectory) args.push('--working-directory', launch.workingDirectory);
|
|
117
124
|
if (launch.launchMode === 'ask') args.push('--launch-mode', 'ask');
|
|
@@ -127,7 +134,7 @@ function buildLaunchArgs(launch) {
|
|
|
127
134
|
return args;
|
|
128
135
|
}
|
|
129
136
|
|
|
130
|
-
async function launchClaimedRequest(claim, deviceFingerprint) {
|
|
137
|
+
export async function launchClaimedRequest(claim, deviceFingerprint) {
|
|
131
138
|
const requestId = claim.request?.id;
|
|
132
139
|
if (!requestId || !claim.launch?.agent || !claim.launch?.ticketId) return false;
|
|
133
140
|
|
|
@@ -137,7 +144,8 @@ async function launchClaimedRequest(claim, deviceFingerprint) {
|
|
|
137
144
|
);
|
|
138
145
|
|
|
139
146
|
await new Promise((resolve, reject) => {
|
|
140
|
-
const
|
|
147
|
+
const spawnImpl = runnerTestHooks.spawn ?? spawn;
|
|
148
|
+
const child = spawnImpl(process.execPath, [OVLD_ENTRY, ...args], {
|
|
141
149
|
stdio: 'inherit',
|
|
142
150
|
env: { ...process.env, OVERLORD_DEVICE_FINGERPRINT: deviceFingerprint }
|
|
143
151
|
});
|
|
@@ -170,7 +178,7 @@ async function launchClaimedRequest(claim, deviceFingerprint) {
|
|
|
170
178
|
return true;
|
|
171
179
|
}
|
|
172
180
|
|
|
173
|
-
async function runOnce(flags, deviceFingerprint) {
|
|
181
|
+
export async function runOnce(flags, deviceFingerprint) {
|
|
174
182
|
const claim = claimExecution(flags, deviceFingerprint);
|
|
175
183
|
if (!claim.request) {
|
|
176
184
|
process.stderr.write('[runner] no queued execution requests\n');
|
package/bin/_cli/setup.mjs
CHANGED
|
@@ -14,10 +14,86 @@ import os from 'node:os';
|
|
|
14
14
|
import path from 'node:path';
|
|
15
15
|
import { fileURLToPath } from 'node:url';
|
|
16
16
|
import { checkForCliUpdate, getCurrentCliVersion, printCliUpdateNotice } from './cli-update.mjs';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Legacy Gemini connector (inlined — the lib/ directory is not published with
|
|
20
|
+
// the npm package, so the old relative import broke on global installs)
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
const GEMINI_LEGACY_COMMANDS_DIR = path.join(os.homedir(), '.gemini', 'commands');
|
|
24
|
+
|
|
25
|
+
function geminiLegacyCommandFiles() {
|
|
26
|
+
const base = GEMINI_LEGACY_COMMANDS_DIR;
|
|
27
|
+
return [
|
|
28
|
+
{
|
|
29
|
+
path: path.join(base, 'connect.toml'),
|
|
30
|
+
content: `description = "Connect this session to another Overlord ticket (requires: ticket-id)."\nprompt = """\nConnect this session to another Overlord ticket.\n\nTreat \`{{args}}\` as the target ticket ID.\nIf no ticket ID was provided, ask the user for one and stop.\n\nRun:\n\`ovld protocol connect --ticket-id <ticket_id>\`\n\nRules:\n- Use \`connect\`, not \`attach\`.\n- Do not load extra ticket context unless the user explicitly asks for it.\n- After the command succeeds, report the returned \`SESSION_KEY\` and confirm that future updates should use that ticket.\n"""`
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
path: path.join(base, 'load.toml'),
|
|
34
|
+
content: `description = "Load Overlord ticket context (requires: ticket-id)."\nprompt = """\nLoad Overlord ticket context without attaching to the ticket.\n\nTreat \`{{args}}\` as the target ticket ID.\nIf no ticket ID was provided, ask the user for one and stop.\n\nRun:\n\`ovld protocol load-context --ticket-id <ticket_id>\`\n\nRules:\n- Use \`load-context\`, not \`attach\`.\n- Do not create or switch sessions.\n- Summarize the returned ticket details, history, artifacts, and shared context for the user.\n"""`
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
path: path.join(base, 'attach.toml'),
|
|
38
|
+
content: `description = "Attach this session to an Overlord ticket (requires: ticket-id)."\nprompt = """\nAttach this session to an Overlord ticket.\n\nTreat \`{{args}}\` as the target ticket ID.\nIf no ticket ID was provided, ask the user for one and stop.\n\nRun:\n\`ovld protocol attach --ticket-id <ticket_id>\`\n\nRules:\n- Use \`attach\` to establish a persistent session with a ticket.\n- After the command succeeds, report the returned \`SESSION_KEY\` and confirm that future updates should use that ticket.\n"""`
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
path: path.join(base, 'discuss-objective.toml'),
|
|
42
|
+
content: `description = "Mark a ticket's draft objective as submitted (in discussion)."\nprompt = """\nMark a draft objective as "submitted", indicating the ticket is in active discussion with an agent.\n\nTreat \`{{args}}\` as the target ticket ID.\nIf no ticket ID was provided, ask the user for one and stop.\n\nRun:\n\`ovld protocol discuss-objective --ticket-id <ticket_id>\`\n\nRules:\n- This does NOT start execution. Use \`attach\` for that.\n- After the command succeeds, confirm the objective was submitted.\n"""`
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
path: path.join(base, 'add-objectives.toml'),
|
|
46
|
+
content: `description = "Append ordered objectives to an existing Overlord ticket."\nprompt = """\nAppend ordered objectives to an existing ticket.\n\nUse this when prompts are sequential steps toward the same feature or goal. Create separate tickets when prompts represent different features or goals.\n\nRun:\n\`ovld protocol add-objectives --ticket-id <ticket_id> --objectives-json '[{"objective":"Step one"},{"objective":"Step two"}]'\`\n\nIndex 0 is the first newly added objective to execute; later indexes queue after it.\n"""`
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
path: path.join(base, 'create.toml'),
|
|
50
|
+
content: `description = "Create a draft Overlord ticket from the current conversation."\nprompt = """\nCreate a draft Overlord ticket from the user's request.\n\nUse \`{{args}}\` as the input.\nIf it already contains flags such as \`--title\`, \`--priority\`, \`--project-id\`, or \`--execution-target\`, pass those flags through after \`ovld protocol create --agent gemini\`.\nOtherwise, treat \`{{args}}\` as the objective text and run:\n\`ovld protocol create --agent gemini --objective "<objective>"\`\n\nIf no objective was provided, ask the user for one and stop.\n\nAfter the command succeeds, report the new \`TICKET_ID\`.\n"""`
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
path: path.join(base, 'prompt.toml'),
|
|
54
|
+
content: `description = "Create a new Overlord ticket from the current conversation."\nprompt = """\nCreate a new Overlord ticket from the user's request.\n\nUse \`{{args}}\` as the input.\nIf it already contains flags such as \`--title\`, \`--priority\`, \`--project-id\`, or \`--execution-target\`, pass those flags through after \`ovld protocol prompt --agent gemini\`.\nOtherwise, treat \`{{args}}\` as the objective text and run:\n\`ovld protocol prompt --agent gemini --objective "<objective>"\`\n\nIf no objective was provided, ask the user for one and stop.\n\nAfter the command succeeds, report the new \`TICKET_ID\` and \`SESSION_KEY\`.\n"""`
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
path: path.join(base, 'record-work.toml'),
|
|
58
|
+
content: `description = "Record completed-from-chat work as a ticket in review + feed post (no attach)."\nprompt = """\nImmediately record the work you just completed in this chat as a new Overlord ticket via \`ovld protocol record-work\`. No agent session is opened — the work is already done.\n\nSynthesize from the current conversation:\n- \`objective\`: what was asked / what was done.\n- \`summary\`: reviewer-friendly narrative for the feed.\n- \`changeRationales\`: one entry per meaningful git-tracked file change (\`label\`, \`file_path\`, \`summary\`, \`why\`, \`impact\`, optional \`hunks\`). Use \`git status\` and \`git diff\` to enumerate changed files.\n- \`artifacts\` (optional): \`next_steps\`, \`test_results\`, \`decision\`, \`note\`, \`url\`.\n\nIf \`{{args}}\` is non-empty, treat it as additional context to weave into the summary.\n\nRun \`ovld protocol record-work --payload-file -\` and stream a JSON object \`{ "objective": "...", "summary": "...", "artifacts": [...], "changeRationales": [...] }\` on stdin via a single-quoted heredoc.\n\nAfter the command succeeds, report the new \`TICKET_ID\`.\n\nRules:\n- Do NOT use this for in-progress work. Use \`/prompt\` for that.\n- The CLI validates that every changed git-tracked file is represented in \`changeRationales\` unless \`--skip-file-change-check\` is passed.\n- If project resolution fails, re-run with \`--project-id <id>\` or \`--personal\`.\n"""`
|
|
59
|
+
}
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isRemovableLegacyGeminiCommandFile({ filePath, content, manifestFiles }) {
|
|
64
|
+
if (manifestFiles.has(filePath)) return true;
|
|
65
|
+
const managed = geminiLegacyCommandFiles().find(file => file.path === filePath);
|
|
66
|
+
if (!managed) return false;
|
|
67
|
+
return content.trim() === managed.content.trim();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function removeLegacyGeminiConnectorFiles({ readManifest, writeManifest, readTextFile, existsSync = fs.existsSync.bind(fs), rmSync = fs.rmSync.bind(fs) }) {
|
|
71
|
+
const removed = [];
|
|
72
|
+
const manifest = readManifest();
|
|
73
|
+
const manifestFiles = new Set(manifest.gemini?.files ?? []);
|
|
74
|
+
|
|
75
|
+
for (const file of geminiLegacyCommandFiles()) {
|
|
76
|
+
if (!existsSync(file.path)) continue;
|
|
77
|
+
const existing = readTextFile(file.path);
|
|
78
|
+
if (!isRemovableLegacyGeminiCommandFile({ filePath: file.path, content: existing, manifestFiles })) continue;
|
|
79
|
+
rmSync(file.path, { force: true });
|
|
80
|
+
removed.push(file.path);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (const filePath of manifestFiles) {
|
|
84
|
+
if (removed.includes(filePath) || !existsSync(filePath)) continue;
|
|
85
|
+
if (!filePath.includes('.gemini/commands') || !filePath.endsWith('.toml')) continue;
|
|
86
|
+
rmSync(filePath, { force: true });
|
|
87
|
+
removed.push(filePath);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (manifest.gemini) {
|
|
91
|
+
delete manifest.gemini;
|
|
92
|
+
writeManifest(manifest);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return removed;
|
|
96
|
+
}
|
|
21
97
|
|
|
22
98
|
const BUNDLE_VERSION = '1.12.0';
|
|
23
99
|
const MD_MARKER_START = '<!-- overlord:managed:start -->';
|