@ekanos/cli 0.13.0 → 0.15.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 (173) hide show
  1. package/README.md +25 -8
  2. package/dist/agents-api.d.ts +11 -2
  3. package/dist/agents-api.js +39 -3
  4. package/dist/agents-api.js.map +1 -1
  5. package/dist/apps-api.js +2 -2
  6. package/dist/apps-api.js.map +1 -1
  7. package/dist/auth/ekanos-api.d.ts +16 -2
  8. package/dist/auth/ekanos-api.js +101 -15
  9. package/dist/auth/ekanos-api.js.map +1 -1
  10. package/dist/auth/host-trust.d.ts +46 -0
  11. package/dist/auth/host-trust.js +79 -0
  12. package/dist/auth/host-trust.js.map +1 -0
  13. package/dist/auth/session.d.ts +10 -1
  14. package/dist/auth/session.js +28 -28
  15. package/dist/auth/session.js.map +1 -1
  16. package/dist/commands/agents-edit.d.ts +6 -2
  17. package/dist/commands/agents-edit.js +57 -26
  18. package/dist/commands/agents-edit.js.map +1 -1
  19. package/dist/commands/agents.d.ts +13 -2
  20. package/dist/commands/agents.js +140 -35
  21. package/dist/commands/agents.js.map +1 -1
  22. package/dist/commands/apps.d.ts +2 -0
  23. package/dist/commands/apps.js +16 -12
  24. package/dist/commands/apps.js.map +1 -1
  25. package/dist/commands/catalog.js +7 -6
  26. package/dist/commands/catalog.js.map +1 -1
  27. package/dist/commands/knowledge.d.ts +19 -0
  28. package/dist/commands/knowledge.js +119 -27
  29. package/dist/commands/knowledge.js.map +1 -1
  30. package/dist/commands/login.d.ts +4 -0
  31. package/dist/commands/login.js +30 -2
  32. package/dist/commands/login.js.map +1 -1
  33. package/dist/commands/logout.d.ts +9 -1
  34. package/dist/commands/logout.js +56 -7
  35. package/dist/commands/logout.js.map +1 -1
  36. package/dist/commands/publish.js +3 -3
  37. package/dist/commands/publish.js.map +1 -1
  38. package/dist/commands/reviewers.d.ts +2 -0
  39. package/dist/commands/reviewers.js +7 -3
  40. package/dist/commands/reviewers.js.map +1 -1
  41. package/dist/commands/routes.d.ts +2 -0
  42. package/dist/commands/routes.js +42 -9
  43. package/dist/commands/routes.js.map +1 -1
  44. package/dist/commands/runs.d.ts +13 -2
  45. package/dist/commands/runs.js +99 -16
  46. package/dist/commands/runs.js.map +1 -1
  47. package/dist/commands/scope-notice.d.ts +21 -0
  48. package/dist/commands/scope-notice.js +35 -0
  49. package/dist/commands/scope-notice.js.map +1 -0
  50. package/dist/commands/skills.d.ts +7 -1
  51. package/dist/commands/skills.js +80 -24
  52. package/dist/commands/skills.js.map +1 -1
  53. package/dist/commands/sources.d.ts +1 -1
  54. package/dist/commands/sources.js +126 -12
  55. package/dist/commands/sources.js.map +1 -1
  56. package/dist/commands/status.js +5 -4
  57. package/dist/commands/status.js.map +1 -1
  58. package/dist/commands/team-agent-readiness.d.ts +49 -0
  59. package/dist/commands/team-agent-readiness.js +308 -0
  60. package/dist/commands/team-agent-readiness.js.map +1 -0
  61. package/dist/commands/team-agents-agents.d.ts +24 -3
  62. package/dist/commands/team-agents-agents.js +314 -35
  63. package/dist/commands/team-agents-agents.js.map +1 -1
  64. package/dist/commands/team-agents-mcp.js +32 -3
  65. package/dist/commands/team-agents-mcp.js.map +1 -1
  66. package/dist/commands/team-agents-triggers.js +230 -55
  67. package/dist/commands/team-agents-triggers.js.map +1 -1
  68. package/dist/commands/team-agents.d.ts +3 -1
  69. package/dist/commands/team-agents.js +27 -7
  70. package/dist/commands/team-agents.js.map +1 -1
  71. package/dist/commands/teams-channel-install.d.ts +24 -0
  72. package/dist/commands/teams-channel-install.js +94 -0
  73. package/dist/commands/teams-channel-install.js.map +1 -0
  74. package/dist/commands/teams-connect.d.ts +41 -0
  75. package/dist/commands/teams-connect.js +212 -57
  76. package/dist/commands/teams-connect.js.map +1 -1
  77. package/dist/commands/teams-events.d.ts +12 -0
  78. package/dist/commands/teams-events.js +138 -0
  79. package/dist/commands/teams-events.js.map +1 -0
  80. package/dist/commands/teams-onboard.d.ts +11 -3
  81. package/dist/commands/teams-onboard.js +60 -7
  82. package/dist/commands/teams-onboard.js.map +1 -1
  83. package/dist/commands/teams-overview.d.ts +11 -0
  84. package/dist/commands/teams-overview.js +160 -0
  85. package/dist/commands/teams-overview.js.map +1 -0
  86. package/dist/commands/teams-scope.d.ts +10 -0
  87. package/dist/commands/teams-scope.js +109 -0
  88. package/dist/commands/teams-scope.js.map +1 -0
  89. package/dist/commands/teams.d.ts +4 -2
  90. package/dist/commands/teams.js +113 -14
  91. package/dist/commands/teams.js.map +1 -1
  92. package/dist/commands/upgrade.js +12 -2
  93. package/dist/commands/upgrade.js.map +1 -1
  94. package/dist/commands/use.js +2 -2
  95. package/dist/commands/use.js.map +1 -1
  96. package/dist/commands/whoami.js +1 -1
  97. package/dist/commands/whoami.js.map +1 -1
  98. package/dist/connections-api.js +1 -1
  99. package/dist/connections-api.js.map +1 -1
  100. package/dist/delegate.js +11 -4
  101. package/dist/delegate.js.map +1 -1
  102. package/dist/harness-scaffold.js +12 -0
  103. package/dist/harness-scaffold.js.map +1 -1
  104. package/dist/index.d.ts +6 -0
  105. package/dist/index.js +682 -167
  106. package/dist/index.js.map +1 -1
  107. package/dist/knowledge-api.d.ts +46 -0
  108. package/dist/knowledge-api.js +32 -1
  109. package/dist/knowledge-api.js.map +1 -1
  110. package/dist/open-url.d.ts +16 -0
  111. package/dist/open-url.js +73 -0
  112. package/dist/open-url.js.map +1 -0
  113. package/dist/parse-argv.d.ts +2 -0
  114. package/dist/parse-argv.js.map +1 -1
  115. package/dist/publish-api.d.ts +2 -1
  116. package/dist/publish-api.js +4 -3
  117. package/dist/publish-api.js.map +1 -1
  118. package/dist/pull-write.d.ts +27 -0
  119. package/dist/pull-write.js +60 -0
  120. package/dist/pull-write.js.map +1 -0
  121. package/dist/routes-api.d.ts +3 -0
  122. package/dist/routes-api.js +9 -4
  123. package/dist/routes-api.js.map +1 -1
  124. package/dist/runs-api.d.ts +7 -1
  125. package/dist/runs-api.js +21 -6
  126. package/dist/runs-api.js.map +1 -1
  127. package/dist/safe-fs.d.ts +2 -0
  128. package/dist/safe-fs.js +12 -6
  129. package/dist/safe-fs.js.map +1 -1
  130. package/dist/secret-file.d.ts +17 -0
  131. package/dist/secret-file.js +69 -0
  132. package/dist/secret-file.js.map +1 -0
  133. package/dist/skills-api.d.ts +10 -2
  134. package/dist/skills-api.js +37 -2
  135. package/dist/skills-api.js.map +1 -1
  136. package/dist/source-products-api.d.ts +4 -0
  137. package/dist/source-products-api.js +14 -4
  138. package/dist/source-products-api.js.map +1 -1
  139. package/dist/source-target.js +2 -1
  140. package/dist/source-target.js.map +1 -1
  141. package/dist/sources-api.d.ts +2 -1
  142. package/dist/sources-api.js +3 -2
  143. package/dist/sources-api.js.map +1 -1
  144. package/dist/team-connect-api.d.ts +44 -2
  145. package/dist/team-connect-api.js +19 -7
  146. package/dist/team-connect-api.js.map +1 -1
  147. package/dist/teams-api.d.ts +41 -1
  148. package/dist/teams-api.js +95 -19
  149. package/dist/teams-api.js.map +1 -1
  150. package/dist/toolchain-api.d.ts +9 -0
  151. package/dist/toolchain-api.js +17 -2
  152. package/dist/toolchain-api.js.map +1 -1
  153. package/dist/toolchain-resolve.d.ts +3 -0
  154. package/dist/toolchain-resolve.js +15 -1
  155. package/dist/toolchain-resolve.js.map +1 -1
  156. package/dist/toolchain.d.ts +6 -0
  157. package/dist/toolchain.js +19 -0
  158. package/dist/toolchain.js.map +1 -1
  159. package/dist/update-notice.js +2 -1
  160. package/dist/update-notice.js.map +1 -1
  161. package/package.json +2 -2
  162. package/templates/AGENTS.md.tmpl +6 -1
  163. package/templates/claude-skill.md.tmpl +5 -0
  164. package/templates/package.json.tmpl +4 -4
  165. package/templates/skills/build-agent/SKILL.md +104 -17
  166. package/templates/skills/build-agent/references/agent-json.md +4 -2
  167. package/templates/skills/build-agent/references/rollout.md +23 -4
  168. package/templates/skills/build-agent/references/set-up-an-integration.md +55 -7
  169. package/templates/skills/build-agent/references/skills.md +20 -1
  170. package/templates/skills/build-agent/references/triggers.md +47 -11
  171. package/templates/skills/build-integration/references/widget-design.md +3 -3
  172. package/templates/skills/ekanos-platform/SKILL.md +45 -16
  173. package/templates/skills/polish-widgets/references/patterns.md +2 -2
