@selleragent/sa-admin 0.3.1 → 0.4.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
@@ -10,6 +10,7 @@ const help_1 = require("./help");
10
10
  const provisioning_1 = require("./provisioning");
11
11
  const rollout_1 = require("./rollout");
12
12
  const project_1 = require("./project");
13
+ const conversations_1 = require("./conversations");
13
14
  const render_1 = require("./render");
14
15
  function flagValue(args, name) {
15
16
  const index = args.indexOf(name);
@@ -57,11 +58,16 @@ function normalizeCommandTokens(tokens) {
57
58
  }
58
59
  return tokens;
59
60
  }
61
+ function resolveProfileFlag(args) {
62
+ return (flagValue(args, '--profile') ??
63
+ flagValue(args, '--business-profile-slug') ??
64
+ flagValue(args, '--business-profile'));
65
+ }
60
66
  function resolveProfileSlug(args, manifestBusinessSlug) {
61
- return flagValue(args, '--business-profile-slug') ?? manifestBusinessSlug;
67
+ return resolveProfileFlag(args) ?? manifestBusinessSlug;
62
68
  }
63
69
  function resolveScopedBusinessSlug(args, manifestBusinessSlug, options = {}) {
64
- const explicit = flagValue(args, '--business-profile-slug');
70
+ const explicit = resolveProfileFlag(args);
65
71
  if (explicit) {
66
72
  return explicit;
67
73
  }
@@ -169,6 +175,15 @@ function summarizeWorkspaceCapabilities(accessContext, workspaceSlug) {
169
175
  canManageTeam: isSystemAdmin || roleRank(role) >= roleRank('workspace_admin')
170
176
  };
171
177
  }
