@ekanos/cli 0.15.0 → 0.17.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 (287) hide show
  1. package/README.md +103 -8
  2. package/dist/auth/ekanos-api.d.ts +23 -0
  3. package/dist/auth/ekanos-api.js +33 -6
  4. package/dist/auth/ekanos-api.js.map +1 -1
  5. package/dist/commands/add.d.ts +2 -0
  6. package/dist/commands/add.js +118 -0
  7. package/dist/commands/add.js.map +1 -0
  8. package/dist/commands/agents.js +3 -2
  9. package/dist/commands/agents.js.map +1 -1
  10. package/dist/commands/apps.d.ts +11 -3
  11. package/dist/commands/apps.js +86 -28
  12. package/dist/commands/apps.js.map +1 -1
  13. package/dist/commands/audiences.d.ts +31 -0
  14. package/dist/commands/audiences.js +239 -0
  15. package/dist/commands/audiences.js.map +1 -0
  16. package/dist/commands/build.d.ts +2 -0
  17. package/dist/commands/build.js +41 -0
  18. package/dist/commands/build.js.map +1 -0
  19. package/dist/commands/catalog.d.ts +4 -2
  20. package/dist/commands/catalog.js +166 -35
  21. package/dist/commands/catalog.js.map +1 -1
  22. package/dist/commands/check.d.ts +2 -0
  23. package/dist/commands/check.js +43 -0
  24. package/dist/commands/check.js.map +1 -0
  25. package/dist/commands/codegen.d.ts +2 -0
  26. package/dist/commands/codegen.js +49 -0
  27. package/dist/commands/codegen.js.map +1 -0
  28. package/dist/commands/contract.d.ts +52 -0
  29. package/dist/commands/contract.js +300 -0
  30. package/dist/commands/contract.js.map +1 -0
  31. package/dist/commands/dev.d.ts +7 -0
  32. package/dist/commands/dev.js +186 -12
  33. package/dist/commands/dev.js.map +1 -1
  34. package/dist/commands/index-experience.d.ts +31 -0
  35. package/dist/commands/index-experience.js +21 -0
  36. package/dist/commands/index-experience.js.map +1 -0
  37. package/dist/commands/init.d.ts +7 -0
  38. package/dist/commands/init.js +12 -0
  39. package/dist/commands/init.js.map +1 -1
  40. package/dist/commands/login.d.ts +2 -0
  41. package/dist/commands/login.js +17 -0
  42. package/dist/commands/login.js.map +1 -1
  43. package/dist/commands/new.d.ts +2 -0
  44. package/dist/commands/new.js +39 -0
  45. package/dist/commands/new.js.map +1 -0
  46. package/dist/commands/publish.d.ts +59 -0
  47. package/dist/commands/publish.js +118 -65
  48. package/dist/commands/publish.js.map +1 -1
  49. package/dist/commands/runs.d.ts +7 -3
  50. package/dist/commands/runs.js +21 -5
  51. package/dist/commands/runs.js.map +1 -1
  52. package/dist/commands/scope-notice.d.ts +13 -0
  53. package/dist/commands/scope-notice.js +12 -4
  54. package/dist/commands/scope-notice.js.map +1 -1
  55. package/dist/commands/sources-roles.d.ts +5 -0
  56. package/dist/commands/sources-roles.js +235 -0
  57. package/dist/commands/sources-roles.js.map +1 -0
  58. package/dist/commands/sources.d.ts +5 -0
  59. package/dist/commands/sources.js +8 -1
  60. package/dist/commands/sources.js.map +1 -1
  61. package/dist/commands/sync.d.ts +2 -0
  62. package/dist/commands/sync.js +150 -0
  63. package/dist/commands/sync.js.map +1 -0
  64. package/dist/commands/team-agent-readiness.d.ts +18 -2
  65. package/dist/commands/team-agent-readiness.js +39 -39
  66. package/dist/commands/team-agent-readiness.js.map +1 -1
  67. package/dist/commands/team-agents-agents.d.ts +5 -2
  68. package/dist/commands/team-agents-agents.js +152 -15
  69. package/dist/commands/team-agents-agents.js.map +1 -1
  70. package/dist/commands/team-agents-define.d.ts +31 -0
  71. package/dist/commands/team-agents-define.js +176 -0
  72. package/dist/commands/team-agents-define.js.map +1 -0
  73. package/dist/commands/team-agents-mcp.js +112 -10
  74. package/dist/commands/team-agents-mcp.js.map +1 -1
  75. package/dist/commands/team-agents-triggers.d.ts +1 -1
  76. package/dist/commands/team-agents-triggers.js +137 -5
  77. package/dist/commands/team-agents-triggers.js.map +1 -1
  78. package/dist/commands/team-agents.d.ts +4 -1
  79. package/dist/commands/team-agents.js +17 -4
  80. package/dist/commands/team-agents.js.map +1 -1
  81. package/dist/commands/team-llm-key.d.ts +14 -0
  82. package/dist/commands/team-llm-key.js +122 -0
  83. package/dist/commands/team-llm-key.js.map +1 -0
  84. package/dist/commands/teams-audience.d.ts +12 -0
  85. package/dist/commands/teams-audience.js +144 -0
  86. package/dist/commands/teams-audience.js.map +1 -0
  87. package/dist/commands/teams-connect.js +123 -31
  88. package/dist/commands/teams-connect.js.map +1 -1
  89. package/dist/commands/teams-lifecycle.d.ts +90 -0
  90. package/dist/commands/teams-lifecycle.js +457 -0
  91. package/dist/commands/teams-lifecycle.js.map +1 -0
  92. package/dist/commands/teams-onboard.d.ts +14 -0
  93. package/dist/commands/teams-onboard.js +62 -13
  94. package/dist/commands/teams-onboard.js.map +1 -1
  95. package/dist/commands/teams-overview.js +6 -2
  96. package/dist/commands/teams-overview.js.map +1 -1
  97. package/dist/commands/teams-scope.js +4 -0
  98. package/dist/commands/teams-scope.js.map +1 -1
  99. package/dist/commands/teams.d.ts +25 -5
  100. package/dist/commands/teams.js +106 -39
  101. package/dist/commands/teams.js.map +1 -1
  102. package/dist/commands/workflows.d.ts +37 -0
  103. package/dist/commands/workflows.js +589 -0
  104. package/dist/commands/workflows.js.map +1 -0
  105. package/dist/compatibility.d.ts +10 -1
  106. package/dist/compatibility.js +10 -1
  107. package/dist/compatibility.js.map +1 -1
  108. package/dist/connect-fields.d.ts +22 -0
  109. package/dist/connect-fields.js +115 -0
  110. package/dist/connect-fields.js.map +1 -0
  111. package/dist/context.d.ts +12 -0
  112. package/dist/context.js +32 -9
  113. package/dist/context.js.map +1 -1
  114. package/dist/dev-experience.d.ts +125 -0
  115. package/dist/dev-experience.js +128 -0
  116. package/dist/dev-experience.js.map +1 -0
  117. package/dist/experience/boundary.d.ts +12 -0
  118. package/dist/experience/boundary.js +260 -0
  119. package/dist/experience/boundary.js.map +1 -0
  120. package/dist/experience/build.d.ts +16 -0
  121. package/dist/experience/build.js +90 -0
  122. package/dist/experience/build.js.map +1 -0
  123. package/dist/experience/checks.d.ts +29 -0
  124. package/dist/experience/checks.js +133 -0
  125. package/dist/experience/checks.js.map +1 -0
  126. package/dist/experience/contract-sources.d.ts +36 -0
  127. package/dist/experience/contract-sources.js +111 -0
  128. package/dist/experience/contract-sources.js.map +1 -0
  129. package/dist/experience/definition-checks.d.ts +26 -0
  130. package/dist/experience/definition-checks.js +201 -0
  131. package/dist/experience/definition-checks.js.map +1 -0
  132. package/dist/experience/definition-shape.d.ts +60 -0
  133. package/dist/experience/definition-shape.js +2 -0
  134. package/dist/experience/definition-shape.js.map +1 -0
  135. package/dist/experience/init.d.ts +10 -0
  136. package/dist/experience/init.js +64 -0
  137. package/dist/experience/init.js.map +1 -0
  138. package/dist/experience/integrations-codegen.d.ts +17 -0
  139. package/dist/experience/integrations-codegen.js +51 -0
  140. package/dist/experience/integrations-codegen.js.map +1 -0
  141. package/dist/experience/json-schema-to-ts.d.ts +1 -0
  142. package/dist/experience/json-schema-to-ts.js +90 -0
  143. package/dist/experience/json-schema-to-ts.js.map +1 -0
  144. package/dist/experience/load-experience.d.ts +23 -0
  145. package/dist/experience/load-experience.js +115 -0
  146. package/dist/experience/load-experience.js.map +1 -0
  147. package/dist/experience/overrides.d.ts +8 -0
  148. package/dist/experience/overrides.js +21 -0
  149. package/dist/experience/overrides.js.map +1 -0
  150. package/dist/experience/pin-integration.d.ts +8 -0
  151. package/dist/experience/pin-integration.js +17 -0
  152. package/dist/experience/pin-integration.js.map +1 -0
  153. package/dist/experience/pin-ops.d.ts +12 -0
  154. package/dist/experience/pin-ops.js +36 -0
  155. package/dist/experience/pin-ops.js.map +1 -0
  156. package/dist/experience/pins.d.ts +35 -0
  157. package/dist/experience/pins.js +91 -0
  158. package/dist/experience/pins.js.map +1 -0
  159. package/dist/experience/project.d.ts +52 -0
  160. package/dist/experience/project.js +149 -0
  161. package/dist/experience/project.js.map +1 -0
  162. package/dist/experience/publish.d.ts +51 -0
  163. package/dist/experience/publish.js +144 -0
  164. package/dist/experience/publish.js.map +1 -0
  165. package/dist/experience/regenerate.d.ts +10 -0
  166. package/dist/experience/regenerate.js +33 -0
  167. package/dist/experience/regenerate.js.map +1 -0
  168. package/dist/experience/report.d.ts +9 -0
  169. package/dist/experience/report.js +23 -0
  170. package/dist/experience/report.js.map +1 -0
  171. package/dist/harness-scaffold.d.ts +52 -18
  172. package/dist/harness-scaffold.js +189 -9
  173. package/dist/harness-scaffold.js.map +1 -1
  174. package/dist/index.d.ts +13 -0
  175. package/dist/index.js +494 -127
  176. package/dist/index.js.map +1 -1
  177. package/dist/invocation.d.ts +31 -0
  178. package/dist/invocation.js +58 -0
  179. package/dist/invocation.js.map +1 -0
  180. package/dist/project-checks.d.ts +12 -0
  181. package/dist/project-checks.js +46 -0
  182. package/dist/project-checks.js.map +1 -1
  183. package/dist/project.d.ts +14 -0
  184. package/dist/project.js +10 -0
  185. package/dist/project.js.map +1 -1
  186. package/dist/repo/publish.d.ts +42 -0
  187. package/dist/repo/publish.js +225 -0
  188. package/dist/repo/publish.js.map +1 -0
  189. package/dist/repo/scaffold.d.ts +30 -0
  190. package/dist/repo/scaffold.js +165 -0
  191. package/dist/repo/scaffold.js.map +1 -0
  192. package/dist/runs-api.js +8 -3
  193. package/dist/runs-api.js.map +1 -1
  194. package/dist/safe-fs.d.ts +18 -0
  195. package/dist/safe-fs.js +57 -0
  196. package/dist/safe-fs.js.map +1 -1
  197. package/dist/secret-form.d.ts +48 -0
  198. package/dist/secret-form.js +212 -0
  199. package/dist/secret-form.js.map +1 -0
  200. package/dist/secret-input.d.ts +96 -0
  201. package/dist/secret-input.js +299 -0
  202. package/dist/secret-input.js.map +1 -0
  203. package/dist/team-connect-api.d.ts +78 -3
  204. package/dist/team-connect-api.js +17 -2
  205. package/dist/team-connect-api.js.map +1 -1
  206. package/dist/team-lifecycle-api.d.ts +435 -0
  207. package/dist/team-lifecycle-api.js +301 -0
  208. package/dist/team-lifecycle-api.js.map +1 -0
  209. package/dist/teams-api.d.ts +49 -1
  210. package/dist/teams-api.js +149 -27
  211. package/dist/teams-api.js.map +1 -1
  212. package/dist/unknown-command.d.ts +18 -0
  213. package/dist/unknown-command.js +50 -0
  214. package/dist/unknown-command.js.map +1 -0
  215. package/dist/validate-findings.js +2 -1
  216. package/dist/validate-findings.js.map +1 -1
  217. package/dist/workflows-api.d.ts +69 -0
  218. package/dist/workflows-api.js +182 -0
  219. package/dist/workflows-api.js.map +1 -0
  220. package/package.json +3 -3
  221. package/templates/AGENTS.md.tmpl +6 -4
  222. package/templates/claude-skill.md.tmpl +4 -1
  223. package/templates/ekanos.json.tmpl +1 -0
  224. package/templates/experience/AGENTS.md.tmpl +22 -0
  225. package/templates/experience/ekanos.json.tmpl +7 -0
  226. package/templates/experience/ekanos.lock.json.tmpl +4 -0
  227. package/templates/experience/eslint.config.mjs.tmpl +25 -0
  228. package/templates/experience/gitignore.tmpl +17 -0
  229. package/templates/experience/package.json.tmpl +41 -0
  230. package/templates/experience/src/.ekanos/integrations.d.ts.tmpl +3 -0
  231. package/templates/experience/src/experience.ts.tmpl +30 -0
  232. package/templates/experience/src/widgets/hello-widget.tsx.tmpl +40 -0
  233. package/templates/experience/tsconfig.json.tmpl +20 -0
  234. package/templates/harness/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +9 -0
  235. package/templates/harness/app/[slug]/activation/page.tsx.tmpl +1 -1
  236. package/templates/harness/app/[slug]/page.tsx.tmpl +1 -1
  237. package/templates/harness/app/[slug]/tile/page.tsx.tmpl +1 -1
  238. package/templates/harness/app/[slug]/triggers/page.tsx.tmpl +1 -1
  239. package/templates/harness/app/[slug]/widgets/[widgetId]/page.tsx.tmpl +1 -1
  240. package/templates/harness/app/[slug]/widgets/page.tsx.tmpl +1 -1
  241. package/templates/harness/app/experience/layout.tsx.tmpl +17 -0
  242. package/templates/harness/app/experience/page.tsx.tmpl +1 -0
  243. package/templates/harness/app/experience/pages/[id]/page.tsx.tmpl +1 -0
  244. package/templates/harness/app/experience/review-queue/page.tsx.tmpl +1 -0
  245. package/templates/harness/app/experience/runs/page.tsx.tmpl +1 -0
  246. package/templates/harness/app/experience/widgets/[id]/page.tsx.tmpl +1 -0
  247. package/templates/harness/app/experience/widgets/page.tsx.tmpl +1 -0
  248. package/templates/harness/app/experience/workflows/[id]/page.tsx.tmpl +1 -0
  249. package/templates/harness/app/experience/workflows/page.tsx.tmpl +1 -0
  250. package/templates/harness/app/integrations/[slug]/connect/page.tsx.tmpl +1 -0
  251. package/templates/harness/app/integrations/[slug]/events/page.tsx.tmpl +1 -0
  252. package/templates/harness/app/integrations/[slug]/health/page.tsx.tmpl +1 -0
  253. package/templates/harness/app/integrations/[slug]/operations/[kind]/[name]/page.tsx.tmpl +1 -0
  254. package/templates/harness/app/integrations/[slug]/operations/page.tsx.tmpl +1 -0
  255. package/templates/harness/app/integrations/[slug]/page.tsx.tmpl +1 -0
  256. package/templates/harness/app/integrations/[slug]/preview/activation/page.tsx.tmpl +1 -0
  257. package/templates/harness/app/integrations/[slug]/preview/layout.tsx.tmpl +1 -0
  258. package/templates/harness/app/integrations/[slug]/preview/page.tsx.tmpl +1 -0
  259. package/templates/harness/app/integrations/[slug]/preview/tile/page.tsx.tmpl +1 -0
  260. package/templates/harness/app/integrations/[slug]/preview/triggers/page.tsx.tmpl +1 -0
  261. package/templates/harness/app/integrations/[slug]/preview/widgets/[widgetId]/page.tsx.tmpl +1 -0
  262. package/templates/harness/app/integrations/[slug]/preview/widgets/page.tsx.tmpl +1 -0
  263. package/templates/harness/app/integrations/[slug]/runs/page.tsx.tmpl +1 -0
  264. package/templates/harness/app/integrations/layout.tsx.tmpl +1 -0
  265. package/templates/harness/app/integrations/page.tsx.tmpl +1 -0
  266. package/templates/harness/app/page.experience.tsx.tmpl +5 -0
  267. package/templates/harness/repo/app/%5F%5Fekanos/api/[...path]/route.ts.tmpl +11 -0
  268. package/templates/harness/repo/app/(repo)/connections/page.tsx.tmpl +1 -0
  269. package/templates/harness/repo/app/(repo)/layout.tsx.tmpl +1 -0
  270. package/templates/harness/repo/app/(repo)/page.tsx.tmpl +1 -0
  271. package/templates/harness/repo/app/experiences/[name]/connect/page.tsx.tmpl +1 -0
  272. package/templates/harness/repo/app/experiences/[name]/layout.tsx.tmpl +9 -0
  273. package/templates/harness/repo/app/experiences/page.tsx.tmpl +5 -0
  274. package/templates/harness/repo/app/harness-shell.tsx.tmpl +9 -0
  275. package/templates/harness/repo/app/integrations/[slug]/layout.tsx.tmpl +1 -0
  276. package/templates/harness/repo/app/integrations/page.tsx.tmpl +5 -0
  277. package/templates/harness/repo/app/repo-experiences.ts.tmpl +5 -0
  278. package/templates/harness/styles/globals.css.tmpl +15 -1
  279. package/templates/package.json.tmpl +4 -4
  280. package/templates/skills/build-agent/SKILL.md +43 -10
  281. package/templates/skills/build-agent/references/agent-json.md +4 -1
  282. package/templates/skills/build-agent/references/app-setup.md +14 -7
  283. package/templates/skills/build-agent/references/rollout.md +26 -2
  284. package/templates/skills/build-agent/references/set-up-an-integration.md +46 -29
  285. package/templates/skills/build-integration/references/widget-design.md +1 -1
  286. package/templates/skills/ekanos-platform/SKILL.md +66 -14
  287. package/templates/skills/polish-widgets/references/patterns.md +3 -3