@@ -0,0 +1,11 @@
1
+ import type { CliContext } from '../context.js';
2
+ import type { ExitCode } from '../exit-codes.js';
3
+ import type { TeamsClient } from './teams.js';
4
+ /**
5
+ * `ekanos teams show <team>` with no product: one screen per team (CLI audit
6
+ * P1-7) — its members, one line per Integration the Source has enabled (whose
7
+ * connection, or the step to make it), and one line per provided agent with
8
+ * the same verdict as `teams agents show`. Reads the team list, the Source
9
+ * catalog, the team's agents, and each enabled Integration's status.
10
+ */
11
+ export declare function runTeamOverview(ctx: CliContext, client: TeamsClient, host: string, source: string, team: string): Promise<ExitCode>;
@@ -0,0 +1,160 @@
1
+ import { SourceCatalogSchema, TeamAgentListSchema, } from '@ekanos/integration-schema';
2
+ import { notFoundError } from '../errors.js';
3
+ import { call, listTeams, showTeamIntegration } from '../teams-api.js';
4
+ import { scopeFix, scopeHiddenWords } from './scope-notice.js';
5
+ import { REASON_WORDS, agentReadiness, liveLine, requirementFix, } from './team-agent-readiness.js';
6
+ /**
7
+ * `ekanos teams show <team>` with no product: one screen per team (CLI audit
8
+ * P1-7) — its members, one line per Integration the Source has enabled (whose
9
+ * connection, or the step to make it), and one line per provided agent with
10
+ * the same verdict as `teams agents show`. Reads the team list, the Source
11
+ * catalog, the team's agents, and each enabled Integration's status.
12
+ */
13
+ export async function runTeamOverview(ctx, client, host, source, team) {
14
+ var _a;
15
+ var _b;
16
+ const [teams, catalog, agents] = await Promise.all([
17
+ client.call((token) => listTeams(host, token, source)),
18
+ client.call((token) => call(host, token, SourceCatalogSchema, {
19
+ method: 'GET',
20
+ path: '/api/partner/catalog',
21
+ source,
22
+ })),
23
+ client.call((token) => call(host, token, TeamAgentListSchema, {
24
+ method: 'GET',
25
+ path: `/api/partner/teams/${encodeURIComponent(team)}/agents`,
26
+ source,
27
+ })),
28
+ ]);
29
+ const ref = teams.teams.find((entry) => entry.slug === team);
30
+ if (!ref) {
31
+ throw notFoundError(`No team "${team}" on ${teams.source.slug}.`, 'Run "ekanos teams list" to see the teams on this Source.');
32
+ }
33
+ const integrations = catalog.products.filter((product) => product.kind === 'integration' && product.availability === 'enabled');
34
+ const statuses = await inBatches(integrations, 4, (product) => client.call((token) => showTeamIntegration(host, token, {
35
+ source,
36
+ team,
37
+ product: product.slug,
38
+ })));
39
+ // What the team's agents need of each product, and whose connection it is.
40
+ const needs = new Map();
41
+ for (const entry of agents.agents) {
42
+ for (const item of (_b = (_a = entry.agent) === null || _a === void 0 ? void 0 : _a.requirements) !== null && _b !== void 0 ? _b : []) {
43
+ if (item.kind === 'product' && !needs.has(item.key)) {
44
+ needs.set(item.key, item);
45
+ }
46
+ }
47
+ }
48
+ const target = { team, source };
49
+ const agentRows = agents.agents.map((entry) => { var _a; return ({
50
+ key: entry.key,
51
+ name: entry.name,
52
+ on: ((_a = entry.agent) === null || _a === void 0 ? void 0 : _a.status) === 'active',
53
+ readiness: agentReadiness(entry, target),
54
+ }); });
55
+ const integrationRows = integrations.map((product, index) => ({
56
+ slug: product.slug,
57
+ name: product.name,
58
+ line: withScope(integrationLine(product, statuses[index], needs.get(product.slug), target), statuses[index], Object.assign(Object.assign({}, target), { product: product.slug })),
59
+ }));
60
+ const width = Math.max(8, ...integrations.map((product) => product.slug.length), ...agents.agents.map((entry) => entry.key.length));
61
+ const pad = (label) => label.padEnd(width);
62
+ const live = agentRows.filter((row) => row.readiness.live).length;
63
+ return ctx.succeed({
64
+ host,
65
+ team: { slug: ref.slug, name: ref.name, memberCount: ref.memberCount },
66
+ integrations: integrationRows.map((row, index) => { var _a; return ({
67
+ slug: row.slug,
68
+ name: row.name,
69
+ status: statuses[index],
70
+ agentsNeed: (_a = needs.get(row.slug)) !== null && _a !== void 0 ? _a : null,
71
+ }); }),
72
+ agents: agentRows,
73
+ }, [
74
+ `${ref.name} (${ref.slug}) · ${ref.memberCount} member${ref.memberCount === 1 ? '' : 's'} · ${agentRows.filter((row) => row.on).length} agent${agentRows.filter((row) => row.on).length === 1 ? '' : 's'} on, ${live} live`,
75
+ '',
76
+ 'Integrations',
77
+ ...(integrationRows.length === 0
78
+ ? [' none enabled for the Source']
79
+ : integrationRows.flatMap((row) => [
80
+ ` ${pad(row.slug)} ${row.line.state}`,
81
+ ...(row.line.fix ? [` ${pad('')} → ${row.line.fix}`] : []),
82
+ ])),
83
+ '',
84
+ 'Agents',
85
+ ...(agentRows.length === 0
86
+ ? [' none provided']
87
+ : agentRows.map((row) => ` ${pad(row.key)} ${liveLine(row.readiness)}${row.readiness.next ? ` · Next: ${row.readiness.next}` : ''}`)),
88
+ '',
89
+ `"ekanos teams agents show ${team} <key>" explains one agent; "ekanos teams show ${team} <product>" one integration.`,
90
+ ].join('\n'));
91
+ }
92
+ function integrationLine(product, status, need, target) {
93
+ var _a;
94
+ if ((need === null || need === void 0 ? void 0 : need.owner) === 'source' || (need === null || need === void 0 ? void 0 : need.via) === 'source') {
95
+ const problem = need.reason && need.reason !== 'not_connected'
96
+ ? REASON_WORDS[need.reason]
97
+ : null;
98
+ const state = !need.connected
99
+ ? `✗ via Source connection — ${problem !== null && problem !== void 0 ? problem : REASON_WORDS.not_connected}`
100
+ : need.via === 'source'
101
+ ? `✓ via Source connection${need.viaDetail ? ` · routes: ${need.viaDetail}` : ''}`
102
+ : `! via team connection (${problem !== null && problem !== void 0 ? problem : 'the Source connection is not used'})`;
103
+ return {
104
+ state,
105
+ fix: need.connected && need.via === 'source'
106
+ ? null
107
+ : requirementFix(need, target.team, target.source),
108
+ };
109
+ }
110
+ if (!product.requiresActivation) {
111
+ return { state: '✓ works as shipped', fix: null };
112
+ }
113
+ if (product.scope === 'user') {
114
+ const person = (need === null || need === void 0 ? void 0 : need.via) === 'person' && need.viaDetail
115
+ ? need.connected
116
+ ? ` · ✓ ${need.viaDetail} connected (an agent runs as them)`
117
+ : ` · ✗ ${need.viaDetail} not connected (an agent runs as them)`
118
+ : '';
119
+ return {
120
+ state: `personal: each person connects their own${person}`,
121
+ fix: need && !need.connected
122
+ ? requirementFix(need, target.team, target.source)
123
+ : null,
124
+ };
125
+ }
126
+ const webhooks = status.teamApps.flatMap((app) => app.webhooks);
127
+ const made = webhooks.filter((webhook) => webhook.endpoint).length;
128
+ const webhookNote = webhooks.length > 0 ? ` · webhook URL ${made}/${webhooks.length} made` : '';
129
+ if (status.activation) {
130
+ const by = (_a = status.activation.activatedBy) === null || _a === void 0 ? void 0 : _a.email;
131
+ return {
132
+ state: `✓ connected${status.activation.activatedAt ? ` ${status.activation.activatedAt.slice(0, 10)}` : ''}${by ? ` by ${by}` : ''}${webhookNote}`,
133
+ fix: null,
134
+ };
135
+ }
136
+ return {
137
+ state: `✗ not connected${webhookNote}`,
138
+ fix: `npx @ekanos/cli teams connect ${target.team} ${product.slug} --source ${target.source}`,
139
+ };
140
+ }
141
+ /** A product the team's scopes hide from it in the app says so, whatever else is true. */
142
+ function withScope(line, status, target) {
143
+ var _a;
144
+ var _b;
145
+ if (!((_a = status.scope) === null || _a === void 0 ? void 0 : _a.hidden))
146
+ return line;
147
+ return {
148
+ state: `${line.state} · ! ${scopeHiddenWords(status.scope)}`,
149
+ fix: (_b = line.fix) !== null && _b !== void 0 ? _b : scopeFix(status.scope, target),
150
+ };
151
+ }
152
+ /** Each item through `read`, at most `size` at a time, in order. */
153
+ async function inBatches(items, size, read) {
154
+ const results = [];
155
+ for (let start = 0; start < items.length; start += size) {
156
+ results.push(...(await Promise.all(items.slice(start, start + size).map(read))));
157
+ }
158
+ return results;
159
+ }
160
+ //# sourceMappingURL=teams-overview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-overview.js","sourceRoot":"","sources":["../../src/commands/teams-overview.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type CatalogProduct,\n type Requirement,\n SourceCatalogSchema,\n TeamAgentListSchema,\n type TeamIntegrationStatus,\n} from '@ekanos/integration-schema';\n\nimport type { CliContext } from '../context';\nimport { notFoundError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { call, listTeams, showTeamIntegration } from '../teams-api';\nimport { scopeFix, scopeHiddenWords } from './scope-notice';\nimport {\n REASON_WORDS,\n agentReadiness,\n liveLine,\n requirementFix,\n} from './team-agent-readiness';\nimport type { TeamsClient } from './teams';\n\n/**\n * `ekanos teams show <team>` with no product: one screen per team (CLI audit\n * P1-7) — its members, one line per Integration the Source has enabled (whose\n * connection, or the step to make it), and one line per provided agent with\n * the same verdict as `teams agents show`. Reads the team list, the Source\n * catalog, the team's agents, and each enabled Integration's status.\n */\nexport async function runTeamOverview(\n ctx: CliContext,\n client: TeamsClient,\n host: string,\n source: string,\n team: string,\n): Promise<ExitCode> {\n const [teams, catalog, agents] = await Promise.all([\n client.call((token) => listTeams(host, token, source)),\n client.call((token) =>\n call(host, token, SourceCatalogSchema, {\n method: 'GET',\n path: '/api/partner/catalog',\n source,\n }),\n ),\n client.call((token) =>\n call(host, token, TeamAgentListSchema, {\n method: 'GET',\n path: `/api/partner/teams/${encodeURIComponent(team)}/agents`,\n source,\n }),\n ),\n ]);\n const ref = teams.teams.find((entry) => entry.slug === team);\n if (!ref) {\n throw notFoundError(\n `No team \"${team}\" on ${teams.source.slug}.`,\n 'Run \"ekanos teams list\" to see the teams on this Source.',\n );\n }\n\n const integrations = catalog.products.filter(\n (product) =>\n product.kind === 'integration' && product.availability === 'enabled',\n );\n const statuses = await inBatches(integrations, 4, (product) =>\n client.call((token) =>\n showTeamIntegration(host, token, {\n source,\n team,\n product: product.slug,\n }),\n ),\n );\n\n // What the team's agents need of each product, and whose connection it is.\n const needs = new Map<string, Requirement>();\n for (const entry of agents.agents) {\n for (const item of entry.agent?.requirements ?? []) {\n if (item.kind === 'product' && !needs.has(item.key)) {\n needs.set(item.key, item);\n }\n }\n }\n\n const target = { team, source };\n const agentRows = agents.agents.map((entry) => ({\n key: entry.key,\n name: entry.name,\n on: entry.agent?.status === 'active',\n readiness: agentReadiness(entry, target),\n }));\n const integrationRows = integrations.map((product, index) => ({\n slug: product.slug,\n name: product.name,\n line: withScope(\n integrationLine(\n product,\n statuses[index]!,\n needs.get(product.slug),\n target,\n ),\n statuses[index]!,\n { ...target, product: product.slug },\n ),\n }));\n\n const width = Math.max(\n 8,\n ...integrations.map((product) => product.slug.length),\n ...agents.agents.map((entry) => entry.key.length),\n );\n const pad = (label: string) => label.padEnd(width);\n const live = agentRows.filter((row) => row.readiness.live).length;\n\n return ctx.succeed(\n {\n host,\n team: { slug: ref.slug, name: ref.name, memberCount: ref.memberCount },\n integrations: integrationRows.map((row, index) => ({\n slug: row.slug,\n name: row.name,\n status: statuses[index],\n agentsNeed: needs.get(row.slug) ?? null,\n })),\n agents: agentRows,\n },\n [\n `${ref.name} (${ref.slug}) · ${ref.memberCount} member${ref.memberCount === 1 ? '' : 's'} · ${agentRows.filter((row) => row.on).length} agent${agentRows.filter((row) => row.on).length === 1 ? '' : 's'} on, ${live} live`,\n '',\n 'Integrations',\n ...(integrationRows.length === 0\n ? [' none enabled for the Source']\n : integrationRows.flatMap((row) => [\n ` ${pad(row.slug)} ${row.line.state}`,\n ...(row.line.fix ? [` ${pad('')} → ${row.line.fix}`] : []),\n ])),\n '',\n 'Agents',\n ...(agentRows.length === 0\n ? [' none provided']\n : agentRows.map(\n (row) =>\n ` ${pad(row.key)} ${liveLine(row.readiness)}${row.readiness.next ? ` · Next: ${row.readiness.next}` : ''}`,\n )),\n '',\n `\"ekanos teams agents show ${team} <key>\" explains one agent; \"ekanos teams show ${team} <product>\" one integration.`,\n ].join('\\n'),\n );\n}\n\nfunction integrationLine(\n product: CatalogProduct,\n status: TeamIntegrationStatus,\n need: Requirement | undefined,\n target: { team: string; source: string },\n): { state: string; fix: string | null } {\n if (need?.owner === 'source' || need?.via === 'source') {\n const problem =\n need.reason && need.reason !== 'not_connected'\n ? REASON_WORDS[need.reason]\n : null;\n const state = !need.connected\n ? `✗ via Source connection — ${problem ?? REASON_WORDS.not_connected}`\n : need.via === 'source'\n ? `✓ via Source connection${need.viaDetail ? ` · routes: ${need.viaDetail}` : ''}`\n : `! via team connection (${problem ?? 'the Source connection is not used'})`;\n return {\n state,\n fix:\n need.connected && need.via === 'source'\n ? null\n : requirementFix(need, target.team, target.source),\n };\n }\n if (!product.requiresActivation) {\n return { state: '✓ works as shipped', fix: null };\n }\n if (product.scope === 'user') {\n const person =\n need?.via === 'person' && need.viaDetail\n ? need.connected\n ? ` · ✓ ${need.viaDetail} connected (an agent runs as them)`\n : ` · ✗ ${need.viaDetail} not connected (an agent runs as them)`\n : '';\n return {\n state: `personal: each person connects their own${person}`,\n fix:\n need && !need.connected\n ? requirementFix(need, target.team, target.source)\n : null,\n };\n }\n const webhooks = status.teamApps.flatMap((app) => app.webhooks);\n const made = webhooks.filter((webhook) => webhook.endpoint).length;\n const webhookNote =\n webhooks.length > 0 ? ` · webhook URL ${made}/${webhooks.length} made` : '';\n if (status.activation) {\n const by = status.activation.activatedBy?.email;\n return {\n state: `✓ connected${status.activation.activatedAt ? ` ${status.activation.activatedAt.slice(0, 10)}` : ''}${by ? ` by ${by}` : ''}${webhookNote}`,\n fix: null,\n };\n }\n return {\n state: `✗ not connected${webhookNote}`,\n fix: `npx @ekanos/cli teams connect ${target.team} ${product.slug} --source ${target.source}`,\n };\n}\n\n/** A product the team's scopes hide from it in the app says so, whatever else is true. */\nfunction withScope(\n line: { state: string; fix: string | null },\n status: TeamIntegrationStatus,\n target: { team: string; source: string; product: string },\n): { state: string; fix: string | null } {\n if (!status.scope?.hidden) return line;\n return {\n state: `${line.state} · ! ${scopeHiddenWords(status.scope)}`,\n fix: line.fix ?? scopeFix(status.scope, target),\n };\n}\n\n/** Each item through `read`, at most `size` at a time, in order. */\nasync function inBatches<T, R>(\n items: T[],\n size: number,\n read: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = [];\n for (let start = 0; start < items.length; start += size) {\n results.push(\n ...(await Promise.all(items.slice(start, start + size).map(read))),\n );\n }\n return results;\n}\n"]}
@@ -0,0 +1,10 @@
1
+ import type { CliContext } from '../context.js';
2
+ import type { ExitCode } from '../exit-codes.js';
3
+ import { type TeamsArgs } from './teams.js';
4
+ /**
5
+ * `teams scope show|set <team>` — which of the Source's product scopes a team
6
+ * holds, and so which products it sees in the app. `set` replaces them
7
+ * (names or ids); taking one away can hide products the team uses, so that
8
+ * needs `--yes`. The Source's scopes and their products: `sources scopes`.
9
+ */
10
+ export declare function runTeamScope(ctx: CliContext, args: TeamsArgs, rest: readonly string[]): Promise<ExitCode>;
@@ -0,0 +1,109 @@
1
+ import { requireSession, resolveAuthEnvironment } from '../auth/session.js';
2
+ import { invalidStateError, usageError } from '../errors.js';
3
+ import { resolveSourceTarget } from '../source-target.js';
4
+ import { setTeamScopes, showTeamScopes } from '../teams-api.js';
5
+ import { TeamsClient } from './teams.js';
6
+ const USAGE = 'Run "ekanos teams scope show <team>" or "ekanos teams scope set <team> <scope> [<scope>…] [--yes]".';
7
+ /**
8
+ * `teams scope show|set <team>` — which of the Source's product scopes a team
9
+ * holds, and so which products it sees in the app. `set` replaces them
10
+ * (names or ids); taking one away can hide products the team uses, so that
11
+ * needs `--yes`. The Source's scopes and their products: `sources scopes`.
12
+ */
13
+ export async function runTeamScope(ctx, args, rest) {
14
+ const [verb, rawTeam, ...scopes] = rest;
15
+ if (verb !== 'show' && verb !== 'set') {
16
+ throw usageError(verb
17
+ ? `Unknown teams scope subcommand "${verb}".`
18
+ : 'teams scope needs a subcommand.', USAGE);
19
+ }
20
+ const team = rawTeam === null || rawTeam === void 0 ? void 0 : rawTeam.trim().toLowerCase();
21
+ if (!team || !/^[a-z0-9][a-z0-9-]*$/.test(team)) {
22
+ throw usageError(rawTeam
23
+ ? `"${rawTeam}" is not a team slug.`
24
+ : `teams scope ${verb} needs a team.`, USAGE);
25
+ }
26
+ if (verb === 'show' && scopes.length > 0) {
27
+ throw usageError(`Unexpected argument "${scopes[0]}".`, USAGE);
28
+ }
29
+ if (verb === 'set' && scopes.length === 0) {
30
+ throw usageError('Name the scope (or scopes) the team should hold.', `"ekanos sources scopes" lists them, e.g. "ekanos teams scope set ${team} Gold".`);
31
+ }
32
+ const env = resolveAuthEnvironment(ctx, args.host, args.env, {
33
+ projectDir: args.dir,
34
+ });
35
+ const resolved = await resolveSourceTarget(env, requireSession(env), args);
36
+ const client = new TeamsClient(env, resolved.session);
37
+ const target = { source: resolved.source, team };
38
+ const current = await client.call((token) => showTeamScopes(env.host, token, target));
39
+ if (verb === 'show') {
40
+ return ctx.succeed(Object.assign({ host: env.host }, current), describe(ctx.ui, current, resolved.source));
41
+ }
42
+ const wanted = scopes.map((scope) => scope.trim().toLowerCase());
43
+ const dropped = current.scopes.filter((scope) => !wanted.includes(scope.name.toLowerCase()) && !wanted.includes(scope.id));
44
+ if (dropped.length > 0 && !args.yes) {
45
+ throw invalidStateError(`${team} would no longer hold ${dropped.map((scope) => scope.name).join(', ')}: any product only ${dropped.length === 1 ? 'it shows' : 'they show'} disappears from the team in the app.`, `Re-run with --yes: ekanos teams scope set ${team} ${scopes.map(quote).join(' ')} --source ${resolved.source} --yes`);
46
+ }
47
+ const result = await client.call((token) => setTeamScopes(env.host, token, target, scopes));
48
+ return ctx.succeed(Object.assign({ host: env.host }, result), [
49
+ ctx.ui.result({
50
+ title: `${result.team.slug} holds ${names(result.scopes)}`,
51
+ tone: result.hidden.length > 0 ? 'warn' : 'ok',
52
+ rows: [
53
+ ['Before', names(result.before)],
54
+ ...(result.hidden.length > 0
55
+ ? [
56
+ [
57
+ 'No longer sees',
58
+ result.hidden.map((product) => product.slug),
59
+ ],
60
+ ]
61
+ : []),
62
+ ],
63
+ }),
64
+ describe(ctx.ui, result, resolved.source),
65
+ ].join('\n\n'));
66
+ }
67
+ function describe(ui, scopes, source) {
68
+ var _a, _b;
69
+ if (scopes.state === 'unscoped') {
70
+ return ui.result({
71
+ title: `${scopes.team.slug} sees every product ${scopes.source.slug} has`,
72
+ rows: [['Scopes', `none: ${scopes.source.slug} has no product scopes`]],
73
+ });
74
+ }
75
+ if (scopes.state === 'locked_out') {
76
+ return ui.result({
77
+ title: `${scopes.team.slug} sees no product in the app`,
78
+ tone: 'warn',
79
+ rows: [['Scopes', `none of ${scopes.source.slug}'s`]],
80
+ notes: [
81
+ `Give it one: ekanos teams scope set ${scopes.team.slug} <scope> --source ${source} ("ekanos sources scopes" lists them).`,
82
+ ],
83
+ });
84
+ }
85
+ return ui.result({
86
+ title: `What ${scopes.team.slug} sees in the app`,
87
+ rows: [
88
+ ['Scopes', names(scopes.scopes)],
89
+ [
90
+ 'Products',
91
+ ((_a = scopes.products) !== null && _a !== void 0 ? _a : []).length > 0
92
+ ? ((_b = scopes.products) !== null && _b !== void 0 ? _b : []).map((product) => product.slug)
93
+ : 'none',
94
+ ],
95
+ ],
96
+ notes: [
97
+ `"ekanos sources scopes add <scope> <product>" shows a product to every team on a scope.`,
98
+ ],
99
+ });
100
+ }
101
+ function names(scopes) {
102
+ return scopes.length > 0
103
+ ? scopes.map((scope) => scope.name).join(', ')
104
+ : 'no scope';
105
+ }
106
+ function quote(name) {
107
+ return /^[A-Za-z0-9._-]+$/.test(name) ? name : `"${name}"`;
108
+ }
109
+ //# sourceMappingURL=teams-scope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams-scope.js","sourceRoot":"","sources":["../../src/commands/teams-scope.ts"],"names":[],"mappings":"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","sourcesContent":["import type { TeamScopes } from '@ekanos/integration-schema';\n\nimport { requireSession, resolveAuthEnvironment } from '../auth/session';\nimport type { CliContext } from '../context';\nimport { invalidStateError, usageError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { resolveSourceTarget } from '../source-target';\nimport { setTeamScopes, showTeamScopes } from '../teams-api';\nimport type { Ui } from '../ui';\nimport { TeamsClient, type TeamsArgs } from './teams';\n\nconst USAGE =\n 'Run \"ekanos teams scope show <team>\" or \"ekanos teams scope set <team> <scope> [<scope>…] [--yes]\".';\n\n/**\n * `teams scope show|set <team>` — which of the Source's product scopes a team\n * holds, and so which products it sees in the app. `set` replaces them\n * (names or ids); taking one away can hide products the team uses, so that\n * needs `--yes`. The Source's scopes and their products: `sources scopes`.\n */\nexport async function runTeamScope(\n ctx: CliContext,\n args: TeamsArgs,\n rest: readonly string[],\n): Promise<ExitCode> {\n const [verb, rawTeam, ...scopes] = rest;\n if (verb !== 'show' && verb !== 'set') {\n throw usageError(\n verb\n ? `Unknown teams scope subcommand \"${verb}\".`\n : 'teams scope needs a subcommand.',\n USAGE,\n );\n }\n const team = rawTeam?.trim().toLowerCase();\n if (!team || !/^[a-z0-9][a-z0-9-]*$/.test(team)) {\n throw usageError(\n rawTeam\n ? `\"${rawTeam}\" is not a team slug.`\n : `teams scope ${verb} needs a team.`,\n USAGE,\n );\n }\n if (verb === 'show' && scopes.length > 0) {\n throw usageError(`Unexpected argument \"${scopes[0]}\".`, USAGE);\n }\n if (verb === 'set' && scopes.length === 0) {\n throw usageError(\n 'Name the scope (or scopes) the team should hold.',\n `\"ekanos sources scopes\" lists them, e.g. \"ekanos teams scope set ${team} Gold\".`,\n );\n }\n\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n const resolved = await resolveSourceTarget(env, requireSession(env), args);\n const client = new TeamsClient(env, resolved.session);\n const target = { source: resolved.source, team };\n\n const current = await client.call((token) =>\n showTeamScopes(env.host, token, target),\n );\n if (verb === 'show') {\n return ctx.succeed(\n { host: env.host, ...current },\n describe(ctx.ui, current, resolved.source),\n );\n }\n\n const wanted = scopes.map((scope) => scope.trim().toLowerCase());\n const dropped = current.scopes.filter(\n (scope) =>\n !wanted.includes(scope.name.toLowerCase()) && !wanted.includes(scope.id),\n );\n if (dropped.length > 0 && !args.yes) {\n throw invalidStateError(\n `${team} would no longer hold ${dropped.map((scope) => scope.name).join(', ')}: any product only ${dropped.length === 1 ? 'it shows' : 'they show'} disappears from the team in the app.`,\n `Re-run with --yes: ekanos teams scope set ${team} ${scopes.map(quote).join(' ')} --source ${resolved.source} --yes`,\n );\n }\n\n const result = await client.call((token) =>\n setTeamScopes(env.host, token, target, scopes),\n );\n return ctx.succeed(\n { host: env.host, ...result },\n [\n ctx.ui.result({\n title: `${result.team.slug} holds ${names(result.scopes)}`,\n tone: result.hidden.length > 0 ? 'warn' : 'ok',\n rows: [\n ['Before', names(result.before)],\n ...(result.hidden.length > 0\n ? ([\n [\n 'No longer sees',\n result.hidden.map((product) => product.slug),\n ],\n ] as Array<[string, string[]]>)\n : []),\n ],\n }),\n describe(ctx.ui, result, resolved.source),\n ].join('\\n\\n'),\n );\n}\n\nfunction describe(ui: Ui, scopes: TeamScopes, source: string): string {\n if (scopes.state === 'unscoped') {\n return ui.result({\n title: `${scopes.team.slug} sees every product ${scopes.source.slug} has`,\n rows: [['Scopes', `none: ${scopes.source.slug} has no product scopes`]],\n });\n }\n if (scopes.state === 'locked_out') {\n return ui.result({\n title: `${scopes.team.slug} sees no product in the app`,\n tone: 'warn',\n rows: [['Scopes', `none of ${scopes.source.slug}'s`]],\n notes: [\n `Give it one: ekanos teams scope set ${scopes.team.slug} <scope> --source ${source} (\"ekanos sources scopes\" lists them).`,\n ],\n });\n }\n return ui.result({\n title: `What ${scopes.team.slug} sees in the app`,\n rows: [\n ['Scopes', names(scopes.scopes)],\n [\n 'Products',\n (scopes.products ?? []).length > 0\n ? (scopes.products ?? []).map((product) => product.slug)\n : 'none',\n ],\n ],\n notes: [\n `\"ekanos sources scopes add <scope> <product>\" shows a product to every team on a scope.`,\n ],\n });\n}\n\nfunction names(scopes: Array<{ name: string }>): string {\n return scopes.length > 0\n ? scopes.map((scope) => scope.name).join(', ')\n : 'no scope';\n}\n\nfunction quote(name: string): string {\n return /^[A-Za-z0-9._-]+$/.test(name) ? name : `\"${name}\"`;\n}\n"]}
@@ -4,11 +4,11 @@ import { type AuthEnvironment } from '../auth/session.js';
4
4
  import type { CliContext } from '../context.js';
