@ekanos/cli 0.15.0 → 0.16.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 (113) hide show
  1. package/README.md +41 -4
  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/agents.js +3 -2
  6. package/dist/commands/agents.js.map +1 -1
  7. package/dist/commands/apps.d.ts +11 -3
  8. package/dist/commands/apps.js +86 -28
  9. package/dist/commands/apps.js.map +1 -1
  10. package/dist/commands/catalog.d.ts +4 -2
  11. package/dist/commands/catalog.js +166 -35
  12. package/dist/commands/catalog.js.map +1 -1
  13. package/dist/commands/login.d.ts +2 -0
  14. package/dist/commands/login.js +17 -0
  15. package/dist/commands/login.js.map +1 -1
  16. package/dist/commands/runs.d.ts +7 -3
  17. package/dist/commands/runs.js +21 -5
  18. package/dist/commands/runs.js.map +1 -1
  19. package/dist/commands/scope-notice.d.ts +7 -0
  20. package/dist/commands/scope-notice.js +4 -11
  21. package/dist/commands/scope-notice.js.map +1 -1
  22. package/dist/commands/sources-roles.d.ts +5 -0
  23. package/dist/commands/sources-roles.js +235 -0
  24. package/dist/commands/sources-roles.js.map +1 -0
  25. package/dist/commands/sources.d.ts +5 -0
  26. package/dist/commands/sources.js +6 -1
  27. package/dist/commands/sources.js.map +1 -1
  28. package/dist/commands/team-agent-readiness.d.ts +18 -2
  29. package/dist/commands/team-agent-readiness.js +39 -39
  30. package/dist/commands/team-agent-readiness.js.map +1 -1
  31. package/dist/commands/team-agents-agents.d.ts +5 -2
  32. package/dist/commands/team-agents-agents.js +152 -15
  33. package/dist/commands/team-agents-agents.js.map +1 -1
  34. package/dist/commands/team-agents-define.d.ts +31 -0
  35. package/dist/commands/team-agents-define.js +176 -0
  36. package/dist/commands/team-agents-define.js.map +1 -0
  37. package/dist/commands/team-agents-mcp.js +112 -10
  38. package/dist/commands/team-agents-mcp.js.map +1 -1
  39. package/dist/commands/team-agents-triggers.d.ts +1 -1
  40. package/dist/commands/team-agents-triggers.js +137 -5
  41. package/dist/commands/team-agents-triggers.js.map +1 -1
  42. package/dist/commands/team-agents.d.ts +4 -1
  43. package/dist/commands/team-agents.js +17 -4
  44. package/dist/commands/team-agents.js.map +1 -1
  45. package/dist/commands/team-llm-key.d.ts +14 -0
  46. package/dist/commands/team-llm-key.js +122 -0
  47. package/dist/commands/team-llm-key.js.map +1 -0
  48. package/dist/commands/teams-connect.js +123 -31
  49. package/dist/commands/teams-connect.js.map +1 -1
  50. package/dist/commands/teams-lifecycle.d.ts +90 -0
  51. package/dist/commands/teams-lifecycle.js +457 -0
  52. package/dist/commands/teams-lifecycle.js.map +1 -0
  53. package/dist/commands/teams-onboard.d.ts +14 -0
  54. package/dist/commands/teams-onboard.js +62 -13
  55. package/dist/commands/teams-onboard.js.map +1 -1
  56. package/dist/commands/teams-overview.js +6 -2
  57. package/dist/commands/teams-overview.js.map +1 -1
  58. package/dist/commands/teams.d.ts +25 -5
  59. package/dist/commands/teams.js +101 -39
  60. package/dist/commands/teams.js.map +1 -1
  61. package/dist/commands/workflows.d.ts +37 -0
  62. package/dist/commands/workflows.js +589 -0
  63. package/dist/commands/workflows.js.map +1 -0
  64. package/dist/connect-fields.d.ts +22 -0
  65. package/dist/connect-fields.js +115 -0
  66. package/dist/connect-fields.js.map +1 -0
  67. package/dist/context.d.ts +12 -0
  68. package/dist/context.js +32 -9
  69. package/dist/context.js.map +1 -1
  70. package/dist/index.d.ts +13 -0
  71. package/dist/index.js +357 -122
  72. package/dist/index.js.map +1 -1
  73. package/dist/invocation.d.ts +31 -0
  74. package/dist/invocation.js +58 -0
  75. package/dist/invocation.js.map +1 -0
  76. package/dist/runs-api.js +8 -3
  77. package/dist/runs-api.js.map +1 -1
  78. package/dist/safe-fs.d.ts +18 -0
  79. package/dist/safe-fs.js +57 -0
  80. package/dist/safe-fs.js.map +1 -1
  81. package/dist/secret-form.d.ts +48 -0
  82. package/dist/secret-form.js +212 -0
  83. package/dist/secret-form.js.map +1 -0
  84. package/dist/secret-input.d.ts +96 -0
  85. package/dist/secret-input.js +299 -0
  86. package/dist/secret-input.js.map +1 -0
  87. package/dist/team-connect-api.d.ts +78 -3
  88. package/dist/team-connect-api.js +17 -2
  89. package/dist/team-connect-api.js.map +1 -1
  90. package/dist/team-lifecycle-api.d.ts +435 -0
  91. package/dist/team-lifecycle-api.js +301 -0
  92. package/dist/team-lifecycle-api.js.map +1 -0
  93. package/dist/teams-api.d.ts +13 -1
  94. package/dist/teams-api.js +88 -27
  95. package/dist/teams-api.js.map +1 -1
  96. package/dist/unknown-command.d.ts +18 -0
  97. package/dist/unknown-command.js +50 -0
  98. package/dist/unknown-command.js.map +1 -0
  99. package/dist/workflows-api.d.ts +69 -0
  100. package/dist/workflows-api.js +182 -0
  101. package/dist/workflows-api.js.map +1 -0
  102. package/package.json +3 -3
  103. package/templates/AGENTS.md.tmpl +6 -4
  104. package/templates/claude-skill.md.tmpl +4 -1
  105. package/templates/package.json.tmpl +4 -4
  106. package/templates/skills/build-agent/SKILL.md +43 -10
  107. package/templates/skills/build-agent/references/agent-json.md +4 -1
  108. package/templates/skills/build-agent/references/app-setup.md +14 -7
  109. package/templates/skills/build-agent/references/rollout.md +26 -2
  110. package/templates/skills/build-agent/references/set-up-an-integration.md +46 -29
  111. package/templates/skills/build-integration/references/widget-design.md +1 -1
  112. package/templates/skills/ekanos-platform/SKILL.md +66 -14
  113. package/templates/skills/polish-widgets/references/patterns.md +3 -3