package/dist/index.js CHANGED
@@ -9,12 +9,16 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
+ import { isRepoRoot } from '@ekanos/integration-schema/repo';
12
13
  import * as fs from 'node:fs';
14
+ import * as path from 'node:path';
13
15
  import { runAgents } from './commands/agents.js';
14
16
  import { TEMPLATE_EDIT_HELP } from './commands/agents-edit.js';
15
17
  import { runApps } from './commands/apps.js';
18
+ import { runAudiences } from './commands/audiences.js';
16
19
  import { runCatalog } from './commands/catalog.js';
17
20
  import { runDev } from './commands/dev.js';
21
+ import { EXPERIENCE_COMMANDS } from './commands/index-experience.js';
18
22
  import { runInit } from './commands/init.js';
19
23
  import { runKnowledge } from './commands/knowledge.js';
20
24
  import { runLogin } from './commands/login.js';
@@ -33,11 +37,13 @@ import { runUpgrade } from './commands/upgrade.js';
33
37
  import { runUse } from './commands/use.js';
34
38
  import { runValidate } from './commands/validate.js';
35
39
  import { runWhoami } from './commands/whoami.js';
40
+ import { runWorkflows } from './commands/workflows.js';
36
41
  import { CliContext } from './context.js';
37
42
  import { SDK_DOCS_URL, docsPage } from './docs-links.js';