5
5
  import type { ExitCode } from '../exit-codes.js';
6
6
  import { type TeamsApiResult } from '../teams-api.js';
7
- export declare const TEAMS_SUBCOMMANDS: readonly ["list", "show", "app", "webhook", "create", "members", "roles", "connect"];
7
+ export declare const TEAMS_SUBCOMMANDS: readonly ["list", "show", "app", "webhook", "events", "scope", "create", "rename", "members", "roles", "connect", "disconnect"];
8
8
  export interface TeamsArgs {
9
9
  host?: string;
10
10
  source?: string;
11
- /** `list`, `show <team> <product>`, `app set <team> <product>`, `webhook <team> <product> [<id>]`. */
11
+ /** `list`, `show <team> [<product>]`, `app set <team> <product>`, `webhook <team> <product> [<id>]`. */
12
12
  positionals: readonly string[];
13
13
  connection?: string;
14
14
  /** `create --slug`, `create --owner <email>`, `members invite --role`. */
@@ -22,6 +22,8 @@ export interface TeamsArgs {
22
22
  noOpen?: boolean;
23
23
  /** `connect --link-only`: print the team's page, as before connect did the work. */
24
24
  linkOnly?: boolean;
25
+ /** `connect --reconnect`: connect again though it reads as connected. */
26
+ reconnect?: boolean;
25
27
  /** Test seams for `connect`: the wait between polls, and the browser opener. */
26
28
  pollDelayMs?: number;
27
29
  openUrl?: (url: string) => void;
@@ -1,15 +1,34 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
12
+ import { loginHint } from '../auth/host-trust.js';
1
13
  import { refreshStoredSession, requireSession, resolveAuthEnvironment, } from '../auth/session.js';
2
14
  import { authRequiredError, invalidStateError, usageError, validationError, } from '../errors.js';
15
+ import { ensureSecretsDir, writeSecretFile } from '../secret-file.js';
3
16
  import { resolveSourceTarget } from '../source-target.js';
4
17
  import { listTeams, mintTeamWebhook, setTeamApp, showTeamIntegration, } from '../teams-api.js';
5
18
  import { promptHidden, promptLine } from './apps.js';
19
+ import { scopeHiddenLines } from './scope-notice.js';
6
20
  import { isTeamAgentArea, runTeamAgentCommands } from './team-agents.js';
21
+ import { runTeamEvents } from './teams-events.js';
7
22
  import { ONBOARDING_SUBCOMMANDS, isOnboardingSubcommand, parseOnboarding, runOnboarding, } from './teams-onboard.js';
23
+ import { runTeamOverview } from './teams-overview.js';
24
+ import { runTeamScope } from './teams-scope.js';
8
25
  export const TEAMS_SUBCOMMANDS = [
9
26
  'list',
10
27
  'show',
11
28
  'app',
12
29
  'webhook',
30
+ 'events',
31
+ 'scope',
13
32
  ...ONBOARDING_SUBCOMMANDS,
14
33
  ];
15
34
  /**
@@ -30,12 +49,18 @@ export async function runTeams(ctx, args) {
30
49
  return runTeamAgentCommands(ctx, args);
31
50
  }
32
51
  const subcommand = parseSubcommand(rawSubcommand);
52
+ if (subcommand === 'events') {
53
+ return runTeamEvents(ctx, args, rest);
54
+ }
55
+ if (subcommand === 'scope') {
56
+ return runTeamScope(ctx, args, rest);
57
+ }
33
58
  if (isOnboardingSubcommand(subcommand)) {
34
59
  const plan = parseOnboarding(subcommand, rest, Object.assign(Object.assign({}, args), { as: typeof ((_a = args.flags) === null || _a === void 0 ? void 0 : _a.as) === 'string' ? args.flags.as : undefined }));
35
60
  const env = resolveAuthEnvironment(ctx, args.host, args.env, {
36
61
  projectDir: args.dir,
37
62
  });
38
- const resolved = await resolveSourceTarget(env, requireSession(env.store, env.host), args);
63
+ const resolved = await resolveSourceTarget(env, requireSession(env), args);
39
64
  return runOnboarding(ctx, args, new TeamsClient(env, resolved.session), env.host, resolved.source, plan);
40
65
  }
41
66
  if (subcommand === 'app' && rest[0] !== 'set') {
@@ -48,7 +73,11 @@ export async function runTeams(ctx, args) {
48
73
  throw usageError('teams list takes no arguments.', 'Run "ekanos teams list --source <slug>".');
49
74
  }
50
75
  const team = subcommand === 'list' ? '' : requireSlug(operands[0], 'team');
51
- const product = subcommand === 'list' ? '' : requireSlug(operands[1], 'product');
76
+ // `teams show <team>` alone is the team's overview.
77
+ const overview = subcommand === 'show' && operands[1] === undefined;
78
+ const product = subcommand === 'list' || overview
79
+ ? ''
80
+ : requireSlug(operands[1], 'product');
52
81
  const extra = operands.slice(subcommand === 'webhook' ? 3 : 2);
53
82
  const appEdits = subcommand === 'app' ? parseAppEdits(args.flags) : null;
54
83
  if (subcommand !== 'list' && extra.length > 0) {
@@ -60,7 +89,7 @@ export async function runTeams(ctx, args) {
60
89
  const env = resolveAuthEnvironment(ctx, args.host, args.env, {
61
90
  projectDir: args.dir,
62
91
  });
63
- const resolved = await resolveSourceTarget(env, requireSession(env.store, env.host), args);
92
+ const resolved = await resolveSourceTarget(env, requireSession(env), args);
64
93
  const client = new TeamsClient(env, resolved.session);
65
94
  const source = resolved.source;
66
95
  if (subcommand === 'list') {
@@ -77,17 +106,20 @@ export async function runTeams(ctx, args) {
77
106
  `${entry.name} · ${entry.memberCount} member${entry.memberCount === 1 ? '' : 's'}`,
78
107
  ]),
79
108
  notes: [
80
- '"ekanos teams show <team> <product>" shows a team\'s setup.',
109
+ '"ekanos teams show <team>" shows a team\'s setup; add a product for its checklist.',
81
110
  ],
82
111
  }));
83
112
  }
113
+ if (overview) {
114
+ return runTeamOverview(ctx, client, env.host, source, team);
115
+ }
84
116
  const target = { source, team, product };
85
117
  const status = await client.call((token) => showTeamIntegration(env.host, token, target));
86
118
  if (subcommand === 'show') {
87
119
  return ctx.succeed(Object.assign({ host: env.host }, status), checklist(status).join('\n'));
88
120
  }
89
121
  if (!status.enabled) {
90
- throw invalidStateError(`"${product}" is not enabled on ${status.source.slug}.`, '"ekanos catalog" lists what this Source has; Ekanos enables a product for a Source.');
122
+ throw invalidStateError(`"${product}" is not enabled on ${status.source.slug}.`, `"ekanos sources products --source ${status.source.slug}" shows what it has; only a super-admin enables a product for a Source (in /admin).`);
91
123
  }
92
124
  if (subcommand === 'app' && appEdits) {
93
125
  return setApp(ctx, args, client, env.host, target, status, appEdits);
@@ -242,19 +274,29 @@ async function mintWebhook(ctx, args, client, host, target, status, requested) {
242
274
  if (webhook.endpoint && !args.yes) {
243
275
  throw invalidStateError(`${target.team} already has a live URL for ${webhook.webhookId} (${webhook.endpoint.tokenPrefix}…). A new one revokes it, and the provider's webhook stops working until you paste the new URL there.`, `Re-run with --yes when you are ready to update the provider: ekanos teams webhook ${target.team} ${target.product} ${webhook.webhookId} --yes`);
244
276
  }
277
+ // The URL carries its token, so it goes to a 0600 file, never the output.
278
+ const secrets = ensureSecretsDir(args.env);
245
279
  const minted = await client.call((token) => mintTeamWebhook(host, token, target, webhook.webhookId));
246
- return ctx.succeed(Object.assign({ host }, minted), ctx.ui.result({
280
+ const { url } = minted, rest = __rest(minted, ["url"]);
281
+ const secretFile = writeSecretFile(secrets, {
282
+ owner: target.team,
283
+ what: `${target.product}-${webhook.webhookId}-webhook-url`,
284
+ purpose: `Webhook URL for team ${target.team}'s ${status.product.name} (${webhook.webhookId}). Paste it into ${status.product.name}'s webhook settings for this team; the token is part of the URL.`,
285
+ values: [['WEBHOOK_URL', url]],
286
+ again: `ekanos teams webhook ${target.team} ${target.product} ${webhook.webhookId} --yes`,
287
+ });
288
+ return ctx.succeed(Object.assign(Object.assign({ host }, rest), { secretFile }), ctx.ui.result({
247
289
  title: `Webhook URL for ${target.team} · ${webhook.webhookId}`,
248
290
  tone: 'warn',
249
291
  rows: [
250
- ['URL', minted.url],
292
+ ['URL', `saved to ${secretFile} (readable only by you)`],
251
293
  [
252
294
  'Paste it into',
253
295
  `${status.product.name}'s webhook settings for this team`,
254
296
  ],
255
297
  ],
256
298
  notes: [
257
- 'This URL is shown once and cannot be read again. Paste it now.',
299
+ 'The URL carries its token, so it is shown nowhere else and cannot be read again. Copy it from that file into the provider yourself — not into a chat or an agent session — then delete the file.',
258
300
  ...(minted.replaced
259
301
  ? ['The previous URL is revoked: the provider must use this one.']
260
302
  : []),
@@ -270,10 +312,12 @@ export function checklist(status) {
270
312
  ` ${mark(status.enabled)} Enabled on the Source`,
271
313
  ];
272
314
  if (!status.enabled) {
273
- lines.push(' "ekanos catalog" lists what this Source has; Ekanos enables a product for a Source.');
315
+ lines.push(` "ekanos sources products --source ${status.source.slug}" shows what it has; only a super-admin enables a product for a Source (in /admin).`);
274
316
  return lines;
275
317
  }
276
318
  const target = { team: status.team.slug, product: status.product.slug };
319
+ lines.push(...scopeHiddenLines(status.scope, Object.assign(Object.assign({}, target), { source: status.source.slug })).map((line) => ` ${line}`));
320
+ lines.push(...connectionLines(status));
277
321
  for (const app of status.teamApps) {
278
322
  lines.push(` Team app: ${app.label} (${app.connectionId})`);
279
323
  if ((_a = app.setup) === null || _a === void 0 ? void 0 : _a.consoleUrl)
@@ -300,11 +344,11 @@ export function checklist(status) {
300
344
  }
301
345
  lines.push(app.connection
302
346
  ? ` ${mark(true)} Connected ${day(app.connection.connectedAt)}${app.connection.connectedBy ? ` by ${who(app.connection.connectedBy)}` : ''}`
303
- : ` ${mark(false)} Connected: a team member clicks Connect at ${status.connectUrl}`);
347
+ : ` ${mark(false)} Connected: not yet\n → in your terminal: ${connectCommand(status, app.connectionId)}`);
304
348
  }
305
349
  lines.push(status.activation
306
350
  ? ` ${mark(true)} Activated for the team${status.activation.activatedAt ? ` ${day(status.activation.activatedAt)}` : ''}${status.activation.activatedBy ? ` by ${who(status.activation.activatedBy)}` : ''}`
307
- : ` ${mark(false)} Activated: a team member connects at ${status.connectUrl}`);
351
+ : ` ${mark(false)} Activated: not yet\n → in your terminal: ${connectCommand(status)}`);
308
352
  lines.push(` Team page: ${status.connectUrl}`);
309
353
  if (status.agentEvents.length > 0) {
310
354
  lines.push(' Agent events it offers:');
@@ -312,6 +356,61 @@ export function checklist(status) {
312
356
  }
313
357
  return lines;
314
358
  }
359
+ /**
360
+ * Each OAuth connection in one line — connected, or installed for a named
361
+ * one, with its app's credentials and webhook URL — and which of the team's
362
+ * agents wait on it. A named connection an agent's trigger needs and the team
363
+ * has not installed is the gap no other line names: the trigger is stored
364
+ * and on, and nothing ever reaches it.
365
+ */
366
+ function connectionLines(status) {
367
+ var _a, _b, _c, _d;
368
+ const connections = (_a = status.connections) !== null && _a !== void 0 ? _a : [];
369
+ if (connections.length === 0)
370
+ return [];
371
+ const lines = [' Connections:'];
372
+ for (const entry of connections) {
373
+ const named = entry.connectionId !== 'default';
374
+ const app = status.teamApps.find((candidate) => candidate.connectionId === entry.connectionId);
375
+ const webhooks = (_b = app === null || app === void 0 ? void 0 : app.webhooks) !== null && _b !== void 0 ? _b : [];
376
+ const steps = [
377
+ ...(entry.credentialsSaved === null
378
+ ? []
379
+ : [
380
+ `app credentials ${entry.credentialsSaved ? 'saved' : 'not saved'}`,
381
+ ]),
382
+ named
383
+ ? entry.connected
384
+ ? `installed ${day((_c = entry.connectedAt) !== null && _c !== void 0 ? _c : '')}`
385
+ : 'not installed'
386
+ : entry.connected
387
+ ? `connected ${day((_d = entry.connectedAt) !== null && _d !== void 0 ? _d : '')}`
388
+ : 'not connected',
389
+ ...(webhooks.length > 0
390
+ ? [
391
+ webhooks.every((webhook) => webhook.endpoint)
392
+ ? 'webhook URL made'
393
+ : 'no webhook URL',
394
+ ]
395
+ : []),
396
+ ];
397
+ const ready = entry.connected &&
398
+ entry.credentialsSaved !== false &&
399
+ webhooks.every((webhook) => webhook.endpoint);
400
+ lines.push(` ${mark(ready)} ${entry.label} (${entry.connectionId}): ${steps.join(' · ')}${entry.connectedBy ? ` by ${who(entry.connectedBy)}` : ''}`);
401
+ if (entry.neededBy.length > 0) {
402
+ lines.push(` Needed by: ${entry.neededBy.map((need) => `${need.agent} (${need.triggers.join(', ')})`).join(', ')}`);
403
+ }
404
+ if (!entry.connected) {
405
+ lines.push(` → in your terminal: ${connectCommand(status, entry.connectionId)}`);
406
+ }
407
+ }
408
+ return lines;
409
+ }
410
+ /** The command that connects the team, for a hint: `--connection` only for a named one. */
411
+ function connectCommand(status, connectionId = 'default') {
412
+ return `npx @ekanos/cli teams connect ${status.team.slug} ${status.product.slug}${connectionId === 'default' ? '' : ` --connection ${connectionId}`} --source ${status.source.slug}`;
413
+ }
315
414
  function appWalkthrough(status, app) {
316
415
  var _a, _b;
317
416
  const lines = [
@@ -337,7 +436,7 @@ function nextSteps(status, app, target) {
337
436
  .map((webhook) => `Next: "ekanos teams webhook ${target.team} ${target.product} ${webhook.webhookId}" for the provider's webhook URL.`),
338
437
  ...(app.connection
339
438
  ? []
340
- : [`Then a team member clicks Connect at ${status.connectUrl}`]),
439
+ : [`Then connect it: ${connectCommand(status, app.connectionId)}`]),
341
440
  ];
342
441
  }
343
442
  export function chooseApp(status, connection) {
@@ -369,7 +468,7 @@ export class TeamsClient {
369
468
  }
370
469
  if (result.status === 'ok')
371
470
  return result.data;
372
- throw authRequiredError(`The session for ${this.env.host} could not authorize the teams call.`, `Run "ekanos login --host ${this.env.host}" and retry.`);
471
+ throw authRequiredError(`The session for ${this.env.host} could not authorize the teams call.`, loginHint(this.env));
373
472
  }
374
473
  }
375
474
  export function canPrompt(ctx, args) {
@@ -382,7 +481,7 @@ function parseSubcommand(value) {
382
481
  return match;
383
482
  throw usageError(value
384
483
  ? `Unknown teams subcommand "${value}".`
385
- : 'teams needs a subcommand.', 'Run one of: ekanos teams list, ekanos teams create <name>, ekanos teams members list <team>, ekanos teams roles list <team>, ekanos teams connect <team> <product>, ekanos teams show <team> <product>, ekanos teams app set <team> <product>, ekanos teams webhook <team> <product> [<webhook-id>], ekanos teams agents|triggers|links|mcp ….');
484
+ : 'teams needs a subcommand.', 'Run one of: ekanos teams list, ekanos teams create <name>, ekanos teams rename <team> <name>, ekanos teams members list <team>, ekanos teams roles list <team>, ekanos teams connect <team> <product>, ekanos teams disconnect <team> <product> --yes, ekanos teams show <team> [<product>], ekanos teams app set <team> <product>, ekanos teams webhook <team> <product> [<webhook-id>], ekanos teams events <team> [<product>], ekanos teams scope show|set <team> …, ekanos teams agents|triggers|links|mcp ….');
386
485
  }
387
486
  function requireSlug(value, what) {
388
487
  const slug = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();