package/dist/index.js CHANGED
@@ -33,11 +33,13 @@ import { runUpgrade } from './commands/upgrade.js';
33
33
  import { runUse } from './commands/use.js';
34
34
  import { runValidate } from './commands/validate.js';
35
35
  import { runWhoami } from './commands/whoami.js';
36
+ import { runWorkflows } from './commands/workflows.js';
36
37
  import { CliContext } from './context.js';
37
38
  import { SDK_DOCS_URL, docsPage } from './docs-links.js';
38
39
  import { toCliError, usageError } from './errors.js';
39
- import { EXIT_CODES } from './exit-codes.js';
40
+ import { detectInvocation, setInvocation } from './invocation.js';
40
41
  import { parseArgv } from './parse-argv.js';
42
+ import { unknownCommand } from './unknown-command.js';
41
43
  export { CliContext } from './context.js';
42
44
  export { EXIT_CODES, ERROR_CODES } from './exit-codes.js';
43
45
  const GLOBAL_SPECS = {
@@ -61,6 +63,34 @@ const DIR = {
61
63
  type: 'string',
62
64
  description: 'Project directory, where ekanos.json and relative paths are read (default: the current one)',
63
65
  };
66
+ /**
67
+ * Where a secret comes from without a terminal (`secret-input.ts`), for every
68
+ * command that takes one. Never a value: a secret in argv lands in shell
69
+ * history and the process list.
70
+ */
71
+ const SECRET_INPUT = {
72
+ 'secret-env': {
73
+ type: 'string',
74
+ description: 'The secret, from this environment variable (its NAME, never the value)',
75
+ },
76
+ 'secret-file': {
77
+ type: 'string',
78
+ description: 'The secret, from this file: yours, mode 600, not a symlink',
79
+ },
80
+ 'secret-stdin': {
81
+ type: 'boolean',
82
+ description: 'The secret, piped in on stdin',
83
+ },
84
+ 'set-secret': {
85
+ type: 'string',
86
+ repeatable: true,
87
+ description: 'One field of a form: <field>=env:<NAME> | file:<path> | stdin',
88
+ },
89
+ browser: {
90
+ type: 'boolean',
91
+ description: 'Type the secret into a one-time form in your browser on this computer (never shown to the run that asked)',
92
+ },
93
+ };
64
94
  const COMMAND_SPECS = {
65
95
  init: {
66
96
  slug: {
@@ -165,179 +195,227 @@ const COMMAND_SPECS = {
165
195
  source: SOURCE,
166
196
  yes: {
167
197
  type: 'boolean',
168
- description: 'Confirm products disable, scopes remove',
198
+ description: 'Confirm products disable, scopes remove, roles delete',
199
+ },
200
+ // `sources roles create|update`.
201
+ 'display-name': {
202
+ type: 'string',
203
+ description: "roles create|update: the role's display name",
204
+ },
205
+ description: {
206
+ type: 'string',
207
+ description: "roles create|update: the role's description",
208
+ },
209
+ level: {
210
+ type: 'string',
211
+ description: 'roles create|update: its level, 1 to 1000; lower ranks higher (default 100)',
212
+ },
213
+ permission: {
214
+ type: 'string',
215
+ repeatable: true,
216
+ description: 'roles create|update: a permission it holds (update: replaces them all)',
169
217
  },
218
+ grant: {
219
+ type: 'string',
220
+ repeatable: true,
221
+ description: 'roles update: add this permission',
222
+ },
223
+ revoke: {
224
+ type: 'string',
225
+ repeatable: true,
226
+ description: 'roles update: take this permission away',
227
+ },
228
+ // `sources people`.
229
+ search: {
230
+ type: 'string',
231
+ description: 'people: only those whose name or email matches',
232
+ },
233
+ page: { type: 'string', description: 'people: which page, from 1' },
170
234
  },
171
235
  catalog: {
172
236
  host: HOST,
173
237
  source: SOURCE,
174
238
  dir: DIR,
175
239
  },
176
- apps: {
177
- host: HOST,
178
- source: SOURCE,
179
- dir: DIR,
180
- 'no-wait': {
240
+ apps: Object.assign({ host: HOST, source: SOURCE, dir: DIR, 'no-wait': {
181
241
  type: 'boolean',
182
242
  description: 'connect: print the links and return without waiting',
183
- },
184
- yes: {
243
+ }, yes: {
185
244
  type: 'boolean',
186
245
  description: 'disconnect: confirm without the question (needed in a script or agent run)',
187
- },
188
- },
189
- teams: {
190
- host: HOST,
191
- source: SOURCE,
192
- dir: DIR,
193
- connection: {
246
+ }, set: {
247
+ type: 'string',
248
+ repeatable: true,
249
+ description: 'set, connect: <name>=<value> for a field that is not a secret (set: clientId)',
250
+ } }, SECRET_INPUT),
251
+ teams: Object.assign(Object.assign({ host: HOST, source: SOURCE, dir: DIR, connection: {
194
252
  type: 'string',
195
253
  description: 'connect, app set: which of the integration\'s connections (e.g. "agent" for Linear\'s agent)',
196
- },
254
+ },
197
255
  // `teams app set`: which fields to enter, non-secret values, removals.
198
256
  field: {
199
257
  type: 'string',
200
258
  repeatable: true,
201
259
  description: 'app set: enter just this field at your terminal',
202
- },
203
- set: {
260
+ }, set: {
204
261
  type: 'string',
205
262
  repeatable: true,
206
- description: 'app set: <name>=<value> for a non-secret field',
207
- },
208
- clear: {
263
+ description: 'app set, connect: <name>=<value> for a field that is not a secret (a list as a,b)',
264
+ }, self: {
265
+ type: 'boolean',
266
+ description: 'members add: you join the team now, with no invitation email',
267
+ } }, SECRET_INPUT), { clear: {
209
268
  type: 'string',
210
269
  repeatable: true,
211
270
  description: "app set: remove this field's saved value",
212
- },
213
- slug: {
271
+ }, slug: {
214
272
  type: 'string',
215
273
  description: "create: the team's slug (default: from its name)",
216
- },
217
- owner: {
274
+ }, owner: {
218
275
  type: 'string',
219
276
  description: 'create: invite this email as the owner instead of you',
220
- },
221
- role: {
277
+ }, role: {
222
278
  type: 'string',
223
- description: 'members invite: the role to invite as (default member)',
224
- },
225
- yes: {
279
+ description: 'members add, members invite: the role to give (default member)',
280
+ }, yes: {
226
281
  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
- },
282
+ 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)',
283
+ },
229
284
  // `teams agents|triggers|links|mcp` (team-agents.ts).
230
285
  as: {
231
286
  type: 'string',
232
- description: 'connect: the member who signs in; agents enable: the member the agent runs as',
233
- },
287
+ description: 'connect: the member who signs in; agents enable: the member the agent runs as (chosen once, when it is turned on)',
288
+ },
234
289
  // `teams agents enable|pin … --draft`: test the unpublished draft there.
235
290
  draft: {
236
291
  type: 'boolean',
237
292
  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).
293
+ },
294
+ // `teams agents enable|policy|create|apply`: also confirm, as yourself,
295
+ // what the agent may use (whoever sets it up confirms it, plan 247.2).
241
296
  'confirm-access': {
242
297
  type: 'boolean',
243
- description: 'agents enable|policy: also confirm, as yourself, what it may use',
244
- },
245
- reads: {
298
+ description: 'agents enable|policy|create|apply: also confirm, as yourself, what it may use',
299
+ },
300
+ // `teams agents create|apply`: the agent.json, read as `agents apply`
301
+ // reads it (team-agents-define.ts).
302
+ from: {
303
+ type: 'string',
304
+ description: 'agents create: the agent directory or agent.json',
305
+ }, personal: {
306
+ type: 'boolean',
307
+ description: 'agents create|apply: your own agent, which acts for you and only you change',
308
+ }, 'base-version': {
309
+ type: 'string',
310
+ description: 'agents apply: the agent version your edit starts from ("teams agents show")',
311
+ }, overwrite: {
312
+ type: 'boolean',
313
+ description: 'agents apply: replace a newer version on the host anyway',
314
+ }, skills: {
315
+ type: 'boolean',
316
+ description: 'agents create|apply: --no-skills skips pushing <dir>/skills/<slug>/ as team skills',
317
+ }, reads: {
246
318
  type: 'string',
247
319
  description: 'agents policy: auto|approve for every read tool',
248
- },
249
- writes: {
320
+ }, writes: {
250
321
  type: 'string',
251
322
  description: 'agents policy: auto|approve for every write tool',
252
- },
253
- effect: {
323
+ }, effect: {
254
324
  type: 'string',
255
325
  description: 'Not read: "teams mcp effect" takes the effect as an argument',
256
- },
257
- kind: { type: 'string', description: 'links add: talk|delegate' },
258
- mode: {
326
+ }, agent: {
327
+ type: 'string',
328
+ description: 'mcp refresh: list again the tools of every server this agent has',
329
+ }, kind: { type: 'string', description: 'links add: talk|delegate' }, mode: {
259
330
  type: 'string',
260
331
  description: 'links add: await|handoff, for a delegate link',
261
- },
262
- url: { type: 'string', description: "mcp add: the server's URL" },
263
- auth: {
332
+ }, url: { type: 'string', description: "mcp add: the server's URL" }, auth: {
264
333
  type: 'string',
265
334
  description: 'mcp add: none|token|oauth (default oauth)',
266
- },
267
- name: { type: 'string', description: "mcp add: the server's display name" },
268
- cron: {
335
+ }, name: {
269
336
  type: 'string',
270
- description: 'triggers add schedule: a cron expression',
271
- },
272
- timezone: {
337
+ description: "mcp add: the server's display name; groups update: the group's new name",
338
+ },
339
+ // `teams groups create|update`.
340
+ description: {
273
341
  type: 'string',
274
- description: 'triggers add schedule: an IANA time zone (default UTC)',
275
- },
276
- prompt: {
342
+ description: "groups create|update: the group's description",
343
+ }, products: {
277
344
  type: 'string',
278
- description: 'triggers add: what the agent is told when it fires',
279
- },
345
+ description: 'groups create|update: the products it narrows to, comma-separated (none: no narrowing)',
346
+ },
347
+ // `teams disconnect|health <team> <product> --mine`: the caller's own.
348
+ mine: {
349
+ type: 'boolean',
350
+ description: "disconnect, health <product>: your own personal connection, never another member's",
351
+ },
352
+ // `teams health <team> <product> --run`.
353
+ run: {
354
+ type: 'boolean',
355
+ description: "health: run the integration's health check now",
356
+ }, cron: {
357
+ type: 'string',
358
+ description: 'triggers add|edit schedule: a cron expression',
359
+ }, timezone: {
360
+ type: 'string',
361
+ description: 'triggers add|edit schedule: an IANA time zone (default UTC)',
362
+ }, prompt: {
363
+ type: 'string',
364
+ description: 'triggers add|edit: what the agent is told when it fires',
365
+ },
280
366
  // Repeatable `field=value`, or one JSON object — as at the template level.
281
367
  filter: {
282
368
  type: 'string',
283
369
  repeatable: true,
284
- description: 'triggers add event: field=a,b (repeatable) or one JSON object',
285
- },
370
+ description: 'triggers add|edit event: field=a,b (repeatable) or one JSON object',
371
+ },
286
372
  // `teams <area> … --team <slug>`: the team, in place of the positional.
287
373
  team: {
288
374
  type: 'string',
289
375
  description: 'The team, in place of the <team> argument (teams <area> … --team <slug>)',
290
- },
376
+ },
291
377
  // `teams triggers add … slack|linear`: where it answers. `--no-mention`
