@sealant/mend 0.7.1 → 0.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/main.js +78 -5
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -1450,33 +1450,106 @@ const envLoad = async (config, args) => {
|
|
|
1450
1450
|
};
|
|
1451
1451
|
const envShow = async (config, args) => {
|
|
1452
1452
|
const project = await findProject(config, takeFlagValue(args, "--project"));
|
|
1453
|
-
const [environment, secrets] = await Promise.all([
|
|
1453
|
+
const [environment, secrets, cluster] = await Promise.all([
|
|
1454
1454
|
api(config, "GET", `/projects/${project.id}/environment`),
|
|
1455
1455
|
api(config, "GET", `/projects/${project.id}/secrets`),
|
|
1456
|
+
api(config, "GET", `/projects/${project.id}/cluster-bindings`),
|
|
1456
1457
|
]);
|
|
1457
|
-
say(`${project.name} ${dim(`· configuration r${environment.revision} · secrets r${secrets.revision}`)}`);
|
|
1458
|
-
if (environment.variables.length === 0 &&
|
|
1458
|
+
say(`${project.name} ${dim(`· configuration r${environment.revision} · secrets r${secrets.revision} · cluster r${cluster.revision}`)}`);
|
|
1459
|
+
if (environment.variables.length === 0 &&
|
|
1460
|
+
secrets.secrets.length === 0 &&
|
|
1461
|
+
cluster.bindings.length === 0 &&
|
|
1462
|
+
cluster.serviceAccount === null) {
|
|
1459
1463
|
say(dim(` nothing stored — load a file: ${cobalt("mend env load")}`));
|
|
1460
1464
|
return;
|
|
1461
1465
|
}
|
|
1462
|
-
const
|
|
1466
|
+
const bindingNames = cluster.bindings.map((b) => `${b.kind}/${b.objectName}`);
|
|
1467
|
+
const width = Math.max(0, ...environment.variables.map((v) => v.name.length), ...secrets.secrets.map((s) => s.name.length), ...bindingNames.map((name) => name.length));
|
|
1463
1468
|
for (const variable of environment.variables) {
|
|
1464
1469
|
say(` ${variable.name.padEnd(width)} configuration ${dim("· plaintext")}`);
|
|
1465
1470
|
}
|
|
1466
1471
|
for (const secret of secrets.secrets) {
|
|
1467
1472
|
say(` ${secret.name.padEnd(width)} secret ${dim("· value set, never shown")}`);
|
|
1468
1473
|
}
|
|
1474
|
+
for (const name of bindingNames) {
|
|
1475
|
+
say(` ${name.padEnd(width)} cluster binding ${dim("· resolved by the platform at launch · contents unknown to Mend")}`);
|
|
1476
|
+
}
|
|
1477
|
+
if (cluster.serviceAccount !== null) {
|
|
1478
|
+
say(` ${cluster.serviceAccount.padEnd(width)} service account ${dim("· workspace pod identity · allowlisted by the operator")}`);
|
|
1479
|
+
}
|
|
1480
|
+
if (!cluster.clusterCapable && (cluster.bindings.length > 0 || cluster.serviceAccount !== null)) {
|
|
1481
|
+
say(amber(` ${cluster.bindings.length} cluster binding${cluster.bindings.length === 1 ? "" : "s"}${cluster.serviceAccount === null ? "" : " · service account set"} · local runner — cluster bindings do not resolve here`));
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
/**
|
|
1485
|
+
* Cluster bindings (`.plans/cluster-env-sources.md`): NAMES of Kubernetes Secrets/ConfigMaps the
|
|
1486
|
+
* platform resolves at launch — Mend never holds the values. Every verb works on every install
|
|
1487
|
+
* (a non-cluster install must be able to remove bindings to launch); only resolution is
|
|
1488
|
+
* Kubernetes-only, and the platform refuses launches there, readably.
|
|
1489
|
+
*/
|
|
1490
|
+
const envCluster = async (config, args) => {
|
|
1491
|
+
const explicitProject = takeFlagValue(args, "--project");
|
|
1492
|
+
const positional = args.filter((arg, i) => !arg.startsWith("--") && args[i - 1] !== "--project");
|
|
1493
|
+
const [verb, ...rest] = positional;
|
|
1494
|
+
const usage = "try: mend env cluster add secret <name> | add configmap <name> | remove <kind>/<name> | sa <name> | sa --clear";
|
|
1495
|
+
const project = await findProject(config, explicitProject);
|
|
1496
|
+
const route = `/projects/${project.id}/cluster-bindings`;
|
|
1497
|
+
const appliesLine = () => say(dim(" applies from the next workspace launch; running workspaces keep what they started with"));
|
|
1498
|
+
if (verb === "add") {
|
|
1499
|
+
const [kind, objectName] = rest;
|
|
1500
|
+
if ((kind !== "secret" && kind !== "configmap") || objectName === undefined) {
|
|
1501
|
+
return fail(`env cluster add takes a kind and an object name — ${usage}`);
|
|
1502
|
+
}
|
|
1503
|
+
const result = await api(config, "POST", route, {
|
|
1504
|
+
kind,
|
|
1505
|
+
objectName,
|
|
1506
|
+
});
|
|
1507
|
+
say(`${green("✓")} bound ${kind}/${objectName} ${dim(`· cluster r${result.revision}`)}`);
|
|
1508
|
+
say(dim(" resolved by the platform at launch · contents unknown to Mend"));
|
|
1509
|
+
return appliesLine();
|
|
1510
|
+
}
|
|
1511
|
+
if (verb === "remove") {
|
|
1512
|
+
const [ref] = rest;
|
|
1513
|
+
const [kind, objectName] = ref?.split("/", 2) ?? [];
|
|
1514
|
+
if ((kind !== "secret" && kind !== "configmap") || objectName === undefined) {
|
|
1515
|
+
return fail(`env cluster remove takes <kind>/<name>, e.g. secret/app-env — ${usage}`);
|
|
1516
|
+
}
|
|
1517
|
+
const snapshot = await api(config, "GET", route);
|
|
1518
|
+
const binding = snapshot.bindings.find((b) => b.kind === kind && b.objectName === objectName);
|
|
1519
|
+
if (binding === undefined)
|
|
1520
|
+
return fail(`${kind}/${objectName} is not bound on ${project.name}`);
|
|
1521
|
+
const result = await api(config, "DELETE", `${route}/${binding.id}`);
|
|
1522
|
+
say(`${green("✓")} removed ${kind}/${objectName} ${dim(`· cluster r${result.revision}`)}`);
|
|
1523
|
+
return appliesLine();
|
|
1524
|
+
}
|
|
1525
|
+
if (verb === "sa") {
|
|
1526
|
+
const clear = args.includes("--clear");
|
|
1527
|
+
const [name] = rest;
|
|
1528
|
+
if (!clear && name === undefined)
|
|
1529
|
+
return fail(`env cluster sa takes a name or --clear — ${usage}`);
|
|
1530
|
+
const result = await api(config, "PUT", `${route}/service-account`, { serviceAccount: clear ? null : name });
|
|
1531
|
+
say(result.serviceAccount === null
|
|
1532
|
+
? `${green("✓")} cleared the workspace service account ${dim(`· cluster r${result.revision}`)}`
|
|
1533
|
+
: `${green("✓")} service account ${result.serviceAccount} ${dim(`· cluster r${result.revision}`)}`);
|
|
1534
|
+
if (result.serviceAccount !== null) {
|
|
1535
|
+
say(amber(" the session agent holds this role's full permissions for the whole session — bind a least-privilege role intended for untrusted code; names outside the platform allowlist fail the launch"));
|
|
1536
|
+
}
|
|
1537
|
+
return appliesLine();
|
|
1538
|
+
}
|
|
1539
|
+
return fail(`unknown env cluster command "${verb ?? ""}" — ${usage}`);
|
|
1469
1540
|
};
|
|
1470
1541
|
const envCommand = async (config, args) => {
|
|
1471
1542
|
const [verb, ...rest] = args;
|
|
1472
1543
|
switch (verb) {
|
|
1473
1544
|
case "load":
|
|
1474
1545
|
return envLoad(config, rest);
|
|
1546
|
+
case "cluster":
|
|
1547
|
+
return envCluster(config, rest);
|
|
1475
1548
|
case "show":
|
|
1476
1549
|
case undefined:
|
|
1477
1550
|
return envShow(config, rest);
|
|
1478
1551
|
default:
|
|
1479
|
-
return fail(`unknown env command "${verb}" — try: mend env load | show`);
|
|
1552
|
+
return fail(`unknown env command "${verb}" — try: mend env load | show | cluster`);
|
|
1480
1553
|
}
|
|
1481
1554
|
};
|
|
1482
1555
|
const dotfilesCommand = async (config, args) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sealant/mend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "The mend CLI: adopt a repository into Mend's store, launch your coding agent in a recorded session worktree, detach and reattach from any terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|