@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.
Files changed (175) hide show
  1. package/README.md +25 -8
  2. package/dist/agents-api.d.ts +11 -2
  3. package/dist/agents-api.js +39 -3
  4. package/dist/agents-api.js.map +1 -1
  5. package/dist/apps-api.d.ts +3 -2
  6. package/dist/apps-api.js +3 -7
  7. package/dist/apps-api.js.map +1 -1
  8. package/dist/auth/ekanos-api.d.ts +16 -2
  9. package/dist/auth/ekanos-api.js +101 -15
  10. package/dist/auth/ekanos-api.js.map +1 -1
  11. package/dist/auth/host-trust.d.ts +46 -0
  12. package/dist/auth/host-trust.js +79 -0
  13. package/dist/auth/host-trust.js.map +1 -0
  14. package/dist/auth/session.d.ts +10 -1
  15. package/dist/auth/session.js +28 -28
  16. package/dist/auth/session.js.map +1 -1
  17. package/dist/commands/agents-edit.d.ts +6 -2
  18. package/dist/commands/agents-edit.js +57 -26
  19. package/dist/commands/agents-edit.js.map +1 -1
  20. package/dist/commands/agents.d.ts +13 -2
  21. package/dist/commands/agents.js +148 -36
  22. package/dist/commands/agents.js.map +1 -1
  23. package/dist/commands/apps.d.ts +15 -4
  24. package/dist/commands/apps.js +133 -51
  25. package/dist/commands/apps.js.map +1 -1
  26. package/dist/commands/catalog.js +7 -6
  27. package/dist/commands/catalog.js.map +1 -1
  28. package/dist/commands/knowledge.d.ts +56 -0
  29. package/dist/commands/knowledge.js +456 -0
  30. package/dist/commands/knowledge.js.map +1 -0
  31. package/dist/commands/login.d.ts +4 -0
  32. package/dist/commands/login.js +30 -2
  33. package/dist/commands/login.js.map +1 -1
  34. package/dist/commands/logout.d.ts +9 -1
  35. package/dist/commands/logout.js +56 -7
  36. package/dist/commands/logout.js.map +1 -1
  37. package/dist/commands/publish.js +3 -3
  38. package/dist/commands/publish.js.map +1 -1
  39. package/dist/commands/reviewers.d.ts +2 -0
  40. package/dist/commands/reviewers.js +7 -3
  41. package/dist/commands/reviewers.js.map +1 -1
  42. package/dist/commands/routes.d.ts +2 -0
  43. package/dist/commands/routes.js +42 -9
  44. package/dist/commands/routes.js.map +1 -1
  45. package/dist/commands/runs.d.ts +13 -2
  46. package/dist/commands/runs.js +96 -15
  47. package/dist/commands/runs.js.map +1 -1
  48. package/dist/commands/scope-notice.d.ts +21 -0
  49. package/dist/commands/scope-notice.js +35 -0
  50. package/dist/commands/scope-notice.js.map +1 -0
  51. package/dist/commands/skills.d.ts +7 -1
  52. package/dist/commands/skills.js +80 -24
  53. package/dist/commands/skills.js.map +1 -1
  54. package/dist/commands/sources.d.ts +4 -0
  55. package/dist/commands/sources.js +233 -5
  56. package/dist/commands/sources.js.map +1 -1
  57. package/dist/commands/status.js +5 -4
  58. package/dist/commands/status.js.map +1 -1
  59. package/dist/commands/team-agent-readiness.d.ts +59 -0
  60. package/dist/commands/team-agent-readiness.js +341 -0
  61. package/dist/commands/team-agent-readiness.js.map +1 -0
  62. package/dist/commands/team-agents-agents.d.ts +24 -3
  63. package/dist/commands/team-agents-agents.js +336 -35
  64. package/dist/commands/team-agents-agents.js.map +1 -1
  65. package/dist/commands/team-agents-mcp.js +32 -3
  66. package/dist/commands/team-agents-mcp.js.map +1 -1
  67. package/dist/commands/team-agents-triggers.js +230 -54
  68. package/dist/commands/team-agents-triggers.js.map +1 -1
  69. package/dist/commands/team-agents.d.ts +3 -1
  70. package/dist/commands/team-agents.js +27 -7
  71. package/dist/commands/team-agents.js.map +1 -1
  72. package/dist/commands/teams-channel-install.d.ts +24 -0
  73. package/dist/commands/teams-channel-install.js +94 -0
  74. package/dist/commands/teams-channel-install.js.map +1 -0
  75. package/dist/commands/teams-connect.d.ts +66 -0
  76. package/dist/commands/teams-connect.js +397 -0
  77. package/dist/commands/teams-connect.js.map +1 -0
  78. package/dist/commands/teams-events.d.ts +12 -0
  79. package/dist/commands/teams-events.js +138 -0
  80. package/dist/commands/teams-events.js.map +1 -0
  81. package/dist/commands/teams-onboard.d.ts +16 -5
  82. package/dist/commands/teams-onboard.js +61 -36
  83. package/dist/commands/teams-onboard.js.map +1 -1
  84. package/dist/commands/teams-overview.d.ts +11 -0
  85. package/dist/commands/teams-overview.js +160 -0
  86. package/dist/commands/teams-overview.js.map +1 -0
  87. package/dist/commands/teams-scope.d.ts +10 -0
  88. package/dist/commands/teams-scope.js +109 -0
  89. package/dist/commands/teams-scope.js.map +1 -0
  90. package/dist/commands/teams.d.ts +64 -7
  91. package/dist/commands/teams.js +254 -60
  92. package/dist/commands/teams.js.map +1 -1
  93. package/dist/commands/upgrade.js +12 -2
  94. package/dist/commands/upgrade.js.map +1 -1
  95. package/dist/commands/use.js +2 -2
  96. package/dist/commands/use.js.map +1 -1
  97. package/dist/commands/whoami.js +1 -1
  98. package/dist/commands/whoami.js.map +1 -1
  99. package/dist/connections-api.js +1 -1
  100. package/dist/connections-api.js.map +1 -1
  101. package/dist/delegate.js +11 -4
  102. package/dist/delegate.js.map +1 -1
  103. package/dist/harness-scaffold.js +12 -0
  104. package/dist/harness-scaffold.js.map +1 -1
  105. package/dist/index.d.ts +6 -0
  106. package/dist/index.js +724 -152
  107. package/dist/index.js.map +1 -1
  108. package/dist/knowledge-api.d.ts +175 -0
  109. package/dist/knowledge-api.js +169 -0
  110. package/dist/knowledge-api.js.map +1 -0
  111. package/dist/open-url.d.ts +16 -0
  112. package/dist/open-url.js +73 -0
  113. package/dist/open-url.js.map +1 -0
  114. package/dist/parse-argv.d.ts +2 -0
  115. package/dist/parse-argv.js.map +1 -1
  116. package/dist/publish-api.d.ts +2 -1
  117. package/dist/publish-api.js +4 -3
  118. package/dist/publish-api.js.map +1 -1
  119. package/dist/pull-write.d.ts +27 -0
  120. package/dist/pull-write.js +60 -0
  121. package/dist/pull-write.js.map +1 -0
  122. package/dist/routes-api.d.ts +3 -0
  123. package/dist/routes-api.js +9 -4
  124. package/dist/routes-api.js.map +1 -1
  125. package/dist/runs-api.d.ts +7 -1
  126. package/dist/runs-api.js +21 -6
  127. package/dist/runs-api.js.map +1 -1
  128. package/dist/safe-fs.d.ts +2 -0
  129. package/dist/safe-fs.js +12 -6
  130. package/dist/safe-fs.js.map +1 -1
  131. package/dist/secret-file.d.ts +17 -0
  132. package/dist/secret-file.js +69 -0
  133. package/dist/secret-file.js.map +1 -0
  134. package/dist/skills-api.d.ts +10 -2
  135. package/dist/skills-api.js +37 -2
  136. package/dist/skills-api.js.map +1 -1
  137. package/dist/source-products-api.d.ts +63 -0
  138. package/dist/source-products-api.js +123 -0
  139. package/dist/source-products-api.js.map +1 -0
  140. package/dist/source-target.js +2 -1
  141. package/dist/source-target.js.map +1 -1
  142. package/dist/sources-api.d.ts +2 -1
  143. package/dist/sources-api.js +3 -2
  144. package/dist/sources-api.js.map +1 -1
  145. package/dist/team-connect-api.d.ts +286 -0
  146. package/dist/team-connect-api.js +107 -0
  147. package/dist/team-connect-api.js.map +1 -0
  148. package/dist/teams-api.d.ts +42 -1
  149. package/dist/teams-api.js +98 -20
  150. package/dist/teams-api.js.map +1 -1
  151. package/dist/toolchain-api.d.ts +9 -0
  152. package/dist/toolchain-api.js +17 -2
  153. package/dist/toolchain-api.js.map +1 -1
  154. package/dist/toolchain-resolve.d.ts +3 -0
  155. package/dist/toolchain-resolve.js +15 -1
  156. package/dist/toolchain-resolve.js.map +1 -1
  157. package/dist/toolchain.d.ts +6 -0
  158. package/dist/toolchain.js +19 -0
  159. package/dist/toolchain.js.map +1 -1
  160. package/dist/update-notice.js +2 -1
  161. package/dist/update-notice.js.map +1 -1
  162. package/package.json +2 -2
  163. package/templates/AGENTS.md.tmpl +6 -1
  164. package/templates/claude-skill.md.tmpl +5 -0
  165. package/templates/package.json.tmpl +4 -4
  166. package/templates/skills/build-agent/SKILL.md +115 -16
  167. package/templates/skills/build-agent/references/agent-json.md +26 -8
  168. package/templates/skills/build-agent/references/app-setup.md +2 -1
  169. package/templates/skills/build-agent/references/rollout.md +27 -4
  170. package/templates/skills/build-agent/references/set-up-an-integration.md +74 -15
  171. package/templates/skills/build-agent/references/skills.md +20 -1
  172. package/templates/skills/build-agent/references/triggers.md +84 -20
  173. package/templates/skills/build-integration/references/widget-design.md +3 -3
  174. package/templates/skills/ekanos-platform/SKILL.md +70 -17
  175. package/templates/skills/polish-widgets/references/patterns.md +2 -2