38
- import { toCliError, usageError } from './errors.js';
39
- import { EXIT_CODES } from './exit-codes.js';
43
+ import { preconditionError, toCliError, usageError } from './errors.js';
44
+ import { detectInvocation, setInvocation } from './invocation.js';
40
45
  import { parseArgv } from './parse-argv.js';
46
+ import { unknownCommand } from './unknown-command.js';
41
47
  export { CliContext } from './context.js';
42
48
  export { EXIT_CODES, ERROR_CODES } from './exit-codes.js';
43
49
  const GLOBAL_SPECS = {
@@ -61,12 +67,44 @@ const DIR = {
61
67
  type: 'string',
62
68
  description: 'Project directory, where ekanos.json and relative paths are read (default: the current one)',
63
69
  };
70
+ /**
71
+ * Where a secret comes from without a terminal (`secret-input.ts`), for every
72
+ * command that takes one. Never a value: a secret in argv lands in shell
73
+ * history and the process list.
74
+ */
75
+ const SECRET_INPUT = {
76
+ 'secret-env': {
77
+ type: 'string',
78
+ description: 'The secret, from this environment variable (its NAME, never the value)',
79
+ },
80
+ 'secret-file': {
81
+ type: 'string',
82
+ description: 'The secret, from this file: yours, mode 600, not a symlink',
83
+ },
84
+ 'secret-stdin': {
85
+ type: 'boolean',
86
+ description: 'The secret, piped in on stdin',
87
+ },
88
+ 'set-secret': {
89
+ type: 'string',
90
+ repeatable: true,
91
+ description: 'One field of a form: <field>=env:<NAME> | file:<path> | stdin',
92
+ },
93
+ browser: {
94
+ type: 'boolean',
95
+ description: 'Type the secret into a one-time form in your browser on this computer (never shown to the run that asked)',
96
+ },
97
+ };
64
98
  const COMMAND_SPECS = {
65
99
  init: {
66
100
  slug: {
67
101
  type: 'string',
68
102
  description: 'Kebab-case id of the integration, e.g. acme-crm',
69
103
  },
104
+ kind: {
105
+ type: 'string',
106
+ description: 'What to scaffold: integration (default), experience (widgets and workflows that depend on integrations) or repo (one repo holding many of both)',
107
+ },
70
108
  dir: {
71
109
  type: 'string',
72
110
  description: 'Target directory (default ./<slug>; --dir . scaffolds in place)',
@@ -103,7 +141,11 @@ const COMMAND_SPECS = {
103
141
  },
104
142
  start: {
105
143
  type: 'boolean',
106
- description: 'Start the server even in JSON mode; --no-start only scaffolds',
144
+ description: 'Start the server even in JSON mode or under an agent (non-interactive runs only scaffold and print the command); --no-start only scaffolds',
145
+ },
146
+ 'contracts-dir': {
147
+ type: 'string',
148
+ description: 'Experiences: a directory of <integration>.contract.json files to resolve integrations from (offline registry)',
107
149
  },
108
150
  },
109
151
  test: {
@@ -165,179 +207,271 @@ const COMMAND_SPECS = {
165
207
  source: SOURCE,
166
208
  yes: {
167
209
  type: 'boolean',
168
- description: 'Confirm products disable, scopes remove',
210
+ description: 'Confirm products disable, scopes remove, roles delete',
169
211
  },
212
+ // `sources roles create|update`.
213
+ 'display-name': {
214
+ type: 'string',
215
+ description: "roles create|update: the role's display name",
216
+ },
217
+ description: {
218
+ type: 'string',
219
+ description: "roles create|update: the role's description",
220
+ },
221
+ level: {
222
+ type: 'string',
223
+ description: 'roles create|update: its level, 1 to 1000; lower ranks higher (default 100)',
224
+ },
225
+ permission: {
226
+ type: 'string',
227
+ repeatable: true,
228
+ description: 'roles create|update: a permission it holds (update: replaces them all)',
229
+ },
230
+ grant: {
231
+ type: 'string',
232
+ repeatable: true,
233
+ description: 'roles update: add this permission',
234
+ },
235
+ revoke: {
236
+ type: 'string',
237
+ repeatable: true,
238
+ description: 'roles update: take this permission away',
239
+ },
240
+ // `sources people`.
241
+ search: {
242
+ type: 'string',
243
+ description: 'people: only those whose name or email matches',
244
+ },
245
+ page: { type: 'string', description: 'people: which page, from 1' },
170
246
  },
171
- catalog: {
172
- host: HOST,
173
- source: SOURCE,
174
- dir: DIR,
175
- },
176
- apps: {
247
+ audiences: {
177
248
  host: HOST,
178
249
  source: SOURCE,
179
250
  dir: DIR,
180
- 'no-wait': {
251
+ yes: {
181
252
  type: 'boolean',
182
- description: 'connect: print the links and return without waiting',
253
+ description: 'set: confirm dropping a product people then stop seeing',
183
254
  },
184
- yes: {
255
+ name: { type: 'string', description: 'set: rename the audience' },
256
+ description: {
257
+ type: 'string',
258
+ description: 'create|set: what the audience is for',
259
+ },
260
+ products: {
261
+ type: 'string',
262
+ description: 'create|set: the products it shows, as a,b (set: replaces them; none empties it)',
263
+ },
264
+ widgets: {
265
+ type: 'string',
266
+ repeatable: true,
267
+ description: 'create|set: <product>=<id>,<id> shows only those widgets of the product (<product>= lifts the rule)',
268
+ },
269
+ tools: {
270
+ type: 'string',
271
+ repeatable: true,
272
+ description: 'create|set: <product>=<name>,<name> allows only those tools of the product (<product>= lifts the rule)',
273
+ },
274
+ default: {
185
275
  type: 'boolean',
186
- description: 'disconnect: confirm without the question (needed in a script or agent run)',
276
+ description: 'create|set: new teams of the Source start on it',
277
+ },
278
+ 'all-entitled': {
279
+ type: 'boolean',
280
+ description: 'create|set: show every product the Source has, now and later (--no-all-entitled turns it off)',
187
281
  },
188
282
  },
189
- teams: {
283
+ catalog: {
190
284
  host: HOST,
191
285
  source: SOURCE,
192
286
  dir: DIR,
193
- connection: {
287
+ },
288
+ apps: Object.assign({ host: HOST, source: SOURCE, dir: DIR, 'no-wait': {
289
+ type: 'boolean',
290
+ description: 'connect: print the links and return without waiting',
291
+ }, yes: {
292
+ type: 'boolean',
293
+ description: 'disconnect: confirm without the question (needed in a script or agent run)',
294
+ }, set: {
295
+ type: 'string',
296
+ repeatable: true,
297
+ description: 'set, connect: <name>=<value> for a field that is not a secret (set: clientId)',
298
+ } }, SECRET_INPUT),
299
+ teams: Object.assign(Object.assign({ host: HOST, source: SOURCE, dir: DIR, connection: {
194
300
  type: 'string',
195
301
  description: 'connect, app set: which of the integration\'s connections (e.g. "agent" for Linear\'s agent)',
196
- },
302
+ },
197
303
  // `teams app set`: which fields to enter, non-secret values, removals.
198
304
  field: {
199
305
  type: 'string',
200
306
  repeatable: true,
201
307
  description: 'app set: enter just this field at your terminal',
202
- },
203
- set: {
308
+ }, set: {
204
309
  type: 'string',
205
310
  repeatable: true,
206
- description: 'app set: <name>=<value> for a non-secret field',
207
- },
208
- clear: {
311
+ description: 'app set, connect: <name>=<value> for a field that is not a secret (a list as a,b)',
312
+ }, self: {
313
+ type: 'boolean',
314
+ description: 'members add: you join the team now, with no invitation email',
315
+ },
316
+ // `teams audience`.
317
+ member: {
318
+ type: 'string',
319
+ description: "audience: one member's own audience, by email",
320
+ }, inherit: {
321
+ type: 'boolean',
322
+ description: "audience --member: clear their own audience so the team's applies",
323
+ } }, SECRET_INPUT), { clear: {
209
324
  type: 'string',
210
325
  repeatable: true,
211
326
  description: "app set: remove this field's saved value",
212
- },
213
- slug: {
327
+ }, slug: {
214
328
  type: 'string',
215
329
  description: "create: the team's slug (default: from its name)",
216
- },
217
- owner: {
330
+ }, owner: {
218
331
  type: 'string',
219
332
  description: 'create: invite this email as the owner instead of you',
220
- },
221
- role: {
333
+ }, role: {
222
334
  type: 'string',
223
- description: 'members invite: the role to invite as (default member)',
224
- },
225
- yes: {
335
+ description: 'members add, members invite: the role to give (default member)',
336
+ }, yes: {
226
337
  type: 'boolean',
227
- description: 'Confirm a change that removes, pauses, revokes or opens something up (members remove, webhook, disconnect, agents disable, triggers add slack, triggers remove|rotate, links remove, mcp remove, scope set that drops a scope)',
228
- },
338
+ description: 'Confirm a change that removes, pauses, revokes or opens something up (members remove, leave, groups delete, groups members remove, webhook, disconnect, agents disable|archive, mcp detach, triggers add slack|disable, triggers edit of a slack audience, triggers remove|rotate, links remove, mcp remove, scope set that drops a scope)',
339
+ },
229
340
  // `teams agents|triggers|links|mcp` (team-agents.ts).
230
341
  as: {
231
342
  type: 'string',
232
- description: 'connect: the member who signs in; agents enable: the member the agent runs as',
233
- },
343
+ description: 'connect: the member who signs in; agents enable: the member the agent runs as (chosen once, when it is turned on)',
344
+ },
234
345
  // `teams agents enable|pin … --draft`: test the unpublished draft there.
235
346
  draft: {
236
347
  type: 'boolean',
237
348
  description: "agents enable|pin: put this team alone on the template's unpublished draft",
238
- },
239
- // `teams agents enable|policy`: also confirm, as yourself, what the
240
- // agent may use (whoever sets it up confirms it, plan 247.2).
349
+ },
350
+ // `teams agents enable|policy|create|apply`: also confirm, as yourself,
351
+ // what the agent may use (whoever sets it up confirms it, plan 247.2).
241
352
  'confirm-access': {
242
353
  type: 'boolean',
243
- description: 'agents enable|policy: also confirm, as yourself, what it may use',
244
- },
245
- reads: {
354
+ description: 'agents enable|policy|create|apply: also confirm, as yourself, what it may use',
355
+ },
356
+ // `teams agents create|apply`: the agent.json, read as `agents apply`
357
+ // reads it (team-agents-define.ts).
358
+ from: {
359
+ type: 'string',
360
+ description: 'agents create: the agent directory or agent.json',
361
+ }, personal: {
362
+ type: 'boolean',
363
+ description: 'agents create|apply: your own agent, which acts for you and only you change',
364
+ }, 'base-version': {
365
+ type: 'string',
366
+ description: 'agents apply: the agent version your edit starts from ("teams agents show")',
367
+ }, overwrite: {
368
+ type: 'boolean',
369
+ description: 'agents apply: replace a newer version on the host anyway',
370
+ }, skills: {
371
+ type: 'boolean',
372
+ description: 'agents create|apply: --no-skills skips pushing <dir>/skills/<slug>/ as team skills',
373
+ }, reads: {
246
374
  type: 'string',
247
375
  description: 'agents policy: auto|approve for every read tool',
248
- },
249
- writes: {
376
+ }, writes: {
250
377
  type: 'string',
251
378
  description: 'agents policy: auto|approve for every write tool',
252
- },
253
- effect: {
379
+ }, effect: {
254
380
  type: 'string',
255
381
  description: 'Not read: "teams mcp effect" takes the effect as an argument',
256
- },
257
- kind: { type: 'string', description: 'links add: talk|delegate' },
258
- mode: {
382
+ }, agent: {
383
+ type: 'string',
384
+ description: 'mcp refresh: list again the tools of every server this agent has',
385
+ }, kind: { type: 'string', description: 'links add: talk|delegate' }, mode: {
259
386
  type: 'string',
260
387
  description: 'links add: await|handoff, for a delegate link',
261
- },
262
- url: { type: 'string', description: "mcp add: the server's URL" },
263
- auth: {
388
+ }, url: { type: 'string', description: "mcp add: the server's URL" }, auth: {
264
389
  type: 'string',
265
390
  description: 'mcp add: none|token|oauth (default oauth)',
266
- },
267
- name: { type: 'string', description: "mcp add: the server's display name" },
268
- cron: {
391
+ }, name: {
269
392
  type: 'string',
270
- description: 'triggers add schedule: a cron expression',
271
- },
272
- timezone: {
393
+ description: "mcp add: the server's display name; groups update: the group's new name",
394
+ },
395
+ // `teams groups create|update`.
396
+ description: {
273
397
  type: 'string',
274
- description: 'triggers add schedule: an IANA time zone (default UTC)',
275
- },
276
- prompt: {
398
+ description: "groups create|update: the group's description",
399
+ }, products: {
277
400
  type: 'string',
278
- description: 'triggers add: what the agent is told when it fires',
279
- },
401
+ description: 'groups create|update: the products it narrows to, comma-separated (none: no narrowing)',
402
+ },
403
+ // `teams disconnect|health <team> <product> --mine`: the caller's own.
404
+ mine: {
405
+ type: 'boolean',
406
+ description: "disconnect, health <product>: your own personal connection, never another member's",
407
+ },
408
+ // `teams health <team> <product> --run`.
409
+ run: {
410
+ type: 'boolean',
411
+ description: "health: run the integration's health check now",
412
+ }, cron: {
413
+ type: 'string',
414
+ description: 'triggers add|edit schedule: a cron expression',
415
+ }, timezone: {
416
+ type: 'string',
417
+ description: 'triggers add|edit schedule: an IANA time zone (default UTC)',
418
+ }, prompt: {
419
+ type: 'string',
420
+ description: 'triggers add|edit: what the agent is told when it fires',
421
+ },
280
422
  // Repeatable `field=value`, or one JSON object — as at the template level.
281
423
  filter: {
282
424
  type: 'string',
283
425
  repeatable: true,
284
- description: 'triggers add event: field=a,b (repeatable) or one JSON object',
285
- },
426
+ description: 'triggers add|edit event: field=a,b (repeatable) or one JSON object',
427
+ },
286
428
  // `teams <area> … --team <slug>`: the team, in place of the positional.
287
429
  team: {
288
430
  type: 'string',
289
431
  description: 'The team, in place of the <team> argument (teams <area> … --team <slug>)',
290
- },
432
+ },
291
433
  // `teams triggers add … slack|linear`: where it answers. `--no-mention`
292
434
  // is the negation of `mention`.
293
435
  channel: {
294
436
  type: 'string',
295
437
  repeatable: true,
296
- description: 'triggers add slack: a channel it answers in (#name or id)',
297
- },
298
- dms: {
438
+ description: 'triggers add|edit slack: a channel it answers in (#name or id); edit replaces the list',
439
+ }, dms: {
299
440
  type: 'boolean',
300
- description: 'triggers add slack: also answer direct messages',
301
- },
302
- label: {
441
+ description: 'triggers add|edit slack: also answer direct messages (edit: --no-dms stops)',
442
+ }, label: {
303
443
  type: 'string',
304
444
  repeatable: true,
305
- description: 'triggers add linear: a label that hands an issue to it',
306
- },
307
- mention: {
445
+ description: 'triggers add|edit linear: a label that hands an issue to it; edit replaces the list',
446
+ }, mention: {
308
447
  type: 'boolean',
309
- description: 'triggers add linear: --no-mention ignores @mentions',
310
- },
448
+ description: 'triggers add|edit linear: --no-mention ignores @mentions (edit: --mention answers them)',
449
+ },
311
450
  // `teams connect`: `--no-wait`, `--no-open` (the negation of `open`),
312
451
  // `--link-only`.
313
452
  'no-wait': {
314
453
  type: 'boolean',
315
454
  description: 'connect: print the sign-in link and return at once',
316
- },
317
- open: {
455
+ }, open: {
318
456
  type: 'boolean',
319
457
  description: 'connect: --no-open prints the link without opening a browser',
320
- },
321
- 'link-only': {
458
+ }, 'link-only': {
322
459
  type: 'boolean',
323
460
  description: "connect: print the team's page instead of connecting",
324
- },
461
+ },
325
462
  // `teams events <team> [--product] [--since] [--limit]`.
326
463
  product: {
327
464
  type: 'string',
328
465
  description: 'events: only this product (or name it after the team)',
329
- },
330
- since: {
466
+ }, since: {
331
467
  type: 'string',
332
468
  description: 'events: since 30m, 24h, 7d or a date',
333
- },
334
- limit: { type: 'string', description: 'events: how many, 1 to 100' },
469
+ }, limit: { type: 'string', description: 'events: how many, 1 to 100' },
335
470
  // `teams connect --reconnect`.
336
471
  reconnect: {
337
472
  type: 'boolean',
338
473
  description: 'connect: connect again though it reads as connected (revoked or expired)',
339
- },
340
- },
474
+ } }),
341
475
  reviewers: {
342
476
  host: HOST,
343
477
  source: SOURCE,
@@ -542,6 +676,51 @@ const COMMAND_SPECS = {
542
676
  description: 'edit: a file with the reply to send instead',
543
677
  },
544
678
  },
