@ekanos/cli 0.9.0 → 0.11.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.
Files changed (61) hide show
  1. package/README.md +1 -0
  2. package/dist/commands/agents-edit.d.ts +49 -0
  3. package/dist/commands/agents-edit.js +385 -0
  4. package/dist/commands/agents-edit.js.map +1 -0
  5. package/dist/commands/agents.d.ts +24 -1
  6. package/dist/commands/agents.js +117 -8
  7. package/dist/commands/agents.js.map +1 -1
  8. package/dist/commands/apps.d.ts +3 -0
  9. package/dist/commands/apps.js +2 -2
  10. package/dist/commands/apps.js.map +1 -1
  11. package/dist/commands/reviewers.d.ts +21 -0
  12. package/dist/commands/reviewers.js +162 -0
  13. package/dist/commands/reviewers.js.map +1 -0
  14. package/dist/commands/runs.d.ts +42 -0
  15. package/dist/commands/runs.js +362 -0
  16. package/dist/commands/runs.js.map +1 -0
  17. package/dist/commands/skills.d.ts +60 -0
  18. package/dist/commands/skills.js +397 -0
  19. package/dist/commands/skills.js.map +1 -0
  20. package/dist/commands/team-agents-agents.d.ts +9 -0
  21. package/dist/commands/team-agents-agents.js +188 -0
  22. package/dist/commands/team-agents-agents.js.map +1 -0
  23. package/dist/commands/team-agents-mcp.d.ts +4 -0
  24. package/dist/commands/team-agents-mcp.js +218 -0
  25. package/dist/commands/team-agents-mcp.js.map +1 -0
  26. package/dist/commands/team-agents-triggers.d.ts +12 -0
  27. package/dist/commands/team-agents-triggers.js +251 -0
  28. package/dist/commands/team-agents-triggers.js.map +1 -0
  29. package/dist/commands/team-agents.d.ts +31 -0
  30. package/dist/commands/team-agents.js +71 -0
  31. package/dist/commands/team-agents.js.map +1 -0
  32. package/dist/commands/teams-onboard.d.ts +51 -0
  33. package/dist/commands/teams-onboard.js +261 -0
  34. package/dist/commands/teams-onboard.js.map +1 -0
  35. package/dist/commands/teams.d.ts +45 -0
  36. package/dist/commands/teams.js +314 -0
  37. package/dist/commands/teams.js.map +1 -0
  38. package/dist/index.d.ts +1 -1
  39. package/dist/index.js +222 -2
  40. package/dist/index.js.map +1 -1
  41. package/dist/reviewers-api.d.ts +33 -0
  42. package/dist/reviewers-api.js +81 -0
  43. package/dist/reviewers-api.js.map +1 -0
  44. package/dist/runs-api.d.ts +34 -0
  45. package/dist/runs-api.js +112 -0
  46. package/dist/runs-api.js.map +1 -0
  47. package/dist/skills-api.d.ts +28 -0
  48. package/dist/skills-api.js +104 -0
  49. package/dist/skills-api.js.map +1 -0
  50. package/dist/teams-api.d.ts +85 -0
  51. package/dist/teams-api.js +168 -0
  52. package/dist/teams-api.js.map +1 -0
  53. package/package.json +2 -2
  54. package/templates/package.json.tmpl +4 -4
  55. package/templates/skills/build-agent/SKILL.md +106 -17
  56. package/templates/skills/build-agent/references/agent-json.md +78 -2
  57. package/templates/skills/build-agent/references/rollout.md +9 -1
  58. package/templates/skills/build-agent/references/set-up-an-integration.md +134 -0
  59. package/templates/skills/build-agent/references/skills.md +78 -0
  60. package/templates/skills/build-agent/references/triggers.md +27 -6
  61. package/templates/skills/ekanos-platform/SKILL.md +115 -5
