@vm0/cli 9.177.19 → 9.178.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vm0/cli",
3
- "version": "9.177.19",
3
+ "version": "9.178.0",
4
4
  "description": "CLI application",
5
5
  "repository": {
6
6
  "type": "git",
package/zero.js CHANGED
@@ -148,7 +148,7 @@ import {
148
148
  upsertZeroOrgModelProvider,
149
149
  withErrorHandler,
150
150
  zeroAgentCustomSkillNameSchema
151
- } from "./chunk-4ILTCW7H.js";
151
+ } from "./chunk-DPEOZVW6.js";
152
152
  import {
153
153
  __toESM,
154
154
  init_esm_shims
@@ -2978,6 +2978,66 @@ How connectors work:
2978
2978
 
2979
2979
  // src/commands/zero/doctor/permission-deny.ts
2980
2980
  init_esm_shims();
2981
+
2982
+ // src/commands/zero/doctor/permission-context.ts
2983
+ init_esm_shims();
2984
+ var LEGACY_PERMISSION_GRANT_MODE = "legacy";
2985
+ function roleFromOrg(org) {
2986
+ if (org.role === "admin") return "admin";
2987
+ if (org.role === "member") return "member";
2988
+ return "unknown";
2989
+ }
2990
+ function permissionGrantModeFromOrg(org) {
2991
+ return org.permissionGrantMode ?? LEGACY_PERMISSION_GRANT_MODE;
2992
+ }
2993
+ async function resolveUserId() {
2994
+ const zeroPayload = decodeZeroTokenPayload();
2995
+ if (zeroPayload?.userId) return zeroPayload.userId;
2996
+ const token = await getToken();
2997
+ const cliPayload = decodeCliTokenPayload(token);
2998
+ return cliPayload?.userId;
2999
+ }
3000
+ async function resolvePermissionGrantMode() {
3001
+ try {
3002
+ const org = await getZeroOrg();
3003
+ return permissionGrantModeFromOrg(org);
3004
+ } catch {
3005
+ return LEGACY_PERMISSION_GRANT_MODE;
3006
+ }
3007
+ }
3008
+ async function resolvePermissionChangeContext(agentId) {
3009
+ try {
3010
+ const org = await getZeroOrg();
3011
+ const permissionGrantMode = permissionGrantModeFromOrg(org);
3012
+ if (!agentId || permissionGrantMode === "user-grants") {
3013
+ return {
3014
+ role: roleFromOrg(org),
3015
+ permissionGrantMode
3016
+ };
3017
+ }
3018
+ if (org.role === "admin") {
3019
+ return { role: "admin", permissionGrantMode };
3020
+ }
3021
+ if (org.role === "member") {
3022
+ const userId = await resolveUserId();
3023
+ if (userId) {
3024
+ const agent = await getZeroAgent(agentId);
3025
+ if (agent.ownerId === userId) {
3026
+ return { role: "owner", permissionGrantMode };
3027
+ }
3028
+ }
3029
+ return { role: "member", permissionGrantMode };
3030
+ }
3031
+ return { role: "unknown", permissionGrantMode };
3032
+ } catch {
3033
+ return {
3034
+ role: "unknown",
3035
+ permissionGrantMode: LEGACY_PERMISSION_GRANT_MODE
3036
+ };
3037
+ }
3038
+ }
3039
+
3040
+ // src/commands/zero/doctor/permission-deny.ts
2981
3041
  var permissionDenyCommand = new Command().name("permission-deny").description(
2982
3042
  "Diagnose a permission denial and find the permission that covers it"
2983
3043
  ).argument("<connector-ref>", "The connector type (e.g. github)").addOption(
@@ -3031,8 +3091,11 @@ Notes:
3031
3091
  return (ruleCount.get(current) ?? Infinity) < (ruleCount.get(narrowest) ?? Infinity) ? current : narrowest;
3032
3092
  });
3033
3093
  console.log(`This is covered by the "${permission}" permission.`);
