@rness/cli 0.5.3 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -45,6 +45,75 @@ async function writeFileAtomic(p, text) {
45
45
  }
46
46
  }
47
47
 
48
+ // src/core/pm.ts
49
+ import { execFile } from "child_process";
50
+ import { promisify } from "util";
51
+ var execFileP = promisify(execFile);
52
+ var PACKAGE_MANAGERS = [
53
+ "npm",
54
+ "pnpm",
55
+ "yarn",
56
+ "bun"
57
+ ];
58
+ function isPackageManager(value) {
59
+ return PACKAGE_MANAGERS.includes(value);
60
+ }
61
+ function detectPackageManager(userAgent = process.env["npm_config_user_agent"] ?? "") {
62
+ const name = userAgent.split("/")[0] ?? "";
63
+ return isPackageManager(name) ? name : "npm";
64
+ }
65
+ function firstLine(text) {
66
+ return text.split("\n").find((l) => l.trim() !== "")?.trim() ?? "unknown error";
67
+ }
68
+ async function packageManagerVersion(pm) {
69
+ try {
70
+ const { stdout } = await execFileP(pm, ["--version"]);
71
+ return stdout.trim();
72
+ } catch (e) {
73
+ throw new Error(`${pm} is not available on PATH`, { cause: e });
74
+ }
75
+ }
76
+ function rnessRunner(pm, version) {
77
+ if (pm === "pnpm") return "pnpm dlx @rness/cli";
78
+ if (pm === "bun") return "bunx @rness/cli";
79
+ if (pm === "yarn" && Number(version?.split(".")[0]) >= 2)
80
+ return "yarn dlx @rness/cli";
81
+ return "npx @rness/cli";
82
+ }
83
+ function rnessCommand(env = process.env) {
84
+ const agent = env["npm_config_user_agent"] ?? "";
85
+ if (agent === "") return "rness";
86
+ const pm = detectPackageManager(agent);
87
+ const [name, version] = (agent.split(" ")[0] ?? "").split("/");
88
+ return rnessRunner(pm, name === pm ? version ?? null : null);
89
+ }
90
+ var BARE_NPM_CODE = /^npm (error|ERR!) code \S+$/;
91
+ function installErrorLine(stderr) {
92
+ const lines = stderr.split("\n").map((l) => l.trim()).filter((l) => l !== "");
93
+ return lines.find((l) => !BARE_NPM_CODE.test(l)) ?? firstLine(stderr);
94
+ }
95
+ var FROZEN = {
96
+ npm: ["ci"],
97
+ pnpm: ["install", "--frozen-lockfile"],
98
+ yarn: ["install", "--immutable"],
99
+ bun: ["install", "--frozen-lockfile"]
100
+ };
101
+ function frozenArgs(pm) {
102
+ return [...FROZEN[pm]];
103
+ }
104
+ async function installDependencies(pm, dir, opts = {}) {
105
+ const args = opts.frozen === true ? frozenArgs(pm) : ["install"];
106
+ try {
107
+ await execFileP(pm, args, { cwd: dir, maxBuffer: 16 * 1024 * 1024 });
108
+ } catch (e) {
109
+ const err = e;
110
+ throw new Error(
111
+ `${pm} ${args.join(" ")} failed in ${dir}: ${installErrorLine(err.stderr ?? err.message)}`,
112
+ { cause: e }
113
+ );
114
+ }
115
+ }
116
+
48
117
  // src/core/auth.ts
49
118
  import { chmod, mkdir, open, readFile as readFile2, rm as rm2, stat } from "fs/promises";
50
119
  import { homedir } from "os";
@@ -298,7 +367,7 @@ async function resolveToken(deps = {}) {
298
367
  await clearAuth();
299
368
  (deps.notify ?? ((l) => process.stderr.write(`${l}
300
369
  `)))(
301
- "your GitHub login expired; run rness login"
370
+ `your GitHub login expired; run ${rnessCommand()} login`
302
371
  );
303
372
  return null;
304
373
  }