292
378
  // is the negation of `mention`.
293
379
  channel: {
294
380
  type: 'string',
295
381
  repeatable: true,
296
- description: 'triggers add slack: a channel it answers in (#name or id)',
297
- },
298
- dms: {
382
+ description: 'triggers add|edit slack: a channel it answers in (#name or id); edit replaces the list',
383
+ }, dms: {
299
384
  type: 'boolean',
300
- description: 'triggers add slack: also answer direct messages',
301
- },
302
- label: {
385
+ description: 'triggers add|edit slack: also answer direct messages (edit: --no-dms stops)',
386
+ }, label: {
303
387
  type: 'string',
304
388
  repeatable: true,
305
- description: 'triggers add linear: a label that hands an issue to it',
306
- },
307
- mention: {
389
+ description: 'triggers add|edit linear: a label that hands an issue to it; edit replaces the list',
390
+ }, mention: {
308
391
  type: 'boolean',
309
- description: 'triggers add linear: --no-mention ignores @mentions',
310
- },
392
+ description: 'triggers add|edit linear: --no-mention ignores @mentions (edit: --mention answers them)',
393
+ },
311
394
  // `teams connect`: `--no-wait`, `--no-open` (the negation of `open`),
312
395
  // `--link-only`.
313
396
  'no-wait': {
314
397
  type: 'boolean',
315
398
  description: 'connect: print the sign-in link and return at once',
316
- },
317
- open: {
399
+ }, open: {
318
400
  type: 'boolean',
319
401
  description: 'connect: --no-open prints the link without opening a browser',
320
- },
321
- 'link-only': {
402
+ }, 'link-only': {
322
403
  type: 'boolean',
323
404
  description: "connect: print the team's page instead of connecting",
324
- },
405
+ },
325
406
  // `teams events <team> [--product] [--since] [--limit]`.
