@openape/apes 1.14.0 → 1.16.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/cli.js CHANGED
@@ -2643,6 +2643,12 @@ if dscl . -read "/Users/$NAME" >/dev/null 2>&1; then
2643
2643
  exit 1
2644
2644
  fi
2645
2645
 
2646
+ # Phase G: agent home dirs live under /var/openape/homes/, not
2647
+ # /Users/. Pre-create the parent and chmod it world-traversable
2648
+ # so per-agent dirs can be reached by their respective uids.
2649
+ mkdir -p /var/openape/homes
2650
+ chmod 755 /var/openape/homes
2651
+
2646
2652
  # Pick the next free UID in the [200, 500) hidden service-account range.
2647
2653
  # Starts the running max at 199 so an empty range yields 200 after the
2648
2654
  # floor check; otherwise NEXT_UID = max(existing in-range UIDs) + 1.
@@ -2707,6 +2713,35 @@ mkdir -p "$HOME_DIR/Library/Logs" "$HOME_DIR/.openape/agent/tasks"
2707
2713
  function buildTroopBootstrapBlock(_troop, _name) {
2708
2714
  return "";
2709
2715
  }
2716
+ function buildPhaseGTeardownScript(input) {
2717
+ const { name, homeDir } = input;
2718
+ return `#!/bin/bash
2719
+ set -u
2720
+
2721
+ NAME=${shQuote(name)}
2722
+ HOME_DIR=${shQuote(homeDir)}
2723
+
2724
+ UID_OF=$(dscl . -read "/Users/$NAME" UniqueID 2>/dev/null | awk '/UniqueID:/ {print $2}')
2725
+
2726
+ if [ -n "$UID_OF" ]; then
2727
+ launchctl bootout "user/$UID_OF" 2>/dev/null || true
2728
+ pkill -9 -u "$UID_OF" 2>/dev/null || true
2729
+ fi
2730
+
2731
+ # Per-agent ecosystem files written by the Nest's pm2-supervisor.
2732
+ rm -rf "/var/openape/agents/$NAME"
2733
+
2734
+ # Home dir lives under /var/openape/homes/ \u2014 no FDA wall, root can
2735
+ # remove directly.
2736
+ if [ -d "$HOME_DIR" ] && [ "$HOME_DIR" != "/" ] && [ "$HOME_DIR" != "" ]; then
2737
+ rm -rf "$HOME_DIR"
2738
+ fi
2739
+
2740
+ # dscl record stays as a tombstone. Operators run
2741
+ # \`sudo sysadminctl -deleteUser $NAME\` to fully clean up if desired.
2742
+ echo "OK Phase-G teardown done for $NAME (dscl record kept as tombstone)"
2743
+ `;
2744
+ }
2710
2745
  function buildDestroyTeardownScript(input) {
2711
2746
  const { name, homeDir, adminUser } = input;
2712
2747
  return `#!/bin/bash
@@ -2846,10 +2881,12 @@ function readMacOSUser(name) {
2846
2881
  }
2847
2882
  const uidMatch = output.match(/UniqueID:\s*(\d+)/);
2848
2883
  const shellMatch = output.match(/UserShell:\s*(\S.*)$/m);
2884
+ const homeMatch = output.match(/NFSHomeDirectory:\s*(\S.*)$/m);
2849
2885
  return {
2850
2886
  name,
2851
2887
  uid: uidMatch ? Number.parseInt(uidMatch[1], 10) : null,
2852
- shell: shellMatch ? shellMatch[1].trim() : null
2888
+ shell: shellMatch ? shellMatch[1].trim() : null,
2889
+ homeDir: homeMatch ? homeMatch[1].trim() : null
2853
2890
  };
2854
2891
  }
2855
2892
  function listMacOSUserNames() {
@@ -3107,14 +3144,18 @@ var destroyAgentCommand = defineCommand21({
3107
3144
  const owned = await apiFetch("/api/my-agents", { idp });
3108
3145
  const idpAgent = owned.find((u) => u.name === name);
3109
3146
  const idpExists = idpAgent !== void 0;
3110
- const osUserExists = !args["keep-os-user"] && isDarwin() && readMacOSUser(name) !== null;
3147
+ const osUser = isDarwin() ? readMacOSUser(name) : null;
3148
+ const osUserExists = !args["keep-os-user"] && osUser !== null;
3111
3149
  if (!idpExists && !osUserExists) {
3112
3150
  consola19.info(`Nothing to destroy: no IdP agent and no OS user named "${name}".`);
3113
3151
  return;
3114
3152
  }
3115
3153
  if (!args.force) {
3116
3154
  const consequences = [];
3117
- if (osUserExists) consequences.push(`\u2022 Remove macOS user ${name} and rm -rf /Users/${name}`);
3155
+ if (osUserExists) {
3156
+ const home = osUser?.homeDir ?? `/Users/${name}`;
3157
+ consequences.push(`\u2022 Remove macOS user ${name} and rm -rf ${home}`);
3158
+ }
3118
3159
  if (idpExists) {
3119
3160
  consequences.push(args.soft ? `\u2022 Deactivate IdP agent ${idpAgent.email} (PATCH isActive=false)` : `\u2022 Hard-delete IdP agent ${idpAgent.email} and all its SSH keys`);
3120
3161
  }
@@ -3143,26 +3184,42 @@ ${consequences.join("\n")}`);
3143
3184
  consola19.info("No IdP agent to remove (skipped).");