679
+ workflows: {
680
+ host: HOST,
681
+ source: SOURCE,
682
+ dir: DIR,
683
+ team: { type: 'string', description: 'The team whose workflows these are' },
684
+ all: { type: 'boolean', description: 'list: include archived workflows' },
685
+ out: {
686
+ type: 'string',
687
+ description: 'pull: where to write workflow.json (default workflows/<name>)',
688
+ },
689
+ force: {
690
+ type: 'boolean',
691
+ description: 'pull: overwrite a local workflow.json that differs',
692
+ },
693
+ update: {
694
+ type: 'string',
695
+ description: 'apply: the workflow (id or name) to replace; without it apply creates a draft',
696
+ },
697
+ 'confirm-access': {
698
+ type: 'boolean',
699
+ description: 'enable: approve, as yourself, what its Custom Code reaches (hosts, writes)',
700
+ },
701
+ 'dry-run': {
702
+ type: 'boolean',
703
+ description: 'run: rehearse it (writes recorded, not sent; gates pass); a draft may run so',
704
+ },
705
+ input: {
706
+ type: 'string',
707
+ description: 'run --dry-run: a JSON file read as the sample trigger.*',
708
+ },
709
+ follow: {
710
+ type: 'boolean',
711
+ description: 'run: wait, printing each step, until it ends or waits on a gate',
712
+ },
713
+ workflow: {
714
+ type: 'string',
715
+ description: "runs list: only this workflow's runs (id or name)",
716
+ },
717
+ status: {
718
+ type: 'string',
719
+ description: 'runs list: only runs in this status (running, waiting_approval, completed, rejected, failed, canceled)',
720
+ },
721
+ reason: { type: 'string', description: 'approvals reject: why' },
722
+ yes: { type: 'boolean', description: 'runs cancel: confirm' },
723
+ },
545
724
  use: {
546
725
  host: HOST,
547
726
  dir: DIR,
@@ -554,6 +733,14 @@ const COMMAND_SPECS = {
554
733
  type: 'boolean',
555
734
  description: 'Publish to the ekanos.json source without asking (needed in a script with several seats)',
556
735
  },
736
+ 'dry-run': {
737
+ type: 'boolean',
738
+ description: 'Experiences and repos: gate, build and pack every package under .ekanos/publish/, publish nothing (publishing an experience is not supported by the host yet)',
739
+ },
740
+ all: {
741
+ type: 'boolean',
742
+ description: 'In a repo: publish every package',
743
+ },
557
744
  },
