overlord-cli 0.2606041130.1 → 0.2606041635.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
@@ -107,6 +107,7 @@ Top-level commands (see `ovld help`):
107
107
  - `<agent> "<prompt>"` - create a ticket from the prompt (project inferred from the current directory) and launch the agent on it in one step, e.g. `ovld claude "fix the flaky login test" --model opus`. Built-in agents need an installed connector (`ovld setup <agent>`, override with `--allow-uninstalled`); your custom agents launch by id. Flags after a standalone `--` pass through to the agent binary.
108
108
  - `attach` - search tickets and launch an agent interactively (`ovld attach [ticketId] [agent]`)
109
109
  - `create` - create a ticket with numbered project selection; supports `--agent`, `--model`, `--delegate` (same delegate flags as `ovld protocol create`)
110
+ - `create-project` - create a project, registering the current directory (or `--directory`) as its primary resource in one step; `--no-directory` makes a bare project (alias for `ovld protocol create-project`)
110
111
  - `prompt` - create a ticket, then launch an agent on it
111
112
  - `auth` - `login`, `status`, `repair` (shared Desktop/CLI credentials), or `logout`
112
113
  - `tickets` - `create` or `list` (optional `--status`)
@@ -150,6 +151,7 @@ Agents can find docs here: https://www.ovld.ai/docs/for-agents
150
151
  - `attachment-download-url` - get a signed download URL for an existing objective attachment
151
152
  - `attachment-upload-file` - prepare, upload, and finalize a local objective attachment in one command
152
153
  - `get-device` / `update-device` - register the caller machine (per organization + user + fingerprint) and rename its label
154
+ - `create-project` - create a project and, when a `--directory` + `--device-fingerprint` are given, register it as the project's primary resource in one step
153
155
  - `list-project-resources` / `add-project-resource` / `update-project-resource` - manage filesystem directories tied to a project device
154
156
  - `request-execution` / `claim-execution` / `list-execution-requests` / `clear-execution-requests` / `complete-execution-launch` / `fail-execution-launch` - durable runner queue operations used by auto-advance and manual Run
155
157
  - `list-organizations` - list every organization the authenticated user (or agent token) belongs to; the runner uses this to poll all of your organizations, not just the one stored at login
@@ -33,6 +33,7 @@ Usage:
33
33
  ${primaryCommand} attach [ticketId] [agent] Search tickets and launch an agent (interactive)
34
34
  ${primaryCommand} create "<objective>" Create a ticket with numbered project selection; supports --agent/--model/--delegate
35
35
  ${primaryCommand} prompt "<objective>" Create a ticket, then launch an agent on it
36
+ ${primaryCommand} create-project --name "<name>" Create a project, optionally linking a directory in one step
36
37
  ${primaryCommand} add-cwd Register the current directory as a project resource (interactive)
37
38
  ${primaryCommand} onboard Create an account, project, cwd link, and onboarding ticket
38
39
  ${primaryCommand} auth <subcommand> Login, logout, repair, or check auth status
@@ -112,6 +113,12 @@ export async function runCli({ primaryCommand }) {
112
113
  return;
113
114
  }
114
115
 
116
+ if (command === 'create-project') {
117
+ // Friendly top-level alias for `ovld protocol create-project`.
118
+ await runProtocolCommand('create-project', rest);
119
+ return;
120
+ }
121
+
115
122
  if (command === 'add-cwd') {
116
123
  const { runAddCwdCommand } = await import('./add-cwd.mjs');
117
124
  await runAddCwdCommand(rest);
@@ -105,14 +105,27 @@ async function promptForYes(question, defaultYes = true) {
105
105
  }
106
106
  }
107
107
 