@@ -313,7 +382,7 @@ async function resolveToken(deps = {}) {
313
382
  var DEFAULT_GITHUB_API = "https://api.github.com";
314
383
  var MAX_PAGES = 50;
315
384
  var PER_PAGE = 100;
316
- var ADD_LATER = "; add repositories later with rness add <repo>";
385
+ var addLater = () => `; add repositories later with ${rnessCommand()} add <repo>`;
317
386
  var NEXT = /<[^>]*>\s*;\s*rel="next"/;
318
387
  function isRepository(value) {
319
388
  if (value === null || typeof value !== "object") return false;
@@ -357,12 +426,12 @@ function httpError(page, path) {
357
426
  return new Error("GitHub rejected the token (401)", { cause });
358
427
  if (page.status === 429 || page.status === 403 && (page.headers.get("x-ratelimit-remaining") === "0" || page.headers.has("retry-after"))) {
359
428
  return new Error(
360
- "GitHub API rate limit reached; set GITHUB_TOKEN or add repositories later with rness add",
429
+ `GitHub API rate limit reached; set GITHUB_TOKEN or add repositories later with ${rnessCommand()} add`,
361
430
  { cause }
362
431
  );
363
432
  }
364
433
  return new Error(
365
- `GitHub API answered ${page.status} for ${path}${ADD_LATER}`,
434
+ `GitHub API answered ${page.status} for ${path}${addLater()}`,
366
435
  { cause }
367
436
  );
368
437
  }
@@ -403,7 +472,7 @@ async function listRepositories(owner, options) {
403
472
  page = await getPage(url(kind, n), headers, timeoutMs);
404
473
  if (page.status === 404) {
405
474
  throw new Error(
406
- `GitHub has no organization or user named "${owner}" (or it is not visible to you)${ADD_LATER}`,
475
+ `GitHub has no organization or user named "${owner}" (or it is not visible to you)${addLater()}`,
407
476
  { cause: { status: 404, path: kind.path } }
408
477
  );
409
478
  }
@@ -599,10 +668,10 @@ async function githubProvider(apiBase) {
599
668
  import { spawn } from "child_process";
600
669
 
601
670
  // src/core/git.ts
602
- import { execFile } from "child_process";
671
+ import { execFile as execFile2 } from "child_process";
603
672
  import { dirname as dirname2, resolve } from "path";
604
- import { promisify } from "util";
605
- var execFileP = promisify(execFile);
673
+ import { promisify as promisify2 } from "util";
674
+ var execFileP2 = promisify2(execFile2);
606
675
  var ENV_HELPER = '!f() { test "$1" = get && printf "username=%s\\npassword=%s\\n" "$RNESS_GIT_USERNAME" "$RNESS_GIT_TOKEN"; }; f';
607
676
  function withCredentials(credentials) {
608
677
  if (credentials === null || credentials === void 0)
@@ -618,7 +687,7 @@ function withCredentials(credentials) {
618
687
  async function git(args, cwd, credentials) {
619
688
  const auth = withCredentials(credentials);
620
689
  try {
621
- const { stdout } = await execFileP("git", [...auth.args, ...args], {
690
+ const { stdout } = await execFileP2("git", [...auth.args, ...args], {
622
691
  ...cwd === void 0 ? {} : { cwd },
623
692
  env: {
624
693
  ...process.env,
@@ -709,7 +778,7 @@ var NOT_FOUND = [
709
778
  /repository .*not found/i,
710
779
  /does not appear to be a git repository/i
711
780
  ];
712
- function firstLine(text) {
781
+ function firstLine2(text) {
713
782
  return text.split("\n").find((l) => l.trim() !== "")?.trim() ?? "unknown error";
714
783
  }
715
784
  function classifyProbe(url, exitCode, stderr) {
@@ -717,15 +786,15 @@ function classifyProbe(url, exitCode, stderr) {
717
786
  if (AUTH.some((r) => r.test(stderr)))
718
787
  return {
719
788
  kind: "error",
720
- message: `cannot access ${url}: ${firstLine(stderr)}`
789
+ message: `cannot access ${url}: ${firstLine2(stderr)}`
721
790
  };
722
791
  if (NOT_FOUND.some((r) => r.test(stderr))) return { kind: "not-found" };
723
- return { kind: "error", message: `cannot reach ${url}: ${firstLine(stderr)}` };
792
+ return { kind: "error", message: `cannot reach ${url}: ${firstLine2(stderr)}` };
724
793
  }
725
794
  var STDERR_FLUSH_MS = 200;
726
795
  async function probeRemote(url, timeoutMs = 6e4, credentials) {
727
796
  positional(url, "repository url");
728
- return new Promise((resolve4) => {
797
+ return new Promise((resolve5) => {
729
798
  const auth = withCredentials(credentials);
730
799
  const child = spawn(
731
800
  "git",
@@ -744,7 +813,7 @@ async function probeRemote(url, timeoutMs = 6e4, credentials) {
744
813
  settled = true;
745
814
  clearTimeout(timer);
746
815
  child.stderr?.destroy();
747
- resolve4(probe);
816
+ resolve5(probe);
748
817
  };
749
818
  const timer = setTimeout(() => {
750
819
  child.kill("SIGKILL");
@@ -814,13 +883,8 @@ function readOrg(value) {
814
883
  }
815
884
  return value;
816
885
  }
817
- function resolveOrg(manifest, root) {
818
- if (manifest.org !== null) return { org: manifest.org, warning: null };
819
- const name = basename2(root);
820
- return {
821
- org: name,
822
- warning: `rness.json: no "org"; using the directory name "${name}"`
823
- };
886
+ function workspaceName(manifest, root) {
887
+ return manifest.org ?? basename2(root);
824
888
  }
825
889
  function readRepos(value) {
826
890
  const raw = value ?? {};
@@ -915,8 +979,13 @@ function parseRepoSpec(spec, org, host) {
915
979
  url = spec;
916
980
  } else {
917
981
  const parts = spec.split("/");
918
- if (parts.length === 1) url = repoUrl(host, org, spec);
919
- else if (parts.length === 2 && parts[0] !== void 0 && parts[1] !== void 0)
982
+ if (parts.length === 1) {
983
+ if (org === null)
984
+ throw new Error(
985
+ "this workspace has no GitHub organization: give <owner>/<repo> or a git URL"
986
+ );
987
+ url = repoUrl(host, org, spec);
988
+ } else if (parts.length === 2 && parts[0] !== void 0 && parts[1] !== void 0)
920
989
  url = repoUrl(host, parts[0], parts[1]);
921
990
  else
922
991
  throw new Error(
@@ -1149,7 +1218,7 @@ var GREETING = /^Hi ([^!\s]+)! You've successfully authenticated/m;
1149
1218
  function parseSshGreeting(stderr) {
1150
1219
  return GREETING.exec(stderr)?.[1] ?? null;
1151
1220
  }
1152
- function firstLine2(text) {
1221
+ function firstLine3(text) {
1153
1222
  return text.split("\n").find((l) => l.trim() !== "")?.trim() ?? null;
1154
1223
  }
1155
1224
  var STDERR_FLUSH_MS2 = 200;
@@ -1162,7 +1231,7 @@ async function detectGithubSsh(opts) {
1162
1231
  ...opts.interactive ? [] : ["-o", "BatchMode=yes"],
1163
1232
  "git@github.com"
1164
1233
  ];
1165
- return new Promise((resolve4) => {
1234
+ return new Promise((resolve5) => {
1166
1235
  const child = spawn2(opts.command ?? "ssh", args, {
1167
1236
  stdio: [opts.interactive ? "inherit" : "ignore", "ignore", "pipe"]
1168
1237
  });
@@ -1175,7 +1244,7 @@ async function detectGithubSsh(opts) {
1175
1244
  settled = true;
1176
1245
  clearTimeout(timer);
1177
1246
  child.stderr?.destroy();
1178
- resolve4(access2);
1247
+ resolve5(access2);
1179
1248
  };
1180
1249
  const timer = setTimeout(() => {
1181
1250
  child.kill("SIGKILL");
@@ -1189,7 +1258,7 @@ async function detectGithubSsh(opts) {
1189
1258
  settle(
1190
1259
  login !== null ? { ok: true, login } : {
1191
1260
  ok: false,
1192
- reason: firstLine2(stderr2) ?? `ssh exited with code ${code ?? "?"}`
1261
+ reason: firstLine3(stderr2) ?? `ssh exited with code ${code ?? "?"}`
1193
1262
  }
1194
1263
  );
1195
1264
  };
@@ -1254,7 +1323,7 @@ var INSTEAD_OF_LINES = [
1254
1323
  ' git config --global url."https://github.com/".insteadOf git@github.com:'
1255
1324
  ];
1256
1325
  function httpsFlagLine(spec) {
1257
- return `or clone this repository over HTTPS: rness add ${spec} --https`;
1326
+ return `or clone this repository over HTTPS: ${rnessCommand()} add ${spec} --https`;
1258
1327
  }
1259
1328
 
1260
1329
  // src/core/workspace.ts
@@ -1828,9 +1897,8 @@ async function syncCommand(opts, deps = {}) {
1828
1897
  try {
1829
1898
  const ws = await findWorkspace(cwd);
1830
1899
  const manifest = await loadManifest(ws.rnessDir);
1831
- const { org, warning } = resolveOrg(manifest, ws.root);
1900
+ const org = workspaceName(manifest, ws.root);
1832
1901
  if (ownUi) ui.intro(`Sync workspace ${org}`);
1833
- if (warning !== null) ui.warn(warning);
1834
1902
  if (opts.scope !== void 0 && !Object.hasOwn(manifest.scopes, opts.scope)) {
1835
1903
  process.stderr.write(`unknown scope: ${opts.scope}
1836
1904
  `);
@@ -1917,7 +1985,7 @@ async function syncCommand(opts, deps = {}) {
1917
1985
  }
1918
1986
  if (notCloned.size > 0)
1919
1987
  lines.push(
1920
- `not cloned: ${[...notCloned].join(", ")} (rness add <name>, or rness sync --all)`
1988
+ `not cloned: ${[...notCloned].join(", ")} (${rnessCommand()} add <name>, or ${rnessCommand()} sync --all)`
1921
1989
  );
1922
1990
  const scoped = new Set(
1923
1991
  Object.values(manifest.scopes).map((s) => s.path.split("/")[1])
@@ -1927,7 +1995,7 @@ async function syncCommand(opts, deps = {}) {
1927
1995
  );
1928
1996
  if (undeclared.length > 0)
1929
1997
  lines.push(
1930
- `not in rness.json: ${undeclared.join(", ")} (rness add <name> declares it)`
1998
+ `not in rness.json: ${undeclared.join(", ")} (${rnessCommand()} add <name> declares it)`
1931
1999
  );
1932
2000
  if (!await exists(join9(ws.root, "org"))) {
1933
2001
  lines.push(["skipped", "blocks (no org/ directory here)"]);
@@ -1991,7 +2059,9 @@ async function syncCommand(opts, deps = {}) {
1991
2059
  lines.push(["updated", `${t.label}`]);
1992
2060
  }
1993
2061
  if (check && differences > 0)
1994
- problems.push(`${differences} block(s) out of date \u2014 run rness sync`);
2062
+ problems.push(
2063
+ `${differences} block(s) out of date \u2014 run ${rnessCommand()} sync`
2064
+ );
1995
2065
  }
1996
2066
  return problems.length > 0 ? 1 : 0;
1997
2067
  } finally {
@@ -2035,9 +2105,7 @@ async function addCommand(spec, opts, deps = {}) {
2035
2105
  try {
2036
2106
  const ws = await findWorkspace(cwd);
2037
2107
  const manifest = await loadManifest(ws.rnessDir);
2038
- const { org, warning } = resolveOrg(manifest, ws.root);
2039
- if (warning !== null) process.stderr.write(`${warn(warning)}
2040
- `);
2108
+ const org = manifest.org;
2041
2109
  let parsed;
2042
2110
  try {
2043
2111
  parsed = parseRepoSpec(spec, org, transport.hosts.https);
@@ -2062,7 +2130,10 @@ async function addCommand(spec, opts, deps = {}) {
2062
2130
  return 2;
2063
2131
  }
2064
2132
  const ui = deps.ui ?? (interactive ? await makeUi(terminal) : { ...plainUi });
2065
- if (ownUi) ui.intro(`Add ${parsed.name} to workspace ${org}`);
2133
+ if (ownUi)
2134
+ ui.intro(
2135
+ `Add ${parsed.name} to workspace ${workspaceName(manifest, ws.root)}`
2136
+ );
2066
2137
  if (opts.host !== void 0 || opts.https === true) ui.gitIsSilent = true;
2067
2138
  let host = opts.host;
2068
2139
  if (host === void 0 && opts.ssh === true) host = transport.hosts.ssh;
@@ -2229,6 +2300,9 @@ async function contextCommand(opts) {
2229
2300
  }
2230
2301
  }
2231
2302
 
2303
+ // src/version.ts
2304
+ var VERSION = "0.6.1";
2305
+
2232
2306
  // src/core/delegate.ts
2233
2307
  import { readFile as readFile6 } from "fs/promises";
2234
2308
  import { join as join11 } from "path";
@@ -2276,61 +2350,6 @@ async function findDelegate(cwd, ownVersion) {
2276
2350
  return { entry: join11(pinned.dir, "dist", "index.js"), version, root };
2277
2351
  }
2278
2352
 
2279
- // src/core/pm.ts
2280
- import { execFile as execFile2 } from "child_process";
2281
- import { promisify as promisify2 } from "util";
2282
- var execFileP2 = promisify2(execFile2);
2283
- var PACKAGE_MANAGERS = [
2284
- "npm",
2285
- "pnpm",
2286
- "yarn",
2287
- "bun"
2288
- ];
2289
- function isPackageManager(value) {
2290
- return PACKAGE_MANAGERS.includes(value);
2291
- }
2292
- function detectPackageManager(userAgent = process.env["npm_config_user_agent"] ?? "") {
2293
- const name = userAgent.split("/")[0] ?? "";
2294
- return isPackageManager(name) ? name : "npm";
2295
- }
2296
- function firstLine3(text) {
2297
- return text.split("\n").find((l) => l.trim() !== "")?.trim() ?? "unknown error";
2298
- }
2299
- async function packageManagerVersion(pm) {
2300
- try {
2301
- const { stdout } = await execFileP2(pm, ["--version"]);
2302
- return stdout.trim();
2303
- } catch (e) {
2304
- throw new Error(`${pm} is not available on PATH`, { cause: e });
2305
- }
2306
- }
2307
- var BARE_NPM_CODE = /^npm (error|ERR!) code \S+$/;
2308
- function installErrorLine(stderr) {
2309
- const lines = stderr.split("\n").map((l) => l.trim()).filter((l) => l !== "");
2310
- return lines.find((l) => !BARE_NPM_CODE.test(l)) ?? firstLine3(stderr);
2311
- }
2312
- var FROZEN = {
2313
- npm: ["ci"],
2314
- pnpm: ["install", "--frozen-lockfile"],
2315
- yarn: ["install", "--immutable"],
2316
- bun: ["install", "--frozen-lockfile"]
2317
- };
2318
- function frozenArgs(pm) {
2319
- return [...FROZEN[pm]];
2320
- }
2321
- async function installDependencies(pm, dir, opts = {}) {
2322
- const args = opts.frozen === true ? frozenArgs(pm) : ["install"];
2323
- try {
2324
- await execFileP2(pm, args, { cwd: dir, maxBuffer: 16 * 1024 * 1024 });
2325
- } catch (e) {
2326
- const err = e;
2327
- throw new Error(
2328
- `${pm} ${args.join(" ")} failed in ${dir}: ${installErrorLine(err.stderr ?? err.message)}`,
2329
- { cause: e }
2330
- );
2331
- }
2332
- }
2333
-
2334
2353
  // src/core/scaffold.ts
2335
2354
  import { statSync } from "fs";
2336
2355
  import { dirname as dirname4, join as join12 } from "path";
@@ -2389,9 +2408,6 @@ async function copyScaffold(dest, tokens) {
2389
2408
  }
2390
2409
  }
2391
2410
 
2392
- // src/version.ts
2393
- var VERSION = "0.5.3";
2394
-
2395
2411
  // src/commands/login.ts
2396
2412
  import { spawn as spawn3 } from "child_process";
2397
2413
  import { realpath } from "fs/promises";
@@ -2469,8 +2485,8 @@ async function offerSetupGit(opts, terminal, bin, ui) {
2469
2485
  const changed = await setupGit(process.execPath, bin);
2470
2486
  ui.line(
2471
2487
  changed ? "updated" : "unchanged",
2472
- "git: rness answers for https://github.com (rness logout undoes it)",
2473
- "git now asks rness for your github.com login over HTTPS (rness logout undoes it)"
2488
+ `git: rness answers for https://github.com (${rnessCommand()} logout undoes it)`,
2489
+ `git now asks rness for your github.com login over HTTPS (${rnessCommand()} logout undoes it)`
2474
2490
  );
2475
2491
  }
2476
2492
  async function loginCommand(opts, deps = {}) {
@@ -2487,7 +2503,7 @@ async function loginCommand(opts, deps = {}) {
2487
2503
  if (stored !== null && await resolveToken() !== null) {
2488
2504
  ui.line(
2489
2505
  "logged in",
2490
- `as ${stored.login} (github.com); rness logout to switch`
2506
+ `as ${stored.login} (github.com); ${rnessCommand()} logout to switch`
2491
2507
  );
2492
2508
  await offerSetupGit(opts, terminal, bin, ui);
2493
2509
  if (own) ui.outro("Nothing to do", "idle");
@@ -2542,9 +2558,13 @@ function revokeUrl() {
2542
2558
  }
2543
2559
 
2544
2560
  // src/commands/create.ts
2545
- import { mkdir as mkdir3, mkdtemp, readdir as readdir2, rename as rename2, rm as rm4, stat as stat3 } from "fs/promises";
2561
+ import { mkdir as mkdir5, mkdtemp, rename as rename2, rm as rm5 } from "fs/promises";
2546
2562
  import { tmpdir } from "os";
2547
- import { dirname as dirname6, join as join15, relative as relative2, resolve as resolve3 } from "path";
2563
+ import { join as join17, relative as relative4, resolve as resolve4 } from "path";
2564
+
2565
+ // src/core/new-workspace.ts
2566
+ import { mkdir as mkdir3, readdir as readdir2, rm as rm4, stat as stat3 } from "fs/promises";
2567
+ import { dirname as dirname6, join as join15, relative as relative2 } from "path";
2548
2568
 
2549
2569
  // src/core/pinned.ts
2550
2570
  import { execFile as execFile4 } from "child_process";
@@ -2746,7 +2766,7 @@ async function syncPinned(root, shown, fallback) {
2746
2766
  if (installed === null) {
2747
2767
  if (fallback !== void 0) return fallback();
2748
2768
  process.stderr.write(
2749
- `@rness/cli is not installed in ${shown}/.rness; install in .rness/, then rness sync
2769
+ `@rness/cli is not installed in ${shown}/.rness; install in .rness/, then ${rnessCommand()} sync
2750
2770
  `
2751
2771
  );
2752
2772
  return 1;
@@ -2784,8 +2804,88 @@ async function syncPinned(root, shown, fallback) {
2784
2804
  }
2785
2805
  }
2786
2806
 
2807
+ // src/core/new-workspace.ts
2808
+ async function isEmptyDir(dir) {
2809
+ return (await readdir2(dir)).length === 0;
2810
+ }
2811
+ async function insideWorkspace(dir) {
2812
+ try {
2813
+ await findWorkspace(dir);
2814
+ return true;
2815
+ } catch {
2816
+ return false;
2817
+ }
2818
+ }
2819
+ async function targetProblem(cwd, root, pm) {
2820
+ const shown = relative2(cwd, root) || ".";
2821
+ if (await insideWorkspace(cwd) || await insideWorkspace(dirname6(root)))
2822
+ return "already inside an rness workspace; create runs from outside one";
2823
+ if (!await exists(root)) return null;
2824
+ if (await exists(join15(root, ".rness", "rness.json"))) {
2825
+ if (!await exists(join15(root, ".rness", "node_modules")))
2826
+ return `${shown} holds an rness workspace whose dependencies are not installed; cd ${shown}/.rness && ${pm} install, then ${rnessCommand()} sync`;
2827
+ return `${shown} already holds an rness workspace (.rness/); run ${rnessCommand()} add inside it to add repositories`;
2828
+ }
2829
+ if (!(await stat3(root)).isDirectory()) return `${shown} is not a directory`;
2830
+ if (!await isEmptyDir(root)) return `${shown} is not empty`;
2831
+ return null;
2832
+ }
2833
+ async function installContext(input) {
2834
+ const { ui, pm, rnessDir } = input;
2835
+ if (input.skip) {
2836
+ ui.line("skipped", `install with ${pm} (--skip-install)`);
2837
+ return;
2838
+ }
2839
+ const frozen = await hasLockfile(rnessDir, pm);
2840
+ await ui.step(
2841
+ { doing: `installing dependencies with ${pm}` },
2842
+ () => installDependencies(pm, rnessDir, { frozen }),
2843
+ () => ["installed", `dependencies with ${pm}`]
2844
+ );
2845
+ }
2846
+ async function buildContext(input) {
2847
+ const { ui, root, shown, pm } = input;
2848
+ const rnessDir = join15(root, ".rness");
2849
+ const pmVersion = await packageManagerVersion(pm);
2850
+ await mkdir3(root, { recursive: true });
2851
+ try {
2852
+ await copyScaffold(rnessDir, {
2853
+ version: VERSION,
2854
+ packageManager: `${pm}@${pmVersion}`
2855
+ });
2856
+ await writeManifest(rnessDir, input.manifest);
2857
+ ui.line("created", `${shown}/.rness (${input.kind})`);
2858
+ await installContext({ ui, pm, rnessDir, skip: input.skipInstall });
2859
+ return true;
2860
+ } catch (e) {
2861
+ reportError(e);
2862
+ await rm4(rnessDir, { recursive: true, force: true }).catch(() => void 0);
2863
+ if (await isEmptyDir(root).catch(() => false))
2864
+ await rm4(root, { recursive: true, force: true }).catch(() => void 0);
2865
+ process.stderr.write(
2866
+ `removed ${shown}/.rness after the failure; fix it and retry
2867
+ `
2868
+ );
2869
+ return false;
2870
+ }
2871
+ }
2872
+ async function commitContext(input) {
2873
+ const { ui, rnessDir, shown } = input;
2874
+ await init(rnessDir);
2875
+ try {
2876
+ await commitAll(rnessDir, "chore: rness workspace context");
2877
+ ui.line("committed", `${shown}/.rness`);
2878
+ } catch (e) {
2879
+ ui.warn(
2880
+ `${e instanceof Error ? e.message : String(e)} \u2014 commit ${shown}/.rness yourself`
2881
+ );
2882
+ }
2883
+ }
2884
+
2787
2885
  // src/core/sync-blocks.ts
2788
2886
  async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
2887
+ if (process.env["RNESS_NO_DELEGATE"] === "1" && inPlace !== void 0)
2888
+ return inPlace();
2789
2889
  if (!ui.session)
2790
2890
  return ui.step(
2791
2891
  { doing: "syncing the blocks", git: true },
@@ -2802,7 +2902,7 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
2802
2902
  if (result === null) {
2803
2903
  if (inPlace !== void 0) return inPlace();
2804
2904
  ui.error(
2805
- `@rness/cli is not installed in ${shown}/.rness; install in .rness/, then rness sync`
2905
+ `@rness/cli is not installed in ${shown}/.rness; install in .rness/, then ${rnessCommand()} sync`
2806
2906
  );
2807
2907
  return 1;
2808
2908
  }
@@ -2812,38 +2912,113 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
2812
2912
  return outcome.code;
2813
2913
  }
2814
2914
 
2815
- // src/commands/create.ts
2816
- async function isEmptyDir(dir) {
2817
- return (await readdir2(dir)).length === 0;
2818
- }
2819
- async function insideWorkspace(dir) {
2820
- try {
2821
- await findWorkspace(dir);
2822
- return true;
2823
- } catch {
2824
- return false;
2915
+ // src/commands/create-blank.ts
2916
+ import { mkdir as mkdir4 } from "fs/promises";
2917
+ import { join as join16, relative as relative3, resolve as resolve3 } from "path";
2918
+ var WORKSPACE_NAME = /^(?!\.{1,2}$)[A-Za-z0-9._][A-Za-z0-9._-]*$/;
2919
+ function workspaceNameError(name) {
2920
+ return `workspace name "${name}" may only contain letters, digits, '.', '_' and '-', not starting with '-'`;
2921
+ }
2922
+ async function createBlank(input) {
2923
+ const { cwd, pm, ui } = input;
2924
+ let name = input.name;
2925
+ let prompted = false;
2926
+ if (name === void 0) {
2927
+ if (!input.interactive) {
2928
+ process.stderr.write(
2929
+ "rness create --blank needs a <name> without a prompt\n"
2930
+ );
2931
+ return 2;
2932
+ }
2933
+ const p = await input.prompts();
2934
+ const answer = await p.text({
2935
+ message: "What should the workspace be called?",
2936
+ placeholder: "my-workspace",
2937
+ validate: (value) => {
2938
+ const v = value?.trim() ?? "";
2939
+ return WORKSPACE_NAME.test(v) ? void 0 : workspaceNameError(v);
2940
+ }
2941
+ });
2942
+ if (p.isCancel(answer) || typeof answer !== "string") {
2943
+ ui.cancelled();
2944
+ return 0;
2945
+ }
2946
+ name = answer.trim();
2947
+ prompted = true;
2948
+ }
2949
+ const root = resolve3(cwd, name);
2950
+ const shown = relative3(cwd, root) || ".";
2951
+ const problem = await targetProblem(cwd, root, pm);
2952
+ if (problem !== null) {
2953
+ process.stderr.write(`${problem}
2954
+ `);
2955
+ return 1;
2956
+ }
2957
+ if (!input.mayWrite) {
2958
+ process.stderr.write(
2959
+ "rness create writes files; pass --yes to run without a prompt\n"
2960
+ );
2961
+ return 2;
2825
2962
  }
2963
+ if (input.interactive && !prompted) {
2964
+ const p = await input.prompts();
2965
+ const ok = await p.confirm({
2966
+ message: `Create blank workspace ${name} in ./${shown}?`
2967
+ });
2968
+ if (p.isCancel(ok) || ok !== true) {
2969
+ ui.cancelled();
2970
+ return 0;
2971
+ }
2972
+ }
2973
+ const built = await buildContext({
2974
+ ui,
2975
+ root,
2976
+ shown,
2977
+ manifest: { contract: 1, org: null, repos: {}, scopes: {} },
2978
+ pm,
2979
+ skipInstall: input.skipInstall,
2980
+ kind: "blank workspace"
2981
+ });
2982
+ if (!built) return 1;
2983
+ await mkdir4(join16(root, "org"), { recursive: true });
2984
+ await commitContext({ ui, rnessDir: join16(root, ".rness"), shown });
2985
+ const code = await syncBlocks(
2986
+ ui,
2987
+ root,
2988
+ shown,
2989
+ () => syncCommand({ yes: true, cwd: root })
2990
+ );
2991
+ if (code !== 0) return code;
2992
+ const next = [
2993
+ `cd ${shown}`,
2994
+ "# bring a repository in: <owner>/<repo> on GitHub, or any git URL",
2995
+ `${rnessCommand()} add <owner>/<repo>`,
2996
+ '# to share it: set "org" in .rness/rness.json, then push .rness to github.com/<org>/.rness'
2997
+ ];
2998
+ if (ui.session) {
2999
+ ui.note("Next steps", next);
3000
+ ui.outro(`Workspace ${name} is ready in ${shown}/`);
3001
+ } else
3002
+ process.stdout.write(
3003
+ [
3004
+ "",
3005
+ `Workspace \`${name}\` is ready in ${shown}/.`,
3006
+ "",
3007
+ "Next:",
3008
+ ...next.map((line) => ` ${line}`),
3009
+ ""
3010
+ ].join("\n")
3011
+ );
3012
+ return 0;
2826
3013
  }
3014
+
3015
+ // src/commands/create.ts
2827
3016
  function firstLine4(text) {
2828
3017
  return text.split("\n").find((l) => l.trim() !== "")?.trim() ?? "unknown error";
2829
3018
  }
2830
3019
  function splitSpecs(list) {
2831
3020
  return list.split(",").map((s) => s.trim()).filter((s) => s !== "");
2832
3021
  }
2833
- async function targetProblem(cwd, root, pm) {
2834
- const shown = relative2(cwd, root) || ".";
2835
- if (await insideWorkspace(cwd) || await insideWorkspace(dirname6(root)))
2836
- return "already inside an rness workspace; create runs from outside one";
2837
- if (!await exists(root)) return null;
2838
- if (await exists(join15(root, ".rness", "rness.json"))) {
2839
- if (!await exists(join15(root, ".rness", "node_modules")))
2840
- return `${shown} holds an rness workspace whose dependencies are not installed; cd ${shown}/.rness && ${pm} install, then rness sync`;
2841
- return `${shown} already holds an rness workspace (.rness/); run rness add inside it to add repositories`;
2842
- }
2843
- if (!(await stat3(root)).isDirectory()) return `${shown} is not a directory`;
2844
- if (!await isEmptyDir(root)) return `${shown} is not empty`;
2845
- return null;
2846
- }
2847
3022
  function cancelled(ui) {
2848
3023
  ui.cancelled();
2849
3024
  return 0;
@@ -2887,11 +3062,11 @@ async function pickRepositories(input) {
2887
3062
  listed = listing.repositories;
2888
3063
  if (listing.owner === "user")
2889
3064
  ui.hint(
2890
- `only public repositories of ${org} are listed; add private ones later with rness add <repo>`
3065
+ `only public repositories of ${org} are listed; add private ones later with ${rnessCommand()} add <repo>`
2891
3066
  );
2892
3067
  else if (!provider.authenticated)
2893
3068
  ui.hint(
2894
- "only public repositories are listed; rness login lists the private ones you can access"
3069
+ `only public repositories are listed; ${rnessCommand()} login lists the private ones you can access`
2895
3070
  );
2896
3071
  if (listing.truncated)
2897
3072
  ui.hint(`listed the first ${MAX_PAGES * PER_PAGE} repositories of ${org}`);
@@ -2966,7 +3141,7 @@ async function cloneSelection(input) {
2966
3141
  { doing: `cloning org/${repo}`, git: true },
2967
3142
  () => clone(
2968
3143
  entry.url,
2969
- join15(input.root, "org", repo),
3144
+ join17(input.root, "org", repo),
2970
3145
  input.provider.credentialsFor(entry.url)
2971
3146
  ),
2972
3147
  () => ["cloned", `org/${repo}`]
@@ -3006,6 +3181,25 @@ async function placeContext(staged, rnessDir, url, credentials) {
3006
3181
  await clone(url, rnessDir, credentials);
3007
3182
  }
3008
3183
  }
3184
+ function blankConflict(opts) {
3185
+ if (opts.org !== void 0) return "--org";
3186
+ if (opts.repos !== void 0) return "--repos";
3187
+ if (opts.ssh === true) return "--ssh";
3188
+ if (opts.https === true) return "--https";
3189
+ if (opts.host !== void 0) return "--host";
3190
+ return null;
3191
+ }
3192
+ async function chooseStart(prompts) {
3193
+ const answer = await prompts.select({
3194
+ message: "How do you want to start?",
3195
+ options: [
3196
+ { value: "github", label: "From a GitHub organization" },
3197
+ { value: "blank", label: "Blank local workspace" }
3198
+ ]
3199
+ });
3200
+ if (prompts.isCancel(answer)) return null;
3201
+ return answer === "blank" ? "blank" : "github";
3202
+ }
3009
3203
  var OTHER = /* @__PURE__ */ Symbol("another organization");
3010
3204
  async function chooseOrganization(provider, prompts) {
3011
3205
  if (!provider.authenticated) return OTHER;
@@ -3082,6 +3276,19 @@ async function createCommand(opts, deps = {}) {
3082
3276
  process.stderr.write("--ssh and --https cannot be combined\n");
3083
3277
  return 2;
3084
3278
  }
3279
+ if (opts.blank === true) {
3280
+ const clash = blankConflict(opts);
3281
+ if (clash !== null) {
3282
+ process.stderr.write(`--blank cannot be combined with ${clash}
3283
+ `);
3284
+ return 2;
3285
+ }
3286
+ if (opts.name !== void 0 && !WORKSPACE_NAME.test(opts.name)) {
3287
+ process.stderr.write(`${workspaceNameError(opts.name)}
3288
+ `);
3289
+ return 2;
3290
+ }
3291
+ }
3085
3292
  if (opts.org !== void 0 && !ORG_NAME.test(opts.org)) {
3086
3293
  process.stderr.write(
3087
3294
  `organization name "${opts.org}" is not a valid GitHub organization name
@@ -3129,6 +3336,23 @@ async function createCommand(opts, deps = {}) {
3129
3336
  );
3130
3337
  return 1;
3131
3338
  }
3339
+ let blank = opts.blank === true;
3340
+ if (!blank && interactive && opts.org === void 0 && opts.repos === void 0) {
3341
+ const start = await chooseStart(await prompts());
3342
+ if (start === null) return cancelled(ui);
3343
+ blank = start === "blank";
3344
+ }
3345
+ if (blank)
3346
+ return await createBlank({
3347
+ name: opts.name,
3348
+ cwd,
3349
+ pm,
3350
+ interactive,
3351
+ mayWrite: opts.yes === true || terminal.isTty(),
3352
+ skipInstall: opts.skipInstall === true,
3353
+ ui,
3354
+ prompts
3355
+ });
3132
3356
  let prompted = false;
3133
3357
  let org = opts.org;
3134
3358
  if (org === void 0 && !interactive) {
@@ -3176,8 +3400,8 @@ async function createCommand(opts, deps = {}) {
3176
3400
  org = answer.trim();
3177
3401
  prompted = true;
3178
3402
  }
3179
- const root = resolve3(cwd, org);
3180
- const shown = relative2(cwd, root) || ".";
3403
+ const root = resolve4(cwd, org);
3404
+ const shown = relative4(cwd, root) || ".";
3181
3405
  const problem = await targetProblem(cwd, root, pm);
3182
3406
  if (problem !== null) {
3183
3407
  process.stderr.write(`${problem}
@@ -3218,8 +3442,8 @@ async function createCommand(opts, deps = {}) {
3218
3442
  );
3219
3443
  let catalogue = { contract: 1, org, repos: {}, scopes: {} };
3220
3444
  const staged = async () => {
3221
- staging ??= await mkdtemp(join15(tmpdir(), "rness-join-"));
3222
- return join15(staging, ".rness");
3445
+ staging ??= await mkdtemp(join17(tmpdir(), "rness-join-"));
3446
+ return join17(staging, ".rness");
3223
3447
  };
3224
3448
  if (joining) {
3225
3449
  const stagedDir = await staged();
@@ -3269,21 +3493,10 @@ async function createCommand(opts, deps = {}) {
3269
3493
  });
3270
3494
  if (p.isCancel(ok) || ok !== true) return cancelled(ui);
3271
3495
  }
3272
- const rnessDir = join15(root, ".rness");
3273
- const install = async () => {
3274
- if (opts.skipInstall === true) {
3275
- ui.line("skipped", `install with ${pm} (--skip-install)`);
3276
- return;
3277
- }
3278
- const frozen = await hasLockfile(rnessDir, pm);
3279
- await ui.step(
3280
- { doing: `installing dependencies with ${pm}` },
3281
- () => installDependencies(pm, rnessDir, { frozen }),
3282
- () => ["installed", `dependencies with ${pm}`]
3283
- );
3284
- };
3496
+ const rnessDir = join17(root, ".rness");
3497
+ const install = () => installContext({ ui, pm, rnessDir, skip: opts.skipInstall === true });
3285
3498
  if (joining) {
3286
- await mkdir3(root, { recursive: true });
3499
+ await mkdir5(root, { recursive: true });
3287
3500
  await placeContext(
3288
3501
  await staged(),
3289
3502
  rnessDir,
@@ -3293,7 +3506,7 @@ async function createCommand(opts, deps = {}) {
3293
3506
  ui.line("cloned", `${shown}/.rness (joined ${org})`);
3294
3507
  if (opts.pm === void 0) pm = await workspacePackageManager(rnessDir);
3295
3508
  await install();
3296
- await mkdir3(join15(root, "org"), { recursive: true });
3509
+ await mkdir5(join17(root, "org"), { recursive: true });
3297
3510
  const failures2 = await cloneSelection({
3298
3511
  root,
3299
3512
  rnessDir,
@@ -3316,15 +3529,18 @@ async function createCommand(opts, deps = {}) {
3316
3529
  const remaining = Object.keys(catalogue.repos).filter(
3317
3530
  (name2) => !cloned.has(name2)
3318
3531
  );
3532
+ const rness2 = rnessCommand();
3319
3533
  const next2 = [
3320
3534
  `cd ${shown}`,
3321
3535
  // The comment above its command, as the new-workspace note does: a
3322
3536
  // line that wraps in a narrow terminal is a line nobody reads.
3323
3537
  ...remaining.length > 0 ? [
3324
3538
  `# clone the catalogue repositories you did not pick: ${remaining.join(", ")}`,
3325
- "rness sync --all"
3539
+ `${rness2} sync --all`
3326
3540
  ] : [],
3327
- ...failures2.map((f) => `rness add ${f.spec} # failed: ${f.message}`)
3541
+ ...failures2.map(
3542
+ (f) => `${rness2} add ${f.spec} # failed: ${f.message}`
3543
+ )
3328
3544
  ];
3329
3545
  if (ui.session) ui.note("Next steps", next2);
3330
3546
  else process.stdout.write(`
@@ -3333,30 +3549,17 @@ ${next2.map((l) => ` ${l}`).join("\n")}
3333
3549
  ui.outro(`You joined ${org} \u2014 your workspace is in ${shown}/`);
3334
3550
  return failures2.length > 0 ? 1 : 0;
3335
3551
  }
3336
- const pmVersion = await packageManagerVersion(pm);
3337
- await mkdir3(root, { recursive: true });
3338
- try {
3339
- await copyScaffold(rnessDir, {
3340
- version: VERSION,
3341
- packageManager: `${pm}@${pmVersion}`
3342
- });
3343
- await writeManifest(rnessDir, catalogue);
3344
- ui.line("created", `${shown}/.rness (new workspace)`);
3345
- await install();
3346
- } catch (e) {
3347
- reportError(e);
3348
- await rm4(rnessDir, { recursive: true, force: true }).catch(
3349
- () => void 0
3350
- );
3351
- if (await isEmptyDir(root).catch(() => false))
3352
- await rm4(root, { recursive: true, force: true }).catch(() => void 0);
3353
- process.stderr.write(
3354
- `removed ${shown}/.rness after the failure; fix it and retry
3355
- `
3356
- );
3357
- return 1;
3358
- }
3359
- await mkdir3(join15(root, "org"), { recursive: true });
3552
+ const built = await buildContext({
3553
+ ui,
3554
+ root,
3555
+ shown,
3556
+ manifest: catalogue,
3557
+ pm,
3558
+ skipInstall: opts.skipInstall === true,
3559
+ kind: "new workspace"
3560
+ });
3561
+ if (!built) return 1;
3562
+ await mkdir5(join17(root, "org"), { recursive: true });
3360
3563
  const failures = await cloneSelection({
3361
3564
  root,
3362
3565
  rnessDir,
@@ -3367,15 +3570,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
3367
3570
  provider: gitProvider,
3368
3571
  ui
3369
3572
  });
3370
- await init(rnessDir);
3371
- try {
3372
- await commitAll(rnessDir, "chore: rness workspace context");
3373
- ui.line("committed", `${shown}/.rness`);
3374
- } catch (e) {
3375
- ui.warn(
3376
- `${e instanceof Error ? e.message : String(e)} \u2014 commit ${shown}/.rness yourself`
3377
- );
3378
- }
3573
+ await commitContext({ ui, rnessDir, shown });
3379
3574
  const code = await syncBlocks(
3380
3575
  ui,
3381
3576
  root,
@@ -3394,11 +3589,12 @@ ${next2.map((l) => ` ${l}`).join("\n")}
3394
3589
  url: contextUrl
3395
3590
  }) : false;
3396
3591
  if (published === null) return cancelled(ui);
3592
+ const rness = rnessCommand();
3397
3593
  const next = [
3398
3594
  `cd ${shown}/.rness`,
3399
3595
  // What was asked for and did not happen, as the command that retries
3400
3596
  // it — the workspace is complete otherwise.
3401
- ...failures.map((f) => `rness add ${f.spec} # failed: ${f.message}`),
3597
+ ...failures.map((f) => `${rness} add ${f.spec} # failed: ${f.message}`),
3402
3598
  ...published ? [] : [
3403
3599
  `# create the empty private repository ${org}/.rness on github.com, then:`,
3404
3600
  `git remote add origin ${contextUrl} && git push -u origin main`,
@@ -3407,7 +3603,9 @@ ${next2.map((l) => ` ${l}`).join("\n")}
3407
3603
  "# or, with the GitHub CLI (gh) installed, in one step:",
3408
3604
  `gh repo create ${org}/.rness --private --source . --push`
3409
3605
  ],
3410
- `# then, for every teammate: npm create rness ${org}`
3606
+ // How this command was launched (npm when rness was typed directly),
3607
+ // whatever --pm installed with.
3608
+ `# then, for every teammate: ${detectPackageManager()} create rness ${org}`
3411
3609
  ];
3412
3610
  if (ui.session) {
3413
3611
  ui.note("Next steps", next);
@@ -3428,7 +3626,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
3428
3626
  return reportError(e);
3429
3627
  } finally {
3430
3628
  if (staging !== void 0)
3431
- await rm4(staging, { recursive: true, force: true }).catch(() => void 0);
3629
+ await rm5(staging, { recursive: true, force: true }).catch(() => void 0);
3432
3630
  }
3433
3631
  }
3434
3632
 
@@ -3462,8 +3660,8 @@ async function logoutCommand() {
3462
3660
 
3463
3661
  // src/commands/upgrade.ts
3464
3662
  import { execFile as execFile5 } from "child_process";
3465
- import { mkdir as mkdir4, readFile as readFile9, readdir as readdir3 } from "fs/promises";
3466
- import { basename as basename5, dirname as dirname7, join as join16, relative as relative3 } from "path";
3663
+ import { mkdir as mkdir6, readFile as readFile9, readdir as readdir3 } from "fs/promises";
3664
+ import { basename as basename5, dirname as dirname7, join as join18, relative as relative5 } from "path";
3467
3665
  import { promisify as promisify5 } from "util";
3468
3666
  var execFileP5 = promisify5(execFile5);
3469
3667
  var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
@@ -3497,7 +3695,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
3497
3695
  const pin = await readPin(ws.rnessDir);
3498
3696
  if (pin === null) {
3499
3697
  process.stderr.write(
3500
- `no @rness/cli entry in ${label}/package.json; add it to devDependencies, then run rness upgrade
3698
+ `no @rness/cli entry in ${label}/package.json; add it to devDependencies, then run ${rnessCommand()} upgrade
3501
3699
  `
3502
3700
  );
3503
3701
  return 1;
@@ -3518,7 +3716,8 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
3518
3716
  );
3519
3717
  if (target === null) {
3520
3718
  process.stderr.write(
3521
- "cannot reach the registry; name the version: rness upgrade <version>\n"
3719
+ `cannot reach the registry; name the version: ${rnessCommand()} upgrade <version>
3720
+ `
3522
3721
  );
3523
3722
  return 1;
3524
3723
  }
@@ -3532,7 +3731,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
3532
3731
  const downgrading = EXACT_VERSION.test(pin.spec) && compareVersions(target, pin.spec) < 0;
3533
3732
  if (downgrading && version === void 0) {
3534
3733
  process.stderr.write(
3535
- `${label} pins @rness/cli ${pin.spec}, newer than the latest release ${target}; to downgrade: rness upgrade ${target}
3734
+ `${label} pins @rness/cli ${pin.spec}, newer than the latest release ${target}; to downgrade: ${rnessCommand()} upgrade ${target}
3536
3735
  `
3537
3736
  );
3538
3737
  return 1;
@@ -3560,15 +3759,15 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
3560
3759
  return 1;
3561
3760
  }
3562
3761
  }
3563
- const packageFile = join16(ws.rnessDir, "package.json");
3762
+ const packageFile = join18(ws.rnessDir, "package.json");
3564
3763
  const restore = /* @__PURE__ */ new Map([
3565
3764
  [packageFile, await readFile9(packageFile, "utf8")]
3566
3765
  ]);
3567
3766
  if (pin.spec !== target) await writePin(ws.rnessDir, pin.spec, target);
3568
- const workflows = join16(ws.rnessDir, ".github", "workflows");
3767
+ const workflows = join18(ws.rnessDir, ".github", "workflows");
3569
3768
  for (const name of await readdir3(workflows).catch(() => [])) {
3570
3769
  if (!/\.ya?ml$/.test(name)) continue;
3571
- const file = join16(workflows, name);
3770
+ const file = join18(workflows, name);
3572
3771
  const text = await readOrNull(file);
3573
3772
  if (text === null) continue;
3574
3773
  const shown = `.github/workflows/${name}`;
@@ -3586,14 +3785,14 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
3586
3785
  ui.warn(`${shown} names @rness/cli@${pin.spec}; update it by hand`);
3587
3786
  }
3588
3787
  }
3589
- const dependabot = join16(ws.rnessDir, ".github", "dependabot.yml");
3788
+ const dependabot = join18(ws.rnessDir, ".github", "dependabot.yml");
3590
3789
  if (await exists(dependabot)) {
3591
3790
  ui.hint(".github/dependabot.yml is already there \u2014 left as it is");
3592
3791
  } else {
3593
- await mkdir4(dirname7(dependabot), { recursive: true });
3792
+ await mkdir6(dirname7(dependabot), { recursive: true });
3594
3793
  await writeFileAtomic(
3595
3794
  dependabot,
3596
- await readFile9(join16(scaffoldDir(), ".github", "dependabot.yml"), "utf8")
3795
+ await readFile9(join18(scaffoldDir(), ".github", "dependabot.yml"), "utf8")
3597
3796
  );
3598
3797
  ui.line(
3599
3798
  "wrote",
@@ -3610,7 +3809,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
3610
3809
  for (const [file, text] of restore) await writeFileAtomic(file, text);
3611
3810
  const message = e instanceof Error ? e.message : String(e);
3612
3811
  ui.error(message);
3613
- ui.hint(`restored ${relative3(cwd, packageFile) || packageFile}`, "stderr");
3812
+ ui.hint(`restored ${relative5(cwd, packageFile) || packageFile}`, "stderr");
3614
3813
  if (/minimum.?release.?age|NO_MATURE/i.test(message))
3615
3814
  ui.hint(
3616
3815
  "pnpm refuses versions published less than 24 h ago: add '@rness/cli' under minimumReleaseAgeExclude in .rness/pnpm-workspace.yaml",
@@ -3629,7 +3828,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
3629
3828
  ui.gitIsSilent = true;
3630
3829
  const code = await syncBlocks(ui, ws.root, basename5(ws.root));
3631
3830
  if (code !== 0) return code;
3632
- const rel = relative3(cwd, ws.rnessDir) || ".";
3831
+ const rel = relative5(cwd, ws.rnessDir) || ".";
3633
3832
  const next = [
3634
3833
  `git -C ${rel} add -A && git -C ${rel} commit -m "chore: rness ${target}"`,
3635
3834
  "# teammates: git pull \u2014 the next rness command installs it"
@@ -3655,7 +3854,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
3655
3854
  }
3656
3855
 
3657
3856
  // src/core/blocks.ts
3658
- import { join as join17 } from "path";
3857
+ import { join as join19 } from "path";
3659
3858
  var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
3660
3859
  async function checkBlocks({
3661
3860
  root,
@@ -3672,13 +3871,13 @@ async function checkBlocks({
3672
3871
  warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
3673
3872
  checks
3674
3873
  });
3675
- if (!await exists(join17(root, "org"))) return derive();
3874
+ if (!await exists(join19(root, "org"))) return derive();
3676
3875
  const targets = [{ scope: null, dir: root, depth: 0, label: "AGENTS.md" }];
3677
3876
  for (const [name, entry] of Object.entries(manifest.scopes)) {
3678
3877
  const segments = entry.path.split("/");
3679
3878
  targets.push({
3680
3879
  scope: name,
3681
- dir: join17(root, ...segments),
3880
+ dir: join19(root, ...segments),
3682
3881
  depth: segments.length,
3683
3882
  label: `${entry.path}/AGENTS.md`
3684
3883
  });
@@ -3692,14 +3891,14 @@ async function checkBlocks({
3692
3891
  );
3693
3892
  continue;
3694
3893
  }
3695
- const text = await readOrNull(join17(t.dir, "AGENTS.md"));
3894
+ const text = await readOrNull(join19(t.dir, "AGENTS.md"));
3696
3895
  const lines = text === null ? [] : text.split(/\r?\n/);
3697
3896
  const found = findBlock(lines);
3698
3897
  if (found.kind === "none") {
3699
3898
  note(
3700
3899
  t.label,
3701
3900
  "no-block",
3702
- `${t.label}: no rness block yet (run rness sync)`
3901
+ `${t.label}: no rness block yet (run ${rnessCommand()} sync)`
3703
3902
  );
3704
3903
  continue;
3705
3904
  }
@@ -3721,13 +3920,17 @@ async function checkBlocks({
3721
3920
  if (isCurrentBlock(lines.slice(found.begin, found.end + 1), fresh2.hash))
3722
3921
  note(t.label, "current");
3723
3922
  else
3724
- note(t.label, "stale", `${t.label}: stale rness block (run rness sync)`);
3923
+ note(
3924
+ t.label,
3925
+ "stale",
3926
+ `${t.label}: stale rness block (run ${rnessCommand()} sync)`
3927
+ );
3725
3928
  }
3726
3929
  return derive();
3727
3930
  }
3728
3931
 
3729
3932
  // src/core/contract.ts
3730
- import { join as join18 } from "path";
3933
+ import { join as join20 } from "path";
3731
3934
  var STATUSES = {
3732
3935
  adr: ["Proposed", "Accepted", "Rejected", "Superseded"],
3733
3936
  specs: [
@@ -3746,7 +3949,7 @@ async function checkContract(rnessDir) {
3746
3949
  const problems = [];
3747
3950
  for (const name of COLLECTIONS) {
3748
3951
  const allowed = STATUSES[name];
3749
- const items = await collectMarkdown(join18(rnessDir, name));
3952
+ const items = await collectMarkdown(join20(rnessDir, name));
3750
3953
  for (const item of items) {
3751
3954
  const where = `${name}/${item.rel}`;
3752
3955
  if (SKIP.has(where)) continue;
@@ -3789,8 +3992,7 @@ async function validateCommand(opts, deps = {}) {
3789
3992
  const ws = await findWorkspace(cwd);
3790
3993
  const manifest = await loadManifest(ws.rnessDir);
3791
3994
  for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
3792
- const { org, warning } = resolveOrg(manifest, ws.root);
3793
- const warnings = warning === null ? [] : [warning];
3995
+ const org = workspaceName(manifest, ws.root);
3794
3996
  const problems = await checkContract(ws.rnessDir);
3795
3997
  const blocks = await checkBlocks({
3796
3998
  root: ws.root,
@@ -3799,12 +4001,11 @@ async function validateCommand(opts, deps = {}) {
3799
4001
  org
3800
4002
  });
3801
4003
  problems.push(...blocks.problems);
3802
- warnings.push(...blocks.warnings);
3803
4004
  const ui = await makeUi(deps.terminal ?? defaultTerminal);
3804
4005
  if (!ui.session) {
3805
4006
  for (const p of problems) process.stderr.write(`${p}
3806
4007
  `);
3807
- for (const w of warnings) process.stderr.write(`${warn(w)}
4008
+ for (const w of blocks.warnings) process.stderr.write(`${warn(w)}
3808
4009
  `);
3809
4010
  if (problems.length > 0) return 1;
3810
4011
  process.stdout.write("context ok\n");
@@ -3817,7 +4018,6 @@ async function validateCommand(opts, deps = {}) {
3817
4018
  ui.error(c.message);
3818
4019
  else ui.warn(c.message);
3819
4020
  for (const p of problems) if (!blocks.problems.includes(p)) ui.error(p);
3820
- for (const w of warnings) if (!blocks.warnings.includes(w)) ui.warn(w);
3821
4021
  ui.outro(
3822
4022
  problems.length > 0 ? "Context mismatch" : "Context OK",
3823
4023
  problems.length > 0 ? "error" : "done"
@@ -3920,8 +4120,11 @@ function buildProgram(state) {
3920
4120
  state.code = await upgradeCommand(version, opts);
3921
4121
  });
3922
4122
  program.command("create").description(
3923
- "Create a workspace for a GitHub organization, or join its existing .rness"
3924
- ).argument("[org]", "the GitHub organization, same as --org").option(
4123
+ "Create or join a GitHub organization's workspace, or a blank one with --blank"
4124
+ ).argument(
4125
+ "[name]",
4126
+ "the GitHub organization, same as --org; with --blank, the workspace directory"
4127
+ ).option(
3925
4128
  "--org <name>",
3926
4129
  "the GitHub organization, exact name (github.com/<name>)"
3927
4130
  ).option(
@@ -3933,7 +4136,16 @@ function buildProgram(state) {
3933
4136
  ).option(
3934
4137
  "--ssh",
3935
4138
  "force git@github.com: (default: SSH when it works, else HTTPS)"
3936
- ).option("--https", "force https://github.com/").option("--skip-install", "do not install .rness/ dependencies").option("-y, --yes", "do not ask for confirmation").option("--template <name>", "reserved").addOption(new Option("--host <base>").hideHelp()).addOption(new Option("--github-api <base>").hideHelp()).addOption(new Option("--cwd <dir>").hideHelp()).action(async (orgArg, opts) => {
4139
+ ).option("--https", "force https://github.com/").option(
4140
+ "--blank",
4141
+ "a local workspace with no GitHub organization: .rness/, an empty org/, the root files"
4142
+ ).option("--skip-install", "do not install .rness/ dependencies").option("-y, --yes", "do not ask for confirmation").option("--template <name>", "reserved").addOption(new Option("--host <base>").hideHelp()).addOption(new Option("--github-api <base>").hideHelp()).addOption(new Option("--cwd <dir>").hideHelp()).action(async (orgArg, opts) => {
4143
+ if (opts.blank === true) {
4144
+ state.code = await createCommand(
4145
+ orgArg === void 0 ? opts : { ...opts, name: orgArg }
4146
+ );
4147
+ return;
4148
+ }
3937
4149
  if (orgArg !== void 0 && opts.org !== void 0 && orgArg !== opts.org) {
3938
4150
  process.stderr.write(
3939
4151
  `organization given twice: "${orgArg}" and --org "${opts.org}"
@@ -4016,6 +4228,11 @@ export {
4016
4228
  exists,
4017
4229
  readOrNull,
4018
4230
  writeFileAtomic,
4231
+ PACKAGE_MANAGERS,
4232
+ isPackageManager,
4233
+ detectPackageManager,
4234
+ packageManagerVersion,
4235
+ installDependencies,
4019
4236
  GitHubOAuthProvider,
4020
4237
  githubProvider,
4021
4238
  DEFAULT_HOST,
@@ -4058,16 +4275,11 @@ export {
4058
4275
  addCommand,
4059
4276
  renderMarkdown,
4060
4277
  contextCommand,
4278
+ VERSION,
4061
4279
  findDelegate,
4062
- PACKAGE_MANAGERS,
4063
- isPackageManager,
4064
- detectPackageManager,
4065
- packageManagerVersion,
4066
- installDependencies,
4067
4280
  SCAFFOLD_FILES,
4068
4281
  scaffoldDir,
4069
4282
  copyScaffold,
4070
- VERSION,
4071
4283
  loginCommand,
4072
4284
  createCommand,
4073
4285
  logoutCommand,
@@ -4080,4 +4292,4 @@ export {
4080
4292
  OWN_COMMANDS,
4081
4293
  catchUp
4082
4294
  };
4083
- //# sourceMappingURL=chunk-CQ6YD63L.js.map
4295
+ //# sourceMappingURL=chunk-YAIZOFYV.js.map