3144
3185
  }
3145
3186
  if (osUserExists) {
3146
- const sudo = whichBinary("sudo");
3147
- if (!sudo) {
3148
- throw new CliError("`sudo` not found on PATH; required for OS teardown.");
3149
- }
3150
- const adminUser = userInfo().username;
3151
- const adminPassword = await collectAdminPassword({ adminUser });
3152
- const scratch = mkdtempSync(join3(tmpdir(), `apes-destroy-${name}-`));
3153
- const scriptPath = join3(scratch, "teardown.sh");
3154
- try {
3155
- const script = buildDestroyTeardownScript({ name, homeDir: `/Users/${name}`, adminUser });
3156
- writeFileSync2(scriptPath, script, { mode: 448 });
3157
- consola19.start("Running teardown via sudo\u2026");
3158
- execFileSync4(sudo, ["-S", "--prompt=", "--", "bash", scriptPath], {
3159
- input: `${adminPassword}
3187
+ const homeDir = osUser?.homeDir ?? `/Users/${name}`;
3188
+ const isPhaseG = homeDir.startsWith("/var/openape/homes/");
3189
+ if (isPhaseG) {
3190
+ const scratch = mkdtempSync(join3(tmpdir(), `apes-destroy-${name}-`));
3191
+ const scriptPath = join3(scratch, "teardown.sh");
3192
+ try {
3193
+ const script = buildPhaseGTeardownScript({ name, homeDir });
3194
+ writeFileSync2(scriptPath, script, { mode: 448 });
3195
+ consola19.start("Running teardown (Phase G \u2014 no admin password needed)\u2026");
3196
+ execFileSync4("apes", ["run", "--as", "root", "--wait", "--", "bash", scriptPath], { stdio: "inherit" });
3197
+ } finally {
3198
+ rmSync(scratch, { recursive: true, force: true });
3199
+ }
3200
+ consola19.info(`dscl record /Users/${name} kept as tombstone (hidden, no home). Run \`sudo sysadminctl -deleteUser ${name}\` to fully remove.`);
3201
+ } else {
3202
+ const sudo = whichBinary("sudo");
3203
+ if (!sudo) {
3204
+ throw new CliError("`sudo` not found on PATH; required for OS teardown.");
3205
+ }
3206
+ const adminUser = userInfo().username;
3207
+ const adminPassword = await collectAdminPassword({ adminUser });
3208
+ const scratch = mkdtempSync(join3(tmpdir(), `apes-destroy-${name}-`));
3209
+ const scriptPath = join3(scratch, "teardown.sh");
3210
+ try {
3211
+ const script = buildDestroyTeardownScript({ name, homeDir, adminUser });
3212
+ writeFileSync2(scriptPath, script, { mode: 448 });
3213
+ consola19.start("Running teardown via sudo\u2026");
3214
+ execFileSync4(sudo, ["-S", "--prompt=", "--", "bash", scriptPath], {
3215
+ input: `${adminPassword}
3160
3216
  ${adminPassword}
3161
3217
  `,
3162
- stdio: ["pipe", "inherit", "inherit"]
3163
- });
3164
- } finally {
3165
- rmSync(scratch, { recursive: true, force: true });
3218
+ stdio: ["pipe", "inherit", "inherit"]
3219
+ });
3220
+ } finally {
3221
+ rmSync(scratch, { recursive: true, force: true });
3222
+ }
3166
3223
  }