326
407
  product: {
327
408
  type: 'string',
328
409
  description: 'events: only this product (or name it after the team)',
329
- },
330
- since: {
410
+ }, since: {
331
411
  type: 'string',
332
412
  description: 'events: since 30m, 24h, 7d or a date',
333
- },
334
- limit: { type: 'string', description: 'events: how many, 1 to 100' },
413
+ }, limit: { type: 'string', description: 'events: how many, 1 to 100' },
335
414
  // `teams connect --reconnect`.
336
415
  reconnect: {
337
416
  type: 'boolean',
338
417
  description: 'connect: connect again though it reads as connected (revoked or expired)',
339
- },
340
- },
418
+ } }),
341
419
  reviewers: {
342
420
  host: HOST,
343
421
  source: SOURCE,
@@ -542,6 +620,51 @@ const COMMAND_SPECS = {
542
620
  description: 'edit: a file with the reply to send instead',
543
621
  },
544
622
  },
623
+ workflows: {
624
+ host: HOST,
625
+ source: SOURCE,
626
+ dir: DIR,
627
+ team: { type: 'string', description: 'The team whose workflows these are' },
628
+ all: { type: 'boolean', description: 'list: include archived workflows' },
629
+ out: {
630
+ type: 'string',
631
+ description: 'pull: where to write workflow.json (default workflows/<name>)',
632
+ },
633
+ force: {
634
+ type: 'boolean',
635
+ description: 'pull: overwrite a local workflow.json that differs',
636
+ },
637
+ update: {
638
+ type: 'string',
639
+ description: 'apply: the workflow (id or name) to replace; without it apply creates a draft',
640
+ },
641
+ 'confirm-access': {
642
+ type: 'boolean',
643
+ description: 'enable: approve, as yourself, what its Custom Code reaches (hosts, writes)',
644
+ },
645
+ 'dry-run': {
646
+ type: 'boolean',
647
+ description: 'run: rehearse it (writes recorded, not sent; gates pass); a draft may run so',
648
+ },
649
+ input: {
650
+ type: 'string',
651
+ description: 'run --dry-run: a JSON file read as the sample trigger.*',
652
+ },
653
+ follow: {
654
+ type: 'boolean',
655
+ description: 'run: wait, printing each step, until it ends or waits on a gate',
656
+ },
657
+ workflow: {
658
+ type: 'string',
659
+ description: "runs list: only this workflow's runs (id or name)",
660
+ },
661
+ status: {
662
+ type: 'string',
663
+ description: 'runs list: only runs in this status (running, waiting_approval, completed, rejected, failed, canceled)',
664
+ },
665
+ reason: { type: 'string', description: 'approvals reject: why' },
666
+ yes: { type: 'boolean', description: 'runs cancel: confirm' },
667
+ },
545
668
  use: {
546
669
  host: HOST,
547
670
  dir: DIR,
@@ -597,6 +720,8 @@ const POSITIONAL_COMMANDS = {
597
720
  knowledge: ['subcommand', 'path-or-query'],
598
721
  runs: ['subcommand', 'agent-key-or-run-id'],
599
722
  approvals: ['subcommand', 'approval-id'],
723
+ // `workflows runs show <run-id>` is the longest form.
724
+ workflows: ['subcommand', 'workflow-or-verb', 'id'],
600
725
  // `triggers <key> add event <trigger-key>` is the longest form: five tokens.
601
726
  agents: ['subcommand', 'key-or-path', 'action', 'arg', 'value'],
602
727
  };
@@ -630,13 +755,27 @@ const USAGE = [
630
755
  ' and pointed at /admin) | scopes (each product scope: its',
631
756
  ' products and how many teams hold it; a team sees in the app',
632
757
  " 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',
758
+ ' scopes remove <scope> <product> --yes | roles [list] (the roles',
759
+ ' every team of the Source hands out, each with its level and',
760
+ ' permissions) | roles create <name> [--display-name <text>]',
761
+ ' [--description <text>] [--level <n>] [--permission <p>]… |',
762
+ ' roles update <role> [--display-name] [--description] [--level]',
763
+ ' [--permission <p>]… (replaces) [--grant <p>]… [--revoke <p>]… |',
764
+ ' roles delete <role> [--yes] (owner, admin and member are system',
765
+ " roles: never changed or deleted; a held role isn't deleted) |",
766
+ ' people [--search <text>] [--page <n>] (everyone with access, one',
767
+ ' row per person, with their teams and role on each)',
768
+ ' catalog What a source can already use, and how to set it up:',
769
+ ' Experiences, then Integrations; one product shows its tools',
770
+ ' with their inputs and whose connection agent runs use',
635
771
  ' ("ekanos catalog [<product-slug>] --source <slug>", --json)',
636
772
  " apps The Source's app for an integration teams sign in through:",
637
773
  ' show <product> | set <product> (Source admin; takes the secret',
638
- ' at your terminal, Enter keeps a saved value) | connect',
639
- ' <product> [--no-wait] (one',
774
+ ' at your terminal, Enter keeps a saved value; without one,',
775
+ ' --set clientId=<id> and --secret-env <NAME> | --secret-file',
776
+ ' <path> | --secret-stdin | --browser) | connect',
777
+ ' <product> [--no-wait] [--set <field>=<value>]… [--set-secret',
778
+ ' <field>=env:<NAME>|file:<path>|stdin]… [--browser] (one',
640
779
  " connection for the whole Source, used by the Source's provided",
641
780
  ' agents in every team) | disconnect <product> [--yes] (a',
642
781
  ' script or agent run needs --yes) | list',
@@ -644,15 +783,32 @@ const USAGE = [
644
783
  ' create <name> [--slug <slug>] [--owner <email>] (you own it',
645
784
  ' unless --owner, who is invited as owner) |',
646
785
  ' 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) |',
786
+ ' members list <team> | members add <team> --self | <email>',
787
+ ' [--role <role>] (a member now, no email: you, or someone with',
788
+ ' an Ekanos account on the Source) | members invite <team> <email>',
789
+ ' [--role <role>] (default member; sends the invitation email) |',
649
790
  ' members role <team> <email> <role> | members remove <team>',
650
- " <email> --yes | roles list <team> (each role's permissions) |",
791
+ ' <email> --yes | members resend <team> <email> (a live',
792
+ ' invitation is resent, an expired one renewed) |',
793
+ " roles list <team> (each role's permissions) |",
794
+ ' leave <team> [--yes] (you leave it; never the primary owner) |',
795
+ ' groups list <team> | groups create <team> <name> [--description',
796
+ ' <text>] [--products <slug,slug>] | groups update <team> <group>',
797
+ ' [--name <name>] [--description <text>] [--products <slug,…>|none]',
798
+ ' | groups delete <team> <group> [--yes] | groups members',
799
+ ' add|remove <team> <group> <email> (leave, groups delete and',
800
+ ' remove ask first; a script or agent run needs --yes) |',
801
+ ' health <team> [<product>] [--run] (each connected integration',
802
+ ' and its health; --run checks one now; <product> --mine: your own',
803
+ ' personal connection, any seat, as a member) | workspace list <team> |',
804
+ ' workspace default <team> <workspace> (where the team lands) |',
651
805
  ' 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;',
806
+ ' <member-email>] [--set <field>=<value>]… [--no-wait] [--no-open]',
807
+ ' [--link-only] [--reconnect] (connects',
808
+ " it: asks the form's fields at your terminal or takes them from",
809
+ ' --set (a list as a,b; secrets and JSON only at a prompt; an optional',
810
+ ' one can be skipped), then opens the sign-in for you — your own',
811
+ ' connection, for a personal product — or the member --as names, and waits;',
656
812
  " --connection agent installs the team's Linear agent (its own",
657
813
  ' Linear app, as a member), which @mention and assign triggers',
658
814
  " need; --link-only prints the team's page; --reconnect connects",
@@ -662,14 +818,22 @@ const USAGE = [
662
818
  ' Slack, or the Ekanos Agents app for Linear, which slack and',
663
819
  ' linear triggers answer through; same link-and-wait) |',
664
820
  " disconnect <team> <product> --yes (takes the team's connection",
665
- " and its tokens down; its own app's saved fields stay) |",
821
+ " and its tokens down; its own app's saved fields stay; --mine:",
822
+ " your own personal connection, never another member's) |",
666
823
  ' list | show <team> (one screen: members, each integration and',
667
824
  ' whose connection it is, each agent live or the next step) |',
668
825
  ' show <team> <product> (a checklist: fields, callback',
669
826
  ' URLs, webhooks, connect link) | app set <team> <product>',
670
827
  ' [--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) |',
828
+ ' [--clear <name>]… [--set-secret <name>=env:<NAME>|file:<path>|stdin]…',
829
+ ' [--browser] (any one field at a time; a field you skip keeps',
830
+ ' its saved value; a secret at your terminal, from the',
831
+ ' environment, a private file, stdin or a browser form, never',
832
+ ' as an argument) |',
833
+ ' llm-key show <team> | llm-key set <team> [--secret-env <NAME> |',
834
+ ' --secret-file <path> | --secret-stdin | --browser] | llm-key',
835
+ " remove <team> --yes (the team's own Anthropic key, as its",
836
+ ' settings page sets it; show gives only its last four characters) |',
673
837
  ' webhook <team> <product> [<webhook-id>] [--yes] (URL saved to a 0600 file under ~/.ekanos/secrets, never printed) |',
674
838
  ' events <team> [<product>] [--product <slug>] [--since 24h|7d|<date>]',
675
839
  ' [--limit <n>] (the integration events recorded for the team, each',
@@ -681,11 +845,21 @@ const USAGE = [
681
845
  ' agents list <team> | agents show <team> <key> (is it live, and if',
682
846
  ' not, why: each connection and whose it is, each trigger and how its',
683
847
  ' events arrive, last event and run, one Next command)',
848
+ ' | agents create <team> --from <dir|agent.json> [--personal]',
849
+ " [--confirm-access] [--no-skills] (the team's own agent, or with",
850
+ ' --personal your own, from the agent.json "agents apply" reads;',
851
+ ' you become its creator, so you must be an active member of the',
852
+ ' team; it starts as a draft) | agents apply <team> <dir|agent.json>',
853
+ ' [--personal] [--base-version <n>] [--overwrite] [--confirm-access]',
854
+ ' (creates it, or changes it from the same file; a personal agent',
855
+ " is its creator's alone to change)",
684
856
  ' | agents enable <team> <key> [--as <member-email>]',
685
857
  ' [--draft] [--confirm-access] (--confirm-access: you also confirm',
686
858
  ' what it may use) | agents confirm <team> <key> (you confirm what',
687
859
  ' it may use, as yourself: whoever sets an agent up confirms it, never',
688
860
  ' a team member) | agents disable <team> <key> --yes | agents',
861
+ ' archive <team> <key> [--yes] (off the list; runs and setup kept) |',
862
+ ' agents unarchive <team> <key> (back, paused) | agents',
689
863
  ' unpin <team> <key> | agents pin <team> <key> <version> | agents pin',
690
864
  ' <team> <key> --draft (this team only runs the unpublished draft, by',
691
865
  ' hand, every write waiting; the next publish puts it back to',
@@ -694,17 +868,29 @@ const USAGE = [
694
868
  ' [--reads auto|approve] [--writes auto|approve] [--confirm-access]',
695
869
  ' | agents skills <team> <key> [add|remove <skill>] (where the',
696
870
  ' template allows team skills; its own skills stay)',
697
- ' triggers list|add|remove <team> <key> … (add … event <id>',
871
+ ' | agents tags <team> <key> [add|remove <tag>] (labels that grant',
872
+ ' nothing; a tag the team lacks is made)',
873
+ ' triggers list|add|remove <team> <key> … (add … schedule --cron',
874
+ ' <expr> [--timezone <zone>] --prompt <text> | add … webhook --prompt',
875
+ ' <text> (its token saved to a 0600 file, never printed; a personal',
876
+ " agent's webhook acts as its creator) | add … event <id>",
698
877
  " [--filter field=a,b]… or --filter '<json>'; remove <trigger-id|key> --yes;",
699
878
  ' add … slack --channel',
700
879
  ' <#name|id>… [--dms] --yes | add … linear [--label <name>]…',
701
880
  ' [--no-mention], once installed with teams connect) | triggers',
702
881
  " 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',
882
+ ' new token, the old one revoked; saved to a 0600 file, never printed)',
883
+ ' | triggers enable|disable <team> <key> <trigger-id|key> (disable',
884
+ ' asks first, --yes in a script) | triggers edit <team> <key>',
885
+ ' <trigger-id|key> [--prompt …] [--cron … --timezone …] [--filter …]',
886
+ ' [--channel …] [--dms|--no-dms] [--label …] [--mention|--no-mention]',
887
+ ' (in place: a webhook keeps its URL and token) | links list|add|remove',
704
888
  ' <team> <from-key> [<to-key> --kind talk|delegate --mode await|handoff]',
705
889
  ' (remove <link-id|to-key> [--kind <kind>] --yes)',
706
- ' | mcp list|add|status|token|effect|remove <team> [<server-key> …]',
707
- ' (remove needs --yes)',
890
+ ' | mcp list|add|status|token|effect|refresh|remove <team> [<server-key> …]',
891
+ ' (remove needs --yes; refresh <team> --agent <key>: every server it',
892
+ ' has) | mcp attach|detach <team> <server-key> <agent-key> (detach',
893
+ ' asks first, --yes in a script; where the template allows)',
708
894
  ' Every area also takes the team as --team <slug>.',
709
895
  ' ("ekanos teams --help" spells out each area)',
710
896
  ' reviewers Who decides agent reply drafts, for the Source admin:',
@@ -782,6 +968,25 @@ const USAGE = [
782
968
  " approvals What a team's agent runs wait on: list --team <slug> |",
783
969
  ' approve|reject <id> --team <slug> [--reason "…"] |',
784
970
  ' edit <id> --team <slug> --body <file> (edit & send a reply draft)',
971
+ ' The Source admin sees all; any other seat holder only what they',
972
+ " may decide (managing or reviewing the team's agents, a named",
973
+ " reviewer, a personal agent's creator). The first decision wins.",
974
+ " workflows A team's workflows, for the Source admin (each takes --team <slug>;",
975
+ ' a workflow is named by its id or its name):',
976
+ ' list [--all] | show <workflow> (status, trigger, Custom Code review',
977
+ ' state, recent runs; --json: the definition) | pull <workflow>',
978
+ ' [--out <dir>] [--force] (writes workflow.json, the JSON the editor',
979
+ ' saves) | apply <file|dir> [--update <workflow>] (a new draft, or',
980
+ " that workflow's definition replaced; the editor's checks: shape,",
981
+ ' tool effects, Custom Code security review, approval gates before',
982
+ ' writes) | enable <workflow> [--confirm-access] (activation re-checks',
983
+ ' it; Custom Code that writes or reaches a host needs your approval,',
984
+ ' asked at a terminal) | disable <workflow> | run <workflow>',
985
+ ' [--dry-run [--input <file.json>]] [--follow] (a live run needs it',
986
+ ' on; --dry-run records writes instead of sending them) |',
987
+ ' runs list [--workflow <workflow>] [--status <status>] | runs show',
988
+ ' <run-id> | runs cancel <run-id> [--yes] | approvals list |',
989
+ ' approvals approve|reject <id> [--reason "…"]',
785
990
  ' use Set this project\'s publish target ("ekanos use <source-slug>")',
786
991
  ' publish Validate, pack and submit the integration to an Ekanos host',
787
992
  ' upgrade Move this toolchain to a coherent target version, with',
@@ -796,7 +1001,7 @@ const USAGE = [
796
1001
  ' bare "ekanos upgrade" MUTATES the project (installs the target versions,',
797
1002
  ' verifies with validate + the test script, rolls back on any failure).',
798
1003
  '',
799
- 'Host resolution (logout, whoami, status, sources, catalog, apps, teams, reviewers, routes, agents, skills, knowledge, runs, approvals, use, publish, upgrade):',
1004
+ 'Host resolution (logout, whoami, status, sources, catalog, apps, teams, reviewers, routes, agents, skills, knowledge, runs, approvals, workflows, use, publish, upgrade):',
800
1005
  ' --host, else EKANOS_HOST, else the "host" field in ekanos.json, else the',
801
1006
  ' sole stored login. "ekanos publish" saves host and source to ekanos.json.',
802
1007
  ' "ekanos login" never reads ekanos.json for a host (--host, else',
@@ -825,16 +1030,17 @@ const USAGE = [
825
1030
  * `process.exit`, so this stays fully testable.
826
1031
  */
827
1032
  export async function run(argv, options = {}) {
828
- var _a, _b, _c, _d;
1033
+ var _a, _b, _c, _d, _e, _f;
829
1034
  const jsonFlag = scanJsonFlag(argv);
1035
+ setInvocation((_a = options.invocation) !== null && _a !== void 0 ? _a : detectInvocation((_b = options.env) !== null && _b !== void 0 ? _b : process.env), COMMANDS);
830
1036
  const ctx = new CliContext({
831
1037
  jsonFlag,
832
1038
  stdout: options.stdout,
833
1039
  stderr: options.stderr,
834
1040
  env: options.env,
835
1041
  });
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;
1042
+ const cwd = (_c = options.cwd) !== null && _c !== void 0 ? _c : process.cwd();
1043
+ const env = (_d = options.env) !== null && _d !== void 0 ? _d : process.env;
838
1044
  try {
839
1045
  // `--version` is global and verb-independent, so it is answered before the
840
1046
  // command is even resolved: `ekanos --version` and `ekanos validate
@@ -853,26 +1059,21 @@ export async function run(argv, options = {}) {
853
1059
  tool: 'ekanos',
854
1060
  error: 'unknown-command',
855
1061
  commands: COMMANDS,
856
- usage: USAGE,
1062
+ help: 'ekanos --help',
857
1063
  });
858
1064
  throw usageError(`Unknown command or flag "${argv[0]}".`, `Run one of: ${COMMANDS.join(', ')}. See "ekanos --help".`);
859
1065
  }
860
1066
  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".`);
1067
+ const unknown = unknownCommand(command, rest, COMMANDS);
1068
+ ctx.emitClaudeCodeHint(Object.assign(Object.assign({ tool: 'ekanos', error: 'unknown-command', given: command }, (unknown.suggestion ? { suggestion: unknown.suggestion } : {})), { commands: COMMANDS, help: 'ekanos --help' }));
1069
+ throw unknown.error;
869
1070
  }
870
1071
  const specs = Object.assign(Object.assign({}, GLOBAL_SPECS), COMMAND_SPECS[command]);
871
1072
  const parsed = parseArgv(rest, specs);
872
1073
  if (parsed.flags.help === true) {
873
1074
  return emitCommandHelp(ctx, command);
874
1075
  }
875
- const positionalNames = (_c = POSITIONAL_COMMANDS[command]) !== null && _c !== void 0 ? _c : [];
1076
+ const positionalNames = (_e = POSITIONAL_COMMANDS[command]) !== null && _e !== void 0 ? _e : [];
876
1077
  if (positionalNames.length === 0 && parsed.positionals.length > 0) {
877
1078
  throw usageError(`"${command}" takes no positional arguments (got "${parsed.positionals[0]}").`, `Pass options as flags, e.g. "--dir <path>". See "ekanos ${command} --help".`);
878
1079
  }
@@ -882,7 +1083,7 @@ export async function run(argv, options = {}) {
882
1083
  `argument${positionalNames.length === 1 ? '' : 's'} (${usage}), ` +
883
1084
  `got ${parsed.positionals.length}.`, `e.g. "ekanos ${command} ${usage}". See "ekanos ${command} --help".`);
884
1085
  }
885
- const dir = (_d = parsed.flags.dir) !== null && _d !== void 0 ? _d : cwd;
1086
+ const dir = (_f = parsed.flags.dir) !== null && _f !== void 0 ? _f : cwd;
886
1087
  switch (command) {
887
1088
  case 'init':
888
1089
  return await runInit(ctx, {
@@ -952,8 +1153,11 @@ export async function run(argv, options = {}) {
952
1153
  positionals: parsed.positionals,
953
1154
  source: parsed.flags.source,
954
1155
  yes: parsed.flags.yes === true,
1156
+ flags: parsed.flags,
955
1157
  dir,
956
1158
  env,
1159
+ stdinIsTTY: options.stdinIsTTY,
1160
+ ask: options.ask,
957
1161
  });
958
1162
  case 'catalog':
959
1163
  return await runCatalog(ctx, {
@@ -977,6 +1181,10 @@ export async function run(argv, options = {}) {
977
1181
  confirm: options.confirm,
978
1182
  noWait: parsed.flags['no-wait'] === true,
979
1183
  yes: parsed.flags.yes === true,
1184
+ flags: parsed.flags,
1185
+ readStdin: options.readStdin,
1186
+ openUrl: options.openUrl,
1187
+ secretFormTimeoutMs: options.secretFormTimeoutMs,
980
1188
  });
981
1189
  case 'teams':
982
1190
  return await runTeams(ctx, {
@@ -1000,6 +1208,8 @@ export async function run(argv, options = {}) {
1000
1208
  askSecret: options.askSecret,
1001
1209
  openUrl: options.openUrl,
1002
1210
  pollDelayMs: options.pollDelayMs,
1211
+ readStdin: options.readStdin,
1212
+ secretFormTimeoutMs: options.secretFormTimeoutMs,
1003
1213
  });
1004
1214
  case 'reviewers':
1005
1215
  return await runReviewers(ctx, {
@@ -1112,6 +1322,30 @@ export async function run(argv, options = {}) {
1112
1322
  stdinIsTTY: options.stdinIsTTY,
1113
1323
  confirm: options.confirm,
1114
1324
  });
1325
+ case 'workflows':
1326
+ return await runWorkflows(ctx, {
1327
+ host: parsed.flags.host,
1328
+ source: parsed.flags.source,
1329
+ positionals: parsed.positionals,
1330
+ team: parsed.flags.team,
1331
+ all: parsed.flags.all === true,
1332
+ out: parsed.flags.out,
1333
+ force: parsed.flags.force === true,
1334
+ update: parsed.flags.update,
1335
+ confirmAccess: parsed.flags['confirm-access'] === true,
1336
+ dryRun: parsed.flags['dry-run'] === true,
1337
+ input: parsed.flags.input,
1338
+ follow: parsed.flags.follow === true,
1339
+ workflow: parsed.flags.workflow,
1340
+ status: parsed.flags.status,
1341
+ reason: parsed.flags.reason,
1342
+ yes: parsed.flags.yes === true,
1343
+ dir,
1344
+ env,
1345
+ sleep: options.sleep,
1346
+ stdinIsTTY: options.stdinIsTTY,
1347
+ confirm: options.confirm,
1348
+ });
1115
1349
  case 'use':
1116
1350
  return await runUse(ctx, {
1117
1351
  host: parsed.flags.host,
@@ -1221,11 +1455,12 @@ function emitVersion(ctx) {
1221
1455
  }
1222
1456
  const DOCS_FOOTER = `\nDocs: ${SDK_DOCS_URL} (commands: ${docsPage('cli/commands')})`;
1223
1457
  function emitHelp(ctx) {
1458
+ // Short on purpose: the command list and where each command's usage is.
1459
+ // The full usage is already the result, and a hint line is not a manual.
1224
1460
  ctx.emitClaudeCodeHint({
1225
1461
  tool: 'ekanos',
1226
1462
  commands: COMMANDS,
1227
- usage: USAGE,
1228
- exitCodes: EXIT_CODES,
1463
+ help: 'ekanos <command> --help',
1229
1464
  });
1230
1465
  return ctx.succeed({ usage: USAGE, commands: COMMANDS, docs: SDK_DOCS_URL }, `${USAGE}${DOCS_FOOTER}`);
1231
1466
  }