@formigio/fazemos-cli 0.10.41 → 0.10.43

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/index.js CHANGED
@@ -7959,20 +7959,34 @@ apiKeys
7959
7959
  .requiredOption('-n, --name <name>', 'Key name (e.g., joe-orchestrator)')
7960
7960
  .requiredOption('-m, --member <id>', 'Member ID to bind the key to')
7961
7961
  .option('-d, --description <desc>', 'Description')
7962
+ .option('--scope <scope>', 'Key scope: org (cross-project, owner/admin only) or project (default, today\'s behavior). --scope org mints a key that works across all projects the member belongs to; active project is ignored.', 'project')
7962
7963
  .action(async (opts) => {
7963
7964
  try {
7964
7965
  const body = { name: opts.name, memberId: opts.member };
7965
7966
  if (opts.description)
7966
7967
  body.description = opts.description;
7968
+ // F45 — pass scope in POST body; 'project' preserves today's back-compat behavior
7969
+ body.scope = opts.scope;
7967
7970
  const data = await api('POST', '/api/api-keys', body);
7971
+ const scope = data.apiKey.scope ?? opts.scope;
7968
7972
  console.log(chalk.green(`API key created: ${opts.name}`));
7969
- console.log(` Key: ${chalk.yellow(data.rawKey)}`);
7970
- console.log(` ID: ${data.apiKey.id}`);
7973
+ console.log(` Key: ${chalk.yellow(data.rawKey)}`);
7974
+ console.log(` ID: ${data.apiKey.id}`);
7975
+ console.log(` Scope: ${scope === 'org' ? chalk.cyan('org') : scope}`);
7971
7976
  console.log('');
7972
7977
  console.log(chalk.red(' ⚠ Save this key now — it will not be shown again.'));
7973
7978
  }
7974
7979
  catch (err) {
7975
- console.error(chalk.red(err.message));
7980
+ // F45 — map specific API error codes to friendly messages
7981
+ if (err.code === 'INSUFFICIENT_ROLE') {
7982
+ console.error(chalk.red('Error: Only org owners and admins can mint org-scoped keys'));
7983
+ }
7984
+ else if (err.code === 'INVALID_SCOPE') {
7985
+ console.error(chalk.red("Error: --scope must be 'org' or 'project'"));
7986
+ }
7987
+ else {
7988
+ console.error(chalk.red(err.message));
7989
+ }
7976
7990
  process.exit(1);
7977
7991
  }
7978
7992
  });
@@ -7987,7 +8001,9 @@ apiKeys
7987
8001
  return;
7988
8002
  }
7989
8003
  for (const k of data.apiKeys) {
7990
- console.log(` ${chalk.cyan(k.name)}${k.id} (created ${k.created_at})`);
8004
+ // F45 show scope field (org or project) alongside existing fields
8005
+ const scopeLabel = k.scope === 'org' ? ` [${chalk.cyan('org-scoped')}]` : '';
8006
+ console.log(` ${chalk.cyan(k.name)}${scopeLabel} — ${k.id} (created ${k.created_at})`);
7991
8007
  }
7992
8008
  }
7993
8009
  catch (err) {
@@ -9136,11 +9152,61 @@ Types: question | task | signal | response | flag | decision | direction`)
9136
9152
  `Run from inside a Fazemos-aware workspace.`);
9137
9153
  }
9138
9154
  // Resolve recipient
9155
+ // rul_dispatch_cli_fallthrough_on_local_unresolved
9139
9156
  const resolved = resolveRole(to, localRegistry);
9140
9157
  if (!resolved) {
9141
- throw new Error(`Role "${to}" not found in local registry or any cross-workspace ref. ` +
9142
- `Check .fazemos/roles.json.`);
9158
+ // Registry-less path: recipient not found in local workspace registry
9159
+ // (e.g. cross-workspace human role like `founder` in a peer workspace).
9160
+ // Fall through to server-side DB resolution via POST /api/dispatches.
9161
+ // The server resolves the recipient via role_registrations (workspace-agnostic).
9162
+ // rul_dispatch_cli_fallthrough_on_local_unresolved
9163
+ const unresolvedDepth = computeChildDispatchDepth(process.env.FAZEMOS_ORIGINATING_DISPATCH_DEPTH);
9164
+ const unresolvedReqBody = {
9165
+ to,
9166
+ from: opts.from,
9167
+ type,
9168
+ priority: opts.priority ?? 'normal',
9169
+ body,
9170
+ thread_id: opts.thread ?? null,
9171
+ re: opts.re ? [opts.re] : null,
9172
+ expires_at: opts.expiresAt ?? null,
9173
+ source: 'cli',
9174
+ runbook_ref: opts.runbookRef ?? null,
9175
+ params: runbookParams ?? null,
9176
+ dispatch_depth: unresolvedDepth,
9177
+ };
9178
+ let unresolvedResp;
9179
+ try {
9180
+ unresolvedResp = await api('POST', '/api/dispatches', unresolvedReqBody);
9181
+ }
9182
+ catch (err) {
9183
+ // rul_dispatch_4xx_still_hard_fails (BUG53 semantics preserved)
9184
+ // Deliberate 4xx (FROM_ROLE_FORBIDDEN, depth ceiling, validation) hard-fails
9185
+ // in the registry-less path too. No local file fallback — no role.inbox
9186
+ // available (no resolved role entry). Preserves the BUG53 loud-failure contract.
9187
+ // rul_dispatch_local_file_best_effort_cross_workspace
9188
+ const status = err instanceof ApiError ? err.status : undefined;
9189
+ if (!shouldFallBackToFile(status)) {
9190
+ const code = err instanceof ApiError && err.code ? ` ${err.code}` : '';
9191
+ console.error(chalk.red(`✗ Dispatch refused (${status}${code}): ${err instanceof Error ? err.message : String(err)}`));
9192
+ console.error(chalk.gray(' Not falling back to file-only — a deliberate refusal must not be bypassed.'));
9193
+ process.exitCode = 1;
9194
+ return;
9195
+ }
9196
+ // Transient failure (network / 5xx) — no local file fallback in registry-less path.
9197
+ // rul_dispatch_local_file_best_effort_cross_workspace
9198
+ throw err;
9199
+ }
9200
+ // 2xx — server resolved the recipient. Print UX §4.4 stdout copy verbatim.
9201
+ // rul_dispatch_local_file_best_effort_cross_workspace
9202
+ console.log(chalk.green(`✓ Dispatched to ${to} (server-resolved) — id: ${unresolvedResp.id}`));
9203
+ console.log(` Local inbox mirror skipped: '${to}' is not in this workspace's registry;`);
9204
+ console.log(` the durable artifact is the DB row + Slack notification.`);
9205
+ // SKIP local inbox-file write (no role.inbox) — rul_dispatch_local_file_best_effort_cross_workspace
9206
+ // SKIP agent-mode commit+push (no relPath) — rul_dispatch_local_file_best_effort_cross_workspace
9207
+ return;
9143
9208
  }
9209
+ // ── Resolved path: existing behavior unchanged ──────────────────────────
9144
9210
  const { role, registry } = resolved;
9145
9211
  const input = {
9146
9212
  to,