@@ -0,0 +1,261 @@
1
+ import { invalidStateError, usageError } from '../errors.js';
2
+ import { createTeam, inviteTeamMember, listTeamMembers, listTeamRoles, removeTeamMember, setTeamMemberRole, showTeamIntegration, } from '../teams-api.js';
3
+ /**
4
+ * Onboarding a team from the terminal, for the Source admin: `teams create`,
5
+ * `teams members list|invite|role|remove`, `teams roles list` and
6
+ * `teams connect`. Parsed before any session is touched (`parseOnboarding`)
7
+ * so a usage error never costs a network round-trip, then run against the
8
+ * session `runTeams` resolved.
9
+ */
10
+ export const ONBOARDING_SUBCOMMANDS = [
11
+ 'create',
12
+ 'members',
13
+ 'roles',
14
+ 'connect',
15
+ ];
16
+ const MEMBERS_VERBS = ['list', 'invite', 'role', 'remove'];
17
+ export function isOnboardingSubcommand(value) {
18
+ return ONBOARDING_SUBCOMMANDS.includes(value);
19
+ }
20
+ export function parseOnboarding(subcommand, rest, flags) {
21
+ var _a;
22
+ if (subcommand === 'create') {
23
+ const [name, ...extra] = rest;
24
+ if (!(name === null || name === void 0 ? void 0 : name.trim())) {
25
+ throw usageError('Name the team.', 'Run "ekanos teams create \\"Bank A\\" [--slug bank-a] [--owner <email>]".');
26
+ }
27
+ noExtra(extra);
28
+ return {
29
+ kind: 'create',
30
+ name: name.trim(),
31
+ slug: flags.slug === undefined ? undefined : slugOf(flags.slug, 'team'),
32
+ ownerEmail: flags.owner === undefined ? undefined : emailOf(flags.owner),
33
+ };
34
+ }
35
+ if (subcommand === 'connect') {
36
+ const [team, product, ...extra] = rest;
37
+ noExtra(extra);
38
+ return {
39
+ kind: 'connect',
40
+ team: slugOf(team, 'team'),
41
+ product: slugOf(product, 'product'),
42
+ };
43
+ }
44
+ if (subcommand === 'roles') {
45
+ const [verb, team, ...extra] = rest;
46
+ if (verb !== 'list') {
47
+ throw usageError(verb
48
+ ? `Unknown teams roles subcommand "${verb}".`
49
+ : 'teams roles needs a subcommand.', 'Run "ekanos teams roles list <team>".');
50
+ }
51
+ noExtra(extra);
52
+ return { kind: 'roles-list', team: slugOf(team, 'team') };
53
+ }
54
+ const [verb, team, email, role, ...extra] = rest;
55
+ const match = MEMBERS_VERBS.find((name) => name === verb);
56
+ if (!match) {
57
+ throw usageError(verb
58
+ ? `Unknown teams members subcommand "${verb}".`
59
+ : 'teams members needs a subcommand.', 'Run one of: ekanos teams members list <team>, ekanos teams members invite <team> <email> --role <role>, ekanos teams members role <team> <email> <role>, ekanos teams members remove <team> <email> --yes.');
60
+ }
61
+ noExtra(extra);
62
+ if (match === 'list') {
63
+ if (email !== undefined)
64
+ noExtra([email]);
65
+ return { kind: 'members-list', team: slugOf(team, 'team') };
66
+ }
67
+ const teamSlug = slugOf(team, 'team');
68
+ const address = emailOf(email);
69
+ if (match === 'invite') {
70
+ if (role !== undefined)
71
+ noExtra([role]);
72
+ return {
73
+ kind: 'members-invite',
74
+ team: teamSlug,
75
+ email: address,
76
+ role: roleOf((_a = flags.role) !== null && _a !== void 0 ? _a : 'member'),
77
+ };
78
+ }
79
+ if (match === 'role') {
80
+ const named = role !== null && role !== void 0 ? role : flags.role;
81
+ if (!named) {
82
+ throw usageError('Name the new role.', `Run "ekanos teams members role ${teamSlug} ${address} <role>"; "ekanos teams roles list ${teamSlug}" lists them.`);
83
+ }
84
+ return {
85
+ kind: 'members-role',
86
+ team: teamSlug,
87
+ email: address,
88
+ role: roleOf(named),
89
+ };
90
+ }
91
+ if (role !== undefined)
92
+ noExtra([role]);
93
+ if (!flags.yes) {
94
+ throw invalidStateError(`Removing ${address} from ${teamSlug} ends their access to the team (a pending invitation is withdrawn instead).`, `Re-run with --yes: ekanos teams members remove ${teamSlug} ${address} --yes`);
95
+ }
96
+ return { kind: 'members-remove', team: teamSlug, email: address };
97
+ }
98
+ export async function runOnboarding(ctx, client, host, source, plan) {
99
+ var _a;
100
+ var _b, _c;
101
+ switch (plan.kind) {
102
+ case 'create': {
103
+ const created = await client.call((token) => createTeam(host, token, source, {
104
+ name: plan.name,
105
+ slug: plan.slug,
106
+ ownerEmail: plan.ownerEmail,
107
+ }));
108
+ return ctx.succeed(Object.assign({ host }, created), ctx.ui.result({
109
+ title: `Created team ${created.team.slug}`,
110
+ rows: [
111
+ ['Name', created.team.name],
112
+ ['Source', created.source.slug],
113
+ [
114
+ 'Owner',
115
+ created.owner.kind === 'caller'
116
+ ? `you${created.owner.email ? ` (${created.owner.email})` : ''}`
117
+ : `${created.owner.email} — invited; ownership moves to them on accept`,
118
+ ],
119
+ ['Team home', created.teamUrl],
120
+ ],
121
+ notes: [
122
+ `Next: "ekanos teams members invite ${created.team.slug} <email> --role <role>" ("ekanos teams roles list ${created.team.slug}" lists the roles).`,
123
+ `Then "ekanos teams connect ${created.team.slug} <product>" for the link a member opens to connect an integration.`,
124
+ ],
125
+ }));
126
+ }
127
+ case 'members-list': {
128
+ const list = await client.call((token) => listTeamMembers(host, token, { source, team: plan.team }));
129
+ const rows = list.members.map((member) => { var _a; return [
130
+ (_a = member.email) !== null && _a !== void 0 ? _a : member.userId,
131
+ `${member.role}${member.primaryOwner ? ' · primary owner' : ''}${member.status === 'active' ? '' : ` · ${member.status}`}${member.name ? ` · ${member.name}` : ''}`,
132
+ ]; });
133
+ for (const invitation of list.invitations) {
134
+ rows.push([
135
+ invitation.email,
136
+ `${invitation.role} · invited, expires ${invitation.expiresAt.slice(0, 10)}`,
137
+ ]);
138
+ }
139
+ return ctx.succeed(Object.assign({ host }, list), ctx.ui.result({
140
+ title: `Members of ${list.team.slug} (${list.team.name})`,
141
+ tone: rows.length === 0 ? 'warn' : 'ok',
142
+ rows,
143
+ notes: [
144
+ `"ekanos teams members invite ${list.team.slug} <email> --role <role>" adds one.`,
145
+ ],
146
+ }));
147
+ }
148
+ case 'members-invite': {
149
+ const invited = await client.call((token) => inviteTeamMember(host, token, { source, team: plan.team }, { email: plan.email, role: plan.role }));
150
+ return ctx.succeed(Object.assign({ host }, invited), ctx.ui.result({
151
+ title: `Invited ${invited.invitation.email} to ${invited.team.slug}`,
152
+ rows: [
153
+ ['Role', invited.invitation.role],
154
+ ['Expires', invited.invitation.expiresAt.slice(0, 10)],
155
+ ],
156
+ notes: [
157
+ 'The invitation email is on its way; they join when they accept it.',
158
+ ],
159
+ }));
160
+ }
161
+ case 'members-role': {
162
+ const changed = await client.call((token) => setTeamMemberRole(host, token, { source, team: plan.team, email: plan.email }, plan.role));
163
+ return ctx.succeed(Object.assign({ host }, changed), ctx.ui.result({
164
+ title: changed.before === changed.after
165
+ ? `${changed.email} is already ${changed.after} on ${changed.team.slug}`
166
+ : `${changed.email} is now ${changed.after} on ${changed.team.slug}`,
167
+ rows: [
168
+ ['Was', (_b = changed.before) !== null && _b !== void 0 ? _b : '—'],
169
+ [
170
+ 'Applies to',
171
+ changed.kind === 'member'
172
+ ? 'their membership'
173
+ : 'their pending invitation',
174
+ ],
175
+ ],
176
+ }));
177
+ }
178
+ case 'members-remove': {
179
+ const removed = await client.call((token) => removeTeamMember(host, token, {
180
+ source,
181
+ team: plan.team,
182
+ email: plan.email,
183
+ }));
184
+ return ctx.succeed(Object.assign({ host }, removed), ctx.ui.result({
185
+ title: removed.kind === 'member'
186
+ ? `Removed ${removed.email} from ${removed.team.slug}`
187
+ : `Withdrew ${removed.email}'s invitation to ${removed.team.slug}`,
188
+ rows: [['Role was', (_c = removed.before) !== null && _c !== void 0 ? _c : '—']],
189
+ }));
190
+ }
191
+ case 'roles-list': {
192
+ const roles = await client.call((token) => listTeamRoles(host, token, { source, team: plan.team }));
193
+ return ctx.succeed(Object.assign({ host }, roles), ctx.ui.result({
194
+ title: `Roles on ${roles.team.slug}`,
195
+ rows: roles.roles.map((role) => [
196
+ `${role.name} (${role.displayName})`,
197
+ role.permissions.length > 0 ? role.permissions : ['no permissions'],
198
+ ]),
199
+ notes: [
200
+ 'Lower roles are listed last. Give one with "ekanos teams members role <team> <email> <role>".',
201
+ ],
202
+ }));
203
+ }
204
+ case 'connect': {
205
+ const status = await client.call((token) => showTeamIntegration(host, token, {
206
+ source,
207
+ team: plan.team,
208
+ product: plan.product,
209
+ }));
210
+ if (!status.enabled) {
211
+ throw invalidStateError(`"${plan.product}" is not enabled on ${status.source.slug}.`, '"ekanos catalog" lists what this Source has; Ekanos enables a product for a Source.');
212
+ }
213
+ const connected = status.activation !== null;
214
+ return ctx.succeed(Object.assign({ host }, status), ctx.ui.result({
215
+ title: connected
216
+ ? `${status.team.slug} has connected ${status.product.name}`
217
+ : `Connect link for ${status.team.slug} · ${status.product.name}`,
218
+ tone: connected ? 'ok' : 'warn',
219
+ rows: [
220
+ ['Open', status.connectUrl],
221
+ [
222
+ 'Status',
223
+ connected
224
+ ? `connected${((_a = status.activation) === null || _a === void 0 ? void 0 : _a.activatedAt) ? ` ${status.activation.activatedAt.slice(0, 10)}` : ''}`
225
+ : 'not connected',
226
+ ],
227
+ ],
228
+ notes: [
229
+ 'A team member signs in there and clicks Connect: sign-in happens in their browser, never in a terminal.',
230
+ `"ekanos teams show ${status.team.slug} ${status.product.slug}" shows the full setup checklist.`,
231
+ ],
232
+ }));
233
+ }
234
+ }
235
+ }
236
+ function noExtra(extra) {
237
+ if (extra.length > 0) {
238
+ throw usageError(`Unexpected argument "${extra[0]}".`, 'See "ekanos teams --help".');
239
+ }
240
+ }
241
+ function slugOf(value, what) {
242
+ const slug = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();
243
+ if (slug && /^[a-z0-9][a-z0-9-]*$/.test(slug))
244
+ return slug;
245
+ throw usageError(value ? `"${value}" is not a ${what} slug.` : `Name the ${what}.`, what === 'team'
246
+ ? '"ekanos teams list" lists the teams, e.g. "ekanos teams members list bank-a".'
247
+ : '"ekanos catalog" lists the products, e.g. "ekanos teams connect bank-a zendesk".');
248
+ }
249
+ function emailOf(value) {
250
+ const email = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();
251
+ if (email && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
252
+ return email;
253
+ throw usageError(value ? `"${value}" is not an email address.` : 'Name the person by email.', 'e.g. "ekanos teams members invite bank-a ana@bank-a.com --role member".');
254
+ }
255
+ function roleOf(value) {
256
+ const role = value.trim().toLowerCase();
257
+ if (/^[a-z][a-z0-9_-]{0,49}$/.test(role))
258
+ return role;
259
+ throw usageError(`"${value}" is not a role name.`, '"ekanos teams roles list <team>" lists the roles this Source uses.');
260
+ }
261
+ //# sourceMappingURL=teams-onboard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-onboard.js","sourceRoot":"","sources":["../../src/commands/teams-onboard.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CliContext } from '../context';\nimport { invalidStateError, usageError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport {\n createTeam,\n inviteTeamMember,\n listTeamMembers,\n listTeamRoles,\n removeTeamMember,\n setTeamMemberRole,\n showTeamIntegration,\n} from '../teams-api';\nimport type { TeamsClient } from './teams';\n\n/**\n * Onboarding a team from the terminal, for the Source admin: `teams create`,\n * `teams members list|invite|role|remove`, `teams roles list` and\n * `teams connect`. Parsed before any session is touched (`parseOnboarding`)\n * so a usage error never costs a network round-trip, then run against the\n * session `runTeams` resolved.\n */\n\nexport const ONBOARDING_SUBCOMMANDS = [\n 'create',\n 'members',\n 'roles',\n 'connect',\n] as const;\nexport type OnboardingSubcommand = (typeof ONBOARDING_SUBCOMMANDS)[number];\n\nexport interface OnboardingFlags {\n slug?: string;\n owner?: string;\n role?: string;\n yes: boolean;\n}\n\nexport type OnboardingPlan =\n | { kind: 'create'; name: string; slug?: string; ownerEmail?: string }\n | { kind: 'members-list'; team: string }\n | { kind: 'members-invite'; team: string; email: string; role: string }\n | { kind: 'members-role'; team: string; email: string; role: string }\n | { kind: 'members-remove'; team: string; email: string }\n | { kind: 'roles-list'; team: string }\n | { kind: 'connect'; team: string; product: string };\n\nconst MEMBERS_VERBS = ['list', 'invite', 'role', 'remove'] as const;\n\nexport function isOnboardingSubcommand(\n value: string,\n): value is OnboardingSubcommand {\n return (ONBOARDING_SUBCOMMANDS as readonly string[]).includes(value);\n}\n\nexport function parseOnboarding(\n subcommand: OnboardingSubcommand,\n rest: readonly string[],\n flags: OnboardingFlags,\n): OnboardingPlan {\n if (subcommand === 'create') {\n const [name, ...extra] = rest;\n if (!name?.trim()) {\n throw usageError(\n 'Name the team.',\n 'Run \"ekanos teams create \\\\\"Bank A\\\\\" [--slug bank-a] [--owner <email>]\".',\n );\n }\n noExtra(extra);\n return {\n kind: 'create',\n name: name.trim(),\n slug: flags.slug === undefined ? undefined : slugOf(flags.slug, 'team'),\n ownerEmail: flags.owner === undefined ? undefined : emailOf(flags.owner),\n };\n }\n\n if (subcommand === 'connect') {\n const [team, product, ...extra] = rest;\n noExtra(extra);\n return {\n kind: 'connect',\n team: slugOf(team, 'team'),\n product: slugOf(product, 'product'),\n };\n }\n\n if (subcommand === 'roles') {\n const [verb, team, ...extra] = rest;\n if (verb !== 'list') {\n throw usageError(\n verb\n ? `Unknown teams roles subcommand \"${verb}\".`\n : 'teams roles needs a subcommand.',\n 'Run \"ekanos teams roles list <team>\".',\n );\n }\n noExtra(extra);\n return { kind: 'roles-list', team: slugOf(team, 'team') };\n }\n\n const [verb, team, email, role, ...extra] = rest;\n const match = MEMBERS_VERBS.find((name) => name === verb);\n if (!match) {\n throw usageError(\n verb\n ? `Unknown teams members subcommand \"${verb}\".`\n : 'teams members needs a subcommand.',\n 'Run one of: ekanos teams members list <team>, ekanos teams members invite <team> <email> --role <role>, ekanos teams members role <team> <email> <role>, ekanos teams members remove <team> <email> --yes.',\n );\n }\n noExtra(extra);\n\n if (match === 'list') {\n if (email !== undefined) noExtra([email]);\n return { kind: 'members-list', team: slugOf(team, 'team') };\n }\n\n const teamSlug = slugOf(team, 'team');\n const address = emailOf(email);\n\n if (match === 'invite') {\n if (role !== undefined) noExtra([role]);\n return {\n kind: 'members-invite',\n team: teamSlug,\n email: address,\n role: roleOf(flags.role ?? 'member'),\n };\n }\n\n if (match === 'role') {\n const named = role ?? flags.role;\n if (!named) {\n throw usageError(\n 'Name the new role.',\n `Run \"ekanos teams members role ${teamSlug} ${address} <role>\"; \"ekanos teams roles list ${teamSlug}\" lists them.`,\n );\n }\n return {\n kind: 'members-role',\n team: teamSlug,\n email: address,\n role: roleOf(named),\n };\n }\n\n if (role !== undefined) noExtra([role]);\n if (!flags.yes) {\n throw invalidStateError(\n `Removing ${address} from ${teamSlug} ends their access to the team (a pending invitation is withdrawn instead).`,\n `Re-run with --yes: ekanos teams members remove ${teamSlug} ${address} --yes`,\n );\n }\n return { kind: 'members-remove', team: teamSlug, email: address };\n}\n\nexport async function runOnboarding(\n ctx: CliContext,\n client: TeamsClient,\n host: string,\n source: string,\n plan: OnboardingPlan,\n): Promise<ExitCode> {\n switch (plan.kind) {\n case 'create': {\n const created = await client.call((token) =>\n createTeam(host, token, source, {\n name: plan.name,\n slug: plan.slug,\n ownerEmail: plan.ownerEmail,\n }),\n );\n return ctx.succeed(\n { host, ...created },\n ctx.ui.result({\n title: `Created team ${created.team.slug}`,\n rows: [\n ['Name', created.team.name],\n ['Source', created.source.slug],\n [\n 'Owner',\n created.owner.kind === 'caller'\n ? `you${created.owner.email ? ` (${created.owner.email})` : ''}`\n : `${created.owner.email} — invited; ownership moves to them on accept`,\n ],\n ['Team home', created.teamUrl],\n ],\n notes: [\n `Next: \"ekanos teams members invite ${created.team.slug} <email> --role <role>\" (\"ekanos teams roles list ${created.team.slug}\" lists the roles).`,\n `Then \"ekanos teams connect ${created.team.slug} <product>\" for the link a member opens to connect an integration.`,\n ],\n }),\n );\n }\n\n case 'members-list': {\n const list = await client.call((token) =>\n listTeamMembers(host, token, { source, team: plan.team }),\n );\n const rows: [string, string][] = list.members.map((member) => [\n member.email ?? member.userId,\n `${member.role}${member.primaryOwner ? ' · primary owner' : ''}${member.status === 'active' ? '' : ` · ${member.status}`}${member.name ? ` · ${member.name}` : ''}`,\n ]);\n for (const invitation of list.invitations) {\n rows.push([\n invitation.email,\n `${invitation.role} · invited, expires ${invitation.expiresAt.slice(0, 10)}`,\n ]);\n }\n return ctx.succeed(\n { host, ...list },\n ctx.ui.result({\n title: `Members of ${list.team.slug} (${list.team.name})`,\n tone: rows.length === 0 ? 'warn' : 'ok',\n rows,\n notes: [\n `\"ekanos teams members invite ${list.team.slug} <email> --role <role>\" adds one.`,\n ],\n }),\n );\n }\n\n case 'members-invite': {\n const invited = await client.call((token) =>\n inviteTeamMember(\n host,\n token,\n { source, team: plan.team },\n { email: plan.email, role: plan.role },\n ),\n );\n return ctx.succeed(\n { host, ...invited },\n ctx.ui.result({\n title: `Invited ${invited.invitation.email} to ${invited.team.slug}`,\n rows: [\n ['Role', invited.invitation.role],\n ['Expires', invited.invitation.expiresAt.slice(0, 10)],\n ],\n notes: [\n 'The invitation email is on its way; they join when they accept it.',\n ],\n }),\n );\n }\n\n case 'members-role': {\n const changed = await client.call((token) =>\n setTeamMemberRole(\n host,\n token,\n { source, team: plan.team, email: plan.email },\n plan.role,\n ),\n );\n return ctx.succeed(\n { host, ...changed },\n ctx.ui.result({\n title:\n changed.before === changed.after\n ? `${changed.email} is already ${changed.after} on ${changed.team.slug}`\n : `${changed.email} is now ${changed.after} on ${changed.team.slug}`,\n rows: [\n ['Was', changed.before ?? '—'],\n [\n 'Applies to',\n changed.kind === 'member'\n ? 'their membership'\n : 'their pending invitation',\n ],\n ],\n }),\n );\n }\n\n case 'members-remove': {\n const removed = await client.call((token) =>\n removeTeamMember(host, token, {\n source,\n team: plan.team,\n email: plan.email,\n }),\n );\n return ctx.succeed(\n { host, ...removed },\n ctx.ui.result({\n title:\n removed.kind === 'member'\n ? `Removed ${removed.email} from ${removed.team.slug}`\n : `Withdrew ${removed.email}'s invitation to ${removed.team.slug}`,\n rows: [['Role was', removed.before ?? '—']],\n }),\n );\n }\n\n case 'roles-list': {\n const roles = await client.call((token) =>\n listTeamRoles(host, token, { source, team: plan.team }),\n );\n return ctx.succeed(\n { host, ...roles },\n ctx.ui.result({\n title: `Roles on ${roles.team.slug}`,\n rows: roles.roles.map((role) => [\n `${role.name} (${role.displayName})`,\n role.permissions.length > 0 ? role.permissions : ['no permissions'],\n ]),\n notes: [\n 'Lower roles are listed last. Give one with \"ekanos teams members role <team> <email> <role>\".',\n ],\n }),\n );\n }\n\n case 'connect': {\n const status = await client.call((token) =>\n showTeamIntegration(host, token, {\n source,\n team: plan.team,\n product: plan.product,\n }),\n );\n if (!status.enabled) {\n throw invalidStateError(\n `\"${plan.product}\" is not enabled on ${status.source.slug}.`,\n '\"ekanos catalog\" lists what this Source has; Ekanos enables a product for a Source.',\n );\n }\n const connected = status.activation !== null;\n return ctx.succeed(\n { host, ...status },\n ctx.ui.result({\n title: connected\n ? `${status.team.slug} has connected ${status.product.name}`\n : `Connect link for ${status.team.slug} · ${status.product.name}`,\n tone: connected ? 'ok' : 'warn',\n rows: [\n ['Open', status.connectUrl],\n [\n 'Status',\n connected\n ? `connected${status.activation?.activatedAt ? ` ${status.activation.activatedAt.slice(0, 10)}` : ''}`\n : 'not connected',\n ],\n ],\n notes: [\n 'A team member signs in there and clicks Connect: sign-in happens in their browser, never in a terminal.',\n `\"ekanos teams show ${status.team.slug} ${status.product.slug}\" shows the full setup checklist.`,\n ],\n }),\n );\n }\n }\n}\n\nfunction noExtra(extra: readonly string[]) {\n if (extra.length > 0) {\n throw usageError(\n `Unexpected argument \"${extra[0]}\".`,\n 'See \"ekanos teams --help\".',\n );\n }\n}\n\nfunction slugOf(value: string | undefined, what: 'team' | 'product'): string {\n const slug = value?.trim().toLowerCase();\n if (slug && /^[a-z0-9][a-z0-9-]*$/.test(slug)) return slug;\n throw usageError(\n value ? `\"${value}\" is not a ${what} slug.` : `Name the ${what}.`,\n what === 'team'\n ? '\"ekanos teams list\" lists the teams, e.g. \"ekanos teams members list bank-a\".'\n : '\"ekanos catalog\" lists the products, e.g. \"ekanos teams connect bank-a zendesk\".',\n );\n}\n\nfunction emailOf(value: string | undefined): string {\n const email = value?.trim().toLowerCase();\n if (email && /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(email)) return email;\n throw usageError(\n value ? `\"${value}\" is not an email address.` : 'Name the person by email.',\n 'e.g. \"ekanos teams members invite bank-a ana@bank-a.com --role member\".',\n );\n}\n\nfunction roleOf(value: string): string {\n const role = value.trim().toLowerCase();\n if (/^[a-z][a-z0-9_-]{0,49}$/.test(role)) return role;\n throw usageError(\n `\"${value}\" is not a role name.`,\n '\"ekanos teams roles list <team>\" lists the roles this Source uses.',\n );\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import type { TeamIntegrationStatus } from '@ekanos/integration-schema';
2
+ import type { StoredSession } from '../auth/credential-store.js';
3
+ import { type AuthEnvironment } from '../auth/session.js';
4
+ import type { CliContext } from '../context.js';
5
+ import type { ExitCode } from '../exit-codes.js';
6
+ import { type TeamsApiResult } from '../teams-api.js';
7
+ export declare const TEAMS_SUBCOMMANDS: readonly ["list", "show", "app", "webhook", "create", "members", "roles", "connect"];
8
+ export interface TeamsArgs {
9
+ host?: string;
10
+ source?: string;
11
+ /** `list`, `show <team> <product>`, `app set <team> <product>`, `webhook <team> <product> [<id>]`. */
12
+ positionals: readonly string[];
13
+ connection?: string;
14
+ /** `create --slug`, `create --owner <email>`, `members invite --role`. */
15
+ slug?: string;
16
+ owner?: string;
17
+ role?: string;
18
+ yes: boolean;
19
+ /** Every parsed flag, for `teams agents|triggers|links|mcp` (team-agents.ts). */
20
+ flags?: Record<string, string | boolean | string[]>;
21
+ dir: string;
22
+ env: Record<string, string | undefined>;
23
+ stdinIsTTY?: boolean;
24
+ /** Test seams for the questions `app set` asks. */
25
+ ask?: (question: string) => Promise<string>;
26
+ askSecret?: (question: string) => Promise<string>;
27
+ }
28
+ /**
29
+ * `teams` — a Source admin's view of its teams' integration setup, the
30
+ * team-level counterpart of `apps`. `list` names the teams; `show` reads one
31
+ * team's setup of one product as a checklist; `app set` enters a team's own
32
+ * app (Zendesk's subdomain, client and signing secret) at the admin's
33
+ * terminal — never from a flag, a file or a non-interactive run, so an
34
+ * agent driving the CLI never handles a secret; `webhook` mints the URL the
35
+ * team's provider sends to, shown once.
36
+ */
37
+ export declare function runTeams(ctx: CliContext, args: TeamsArgs): Promise<ExitCode>;
38
+ /** `teams show`, read top to bottom like a setup checklist. */
39
+ export declare function checklist(status: TeamIntegrationStatus): string[];
40
+ export declare class TeamsClient {
41
+ private readonly env;
42
+ private session;
43
+ constructor(env: AuthEnvironment, session: StoredSession);
44
+ call<T>(send: (accessToken: string) => Promise<TeamsApiResult<T>>): Promise<T>;
45
+ }