3167
3224
  } else if (!args["keep-os-user"] && isDarwin()) {
3168
3225
  consola19.info("No macOS user to remove (skipped).");
@@ -3215,13 +3272,17 @@ var listAgentsCommand = defineCommand22({
3215
3272
  const all = await apiFetch("/api/my-agents", { idp });
3216
3273
  const filtered = args["include-inactive"] ? all : all.filter((u) => u.isActive !== false);
3217
3274
  const osUsers = isDarwin() ? listMacOSUserNames() : /* @__PURE__ */ new Set();
3218
- const home = (name) => `/Users/${name}`;
3275
+ const homeOf = (name) => {
3276
+ if (!osUsers.has(name)) return null;
3277
+ const u = readMacOSUser(name);
3278
+ return u?.homeDir ?? `/Users/${name}`;
3279
+ };
3219
3280
  const rows = filtered.map((u) => ({
3220
3281
  name: u.name,
3221
3282
  email: u.email,
3222
3283
  isActive: u.isActive !== false,
3223
3284
  osUser: osUsers.has(u.name),
3224
- home: osUsers.has(u.name) ? home(u.name) : null
3285
+ home: homeOf(u.name)
3225
3286
  }));
3226
3287
  if (args.json) {
3227
3288
  process.stdout.write(`${JSON.stringify(rows, null, 2)}
@@ -4029,7 +4090,7 @@ and try again.`
4029
4090
  if (existing) {
4030
4091
  throw new CliError(`macOS user "${name}" already exists (uid=${existing.uid ?? "?"}). Refusing to overwrite.`);
4031
4092
  }
4032
- const homeDir = `/Users/${name}`;
4093
+ const homeDir = `/var/openape/homes/${name}`;
4033
4094
  const scratch = mkdtempSync2(join7(tmpdir2(), `apes-spawn-${name}-`));
4034
4095
  const scriptPath = join7(scratch, "setup.sh");
4035
4096
  try {
@@ -6306,7 +6367,7 @@ var mcpCommand = defineCommand48({
6306
6367
  if (transport !== "stdio" && transport !== "sse") {
6307
6368
  throw new Error('Transport must be "stdio" or "sse"');
6308
6369
  }
6309
- const { startMcpServer } = await import("./server-P2XXAHS5.js");
6370
+ const { startMcpServer } = await import("./server-FVFFPVVN.js");
6310
6371
  await startMcpServer(transport, port);
6311
6372
  }
6312
6373
  });
@@ -6944,7 +7005,7 @@ async function bestEffortGrantCount(idp) {
6944
7005
  }
6945
7006
  }
6946
7007
  async function runHealth(args) {
6947
- const version = true ? "1.14.0" : "0.0.0";
7008
+ const version = true ? "1.16.0" : "0.0.0";
6948
7009
  const auth = loadAuth();
6949
7010
  if (!auth) {
6950
7011
  throw new CliError("Not logged in. Run `apes login` first.", 1);
@@ -7217,10 +7278,10 @@ if (shellRewrite) {
7217
7278
  if (shellRewrite.action === "rewrite") {
7218
7279
  process.argv = shellRewrite.argv;
7219
7280
  } else if (shellRewrite.action === "version") {
7220
- console.log(`ape-shell ${"1.14.0"} (OpenApe DDISA shell wrapper)`);
7281
+ console.log(`ape-shell ${"1.16.0"} (OpenApe DDISA shell wrapper)`);
7221
7282
  process.exit(0);
7222
7283
  } else if (shellRewrite.action === "help") {
7223
- console.log(`ape-shell ${"1.14.0"} \u2014 OpenApe DDISA shell wrapper`);
7284
+ console.log(`ape-shell ${"1.16.0"} \u2014 OpenApe DDISA shell wrapper`);
7224
7285
  console.log("");
7225
7286
  console.log("Usage:");
7226
7287
  console.log(" ape-shell Start interactive grant-mediated REPL");
@@ -7278,7 +7339,7 @@ var configCommand = defineCommand60({
7278
7339
  var main = defineCommand60({
7279
7340
  meta: {
7280
7341
  name: "apes",
7281
- version: "1.14.0",
7342
+ version: "1.16.0",
7282
7343
  description: "Unified CLI for OpenApe"
7283
7344
  },
7284
7345
  subCommands: {
@@ -7335,7 +7396,7 @@ async function maybeRefreshAuth() {
7335
7396
  }
7336
7397
  }
7337
7398
  await maybeRefreshAuth();
7338
- await maybeWarnStaleVersion("1.14.0").catch(() => {
7399
+ await maybeWarnStaleVersion("1.16.0").catch(() => {
7339
7400
  });
7340
7401
  runMain(main).catch((err) => {
7341
7402
  if (err instanceof CliExit) {