@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.
- package/README.md +25 -8
- package/dist/agents-api.d.ts +11 -2
- package/dist/agents-api.js +39 -3
- package/dist/agents-api.js.map +1 -1
- package/dist/apps-api.js +2 -2
- package/dist/apps-api.js.map +1 -1
- package/dist/auth/ekanos-api.d.ts +16 -2
- package/dist/auth/ekanos-api.js +101 -15
- package/dist/auth/ekanos-api.js.map +1 -1
- package/dist/auth/host-trust.d.ts +46 -0
- package/dist/auth/host-trust.js +79 -0
- package/dist/auth/host-trust.js.map +1 -0
- package/dist/auth/session.d.ts +10 -1
- package/dist/auth/session.js +28 -28
- package/dist/auth/session.js.map +1 -1
- package/dist/commands/agents-edit.d.ts +6 -2
- package/dist/commands/agents-edit.js +57 -26
- package/dist/commands/agents-edit.js.map +1 -1
- package/dist/commands/agents.d.ts +13 -2
- package/dist/commands/agents.js +140 -35
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/apps.d.ts +2 -0
- package/dist/commands/apps.js +16 -12
- package/dist/commands/apps.js.map +1 -1
- package/dist/commands/catalog.js +7 -6
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/knowledge.d.ts +19 -0
- package/dist/commands/knowledge.js +119 -27
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/login.d.ts +4 -0
- package/dist/commands/login.js +30 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.d.ts +9 -1
- package/dist/commands/logout.js +56 -7
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/publish.js +3 -3
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/reviewers.d.ts +2 -0
- package/dist/commands/reviewers.js +7 -3
- package/dist/commands/reviewers.js.map +1 -1
- package/dist/commands/routes.d.ts +2 -0
- package/dist/commands/routes.js +42 -9
- package/dist/commands/routes.js.map +1 -1
- package/dist/commands/runs.d.ts +13 -2
- package/dist/commands/runs.js +99 -16
- package/dist/commands/runs.js.map +1 -1
- package/dist/commands/scope-notice.d.ts +21 -0
- package/dist/commands/scope-notice.js +35 -0
- package/dist/commands/scope-notice.js.map +1 -0
- package/dist/commands/skills.d.ts +7 -1
- package/dist/commands/skills.js +80 -24
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/sources.d.ts +1 -1
- package/dist/commands/sources.js +126 -12
- package/dist/commands/sources.js.map +1 -1
- package/dist/commands/status.js +5 -4
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/team-agent-readiness.d.ts +49 -0
- package/dist/commands/team-agent-readiness.js +308 -0
- package/dist/commands/team-agent-readiness.js.map +1 -0
- package/dist/commands/team-agents-agents.d.ts +24 -3
- package/dist/commands/team-agents-agents.js +314 -35
- package/dist/commands/team-agents-agents.js.map +1 -1
- package/dist/commands/team-agents-mcp.js +32 -3
- package/dist/commands/team-agents-mcp.js.map +1 -1
- package/dist/commands/team-agents-triggers.js +230 -55
- package/dist/commands/team-agents-triggers.js.map +1 -1
- package/dist/commands/team-agents.d.ts +3 -1
- package/dist/commands/team-agents.js +27 -7
- package/dist/commands/team-agents.js.map +1 -1
- package/dist/commands/teams-channel-install.d.ts +24 -0
- package/dist/commands/teams-channel-install.js +94 -0
- package/dist/commands/teams-channel-install.js.map +1 -0
- package/dist/commands/teams-connect.d.ts +41 -0
- package/dist/commands/teams-connect.js +212 -57
- package/dist/commands/teams-connect.js.map +1 -1
- package/dist/commands/teams-events.d.ts +12 -0
- package/dist/commands/teams-events.js +138 -0
- package/dist/commands/teams-events.js.map +1 -0
- package/dist/commands/teams-onboard.d.ts +11 -3
- package/dist/commands/teams-onboard.js +60 -7
- package/dist/commands/teams-onboard.js.map +1 -1
- package/dist/commands/teams-overview.d.ts +11 -0
- package/dist/commands/teams-overview.js +160 -0
- package/dist/commands/teams-overview.js.map +1 -0
- package/dist/commands/teams-scope.d.ts +10 -0
- package/dist/commands/teams-scope.js +109 -0
- package/dist/commands/teams-scope.js.map +1 -0
- package/dist/commands/teams.d.ts +4 -2
- package/dist/commands/teams.js +113 -14
- package/dist/commands/teams.js.map +1 -1
- package/dist/commands/upgrade.js +12 -2
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/commands/use.js +2 -2
- package/dist/commands/use.js.map +1 -1
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/connections-api.js +1 -1
- package/dist/connections-api.js.map +1 -1
- package/dist/delegate.js +11 -4
- package/dist/delegate.js.map +1 -1
- package/dist/harness-scaffold.js +12 -0
- package/dist/harness-scaffold.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +682 -167
- package/dist/index.js.map +1 -1
- package/dist/knowledge-api.d.ts +46 -0
- package/dist/knowledge-api.js +32 -1
- package/dist/knowledge-api.js.map +1 -1
- package/dist/open-url.d.ts +16 -0
- package/dist/open-url.js +73 -0
- package/dist/open-url.js.map +1 -0
- package/dist/parse-argv.d.ts +2 -0
- package/dist/parse-argv.js.map +1 -1
- package/dist/publish-api.d.ts +2 -1
- package/dist/publish-api.js +4 -3
- package/dist/publish-api.js.map +1 -1
- package/dist/pull-write.d.ts +27 -0
- package/dist/pull-write.js +60 -0
- package/dist/pull-write.js.map +1 -0
- package/dist/routes-api.d.ts +3 -0
- package/dist/routes-api.js +9 -4
- package/dist/routes-api.js.map +1 -1
- package/dist/runs-api.d.ts +7 -1
- package/dist/runs-api.js +21 -6
- package/dist/runs-api.js.map +1 -1
- package/dist/safe-fs.d.ts +2 -0
- package/dist/safe-fs.js +12 -6
- package/dist/safe-fs.js.map +1 -1
- package/dist/secret-file.d.ts +17 -0
- package/dist/secret-file.js +69 -0
- package/dist/secret-file.js.map +1 -0
- package/dist/skills-api.d.ts +10 -2
- package/dist/skills-api.js +37 -2
- package/dist/skills-api.js.map +1 -1
- package/dist/source-products-api.d.ts +4 -0
- package/dist/source-products-api.js +14 -4
- package/dist/source-products-api.js.map +1 -1
- package/dist/source-target.js +2 -1
- package/dist/source-target.js.map +1 -1
- package/dist/sources-api.d.ts +2 -1
- package/dist/sources-api.js +3 -2
- package/dist/sources-api.js.map +1 -1
- package/dist/team-connect-api.d.ts +44 -2
- package/dist/team-connect-api.js +19 -7
- package/dist/team-connect-api.js.map +1 -1
- package/dist/teams-api.d.ts +41 -1
- package/dist/teams-api.js +95 -19
- package/dist/teams-api.js.map +1 -1
- package/dist/toolchain-api.d.ts +9 -0
- package/dist/toolchain-api.js +17 -2
- package/dist/toolchain-api.js.map +1 -1
- package/dist/toolchain-resolve.d.ts +3 -0
- package/dist/toolchain-resolve.js +15 -1
- package/dist/toolchain-resolve.js.map +1 -1
- package/dist/toolchain.d.ts +6 -0
- package/dist/toolchain.js +19 -0
- package/dist/toolchain.js.map +1 -1
- package/dist/update-notice.js +2 -1
- package/dist/update-notice.js.map +1 -1
- package/package.json +2 -2
- package/templates/AGENTS.md.tmpl +6 -1
- package/templates/claude-skill.md.tmpl +5 -0
- package/templates/package.json.tmpl +4 -4
- package/templates/skills/build-agent/SKILL.md +104 -17
- package/templates/skills/build-agent/references/agent-json.md +4 -2
- package/templates/skills/build-agent/references/rollout.md +23 -4
- package/templates/skills/build-agent/references/set-up-an-integration.md +55 -7
- package/templates/skills/build-agent/references/skills.md +20 -1
- package/templates/skills/build-agent/references/triggers.md +47 -11
- package/templates/skills/build-integration/references/widget-design.md +3 -3
- package/templates/skills/ekanos-platform/SKILL.md +45 -16
- 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":"AAAA,OAAO,EAGL,mBAAmB,EACnB,mBAAmB,GAEpB,MAAM,4BAA4B,CAAC;AAGpC,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EACL,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,cAAc,GACf,MAAM,wBAAwB,CAAC;AAGhC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAe,EACf,MAAmB,EACnB,IAAY,EACZ,MAAc,EACd,IAAY;;;IAEZ,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QACtD,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACpB,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,mBAAmB,EAAE;YACrC,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,sBAAsB;YAC5B,MAAM;SACP,CAAC,CACH;QACD,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACpB,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,mBAAmB,EAAE;YACrC,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,sBAAsB,kBAAkB,CAAC,IAAI,CAAC,SAAS;YAC7D,MAAM;SACP,CAAC,CACH;KACF,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC7D,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,aAAa,CACjB,YAAY,IAAI,QAAQ,KAAK,CAAC,MAAM,CAAC,IAAI,GAAG,EAC5C,0DAA0D,CAC3D,CAAC;IACJ,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,MAAM,CAC1C,CAAC,OAAO,EAAE,EAAE,CACV,OAAO,CAAC,IAAI,KAAK,aAAa,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,CACvE,CAAC;IACF,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,YAAY,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAC5D,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACpB,mBAAmB,CAAC,IAAI,EAAE,KAAK,EAAE;QAC/B,MAAM;QACN,IAAI;QACJ,OAAO,EAAE,OAAO,CAAC,IAAI;KACtB,CAAC,CACH,CACF,CAAC;IAEF,2EAA2E;IAC3E,MAAM,KAAK,GAAG,IAAI,GAAG,EAAuB,CAAC;IAC7C,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClC,KAAK,MAAM,IAAI,UAAI,MAAA,KAAK,CAAC,KAAK,0CAAE,YAAY,mCAAI,EAAE,EAAE,CAAC;YACnD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBACpD,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAChC,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,kBAAC,CAAC;QAC9C,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,EAAE,EAAE,CAAA,MAAA,KAAK,CAAC,KAAK,0CAAE,MAAM,MAAK,QAAQ;QACpC,SAAS,EAAE,cAAc,CAAC,KAAK,EAAE,MAAM,CAAC;KACzC,CAAC,GAAA,CAAC,CAAC;IACJ,MAAM,eAAe,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QAC5D,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,IAAI,EAAE,SAAS,CACb,eAAe,CACb,OAAO,EACP,QAAQ,CAAC,KAAK,CAAE,EAChB,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EACvB,MAAM,CACP,EACD,QAAQ,CAAC,KAAK,CAAE,kCACX,MAAM,KAAE,OAAO,EAAE,OAAO,CAAC,IAAI,IACnC;KACF,CAAC,CAAC,CAAC;IAEJ,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,CAAC,EACD,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EACrD,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAClD,CAAC;IACF,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;IAElE,OAAO,GAAG,CAAC,OAAO,CAChB;QACE,IAAI;QACJ,IAAI,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,WAAW,EAAE;QACtE,YAAY,EAAE,eAAe,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,kBAAC,CAAC;YACjD,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC;YACvB,UAAU,QAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,mCAAI,IAAI;SACxC,CAAC,GAAA,CAAC;QACH,MAAM,EAAE,SAAS;KAClB,EACD;QACE,GAAG,GAAG,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,WAAW,UAAU,GAAG,CAAC,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,MAAM,SAAS,SAAS,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,OAAO;QAC3N,EAAE;QACF,cAAc;QACd,GAAG,CAAC,eAAe,CAAC,MAAM,KAAK,CAAC;YAC9B,CAAC,CAAC,CAAC,+BAA+B,CAAC;YACnC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC;gBAC/B,KAAK,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE;gBACvC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,EAAE,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;aAC7D,CAAC,CAAC;QACP,EAAE;QACF,QAAQ;QACR,GAAG,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;YACxB,CAAC,CAAC,CAAC,iBAAiB,CAAC;YACrB,CAAC,CAAC,SAAS,CAAC,GAAG,CACX,CAAC,GAAG,EAAE,EAAE,CACN,KAAK,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,QAAQ,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAC/G,CAAC;QACN,EAAE;QACF,6BAA6B,IAAI,kDAAkD,IAAI,8BAA8B;KACtH,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CACtB,OAAuB,EACvB,MAA6B,EAC7B,IAA6B,EAC7B,MAAwC;;IAExC,IAAI,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,KAAK,MAAK,QAAQ,IAAI,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,GAAG,MAAK,QAAQ,EAAE,CAAC;QACvD,MAAM,OAAO,GACX,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,MAAM,KAAK,eAAe;YAC5C,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC;YAC3B,CAAC,CAAC,IAAI,CAAC;QACX,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,SAAS;YAC3B,CAAC,CAAC,6BAA6B,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,YAAY,CAAC,aAAa,EAAE;YACtE,CAAC,CAAC,IAAI,CAAC,GAAG,KAAK,QAAQ;gBACrB,CAAC,CAAC,0BAA0B,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE;gBAClF,CAAC,CAAC,0BAA0B,OAAO,aAAP,OAAO,cAAP,OAAO,GAAI,mCAAmC,GAAG,CAAC;QAClF,OAAO;YACL,KAAK;YACL,GAAG,EACD,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,KAAK,QAAQ;gBACrC,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC;SACvD,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC;QAChC,OAAO,EAAE,KAAK,EAAE,oBAAoB,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;IACpD,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;QAC7B,MAAM,MAAM,GACV,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,GAAG,MAAK,QAAQ,IAAI,IAAI,CAAC,SAAS;YACtC,CAAC,CAAC,IAAI,CAAC,SAAS;gBACd,CAAC,CAAC,QAAQ,IAAI,CAAC,SAAS,oCAAoC;gBAC5D,CAAC,CAAC,QAAQ,IAAI,CAAC,SAAS,wCAAwC;YAClE,CAAC,CAAC,EAAE,CAAC;QACT,OAAO;YACL,KAAK,EAAE,2CAA2C,MAAM,EAAE;YAC1D,GAAG,EACD,IAAI,IAAI,CAAC,IAAI,CAAC,SAAS;gBACrB,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC;gBAClD,CAAC,CAAC,IAAI;SACX,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAChE,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC;IACnE,MAAM,WAAW,GACf,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,kBAAkB,IAAI,IAAI,QAAQ,CAAC,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9E,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,MAAM,EAAE,GAAG,MAAA,MAAM,CAAC,UAAU,CAAC,WAAW,0CAAE,KAAK,CAAC;QAChD,OAAO;YACL,KAAK,EAAE,cAAc,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,WAAW,EAAE;YAClJ,GAAG,EAAE,IAAI;SACV,CAAC;IACJ,CAAC;IACD,OAAO;QACL,KAAK,EAAE,kBAAkB,WAAW,EAAE;QACtC,GAAG,EAAE,iCAAiC,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,aAAa,MAAM,CAAC,MAAM,EAAE;KAC9F,CAAC;AACJ,CAAC;AAED,0FAA0F;AAC1F,SAAS,SAAS,CAChB,IAA2C,EAC3C,MAA6B,EAC7B,MAAyD;;;IAEzD,IAAI,EAAC,MAAA,MAAM,CAAC,KAAK,0CAAE,MAAM,CAAA;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO;QACL,KAAK,EAAE,GAAG,IAAI,CAAC,KAAK,QAAQ,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;QAC5D,GAAG,QAAE,IAAI,CAAC,GAAG,mCAAI,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC;KAChD,CAAC;AACJ,CAAC;AAED,oEAAoE;AACpE,KAAK,UAAU,SAAS,CACtB,KAAU,EACV,IAAY,EACZ,IAA6B;IAE7B,MAAM,OAAO,GAAQ,EAAE,CAAC;IACxB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC,MAAM,EAAE,KAAK,IAAI,IAAI,EAAE,CAAC;QACxD,OAAO,CAAC,IAAI,CACV,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CACnE,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC","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"]}
|
package/dist/commands/teams.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/dist/commands/teams.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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>
|
|
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}.`,
|
|
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
|
-
|
|
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',
|
|
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
|
-
'
|
|
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(
|
|
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:
|
|
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:
|
|
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
|
|
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.`,
|
|
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
|
|
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();
|