@ekanos/cli 0.12.0 → 0.14.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.d.ts +3 -2
- package/dist/apps-api.js +3 -7
- 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 +148 -36
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/apps.d.ts +15 -4
- package/dist/commands/apps.js +133 -51
- 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 +56 -0
- package/dist/commands/knowledge.js +456 -0
- package/dist/commands/knowledge.js.map +1 -0
- 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 +96 -15
- 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 +4 -0
- package/dist/commands/sources.js +233 -5
- 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 +59 -0
- package/dist/commands/team-agent-readiness.js +341 -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 +336 -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 -54
- 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 +66 -0
- package/dist/commands/teams-connect.js +397 -0
- package/dist/commands/teams-connect.js.map +1 -0
- 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 +16 -5
- package/dist/commands/teams-onboard.js +61 -36
- 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 +64 -7
- package/dist/commands/teams.js +254 -60
- 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 +724 -152
- package/dist/index.js.map +1 -1
- package/dist/knowledge-api.d.ts +175 -0
- package/dist/knowledge-api.js +169 -0
- package/dist/knowledge-api.js.map +1 -0
- 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 +63 -0
- package/dist/source-products-api.js +123 -0
- package/dist/source-products-api.js.map +1 -0
- 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 +286 -0
- package/dist/team-connect-api.js +107 -0
- package/dist/team-connect-api.js.map +1 -0
- package/dist/teams-api.d.ts +42 -1
- package/dist/teams-api.js +98 -20
- 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 +115 -16
- package/templates/skills/build-agent/references/agent-json.md +26 -8
- package/templates/skills/build-agent/references/app-setup.md +2 -1
- package/templates/skills/build-agent/references/rollout.md +27 -4
- package/templates/skills/build-agent/references/set-up-an-integration.md +74 -15
- package/templates/skills/build-agent/references/skills.md +20 -1
- package/templates/skills/build-agent/references/triggers.md +84 -20
- package/templates/skills/build-integration/references/widget-design.md +3 -3
- package/templates/skills/ekanos-platform/SKILL.md +70 -17
- package/templates/skills/polish-widgets/references/patterns.md +2 -2
package/dist/commands/teams.js
CHANGED
|
@@ -1,39 +1,67 @@
|
|
|
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
|
/**
|
|
16
35
|
* `teams` — a Source admin's view of its teams' integration setup, the
|
|
17
36
|
* team-level counterpart of `apps`. `list` names the teams; `show` reads one
|
|
18
|
-
* team's setup of one product as a checklist; `app set` enters
|
|
19
|
-
* app (Zendesk's subdomain, client and signing
|
|
20
|
-
*
|
|
21
|
-
*
|
|
37
|
+
* team's setup of one product as a checklist; `app set` enters or updates
|
|
38
|
+
* any of a team's own app fields (Zendesk's subdomain, client and signing
|
|
39
|
+
* secret), one at a time if need be — a field left alone keeps its saved
|
|
40
|
+
* value. Secrets are only ever typed at the admin's terminal, never from a
|
|
41
|
+
* flag, a file or a non-interactive run, so an agent driving the CLI never
|
|
42
|
+
* handles one; a non-secret field may be passed as `--set name=value`; `webhook` mints the URL the
|
|
22
43
|
* team's provider sends to, shown once.
|
|
23
44
|
*/
|
|
24
45
|
export async function runTeams(ctx, args) {
|
|
46
|
+
var _a;
|
|
25
47
|
const [rawSubcommand, ...rest] = args.positionals;
|
|
26
48
|
if (isTeamAgentArea(rawSubcommand)) {
|
|
27
49
|
return runTeamAgentCommands(ctx, args);
|
|
28
50
|
}
|
|
29
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
|
+
}
|
|
30
58
|
if (isOnboardingSubcommand(subcommand)) {
|
|
31
|
-
const plan = parseOnboarding(subcommand, rest, args);
|
|
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 }));
|
|
32
60
|
const env = resolveAuthEnvironment(ctx, args.host, args.env, {
|
|
33
61
|
projectDir: args.dir,
|
|
34
62
|
});
|
|
35
|
-
const resolved = await resolveSourceTarget(env, requireSession(env
|
|
36
|
-
return runOnboarding(ctx, new TeamsClient(env, resolved.session), env.host, resolved.source, plan);
|
|
63
|
+
const resolved = await resolveSourceTarget(env, requireSession(env), args);
|
|
64
|
+
return runOnboarding(ctx, args, new TeamsClient(env, resolved.session), env.host, resolved.source, plan);
|
|
37
65
|
}
|
|
38
66
|
if (subcommand === 'app' && rest[0] !== 'set') {
|
|
39
67
|
throw usageError(rest[0]
|
|
@@ -45,18 +73,23 @@ export async function runTeams(ctx, args) {
|
|
|
45
73
|
throw usageError('teams list takes no arguments.', 'Run "ekanos teams list --source <slug>".');
|
|
46
74
|
}
|
|
47
75
|
const team = subcommand === 'list' ? '' : requireSlug(operands[0], 'team');
|
|
48
|
-
|
|
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');
|
|
49
81
|
const extra = operands.slice(subcommand === 'webhook' ? 3 : 2);
|
|
82
|
+
const appEdits = subcommand === 'app' ? parseAppEdits(args.flags) : null;
|
|
50
83
|
if (subcommand !== 'list' && extra.length > 0) {
|
|
51
84
|
throw usageError(`Unexpected argument "${extra[0]}".`, 'See "ekanos teams --help".');
|
|
52
85
|
}
|
|
53
|
-
if (
|
|
54
|
-
throw usageError("teams app set takes the team's secrets at a terminal, never in a script or an agent run.", `Run it yourself in your terminal: npx @ekanos/cli teams app set ${team} ${product}${args.connection ? ` --connection ${args.connection}` : ''}${args.source ? ` --source ${args.source}` : ''}`);
|
|
86
|
+
if ((appEdits === null || appEdits === void 0 ? void 0 : appEdits.prompts) && !canPrompt(ctx, args)) {
|
|
87
|
+
throw usageError("teams app set takes the team's secrets at a terminal, never in a script or an agent run. Non-secret fields can be passed with --set <name>=<value>.", `Run it yourself in your terminal: npx @ekanos/cli teams app set ${team} ${product}${args.connection ? ` --connection ${args.connection}` : ''}${args.source ? ` --source ${args.source}` : ''}`);
|
|
55
88
|
}
|
|
56
89
|
const env = resolveAuthEnvironment(ctx, args.host, args.env, {
|
|
57
90
|
projectDir: args.dir,
|
|
58
91
|
});
|
|
59
|
-
const resolved = await resolveSourceTarget(env, requireSession(env
|
|
92
|
+
const resolved = await resolveSourceTarget(env, requireSession(env), args);
|
|
60
93
|
const client = new TeamsClient(env, resolved.session);
|
|
61
94
|
const source = resolved.source;
|
|
62
95
|
if (subcommand === 'list') {
|
|
@@ -73,63 +106,156 @@ export async function runTeams(ctx, args) {
|
|
|
73
106
|
`${entry.name} · ${entry.memberCount} member${entry.memberCount === 1 ? '' : 's'}`,
|
|
74
107
|
]),
|
|
75
108
|
notes: [
|
|
76
|
-
'"ekanos teams show <team>
|
|
109
|
+
'"ekanos teams show <team>" shows a team\'s setup; add a product for its checklist.',
|
|
77
110
|
],
|
|
78
111
|
}));
|
|
79
112
|
}
|
|
113
|
+
if (overview) {
|
|
114
|
+
return runTeamOverview(ctx, client, env.host, source, team);
|
|
115
|
+
}
|
|
80
116
|
const target = { source, team, product };
|
|
81
117
|
const status = await client.call((token) => showTeamIntegration(env.host, token, target));
|
|
82
118
|
if (subcommand === 'show') {
|
|
83
119
|
return ctx.succeed(Object.assign({ host: env.host }, status), checklist(status).join('\n'));
|
|
84
120
|
}
|
|
85
121
|
if (!status.enabled) {
|
|
86
|
-
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).`);
|
|
87
123
|
}
|
|
88
|
-
if (subcommand === 'app') {
|
|
89
|
-
return setApp(ctx, args, client, env.host, target, status);
|
|
124
|
+
if (subcommand === 'app' && appEdits) {
|
|
125
|
+
return setApp(ctx, args, client, env.host, target, status, appEdits);
|
|
90
126
|
}
|
|
91
127
|
return mintWebhook(ctx, args, client, env.host, target, status, operands[2]);
|
|
92
128
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const missing = app.fields.filter((field) => !field.set && !fields[field.secretName]);
|
|
110
|
-
if (missing.length > 0) {
|
|
111
|
-
throw validationError(`Every field is needed: ${missing.map((field) => field.label).join(', ')}.`, `Run "ekanos teams app set ${target.team} ${target.product}" again.`);
|
|
112
|
-
}
|
|
113
|
-
if (Object.keys(fields).length === 0) {
|
|
114
|
-
throw validationError('Nothing was changed.', `Run "ekanos teams show ${target.team} ${target.product}" to see the current state.`);
|
|
129
|
+
function parseAppEdits(flags) {
|
|
130
|
+
const list = (name) => {
|
|
131
|
+
const raw = flags === null || flags === void 0 ? void 0 : flags[name];
|
|
132
|
+
if (raw === undefined || raw === false)
|
|
133
|
+
return [];
|
|
134
|
+
if (Array.isArray(raw))
|
|
135
|
+
return raw;
|
|
136
|
+
return typeof raw === 'string' ? [raw] : [];
|
|
137
|
+
};
|
|
138
|
+
const values = {};
|
|
139
|
+
for (const pair of list('set')) {
|
|
140
|
+
const at = pair.indexOf('=');
|
|
141
|
+
if (at <= 0) {
|
|
142
|
+
throw usageError(`--set "${pair}" is not <field>=<value>.`, 'For example: --set subdomain=acme. Secrets are never passed as arguments: use --field <name> and enter it at the prompt.');
|
|
143
|
+
}
|
|
144
|
+
values[pair.slice(0, at).trim()] = pair.slice(at + 1).trim();
|
|
115
145
|
}
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
146
|
+
const only = list('field');
|
|
147
|
+
const clear = list('clear');
|
|
148
|
+
return {
|
|
149
|
+
only,
|
|
150
|
+
values,
|
|
151
|
+
clear,
|
|
152
|
+
// Bare `app set` walks every field; `--field` asks the named ones.
|
|
153
|
+
prompts: only.length > 0 ||
|
|
154
|
+
(Object.keys(values).length === 0 && clear.length === 0),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
async function setApp(ctx, args, client, host, target, status, edits) {
|
|
158
|
+
const app = chooseApp(status, args.connection);
|
|
159
|
+
const result = await promptAndSaveTeamApp(ctx, args, client, host, target, status, app, edits);
|
|
160
|
+
const missing = result.fields.filter((field) => !field.set);
|
|
161
|
+
const next = [
|
|
162
|
+
...(missing.length > 0
|
|
163
|
+
? [
|
|
164
|
+
`Still not set: ${missing.map((field) => field.label).join(', ')} — a team member can't connect until it is. Run "ekanos teams app set ${target.team} ${target.product} ${missing.map((field) => `--field ${field.secretName}`).join(' ')}".`,
|
|
165
|
+
]
|
|
166
|
+
: []),
|
|
167
|
+
...nextSteps(status, app, target),
|
|
168
|
+
];
|
|
121
169
|
return ctx.succeed(Object.assign({ host }, result), ctx.ui.result({
|
|
122
170
|
title: `Saved ${target.team}'s ${result.product.name} app`,
|
|
123
171
|
rows: [
|
|
124
172
|
['Source', status.source.slug],
|
|
125
|
-
[
|
|
126
|
-
'Fields',
|
|
127
|
-
result.fields.map((field) => `${mark(field.set)} ${field.label}`),
|
|
128
|
-
],
|
|
173
|
+
['Fields', result.fields.map(fieldLine)],
|
|
129
174
|
],
|
|
130
175
|
notes: next,
|
|
131
176
|
}));
|
|
132
177
|
}
|
|
178
|
+
/** Every field, asked in turn: bare `teams app set`, and `teams connect`. */
|
|
179
|
+
const WALK_EVERY_FIELD = {
|
|
180
|
+
only: [],
|
|
181
|
+
values: {},
|
|
182
|
+
clear: [],
|
|
183
|
+
prompts: true,
|
|
184
|
+
};
|
|
185
|
+
/**
|
|
186
|
+
* The `teams app set` walkthrough and prompts for one team app, then the
|
|
187
|
+
* save — of only what was entered: a field left empty keeps its saved value.
|
|
188
|
+
* Shared with `teams connect`, which runs it inline when the integration
|
|
189
|
+
* needs the team's own app (Zendesk) before anyone can sign in.
|
|
190
|
+
*/
|
|
191
|
+
export async function promptAndSaveTeamApp(ctx, args, client, host, target, status, app, edits = WALK_EVERY_FIELD) {
|
|
192
|
+
var _a;
|
|
193
|
+
var _b, _c;
|
|
194
|
+
const known = new Map(app.fields.map((field) => [field.secretName, field]));
|
|
195
|
+
const names = app.fields.map((field) => field.secretName).join(', ');
|
|
196
|
+
for (const name of [
|
|
197
|
+
...edits.only,
|
|
198
|
+
...Object.keys(edits.values),
|
|
199
|
+
...edits.clear,
|
|
200
|
+
]) {
|
|
201
|
+
if (!known.has(name)) {
|
|
202
|
+
throw usageError(`${status.product.name}'s team app has no field "${name}".`, `Its fields are: ${names}.`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
for (const name of Object.keys(edits.values)) {
|
|
206
|
+
if ((_a = known.get(name)) === null || _a === void 0 ? void 0 : _a.sensitive) {
|
|
207
|
+
throw usageError(`"${name}" is a secret: it is never passed as an argument.`, `Enter it at the prompt: ekanos teams app set ${target.team} ${target.product} --field ${name}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const fields = Object.assign({}, edits.values);
|
|
211
|
+
if (edits.prompts) {
|
|
212
|
+
const toAsk = edits.only.length > 0
|
|
213
|
+
? app.fields.filter((field) => edits.only.includes(field.secretName))
|
|
214
|
+
: app.fields;
|
|
215
|
+
if (edits.only.length === 0)
|
|
216
|
+
ctx.log(appWalkthrough(status, app).join('\n'));
|
|
217
|
+
else {
|
|
218
|
+
ctx.log(`${status.product.name} app for team ${target.team} on ${status.source.slug}`);
|
|
219
|
+
}
|
|
220
|
+
for (const field of toAsk) {
|
|
221
|
+
const value = (await (field.sensitive
|
|
222
|
+
? ((_b = args.askSecret) !== null && _b !== void 0 ? _b : promptHidden)
|
|
223
|
+
: ((_c = args.ask) !== null && _c !== void 0 ? _c : promptLine))(fieldQuestion(field))).trim();
|
|
224
|
+
if (value)
|
|
225
|
+
fields[field.secretName] = value;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (Object.keys(fields).length === 0 && edits.clear.length === 0) {
|
|
229
|
+
throw validationError('Nothing was changed.', `Run "ekanos teams show ${target.team} ${target.product}" to see the current state.`);
|
|
230
|
+
}
|
|
231
|
+
return client.call((token) => setTeamApp(host, token, target, Object.assign({ connectionId: app.connectionId, fields }, (edits.clear.length > 0 ? { clear: edits.clear } : {}))));
|
|
232
|
+
}
|
|
233
|
+
/** The prompt for one field: what is saved, and that Enter keeps it. */
|
|
234
|
+
function fieldQuestion(field) {
|
|
235
|
+
const name = `${field.label} [${field.secretName}]`;
|
|
236
|
+
if (field.sensitive) {
|
|
237
|
+
return field.set
|
|
238
|
+
? `${name} (saved — Enter to keep, hidden): `
|
|
239
|
+
: `${name} (hidden): `;
|
|
240
|
+
}
|
|
241
|
+
if (field.set) {
|
|
242
|
+
return field.value
|
|
243
|
+
? `${name} (current: ${field.value} — Enter to keep): `
|
|
244
|
+
: `${name} (saved — Enter to keep): `;
|
|
245
|
+
}
|
|
246
|
+
return `${name}: `;
|
|
247
|
+
}
|
|
248
|
+
/** One field's state: a secret only as saved / not set, never its value. */
|
|
249
|
+
function fieldLine(field) {
|
|
250
|
+
const state = !field.set
|
|
251
|
+
? 'not set'
|
|
252
|
+
: field.sensitive
|
|
253
|
+
? 'saved'
|
|
254
|
+
: field.value
|
|
255
|
+
? field.value
|
|
256
|
+
: 'saved';
|
|
257
|
+
return `${mark(field.set)} ${field.label} (${field.secretName}): ${state}`;
|
|
258
|
+
}
|
|
133
259
|
async function mintWebhook(ctx, args, client, host, target, status, requested) {
|
|
134
260
|
const webhooks = status.teamApps.flatMap((app) => app.webhooks);
|
|
135
261
|
if (webhooks.length === 0) {
|
|
@@ -148,19 +274,29 @@ async function mintWebhook(ctx, args, client, host, target, status, requested) {
|
|
|
148
274
|
if (webhook.endpoint && !args.yes) {
|
|
149
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`);
|
|
150
276
|
}
|
|
277
|
+
// The URL carries its token, so it goes to a 0600 file, never the output.
|
|
278
|
+
const secrets = ensureSecretsDir(args.env);
|
|
151
279
|
const minted = await client.call((token) => mintTeamWebhook(host, token, target, webhook.webhookId));
|
|
152
|
-
|
|
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({
|
|
153
289
|
title: `Webhook URL for ${target.team} · ${webhook.webhookId}`,
|
|
154
290
|
tone: 'warn',
|
|
155
291
|
rows: [
|
|
156
|
-
['URL',
|
|
292
|
+
['URL', `saved to ${secretFile} (readable only by you)`],
|
|
157
293
|
[
|
|
158
294
|
'Paste it into',
|
|
159
295
|
`${status.product.name}'s webhook settings for this team`,
|
|
160
296
|
],
|
|
161
297
|
],
|
|
162
298
|
notes: [
|
|
163
|
-
'
|
|
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.',
|
|
164
300
|
...(minted.replaced
|
|
165
301
|
? ['The previous URL is revoked: the provider must use this one.']
|
|
166
302
|
: []),
|
|
@@ -176,10 +312,12 @@ export function checklist(status) {
|
|
|
176
312
|
` ${mark(status.enabled)} Enabled on the Source`,
|
|
177
313
|
];
|
|
178
314
|
if (!status.enabled) {
|
|
179
|
-
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).`);
|
|
180
316
|
return lines;
|
|
181
317
|
}
|
|
182
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));
|
|
183
321
|
for (const app of status.teamApps) {
|
|
184
322
|
lines.push(` Team app: ${app.label} (${app.connectionId})`);
|
|
185
323
|
if ((_a = app.setup) === null || _a === void 0 ? void 0 : _a.consoleUrl)
|
|
@@ -190,10 +328,11 @@ export function checklist(status) {
|
|
|
190
328
|
if (app.guideUrl)
|
|
191
329
|
lines.push(` Guide: ${app.guideUrl}`);
|
|
192
330
|
for (const field of app.fields) {
|
|
193
|
-
lines.push(` ${
|
|
331
|
+
lines.push(` ${fieldLine(field)}`);
|
|
194
332
|
}
|
|
195
|
-
|
|
196
|
-
|
|
333
|
+
const unset = app.fields.filter((field) => !field.set);
|
|
334
|
+
if (unset.length > 0) {
|
|
335
|
+
lines.push(` → in your terminal: npx @ekanos/cli teams app set ${target.team} ${target.product} --connection ${app.connectionId} ${unset.map((field) => `--field ${field.secretName}`).join(' ')} --source ${status.source.slug}`);
|
|
197
336
|
}
|
|
198
337
|
for (const webhook of app.webhooks) {
|
|
199
338
|
lines.push(webhook.endpoint
|
|
@@ -205,11 +344,11 @@ export function checklist(status) {
|
|
|
205
344
|
}
|
|
206
345
|
lines.push(app.connection
|
|
207
346
|
? ` ${mark(true)} Connected ${day(app.connection.connectedAt)}${app.connection.connectedBy ? ` by ${who(app.connection.connectedBy)}` : ''}`
|
|
208
|
-
: ` ${mark(false)} Connected:
|
|
347
|
+
: ` ${mark(false)} Connected: not yet\n → in your terminal: ${connectCommand(status, app.connectionId)}`);
|
|
209
348
|
}
|
|
210
349
|
lines.push(status.activation
|
|
211
350
|
? ` ${mark(true)} Activated for the team${status.activation.activatedAt ? ` ${day(status.activation.activatedAt)}` : ''}${status.activation.activatedBy ? ` by ${who(status.activation.activatedBy)}` : ''}`
|
|
212
|
-
: ` ${mark(false)} Activated:
|
|
351
|
+
: ` ${mark(false)} Activated: not yet\n → in your terminal: ${connectCommand(status)}`);
|
|
213
352
|
lines.push(` Team page: ${status.connectUrl}`);
|
|
214
353
|
if (status.agentEvents.length > 0) {
|
|
215
354
|
lines.push(' Agent events it offers:');
|
|
@@ -217,6 +356,61 @@ export function checklist(status) {
|
|
|
217
356
|
}
|
|
218
357
|
return lines;
|
|
219
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
|
+
}
|
|
220
414
|
function appWalkthrough(status, app) {
|
|
221
415
|
var _a, _b;
|
|
222
416
|
const lines = [
|
|
@@ -242,10 +436,10 @@ function nextSteps(status, app, target) {
|
|
|
242
436
|
.map((webhook) => `Next: "ekanos teams webhook ${target.team} ${target.product} ${webhook.webhookId}" for the provider's webhook URL.`),
|
|
243
437
|
...(app.connection
|
|
244
438
|
? []
|
|
245
|
-
: [`Then
|
|
439
|
+
: [`Then connect it: ${connectCommand(status, app.connectionId)}`]),
|
|
246
440
|
];
|
|
247
441
|
}
|
|
248
|
-
function chooseApp(status, connection) {
|
|
442
|
+
export function chooseApp(status, connection) {
|
|
249
443
|
const apps = status.teamApps;
|
|
250
444
|
if (apps.length === 0) {
|
|
251
445
|
throw invalidStateError(`${status.product.name} takes no team app: teams connect through the Source's app, or need none.`, `Run "ekanos apps show ${status.product.slug}" for the Source's app.`);
|
|
@@ -274,10 +468,10 @@ export class TeamsClient {
|
|
|
274
468
|
}
|
|
275
469
|
if (result.status === 'ok')
|
|
276
470
|
return result.data;
|
|
277
|
-
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));
|
|
278
472
|
}
|
|
279
473
|
}
|
|
280
|
-
function canPrompt(ctx, args) {
|
|
474
|
+
export function canPrompt(ctx, args) {
|
|
281
475
|
var _a;
|
|
282
476
|
return !ctx.jsonMode && ((_a = args.stdinIsTTY) !== null && _a !== void 0 ? _a : process.stdin.isTTY === true);
|
|
283
477
|
}
|
|
@@ -287,7 +481,7 @@ function parseSubcommand(value) {
|
|
|
287
481
|
return match;
|
|
288
482
|
throw usageError(value
|
|
289
483
|
? `Unknown teams subcommand "${value}".`
|
|
290
|
-
: '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 ….');
|
|
291
485
|
}
|
|
292
486
|
function requireSlug(value, what) {
|
|
293
487
|
const slug = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();
|