108
- function buildSignupUrl(platformUrl, verificationUri, name) {
108
+ function buildSignupUrl(platformUrl, verificationUri, name, invite) {
109
109
  const verificationUrl = new URL(verificationUri);
110
110
  const signupUrl = new URL('/signup', platformUrl);
111
111
  signupUrl.searchParams.set('next', `${verificationUrl.pathname}${verificationUrl.search}`);
112
112
  if (name) signupUrl.searchParams.set('name', name);
113
+ // Pre-fill the invited email and accept the invite after signup (web fallback parity).
114
+ if (invite) signupUrl.searchParams.set('invite', invite);
113
115
  return signupUrl.toString();
114
116
  }
115
117
 
118
+ /**
119
+ * Accept either a bare invitation token or a full `…/invite/<token>` URL pasted
120
+ * from the invite email, returning the bare token (or '' when not provided).
121
+ */
122
+ function normalizeInviteToken(raw) {
123
+ const value = typeof raw === 'string' ? raw.trim() : '';
124
+ if (!value) return '';
125
+ const match = value.match(/\/invite\/([^/?#]+)/);
126
+ return match ? decodeURIComponent(match[1]) : value;
127
+ }
128
+
116
129
  async function completeCliOnboarding(platformUrl, localSecret, credentials, payload) {
117
130
  const res = await fetch(`${platformUrl}/api/auth/cli-onboarding`, {
118
131
  method: 'POST',
@@ -141,7 +154,7 @@ export async function runOnboardCommand(args) {
141
154
  const flags = parseFlags(args);
142
155
 
143
156
  if (flags.help === true || flags.h === true || args[0] === 'help') {
144
- console.log(`Usage: ovld onboard [--name <name>] [--organization-name <name>] [--project-name <name>] [--directory <path>] [--yes] [--no-desktop]
157
+ console.log(`Usage: ovld onboard [--name <name>] [--organization-name <name>] [--project-name <name>] [--directory <path>] [--invite <token|url>] [--yes] [--no-desktop]
145
158
 
146
159
  Creates an Overlord account setup from the terminal:
147
160
  1. collects your name, organization, project, and repository directory
@@ -149,21 +162,32 @@ Creates an Overlord account setup from the terminal:
149
162
  3. creates the organization/project, links this directory, and creates the first onboarding ticket
150
163
  4. opens the Desktop download page, or the web project when --no-desktop is used
151
164
 
165
+ With --invite <token>, joins the inviting organization (from an invite email)
166
+ with the invited role instead of creating a new organization; the organization
167
+ prompt is skipped. Accepts a bare token or a full /invite/<token> URL.
168
+
152
169
  Run this from the repository you want Overlord agents to work in.`);
153
170
  return;
154
171
  }
155
172
 
173
+ const inviteToken = normalizeInviteToken(flags.invite ?? flags['invite-code']);
174
+
156
175
  const directoryPath = path.resolve(trimFlag(flags, 'directory') || process.cwd());
157
176
  const directoryName = path.basename(directoryPath) || 'My project';
158
177
  const inferredName = os.userInfo().username || 'User';
159
178
  const name = trimFlag(flags, 'name') || (await promptForValue('Your name', inferredName));
160
- const organizationName =
161
- trimFlag(flags, 'organization-name') || (await promptForValue('Organization name', name));
179
+ // On the invite path the org comes from the invitation, so skip the org prompt.
180
+ const organizationName = inviteToken
181
+ ? trimFlag(flags, 'organization-name')
182
+ : trimFlag(flags, 'organization-name') || (await promptForValue('Organization name', name));
162
183
  const projectName =
163
184
  trimFlag(flags, 'project-name') || (await promptForValue('Project name', directoryName));
164
185
 
165
- if (!name || !organizationName || !projectName) {
166
- throw new Error('Name, organization name, and project name are required.');
186
+ if (!name || !projectName) {
187
+ throw new Error('Name and project name are required.');
188
+ }
189
+ if (!inviteToken && !organizationName) {
190
+ throw new Error('Organization name is required (or pass --invite <token> to join an org).');
167
191
  }
168
192
 
169
193
  const storedCredentials = loadCredentials();
@@ -176,7 +200,7 @@ Run this from the repository you want Overlord agents to work in.`);
176
200
 
177
201
  const credentials = await authLoginViaDeviceFlow(platformUrl, localSecret, {
178
202
  browserOpener: verificationUri => {
179
- const signupUrl = buildSignupUrl(platformUrl, verificationUri, name);
203
+ const signupUrl = buildSignupUrl(platformUrl, verificationUri, name, inviteToken);
180
204
  console.log(` Signup URL: ${signupUrl}`);
181
205
  openBrowser(signupUrl);
182
206
  }
@@ -186,12 +210,13 @@ Run this from the repository you want Overlord agents to work in.`);
186
210
  const deviceFingerprint = readOrCreateDeviceFingerprint(flags);
187
211
  const result = await completeCliOnboarding(resolvedPlatformUrl, localSecret, credentials, {
188
212
  name,
189
- organizationName,
213
+ ...(organizationName ? { organizationName } : {}),
190
214
  projectName,
191
215
  directoryPath,
192
216
  deviceFingerprint,
193
217
  deviceHostname: os.hostname(),
194
- devicePlatform: process.platform
218
+ devicePlatform: process.platform,
219
+ ...(inviteToken ? { inviteToken } : {})
195
220
  });
196
221
 
197
222
  saveCredentials({
@@ -216,9 +241,14 @@ Run this from the repository you want Overlord agents to work in.`);
216
241
 
217
242
  console.log('\nOverlord setup complete.');
218
243
  console.log(` Organization: ${result.organization.organizationName}`);
244
+ if (result.organization.role) {
245
+ console.log(` Role: ${result.organization.role}`);
246
+ }
219
247
  console.log(` Project: ${result.project.name}`);
220
248
  console.log(` Directory: ${directoryPath}`);
221
- console.log(` Onboarding ticket: ${result.ticket.reference ?? result.ticket.id}`);
249
+ if (result.ticket) {
250
+ console.log(` Onboarding ticket: ${result.ticket.reference ?? result.ticket.id}`);
251
+ }
222
252
 
223
253
  const skipDesktop = flags['no-desktop'] === true || flags.desktop === 'false';
224
254
  const autoYes = flags.yes === true || flags.y === true;
@@ -1982,6 +1982,135 @@ async function protocolUpdateProjectResource(args) {
1982
1982
  console.log(JSON.stringify(data, null, 2));
1983
1983
  }
1984
1984
 
1985
+ // ---------------------------------------------------------------------------
1986
+ // create-project (create a project, optionally registering a primary directory)
1987
+ // ---------------------------------------------------------------------------
1988
+
1989
+ async function protocolCreateProject(args) {
1990
+ const flags = parseFlags(args);
1991
+
1992
+ if (flags.help === true || flags.h === true) {
1993
+ console.log(`Usage: ovld create-project --name "<name>" [options]
1994
+
1995
+ Create a project. By default the current working directory is registered as the
1996
+ project's primary resource in the same call (one-step setup).
1997
+
1998
+ Options:
1999
+ --name <text> Project name (required)
2000
+ --color <#rrggbb> Project color (defaults to a soft rose)
2001
+ --directory <path> Directory to register as the primary resource
2002
+ (defaults to the current working directory)
2003
+ --no-directory Create a bare project without registering a directory
2004
+ --device-fingerprint <fp> Device for the directory (or OVERLORD_DEVICE_FINGERPRINT;
2005
+ auto-generated when omitted)
2006
+ --label <text> Label for the registered directory
2007
+ --is-primary <true|false> Override primary status (directory defaults to primary)
2008
+ --organization-id <id> Create in a specific organization (defaults to membership)
2009
+
2010
+ This command is also available as \`ovld protocol create-project\`.`);
2011
+ return;
2012
+ }
2013
+
2014
+ const { platformUrl, bearerToken, localSecret, organizationId } =
2015
+ await resolveProtocolAuthForFlags(flags);
2016
+ const timeoutMs = resolveTimeout(flags);
2017
+
2018
+ const name = typeof flags.name === 'string' ? flags.name.trim() : '';
2019
+ if (!name) {
2020
+ console.error('Error: --name is required');
2021
+ process.exit(1);
2022
+ }
2023
+
2024
+ // A directory makes project creation a one-step process: the project is
2025
+ // created and the directory is registered as its primary resource together.
2026
+ // Pass --no-directory (or --directory=false) to create a bare project.
2027
+ const directoryRequested = !(flags.directory === false || flags.directory === 'false');
2028
+ let directoryPath = '';
2029
+ if (directoryRequested) {
2030
+ directoryPath = path.resolve(
2031
+ typeof flags.directory === 'string' && flags.directory.trim()
2032
+ ? flags.directory.trim()
2033
+ : process.cwd()
2034
+ );
2035
+ if (!fs.existsSync(directoryPath)) {
2036
+ console.error(`Error: Directory does not exist: ${directoryPath}`);
2037
+ process.exit(1);
2038
+ }
2039
+ }
2040
+
2041
+ let deviceFingerprint = '';
2042
+ if (directoryPath) {
2043
+ deviceFingerprint = String(
2044
+ flags['device-fingerprint'] ?? process.env.OVERLORD_DEVICE_FINGERPRINT ?? ''
2045
+ ).trim();
2046
+ if (!deviceFingerprint) {
2047
+ const { readOrCreateDeviceFingerprint } = await import('./runner.mjs');
2048
+ deviceFingerprint = readOrCreateDeviceFingerprint(flags);
2049
+ }
2050
+ }
2051
+
2052
+ const isPrimary =
2053
+ flags['is-primary'] !== undefined
2054
+ ? flags['is-primary'] === true || flags['is-primary'] === 'true'
2055
+ : flags.primary !== undefined
2056
+ ? !(flags.primary === false || flags.primary === 'false')
2057
+ : undefined;
2058
+
2059
+ const body = {
2060
+ name,
2061
+ ...(typeof flags.color === 'string' && flags.color.trim() ? { color: flags.color.trim() } : {}),
2062
+ ...(directoryPath
2063
+ ? {
2064
+ directoryPath,
2065
+ deviceFingerprint,
2066
+ ...(isPrimary !== undefined ? { isPrimary } : {}),
2067
+ ...(typeof flags.label === 'string' ? { label: flags.label } : {}),
2068
+ ...(flags['device-hostname']
2069
+ ? { deviceHostname: String(flags['device-hostname']) }
2070
+ : { deviceHostname: os.hostname() }),
2071
+ ...(flags['device-platform']
2072
+ ? { devicePlatform: String(flags['device-platform']) }
2073
+ : { devicePlatform: process.platform }),
2074
+ ...(flags['device-port'] != null ? { devicePort: Number(flags['device-port']) } : {})
2075
+ }
2076
+ : {})
2077
+ };
2078
+
2079
+ const data = await apiPost(
2080
+ platformUrl,
2081
+ bearerToken,
2082
+ localSecret,
2083
+ organizationId,
2084
+ '/api/protocol/create-project',
2085
+ body,
2086
+ timeoutMs
2087
+ );
2088
+ console.log(JSON.stringify(data, null, 2));
2089
+
2090
+ if (data.project?.id) {
2091
+ process.stderr.write(`\nPROJECT_ID=${data.project.id}\n`);
2092
+ }
2093
+ if (data.resource?.id) {
2094
+ process.stderr.write(`RESOURCE_ID=${data.resource.id}\n`);
2095
+ }
2096
+
2097
+ // Write .overlord/project.json so future cwd-based resolution finds the project.
2098
+ if (directoryPath && data.project?.id && data.project?.name) {
2099
+ try {
2100
+ const { upsertLocalOverlordConfig } = await import('./local-config.mjs');
2101
+ const result = await upsertLocalOverlordConfig({
2102
+ directoryPath,
2103
+ project: { id: data.project.id, name: data.project.name }
2104
+ });
2105
+ process.stderr.write(`OVERLORD_CONFIG=${result.filePath} (${result.action})\n`);
2106
+ } catch (configError) {
2107
+ process.stderr.write(
2108
+ `\nWarning: could not write .overlord/project.json: ${configError instanceof Error ? configError.message : configError}\n`
2109
+ );
2110
+ }
2111
+ }
2112
+ }
2113
+
1985
2114
  // ---------------------------------------------------------------------------
1986
2115
  // execution requests (durable runner queue)
1987
2116
  // ---------------------------------------------------------------------------
@@ -2750,6 +2879,7 @@ Subcommands:
2750
2879
  attachment-upload-file Prepare, upload, and finalize a local file in one command
2751
2880
  get-device Identify / register the current device and return its label
2752
2881
  update-device Rename this device's label (must be lowercase kebab-case)
2882
+ create-project Create a project, optionally linking the current directory in one step
2753
2883
  list-project-resources List resource directories registered for a project
2754
2884
  add-project-resource Register a local directory as a project resource on this device
2755
2885
  update-project-resource Update a resource directory's path, label, or primary status
@@ -3180,6 +3310,20 @@ update-device:
3180
3310
  --device-fingerprint <fp> (or OVERLORD_DEVICE_FINGERPRINT env var)
3181
3311
  --label <label> Lowercase kebab-case, unique within org
3182
3312
 
3313
+ create-project:
3314
+ Required:
3315
+ --name <text> Project name
3316
+ Optional:
3317
+ --color <#rrggbb> Project color (defaults to a soft rose)
3318
+ --directory <path> Register this directory as the project's primary resource
3319
+ in one step (defaults to the current working directory;
3320
+ pass --no-directory to create a bare project)
3321
+ --device-fingerprint <fp> Device for the directory (or OVERLORD_DEVICE_FINGERPRINT;
3322
+ auto-generated when omitted)
3323
+ --label <text> Label for the registered directory
3324
+ --is-primary <true|false> Override primary status (directory defaults to primary)
3325
+ --organization-id <id> Create in a specific organization (defaults to your membership)
3326
+
3183
3327
  list-project-resources:
3184
3328
  Required:
3185
3329
  --project-id <uuid>
@@ -3322,6 +3466,8 @@ Examples:
3322
3466
  EOF
3323
3467
  ovld protocol get-device --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT
3324
3468
  ovld protocol update-device --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT --label work-macbook
3469
+ ovld protocol create-project --name "Acme Web" --directory /path/to/repo
3470
+ ovld protocol create-project --name "Acme Web" --no-directory
3325
3471
  ovld protocol list-project-resources --project-id <project-uuid>
3326
3472
  ovld protocol list-project-resources --project-id <project-uuid> --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT
3327
3473
  ovld protocol add-project-resource --project-id <project-uuid> --device-fingerprint $OVERLORD_DEVICE_FINGERPRINT
@@ -3476,6 +3622,10 @@ EOF
3476
3622
  await protocolUpdateDevice(args);
3477
3623
  return;
3478
3624
  }
3625
+ if (subcommand === 'create-project') {
3626
+ await protocolCreateProject(args);
3627
+ return;
3628
+ }
3479
3629
  if (subcommand === 'list-project-resources') {
3480
3630
  await protocolListProjectResources(args);
3481
3631
  return;
@@ -1125,6 +1125,95 @@ function removeCursorBeforeSubmitHook(paths) {
1125
1125
  }
1126
1126
 
1127
1127
 
1128
+ // ---------------------------------------------------------------------------
1129
+ // Headless marketplace plugin install (Claude + Codex, via the agent CLI)
1130
+ // ---------------------------------------------------------------------------
1131
+
1132
+ const OVERLORD_PLUGIN_NAME = 'overlord';
1133
+ const OVERLORD_MARKETPLACE_NAME = 'overlord-local';
1134
+ const OVERLORD_PLUGIN_KEY = `${OVERLORD_PLUGIN_NAME}@${OVERLORD_MARKETPLACE_NAME}`;
1135
+ const CLAUDE_MARKETPLACE_ROOT = path.join(os.homedir(), '.ovld', 'claude-marketplace');
1136
+
1137
+ /**
1138
+ * Run an agent CLI plugin command without throwing.
1139
+ * `missing` is true when the agent binary is not on PATH (ENOENT).
1140
+ *
1141
+ * @returns {{ ok: boolean, missing: boolean, stdout: string, stderr: string }}
1142
+ */
1143
+ function runAgentPluginCli(binary, args) {
1144
+ try {
1145
+ const stdout = execFileSync(binary, args, {
1146
+ encoding: 'utf8',
1147
+ stdio: ['ignore', 'pipe', 'pipe']
1148
+ });
1149
+ return { ok: true, missing: false, stdout: stdout ?? '', stderr: '' };
1150
+ } catch (err) {
1151
+ return {
1152
+ ok: false,
1153
+ missing: err?.code === 'ENOENT',
1154
+ stdout: err?.stdout?.toString?.() ?? '',
1155
+ stderr: err?.stderr?.toString?.() ?? ''
1156
+ };
1157
+ }
1158
+ }
1159
+
1160
+ /**
1161
+ * Build the local marketplace wrapper under ~/.ovld/claude-marketplace and
1162
+ * register + install the Overlord plugin through the `claude` CLI so the plugin
1163
+ * (and its `overlord:overlord-ticket` skill) loads in every Claude session —
1164
+ * including headless/pod environments where Overlord Desktop never ran. Mirrors
1165
+ * the desktop installer's registerClaudeLocalMarketplace, but drives Claude's
1166
+ * own plugin system instead of hand-writing its JSON.
1167
+ *
1168
+ * @returns {{ ok: boolean, missing: boolean }}
1169
+ */
1170
+ function installClaudeMarketplacePlugin(sourceDir, version) {
1171
+ const pluginDir = path.join(CLAUDE_MARKETPLACE_ROOT, 'plugins', OVERLORD_PLUGIN_NAME);
1172
+ fs.mkdirSync(path.dirname(pluginDir), { recursive: true });
1173
+ fs.rmSync(pluginDir, { recursive: true, force: true });
1174
+ fs.cpSync(sourceDir, pluginDir, { recursive: true });
1175
+
1176
+ const marketplaceManifest = {
1177
+ name: OVERLORD_MARKETPLACE_NAME,
1178
+ owner: { name: 'Cooperativ', email: 'support@ovld.ai' },
1179
+ metadata: {
1180
+ description: 'Overlord plugin registered locally by the Overlord CLI.',
1181
+ version
1182
+ },
1183
+ plugins: [
1184
+ {
1185
+ name: OVERLORD_PLUGIN_NAME,
1186
+ source: `./plugins/${OVERLORD_PLUGIN_NAME}`,
1187
+ description:
1188
+ 'Overlord ticket protocol workflow for Claude Code (attach/update/ask/deliver, slash commands, permission hook).',
1189
+ version
1190
+ }
1191
+ ]
1192
+ };
1193
+ const manifestPath = path.join(CLAUDE_MARKETPLACE_ROOT, '.claude-plugin', 'marketplace.json');
1194
+ fs.mkdirSync(path.dirname(manifestPath), { recursive: true });
1195
+ fs.writeFileSync(manifestPath, JSON.stringify(marketplaceManifest, null, 2) + '\n');
1196
+
1197
+ // Register the marketplace (idempotent: refresh if it was already added).
1198
+ const added = runAgentPluginCli('claude', [
1199
+ 'plugin',
1200
+ 'marketplace',
1201
+ 'add',
1202
+ CLAUDE_MARKETPLACE_ROOT
1203
+ ]);
1204
+ if (added.missing) return { ok: false, missing: true };
1205
+ if (!added.ok) {
1206
+ runAgentPluginCli('claude', ['plugin', 'marketplace', 'update', OVERLORD_MARKETPLACE_NAME]);
1207
+ }
1208
+
1209
+ // Install the plugin (idempotent: update if it was already installed).
1210
+ let installed = runAgentPluginCli('claude', ['plugin', 'install', OVERLORD_PLUGIN_KEY]);
1211
+ if (!installed.ok && !installed.missing) {
1212
+ installed = runAgentPluginCli('claude', ['plugin', 'update', OVERLORD_PLUGIN_KEY]);
1213
+ }
1214
+ return { ok: installed.ok, missing: installed.missing };
1215
+ }
1216
+
1128
1217
  function installClaude() {
1129
1218
  const sourceDir = claudeSourcePluginDir();
1130
1219
  const sourceManifest = path.join(sourceDir, '.claude-plugin', 'plugin.json');
@@ -1138,9 +1227,18 @@ function installClaude() {
1138
1227
  } else {
1139
1228
  console.log(' ✓ No v3.25 Claude connector files needed migration.');
1140
1229
  }
1141
- console.log(
1142
- ' ✓ `ovld launch claude` now uses the installed Claude local marketplace plugin by default.'
1143
- );
1230
+
1231
+ const result = installClaudeMarketplacePlugin(sourceDir, sourceVersion);
1232
+ if (result.ok) {
1233
+ console.log(` ✓ Registered + installed the Claude plugin (${OVERLORD_PLUGIN_KEY}) via the claude CLI.`);
1234
+ } else if (result.missing) {
1235
+ console.log(' ⚠ `claude` CLI not found on PATH — wrote the local marketplace but could not install the plugin.');
1236
+ console.log(
1237
+ ` Run \`claude plugin marketplace add ${CLAUDE_MARKETPLACE_ROOT} && claude plugin install ${OVERLORD_PLUGIN_KEY}\` once Claude Code is installed.`
1238
+ );
1239
+ } else {
1240
+ console.log(' ⚠ Could not install the Claude plugin via the claude CLI. The local marketplace was written; rerun `ovld setup claude` to retry.');
1241
+ }
1144
1242
 
1145
1243
  const manifest = readManifest();
1146
1244
  manifest.claude = {
@@ -1234,6 +1332,21 @@ function installCodex() {
1234
1332
 
1235
1333
  removeLegacyCodexBundle();
1236
1334
 
1335
+ // Copying the plugin + writing the marketplace only makes the plugin
1336
+ // "available". `codex plugin add` is what actually installs/enables it, so the
1337
+ // skill loads in headless/pod sessions where Overlord Desktop never ran.
1338
+ const codexAdd = runAgentPluginCli('codex', ['plugin', 'add', OVERLORD_PLUGIN_KEY]);
1339
+ if (codexAdd.ok) {
1340
+ console.log(` ✓ Installed plugin via codex CLI: ${OVERLORD_PLUGIN_KEY}`);
1341
+ } else if (codexAdd.missing) {
1342
+ console.log(' ⚠ `codex` CLI not found on PATH — wrote the marketplace but could not install the plugin.');
1343
+ console.log(` Run \`codex plugin add ${OVERLORD_PLUGIN_KEY}\` once Codex is installed.`);
1344
+ } else if (/already/i.test(`${codexAdd.stdout}${codexAdd.stderr}`)) {
1345
+ console.log(` ✓ Codex plugin already installed: ${OVERLORD_PLUGIN_KEY}`);
1346
+ } else {
1347
+ console.log(' ⚠ Could not install the Codex plugin via the codex CLI. The marketplace was written; rerun `ovld setup codex` to retry.');
1348
+ }
1349
+
1237
1350
  const installedFiles = [
1238
1351
  ...listFilesRecursive(CODEX_TARGET_PLUGIN_DIR),
1239
1352
  CODEX_TARGET_MARKETPLACE,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "overlord-cli",
3
- "version": "0.2606041130.1",
3
+ "version": "0.2606041635.0",
4
4
  "description": "Overlord CLI — launch AI agents on tickets from anywhere",
5
5
  "homepage": "https://www.ovld.ai/docs/",
6
6
  "type": "module",
@@ -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.11 -->
117
+ <!-- version: 0.5.12 -->
118
118
 
@@ -140,6 +140,29 @@ ovld protocol discover-project --working-directory /path/to/repo
140
140
 
141
141
  Use `--project-id` when the project id is already known. Use `--working-directory` to override cwd path matching. If the runtime has an `OVERLORD_DEVICE_FINGERPRINT`, pass `--device-fingerprint "$OVERLORD_DEVICE_FINGERPRINT"` so resource-directory matching prefers the current device.
142
142
 
143
+ ## Creating Projects
144
+
145
+ Users and agents can create a project directly from the CLI. By default the current
146
+ working directory is registered as the new project's primary resource in the same
147
+ call (one-step setup); pass `--no-directory` to create a bare project.
148
+
149
+ ```bash
150
+ # Create a project and link the current directory in one step
151
+ ovld protocol create-project --name "Acme Web"
152
+
153
+ # Link a specific directory
154
+ ovld protocol create-project --name "Acme Web" --directory /path/to/repo
155
+
156
+ # Create a bare project with no directory
157
+ ovld protocol create-project --name "Acme Web" --no-directory
158
+ ```
159
+
160
+ `ovld create-project` is a friendly top-level alias for `ovld protocol create-project`.
161
+ When a directory is registered the command also writes `.overlord/project.json` so
162
+ future cwd-based resolution finds the project. Pass `--organization-id <id>` to create
163
+ in a specific organization (defaults to your membership); `--color <#rrggbb>` sets the
164
+ project color.
165
+
143
166
  ### Resolving the project ID when you don't have one
144
167
 
145
168
  When you need a project ID for a protocol command and the ticket prompt did not supply one, resolve it in this order.
@@ -1,6 +1,6 @@
1
1
  # Execution Targets And Checkout Paths
2
2
 
3
- Execution targets are canonical rows keyed by a real `device_fingerprint` when known, or by a placeholder until the target registers. Organization labels, user access, project membership, and project checkout paths live in separate association rows. Persist a fingerprint per machine, upsert via `ovld protocol get-device`, then maintain checkout paths via `list-project-resources`, `add-project-resource`, `update-project-resource`, and `update-device` (`ovld protocol help` lists flags).
3
+ Execution targets are canonical rows keyed by a real `device_fingerprint` when known, or by a placeholder until the target registers. Organization labels, user access, project membership, and project checkout paths live in separate association rows. Persist a fingerprint per machine, upsert via `ovld protocol get-device`, then maintain checkout paths via `list-project-resources`, `add-project-resource`, `update-project-resource`, and `update-device` (`ovld protocol help` lists flags). To create a brand-new project (optionally registering a directory as its primary resource in one step) use `ovld protocol create-project --name "..."`.
4
4
 
5
5
  ```bash
6
6
  ovld protocol get-device --device-fingerprint "$OVERLORD_DEVICE_FINGERPRINT"
@@ -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.11 -->
117
+ <!-- version: 0.5.12 -->
118
118
 
@@ -140,6 +140,29 @@ ovld protocol discover-project --working-directory /path/to/repo
140
140
 
141
141
  Use `--project-id` when the project id is already known. Use `--working-directory` to override cwd path matching. If the runtime has an `OVERLORD_DEVICE_FINGERPRINT`, pass `--device-fingerprint "$OVERLORD_DEVICE_FINGERPRINT"` so resource-directory matching prefers the current device.
142
142
 
143
+ ## Creating Projects
144
+
145
+ Users and agents can create a project directly from the CLI. By default the current
146
+ working directory is registered as the new project's primary resource in the same
147
+ call (one-step setup); pass `--no-directory` to create a bare project.
148
+
149
+ ```bash
150
+ # Create a project and link the current directory in one step
151
+ ovld protocol create-project --name "Acme Web"
152
+
153
+ # Link a specific directory
154
+ ovld protocol create-project --name "Acme Web" --directory /path/to/repo
155
+
156
+ # Create a bare project with no directory
157
+ ovld protocol create-project --name "Acme Web" --no-directory
158
+ ```
159
+
160
+ `ovld create-project` is a friendly top-level alias for `ovld protocol create-project`.
161
+ When a directory is registered the command also writes `.overlord/project.json` so
162
+ future cwd-based resolution finds the project. Pass `--organization-id <id>` to create
163
+ in a specific organization (defaults to your membership); `--color <#rrggbb>` sets the
164
+ project color.
165
+
143
166
  ### Resolving the project ID when you don't have one
144
167
 
145
168
  When you need a project ID for a protocol command and the ticket prompt did not supply one, resolve it in this order.
@@ -1,6 +1,6 @@
1
1
  # Execution Targets And Checkout Paths
2
2
 
3
- Execution targets are canonical rows keyed by a real `device_fingerprint` when known, or by a placeholder until the target registers. Organization labels, user access, project membership, and project checkout paths live in separate association rows. Persist a fingerprint per machine, upsert via `ovld protocol get-device`, then maintain checkout paths via `list-project-resources`, `add-project-resource`, `update-project-resource`, and `update-device` (`ovld protocol help` lists flags).
3
+ Execution targets are canonical rows keyed by a real `device_fingerprint` when known, or by a placeholder until the target registers. Organization labels, user access, project membership, and project checkout paths live in separate association rows. Persist a fingerprint per machine, upsert via `ovld protocol get-device`, then maintain checkout paths via `list-project-resources`, `add-project-resource`, `update-project-resource`, and `update-device` (`ovld protocol help` lists flags). To create a brand-new project (optionally registering a directory as its primary resource in one step) use `ovld protocol create-project --name "..."`.
4
4
 
5
5
  ```bash
6
6
  ovld protocol get-device --device-fingerprint "$OVERLORD_DEVICE_FINGERPRINT"
@@ -856,6 +856,61 @@ const tools = [
856
856
  }),
857
857
  subcommand: 'update-device'
858
858
  },
859
+ {
860
+ name: 'create_project',
861
+ description:
862
+ 'Create a new project. By default the current working directory is registered as the project\'s primary resource in one step; pass no_directory:true for a bare project, or directory_path to link a specific directory.',
863
+ inputSchema: {
864
+ type: 'object',
865
+ properties: {
866
+ name: { type: 'string', description: 'Project name.' },
867
+ color: { type: 'string', description: 'Optional hex color like #d4d4d8.' },
868
+ directory_path: {
869
+ type: 'string',
870
+ description: 'Absolute path to register as primary; omit to use cwd.'
871
+ },
872
+ no_directory: {
873
+ type: 'boolean',
874
+ description: 'Create a bare project without registering any directory.'
875
+ },
876
+ device_fingerprint: { type: 'string' },
877
+ label: { type: 'string' },
878
+ is_primary: { type: 'boolean' },
879
+ device_hostname: { type: 'string' },
880
+ device_platform: { type: 'string' },
881
+ organization_id: { type: 'string' }
882
+ },
883
+ required: ['name']
884
+ },
885
+ toCliFlags: args => ({
886
+ name: args.name,
887
+ ...(typeof args.color === 'string' && args.color.trim().length > 0
888
+ ? { color: args.color.trim() }
889
+ : {}),
890
+ ...(args.no_directory === true || args.no_directory === 'true'
891
+ ? { directory: 'false' }
892
+ : typeof args.directory_path === 'string' && args.directory_path.trim().length > 0
893
+ ? { directory: args.directory_path }
894
+ : {}),
895
+ ...(typeof args.device_fingerprint === 'string' && args.device_fingerprint.trim().length > 0
896
+ ? { 'device-fingerprint': args.device_fingerprint.trim() }
897
+ : {}),
898
+ ...(typeof args.label === 'string' ? { label: args.label } : {}),
899
+ ...(args.is_primary !== undefined
900
+ ? { 'is-primary': args.is_primary === true || args.is_primary === 'true' }
901
+ : {}),
902
+ ...(typeof args.device_hostname === 'string' && args.device_hostname.trim().length > 0
903
+ ? { 'device-hostname': args.device_hostname }
904
+ : {}),
905
+ ...(typeof args.device_platform === 'string' && args.device_platform.trim().length > 0
906
+ ? { 'device-platform': args.device_platform }
907
+ : {}),
908
+ ...(typeof args.organization_id === 'string' && args.organization_id.trim().length > 0
909
+ ? { 'organization-id': args.organization_id.trim() }
910
+ : {})
911
+ }),
912
+ subcommand: 'create-project'
913
+ },
859
914
  {
860
915
  name: 'list_project_resources',
861
916
  description:
@@ -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.11 -->
118
+ <!-- version: 0.5.12 -->
119
119
 
@@ -140,6 +140,29 @@ ovld protocol discover-project --working-directory /path/to/repo
140
140
 
141
141
  Use `--project-id` when the project id is already known. Use `--working-directory` to override cwd path matching. If the runtime has an `OVERLORD_DEVICE_FINGERPRINT`, pass `--device-fingerprint "$OVERLORD_DEVICE_FINGERPRINT"` so resource-directory matching prefers the current device.
142
142
 
143
+ ## Creating Projects
144
+
145
+ Users and agents can create a project directly from the CLI. By default the current
146
+ working directory is registered as the new project's primary resource in the same
147
+ call (one-step setup); pass `--no-directory` to create a bare project.
148
+
149
+ ```bash
150
+ # Create a project and link the current directory in one step
151
+ ovld protocol create-project --name "Acme Web"
152
+
153
+ # Link a specific directory
154
+ ovld protocol create-project --name "Acme Web" --directory /path/to/repo
155
+
156
+ # Create a bare project with no directory
157
+ ovld protocol create-project --name "Acme Web" --no-directory
158
+ ```
159
+
160
+ `ovld create-project` is a friendly top-level alias for `ovld protocol create-project`.
161
+ When a directory is registered the command also writes `.overlord/project.json` so
162
+ future cwd-based resolution finds the project. Pass `--organization-id <id>` to create
163
+ in a specific organization (defaults to your membership); `--color <#rrggbb>` sets the
164
+ project color.
165
+
143
166
  ### Resolving the project ID when you don't have one
144
167
 
145
168
  When you need a project ID for a protocol command and the ticket prompt did not supply one, resolve it in this order.
@@ -1,6 +1,6 @@
1
1
  # Execution Targets And Checkout Paths
2
2
 
3
- Execution targets are canonical rows keyed by a real `device_fingerprint` when known, or by a placeholder until the target registers. Organization labels, user access, project membership, and project checkout paths live in separate association rows. Persist a fingerprint per machine, upsert via `ovld protocol get-device`, then maintain checkout paths via `list-project-resources`, `add-project-resource`, `update-project-resource`, and `update-device` (`ovld protocol help` lists flags).
3
+ Execution targets are canonical rows keyed by a real `device_fingerprint` when known, or by a placeholder until the target registers. Organization labels, user access, project membership, and project checkout paths live in separate association rows. Persist a fingerprint per machine, upsert via `ovld protocol get-device`, then maintain checkout paths via `list-project-resources`, `add-project-resource`, `update-project-resource`, and `update-device` (`ovld protocol help` lists flags). To create a brand-new project (optionally registering a directory as its primary resource in one step) use `ovld protocol create-project --name "..."`.
4
4
 
5
5
  ```bash
6
6
  ovld protocol get-device --device-fingerprint "$OVERLORD_DEVICE_FINGERPRINT"