178
+ function summarizeProfileCapabilities(input) {
179
+ const fallback = summarizeWorkspaceCapabilities(input.accessContext, input.workspaceSlug);
180
+ return {
181
+ ...fallback,
182
+ workspaceName: input.workspaceName ?? fallback.workspaceName,
183
+ membershipStatus: input.membershipStatus ?? fallback.membershipStatus,
184
+ role: input.role ?? fallback.role
185
+ };
186
+ }
172
187
  function describeWorkspaceAccess(input) {
173
188
  if (input.isSystemAdmin) {
174
189
  return `${input.workspaceSlug} (${input.workspaceName ?? 'unknown workspace'}): full access as system admin`;
@@ -179,6 +194,108 @@ function describeWorkspaceAccess(input) {
179
194
  }
180
195
  return `${input.workspaceSlug} (${input.workspaceName ?? 'unknown workspace'}): ${input.role ?? 'no role'} [${capabilities.join(', ')}]`;
181
196
  }
197
+ function uniqueByWorkspaceSlug(entries) {
198
+ const seen = new Set();
199
+ const result = [];
200
+ for (const entry of entries) {
201
+ if (seen.has(entry.workspaceSlug)) {
202
+ continue;
203
+ }
204
+ seen.add(entry.workspaceSlug);
205
+ result.push(entry);
206
+ }
207
+ return result;
208
+ }
209
+ async function collectAccessibleProfiles(input) {
210
+ const explicitProfile = input.explicitProfile?.trim() || null;
211
+ if (input.accessContext?.isSystemAdmin) {
212
+ const listed = await input.auth.client.businessProfiles.list({});
213
+ const profiles = listed.profiles.map((profile) => ({
214
+ workspaceSlug: profile.businessProfileSlug,
215
+ workspaceName: profile.businessProfileName,
216
+ status: profile.status,
217
+ role: null,
218
+ membershipStatus: null
219
+ }));
220
+ const filtered = explicitProfile
221
+ ? profiles.filter((profile) => profile.workspaceSlug === explicitProfile)
222
+ : profiles;
223
+ if (explicitProfile && filtered.length === 0) {
224
+ throw new Error(`Business profile ${explicitProfile} was not found.`);
225
+ }
226
+ return filtered;
227
+ }
228
+ const memberships = uniqueByWorkspaceSlug((input.accessContext?.memberships ?? []).map((membership) => ({
229
+ workspaceSlug: membership.workspaceSlug,
230
+ workspaceName: membership.workspaceName,
231
+ status: 'active',
232
+ role: membership.role,
233
+ membershipStatus: membership.status
234
+ })));
235
+ const filtered = explicitProfile
236
+ ? memberships.filter((membership) => membership.workspaceSlug === explicitProfile)
237
+ : memberships;
238
+ if (explicitProfile && filtered.length === 0) {
239
+ throw new Error(`Current account does not have access to profile ${explicitProfile}.`);
240
+ }
241
+ return filtered;
242
+ }
243
+ async function renderProfileAccessView(input) {
244
+ const auth = await (0, auth_1.resolveAuthenticatedClient)({ context: input.context });
245
+ const whoami = await (0, auth_1.resolveWhoAmI)(input.context, {
246
+ preferActive: true,
247
+ allowAnyActiveFallback: !Boolean(flagValue(input.args, '--base-url') || flagValue(input.args, '--environment'))
248
+ });
249
+ const explicitProfile = resolveProfileFlag(input.args);
250
+ const profiles = await collectAccessibleProfiles({
251
+ auth,
252
+ accessContext: whoami.accessContext,
253
+ explicitProfile
254
+ });
255
+ (0, render_1.renderOutput)({
256
+ session: whoami.session,
257
+ authenticated: whoami.accessContext?.authenticated ?? false,
258
+ operator: whoami.accessContext?.operator ?? null,
259
+ isSystemAdmin: whoami.accessContext?.isSystemAdmin ?? false,
260
+ profiles: profiles.map((profile) => summarizeProfileCapabilities({
261
+ accessContext: whoami.accessContext,
262
+ workspaceSlug: profile.workspaceSlug,
263
+ workspaceName: profile.workspaceName,
264
+ membershipStatus: profile.membershipStatus,
265
+ role: profile.role
266
+ }))
267
+ }, input.outputMode);
268
+ }
269
+ async function renderProfileMembersList(input) {
270
+ const auth = await (0, auth_1.resolveAuthenticatedClient)({ context: input.context });
271
+ const whoami = await (0, auth_1.resolveWhoAmI)(input.context, {
272
+ preferActive: true,
273
+ allowAnyActiveFallback: !Boolean(flagValue(input.args, '--base-url') || flagValue(input.args, '--environment'))
274
+ });
275
+ const profiles = await collectAccessibleProfiles({
276
+ auth,
277
+ accessContext: whoami.accessContext,
278
+ explicitProfile: resolveProfileFlag(input.args)
279
+ });
280
+ const entries = await Promise.all(profiles.map(async (profile) => ({
281
+ ...(await auth.client.auth.listWorkspaceMembers({
282
+ workspaceSlug: profile.workspaceSlug
283
+ })),
284
+ profileAccess: summarizeProfileCapabilities({
285
+ accessContext: whoami.accessContext,
286
+ workspaceSlug: profile.workspaceSlug,
287
+ workspaceName: profile.workspaceName,
288
+ membershipStatus: profile.membershipStatus,
289
+ role: profile.role
290
+ })
291
+ })));
292
+ (0, render_1.renderOutput)({
293
+ authenticated: whoami.accessContext?.authenticated ?? false,
294
+ operator: whoami.accessContext?.operator ?? null,
295
+ isSystemAdmin: whoami.accessContext?.isSystemAdmin ?? false,
296
+ profiles: entries
297
+ }, input.outputMode);
298
+ }
182
299
  function formatAuthLoginSuccess(input) {
183
300
  const lines = [
184
301
  `Authenticated as ${input.accessContext?.operator?.email ?? input.state.operatorEmail ?? 'unknown account'}`,
@@ -218,7 +335,7 @@ function parseEmployeeRole(value, fallback = 'operator') {
218
335
  throw new Error(`Unsupported role ${value}. Use employee|admin|observer.`);
219
336
  }
220
337
  function resolveAuthBusinessProfileSlug(context, args) {
221
- const explicit = flagValue(args, '--business-profile-slug') ?? flagValue(args, '--business-profile');
338
+ const explicit = resolveProfileFlag(args);
222
339
  if (explicit) {
223
340
  return explicit;
224
341
  }
@@ -226,7 +343,7 @@ function resolveAuthBusinessProfileSlug(context, args) {
226
343
  if (manifestBusinessSlug) {
227
344
  return manifestBusinessSlug;
228
345
  }
229
- throw new Error('This command requires --business-profile-slug when you are not inside a business-profile repo.');
346
+ throw new Error('This command requires --profile when you are not inside a business-profile repo.');
230
347
  }
231
348
  function parseDeployActions(args) {
232
349
  const values = [
@@ -242,12 +359,19 @@ function parseDeployActions(args) {
242
359
  }
243
360
  async function handleProfileCommand(input) {
244
361
  const { args, subcommand, context, outputMode } = input;
362
+ const repoContext = 'manifest' in context ? context : null;
245
363
  if (subcommand === 'validate') {
246
- (0, render_1.renderOutput)(await (0, project_1.validateProfileProject)(context), outputMode);
364
+ if (!repoContext) {
365
+ throw new Error('profile validate requires a business-profile repo checkout.');
366
+ }
367
+ (0, render_1.renderOutput)(await (0, project_1.validateProfileProject)(repoContext), outputMode);
247
368
  return;
248
369
  }
249
370
  if (subcommand === 'upload') {
250
- const prepared = await (0, project_1.buildProfileBundleForUpload)(context);
371
+ if (!repoContext) {
372
+ throw new Error('profile upload requires a business-profile repo checkout.');
373
+ }
374
+ const prepared = await (0, project_1.buildProfileBundleForUpload)(repoContext);
251
375
  const auth = await (0, auth_1.resolveAuthenticatedClient)({
252
376
  context
253
377
  });
@@ -262,8 +386,8 @@ async function handleProfileCommand(input) {
262
386
  (0, render_1.renderOutput)({
263
387
  ...result,
264
388
  authMode: auth.authMode,
265
- projectRoot: context.root.rootDir,
266
- manifestPath: context.root.manifestPath,
389
+ projectRoot: repoContext.root.rootDir,
390
+ manifestPath: repoContext.root.manifestPath,
267
391
  sourceRepositoryUrl: prepared.sourceRepositoryUrl,
268
392
  sourceCommitSha: prepared.sourceCommitSha,
269
393
  resolvedBaseUrl: context.baseUrl,
@@ -273,15 +397,18 @@ async function handleProfileCommand(input) {
273
397
  return;
274
398
  }
275
399
  if (subcommand === 'download') {
400
+ if (!repoContext) {
401
+ throw new Error('profile download requires a business-profile repo checkout.');
402
+ }
276
403
  const auth = await (0, auth_1.resolveAuthenticatedClient)({
277
404
  context
278
405
  });
279
- const businessProfileSlug = resolveProfileSlug(args, context.manifest.business_slug);
406
+ const businessProfileSlug = resolveProfileSlug(args, repoContext.manifest.business_slug);
280
407
  const result = await auth.client.businessProfiles.downloadBundle({
281
408
  businessProfileSlug,
282
409
  versionId: flagValue(args, '--version-id')
283
410
  });
284
- const outputDir = (0, node_path_1.resolve)(context.root.rootDir, flagValue(args, '--output-dir') ?? '.');
411
+ const outputDir = (0, node_path_1.resolve)(repoContext.root.rootDir, flagValue(args, '--output-dir') ?? '.');
285
412
  await (0, project_1.writeDownloadedProfileBundle)({
286
413
  rootDir: outputDir,
287
414
  bundle: result.bundle
@@ -295,6 +422,9 @@ async function handleProfileCommand(input) {
295
422
  return;
296
423
  }
297
424
  if (subcommand === 'activate') {
425
+ if (!repoContext) {
426
+ throw new Error('profile activate requires a business-profile repo checkout.');
427
+ }
298
428
  const specifier = flagValue(args, '--version') ?? flagValue(args, '--version-id');
299
429
  if (!specifier) {
300
430
  throw new Error('profile activate requires --version <latest|active|version-id>.');
@@ -302,7 +432,7 @@ async function handleProfileCommand(input) {
302
432
  const auth = await (0, auth_1.resolveAuthenticatedClient)({
303
433
  context
304
434
  });
305
- const businessProfileSlug = resolveProfileSlug(args, context.manifest.business_slug);
435
+ const businessProfileSlug = resolveProfileSlug(args, repoContext.manifest.business_slug);
306
436
  const resolvedVersion = await resolveVersionId({
307
437
  client: auth.client,
308
438
  businessProfileSlug,
@@ -320,10 +450,13 @@ async function handleProfileCommand(input) {
320
450
  return;
321
451
  }
322
452
  if (subcommand === 'status') {
453
+ if (!repoContext) {
454
+ throw new Error('profile status requires a business-profile repo checkout.');
455
+ }
323
456
  const auth = await (0, auth_1.resolveAuthenticatedClient)({
324
457
  context
325
458
  });
326
- const businessProfileSlug = resolveProfileSlug(args, context.manifest.business_slug);
459
+ const businessProfileSlug = resolveProfileSlug(args, repoContext.manifest.business_slug);
327
460
  const [profile, versions, members] = await Promise.all([
328
461
  auth.client.businessProfiles.get({
329
462
  businessProfileSlug
@@ -345,10 +478,13 @@ async function handleProfileCommand(input) {
345
478
  return;
346
479
  }
347
480
  if (subcommand === 'versions') {
481
+ if (!repoContext) {
482
+ throw new Error('profile versions requires a business-profile repo checkout.');
483
+ }
348
484
  const auth = await (0, auth_1.resolveAuthenticatedClient)({
349
485
  context
350
486
  });
351
- const businessProfileSlug = resolveProfileSlug(args, context.manifest.business_slug);
487
+ const businessProfileSlug = resolveProfileSlug(args, repoContext.manifest.business_slug);
352
488
  const versions = await auth.client.businessProfiles.listVersions({
353
489
  businessProfileSlug
354
490
  });
@@ -359,15 +495,11 @@ async function handleProfileCommand(input) {
359
495
  return;
360
496
  }
361
497
  if (subcommand === 'access') {
362
- const whoami = await (0, auth_1.resolveWhoAmI)(context);
363
- (0, render_1.renderOutput)({
364
- session: whoami.session,
365
- authenticated: whoami.accessContext?.authenticated ?? false,
366
- operator: whoami.accessContext?.operator ?? null,
367
- isSystemAdmin: whoami.accessContext?.isSystemAdmin ?? false,
368
- businessProfileSlug: context.manifest.business_slug,
369
- access: summarizeWorkspaceCapabilities(whoami.accessContext, context.manifest.business_slug)
370
- }, outputMode);
498
+ await renderProfileAccessView({
499
+ args,
500
+ context,
501
+ outputMode
502
+ });
371
503
  return;
372
504
  }
373
505
  if (subcommand === 'members') {
@@ -375,15 +507,20 @@ async function handleProfileCommand(input) {
375
507
  context
376
508
  });
377
509
  const memberAction = args[2] ?? null;
378
- const workspaceSlug = context.manifest.business_slug;
379
510
  if (memberAction === 'list') {
380
- (0, render_1.renderOutput)(await auth.client.auth.listWorkspaceMembers({
381
- workspaceSlug
382
- }), outputMode);
511
+ await renderProfileMembersList({
512
+ args,
513
+ context,
514
+ outputMode
515
+ });
383
516
  return;
384
517
  }
385
518
  if (memberAction === 'add') {
519
+ const workspaceSlug = resolveProfileFlag(args);
386
520
  const email = flagValue(args, '--email');
521
+ if (!workspaceSlug) {
522
+ throw new Error('profile members add requires --profile.');
523
+ }
387
524
  if (!email) {
388
525
  throw new Error('profile members add requires --email.');
389
526
  }
@@ -396,7 +533,11 @@ async function handleProfileCommand(input) {
396
533
  return;
397
534
  }
398
535
  if (memberAction === 'promote') {
536
+ const workspaceSlug = resolveProfileFlag(args);
399
537
  const email = flagValue(args, '--email');
538
+ if (!workspaceSlug) {
539
+ throw new Error('profile members promote requires --profile.');
540
+ }
400
541
  if (!email) {
401
542
  throw new Error('profile members promote requires --email.');
402
543
  }
@@ -412,6 +553,49 @@ async function handleProfileCommand(input) {
412
553
  }
413
554
  throw new Error(`Unknown profile command: ${subcommand ?? '(missing)'}.`);
414
555
  }
556
+ async function handleConversationCommand(input) {
557
+ const { args, subcommand, context, outputMode } = input;
558
+ if (subcommand === 'export') {
559
+ const conversationId = flagValue(args, '--conversation-id');
560
+ const channelKind = flagValue(args, '--channel-kind');
561
+ const threadRef = flagValue(args, '--thread-ref');
562
+ const result = await (0, conversations_1.exportConversationTranscript)({
563
+ context,
564
+ lookup: {
565
+ ...(conversationId ? { conversationId } : {}),
566
+ ...(channelKind ? { channelKind } : {}),
567
+ ...(threadRef ? { threadRef } : {})
568
+ },
569
+ outputFile: flagValue(args, '--output-file'),
570
+ transcriptId: flagValue(args, '--transcript-id'),
571
+ familyId: flagValue(args, '--family-id')
572
+ });
573
+ (0, render_1.renderOutput)(result, outputMode);
574
+ return;
575
+ }
576
+ if (subcommand === 'replay') {
577
+ const transcriptFile = flagValue(args, '--file');
578
+ if (!transcriptFile) {
579
+ throw new Error('conversation replay requires --file <canonical-transcript.yaml>.');
580
+ }
581
+ const channelKind = flagValue(args, '--channel-kind');
582
+ const result = await (0, conversations_1.replayConversationTranscript)({
583
+ context,
584
+ transcriptFile,
585
+ outputFile: flagValue(args, '--output-file'),
586
+ transcriptId: flagValue(args, '--transcript-id'),
587
+ familyId: flagValue(args, '--family-id'),
588
+ channelKind,
589
+ channelAccountId: flagValue(args, '--channel-account-id'),
590
+ threadRef: flagValue(args, '--thread-ref'),
591
+ externalUserId: flagValue(args, '--external-user-id'),
592
+ displayName: flagValue(args, '--display-name')
593
+ });
594
+ (0, render_1.renderOutput)(result, outputMode);
595
+ return;
596
+ }
597
+ throw new Error(`Unknown conversation command: ${subcommand ?? '(missing)'}.`);
598
+ }
415
599
  async function handleTelegramIntegrationsCommand(input) {
416
600
  const { args, action, context, outputMode } = input;
417
601
  if (action === 'upload') {
@@ -1220,12 +1404,16 @@ async function handleAuthCommand(input) {
1220
1404
  allowAnyActiveFallback: !explicitTarget
1221
1405
  };
1222
1406
  if (subcommand === 'login') {
1407
+ const positionalEmail = (() => {
1408
+ const candidate = args[2] ?? null;
1409
+ return candidate && !candidate.startsWith('-') ? candidate : null;
1410
+ })();
1223
1411
  const result = await (0, auth_1.loginAdminSession)({
1224
1412
  context,
1225
- email: flagValue(args, '--email'),
1413
+ email: flagValue(args, '--email') ?? positionalEmail,
1226
1414
  nextPath: flagValue(args, '--next-path'),
1227
1415
  timeoutSeconds: Number(flagValue(args, '--timeout-seconds') ?? '600'),
1228
- openBrowser: !hasFlag(args, '--no-browser'),
1416
+ openBrowser: positionalEmail || flagValue(args, '--email') ? false : !hasFlag(args, '--no-browser'),
1229
1417
  onStatus: outputMode === 'pretty'
1230
1418
  ? (message) => {
1231
1419
  process.stdout.write(`${message}\n`);
@@ -1303,9 +1491,7 @@ async function handleAuthCommand(input) {
1303
1491
  authMode: auth.authMode,
1304
1492
  ...(await auth.client.auth.listSessions({
1305
1493
  source: flagValue(args, '--source') ?? null,
1306
- businessProfileSlug: flagValue(args, '--business-profile-slug') ??
1307
- flagValue(args, '--business-profile') ??
1308
- null,
1494
+ businessProfileSlug: resolveProfileFlag(args),
1309
1495
  includeRevoked: hasFlag(args, '--include-revoked')
1310
1496
  }))
1311
1497
  }, outputMode);
@@ -1382,14 +1568,47 @@ async function main() {
1382
1568
  await handleAuthCommand({ args, subcommand, context: authContext, outputMode });
1383
1569
  return;
1384
1570
  }
1571
+ if (command === 'profile') {
1572
+ const authOnlyProfileCommand = subcommand === 'access' ||
1573
+ (subcommand === 'members' &&
1574
+ (action === 'list' || action === 'add' || action === 'promote'));
1575
+ if (authOnlyProfileCommand) {
1576
+ try {
1577
+ const repoAwareContext = await (0, context_1.resolveSaAdminContext)({
1578
+ ...(explicitCwd ? { cwd: explicitCwd } : {}),
1579
+ baseUrl: flagValue(args, '--base-url'),
1580
+ environment: flagValue(args, '--environment'),
1581
+ envFiles: flagValues(args, '--env-file')
1582
+ });
1583
+ await handleProfileCommand({ args, subcommand, context: repoAwareContext, outputMode });
1584
+ return;
1585
+ }
1586
+ catch {
1587
+ const authContext = await (0, context_1.resolveSaAdminAuthContext)({
1588
+ baseUrl: flagValue(args, '--base-url'),
1589
+ environment: flagValue(args, '--environment')
1590
+ });
1591
+ await handleProfileCommand({ args, subcommand, context: authContext, outputMode });
1592
+ return;
1593
+ }
1594
+ }
1595
+ const context = await (0, context_1.resolveSaAdminContext)({
1596
+ ...(explicitCwd ? { cwd: explicitCwd } : {}),
1597
+ baseUrl: flagValue(args, '--base-url'),
1598
+ environment: flagValue(args, '--environment'),
1599
+ envFiles: flagValues(args, '--env-file')
1600
+ });
1601
+ await handleProfileCommand({ args, subcommand, context, outputMode });
1602
+ return;
1603
+ }
1385
1604
  const context = await (0, context_1.resolveSaAdminContext)({
1386
1605
  ...(explicitCwd ? { cwd: explicitCwd } : {}),
1387
1606
  baseUrl: flagValue(args, '--base-url'),
1388
1607
  environment: flagValue(args, '--environment'),
1389
1608
  envFiles: flagValues(args, '--env-file')
1390
1609
  });
1391
- if (command === 'profile') {
1392
- await handleProfileCommand({ args, subcommand, context, outputMode });
1610
+ if (command === 'conversation') {
1611
+ await handleConversationCommand({ args, subcommand, context, outputMode });
1393
1612
  return;
1394
1613
  }
1395
1614
  if (command === 'db') {