alexandr 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alexandr",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Run the alexandr workspace runtime locally — a thin Docker front door (npx alexandr up). Pulls + boots the published kernel image.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/commands.js CHANGED
@@ -258,13 +258,46 @@ export async function destroy(flags) {
258
258
  ensureDocker();
259
259
  const inst = resolveInstance(flags);
260
260
  if (!isMaterialized(inst.dir)) fail("No alexandr instance here.", EXIT.NO_INSTANCE);
261
- const wipe = Boolean(flags.volumes || flags.data);
262
- if (wipe && !confirmed(flags)) {
261
+ let wipe = Boolean(flags.volumes || flags.data);
262
+ let unlink = Boolean(flags.unlink);
263
+
264
+ // Interactive teardown (owner feedback, 2026-08-14): a destructive verb should ASK about
265
+ // everything it could take, not hide the data behind a flag you learn about afterwards.
266
+ // On a TTY without --yes, walk the choices; flags pre-answer their question. Non-TTY
267
+ // keeps the strict behavior: destructive combinations demand --yes (exit 8), scripts
268
+ // stay explicit.
269
+ const interactive = !confirmed(flags) && Boolean(process.stdin.isTTY && process.stdout.isTTY);
270
+ if (interactive) {
271
+ if (!wipe) {
272
+ wipe = await select("Also delete the workspace data (the /data volume)?", [
273
+ { label: "Keep the data", hint: "containers go, /data stays — `alexandr up` resumes it", value: false },
274
+ { label: "Delete everything", hint: "workspace DB, apps, files — irreversible", value: true },
275
+ ]);
276
+ }
277
+ if (!unlink && isLinked(inst.dir)) {
278
+ unlink = await select("Also remove this runtime from your alexandr account?", [
279
+ { label: "Keep the account link", hint: "the workspace stays listed in your hub", value: false },
280
+ { label: "Remove it", hint: "sign-in required — the hub entry disappears", value: true },
281
+ ]);
282
+ }
283
+ const go = await select(
284
+ wipe ? "Destroy this runtime AND its data?" : "Destroy this runtime (data preserved)?",
285
+ [
286
+ { label: "Cancel", value: false },
287
+ { label: wipe ? "Yes, destroy everything" : "Yes, destroy it", value: true },
288
+ ],
289
+ );
290
+ if (!go) {
291
+ log(dim("Nothing touched."));
292
+ return;
293
+ }
294
+ } else if (wipe && !confirmed(flags)) {
263
295
  fail("`destroy --volumes` deletes /data (workspace DB, apps, blobs) irreversibly. Re-run with --yes to confirm.", EXIT.CONFIRMATION);
264
296
  }
297
+
265
298
  // Optional account-side cleanup: sign in and remove the workspace record too, so the hub
266
299
  // doesn't keep a phantom entry. Off by default — destroying the containers never needs it.
267
- if (flags.unlink && isLinked(inst.dir)) {
300
+ if (unlink && isLinked(inst.dir)) {
268
301
  if (await unlinkFromAccount(inst, flags)) ok("Removed from your account.");
269
302
  else warn("Couldn't remove it from your account — remove it from your account page instead.");
270
303
  }
package/src/link.js CHANGED
@@ -258,7 +258,14 @@ async function registryLogin(sessionToken) {
258
258
  {},
259
259
  { authorization: `Bearer ${sessionToken}` },
260
260
  );
261
- if (!r.data?.token || !r.data?.username) return;
261
+ if (!r.data?.token || !r.data?.username) {
262
+ // LOUD on purpose (the silent skip hid a dead prod credential on the first real
263
+ // install, 2026-08-14): the image is private, so no credential = the pull WILL fail.
264
+ warn(
265
+ `The control plane couldn't provide the runtime-image credential (${r.error ?? "malformed response"}) — the image pull will fail. Retry later, or contact your operator.`,
266
+ );
267
+ return;
268
+ }
262
269
  pendingPullCred = r.data;
263
270
  applyRegistryLogin();
264
271
  }