@@ -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 a team's own
19
- * app (Zendesk's subdomain, client and signing secret) at the admin's
20
- * terminal — never from a flag, a file or a non-interactive run, so an
21
- * agent driving the CLI never handles a secret; `webhook` mints the URL the
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.store, env.host), args);
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
- const product = subcommand === 'list' ? '' : requireSlug(operands[1], 'product');
76
+ // `teams show <team>` alone is the team's overview.
77
+ const overview = subcommand === 'show' && operands[1] === undefined;
78
+ const product = subcommand === 'list' || overview
79
+ ? ''
80
+ : requireSlug(operands[1], 'product');
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 (subcommand === 'app' && !canPrompt(ctx, args)) {
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.store, env.host), args);
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> <product>" shows a team\'s setup.',
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}.`, '"ekanos catalog" lists what this Source has; Ekanos enables a product for a Source.');
122
+ throw invalidStateError(`"${product}" is not enabled on ${status.source.slug}.`, `"ekanos sources products --source ${status.source.slug}" shows what it has; only a super-admin enables a product for a Source (in /admin).`);
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
- async function setApp(ctx, args, client, host, target, status) {
94
- var _a, _b;
95
- const app = chooseApp(status, args.connection);
96
- ctx.log(appWalkthrough(status, app).join('\n'));
97
- const fields = {};
98
- for (const field of app.fields) {
99
- const keep = field.set ? ' (set — Enter keeps it)' : '';
100
- const question = field.sensitive
101
- ? `${field.label}${keep} (hidden): `
102
- : `${field.label}${keep}: `;
103
- const value = (await (field.sensitive
104
- ? ((_a = args.askSecret) !== null && _a !== void 0 ? _a : promptHidden)
105
- : ((_b = args.ask) !== null && _b !== void 0 ? _b : promptLine))(question)).trim();
106
- if (value)
107
- fields[field.secretName] = value;
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 result = await client.call((token) => setTeamApp(host, token, target, {
117
- connectionId: app.connectionId,
118
- fields,
119
- }));
120
- const next = nextSteps(status, app, target);
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
- return ctx.succeed(Object.assign({ host }, minted), ctx.ui.result({
280
+ const { url } = minted, rest = __rest(minted, ["url"]);
281
+ const secretFile = writeSecretFile(secrets, {
282
+ owner: target.team,
283
+ what: `${target.product}-${webhook.webhookId}-webhook-url`,
284
+ purpose: `Webhook URL for team ${target.team}'s ${status.product.name} (${webhook.webhookId}). Paste it into ${status.product.name}'s webhook settings for this team; the token is part of the URL.`,
285
+ values: [['WEBHOOK_URL', url]],
286
+ again: `ekanos teams webhook ${target.team} ${target.product} ${webhook.webhookId} --yes`,
287
+ });
288
+ return ctx.succeed(Object.assign(Object.assign({ host }, rest), { secretFile }), ctx.ui.result({
153
289
  title: `Webhook URL for ${target.team} · ${webhook.webhookId}`,
154
290
  tone: 'warn',
155
291
  rows: [
156
- ['URL', minted.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
- 'This URL is shown once and cannot be read again. Paste it now.',
299
+ 'The URL carries its token, so it is shown nowhere else and cannot be read again. Copy it from that file into the provider yourself — not into a chat or an agent session — then delete the file.',
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(' "ekanos catalog" lists what this Source has; Ekanos enables a product for a Source.');
315
+ lines.push(` "ekanos sources products --source ${status.source.slug}" shows what it has; only a super-admin enables a product for a Source (in /admin).`);
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(` ${mark(field.set)} ${field.label}`);
331
+ lines.push(` ${fieldLine(field)}`);
194
332
  }
195
- if (app.fields.some((field) => !field.set)) {
196
- lines.push(` → in your terminal: npx @ekanos/cli teams app set ${target.team} ${target.product} --connection ${app.connectionId} --source ${status.source.slug}`);
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: a team member clicks Connect at ${status.connectUrl}`);
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: a team member connects at ${status.connectUrl}`);
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 a team member clicks Connect at ${status.connectUrl}`]),
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.`, `Run "ekanos login --host ${this.env.host}" and retry.`);
471
+ throw authRequiredError(`The session for ${this.env.host} could not authorize the teams call.`, loginHint(this.env));
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>, ekanos teams app set <team> <product>, ekanos teams webhook <team> <product> [<webhook-id>], ekanos teams agents|triggers|links|mcp ….');
484
+ : 'teams needs a subcommand.', 'Run one of: ekanos teams list, ekanos teams create <name>, ekanos teams rename <team> <name>, ekanos teams members list <team>, ekanos teams roles list <team>, ekanos teams connect <team> <product>, ekanos teams disconnect <team> <product> --yes, ekanos teams show <team> [<product>], ekanos teams app set <team> <product>, ekanos teams webhook <team> <product> [<webhook-id>], ekanos teams events <team> [<product>], ekanos teams scope show|set <team> …, ekanos teams agents|triggers|links|mcp ….');
291
485
  }
292
486
  function requireSlug(value, what) {
293
487
  const slug = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();