3094
+ const permissionGrantMode = await resolvePermissionGrantMode();
3095
+ const reasonArg = permissionGrantMode === "user-grants" ? "" : ' --reason "why this is needed"';
3096
+ const actionVerb = permissionGrantMode === "user-grants" ? "allow" : "request";
3034
3097
  console.log(
3035
- `To request this permission, run: zero doctor permission-change ${connectorRef} --permission ${permission} --enable --reason "why this is needed"`
3098
+ `To ${actionVerb} this permission, run: zero doctor permission-change ${connectorRef} --permission ${permission} --enable${reasonArg}`
3036
3099
  );
3037
3100
  }
3038
3101
  )
@@ -3040,36 +3103,6 @@ Notes:
3040
3103
 
3041
3104
  // src/commands/zero/doctor/permission-change.ts
3042
3105
  init_esm_shims();
3043
-
3044
- // src/commands/zero/doctor/resolve-role.ts
3045
- init_esm_shims();
3046
- async function resolveUserId() {
3047
- const zeroPayload = decodeZeroTokenPayload();
3048
- if (zeroPayload?.userId) return zeroPayload.userId;
3049
- const token = await getToken();
3050
- const cliPayload = decodeCliTokenPayload(token);
3051
- return cliPayload?.userId;
3052
- }
3053
- async function resolveAgentRole(agentId) {
3054
- try {
3055
- const org = await getZeroOrg();
3056
- if (org.role === "admin") return "admin";
3057
- if (org.role === "member") {
3058
- const userId = await resolveUserId();
3059
- if (userId) {
3060
- const agent = await getZeroAgent(agentId);
3061
- if (agent.ownerId === userId) return "owner";
3062
- }
3063
- return "member";
3064
- }
3065
- return "unknown";
3066
- } catch (error) {
3067
- console.debug("resolveAgentRole failed, falling back to unknown:", error);
3068
- return "unknown";
3069
- }
3070
- }
3071
-
3072
- // src/commands/zero/doctor/permission-change.ts
3073
3106
  function findPermissionInConfig(ref, permissionName) {
3074
3107
  if (!isFirewallConnectorType(ref)) return false;
3075
3108
  const config = getConnectorFirewall(ref);
@@ -3082,23 +3115,15 @@ function findPermissionInConfig(ref, permissionName) {
3082
3115
  return false;
3083
3116
  }
3084
3117
  var REASON_MAX_LENGTH = 500;
3085
- async function outputPermissionChangeMessage(connectorRef, permission, action, reason) {
3086
- const { label } = CONNECTOR_TYPES[connectorRef];
3087
- const platformOrigin = await getPlatformOrigin();
3088
- const agentId = process.env.ZERO_AGENT_ID;
3089
- const role = agentId ? await resolveAgentRole(agentId) : "unknown";
3090
- const urlParams = new URLSearchParams({
3091
- ref: connectorRef,
3092
- permission,
3093
- action: action === "enable" ? "allow" : "deny"
3094
- });
3095
- if (role === "member" && reason) {
3096
- const truncated = reason.length > REASON_MAX_LENGTH ? reason.slice(0, REASON_MAX_LENGTH) : reason;
3097
- urlParams.set("reason", truncated);
3098
- }
3099
- const pagePath = agentId ? `/agents/${agentId}/permissions` : "/agents";
3100
- const url = `${platformOrigin}${pagePath}?${urlParams.toString()}`;
3101
- if (connectorRef === "slack" && permission === "chat:write" && action === "enable") {
3118
+ function addReasonParam(urlParams, role, usesUserGrants, reason) {
3119
+ if (usesUserGrants || role !== "member" || !reason) return;
3120
+ const truncated = reason.length > REASON_MAX_LENGTH ? reason.slice(0, REASON_MAX_LENGTH) : reason;
3121
+ urlParams.set("reason", truncated);
3122
+ }
3123
+ function printSensitivePermissionGuidance(connectorRef, permission, action, usesUserGrants) {
3124
+ if (action !== "enable") return;
3125
+ const approvalWording = usesUserGrants ? "Only allow this permission below" : "Only request user approval below";
3126
+ if (connectorRef === "slack" && permission === "chat:write") {
3102
3127
  console.log("");
3103
3128
  console.log(
3104
3129
  "IMPORTANT: Granting chat:write allows sending messages AS THE USER's identity, not as a bot."
@@ -3107,11 +3132,11 @@ async function outputPermissionChangeMessage(connectorRef, permission, action, r
3107
3132
  "Use `zero slack message send -c <channel> -t <text>` to send messages as the bot instead \u2014 this is the recommended approach for most use cases."
3108
3133
  );
3109
3134
  console.log(
3110
- "Only request user approval below if acting as the user is specifically required."
3135
+ `${approvalWording} if acting as the user is specifically required.`
3111
3136
  );
3112
3137
  console.log("");
3113
3138
  }
3114
- if (connectorRef === "gmail" && permission === "gmail.send" && action === "enable") {
3139
+ if (connectorRef === "gmail" && permission === "gmail.send") {
3115
3140
  console.log("");
3116
3141
  console.log(
3117
3142
  "IMPORTANT: Granting gmail.send allows the agent to send emails directly as the user."
@@ -3120,51 +3145,98 @@ async function outputPermissionChangeMessage(connectorRef, permission, action, r
3120
3145
  "Consider keeping gmail.send disabled and using gmail.compose instead \u2014 the agent can create drafts for the user to review and send manually."
3121
3146
  );
3122
3147
  console.log(
3123
- "Only request user approval below if direct sending is specifically required."
3148
+ `${approvalWording} if direct sending is specifically required.`
3124
3149
  );
3125
3150
  console.log("");
3126
3151
  }
3127
- if (role === "admin" || role === "owner") {
3152
+ }
3153
+ function printPermissionActionMessage(args) {
3154
+ if (args.usesUserGrants) {
3155
+ const grantAction = args.action === "enable" ? "allow" : "deny";
3128
3156
  console.log(
3129
- `You can ${action} the "${permission}" permission directly: [Manage ${label} permissions](${url})`
3157
+ `You can ${grantAction} the "${args.permission}" permission for your connector access: [Manage ${args.label} permissions](${args.url})`
3130
3158
  );
3131
- } else if (role === "member") {
3132
- if (!reason) {
3133
- console.log(
3134
- `IMPORTANT: Re-run with \`--reason "one sentence why this is needed"\` so the admin can review your request faster.`
3135
- );
3136
- } else if (action === "enable") {
3137
- console.log(
3138
- `Permission changes require admin approval. Request access at: [Request ${label} access](${url})`
3139
- );
3140
- } else {
3141
- console.log(
3142
- `Permission changes require admin approval. Contact an org admin to disable this permission: [View ${label} permissions](${url})`
3143
- );
3144
- }
3145
- } else {
3159
+ return;
3160
+ }
3161
+ if (args.role === "admin" || args.role === "owner") {
3162
+ console.log(
3163
+ `You can ${args.action} the "${args.permission}" permission directly: [Manage ${args.label} permissions](${args.url})`
3164
+ );
3165
+ return;
3166
+ }
3167
+ if (args.role !== "member") {
3168
+ console.log(
3169
+ `To ${args.action} the "${args.permission}" permission for ${args.label}: [Manage ${args.label} permissions](${args.url})`
3170
+ );
3171
+ return;
3172
+ }
3173
+ if (!args.reason) {
3174
+ console.log(
3175
+ `IMPORTANT: Re-run with \`--reason "one sentence why this is needed"\` so the admin can review your request faster.`
3176
+ );
3177
+ return;
3178
+ }
3179
+ if (args.action === "enable") {
3146
3180
  console.log(
3147
- `To ${action} the "${permission}" permission for ${label}: [Manage ${label} permissions](${url})`
3181
+ `Permission changes require admin approval. Request access at: [Request ${args.label} access](${args.url})`
3148
3182
  );
3183
+ return;
3149
3184
  }
3185
+ console.log(
3186
+ `Permission changes require admin approval. Contact an org admin to disable this permission: [View ${args.label} permissions](${args.url})`
3187
+ );
3150
3188
  }
3151
- var permissionChangeCommand = new Command().name("permission-change").description("Request a permission change (enable or disable)").argument("<connector-ref>", "The connector type (e.g. github)").addOption(
3189
+ async function outputPermissionChangeMessage(connectorRef, permission, action, reason) {
3190
+ const { label } = CONNECTOR_TYPES[connectorRef];
3191
+ const platformOrigin = await getPlatformOrigin();
3192
+ const agentId = process.env.ZERO_AGENT_ID;
3193
+ const context = await resolvePermissionChangeContext(agentId || void 0);
3194
+ const role = context.role;
3195
+ const permissionGrantMode = context.permissionGrantMode;
3196
+ const usesUserGrants = permissionGrantMode === "user-grants";
3197
+ const urlParams = new URLSearchParams({
3198
+ ref: connectorRef,
3199
+ permission,
3200
+ action: action === "enable" ? "allow" : "deny"
3201
+ });
3202
+ addReasonParam(urlParams, role, usesUserGrants, reason);
3203
+ const pagePath = agentId ? `/agents/${agentId}/permissions` : "/agents";
3204
+ const url = `${platformOrigin}${pagePath}?${urlParams.toString()}`;
3205
+ printSensitivePermissionGuidance(
3206
+ connectorRef,
3207
+ permission,
3208
+ action,
3209
+ usesUserGrants
3210
+ );
3211
+ printPermissionActionMessage({
3212
+ usesUserGrants,
3213
+ action,
3214
+ role,
3215
+ permission,
3216
+ label,
3217
+ url,
3218
+ reason
3219
+ });
3220
+ }
3221
+ var permissionChangeCommand = new Command().name("permission-change").description("Change or request a permission (enable or disable)").argument("<connector-ref>", "The connector type (e.g. github)").addOption(
3152
3222
  new Option(
3153
3223
  "--permission <name>",
3154
3224
  "The permission name to change"
3155
3225
  ).makeOptionMandatory()
3156
3226
  ).addOption(
3157
- new Option("--enable", "Request to enable the permission").conflicts(
3158
- "disable"
3159
- )
3227
+ new Option(
3228
+ "--enable",
3229
+ "Enable or request enabling the permission"
3230
+ ).conflicts("disable")
3160
3231
  ).addOption(
3161
- new Option("--disable", "Request to disable the permission").conflicts(
3162
- "enable"
3163
- )
3232
+ new Option(
3233
+ "--disable",
3234
+ "Disable or request disabling the permission"
3235
+ ).conflicts("enable")
3164
3236
  ).addOption(
3165
3237
  new Option(
3166
3238
  "--reason <text>",
3167
- "Brief reason why the permission is needed (max 500 chars)"
3239
+ "Brief reason for admin approval requests (max 500 chars)"
3168
3240
  )
3169
3241
  ).addHelpText(
3170
3242
  "after",
@@ -3175,7 +3247,7 @@ Examples:
3175
3247
 
3176
3248
  Notes:
3177
3249
  - Outputs a platform URL for the user to adjust the permission
3178
- - Admins can change permissions directly; members must request approval`
3250
+ - Depending on rollout state, members either request approval or manage their own permission grants`
3179
3251
  ).action(
3180
3252
  withErrorHandler(
3181
3253
  async (connectorRef, opts) => {
@@ -13229,7 +13301,7 @@ function registerZeroCommands(prog, commands) {
13229
13301
  var program = new Command();
13230
13302
  program.name("zero").description(
13231
13303
  "Zero CLI \u2014 interact with the zero platform from inside the sandbox"
13232
- ).version("9.177.19").addHelpText("after", () => {
13304
+ ).version("9.178.0").addHelpText("after", () => {
13233
13305
  return buildZeroHelpText();
13234
13306
  });
13235
13307
  if (process.argv[1]?.endsWith("zero.js") || process.argv[1]?.endsWith("zero.ts") || process.argv[1]?.endsWith("zero")) {