@getmonoceros/workbench 1.8.0 → 1.9.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/dist/bin.js CHANGED
@@ -251,25 +251,25 @@ function detectHelpRequest(argv, main2) {
251
251
  const separatorIdx = argv.indexOf("--");
252
252
  if (helpIdx === -1) return null;
253
253
  if (separatorIdx !== -1 && separatorIdx < helpIdx) return null;
254
- const path16 = [];
254
+ const path17 = [];
255
255
  const tokens = argv.slice(
256
256
  0,
257
257
  separatorIdx === -1 ? argv.length : separatorIdx
258
258
  );
259
259
  let cursor = main2;
260
260
  const mainName = (main2.meta ?? {}).name ?? "monoceros";
261
- path16.push(mainName);
261
+ path17.push(mainName);
262
262
  for (const tok of tokens) {
263
263
  if (tok.startsWith("-")) continue;
264
264
  const subs = cursor.subCommands ?? {};
265
265
  if (tok in subs) {
266
266
  cursor = subs[tok];
267
- path16.push(tok);
267
+ path17.push(tok);
268
268
  continue;
269
269
  }
270
270
  break;
271
271
  }
272
- return { path: path16, cmd: cursor };
272
+ return { path: path17, cmd: cursor };
273
273
  }
274
274
  async function maybeRenderHelp(argv, main2) {
275
275
  const hit = detectHelpRequest(argv, main2);
@@ -301,17 +301,17 @@ function getInnerArgs() {
301
301
  }
302
302
 
303
303
  // src/main.ts
304
- import { defineCommand as defineCommand28 } from "citty";
304
+ import { defineCommand as defineCommand29 } from "citty";
305
305
 
306
306
  // src/commands/add-apt-packages.ts
307
307
  import { defineCommand } from "citty";
308
308
  import { consola as consola2 } from "consola";
309
309
 
310
310
  // src/modify/index.ts
311
- import { promises as fs7 } from "fs";
311
+ import { promises as fs8 } from "fs";
312
312
  import { consola } from "consola";
313
313
  import { createPatch } from "diff";
314
- import path6 from "path";
314
+ import path7 from "path";
315
315
 
316
316
  // src/config/io.ts
317
317
  import { promises as fs } from "fs";
@@ -1161,10 +1161,175 @@ ${existing}` : leakedComment;
1161
1161
  }
1162
1162
  }
1163
1163
 
1164
+ // src/init/components.ts
1165
+ import { existsSync as existsSync2, promises as fs4 } from "fs";
1166
+ import path3 from "path";
1167
+ import { z as z3 } from "zod";
1168
+ import { parse as parseYaml } from "yaml";
1169
+ var CategorySchema = z3.enum(["language", "service", "feature"]);
1170
+ var FeatureContributionSchema = z3.object({
1171
+ ref: z3.string().regex(REGEX.featureRef),
1172
+ options: z3.record(z3.string(), FeatureOptionValueSchema).optional()
1173
+ });
1174
+ var ComponentFileSchema = z3.object({
1175
+ displayName: z3.string().min(1),
1176
+ description: z3.string().min(1),
1177
+ category: CategorySchema,
1178
+ contributes: z3.object({
1179
+ languages: z3.array(z3.string().min(1)).optional(),
1180
+ services: z3.array(z3.string().min(1)).optional(),
1181
+ features: z3.array(FeatureContributionSchema).optional()
1182
+ })
1183
+ }).superRefine((data, ctx) => {
1184
+ const c = data.contributes;
1185
+ const filled = [
1186
+ c.languages && c.languages.length > 0 ? "languages" : null,
1187
+ c.services && c.services.length > 0 ? "services" : null,
1188
+ c.features && c.features.length > 0 ? "features" : null
1189
+ ].filter((x) => x !== null);
1190
+ if (filled.length === 0) {
1191
+ ctx.addIssue({
1192
+ code: z3.ZodIssueCode.custom,
1193
+ message: "contributes must set at least one of languages/services/features"
1194
+ });
1195
+ return;
1196
+ }
1197
+ if (filled.length > 1) {
1198
+ ctx.addIssue({
1199
+ code: z3.ZodIssueCode.custom,
1200
+ message: `contributes must set exactly one of languages/services/features, got: ${filled.join(", ")}`
1201
+ });
1202
+ return;
1203
+ }
1204
+ const expected = data.category === "language" ? "languages" : data.category === "service" ? "services" : "features";
1205
+ if (filled[0] !== expected) {
1206
+ ctx.addIssue({
1207
+ code: z3.ZodIssueCode.custom,
1208
+ message: `category '${data.category}' requires contributes.${expected}, got contributes.${filled[0]}`
1209
+ });
1210
+ }
1211
+ });
1212
+ async function loadComponentCatalog(rootDir = componentsDir()) {
1213
+ if (!existsSync2(rootDir)) {
1214
+ return /* @__PURE__ */ new Map();
1215
+ }
1216
+ const out = /* @__PURE__ */ new Map();
1217
+ await walk(rootDir, rootDir, out);
1218
+ return out;
1219
+ }
1220
+ async function walk(baseDir, currentDir, out) {
1221
+ const entries = await fs4.readdir(currentDir, { withFileTypes: true });
1222
+ for (const entry2 of entries) {
1223
+ const full = path3.join(currentDir, entry2.name);
1224
+ if (entry2.isDirectory()) {
1225
+ await walk(baseDir, full, out);
1226
+ continue;
1227
+ }
1228
+ if (!entry2.isFile() || !entry2.name.endsWith(".yml")) continue;
1229
+ const relative = path3.relative(baseDir, full);
1230
+ const name = relative.replace(/\.yml$/, "").split(path3.sep).join("/");
1231
+ const text = await fs4.readFile(full, "utf8");
1232
+ let raw;
1233
+ try {
1234
+ raw = parseYaml(text);
1235
+ } catch (err) {
1236
+ throw new Error(
1237
+ `Failed to parse component ${name} (${full}): ${err.message}`
1238
+ );
1239
+ }
1240
+ const parsed = ComponentFileSchema.safeParse(raw);
1241
+ if (!parsed.success) {
1242
+ const issues = parsed.error.issues.map((issue) => {
1243
+ const where = issue.path.length > 0 ? issue.path.join(".") : "(root)";
1244
+ return ` - ${where}: ${issue.message}`;
1245
+ }).join("\n");
1246
+ throw new Error(`Invalid component ${name} (${full}):
1247
+ ${issues}`);
1248
+ }
1249
+ out.set(name, { name, sourcePath: full, file: parsed.data });
1250
+ }
1251
+ }
1252
+ function mergeComponents(resolved) {
1253
+ const languages = [];
1254
+ const services = [];
1255
+ const featureByRef = /* @__PURE__ */ new Map();
1256
+ for (const entry2 of resolved) {
1257
+ const c = isResolvedComponent(entry2) ? entry2.component : entry2;
1258
+ const version = isResolvedComponent(entry2) ? entry2.version : void 0;
1259
+ const ct = c.file.contributes;
1260
+ for (const lang of ct.languages ?? []) {
1261
+ const value = version !== void 0 ? `${lang}:${version}` : lang;
1262
+ if (!languages.includes(value)) languages.push(value);
1263
+ }
1264
+ for (const svc of ct.services ?? []) {
1265
+ if (!services.includes(svc)) services.push(svc);
1266
+ }
1267
+ for (const f of ct.features ?? []) {
1268
+ const existing = featureByRef.get(f.ref);
1269
+ if (!existing) {
1270
+ featureByRef.set(f.ref, {
1271
+ ref: f.ref,
1272
+ options: { ...f.options ?? {} }
1273
+ });
1274
+ continue;
1275
+ }
1276
+ existing.options = mergeFeatureOptions(existing.options, f.options ?? {});
1277
+ }
1278
+ }
1279
+ return {
1280
+ languages,
1281
+ services,
1282
+ features: [...featureByRef.values()]
1283
+ };
1284
+ }
1285
+ function isResolvedComponent(x) {
1286
+ return "component" in x;
1287
+ }
1288
+ function mergeFeatureOptions(a, b) {
1289
+ const result = { ...a };
1290
+ for (const [key, valueB] of Object.entries(b)) {
1291
+ const valueA = result[key];
1292
+ if (typeof valueA === "boolean" && typeof valueB === "boolean") {
1293
+ result[key] = valueA || valueB;
1294
+ continue;
1295
+ }
1296
+ result[key] = valueB;
1297
+ }
1298
+ return result;
1299
+ }
1300
+ function resolveComponents(catalog, names) {
1301
+ const unknown = [];
1302
+ const out = [];
1303
+ for (const raw of names) {
1304
+ const colon = raw.indexOf(":");
1305
+ const name = colon === -1 ? raw : raw.slice(0, colon);
1306
+ const version = colon === -1 ? void 0 : raw.slice(colon + 1);
1307
+ const c = catalog.get(name);
1308
+ if (!c) {
1309
+ unknown.push(raw);
1310
+ continue;
1311
+ }
1312
+ if (version !== void 0 && c.file.category !== "language") {
1313
+ throw new Error(
1314
+ `Component '${name}' is a ${c.file.category}, not a language \u2014 a ':${version}' suffix has no meaning here.`
1315
+ );
1316
+ }
1317
+ out.push({ component: c, ...version !== void 0 ? { version } : {} });
1318
+ }
1319
+ if (unknown.length > 0) {
1320
+ const available = [...catalog.keys()].sort();
1321
+ throw new Error(
1322
+ `Unknown component${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}.
1323
+ Available: ${available.join(", ")}.`
1324
+ );
1325
+ }
1326
+ return out;
1327
+ }
1328
+
1164
1329
  // src/proxy/index.ts
1165
1330
  import { spawn as spawn3 } from "child_process";
1166
- import { promises as fs4 } from "fs";
1167
- import path3 from "path";
1331
+ import { promises as fs5 } from "fs";
1332
+ import path4 from "path";
1168
1333
  var PROXY_CONTAINER_NAME = "monoceros-proxy";
1169
1334
  var PROXY_NETWORK_NAME = "monoceros-proxy";
1170
1335
  var TRAEFIK_IMAGE = "traefik:v3.3";
@@ -1190,12 +1355,12 @@ var defaultDockerExec = (args) => {
1190
1355
  };
1191
1356
  var realDocker = defaultDockerExec;
1192
1357
  function proxyDynamicDir(home) {
1193
- return path3.join(home ?? monocerosHome(), "traefik", "dynamic");
1358
+ return path4.join(home ?? monocerosHome(), "traefik", "dynamic");
1194
1359
  }
1195
1360
  async function ensureProxy(opts = {}) {
1196
1361
  const docker = opts.docker ?? realDocker;
1197
1362
  const dyn = proxyDynamicDir(opts.monocerosHome);
1198
- await fs4.mkdir(dyn, { recursive: true });
1363
+ await fs5.mkdir(dyn, { recursive: true });
1199
1364
  const netInspect = await docker(["network", "inspect", PROXY_NETWORK_NAME]);
1200
1365
  if (netInspect.exitCode !== 0) {
1201
1366
  const create = await docker(["network", "create", PROXY_NETWORK_NAME]);
@@ -1281,8 +1446,8 @@ async function maybeStopProxy(opts = {}) {
1281
1446
  }
1282
1447
 
1283
1448
  // src/proxy/dynamic.ts
1284
- import { promises as fs5 } from "fs";
1285
- import path4 from "path";
1449
+ import { promises as fs6 } from "fs";
1450
+ import path5 from "path";
1286
1451
  async function writeDynamicConfig(name, ports, opts = {}) {
1287
1452
  if (ports.length === 0) {
1288
1453
  throw new Error(
@@ -1290,14 +1455,14 @@ async function writeDynamicConfig(name, ports, opts = {}) {
1290
1455
  );
1291
1456
  }
1292
1457
  const dir = proxyDynamicDir(opts.monocerosHome);
1293
- await fs5.mkdir(dir, { recursive: true });
1294
- const file = path4.join(dir, `${name}.yml`);
1295
- await fs5.writeFile(file, renderDynamicConfig(name, ports), "utf8");
1458
+ await fs6.mkdir(dir, { recursive: true });
1459
+ const file = path5.join(dir, `${name}.yml`);
1460
+ await fs6.writeFile(file, renderDynamicConfig(name, ports), "utf8");
1296
1461
  return file;
1297
1462
  }
1298
1463
  async function removeDynamicConfig(name, opts = {}) {
1299
- const file = path4.join(proxyDynamicDir(opts.monocerosHome), `${name}.yml`);
1300
- await fs5.rm(file, { force: true });
1464
+ const file = path5.join(proxyDynamicDir(opts.monocerosHome), `${name}.yml`);
1465
+ await fs6.rm(file, { force: true });
1301
1466
  }
1302
1467
  function renderDynamicConfig(name, ports) {
1303
1468
  const lines = [];
@@ -1498,8 +1663,8 @@ function knownServices() {
1498
1663
  }
1499
1664
 
1500
1665
  // src/create/scaffold.ts
1501
- import { existsSync as existsSync2, readFileSync, promises as fs6 } from "fs";
1502
- import path5 from "path";
1666
+ import { existsSync as existsSync3, readFileSync, promises as fs7 } from "fs";
1667
+ import path6 from "path";
1503
1668
 
1504
1669
  // src/util/ref.ts
1505
1670
  var FEATURE_NAME_CHARSET = "[a-z0-9._-]+";
@@ -1670,8 +1835,8 @@ function resolveFeatures(opts) {
1670
1835
  if (match) {
1671
1836
  const name = match.name;
1672
1837
  const checkout = workbenchCheckoutRoot();
1673
- const localSourceDir = checkout ? path5.join(checkout, "images", "features", name) : null;
1674
- if (localSourceDir && existsSync2(localSourceDir)) {
1838
+ const localSourceDir = checkout ? path6.join(checkout, "images", "features", name) : null;
1839
+ if (localSourceDir && existsSync3(localSourceDir)) {
1675
1840
  const { paths, files } = readPersistentHomeEntries(localSourceDir);
1676
1841
  resolved.push({
1677
1842
  devcontainerKey: `./features/${name}`,
@@ -1695,7 +1860,7 @@ function resolveFeatures(opts) {
1695
1860
  return resolved;
1696
1861
  }
1697
1862
  function readPersistentHomeEntries(localSourceDir) {
1698
- const manifestPath = path5.join(localSourceDir, "devcontainer-feature.json");
1863
+ const manifestPath = path6.join(localSourceDir, "devcontainer-feature.json");
1699
1864
  try {
1700
1865
  const text = readFileSync(manifestPath, "utf8");
1701
1866
  const parsed = JSON.parse(text);
@@ -1980,86 +2145,86 @@ function buildPostCreateScript(opts) {
1980
2145
  return lines.join("\n") + "\n";
1981
2146
  }
1982
2147
  async function writePostCreateScript(devcontainerDir, opts) {
1983
- const dest = path5.join(devcontainerDir, "post-create.sh");
1984
- await fs6.writeFile(dest, buildPostCreateScript(opts));
1985
- await fs6.chmod(dest, 493);
2148
+ const dest = path6.join(devcontainerDir, "post-create.sh");
2149
+ await fs7.writeFile(dest, buildPostCreateScript(opts));
2150
+ await fs7.chmod(dest, 493);
1986
2151
  }
1987
2152
  async function writeScaffold(opts, targetDir, scaffoldOpts = {}) {
1988
2153
  const dockerMode = scaffoldOpts.dockerMode ?? "rootful";
1989
- const devcontainerDir = path5.join(targetDir, ".devcontainer");
1990
- const monocerosDir = path5.join(targetDir, ".monoceros");
1991
- const projectsDir = path5.join(targetDir, "projects");
1992
- const homeDir = path5.join(targetDir, "home");
1993
- const dataDir = path5.join(targetDir, "data");
1994
- await fs6.mkdir(devcontainerDir, { recursive: true });
1995
- await fs6.mkdir(monocerosDir, { recursive: true });
1996
- await fs6.mkdir(projectsDir, { recursive: true });
1997
- await fs6.mkdir(homeDir, { recursive: true });
2154
+ const devcontainerDir = path6.join(targetDir, ".devcontainer");
2155
+ const monocerosDir = path6.join(targetDir, ".monoceros");
2156
+ const projectsDir = path6.join(targetDir, "projects");
2157
+ const homeDir = path6.join(targetDir, "home");
2158
+ const dataDir = path6.join(targetDir, "data");
2159
+ await fs7.mkdir(devcontainerDir, { recursive: true });
2160
+ await fs7.mkdir(monocerosDir, { recursive: true });
2161
+ await fs7.mkdir(projectsDir, { recursive: true });
2162
+ await fs7.mkdir(homeDir, { recursive: true });
1998
2163
  if (needsCompose(opts)) {
1999
- await fs6.mkdir(dataDir, { recursive: true });
2164
+ await fs7.mkdir(dataDir, { recursive: true });
2000
2165
  for (const svcId of opts.services) {
2001
2166
  const def = SERVICE_CATALOG[svcId];
2002
2167
  if (def?.dataMount) {
2003
- await fs6.mkdir(path5.join(dataDir, def.id), { recursive: true });
2168
+ await fs7.mkdir(path6.join(dataDir, def.id), { recursive: true });
2004
2169
  }
2005
2170
  }
2006
2171
  }
2007
- const containerGitignore = path5.join(targetDir, ".gitignore");
2008
- await fs6.writeFile(containerGitignore, "/home/\n/.monoceros/\n/data/\n");
2009
- const gitkeep = path5.join(projectsDir, ".gitkeep");
2010
- if (!existsSync2(gitkeep)) {
2011
- await fs6.writeFile(gitkeep, "");
2172
+ const containerGitignore = path6.join(targetDir, ".gitignore");
2173
+ await fs7.writeFile(containerGitignore, "/home/\n/.monoceros/\n/data/\n");
2174
+ const gitkeep = path6.join(projectsDir, ".gitkeep");
2175
+ if (!existsSync3(gitkeep)) {
2176
+ await fs7.writeFile(gitkeep, "");
2012
2177
  }
2013
- await fs6.writeFile(
2014
- path5.join(monocerosDir, ".gitignore"),
2178
+ await fs7.writeFile(
2179
+ path6.join(monocerosDir, ".gitignore"),
2015
2180
  "git-credentials*\ngitconfig\n"
2016
2181
  );
2017
2182
  const devcontainerJson = buildDevcontainerJson(opts, dockerMode);
2018
- await fs6.writeFile(
2019
- path5.join(devcontainerDir, "devcontainer.json"),
2183
+ await fs7.writeFile(
2184
+ path6.join(devcontainerDir, "devcontainer.json"),
2020
2185
  JSON.stringify(devcontainerJson, null, 2) + "\n"
2021
2186
  );
2022
- const featuresDir = path5.join(devcontainerDir, "features");
2023
- if (existsSync2(featuresDir)) {
2024
- await fs6.rm(featuresDir, { recursive: true, force: true });
2187
+ const featuresDir = path6.join(devcontainerDir, "features");
2188
+ if (existsSync3(featuresDir)) {
2189
+ await fs7.rm(featuresDir, { recursive: true, force: true });
2025
2190
  }
2026
2191
  const resolvedFeatures = resolveFeatures(opts);
2027
2192
  for (const f of resolvedFeatures) {
2028
2193
  if (!f.localSourceDir || !f.localName) continue;
2029
- const dest = path5.join(featuresDir, f.localName);
2030
- await fs6.mkdir(dest, { recursive: true });
2031
- await fs6.cp(f.localSourceDir, dest, { recursive: true });
2194
+ const dest = path6.join(featuresDir, f.localName);
2195
+ await fs7.mkdir(dest, { recursive: true });
2196
+ await fs7.cp(f.localSourceDir, dest, { recursive: true });
2032
2197
  }
2033
2198
  for (const f of resolvedFeatures) {
2034
2199
  for (const sub of f.persistentHomePaths) {
2035
- await fs6.mkdir(path5.join(homeDir, sub), { recursive: true });
2200
+ await fs7.mkdir(path6.join(homeDir, sub), { recursive: true });
2036
2201
  }
2037
2202
  for (const entry2 of f.persistentHomeFiles) {
2038
- const filePath = path5.join(homeDir, entry2.path);
2039
- await fs6.mkdir(path5.dirname(filePath), { recursive: true });
2040
- if (!existsSync2(filePath)) {
2041
- await fs6.writeFile(filePath, entry2.initialContent);
2203
+ const filePath = path6.join(homeDir, entry2.path);
2204
+ await fs7.mkdir(path6.dirname(filePath), { recursive: true });
2205
+ if (!existsSync3(filePath)) {
2206
+ await fs7.writeFile(filePath, entry2.initialContent);
2042
2207
  }
2043
2208
  }
2044
2209
  }
2045
2210
  await writePostCreateScript(devcontainerDir, opts);
2046
- const composePath = path5.join(devcontainerDir, "compose.yaml");
2211
+ const composePath = path6.join(devcontainerDir, "compose.yaml");
2047
2212
  if (needsCompose(opts)) {
2048
- await fs6.writeFile(composePath, buildComposeYaml(opts, dockerMode));
2049
- } else if (existsSync2(composePath)) {
2050
- await fs6.rm(composePath);
2213
+ await fs7.writeFile(composePath, buildComposeYaml(opts, dockerMode));
2214
+ } else if (existsSync3(composePath)) {
2215
+ await fs7.rm(composePath);
2051
2216
  }
2052
- const workspacePath = path5.join(targetDir, `${opts.name}.code-workspace`);
2217
+ const workspacePath = path6.join(targetDir, `${opts.name}.code-workspace`);
2053
2218
  let existingWorkspace;
2054
2219
  try {
2055
- const raw = await fs6.readFile(workspacePath, "utf8");
2220
+ const raw = await fs7.readFile(workspacePath, "utf8");
2056
2221
  existingWorkspace = JSON.parse(raw);
2057
2222
  } catch {
2058
2223
  existingWorkspace = void 0;
2059
2224
  }
2060
2225
  const generated = buildCodeWorkspaceJson(opts);
2061
2226
  const merged = mergeCodeWorkspace(existingWorkspace, generated);
2062
- await fs6.writeFile(workspacePath, JSON.stringify(merged, null, 2) + "\n");
2227
+ await fs7.writeFile(workspacePath, JSON.stringify(merged, null, 2) + "\n");
2063
2228
  }
2064
2229
 
2065
2230
  // src/modify/yml.ts
@@ -2417,8 +2582,8 @@ function removeRepoFromDoc(doc, urlOrPath) {
2417
2582
  if (!isMap2(item)) return false;
2418
2583
  const url = item.get("url");
2419
2584
  if (url === urlOrPath) return true;
2420
- const path16 = item.get("path");
2421
- const effectivePath = typeof path16 === "string" ? path16 : typeof url === "string" ? deriveRepoName(url) : void 0;
2585
+ const path17 = item.get("path");
2586
+ const effectivePath = typeof path17 === "string" ? path17 : typeof url === "string" ? deriveRepoName(url) : void 0;
2422
2587
  return effectivePath === urlOrPath;
2423
2588
  });
2424
2589
  if (idx < 0) return false;
@@ -2468,7 +2633,7 @@ async function runAddRepo(input) {
2468
2633
  "Missing repo URL. Usage: monoceros add-repo <containername> <url>."
2469
2634
  );
2470
2635
  }
2471
- const path16 = (input.path ?? deriveRepoName(url)).trim();
2636
+ const path17 = (input.path ?? deriveRepoName(url)).trim();
2472
2637
  const hasName = typeof input.gitName === "string" && input.gitName.trim().length > 0;
2473
2638
  const hasEmail = typeof input.gitEmail === "string" && input.gitEmail.trim().length > 0;
2474
2639
  if (hasName !== hasEmail) {
@@ -2497,7 +2662,7 @@ async function runAddRepo(input) {
2497
2662
  const providerToWrite = !canonical && explicitProvider ? explicitProvider : void 0;
2498
2663
  const entry2 = {
2499
2664
  url,
2500
- path: path16,
2665
+ path: path17,
2501
2666
  ...hasName && hasEmail ? {
2502
2667
  gitUser: {
2503
2668
  name: input.gitName.trim(),
@@ -2573,14 +2738,14 @@ async function tryCloneInRunningContainer(input, entry2) {
2573
2738
  );
2574
2739
  return;
2575
2740
  }
2576
- const containerName = input.name;
2741
+ const containerName2 = input.name;
2577
2742
  const targetRel = `projects/${entry2.path}`;
2578
2743
  const parentRel = entry2.path.includes("/") ? `projects/${entry2.path.split("/").slice(0, -1).join("/")}` : "projects";
2579
- const credentialsFile = `/workspaces/${containerName}/.monoceros/git-credentials`;
2744
+ const credentialsFile = `/workspaces/${containerName2}/.monoceros/git-credentials`;
2580
2745
  const credentialHelper = `store --file=${credentialsFile}`;
2581
2746
  const script = [
2582
2747
  `set -eu`,
2583
- `cd /workspaces/${containerName}`,
2748
+ `cd /workspaces/${containerName2}`,
2584
2749
  `if [ -d ${shquote(targetRel)} ]; then`,
2585
2750
  ` echo "[add-repo] ${targetRel} already exists \u2014 skipping clone."`,
2586
2751
  ` exit 0`,
@@ -2611,9 +2776,9 @@ async function tryCloneInRunningContainer(input, entry2) {
2611
2776
  return;
2612
2777
  }
2613
2778
  logger.info(
2614
- `Cloned ${entry2.url} into /workspaces/${containerName}/${targetRel} inside the running container.`
2779
+ `Cloned ${entry2.url} into /workspaces/${containerName2}/${targetRel} inside the running container.`
2615
2780
  );
2616
- void path6;
2781
+ void path7;
2617
2782
  }
2618
2783
  function shquote(value) {
2619
2784
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -2678,14 +2843,56 @@ function normalizePorts(raw) {
2678
2843
  }
2679
2844
  return result;
2680
2845
  }
2681
- function runAddFeature(input) {
2682
- const ref = input.ref.trim();
2683
- if (ref.length === 0) {
2846
+ async function runAddFeature(input) {
2847
+ const raw = input.ref.trim();
2848
+ if (raw.length === 0) {
2849
+ throw new Error(
2850
+ "Missing feature ref. Usage: monoceros add-feature <containername> <feature>."
2851
+ );
2852
+ }
2853
+ const resolved = await resolveFeatureRefOrShortname(raw);
2854
+ const merged = {
2855
+ ...resolved.defaultOptions,
2856
+ ...input.options ?? {}
2857
+ };
2858
+ return mutate(input, (doc) => addFeatureToDoc(doc, resolved.ref, merged));
2859
+ }
2860
+ async function resolveFeatureRefOrShortname(input) {
2861
+ if (REGEX.featureRef.test(input)) {
2862
+ return { ref: input, defaultOptions: {} };
2863
+ }
2864
+ const catalog = await loadComponentCatalog();
2865
+ const component = catalog.get(input);
2866
+ if (!component) {
2867
+ const featureShorts = [...catalog.values()].filter((c) => c.file.category === "feature").map((c) => c.name).sort();
2868
+ const knownList = featureShorts.length > 0 ? featureShorts.join(", ") : "(none)";
2869
+ throw new Error(
2870
+ `Unknown feature: ${JSON.stringify(input)}. Pass either a catalog short-name (one of: ${knownList}) or a full OCI ref like 'ghcr.io/getmonoceros/monoceros-features/<name>:<tag>'.`
2871
+ );
2872
+ }
2873
+ if (component.file.category !== "feature") {
2874
+ throw new Error(
2875
+ `'${input}' is a ${component.file.category}, not a feature. Use 'monoceros add-${component.file.category} <name> ${input}' instead.`
2876
+ );
2877
+ }
2878
+ const features = component.file.contributes.features ?? [];
2879
+ if (features.length === 0) {
2684
2880
  throw new Error(
2685
- "Missing feature ref. Usage: monoceros add-feature <containername> <ref>."
2881
+ `Catalog entry '${input}' contributes no feature ref \u2014 bug or stale catalog.`
2686
2882
  );
2687
2883
  }
2688
- return mutate(input, (doc) => addFeatureToDoc(doc, ref, input.options ?? {}));
2884
+ if (features.length > 1) {
2885
+ throw new Error(
2886
+ `'${input}' bundles multiple feature refs (${features.map((f) => f.ref).join(
2887
+ ", "
2888
+ )}). add-feature handles one at a time \u2014 pass the OCI ref directly.`
2889
+ );
2890
+ }
2891
+ const [first] = features;
2892
+ return {
2893
+ ref: first.ref,
2894
+ defaultOptions: { ...first.options ?? {} }
2895
+ };
2689
2896
  }
2690
2897
  function runRemoveLanguage(input) {
2691
2898
  return mutate(input, (doc) => removeLanguageFromDoc(doc, input.language));
@@ -2752,7 +2959,7 @@ async function mutate(opts, apply) {
2752
2959
  const logger = opts.logger ?? defaultLogger();
2753
2960
  let oldText;
2754
2961
  try {
2755
- oldText = await fs7.readFile(ymlPath, "utf8");
2962
+ oldText = await fs8.readFile(ymlPath, "utf8");
2756
2963
  } catch {
2757
2964
  throw new Error(
2758
2965
  `No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
@@ -2776,7 +2983,7 @@ async function mutate(opts, apply) {
2776
2983
  return { status: "aborted" };
2777
2984
  }
2778
2985
  }