558
745
  upgrade: {
559
746
  dir: DIR,
@@ -577,9 +764,12 @@ const COMMAND_SPECS = {
577
764
  /** Commands that take positional arguments, and what each means, in order. Every other command is positional-free. */
578
765
  const POSITIONAL_COMMANDS = {
579
766
  use: ['source-slug'],
767
+ // In a repo: `publish <package>...` (repeatable, see VARIADIC_COMMANDS).
768
+ publish: ['package'],
580
769
  // `sources scopes add <scope> <product>` is the longest form.
581
770
  sources: ['subcommand', 'verb', 'product-or-scope', 'product-slug'],
582
771
  catalog: ['product-slug'],
772
+ audiences: ['subcommand', 'audience'],
583
773
  apps: ['subcommand', 'product-slug'],
584
774
  // `teams agents policy <team> <key> set <tool> auto` is the longest form.
585
775
  teams: [
@@ -597,15 +787,37 @@ const POSITIONAL_COMMANDS = {
597
787
  knowledge: ['subcommand', 'path-or-query'],
598
788
  runs: ['subcommand', 'agent-key-or-run-id'],
599
789
  approvals: ['subcommand', 'approval-id'],
790
+ // `workflows runs show <run-id>` is the longest form.
791
+ workflows: ['subcommand', 'workflow-or-verb', 'id'],
600
792
  // `triggers <key> add event <trigger-key>` is the longest form: five tokens.
601
793
  agents: ['subcommand', 'key-or-path', 'action', 'arg', 'value'],
602
794
  };
795
+ for (const command of EXPERIENCE_COMMANDS) {
796
+ COMMAND_SPECS[command.name] = command.specs;
797
+ if (command.positionals)
798
+ POSITIONAL_COMMANDS[command.name] = command.positionals;
799
+ }
800
+ /** Commands that act on one package: refused at a repo root, with the way in. */
801
+ const REPO_ROOT_REFUSED = new Set([
802
+ 'validate',
803
+ 'test',
804
+ 'check',
805
+ 'build',
806
+ 'sync',
807
+ 'add',
808
+ 'codegen',
809
+ 'contract',
810
+ ]);
811
+ /** Commands whose last positional repeats. */
812
+ const VARIADIC_COMMANDS = new Set(['publish']);
603
813
  const COMMANDS = Object.keys(COMMAND_SPECS);
604
814
  const USAGE = [
605
815
  'Usage: ekanos <command> [options]',
606
816
  '',
607
817
  'Commands:',
608
- ' init Scaffold a new integration project into ./<slug> (--slug <slug> --yes)',
818
+ ' init Scaffold a new integration project into ./<slug> (--slug <slug> --yes);',
819
+ ' --kind experience for an experience, --kind repo for a repo of many',
820
+ ...EXPERIENCE_COMMANDS.flatMap((command) => command.usage),
609
821
  ' validate Validate ekanos.json + the integration definition (--json)',
610
822
  ' dev Scaffold .ekanos/harness and run it with your own Next',
611
823
  ' test Run the project test script via its package manager (--json).',
@@ -630,13 +842,35 @@ const USAGE = [
630
842
  ' and pointed at /admin) | scopes (each product scope: its',
631
843
  ' products and how many teams hold it; a team sees in the app',
632
844
  " only its scopes' products) | scopes add <scope> <product> |",
633
- ' scopes remove <scope> <product> --yes',
634
- ' catalog What a source can already use, and how to set it up',
845
+ ' scopes remove <scope> <product> --yes | roles [list] (the roles',
846
+ ' every team of the Source hands out, each with its level and',
847
+ ' permissions) | roles create <name> [--display-name <text>]',
848
+ ' [--description <text>] [--level <n>] [--permission <p>]… |',
849
+ ' roles update <role> [--display-name] [--description] [--level]',
850
+ ' [--permission <p>]… (replaces) [--grant <p>]… [--revoke <p>]… |',
851
+ ' roles delete <role> [--yes] (owner, admin and member are system',
852
+ " roles: never changed or deleted; a held role isn't deleted) |",
853
+ ' people [--search <text>] [--page <n>] (everyone with access, one',
854
+ ' row per person, with their teams and role on each)',
855
+ " audiences A source's audiences, for its Source admin (they replace product",
856
+ ' scopes): list | show <audience> | create <name> [--products a,b |',
857
+ ' --all-entitled] [--description <text>] [--default] [--widgets',
858
+ ' <product>=<id>,…]… [--tools <product>=<name>,…]… | set <audience>',
859
+ ' [--name] [--description] [--default] [--all-entitled] [--products',
860
+ ' a,b|none] [--widgets …]… [--tools …]… [--yes] (--products replaces;',
861
+ ' dropping a product needs --yes). A team or member is put on one',
862
+ ' with "teams audience <team> [<audience>] [--member <email>]"',
863
+ ' catalog What a source can already use, and how to set it up:',
864
+ ' Experiences, then Integrations; one product shows its tools',
865
+ ' with their inputs and whose connection agent runs use',
635
866
  ' ("ekanos catalog [<product-slug>] --source <slug>", --json)',
636
867
  " apps The Source's app for an integration teams sign in through:",
637
868
  ' show <product> | set <product> (Source admin; takes the secret',
638
- ' at your terminal, Enter keeps a saved value) | connect',
639
- ' <product> [--no-wait] (one',
869
+ ' at your terminal, Enter keeps a saved value; without one,',
870
+ ' --set clientId=<id> and --secret-env <NAME> | --secret-file',
871
+ ' <path> | --secret-stdin | --browser) | connect',
872
+ ' <product> [--no-wait] [--set <field>=<value>]… [--set-secret',
873
+ ' <field>=env:<NAME>|file:<path>|stdin]… [--browser] (one',
640
874
  " connection for the whole Source, used by the Source's provided",
641
875
  ' agents in every team) | disconnect <product> [--yes] (a',
642
876
  ' script or agent run needs --yes) | list',
@@ -644,15 +878,32 @@ const USAGE = [
644
878
  ' create <name> [--slug <slug>] [--owner <email>] (you own it',
645
879
  ' unless --owner, who is invited as owner) |',
646
880
  ' rename <team> <name> (the slug follows the name, as in the app) |',
647
- ' members list <team> | members invite <team> <email> [--role',
648
- ' <role>] (default member; sends the invitation email) |',
881
+ ' members list <team> | members add <team> --self | <email>',
882
+ ' [--role <role>] (a member now, no email: you, or someone with',
883
+ ' an Ekanos account on the Source) | members invite <team> <email>',
884
+ ' [--role <role>] (default member; sends the invitation email) |',
649
885
  ' members role <team> <email> <role> | members remove <team>',
650
- " <email> --yes | roles list <team> (each role's permissions) |",
886
+ ' <email> --yes | members resend <team> <email> (a live',
887
+ ' invitation is resent, an expired one renewed) |',
888
+ " roles list <team> (each role's permissions) |",
889
+ ' leave <team> [--yes] (you leave it; never the primary owner) |',
890
+ ' groups list <team> | groups create <team> <name> [--description',
891
+ ' <text>] [--products <slug,slug>] | groups update <team> <group>',
892
+ ' [--name <name>] [--description <text>] [--products <slug,…>|none]',
893
+ ' | groups delete <team> <group> [--yes] | groups members',
894
+ ' add|remove <team> <group> <email> (leave, groups delete and',
895
+ ' remove ask first; a script or agent run needs --yes) |',
896
+ ' health <team> [<product>] [--run] (each connected integration',
897
+ ' and its health; --run checks one now; <product> --mine: your own',
898
+ ' personal connection, any seat, as a member) | workspace list <team> |',
899
+ ' workspace default <team> <workspace> (where the team lands) |',
651
900
  ' connect <team> <product> [--connection <id>] [--as',
652
- ' <member-email>] [--no-wait] [--no-open] [--link-only]',
653
- ' [--reconnect] (connects',
654
- " it: asks the form's fields at your terminal, or opens the",
655
- ' sign-in for you or the member --as names and waits;',
901
+ ' <member-email>] [--set <field>=<value>]… [--no-wait] [--no-open]',
902
+ ' [--link-only] [--reconnect] (connects',
903
+ " it: asks the form's fields at your terminal or takes them from",
904
+ ' --set (a list as a,b; secrets and JSON only at a prompt; an optional',
905
+ ' one can be skipped), then opens the sign-in for you — your own',
906
+ ' connection, for a personal product — or the member --as names, and waits;',
656
907
  " --connection agent installs the team's Linear agent (its own",
657
908
  ' Linear app, as a member), which @mention and assign triggers',
658
909
  " need; --link-only prints the team's page; --reconnect connects",
@@ -662,30 +913,52 @@ const USAGE = [
662
913
  ' Slack, or the Ekanos Agents app for Linear, which slack and',
663
914
  ' linear triggers answer through; same link-and-wait) |',
664
915
  " disconnect <team> <product> --yes (takes the team's connection",
665
- " and its tokens down; its own app's saved fields stay) |",
916
+ " and its tokens down; its own app's saved fields stay; --mine:",
917
+ " your own personal connection, never another member's) |",
666
918
  ' list | show <team> (one screen: members, each integration and',
667
919
  ' whose connection it is, each agent live or the next step) |',
668
920
  ' show <team> <product> (a checklist: fields, callback',
669
921
  ' URLs, webhooks, connect link) | app set <team> <product>',
670
922
  ' [--connection <id>] [--field <name>]… [--set <name>=<value>]…',
671
- ' [--clear <name>]… (any one field at a time; a field you skip',
672
- ' keeps its saved value; secrets only at your terminal) |',
923
+ ' [--clear <name>]… [--set-secret <name>=env:<NAME>|file:<path>|stdin]…',
924
+ ' [--browser] (any one field at a time; a field you skip keeps',
925
+ ' its saved value; a secret at your terminal, from the',
926
+ ' environment, a private file, stdin or a browser form, never',
927
+ ' as an argument) |',
928
+ ' llm-key show <team> | llm-key set <team> [--secret-env <NAME> |',
929
+ ' --secret-file <path> | --secret-stdin | --browser] | llm-key',
930
+ " remove <team> --yes (the team's own Anthropic key, as its",
931
+ ' settings page sets it; show gives only its last four characters) |',
673
932
  ' webhook <team> <product> [<webhook-id>] [--yes] (URL saved to a 0600 file under ~/.ekanos/secrets, never printed) |',
674
933
  ' events <team> [<product>] [--product <slug>] [--since 24h|7d|<date>]',
675
934
  ' [--limit <n>] (the integration events recorded for the team, each',
676
935
  ' with its status, last error and the runs it started, or why none did)',
677
- ' | scope show <team> (the product scopes it holds and the products',
936
+ ' | audience <team> [<audience>] [--member <email> [--inherit]] [--yes]',
937
+ " (the audience a team starts its members on; a member's own, a Source",
938
+ " admin's only, overrides it; --inherit clears it; moving people to an",
939
+ ' audience that shows less needs --yes)',
940
+ ' | scope show <team> (DEPRECATED, use audience: the product scopes it holds and the products',
678
941
  ' it sees in the app) | scope set <team> <scope> [<scope>…] [--yes]',
679
942
  ' (replaces them; dropping one needs --yes)',
680
943
  " A team's agents, for the Source admin (teams <area> <verb> <team> …):",
681
944
  ' agents list <team> | agents show <team> <key> (is it live, and if',
682
945
  ' not, why: each connection and whose it is, each trigger and how its',
683
946
  ' events arrive, last event and run, one Next command)',
947
+ ' | agents create <team> --from <dir|agent.json> [--personal]',
948
+ " [--confirm-access] [--no-skills] (the team's own agent, or with",
949
+ ' --personal your own, from the agent.json "agents apply" reads;',
950
+ ' you become its creator, so you must be an active member of the',
951
+ ' team; it starts as a draft) | agents apply <team> <dir|agent.json>',
952
+ ' [--personal] [--base-version <n>] [--overwrite] [--confirm-access]',
953
+ ' (creates it, or changes it from the same file; a personal agent',
954
+ " is its creator's alone to change)",
684
955
  ' | agents enable <team> <key> [--as <member-email>]',
685
956
  ' [--draft] [--confirm-access] (--confirm-access: you also confirm',
686
957
  ' what it may use) | agents confirm <team> <key> (you confirm what',
687
958
  ' it may use, as yourself: whoever sets an agent up confirms it, never',
688
959
  ' a team member) | agents disable <team> <key> --yes | agents',
960
+ ' archive <team> <key> [--yes] (off the list; runs and setup kept) |',
961
+ ' agents unarchive <team> <key> (back, paused) | agents',
689
962
  ' unpin <team> <key> | agents pin <team> <key> <version> | agents pin',
690
963
  ' <team> <key> --draft (this team only runs the unpublished draft, by',
691
964
  ' hand, every write waiting; the next publish puts it back to',
@@ -694,17 +967,29 @@ const USAGE = [
694
967
  ' [--reads auto|approve] [--writes auto|approve] [--confirm-access]',
695
968
  ' | agents skills <team> <key> [add|remove <skill>] (where the',
696
969
  ' template allows team skills; its own skills stay)',
697
- ' triggers list|add|remove <team> <key> … (add … event <id>',
970
+ ' | agents tags <team> <key> [add|remove <tag>] (labels that grant',
971
+ ' nothing; a tag the team lacks is made)',
972
+ ' triggers list|add|remove <team> <key> … (add … schedule --cron',
973
+ ' <expr> [--timezone <zone>] --prompt <text> | add … webhook --prompt',
974
+ ' <text> (its token saved to a 0600 file, never printed; a personal',
975
+ " agent's webhook acts as its creator) | add … event <id>",
698
976
  " [--filter field=a,b]… or --filter '<json>'; remove <trigger-id|key> --yes;",
699
977
  ' add … slack --channel',
700
978
  ' <#name|id>… [--dms] --yes | add … linear [--label <name>]…',
701
979
  ' [--no-mention], once installed with teams connect) | triggers',
702
980
  " rotate <team> <key> <trigger-key|id> [--yes] (a webhook trigger's",
703
- ' new token, the old one revoked; saved to a 0600 file, never printed) | links list|add|remove',
981
+ ' new token, the old one revoked; saved to a 0600 file, never printed)',
982
+ ' | triggers enable|disable <team> <key> <trigger-id|key> (disable',
983
+ ' asks first, --yes in a script) | triggers edit <team> <key>',
984
+ ' <trigger-id|key> [--prompt …] [--cron … --timezone …] [--filter …]',
985
+ ' [--channel …] [--dms|--no-dms] [--label …] [--mention|--no-mention]',
986
+ ' (in place: a webhook keeps its URL and token) | links list|add|remove',
704
987
  ' <team> <from-key> [<to-key> --kind talk|delegate --mode await|handoff]',
705
988
  ' (remove <link-id|to-key> [--kind <kind>] --yes)',
706
- ' | mcp list|add|status|token|effect|remove <team> [<server-key> …]',
707
- ' (remove needs --yes)',
989
+ ' | mcp list|add|status|token|effect|refresh|remove <team> [<server-key> …]',
990
+ ' (remove needs --yes; refresh <team> --agent <key>: every server it',
991
+ ' has) | mcp attach|detach <team> <server-key> <agent-key> (detach',
992
+ ' asks first, --yes in a script; where the template allows)',
708
993
  ' Every area also takes the team as --team <slug>.',
709
994
  ' ("ekanos teams --help" spells out each area)',
710
995
  ' reviewers Who decides agent reply drafts, for the Source admin:',
@@ -782,8 +1067,29 @@ const USAGE = [
782
1067
  " approvals What a team's agent runs wait on: list --team <slug> |",
783
1068
  ' approve|reject <id> --team <slug> [--reason "…"] |',
784
1069
  ' edit <id> --team <slug> --body <file> (edit & send a reply draft)',
1070
+ ' The Source admin sees all; any other seat holder only what they',
1071
+ " may decide (managing or reviewing the team's agents, a named",
1072
+ " reviewer, a personal agent's creator). The first decision wins.",
1073
+ " workflows A team's workflows, for the Source admin (each takes --team <slug>;",
1074
+ ' a workflow is named by its id or its name):',
1075
+ ' list [--all] | show <workflow> (status, trigger, Custom Code review',
1076
+ ' state, recent runs; --json: the definition) | pull <workflow>',
1077
+ ' [--out <dir>] [--force] (writes workflow.json, the JSON the editor',
1078
+ ' saves) | apply <file|dir> [--update <workflow>] (a new draft, or',
1079
+ " that workflow's definition replaced; the editor's checks: shape,",
1080
+ ' tool effects, Custom Code security review, approval gates before',
1081
+ ' writes) | enable <workflow> [--confirm-access] (activation re-checks',
1082
+ ' it; Custom Code that writes or reaches a host needs your approval,',
1083
+ ' asked at a terminal) | disable <workflow> | run <workflow>',
1084
+ ' [--dry-run [--input <file.json>]] [--follow] (a live run needs it',
1085
+ ' on; --dry-run records writes instead of sending them) |',
1086
+ ' runs list [--workflow <workflow>] [--status <status>] | runs show',
1087
+ ' <run-id> | runs cancel <run-id> [--yes] | approvals list |',
1088
+ ' approvals approve|reject <id> [--reason "…"]',
785
1089
  ' use Set this project\'s publish target ("ekanos use <source-slug>")',
786
- ' publish Validate, pack and submit the integration to an Ekanos host',
1090
+ ' publish Validate, pack and submit the integration to an Ekanos host.',
1091
+ ' In a repo: publish [<package>...] | --all [--dry-run] (integrations',
1092
+ ' first, each package gated, artifacts under .ekanos/publish/)',
787
1093
  ' upgrade Move this toolchain to a coherent target version, with',
788
1094
  ' verify-and-rollback (--check --yes --no-verify --host <url>)',
789
1095
  '',
@@ -796,7 +1102,7 @@ const USAGE = [
796
1102
  ' bare "ekanos upgrade" MUTATES the project (installs the target versions,',
797
1103
  ' verifies with validate + the test script, rolls back on any failure).',
798
1104
  '',
799
- 'Host resolution (logout, whoami, status, sources, catalog, apps, teams, reviewers, routes, agents, skills, knowledge, runs, approvals, use, publish, upgrade):',
1105
+ 'Host resolution (logout, whoami, status, sources, catalog, apps, teams, reviewers, routes, agents, skills, knowledge, runs, approvals, workflows, use, publish, upgrade):',
800
1106
  ' --host, else EKANOS_HOST, else the "host" field in ekanos.json, else the',
801
1107
  ' sole stored login. "ekanos publish" saves host and source to ekanos.json.',
802
1108
  ' "ekanos login" never reads ekanos.json for a host (--host, else',
@@ -825,16 +1131,17 @@ const USAGE = [
825
1131
  * `process.exit`, so this stays fully testable.
826
1132
  */
827
1133
  export async function run(argv, options = {}) {
828
- var _a, _b, _c, _d;
1134
+ var _a, _b, _c, _d, _e, _f;
829
1135
  const jsonFlag = scanJsonFlag(argv);
1136
+ setInvocation((_a = options.invocation) !== null && _a !== void 0 ? _a : detectInvocation((_b = options.env) !== null && _b !== void 0 ? _b : process.env), COMMANDS);
830
1137
  const ctx = new CliContext({
831
1138
  jsonFlag,
832
1139
  stdout: options.stdout,
833
1140
  stderr: options.stderr,
834
1141
  env: options.env,
835
1142
  });
836
- const cwd = (_a = options.cwd) !== null && _a !== void 0 ? _a : process.cwd();
837
- const env = (_b = options.env) !== null && _b !== void 0 ? _b : process.env;
1143
+ const cwd = (_c = options.cwd) !== null && _c !== void 0 ? _c : process.cwd();
1144
+ const env = (_d = options.env) !== null && _d !== void 0 ? _d : process.env;
838
1145
  try {
839
1146
  // `--version` is global and verb-independent, so it is answered before the
840
1147
  // command is even resolved: `ekanos --version` and `ekanos validate
@@ -853,36 +1160,35 @@ export async function run(argv, options = {}) {
853
1160
  tool: 'ekanos',
854
1161
  error: 'unknown-command',
855
1162
  commands: COMMANDS,
856
- usage: USAGE,
1163
+ help: 'ekanos --help',
857
1164
  });
858
1165
  throw usageError(`Unknown command or flag "${argv[0]}".`, `Run one of: ${COMMANDS.join(', ')}. See "ekanos --help".`);
859
1166
  }
860
1167
  if (!COMMANDS.includes(command)) {
861
- ctx.emitClaudeCodeHint({
862
- tool: 'ekanos',
863
- error: 'unknown-command',
864
- given: command,
865
- commands: COMMANDS,
866
- usage: USAGE,
867
- });
868
- throw usageError(`Unknown command "${command}".`, `Run one of: ${COMMANDS.join(', ')}. See "ekanos --help".`);
1168
+ const unknown = unknownCommand(command, rest, COMMANDS);
1169
+ ctx.emitClaudeCodeHint(Object.assign(Object.assign({ tool: 'ekanos', error: 'unknown-command', given: command }, (unknown.suggestion ? { suggestion: unknown.suggestion } : {})), { commands: COMMANDS, help: 'ekanos --help' }));
1170
+ throw unknown.error;
869
1171
  }
870
1172
  const specs = Object.assign(Object.assign({}, GLOBAL_SPECS), COMMAND_SPECS[command]);
871
1173
  const parsed = parseArgv(rest, specs);
872
1174
  if (parsed.flags.help === true) {
873
1175
  return emitCommandHelp(ctx, command);
874
1176
  }
875
- const positionalNames = (_c = POSITIONAL_COMMANDS[command]) !== null && _c !== void 0 ? _c : [];
1177
+ const positionalNames = (_e = POSITIONAL_COMMANDS[command]) !== null && _e !== void 0 ? _e : [];
876
1178
  if (positionalNames.length === 0 && parsed.positionals.length > 0) {
877
1179
  throw usageError(`"${command}" takes no positional arguments (got "${parsed.positionals[0]}").`, `Pass options as flags, e.g. "--dir <path>". See "ekanos ${command} --help".`);
878
1180
  }
879
- if (parsed.positionals.length > positionalNames.length) {
1181
+ if (parsed.positionals.length > positionalNames.length &&
1182
+ !VARIADIC_COMMANDS.has(command)) {
880
1183
  const usage = positionalNames.map((name) => `<${name}>`).join(' ');
881
1184
  throw usageError(`"${command}" takes at most ${positionalNames.length} positional ` +
882
1185
  `argument${positionalNames.length === 1 ? '' : 's'} (${usage}), ` +
883
1186
  `got ${parsed.positionals.length}.`, `e.g. "ekanos ${command} ${usage}". See "ekanos ${command} --help".`);
884
1187
  }
885
- const dir = (_d = parsed.flags.dir) !== null && _d !== void 0 ? _d : cwd;
1188
+ const dir = (_f = parsed.flags.dir) !== null && _f !== void 0 ? _f : cwd;
1189
+ if (REPO_ROOT_REFUSED.has(command) && isRepoRoot(path.resolve(dir))) {
1190
+ throw preconditionError(`"${command}" works on one package, and ${dir} is a repo root.`, `Run it inside a package (cd integrations/<slug> or cd experiences/<slug>), or use "ekanos publish --all" for the whole repo.`);
1191
+ }
886
1192
  switch (command) {
887
1193
  case 'init':
888
1194
  return await runInit(ctx, {
@@ -892,6 +1198,7 @@ export async function run(argv, options = {}) {
892
1198
  force: parsed.flags.force === true,
893
1199
  yes: parsed.flags.yes === true,
894
1200
  docs: parsed.flags.docs,
1201
+ kind: parsed.flags.kind,
895
1202
  stdinIsTTY: options.stdinIsTTY,
896
1203
  });
897
1204
  case 'validate':
@@ -903,6 +1210,7 @@ export async function run(argv, options = {}) {
903
1210
  host: parsed.flags.host,
904
1211
  force: parsed.flags.force === true,
905
1212
  start: parsed.flags.start,
1213
+ contractsDir: parsed.flags['contracts-dir'],
906
1214
  env,
907
1215
  });
908
1216
  case 'test':
@@ -952,6 +1260,19 @@ export async function run(argv, options = {}) {
952
1260
  positionals: parsed.positionals,
953
1261
  source: parsed.flags.source,
954
1262
  yes: parsed.flags.yes === true,
1263
+ flags: parsed.flags,
1264
+ dir,
1265
+ env,
1266
+ stdinIsTTY: options.stdinIsTTY,
1267
+ ask: options.ask,
1268
+ });
1269
+ case 'audiences':
1270
+ return await runAudiences(ctx, {
1271
+ host: parsed.flags.host,
1272
+ source: parsed.flags.source,
1273
+ positionals: parsed.positionals,
1274
+ yes: parsed.flags.yes === true,
1275
+ flags: parsed.flags,
955
1276
  dir,
956
1277
  env,
957
1278
  });
@@ -977,6 +1298,10 @@ export async function run(argv, options = {}) {
977
1298
  confirm: options.confirm,
978
1299
  noWait: parsed.flags['no-wait'] === true,
979
1300
  yes: parsed.flags.yes === true,
1301
+ flags: parsed.flags,
1302
+ readStdin: options.readStdin,
1303
+ openUrl: options.openUrl,
1304
+ secretFormTimeoutMs: options.secretFormTimeoutMs,
980
1305
  });
981
1306
  case 'teams':
982
1307
  return await runTeams(ctx, {
@@ -1000,6 +1325,8 @@ export async function run(argv, options = {}) {
1000
1325
  askSecret: options.askSecret,
1001
1326
  openUrl: options.openUrl,
1002
1327
  pollDelayMs: options.pollDelayMs,
1328
+ readStdin: options.readStdin,
1329
+ secretFormTimeoutMs: options.secretFormTimeoutMs,
1003
1330
  });
1004
1331
  case 'reviewers':
1005
1332
  return await runReviewers(ctx, {
@@ -1112,6 +1439,30 @@ export async function run(argv, options = {}) {
1112
1439
  stdinIsTTY: options.stdinIsTTY,
1113
1440
  confirm: options.confirm,
1114
1441
  });
1442
+ case 'workflows':
1443
+ return await runWorkflows(ctx, {
1444
+ host: parsed.flags.host,
1445
+ source: parsed.flags.source,
1446
+ positionals: parsed.positionals,
1447
+ team: parsed.flags.team,
1448
+ all: parsed.flags.all === true,
1449
+ out: parsed.flags.out,
1450
+ force: parsed.flags.force === true,
1451
+ update: parsed.flags.update,
1452
+ confirmAccess: parsed.flags['confirm-access'] === true,
1453
+ dryRun: parsed.flags['dry-run'] === true,
1454
+ input: parsed.flags.input,
1455
+ follow: parsed.flags.follow === true,
1456
+ workflow: parsed.flags.workflow,
1457
+ status: parsed.flags.status,
1458
+ reason: parsed.flags.reason,
1459
+ yes: parsed.flags.yes === true,
1460
+ dir,
1461
+ env,
1462
+ sleep: options.sleep,
1463
+ stdinIsTTY: options.stdinIsTTY,
1464
+ confirm: options.confirm,
1465
+ });
1115
1466
  case 'use':
1116
1467
  return await runUse(ctx, {
1117
1468
  host: parsed.flags.host,
@@ -1125,6 +1476,9 @@ export async function run(argv, options = {}) {
1125
1476
  source: parsed.flags.source,
1126
1477
  dir,
1127
1478
  yes: parsed.flags.yes === true,
1479
+ dryRun: parsed.flags['dry-run'] === true,
1480
+ packages: parsed.positionals,
1481
+ all: parsed.flags.all === true,
1128
1482
  env,
1129
1483
  stdinIsTTY: options.stdinIsTTY,
1130
1484
  confirm: options.confirm,
@@ -1140,9 +1494,21 @@ export async function run(argv, options = {}) {
1140
1494
  stdinIsTTY: options.stdinIsTTY,
1141
1495
  confirm: options.confirm,
1142
1496
  });
1143
- default:
1497
+ default: {
1498
+ const experienceCommand = EXPERIENCE_COMMANDS.find((candidate) => candidate.name === command);
1499
+ if (experienceCommand) {
1500
+ return await experienceCommand.run(ctx, {
1501
+ flags: parsed.flags,
1502
+ positionals: parsed.positionals,
1503
+ passthrough: parsed.passthrough,
1504
+ dir,
1505
+ cwd,
1506
+ env,
1507
+ });
1508
+ }
1144
1509
  // Unreachable — COMMANDS gate above — but keeps the switch total.
1145
1510
  throw usageError(`Unknown command "${command}".`, `Run one of: ${COMMANDS.join(', ')}.`);
1511
+ }
1146
1512
  }
1147
1513
  }
1148
1514
  catch (error) {
@@ -1221,11 +1587,12 @@ function emitVersion(ctx) {
1221
1587
  }
1222
1588
  const DOCS_FOOTER = `\nDocs: ${SDK_DOCS_URL} (commands: ${docsPage('cli/commands')})`;
1223
1589
  function emitHelp(ctx) {
1590
+ // Short on purpose: the command list and where each command's usage is.
1591
+ // The full usage is already the result, and a hint line is not a manual.
1224
1592
  ctx.emitClaudeCodeHint({
1225
1593
  tool: 'ekanos',
1226
1594
  commands: COMMANDS,
1227
- usage: USAGE,
1228
- exitCodes: EXIT_CODES,
1595
+ help: 'ekanos <command> --help',
1229
1596
  });
1230
1597
  return ctx.succeed({ usage: USAGE, commands: COMMANDS, docs: SDK_DOCS_URL }, `${USAGE}${DOCS_FOOTER}`);
1231
1598
  }