2779
- await fs7.writeFile(ymlPath, newText, "utf8");
2986
+ await fs8.writeFile(ymlPath, newText, "utf8");
2780
2987
  logger.success(`Updated ${ymlPath}.`);
2781
2988
  logger.info(
2782
2989
  `Run \`monoceros apply ${opts.name}\` to rebuild the dev-container and pick up the change.`
@@ -2912,7 +3119,7 @@ var addFeatureCommand = defineCommand2({
2912
3119
  },
2913
3120
  ref: {
2914
3121
  type: "positional",
2915
- description: "Devcontainer feature ref (OCI image style, e.g. `ghcr.io/devcontainers/features/docker-in-docker:2`).",
3122
+ description: "Feature to add. Either a Monoceros catalog short-name (e.g. `atlassian`, `atlassian/twg`, `claude` \u2014 see `monoceros list-components`) or a full OCI feature ref (e.g. `ghcr.io/devcontainers/features/docker-in-docker:2`). The short-name brings its catalog-defined default options; `-- key=value` overrides them.",
2916
3123
  required: true
2917
3124
  },
2918
3125
  yes: {
@@ -3245,12 +3452,12 @@ var addServiceCommand = defineCommand7({
3245
3452
  import { defineCommand as defineCommand8 } from "citty";
3246
3453
 
3247
3454
  // src/apply/index.ts
3248
- import { existsSync as existsSync4, promises as fs10 } from "fs";
3455
+ import { existsSync as existsSync5, promises as fs11 } from "fs";
3249
3456
  import { consola as consola11 } from "consola";
3250
3457
 
3251
3458
  // src/config/state.ts
3252
- import { promises as fs8 } from "fs";
3253
- import path7 from "path";
3459
+ import { promises as fs9 } from "fs";
3460
+ import path8 from "path";
3254
3461
  function buildStateFile(opts) {
3255
3462
  return {
3256
3463
  schemaVersion: CONFIG_SCHEMA_VERSION,
@@ -3260,20 +3467,20 @@ function buildStateFile(opts) {
3260
3467
  };
3261
3468
  }
3262
3469
  function stateFilePath(targetDir) {
3263
- return path7.join(targetDir, ".monoceros", "state.json");
3470
+ return path8.join(targetDir, ".monoceros", "state.json");
3264
3471
  }
3265
3472
  async function readStateFile(targetDir) {
3266
3473
  try {
3267
- const content = await fs8.readFile(stateFilePath(targetDir), "utf8");
3474
+ const content = await fs9.readFile(stateFilePath(targetDir), "utf8");
3268
3475
  return JSON.parse(content);
3269
3476
  } catch {
3270
3477
  return void 0;
3271
3478
  }
3272
3479
  }
3273
3480
  async function writeStateFile(targetDir, state) {
3274
- const monocerosDir = path7.join(targetDir, ".monoceros");
3275
- await fs8.mkdir(monocerosDir, { recursive: true });
3276
- await fs8.writeFile(
3481
+ const monocerosDir = path8.join(targetDir, ".monoceros");
3482
+ await fs9.mkdir(monocerosDir, { recursive: true });
3483
+ await fs9.writeFile(
3277
3484
  stateFilePath(targetDir),
3278
3485
  JSON.stringify(state, null, 2) + "\n"
3279
3486
  );
@@ -3343,8 +3550,8 @@ function solutionConfigToCreateOptions(config, featureDefaults = {}) {
3343
3550
 
3344
3551
  // src/devcontainer/compose.ts
3345
3552
  import { spawn as spawn5 } from "child_process";
3346
- import { existsSync as existsSync3 } from "fs";
3347
- import path9 from "path";
3553
+ import { existsSync as existsSync4 } from "fs";
3554
+ import path10 from "path";
3348
3555
  import { consola as consola9 } from "consola";
3349
3556
 
3350
3557
  // src/util/mask-secrets.ts
@@ -3407,7 +3614,7 @@ function createSecretMaskStream() {
3407
3614
  import { spawn as spawn4 } from "child_process";
3408
3615
  import { readFileSync as readFileSync2 } from "fs";
3409
3616
  import { createRequire } from "module";
3410
- import path8 from "path";
3617
+ import path9 from "path";
3411
3618
  var require_ = createRequire(import.meta.url);
3412
3619
  var cachedBinaryPath = null;
3413
3620
  function devcontainerCliPath() {
@@ -3418,7 +3625,7 @@ function devcontainerCliPath() {
3418
3625
  if (!binEntry) {
3419
3626
  throw new Error("Could not resolve @devcontainers/cli bin entry.");
3420
3627
  }
3421
- cachedBinaryPath = path8.resolve(path8.dirname(pkgJsonPath), binEntry);
3628
+ cachedBinaryPath = path9.resolve(path9.dirname(pkgJsonPath), binEntry);
3422
3629
  return cachedBinaryPath;
3423
3630
  }
3424
3631
  var spawnDevcontainer = (args, cwd, options = {}) => {
@@ -3490,16 +3697,16 @@ var spawnBash = (args, cwd) => {
3490
3697
  });
3491
3698
  };
3492
3699
  function composeProjectName(root) {
3493
- return `${path9.basename(root)}_devcontainer`;
3700
+ return `${path10.basename(root)}_devcontainer`;
3494
3701
  }
3495
3702
  function resolveCompose(root) {
3496
- if (!existsSync3(path9.join(root, ".devcontainer"))) {
3703
+ if (!existsSync4(path10.join(root, ".devcontainer"))) {
3497
3704
  throw new Error(
3498
3705
  `No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
3499
3706
  );
3500
3707
  }
3501
- const composeFile = path9.join(root, ".devcontainer", "compose.yaml");
3502
- if (!existsSync3(composeFile)) {
3708
+ const composeFile = path10.join(root, ".devcontainer", "compose.yaml");
3709
+ if (!existsSync4(composeFile)) {
3503
3710
  throw new Error(
3504
3711
  `No compose.yaml at ${composeFile}. \`start\` / \`stop\` / \`status\` / \`logs\` require services configured via \`monoceros add-service <name> <svc>\`. Use \`monoceros shell <name>\` to enter the container directly.`
3505
3712
  );
@@ -3793,8 +4000,8 @@ function formatRootlessNotSupportedError() {
3793
4000
 
3794
4001
  // src/devcontainer/identity.ts
3795
4002
  import { spawn as spawn8 } from "child_process";
3796
- import { promises as fs9 } from "fs";
3797
- import path10 from "path";
4003
+ import { promises as fs10 } from "fs";
4004
+ import path11 from "path";
3798
4005
  import { consola as consola10 } from "consola";
3799
4006
  var realGitConfigGet = (key) => {
3800
4007
  return new Promise((resolve, reject) => {
@@ -3908,8 +4115,8 @@ async function resolveIdentityWithPrompt(options = {}) {
3908
4115
  };
3909
4116
  }
3910
4117
  async function collectGitIdentity(devContainerRoot, options = {}) {
3911
- const gitconfigDir = path10.join(devContainerRoot, ".monoceros");
3912
- const gitconfigPath = path10.join(gitconfigDir, "gitconfig");
4118
+ const gitconfigDir = path11.join(devContainerRoot, ".monoceros");
4119
+ const gitconfigPath = path11.join(gitconfigDir, "gitconfig");
3913
4120
  const logger = options.logger ?? { info: () => {
3914
4121
  }, warn: () => {
3915
4122
  } };
@@ -3922,8 +4129,8 @@ async function collectGitIdentity(devContainerRoot, options = {}) {
3922
4129
  const lines = ["[user]"];
3923
4130
  if (resolved.name !== void 0) lines.push(` name = ${resolved.name}`);
3924
4131
  if (resolved.email !== void 0) lines.push(` email = ${resolved.email}`);
3925
- await fs9.mkdir(gitconfigDir, { recursive: true });
3926
- await fs9.writeFile(gitconfigPath, lines.join("\n") + "\n");
4132
+ await fs10.mkdir(gitconfigDir, { recursive: true });
4133
+ await fs10.writeFile(gitconfigPath, lines.join("\n") + "\n");
3927
4134
  return {
3928
4135
  ...resolved.name !== void 0 ? { name: resolved.name } : {},
3929
4136
  ...resolved.email !== void 0 ? { email: resolved.email } : {},
@@ -3966,7 +4173,7 @@ async function readKeyFromHost(spawnFn, key, logger) {
3966
4173
  }
3967
4174
  async function readExistingGitconfig(filePath) {
3968
4175
  try {
3969
- const content = await fs9.readFile(filePath, "utf8");
4176
+ const content = await fs10.readFile(filePath, "utf8");
3970
4177
  const result = {};
3971
4178
  const nameMatch = /^\s*name\s*=\s*(.+?)\s*$/m.exec(content);
3972
4179
  const emailMatch = /^\s*email\s*=\s*(.+?)\s*$/m.exec(content);
@@ -3999,7 +4206,7 @@ ${sectionLine(label)}
3999
4206
  );
4000
4207
  }
4001
4208
  const ymlPath = containerConfigPath(opts.name, home);
4002
- if (!existsSync4(ymlPath)) {
4209
+ if (!existsSync5(ymlPath)) {
4003
4210
  throw new Error(
4004
4211
  `No such config: ${ymlPath}. Run \`monoceros init <template> ${opts.name}\` first.`
4005
4212
  );
@@ -4070,7 +4277,7 @@ ${sectionLine(label)}
4070
4277
  if (dockerMode === "rootless") {
4071
4278
  throw new Error(formatRootlessNotSupportedError());
4072
4279
  }
4073
- await fs10.mkdir(targetDir, { recursive: true });
4280
+ await fs11.mkdir(targetDir, { recursive: true });
4074
4281
  await writeScaffold(createOpts, targetDir, { dockerMode });
4075
4282
  await writeStateFile(
4076
4283
  targetDir,
@@ -4128,8 +4335,8 @@ ${sectionLine(label)}
4128
4335
  return { targetDir, configPath: ymlPath, containerExitCode: exitCode };
4129
4336
  }
4130
4337
  async function assertSafeTargetDir(targetDir, expectedOrigin) {
4131
- if (!existsSync4(targetDir)) return;
4132
- const entries = await fs10.readdir(targetDir);
4338
+ if (!existsSync5(targetDir)) return;
4339
+ const entries = await fs11.readdir(targetDir);
4133
4340
  if (entries.length === 0) return;
4134
4341
  const state = await readStateFile(targetDir);
4135
4342
  if (state) {
@@ -4199,7 +4406,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
4199
4406
  }
4200
4407
  if (wantContainer) {
4201
4408
  try {
4202
- const text = await fs10.readFile(ymlPath, "utf8");
4409
+ const text = await fs11.readFile(ymlPath, "utf8");
4203
4410
  const parsed = parseConfig(text, ymlPath);
4204
4411
  const changed = setContainerGitUserInDoc(parsed.doc, {
4205
4412
  name: prompted.name,
@@ -4207,7 +4414,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
4207
4414
  });
4208
4415
  if (changed) {
4209
4416
  const out = stringifyConfig(parsed.doc);
4210
- await fs10.writeFile(ymlPath, out, "utf8");
4417
+ await fs11.writeFile(ymlPath, out, "utf8");
4211
4418
  logger.info(
4212
4419
  `Saved identity in this container \u2014 wrote git.user into ${prettyPath(ymlPath)}.`
4213
4420
  );
@@ -4221,7 +4428,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
4221
4428
  }
4222
4429
 
4223
4430
  // src/version.ts
4224
- var CLI_VERSION = true ? "1.8.0" : "dev";
4431
+ var CLI_VERSION = true ? "1.9.0" : "dev";
4225
4432
 
4226
4433
  // src/commands/_dispatch.ts
4227
4434
  import { consola as consola12 } from "consola";
@@ -4262,96 +4469,24 @@ var applyCommand = defineCommand8({
4262
4469
 
4263
4470
  // src/commands/completion.ts
4264
4471
  import { defineCommand as defineCommand9 } from "citty";
4265
- var ALL_COMMANDS = [
4266
- "init",
4267
- "list-components",
4268
- "shell",
4269
- "run",
4270
- "logs",
4271
- "start",
4272
- "stop",
4273
- "status",
4274
- "apply",
4275
- "remove",
4276
- "restore",
4277
- "add-service",
4278
- "add-language",
4279
- "add-apt-packages",
4280
- "add-feature",
4281
- "add-from-url",
4282
- "add-repo",
4283
- "add-port",
4284
- "remove-service",
4285
- "remove-language",
4286
- "remove-apt-packages",
4287
- "remove-feature",
4288
- "remove-from-url",
4289
- "remove-repo",
4290
- "remove-port",
4291
- "port",
4292
- "completion"
4293
- ];
4294
- var COMMANDS_WITH_CONTAINER_ARG = [
4295
- "shell",
4296
- "run",
4297
- "logs",
4298
- "start",
4299
- "stop",
4300
- "status",
4301
- "apply",
4302
- "remove",
4303
- "add-service",
4304
- "add-language",
4305
- "add-apt-packages",
4306
- "add-feature",
4307
- "add-from-url",
4308
- "add-repo",
4309
- "add-port",
4310
- "remove-service",
4311
- "remove-language",
4312
- "remove-apt-packages",
4313
- "remove-feature",
4314
- "remove-from-url",
4315
- "remove-repo",
4316
- "remove-port",
4317
- "port"
4318
- ];
4319
4472
  var SHELLS = ["bash", "zsh", "pwsh"];
4320
4473
  function renderCompletionScript(shell) {
4321
- const commands = ALL_COMMANDS.join(" ");
4322
- const containerCommandsRegex = COMMANDS_WITH_CONTAINER_ARG.join("|");
4323
4474
  if (shell === "bash") {
4324
4475
  return [
4325
4476
  "# bash completion for monoceros",
4326
4477
  "# install: source this file from .bashrc, e.g.",
4327
4478
  "# monoceros completion bash > ~/.bash_completion.d/monoceros",
4328
4479
  '# echo "source ~/.bash_completion.d/monoceros" >> ~/.bashrc',
4480
+ "#",
4481
+ "# The work is done by `monoceros __complete --line --point`; this",
4482
+ "# shell wrapper only forwards the cursor view.",
4329
4483
  "",
4330
4484
  "_monoceros() {",
4331
- " local cur prev cmd home configs_dir names",
4332
- ' cur="${COMP_WORDS[COMP_CWORD]}"',
4333
- "",
4334
- " if [[ $COMP_CWORD -eq 1 ]]; then",
4335
- ` COMPREPLY=( $(compgen -W "${commands}" -- "$cur") )`,
4336
- " return",
4337
- " fi",
4338
- "",
4339
- ' cmd="${COMP_WORDS[1]}"',
4340
- " if [[ $COMP_CWORD -eq 2 ]]; then",
4341
- ' case "$cmd" in',
4342
- ` ${containerCommandsRegex})`,
4343
- ' home="${MONOCEROS_HOME:-$HOME/.monoceros}"',
4344
- ' configs_dir="$home/container-configs"',
4345
- ' if [[ -d "$configs_dir" ]]; then',
4346
- ` names=$(cd "$configs_dir" && ls *.yml 2>/dev/null | sed 's/\\.yml$//')`,
4347
- ' COMPREPLY=( $(compgen -W "$names" -- "$cur") )',
4348
- " fi",
4349
- " ;;",
4350
- " completion)",
4351
- ` COMPREPLY=( $(compgen -W "${SHELLS.join(" ")}" -- "$cur") )`,
4352
- " ;;",
4353
- " esac",
4354
- " fi",
4485
+ " local IFS=$'\\n'",
4486
+ " local candidates",
4487
+ ' candidates=$(monoceros __complete --line "$COMP_LINE" --point "$COMP_POINT" 2>/dev/null)',
4488
+ ' local cur="${COMP_WORDS[COMP_CWORD]}"',
4489
+ ' COMPREPLY=( $(compgen -W "$candidates" -- "$cur") )',
4355
4490
  "}",
4356
4491
  "complete -F _monoceros monoceros",
4357
4492
  ""
@@ -4363,44 +4498,20 @@ function renderCompletionScript(shell) {
4363
4498
  "# install: dot-source this file from your $PROFILE, e.g.",
4364
4499
  "# monoceros completion pwsh > $HOME/.config/monoceros/completion.ps1",
4365
4500
  "# Add-Content $PROFILE '. $HOME/.config/monoceros/completion.ps1'",
4501
+ "#",
4502
+ "# The work is done by `monoceros __complete --line --point`; this",
4503
+ "# shell wrapper only forwards the cursor view.",
4366
4504
  "",
4367
4505
  "Register-ArgumentCompleter -Native -CommandName monoceros -ScriptBlock {",
4368
4506
  " param($wordToComplete, $commandAst, $cursorPosition)",
4369
- "",
4370
- " $commands = @(",
4371
- ...ALL_COMMANDS.map((c) => ` '${c}'`),
4372
- " )",
4373
- ` $shells = @('${SHELLS.join("', '")}')`,
4374
- " $containerCommands = @(",
4375
- ...COMMANDS_WITH_CONTAINER_ARG.map((c) => ` '${c}'`),
4376
- " )",
4377
- "",
4378
- " $tokens = $commandAst.CommandElements",
4379
- " $position = $tokens.Count",
4380
- " if ($wordToComplete) { $position-- }",
4381
- "",
4382
- " if ($position -eq 1) {",
4383
- ' $commands | Where-Object { $_ -like "$wordToComplete*" } |',
4384
- ' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_) }',
4385
- " return",
4386
- " }",
4387
- "",
4388
- " if ($position -eq 2) {",
4389
- " $cmd = $tokens[1].Value",
4390
- " if ($containerCommands -contains $cmd) {",
4391
- ' $home = if ($env:MONOCEROS_HOME) { $env:MONOCEROS_HOME } else { Join-Path $env:USERPROFILE ".monoceros" }',
4392
- ' $configsDir = Join-Path $home "container-configs"',
4393
- " if (Test-Path $configsDir) {",
4394
- ' Get-ChildItem -Path $configsDir -Filter "*.yml" |',
4395
- " ForEach-Object { $_.BaseName } |",
4396
- ' Where-Object { $_ -like "$wordToComplete*" } |',
4397
- ' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_) }',
4398
- " }",
4399
- ' } elseif ($cmd -eq "completion") {',
4400
- ' $shells | Where-Object { $_ -like "$wordToComplete*" } |',
4401
- ' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_) }',
4402
- " }",
4403
- " }",
4507
+ " $line = $commandAst.Extent.Text",
4508
+ " $point = $cursorPosition - $commandAst.Extent.StartOffset",
4509
+ " if ($point -lt 0) { $point = 0 }",
4510
+ " $raw = & monoceros __complete --line $line --point $point 2>$null",
4511
+ " if (-not $raw) { return }",
4512
+ ' $raw -split "`n" |',
4513
+ ' Where-Object { $_.Length -gt 0 -and $_ -like "$wordToComplete*" } |',
4514
+ ' ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, "ParameterValue", $_) }',
4404
4515
  "}",
4405
4516
  ""
4406
4517
  ].join("\n");
@@ -4411,36 +4522,17 @@ function renderCompletionScript(shell) {
4411
4522
  "# install: drop this file somewhere on your $fpath as `_monoceros`,",
4412
4523
  "# then start a new shell (or run `compinit`). Example:",
4413
4524
  '# monoceros completion zsh > "${fpath[1]}/_monoceros"',
4525
+ "#",
4526
+ "# The work is done by `monoceros __complete --line --point`; this",
4527
+ "# shell wrapper only forwards the cursor view.",
4414
4528
  "",
4415
4529
  "_monoceros() {",
4416
- " local -a commands shells",
4417
- " commands=(",
4418
- ...ALL_COMMANDS.map((c) => ` '${c}'`),
4419
- " )",
4420
- ` shells=(${SHELLS.map((s) => `'${s}'`).join(" ")})`,
4421
- "",
4422
- " if (( CURRENT == 2 )); then",
4423
- " _describe 'monoceros command' commands",
4424
- " return",
4425
- " fi",
4426
- "",
4427
- " local cmd=${words[2]}",
4428
- " if (( CURRENT == 3 )); then",
4429
- " case $cmd in",
4430
- ` ${containerCommandsRegex})`,
4431
- ' local home="${MONOCEROS_HOME:-$HOME/.monoceros}"',
4432
- ' local configs_dir="$home/container-configs"',
4433
- " if [[ -d $configs_dir ]]; then",
4434
- " local -a names",
4435
- " names=(${configs_dir}/*.yml(N:t:r))",
4436
- " _describe 'container' names",
4437
- " fi",
4438
- " ;;",
4439
- " completion)",
4440
- " _describe 'shell' shells",
4441
- " ;;",
4442
- " esac",
4443
- " fi",
4530
+ ' local line="$BUFFER"',
4531
+ ' local point="$CURSOR"',
4532
+ " local -a candidates",
4533
+ ' candidates=("${(@f)$(monoceros __complete --line "$line" --point "$point" 2>/dev/null)}")',
4534
+ ' candidates=("${(@)candidates:#}")',
4535
+ " _describe 'completion' candidates",
4444
4536
  "}",
4445
4537
  "",
4446
4538
  '_monoceros "$@"',
@@ -4473,178 +4565,474 @@ var completionCommand = defineCommand9({
4473
4565
  }
4474
4566
  });
4475
4567
 
4476
- // src/commands/init.ts
4568
+ // src/commands/__complete.ts
4477
4569
  import { defineCommand as defineCommand10 } from "citty";
4478
- import { consola as consola14 } from "consola";
4479
4570
 
4480
- // src/init/index.ts
4571
+ // src/completion/resolve.ts
4481
4572
  import { existsSync as existsSync7, promises as fs12 } from "fs";
4482
- import { consola as consola13 } from "consola";
4573
+ import path13 from "path";
4483
4574
 
4484
- // src/init/components.ts
4485
- import { existsSync as existsSync5, promises as fs11 } from "fs";
4486
- import path11 from "path";
4487
- import { z as z3 } from "zod";
4488
- import { parse as parseYaml } from "yaml";
4489
- var CategorySchema = z3.enum(["language", "service", "feature"]);
4490
- var FeatureContributionSchema = z3.object({
4491
- ref: z3.string().regex(REGEX.featureRef),
4492
- options: z3.record(z3.string(), FeatureOptionValueSchema).optional()
4493
- });
4494
- var ComponentFileSchema = z3.object({
4495
- displayName: z3.string().min(1),
4496
- description: z3.string().min(1),
4497
- category: CategorySchema,
4498
- contributes: z3.object({
4499
- languages: z3.array(z3.string().min(1)).optional(),
4500
- services: z3.array(z3.string().min(1)).optional(),
4501
- features: z3.array(FeatureContributionSchema).optional()
4502
- })
4503
- }).superRefine((data, ctx) => {
4504
- const c = data.contributes;
4505
- const filled = [
4506
- c.languages && c.languages.length > 0 ? "languages" : null,
4507
- c.services && c.services.length > 0 ? "services" : null,
4508
- c.features && c.features.length > 0 ? "features" : null
4509
- ].filter((x) => x !== null);
4510
- if (filled.length === 0) {
4511
- ctx.addIssue({
4512
- code: z3.ZodIssueCode.custom,
4513
- message: "contributes must set at least one of languages/services/features"
4514
- });
4515
- return;
4575
+ // src/init/manifest.ts
4576
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
4577
+ import path12 from "path";
4578
+ function resolveManifestPath(name, checkoutRoot) {
4579
+ if (checkoutRoot) {
4580
+ const checkoutPath = path12.join(
4581
+ checkoutRoot,
4582
+ "images",
4583
+ "features",
4584
+ name,
4585
+ "devcontainer-feature.json"
4586
+ );
4587
+ if (existsSync6(checkoutPath)) return checkoutPath;
4516
4588
  }
4517
- if (filled.length > 1) {
4518
- ctx.addIssue({
4519
- code: z3.ZodIssueCode.custom,
4520
- message: `contributes must set exactly one of languages/services/features, got: ${filled.join(", ")}`
4521
- });
4522
- return;
4589
+ const bundlePath = path12.join(
4590
+ bundledFeaturesDir(),
4591
+ name,
4592
+ "devcontainer-feature.json"
4593
+ );
4594
+ if (existsSync6(bundlePath)) return bundlePath;
4595
+ return null;
4596
+ }
4597
+ function loadFeatureManifestSummary(ref, checkoutRoot = workbenchCheckoutRoot()) {
4598
+ const match = matchMonocerosFeature(ref);
4599
+ if (!match) return void 0;
4600
+ const manifestPath = resolveManifestPath(match.name, checkoutRoot);
4601
+ if (!manifestPath) return void 0;
4602
+ try {
4603
+ const text = readFileSync3(manifestPath, "utf8");
4604
+ const parsed = JSON.parse(text);
4605
+ const rawHints = parsed["x-monoceros"]?.optionHints;
4606
+ const optionHints = Array.isArray(rawHints) ? rawHints.filter(
4607
+ (x) => typeof x === "string" && x.length > 0
4608
+ ) : [];
4609
+ const rawNotes = parsed["x-monoceros"]?.usageNotes;
4610
+ const usageNotes = Array.isArray(rawNotes) ? rawNotes.filter(
4611
+ (x) => typeof x === "string" && x.length > 0
4612
+ ) : [];
4613
+ const optionDescriptions = {};
4614
+ const optionTypes = {};
4615
+ const optionNames = [];
4616
+ if (parsed.options) {
4617
+ for (const [key, opt] of Object.entries(parsed.options)) {
4618
+ if (!opt || typeof opt !== "object") continue;
4619
+ optionNames.push(key);
4620
+ if (typeof opt.description === "string" && opt.description.length > 0) {
4621
+ optionDescriptions[key] = opt.description;
4622
+ }
4623
+ if (opt.type === "boolean") {
4624
+ optionTypes[key] = "boolean";
4625
+ } else if (opt.type === "string") {
4626
+ optionTypes[key] = "string";
4627
+ }
4628
+ }
4629
+ }
4630
+ const name = typeof parsed.name === "string" ? parsed.name : "";
4631
+ const description = typeof parsed.description === "string" ? parsed.description : "";
4632
+ const rawUrl = typeof parsed.documentationURL === "string" ? parsed.documentationURL.trim() : "";
4633
+ const documentationURL = rawUrl.length > 0 && rawUrl.toLowerCase() !== "tbd" ? rawUrl : void 0;
4634
+ return {
4635
+ name,
4636
+ description,
4637
+ documentationURL,
4638
+ optionHints,
4639
+ optionDescriptions,
4640
+ optionNames,
4641
+ optionTypes,
4642
+ usageNotes
4643
+ };
4644
+ } catch {
4645
+ return void 0;
4523
4646
  }
4524
- const expected = data.category === "language" ? "languages" : data.category === "service" ? "services" : "features";
4525
- if (filled[0] !== expected) {
4526
- ctx.addIssue({
4527
- code: z3.ZodIssueCode.custom,
4528
- message: `category '${data.category}' requires contributes.${expected}, got contributes.${filled[0]}`
4529
- });
4647
+ }
4648
+
4649
+ // src/completion/resolve.ts
4650
+ async function resolveCompletions(line, point, opts = {}) {
4651
+ const { prev, current } = parseCompletionLine(line, point);
4652
+ const ctx = { prev, current, opts };
4653
+ const head = prev[0];
4654
+ const afterProgram = head === void 0 ? [] : prev.slice(1);
4655
+ if (afterProgram.length === 0) {
4656
+ return filterPrefix(ALL_COMMANDS, current);
4657
+ }
4658
+ const command = afterProgram[0];
4659
+ const spec = COMMAND_SPECS[command];
4660
+ if (!spec) {
4661
+ return [];
4662
+ }
4663
+ const argTokens = afterProgram.slice(1);
4664
+ return dispatchCommand(spec, argTokens, ctx);
4665
+ }
4666
+ function parseCompletionLine(line, point) {
4667
+ const before = line.slice(0, Math.max(0, Math.min(point, line.length)));
4668
+ const tokens = tokenize(before);
4669
+ const lastChar = before.length > 0 ? before[before.length - 1] : "";
4670
+ if (tokens.length === 0 || isShellWhitespace(lastChar)) {
4671
+ return { prev: tokens, current: "" };
4530
4672
  }
4531
- });
4532
- async function loadComponentCatalog(rootDir = componentsDir()) {
4533
- if (!existsSync5(rootDir)) {
4534
- return /* @__PURE__ */ new Map();
4673
+ return {
4674
+ prev: tokens.slice(0, -1),
4675
+ current: tokens[tokens.length - 1]
4676
+ };
4677
+ }
4678
+ function tokenize(text) {
4679
+ const out = [];
4680
+ let i = 0;
4681
+ while (i < text.length) {
4682
+ while (i < text.length && isShellWhitespace(text[i])) i++;
4683
+ if (i >= text.length) break;
4684
+ let token = "";
4685
+ let quote = null;
4686
+ while (i < text.length) {
4687
+ const ch = text[i];
4688
+ if (quote === null && isShellWhitespace(ch)) break;
4689
+ if (quote === null && (ch === '"' || ch === "'")) {
4690
+ quote = ch;
4691
+ i++;
4692
+ continue;
4693
+ }
4694
+ if (quote !== null && ch === quote) {
4695
+ quote = null;
4696
+ i++;
4697
+ continue;
4698
+ }
4699
+ token += ch;
4700
+ i++;
4701
+ }
4702
+ out.push(token);
4535
4703
  }
4536
- const out = /* @__PURE__ */ new Map();
4537
- await walk(rootDir, rootDir, out);
4538
4704
  return out;
4539
4705
  }
4540
- async function walk(baseDir, currentDir, out) {
4541
- const entries = await fs11.readdir(currentDir, { withFileTypes: true });
4542
- for (const entry2 of entries) {
4543
- const full = path11.join(currentDir, entry2.name);
4544
- if (entry2.isDirectory()) {
4545
- await walk(baseDir, full, out);
4706
+ function isShellWhitespace(ch) {
4707
+ return ch === " " || ch === " ";
4708
+ }
4709
+ function dispatchCommand(spec, argTokens, ctx) {
4710
+ const dashDashIdx = argTokens.indexOf("--");
4711
+ const preDash = dashDashIdx < 0 ? argTokens : argTokens.slice(0, dashDashIdx);
4712
+ const postDash = dashDashIdx < 0 ? [] : argTokens.slice(dashDashIdx + 1);
4713
+ const inPostDash = dashDashIdx >= 0;
4714
+ if (inPostDash && spec.innerArgs) {
4715
+ return resolveValues(spec.innerArgs, ctx, ctx.current);
4716
+ }
4717
+ return resolvePreDash(spec, preDash, ctx);
4718
+ }
4719
+ async function resolvePreDash(spec, preDash, ctx) {
4720
+ const current = ctx.current;
4721
+ if (current.startsWith("--") && current.includes("=")) {
4722
+ const eqIdx = current.indexOf("=");
4723
+ const flagName = current.slice(0, eqIdx);
4724
+ const valueFragment = current.slice(eqIdx + 1);
4725
+ const flag = spec.flags?.[flagName];
4726
+ if (!flag || flag.type !== "value" || !flag.values) return [];
4727
+ const completions = await resolveValues(flag.values, ctx, valueFragment);
4728
+ return completions.map((v) => `${flagName}=${v}`);
4729
+ }
4730
+ if (current.startsWith("-")) {
4731
+ const flags = spec.flags ?? {};
4732
+ const names = Object.keys(flags);
4733
+ const all = [];
4734
+ for (const n of names) {
4735
+ all.push(n);
4736
+ for (const a of flags[n].aliases ?? []) all.push(a);
4737
+ }
4738
+ return filterPrefix(all, current);
4739
+ }
4740
+ const lastPrev = preDash[preDash.length - 1];
4741
+ if (lastPrev && lastPrev.startsWith("--") && !lastPrev.includes("=")) {
4742
+ const flag = spec.flags?.[lastPrev];
4743
+ if (flag && flag.type === "value" && flag.values) {
4744
+ return resolveValues(flag.values, ctx, current);
4745
+ }
4746
+ }
4747
+ const positionalIdx = countCompletedPositionals(preDash, spec.flags ?? {});
4748
+ const positional = spec.positionals?.[positionalIdx];
4749
+ if (positional) {
4750
+ return resolveValues(positional, ctx, current);
4751
+ }
4752
+ return [];
4753
+ }
4754
+ function countCompletedPositionals(preDash, flags) {
4755
+ let count = 0;
4756
+ for (let i = 0; i < preDash.length; i++) {
4757
+ const t = preDash[i];
4758
+ if (t.startsWith("--") && t.includes("=")) continue;
4759
+ if (t.startsWith("-")) {
4760
+ const flag = flags[t] ?? aliasFlag(flags, t);
4761
+ if (flag && flag.type === "value" && i + 1 < preDash.length) {
4762
+ i++;
4763
+ }
4546
4764
  continue;
4547
4765
  }
4548
- if (!entry2.isFile() || !entry2.name.endsWith(".yml")) continue;
4549
- const relative = path11.relative(baseDir, full);
4550
- const name = relative.replace(/\.yml$/, "").split(path11.sep).join("/");
4551
- const text = await fs11.readFile(full, "utf8");
4552
- let raw;
4553
- try {
4554
- raw = parseYaml(text);
4555
- } catch (err) {
4556
- throw new Error(
4557
- `Failed to parse component ${name} (${full}): ${err.message}`
4558
- );
4559
- }
4560
- const parsed = ComponentFileSchema.safeParse(raw);
4561
- if (!parsed.success) {
4562
- const issues = parsed.error.issues.map((issue) => {
4563
- const where = issue.path.length > 0 ? issue.path.join(".") : "(root)";
4564
- return ` - ${where}: ${issue.message}`;
4565
- }).join("\n");
4566
- throw new Error(`Invalid component ${name} (${full}):
4567
- ${issues}`);
4568
- }
4569
- out.set(name, { name, sourcePath: full, file: parsed.data });
4766
+ count++;
4570
4767
  }
4768
+ return count;
4571
4769
  }
4572
- function mergeComponents(resolved) {
4573
- const languages = [];
4574
- const services = [];
4575
- const featureByRef = /* @__PURE__ */ new Map();
4576
- for (const entry2 of resolved) {
4577
- const c = isResolvedComponent(entry2) ? entry2.component : entry2;
4578
- const version = isResolvedComponent(entry2) ? entry2.version : void 0;
4579
- const ct = c.file.contributes;
4580
- for (const lang of ct.languages ?? []) {
4581
- const value = version !== void 0 ? `${lang}:${version}` : lang;
4582
- if (!languages.includes(value)) languages.push(value);
4583
- }
4584
- for (const svc of ct.services ?? []) {
4585
- if (!services.includes(svc)) services.push(svc);
4586
- }
4587
- for (const f of ct.features ?? []) {
4588
- const existing = featureByRef.get(f.ref);
4589
- if (!existing) {
4590
- featureByRef.set(f.ref, {
4591
- ref: f.ref,
4592
- options: { ...f.options ?? {} }
4593
- });
4594
- continue;
4595
- }
4596
- existing.options = mergeFeatureOptions(existing.options, f.options ?? {});
4597
- }
4770
+ function aliasFlag(flags, token) {
4771
+ for (const f of Object.values(flags)) {
4772
+ if (f.aliases?.includes(token)) return f;
4598
4773
  }
4599
- return {
4600
- languages,
4601
- services,
4602
- features: [...featureByRef.values()]
4603
- };
4774
+ return void 0;
4604
4775
  }
4605
- function isResolvedComponent(x) {
4606
- return "component" in x;
4776
+ async function resolveValues(source, ctx, fragment) {
4777
+ const values = await source(ctx);
4778
+ const commaIdx = fragment.lastIndexOf(",");
4779
+ if (commaIdx < 0) {
4780
+ return filterPrefix(values, fragment);
4781
+ }
4782
+ const prefix = fragment.slice(0, commaIdx + 1);
4783
+ const tail = fragment.slice(commaIdx + 1);
4784
+ return filterPrefix(values, tail).map((v) => `${prefix}${v}`);
4785
+ }
4786
+ function filterPrefix(values, fragment) {
4787
+ if (fragment.length === 0) return [...values];
4788
+ return values.filter((v) => v.startsWith(fragment));
4789
+ }
4790
+ async function listContainerNames(ctx) {
4791
+ const home = ctx.opts.monocerosHome ?? monocerosHome();
4792
+ const dir = path13.join(home, "container-configs");
4793
+ if (!existsSync7(dir)) return [];
4794
+ const entries = await fs12.readdir(dir);
4795
+ return entries.filter((e) => e.endsWith(".yml")).map((e) => e.slice(0, -".yml".length)).sort();
4796
+ }
4797
+ async function listAllCatalogComponents() {
4798
+ const catalog = await loadComponentCatalog();
4799
+ return [...catalog.keys()].sort();
4800
+ }
4801
+ async function listFeatureComponents() {
4802
+ const catalog = await loadComponentCatalog();
4803
+ return [...catalog.values()].filter((c) => c.file.category === "feature").map((c) => c.name).sort();
4804
+ }
4805
+ function listLanguageNames() {
4806
+ return knownLanguages().sort();
4807
+ }
4808
+ function listServiceNames() {
4809
+ return knownServices().sort();
4810
+ }
4811
+ function listProviders() {
4812
+ return [...PROVIDER_VALUES];
4813
+ }
4814
+ function listShellNames() {
4815
+ return ["bash", "zsh", "pwsh"];
4816
+ }
4817
+ async function listFeatureOptionInnerArgs(ctx) {
4818
+ const after = ctx.prev.slice(2);
4819
+ let positionalCount = 0;
4820
+ let featureToken;
4821
+ for (let i = 0; i < after.length; i++) {
4822
+ const t = after[i];
4823
+ if (t === "--") break;
4824
+ if (t.startsWith("-")) continue;
4825
+ positionalCount++;
4826
+ if (positionalCount === 2) {
4827
+ featureToken = t;
4828
+ break;
4829
+ }
4830
+ }
4831
+ if (!featureToken) return [];
4832
+ const ref = await resolveFeatureRefForCompletion(featureToken);
4833
+ if (!ref) return [];
4834
+ const summary = loadFeatureManifestSummary(ref);
4835
+ if (!summary) return [];
4836
+ const dashDash = ctx.prev.indexOf("--");
4837
+ const innerSoFar = dashDash >= 0 ? ctx.prev.slice(dashDash + 1) : [];
4838
+ const usedKeys = /* @__PURE__ */ new Set();
4839
+ for (const t of innerSoFar) {
4840
+ const eq = t.indexOf("=");
4841
+ if (eq > 0) usedKeys.add(t.slice(0, eq));
4842
+ }
4843
+ const eqIdx = ctx.current.indexOf("=");
4844
+ if (eqIdx >= 0) {
4845
+ const key = ctx.current.slice(0, eqIdx);
4846
+ const valueFragment = ctx.current.slice(eqIdx + 1);
4847
+ const type = summary.optionTypes[key];
4848
+ if (type === "boolean") {
4849
+ return ["true", "false"].filter((v) => v.startsWith(valueFragment)).map((v) => `${key}=${v}`);
4850
+ }
4851
+ return [];
4852
+ }
4853
+ return summary.optionNames.filter((n) => !usedKeys.has(n));
4854
+ }
4855
+ async function resolveFeatureRefForCompletion(token) {
4856
+ if (REGEX.featureRef.test(token)) return token;
4857
+ const catalog = await loadComponentCatalog();
4858
+ const c = catalog.get(token);
4859
+ if (!c || c.file.category !== "feature") return void 0;
4860
+ const f = c.file.contributes.features?.[0];
4861
+ return f?.ref;
4607
4862
  }
4608
- function mergeFeatureOptions(a, b) {
4609
- const result = { ...a };
4610
- for (const [key, valueB] of Object.entries(b)) {
4611
- const valueA = result[key];
4612
- if (typeof valueA === "boolean" && typeof valueB === "boolean") {
4613
- result[key] = valueA || valueB;
4614
- continue;
4863
+ var ALL_COMMANDS = [
4864
+ "init",
4865
+ "list-components",
4866
+ "shell",
4867
+ "run",
4868
+ "logs",
4869
+ "start",
4870
+ "stop",
4871
+ "status",
4872
+ "apply",
4873
+ "remove",
4874
+ "restore",
4875
+ "add-service",
4876
+ "add-language",
4877
+ "add-apt-packages",
4878
+ "add-feature",
4879
+ "add-from-url",
4880
+ "add-repo",
4881
+ "add-port",
4882
+ "remove-service",
4883
+ "remove-language",
4884
+ "remove-apt-packages",
4885
+ "remove-feature",
4886
+ "remove-from-url",
4887
+ "remove-repo",
4888
+ "remove-port",
4889
+ "port",
4890
+ "completion"
4891
+ ];
4892
+ var containerName = (ctx) => listContainerNames(ctx);
4893
+ var COMMAND_SPECS = {
4894
+ init: {
4895
+ // First positional is a FRESH name → no suggestions from existing
4896
+ // container-configs (would invite collisions).
4897
+ flags: {
4898
+ "--with": { type: "value", values: () => listAllCatalogComponents() },
4899
+ "--with-repo": { type: "value" },
4900
+ "--with-ports": { type: "value" }
4615
4901
  }
4616
- result[key] = valueB;
4902
+ },
4903
+ apply: {
4904
+ positionals: [containerName],
4905
+ flags: { "--yes": { type: "boolean", aliases: ["-y"] } }
4906
+ },
4907
+ remove: {
4908
+ positionals: [containerName],
4909
+ flags: {
4910
+ "--yes": { type: "boolean", aliases: ["-y"] },
4911
+ "--no-backup": { type: "boolean" }
4912
+ }
4913
+ },
4914
+ shell: { positionals: [containerName] },
4915
+ run: { positionals: [containerName] },
4916
+ logs: { positionals: [containerName] },
4917
+ start: { positionals: [containerName] },
4918
+ stop: { positionals: [containerName] },
4919
+ status: { positionals: [containerName] },
4920
+ "add-language": {
4921
+ positionals: [containerName, () => listLanguageNames()]
4922
+ },
4923
+ "add-service": {
4924
+ positionals: [containerName, () => listServiceNames()]
4925
+ },
4926
+ "add-apt-packages": {
4927
+ positionals: [containerName],
4928
+ innerArgs: () => []
4929
+ // freeform package names — no useful suggestion list
4930
+ },
4931
+ "add-feature": {
4932
+ positionals: [containerName, () => listFeatureComponents()],
4933
+ flags: { "--yes": { type: "boolean", aliases: ["-y"] } },
4934
+ innerArgs: (ctx) => listFeatureOptionInnerArgs(ctx)
4935
+ },
4936
+ "add-from-url": { positionals: [containerName] },
4937
+ "add-repo": {
4938
+ positionals: [containerName],
4939
+ flags: {
4940
+ "--path": { type: "value" },
4941
+ "--git-name": { type: "value" },
4942
+ "--git-email": { type: "value" },
4943
+ "--provider": { type: "value", values: () => listProviders() },
4944
+ "--yes": { type: "boolean", aliases: ["-y"] }
4945
+ }
4946
+ },
4947
+ "add-port": {
4948
+ positionals: [containerName],
4949
+ flags: {
4950
+ "--default": { type: "boolean" },
4951
+ "--yes": { type: "boolean", aliases: ["-y"] }
4952
+ },
4953
+ innerArgs: () => []
4954
+ },
4955
+ "remove-language": {
4956
+ positionals: [containerName, () => listLanguageNames()]
4957
+ },
4958
+ "remove-service": {
4959
+ positionals: [containerName, () => listServiceNames()]
4960
+ },
4961
+ "remove-apt-packages": {
4962
+ positionals: [containerName],
4963
+ innerArgs: () => []
4964
+ },
4965
+ "remove-feature": {
4966
+ positionals: [containerName, () => listFeatureComponents()],
4967
+ flags: { "--yes": { type: "boolean", aliases: ["-y"] } }
4968
+ },
4969
+ "remove-from-url": { positionals: [containerName] },
4970
+ "remove-repo": { positionals: [containerName] },
4971
+ "remove-port": {
4972
+ positionals: [containerName],
4973
+ flags: { "--yes": { type: "boolean", aliases: ["-y"] } },
4974
+ innerArgs: () => []
4975
+ },
4976
+ port: {
4977
+ positionals: [containerName],
4978
+ flags: { "--default": { type: "boolean" } },
4979
+ innerArgs: () => []
4980
+ },
4981
+ completion: {
4982
+ positionals: [() => listShellNames()]
4983
+ },
4984
+ "list-components": {},
4985
+ restore: {
4986
+ // First positional is a backup-path; no value suggestions today
4987
+ // (could plug filesystem completion later).
4617
4988
  }
4618
- return result;
4619
- }
4620
- function resolveComponents(catalog, names) {
4621
- const unknown = [];
4622
- const out = [];
4623
- for (const raw of names) {
4624
- const colon = raw.indexOf(":");
4625
- const name = colon === -1 ? raw : raw.slice(0, colon);
4626
- const version = colon === -1 ? void 0 : raw.slice(colon + 1);
4627
- const c = catalog.get(name);
4628
- if (!c) {
4629
- unknown.push(raw);
4630
- continue;
4989
+ };
4990
+ var COMPLETION_COMMAND_SPEC_KEYS = Object.keys(COMMAND_SPECS);
4991
+
4992
+ // src/commands/__complete.ts
4993
+ var __completeCommand = defineCommand10({
4994
+ meta: {
4995
+ name: "__complete",
4996
+ group: "internal",
4997
+ description: "Internal \u2014 shell completion engine. Used by the wrappers emitted by `monoceros completion <shell>`. Output one candidate completion per line."
4998
+ },
4999
+ args: {
5000
+ line: {
5001
+ type: "string",
5002
+ description: "Full command line buffer up to (and possibly past) the cursor.",
5003
+ default: ""
5004
+ },
5005
+ point: {
5006
+ type: "string",
5007
+ description: "Byte offset of the cursor within --line. Default: end of line.",
5008
+ default: ""
4631
5009
  }
4632
- if (version !== void 0 && c.file.category !== "language") {
4633
- throw new Error(
4634
- `Component '${name}' is a ${c.file.category}, not a language \u2014 a ':${version}' suffix has no meaning here.`
5010
+ },
5011
+ async run({ args }) {
5012
+ const line = String(args.line ?? "");
5013
+ const point = args.point && String(args.point).length > 0 ? Number.parseInt(String(args.point), 10) : line.length;
5014
+ let candidates;
5015
+ try {
5016
+ candidates = await resolveCompletions(
5017
+ line,
5018
+ Number.isFinite(point) ? point : line.length
4635
5019
  );
5020
+ } catch {
5021
+ candidates = [];
5022
+ }
5023
+ if (candidates.length > 0) {
5024
+ process.stdout.write(candidates.join("\n") + "\n");
4636
5025
  }
4637
- out.push({ component: c, ...version !== void 0 ? { version } : {} });
4638
- }
4639
- if (unknown.length > 0) {
4640
- const available = [...catalog.keys()].sort();
4641
- throw new Error(
4642
- `Unknown component${unknown.length > 1 ? "s" : ""}: ${unknown.join(", ")}.
4643
- Available: ${available.join(", ")}.`
4644
- );
4645
5026
  }
4646
- return out;
4647
- }
5027
+ });
5028
+
5029
+ // src/commands/init.ts
5030
+ import { defineCommand as defineCommand11 } from "citty";
5031
+ import { consola as consola14 } from "consola";
5032
+
5033
+ // src/init/index.ts
5034
+ import { existsSync as existsSync8, promises as fs13 } from "fs";
5035
+ import { consola as consola13 } from "consola";
4648
5036
 
4649
5037
  // src/init/generator.ts
4650
5038
  var SCHEMA_HEADER_ACTIVE = "# Solution-config \u2014 describes what should be inside your dev-container.\n# Edit any section, then run `monoceros apply <name>` to (re-)build.";
@@ -5011,69 +5399,6 @@ function ensureTrailingNewline(s) {
5011
5399
  return s.endsWith("\n") ? s : s + "\n";
5012
5400
  }
5013
5401
 
5014
- // src/init/manifest.ts
5015
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
5016
- import path12 from "path";
5017
- function resolveManifestPath(name, checkoutRoot) {
5018
- if (checkoutRoot) {
5019
- const checkoutPath = path12.join(
5020
- checkoutRoot,
5021
- "images",
5022
- "features",
5023
- name,
5024
- "devcontainer-feature.json"
5025
- );
5026
- if (existsSync6(checkoutPath)) return checkoutPath;
5027
- }
5028
- const bundlePath = path12.join(
5029
- bundledFeaturesDir(),
5030
- name,
5031
- "devcontainer-feature.json"
5032
- );
5033
- if (existsSync6(bundlePath)) return bundlePath;
5034
- return null;
5035
- }
5036
- function loadFeatureManifestSummary(ref, checkoutRoot = workbenchCheckoutRoot()) {
5037
- const match = matchMonocerosFeature(ref);
5038
- if (!match) return void 0;
5039
- const manifestPath = resolveManifestPath(match.name, checkoutRoot);
5040
- if (!manifestPath) return void 0;
5041
- try {
5042
- const text = readFileSync3(manifestPath, "utf8");
5043
- const parsed = JSON.parse(text);
5044
- const rawHints = parsed["x-monoceros"]?.optionHints;
5045
- const optionHints = Array.isArray(rawHints) ? rawHints.filter(
5046
- (x) => typeof x === "string" && x.length > 0
5047
- ) : [];
5048
- const rawNotes = parsed["x-monoceros"]?.usageNotes;
5049
- const usageNotes = Array.isArray(rawNotes) ? rawNotes.filter(
5050
- (x) => typeof x === "string" && x.length > 0
5051
- ) : [];
5052
- const optionDescriptions = {};
5053
- if (parsed.options) {
5054
- for (const [key, opt] of Object.entries(parsed.options)) {
5055
- if (opt && typeof opt === "object" && typeof opt.description === "string" && opt.description.length > 0) {
5056
- optionDescriptions[key] = opt.description;
5057
- }
5058
- }
5059
- }
5060
- const name = typeof parsed.name === "string" ? parsed.name : "";
5061
- const description = typeof parsed.description === "string" ? parsed.description : "";
5062
- const rawUrl = typeof parsed.documentationURL === "string" ? parsed.documentationURL.trim() : "";
5063
- const documentationURL = rawUrl.length > 0 && rawUrl.toLowerCase() !== "tbd" ? rawUrl : void 0;
5064
- return {
5065
- name,
5066
- description,
5067
- documentationURL,
5068
- optionHints,
5069
- optionDescriptions,
5070
- usageNotes
5071
- };
5072
- } catch {
5073
- return void 0;
5074
- }
5075
- }
5076
-
5077
5402
  // src/init/index.ts
5078
5403
  async function runInit(opts) {
5079
5404
  const workbench = opts.workbenchRoot ?? workbenchRoot();
@@ -5088,7 +5413,7 @@ async function runInit(opts) {
5088
5413
  );
5089
5414
  }
5090
5415
  const dest = containerConfigPath(opts.name, home);
5091
- if (existsSync7(dest)) {
5416
+ if (existsSync8(dest)) {
5092
5417
  throw new Error(
5093
5418
  `Config already exists: ${dest}. Delete it manually before re-running \`monoceros init\` \u2014 this protects any hand-edits.`
5094
5419
  );
@@ -5165,8 +5490,8 @@ async function runInit(opts) {
5165
5490
  const components = resolveComponents(catalog, requested);
5166
5491
  text = generateComposedYml(opts.name, components, lookup, repos, ports);
5167
5492
  }
5168
- await fs12.mkdir(containerConfigsDir(home), { recursive: true });
5169
- await fs12.writeFile(dest, text, "utf8");
5493
+ await fs13.mkdir(containerConfigsDir(home), { recursive: true });
5494
+ await fs13.writeFile(dest, text, "utf8");
5170
5495
  if (promptedIdentity?.prompted) {
5171
5496
  const { name, email, scope } = promptedIdentity.prompted;
5172
5497
  if (scope === "g" || scope === "b") {
@@ -5196,11 +5521,11 @@ async function runInit(opts) {
5196
5521
  }
5197
5522
  if (scope === "c" || scope === "b") {
5198
5523
  try {
5199
- const written = await fs12.readFile(dest, "utf8");
5524
+ const written = await fs13.readFile(dest, "utf8");
5200
5525
  const parsed = parseConfig(written, dest);
5201
5526
  const changed = setContainerGitUserInDoc(parsed.doc, { name, email });
5202
5527
  if (changed) {
5203
- await fs12.writeFile(dest, stringifyConfig(parsed.doc), "utf8");
5528
+ await fs13.writeFile(dest, stringifyConfig(parsed.doc), "utf8");
5204
5529
  logger.info(
5205
5530
  `Saved identity in ${prettyPath(dest)} (container-level git.user).`
5206
5531
  );
@@ -5230,7 +5555,7 @@ async function runInit(opts) {
5230
5555
  }
5231
5556
 
5232
5557
  // src/commands/init.ts
5233
- var initCommand = defineCommand10({
5558
+ var initCommand = defineCommand11({
5234
5559
  meta: {
5235
5560
  name: "init",
5236
5561
  group: "lifecycle",
@@ -5349,7 +5674,7 @@ function collectWithList(withArg, rawArgs) {
5349
5674
  }
5350
5675
 
5351
5676
  // src/commands/list-components.ts
5352
- import { defineCommand as defineCommand11 } from "citty";
5677
+ import { defineCommand as defineCommand12 } from "citty";
5353
5678
  import { consola as consola15 } from "consola";
5354
5679
  var CATEGORY_LABELS = {
5355
5680
  language: "Languages",
@@ -5361,7 +5686,7 @@ var CATEGORY_ORDER = [
5361
5686
  "service",
5362
5687
  "feature"
5363
5688
  ];
5364
- var listComponentsCommand = defineCommand11({
5689
+ var listComponentsCommand = defineCommand12({
5365
5690
  meta: {
5366
5691
  name: "list-components",
5367
5692
  group: "discovery",
@@ -5430,8 +5755,8 @@ var listComponentsCommand = defineCommand11({
5430
5755
  });
5431
5756
 
5432
5757
  // src/commands/logs.ts
5433
- import { defineCommand as defineCommand12 } from "citty";
5434
- var logsCommand = defineCommand12({
5758
+ import { defineCommand as defineCommand13 } from "citty";
5759
+ var logsCommand = defineCommand13({
5435
5760
  meta: {
5436
5761
  name: "logs",
5437
5762
  group: "run",
@@ -5466,7 +5791,7 @@ var logsCommand = defineCommand12({
5466
5791
  });
5467
5792
 
5468
5793
  // src/commands/port.ts
5469
- import { defineCommand as defineCommand13 } from "citty";
5794
+ import { defineCommand as defineCommand14 } from "citty";
5470
5795
  import { consola as consola16 } from "consola";
5471
5796
  async function runPortListing(opts) {
5472
5797
  const out = opts.out ?? process.stdout;
@@ -5518,7 +5843,7 @@ async function runPortListing(opts) {
5518
5843
  }
5519
5844
  return 0;
5520
5845
  }
5521
- var portCommand = defineCommand13({
5846
+ var portCommand = defineCommand14({
5522
5847
  meta: {
5523
5848
  name: "port",
5524
5849
  group: "discovery",
@@ -5543,9 +5868,9 @@ var portCommand = defineCommand13({
5543
5868
  });
5544
5869
 
5545
5870
  // src/commands/remove-apt-packages.ts
5546
- import { defineCommand as defineCommand14 } from "citty";
5871
+ import { defineCommand as defineCommand15 } from "citty";
5547
5872
  import { consola as consola17 } from "consola";
5548
- var removeAptPackagesCommand = defineCommand14({
5873
+ var removeAptPackagesCommand = defineCommand15({
5549
5874
  meta: {
5550
5875
  name: "remove-apt-packages",
5551
5876
  group: "edit",
@@ -5587,9 +5912,9 @@ var removeAptPackagesCommand = defineCommand14({
5587
5912
  });
5588
5913
 
5589
5914
  // src/commands/remove-feature.ts
5590
- import { defineCommand as defineCommand15 } from "citty";
5915
+ import { defineCommand as defineCommand16 } from "citty";
5591
5916
  import { consola as consola18 } from "consola";
5592
- var removeFeatureCommand = defineCommand15({
5917
+ var removeFeatureCommand = defineCommand16({
5593
5918
  meta: {
5594
5919
  name: "remove-feature",
5595
5920
  group: "edit",
@@ -5629,13 +5954,13 @@ var removeFeatureCommand = defineCommand15({
5629
5954
  });
5630
5955
 
5631
5956
  // src/commands/remove.ts
5632
- import { defineCommand as defineCommand16 } from "citty";
5957
+ import { defineCommand as defineCommand17 } from "citty";
5633
5958
  import { consola as consola20 } from "consola";
5634
5959
  import { createInterface } from "readline/promises";
5635
5960
 
5636
5961
  // src/remove/index.ts
5637
- import { existsSync as existsSync8, promises as fs13 } from "fs";
5638
- import path13 from "path";
5962
+ import { existsSync as existsSync9, promises as fs14 } from "fs";
5963
+ import path14 from "path";
5639
5964
  import { consola as consola19 } from "consola";
5640
5965
  async function runRemove(opts) {
5641
5966
  const home = opts.monocerosHome ?? monocerosHome();
@@ -5651,8 +5976,8 @@ async function runRemove(opts) {
5651
5976
  }
5652
5977
  const ymlPath = containerConfigPath(opts.name, home);
5653
5978
  const containerPath = containerDir(opts.name, home);
5654
- const hasYml = existsSync8(ymlPath);
5655
- const hasContainer = existsSync8(containerPath);
5979
+ const hasYml = existsSync9(ymlPath);
5980
+ const hasContainer = existsSync9(containerPath);
5656
5981
  if (!hasYml && !hasContainer) {
5657
5982
  throw new Error(
5658
5983
  `Nothing to remove for '${opts.name}': neither ${ymlPath} nor ${containerPath} exists.`
@@ -5686,23 +6011,23 @@ async function runRemove(opts) {
5686
6011
  let backupPath = null;
5687
6012
  if (!opts.noBackup && (hasYml || hasContainer)) {
5688
6013
  const ts = (opts.now ?? /* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
5689
- backupPath = path13.join(home, "container-backups", `${opts.name}-${ts}`);
5690
- await fs13.mkdir(backupPath, { recursive: true });
6014
+ backupPath = path14.join(home, "container-backups", `${opts.name}-${ts}`);
6015
+ await fs14.mkdir(backupPath, { recursive: true });
5691
6016
  if (hasYml) {
5692
- await fs13.copyFile(ymlPath, path13.join(backupPath, `${opts.name}.yml`));
6017
+ await fs14.copyFile(ymlPath, path14.join(backupPath, `${opts.name}.yml`));
5693
6018
  }
5694
6019
  if (hasContainer) {
5695
- await fs13.cp(containerPath, path13.join(backupPath, "container"), {
6020
+ await fs14.cp(containerPath, path14.join(backupPath, "container"), {
5696
6021
  recursive: true
5697
6022
  });
5698
6023
  }
5699
6024
  logger.info(`Backup written to ${prettyPath(backupPath)}.`);
5700
6025
  }
5701
6026
  if (hasYml) {
5702
- await fs13.rm(ymlPath, { force: true });
6027
+ await fs14.rm(ymlPath, { force: true });
5703
6028
  }
5704
6029
  if (hasContainer) {
5705
- await fs13.rm(containerPath, { recursive: true, force: true });
6030
+ await fs14.rm(containerPath, { recursive: true, force: true });
5706
6031
  }
5707
6032
  logger.success(
5708
6033
  `Removed '${opts.name}': docker objects gone, container-configs entry deleted, container directory deleted.`
@@ -5739,7 +6064,7 @@ async function runRemove(opts) {
5739
6064
  }
5740
6065
 
5741
6066
  // src/commands/remove.ts
5742
- var removeCommand = defineCommand16({
6067
+ var removeCommand = defineCommand17({
5743
6068
  meta: {
5744
6069
  name: "remove",
5745
6070
  group: "lifecycle",
@@ -5800,12 +6125,12 @@ var removeCommand = defineCommand16({
5800
6125
  });
5801
6126
 
5802
6127
  // src/commands/restore.ts
5803
- import { defineCommand as defineCommand17 } from "citty";
6128
+ import { defineCommand as defineCommand18 } from "citty";
5804
6129
  import { consola as consola22 } from "consola";
5805
6130
 
5806
6131
  // src/restore/index.ts
5807
- import { existsSync as existsSync9, promises as fs14 } from "fs";
5808
- import path14 from "path";
6132
+ import { existsSync as existsSync10, promises as fs15 } from "fs";
6133
+ import path15 from "path";
5809
6134
  import { consola as consola21 } from "consola";
5810
6135
  async function runRestore(opts) {
5811
6136
  const home = opts.monocerosHome ?? monocerosHome();
@@ -5813,15 +6138,15 @@ async function runRestore(opts) {
5813
6138
  info: (msg) => consola21.info(msg),
5814
6139
  success: (msg) => consola21.success(msg)
5815
6140
  };
5816
- const backup = path14.resolve(opts.backupPath);
5817
- if (!existsSync9(backup)) {
6141
+ const backup = path15.resolve(opts.backupPath);
6142
+ if (!existsSync10(backup)) {
5818
6143
  throw new Error(`Backup not found: ${backup}.`);
5819
6144
  }
5820
- const stat = await fs14.stat(backup);
6145
+ const stat = await fs15.stat(backup);
5821
6146
  if (!stat.isDirectory()) {
5822
6147
  throw new Error(`Backup path is not a directory: ${backup}.`);
5823
6148
  }
5824
- const entries = await fs14.readdir(backup);
6149
+ const entries = await fs15.readdir(backup);
5825
6150
  const ymlFiles = entries.filter((f) => f.endsWith(".yml"));
5826
6151
  if (ymlFiles.length === 0) {
5827
6152
  throw new Error(
@@ -5835,24 +6160,24 @@ async function runRestore(opts) {
5835
6160
  }
5836
6161
  const ymlFile = ymlFiles[0];
5837
6162
  const name = ymlFile.replace(/\.yml$/, "");
5838
- const containerInBackup = path14.join(backup, "container");
5839
- const hasContainer = existsSync9(containerInBackup);
6163
+ const containerInBackup = path15.join(backup, "container");
6164
+ const hasContainer = existsSync10(containerInBackup);
5840
6165
  const destYml = containerConfigPath(name, home);
5841
6166
  const destContainer = containerDir(name, home);
5842
- if (existsSync9(destYml)) {
6167
+ if (existsSync10(destYml)) {
5843
6168
  throw new Error(
5844
6169
  `Refusing to restore: ${destYml} already exists. Remove the current container first (\`monoceros remove ${name}\`) or rename the existing config.`
5845
6170
  );
5846
6171
  }
5847
- if (hasContainer && existsSync9(destContainer)) {
6172
+ if (hasContainer && existsSync10(destContainer)) {
5848
6173
  throw new Error(
5849
6174
  `Refusing to restore: ${destContainer} already exists. Remove the current container first (\`monoceros remove ${name}\`).`
5850
6175
  );
5851
6176
  }
5852
- await fs14.mkdir(containerConfigsDir(home), { recursive: true });
5853
- await fs14.copyFile(path14.join(backup, ymlFile), destYml);
6177
+ await fs15.mkdir(containerConfigsDir(home), { recursive: true });
6178
+ await fs15.copyFile(path15.join(backup, ymlFile), destYml);
5854
6179
  if (hasContainer) {
5855
- await fs14.cp(containerInBackup, destContainer, { recursive: true });
6180
+ await fs15.cp(containerInBackup, destContainer, { recursive: true });
5856
6181
  }
5857
6182
  logger.success(`Restored '${name}' from ${prettyPath(backup)}.`);
5858
6183
  logger.info(
@@ -5866,7 +6191,7 @@ async function runRestore(opts) {
5866
6191
  }
5867
6192
 
5868
6193
  // src/commands/restore.ts
5869
- var restoreCommand = defineCommand17({
6194
+ var restoreCommand = defineCommand18({
5870
6195
  meta: {
5871
6196
  name: "restore",
5872
6197
  group: "lifecycle",
@@ -5890,9 +6215,9 @@ var restoreCommand = defineCommand17({
5890
6215
  });
5891
6216
 
5892
6217
  // src/commands/remove-from-url.ts
5893
- import { defineCommand as defineCommand18 } from "citty";
6218
+ import { defineCommand as defineCommand19 } from "citty";
5894
6219
  import { consola as consola23 } from "consola";
5895
- var removeFromUrlCommand = defineCommand18({
6220
+ var removeFromUrlCommand = defineCommand19({
5896
6221
  meta: {
5897
6222
  name: "remove-from-url",
5898
6223
  group: "edit",
@@ -5932,9 +6257,9 @@ var removeFromUrlCommand = defineCommand18({
5932
6257
  });
5933
6258
 
5934
6259
  // src/commands/remove-language.ts
5935
- import { defineCommand as defineCommand19 } from "citty";
6260
+ import { defineCommand as defineCommand20 } from "citty";
5936
6261
  import { consola as consola24 } from "consola";
5937
- var removeLanguageCommand = defineCommand19({
6262
+ var removeLanguageCommand = defineCommand20({
5938
6263
  meta: {
5939
6264
  name: "remove-language",
5940
6265
  group: "edit",
@@ -5974,9 +6299,9 @@ var removeLanguageCommand = defineCommand19({
5974
6299
  });
5975
6300
 
5976
6301
  // src/commands/remove-port.ts
5977
- import { defineCommand as defineCommand20 } from "citty";
6302
+ import { defineCommand as defineCommand21 } from "citty";
5978
6303
  import { consola as consola25 } from "consola";
5979
- var removePortCommand = defineCommand20({
6304
+ var removePortCommand = defineCommand21({
5980
6305
  meta: {
5981
6306
  name: "remove-port",
5982
6307
  group: "edit",
@@ -6022,9 +6347,9 @@ function coerceToken2(raw) {
6022
6347
  }
6023
6348
 
6024
6349
  // src/commands/remove-repo.ts
6025
- import { defineCommand as defineCommand21 } from "citty";
6350
+ import { defineCommand as defineCommand22 } from "citty";
6026
6351
  import { consola as consola26 } from "consola";
6027
- var removeRepoCommand = defineCommand21({
6352
+ var removeRepoCommand = defineCommand22({
6028
6353
  meta: {
6029
6354
  name: "remove-repo",
6030
6355
  group: "edit",
@@ -6064,9 +6389,9 @@ var removeRepoCommand = defineCommand21({
6064
6389
  });
6065
6390
 
6066
6391
  // src/commands/remove-service.ts
6067
- import { defineCommand as defineCommand22 } from "citty";
6392
+ import { defineCommand as defineCommand23 } from "citty";
6068
6393
  import { consola as consola27 } from "consola";
6069
- var removeServiceCommand = defineCommand22({
6394
+ var removeServiceCommand = defineCommand23({
6070
6395
  meta: {
6071
6396
  name: "remove-service",
6072
6397
  group: "edit",
@@ -6106,12 +6431,12 @@ var removeServiceCommand = defineCommand22({
6106
6431
  });
6107
6432
 
6108
6433
  // src/commands/run.ts
6109
- import { defineCommand as defineCommand23 } from "citty";
6434
+ import { defineCommand as defineCommand24 } from "citty";
6110
6435
  import { consola as consola28 } from "consola";
6111
6436
 
6112
6437
  // src/devcontainer/shell.ts
6113
- import { existsSync as existsSync10 } from "fs";
6114
- import path15 from "path";
6438
+ import { existsSync as existsSync11 } from "fs";
6439
+ import path16 from "path";
6115
6440
  async function runShell(opts) {
6116
6441
  assertContainerExists(opts.root);
6117
6442
  const spawnFn = opts.spawn ?? spawnDevcontainer;
@@ -6134,7 +6459,7 @@ async function runShell(opts) {
6134
6459
  );
6135
6460
  }
6136
6461
  function assertContainerExists(root) {
6137
- if (!existsSync10(path15.join(root, ".devcontainer"))) {
6462
+ if (!existsSync11(path16.join(root, ".devcontainer"))) {
6138
6463
  throw new Error(
6139
6464
  `No .devcontainer/ at ${root}. Run \`monoceros apply <name>\` first.`
6140
6465
  );
@@ -6170,7 +6495,7 @@ async function runInContainer(opts) {
6170
6495
  }
6171
6496
 
6172
6497
  // src/commands/run.ts
6173
- var runCommand = defineCommand23({
6498
+ var runCommand = defineCommand24({
6174
6499
  meta: {
6175
6500
  name: "run",
6176
6501
  group: "run",
@@ -6205,9 +6530,9 @@ var runCommand = defineCommand23({
6205
6530
  });
6206
6531
 
6207
6532
  // src/commands/shell.ts
6208
- import { defineCommand as defineCommand24 } from "citty";
6533
+ import { defineCommand as defineCommand25 } from "citty";
6209
6534
  import { consola as consola29 } from "consola";
6210
- var shellCommand = defineCommand24({
6535
+ var shellCommand = defineCommand25({
6211
6536
  meta: {
6212
6537
  name: "shell",
6213
6538
  group: "run",
@@ -6232,9 +6557,9 @@ var shellCommand = defineCommand24({
6232
6557
  });
6233
6558
 
6234
6559
  // src/commands/start.ts
6235
- import { defineCommand as defineCommand25 } from "citty";
6560
+ import { defineCommand as defineCommand26 } from "citty";
6236
6561
  import { consola as consola30 } from "consola";
6237
- var startCommand = defineCommand25({
6562
+ var startCommand = defineCommand26({
6238
6563
  meta: {
6239
6564
  name: "start",
6240
6565
  group: "run",
@@ -6273,8 +6598,8 @@ var startCommand = defineCommand25({
6273
6598
  });
6274
6599
 
6275
6600
  // src/commands/status.ts
6276
- import { defineCommand as defineCommand26 } from "citty";
6277
- var statusCommand = defineCommand26({
6601
+ import { defineCommand as defineCommand27 } from "citty";
6602
+ var statusCommand = defineCommand27({
6278
6603
  meta: {
6279
6604
  name: "status",
6280
6605
  group: "run",
@@ -6302,9 +6627,9 @@ var statusCommand = defineCommand26({
6302
6627
  });
6303
6628
 
6304
6629
  // src/commands/stop.ts
6305
- import { defineCommand as defineCommand27 } from "citty";
6630
+ import { defineCommand as defineCommand28 } from "citty";
6306
6631
  import { consola as consola31 } from "consola";
6307
- var stopCommand = defineCommand27({
6632
+ var stopCommand = defineCommand28({
6308
6633
  meta: {
6309
6634
  name: "stop",
6310
6635
  group: "run",
@@ -6342,7 +6667,7 @@ var stopCommand = defineCommand27({
6342
6667
  });
6343
6668
 
6344
6669
  // src/main.ts
6345
- var main = defineCommand28({
6670
+ var main = defineCommand29({
6346
6671
  meta: {
6347
6672
  name: "monoceros",
6348
6673
  version: CLI_VERSION,
@@ -6375,7 +6700,8 @@ var main = defineCommand28({
6375
6700
  "remove-repo": removeRepoCommand,
6376
6701
  "remove-port": removePortCommand,
6377
6702
  port: portCommand,
6378
- completion: completionCommand
6703
+ completion: completionCommand,
6704
+ __complete: __completeCommand
6379
6705
  }
6380
6706
  });
6381
6707