@ekanos/cli 0.8.0 → 0.10.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.
@@ -29,3 +29,6 @@ export declare function runApps(ctx: CliContext, args: AppsArgs): Promise<ExitCo
29
29
  * callback URL filled in) when it declares them, else the generic three.
30
30
  */
31
31
  export declare function walkthrough(status: ProviderAppStatus): string[];
32
+ export declare function promptLine(question: string): Promise<string>;
33
+ /** Read a line without echoing it: the secret never appears on screen or in scrollback. */
34
+ export declare function promptHidden(question: string): Promise<string>;
@@ -54,7 +54,7 @@ export async function runApps(ctx, args) {
54
54
  rows: [
55
55
  ['Source', result.source.slug],
56
56
  ['Teams can connect', result.configured ? 'yes' : 'not yet'],
57
- ['Callback URL', result.callbackUrl],
57
+ ['Callback URLs', callbackUrls(result)],
58
58
  ],
59
59
  }));
60
60
  }
@@ -72,11 +72,11 @@ export function walkthrough(status) {
72
72
  const steps = ((_b = status.setup) === null || _b === void 0 ? void 0 : _b.steps.length)
73
73
  ? [
74
74
  ...status.setup.steps,
75
- `Make sure the callback URL is exactly: ${status.callbackUrl}`,
75
+ `Check the app lists exactly: ${callbackUrls(status).join(', ')}`,
76
76
  ]
77
77
  : [
78
78
  `In ${name}, create an OAuth app (or open the one you use).`,
79
- `Register this callback URL exactly: ${status.callbackUrl}`,
79
+ `Register exactly these callback URLs: ${callbackUrls(status).join(', ')}`,
80
80
  ];
81
81
  steps.push("Enter its client ID and secret below. The secret is stored in Ekanos's vault and never shown again.");
82
82
  lines.push(...steps.map((step, index) => ` ${index + 1}. ${step}`));
@@ -106,7 +106,7 @@ function summary(ctx, status) {
106
106
  ? 'yes — teams can connect'
107
107
  : 'no — no team can connect yet',
108
108
  ],
109
- ['Callback URL', status.callbackUrl],
109
+ ['Callback URLs', callbackUrls(status)],
110
110
  ];
111
111
  return [
112
112
  ctx.ui.result({ title: `${status.product.name} app`, rows }),
@@ -151,7 +151,7 @@ function requireProduct(value, verb) {
151
151
  ? `"${value}" is not a product slug.`
152
152
  : `apps ${verb} needs a product.`, `Pass the product's slug, e.g. "ekanos apps ${verb} linear". "ekanos catalog" lists them.`);
153
153
  }
154
- async function promptLine(question) {
154
+ export async function promptLine(question) {
155
155
  const readline = await import('node:readline/promises');
156
156
  const rl = readline.createInterface({
157
157
  input: process.stdin,
@@ -165,7 +165,7 @@ async function promptLine(question) {
165
165
  }
166
166
  }
167
167
  /** Read a line without echoing it: the secret never appears on screen or in scrollback. */
168
- async function promptHidden(question) {
168
+ export async function promptHidden(question) {
169
169
  const readline = await import('node:readline');
170
170
  process.stderr.write(question);
171
171
  const rl = readline.createInterface({
@@ -186,4 +186,11 @@ async function promptHidden(question) {
186
186
  process.stderr.write('\n');
187
187
  }
188
188
  }
189
+ /** Every redirect URI to register; an older host sends only the one. */
190
+ function callbackUrls(status) {
191
+ var _a;
192
+ return ((_a = status.callbackUrls) === null || _a === void 0 ? void 0 : _a.length)
193
+ ? status.callbackUrls
194
+ : [status.callbackUrl];
195
+ }
189
196
  //# sourceMappingURL=apps.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"apps.js","sourceRoot":"","sources":["../../src/commands/apps.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,cAAc,EACd,eAAe,GAChB,MAAM,aAAa,CAAC;AAErB,OAAO,EAEL,oBAAoB,EACpB,cAAc,EACd,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAE3E,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,KAAK,CAAU,CAAC;AAiBzD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,GAAe,EACf,IAAc;;IAEd,MAAM,UAAU,GAAG,eAAe,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAEzD,IAAI,UAAU,KAAK,KAAK,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC;QAClD,MAAM,UAAU,CACd,oFAAoF,EACpF,8DAA8D,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,aAAa,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CACxH,CAAC;IACJ,CAAC;IAED,MAAM,GAAG,GAAG,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE;QAC3D,UAAU,EAAE,IAAI,CAAC,GAAG;KACrB,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CACxC,GAAG,EACH,cAAc,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,EACnC,IAAI,CACL,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC;IAE/B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACzC,eAAe,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAClD,CAAC;IAEF,IAAI,UAAU,KAAK,MAAM,EAAE,CAAC;QAC1B,OAAO,GAAG,CAAC,OAAO,iBAAG,IAAI,EAAE,GAAG,CAAC,IAAI,IAAK,MAAM,GAAI,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;IAC1E,CAAC;IAED,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACxC,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,MAAM,OAAC,IAAI,CAAC,OAAO,mCAAI,WAAW,CAAC,CACjD,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,gBAAgB,MAAM,CAAC,OAAO,CAAC,IAAI,mCAAmC,CAC5F,CAAC;QACF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,eAAe,CACnB,sBAAsB,EACtB,yBAAyB,OAAO,6BAA6B,CAC9D,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,MAAM,OAAC,IAAI,CAAC,GAAG,mCAAI,UAAU,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACxE,MAAM,YAAY,GAAG,CACnB,MAAM,OAAC,IAAI,CAAC,SAAS,mCAAI,YAAY,CAAC,CAAC,0BAA0B,CAAC,CACnE,CAAC,IAAI,EAAE,CAAC;IACT,IAAI,CAAC,QAAQ,IAAI,CAAC,YAAY,EAAE,CAAC;QAC/B,MAAM,eAAe,CACnB,sDAAsD,EACtD,wBAAwB,OAAO,UAAU,CAC1C,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CACzC,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE;QAC9B,MAAM;QACN,OAAO;QACP,QAAQ;QACR,YAAY;KACb,CAAC,CACH,CAAC;IACF,OAAO,GAAG,CAAC,OAAO,iBACd,IAAI,EAAE,GAAG,CAAC,IAAI,IAAK,MAAM,GAC3B,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC;QACZ,KAAK,EAAE,MAAM,CAAC,QAAQ;YACpB,CAAC,CAAC,gBAAgB,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM;YAC3C,CAAC,CAAC,cAAc,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM;QAC3C,IAAI,EAAE;YACJ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;YAC9B,CAAC,mBAAmB,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;YAC5D,CAAC,cAAc,EAAE,MAAM,CAAC,WAAW,CAAC;SACrC;KACF,CAAC,CACH,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAAyB;;IACnD,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;IACjC,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,YAAY,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACxD,IAAI,MAAA,MAAM,CAAC,KAAK,0CAAE,UAAU,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,WAAW,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;IACnD,CAAC;IACD,MAAM,KAAK,GAAG,CAAA,MAAA,MAAM,CAAC,KAAK,0CAAE,KAAK,CAAC,MAAM;QACtC,CAAC,CAAC;YACE,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK;YACrB,0CAA0C,MAAM,CAAC,WAAW,EAAE;SAC/D;QACH,CAAC,CAAC;YACE,MAAM,IAAI,kDAAkD;YAC5D,uCAAuC,MAAM,CAAC,WAAW,EAAE;SAC5D,CAAC;IACN,KAAK,CAAC,IAAI,CACR,qGAAqG,CACtG,CAAC;IACF,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC;IACrE,IAAI,MAAA,MAAM,CAAC,KAAK,0CAAE,QAAQ;QAAE,KAAK,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC5E,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,OAAO,CAAC,GAAe,EAAE,MAAyB;IACzD,MAAM,IAAI,GAAgB;QACxB,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QAC9B,GAAG,CAAC,MAAM,CAAC,KAAK;YACd,CAAC,CAAE;gBACC;oBACE,kBAAkB;oBAClB;wBACE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU;4BACzB,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC;4BACrC,CAAC,CAAC,EAAE,CAAC;wBACP,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CACvB,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,GAAG,CAAC,KAAK,IAAI,EAAE,CACzC;qBACF;iBACF;aACQ;YACb,CAAC,CAAC,EAAE,CAAC;QACP;YACE,QAAQ;YACR,MAAM,CAAC,UAAU;gBACf,CAAC,CAAC,yBAAyB;gBAC3B,CAAC,CAAC,8BAA8B;SACnC;QACD,CAAC,cAAc,EAAE,MAAM,CAAC,WAAW,CAAC;KACrC,CAAC;IACF,OAAO;QACL,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,EAAE,IAAI,EAAE,CAAC;QAC5D,GAAG,CAAC,MAAM,CAAC,UAAU;YACnB,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,GAAG,CAAC,EAAE,CAAC,IAAI,CACT,wBAAwB,MAAM,CAAC,OAAO,CAAC,IAAI,8CAA8C,CAC1F;aACF,CAAC;KACP,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED,MAAM,UAAU;IACd,YACmB,GAAoB,EAC7B,OAAsB;mBADb,GAAG;uBACZ,OAAO;IACd,CAAC;IAEJ,KAAK,CAAC,IAAI,CACR,IAAwD;QAExD,IAAI,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAClD,IAAI,MAAM,CAAC,MAAM,KAAK,cAAc,EAAE,CAAC;YACrC,IAAI,CAAC,OAAO,GAAG,MAAM,oBAAoB,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;YAClE,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QAChD,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAC,IAAI,CAAC;QAC/C,MAAM,iBAAiB,CACrB,mBAAmB,IAAI,CAAC,GAAG,CAAC,IAAI,qCAAqC,EACrE,4BAA4B,IAAI,CAAC,GAAG,CAAC,IAAI,cAAc,CACxD,CAAC;IACJ,CAAC;CACF;AAED,SAAS,SAAS,CAAC,GAAe,EAAE,IAAc;;IAChD,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,OAAC,IAAI,CAAC,UAAU,mCAAI,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;AAC5E,CAAC;AAED,SAAS,eAAe,CAAC,KAAyB;IAChD,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,KAAK,CAAC,CAAC;IAC9D,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,UAAU,CACd,KAAK,CAAC,CAAC,CAAC,4BAA4B,KAAK,IAAI,CAAC,CAAC,CAAC,0BAA0B,EAC1E,eAAe,gBAAgB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,IAAI,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC7F,CAAC;AACJ,CAAC;AAED,SAAS,cAAc,CAAC,KAAyB,EAAE,IAAY;IAC7D,MAAM,IAAI,GAAG,KAAK,aAAL,KAAK,uBAAL,KAAK,CAAE,IAAI,GAAG,WAAW,EAAE,CAAC;IACzC,IAAI,IAAI,IAAI,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3D,MAAM,UAAU,CACd,KAAK;QACH,CAAC,CAAC,IAAI,KAAK,0BAA0B;QACrC,CAAC,CAAC,QAAQ,IAAI,mBAAmB,EACnC,8CAA8C,IAAI,wCAAwC,CAC3F,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,QAAgB;IACxC,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;IACxD,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;IACH,IAAI,CAAC;QACH,OAAO,MAAM,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAED,2FAA2F;AAC3F,KAAK,UAAU,YAAY,CAAC,QAAgB;IAC1C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;IAC/C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC/B,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;QAClC,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,IAAI;KACf,CAAC,CAAC;IACH,8EAA8E;IAC7E,EAA4D,CAAC,cAAc;QAC1E,GAAG,EAAE,GAAE,CAAC,CAAC;IACX,IAAI,CAAC;QACH,OAAO,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,EAAE;YAC3C,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC","sourcesContent":["import type { ProviderAppStatus } from '@ekanos/integration-schema';\n\nimport {\n type AppsApiResult,\n setProviderApp,\n showProviderApp,\n} from '../apps-api';\nimport type { StoredSession } from '../auth/credential-store';\nimport {\n type AuthEnvironment,\n refreshStoredSession,\n requireSession,\n resolveAuthEnvironment,\n} from '../auth/session';\nimport type { CliContext } from '../context';\nimport { authRequiredError, usageError, validationError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { resolveSourceTarget } from '../source-target';\nimport type { ResultRow } from '../ui';\nimport { promptYesNo } from './publish';\n\nexport const APPS_SUBCOMMANDS = ['show', 'set'] as const;\ntype AppsSubcommand = (typeof APPS_SUBCOMMANDS)[number];\n\nexport interface AppsArgs {\n host?: string;\n source?: string;\n subcommand?: string;\n product?: string;\n dir: string;\n env: Record<string, string | undefined>;\n stdinIsTTY?: boolean;\n /** Test seams for the questions `set` asks. */\n ask?: (question: string) => Promise<string>;\n askSecret?: (question: string) => Promise<string>;\n confirm?: (question: string) => Promise<boolean>;\n}\n\n/**\n * `apps` — a Source's provider apps: the OAuth client an integration signs\n * every team in through (Linear's, say). `show` says whether the Source has\n * one and which callback URL to register; `set` takes the client id and\n * secret from the Source admin at the terminal — never from a flag, a file\n * or a non-interactive run, so an agent driving the CLI never handles the\n * secret.\n */\nexport async function runApps(\n ctx: CliContext,\n args: AppsArgs,\n): Promise<ExitCode> {\n const subcommand = parseSubcommand(args.subcommand);\n const product = requireProduct(args.product, subcommand);\n\n if (subcommand === 'set' && !canPrompt(ctx, args)) {\n throw usageError(\n 'apps set takes the client secret at a terminal, never in a script or an agent run.',\n `Run it yourself in your terminal: npx @ekanos/cli apps set ${product}${args.source ? ` --source ${args.source}` : ''}`,\n );\n }\n\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n const resolved = await resolveSourceTarget(\n env,\n requireSession(env.store, env.host),\n args,\n );\n const client = new AppsClient(env, resolved.session);\n const source = resolved.source;\n\n const status = await client.call((token) =>\n showProviderApp(env.host, token, source, product),\n );\n\n if (subcommand === 'show') {\n return ctx.succeed({ host: env.host, ...status }, summary(ctx, status));\n }\n\n ctx.log(walkthrough(status).join('\\n'));\n if (status.configured) {\n const replace = await (args.confirm ?? promptYesNo)(\n `${status.source.slug} already has ${status.product.name} credentials. Replace them? [y/N]`,\n );\n if (!replace) {\n throw validationError(\n 'Nothing was changed.',\n `Run \"ekanos apps show ${product}\" to see the current state.`,\n );\n }\n }\n\n const clientId = (await (args.ask ?? promptLine)('Client ID: ')).trim();\n const clientSecret = (\n await (args.askSecret ?? promptHidden)('Client secret (hidden): ')\n ).trim();\n if (!clientId || !clientSecret) {\n throw validationError(\n 'Both the client ID and the client secret are needed.',\n `Run \"ekanos apps set ${product}\" again.`,\n );\n }\n\n const result = await client.call((token) =>\n setProviderApp(env.host, token, {\n source,\n product,\n clientId,\n clientSecret,\n }),\n );\n return ctx.succeed(\n { host: env.host, ...result },\n ctx.ui.result({\n title: result.replaced\n ? `Replaced the ${result.product.name} app`\n : `Set up the ${result.product.name} app`,\n rows: [\n ['Source', result.source.slug],\n ['Teams can connect', result.configured ? 'yes' : 'not yet'],\n ['Callback URL', result.callbackUrl],\n ],\n }),\n );\n}\n\n/**\n * How to create the app: the integration's own steps (`oauth.setup`, with the\n * callback URL filled in) when it declares them, else the generic three.\n */\nexport function walkthrough(status: ProviderAppStatus): string[] {\n const name = status.product.name;\n const lines = [`${name} app for ${status.source.slug}`];\n if (status.setup?.consoleUrl) {\n lines.push(` Open: ${status.setup.consoleUrl}`);\n }\n const steps = status.setup?.steps.length\n ? [\n ...status.setup.steps,\n `Make sure the callback URL is exactly: ${status.callbackUrl}`,\n ]\n : [\n `In ${name}, create an OAuth app (or open the one you use).`,\n `Register this callback URL exactly: ${status.callbackUrl}`,\n ];\n steps.push(\n \"Enter its client ID and secret below. The secret is stored in Ekanos's vault and never shown again.\",\n );\n lines.push(...steps.map((step, index) => ` ${index + 1}. ${step}`));\n if (status.setup?.guideUrl) lines.push(` Guide: ${status.setup.guideUrl}`);\n return lines;\n}\n\nfunction summary(ctx: CliContext, status: ProviderAppStatus): string {\n const rows: ResultRow[] = [\n ['Source', status.source.slug],\n ...(status.setup\n ? ([\n [\n 'How to create it',\n [\n ...(status.setup.consoleUrl\n ? [`Open ${status.setup.consoleUrl}`]\n : []),\n ...status.setup.steps.map(\n (step, index) => `${index + 1}. ${step}`,\n ),\n ],\n ],\n ] as const)\n : []),\n [\n 'Set up',\n status.configured\n ? 'yes — teams can connect'\n : 'no — no team can connect yet',\n ],\n ['Callback URL', status.callbackUrl],\n ];\n return [\n ctx.ui.result({ title: `${status.product.name} app`, rows }),\n ...(status.configured\n ? []\n : [\n ctx.ui.hint(\n `Run \"ekanos apps set ${status.product.slug}\" in your terminal to enter its credentials.`,\n ),\n ]),\n ].join('\\n');\n}\n\nclass AppsClient {\n constructor(\n private readonly env: AuthEnvironment,\n private session: StoredSession,\n ) {}\n\n async call<T>(\n send: (accessToken: string) => Promise<AppsApiResult<T>>,\n ): Promise<T> {\n let result = await send(this.session.accessToken);\n if (result.status === 'unauthorized') {\n this.session = await refreshStoredSession(this.env, this.session);\n result = await send(this.session.accessToken);\n }\n if (result.status === 'ok') return result.data;\n throw authRequiredError(\n `The session for ${this.env.host} could not authorize the apps call.`,\n `Run \"ekanos login --host ${this.env.host}\" and retry.`,\n );\n }\n}\n\nfunction canPrompt(ctx: CliContext, args: AppsArgs): boolean {\n return !ctx.jsonMode && (args.stdinIsTTY ?? process.stdin.isTTY === true);\n}\n\nfunction parseSubcommand(value: string | undefined): AppsSubcommand {\n const match = APPS_SUBCOMMANDS.find((name) => name === value);\n if (match) return match;\n throw usageError(\n value ? `Unknown apps subcommand \"${value}\".` : 'apps needs a subcommand.',\n `Run one of: ${APPS_SUBCOMMANDS.map((name) => `ekanos apps ${name} <product>`).join(', ')}.`,\n );\n}\n\nfunction requireProduct(value: string | undefined, verb: string): string {\n const slug = value?.trim().toLowerCase();\n if (slug && /^[a-z0-9][a-z0-9-]*$/.test(slug)) return slug;\n throw usageError(\n value\n ? `\"${value}\" is not a product slug.`\n : `apps ${verb} needs a product.`,\n `Pass the product's slug, e.g. \"ekanos apps ${verb} linear\". \"ekanos catalog\" lists them.`,\n );\n}\n\nasync function promptLine(question: string): Promise<string> {\n const readline = await import('node:readline/promises');\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stderr,\n });\n try {\n return await rl.question(question);\n } finally {\n rl.close();\n }\n}\n\n/** Read a line without echoing it: the secret never appears on screen or in scrollback. */\nasync function promptHidden(question: string): Promise<string> {\n const readline = await import('node:readline');\n process.stderr.write(question);\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stderr,\n terminal: true,\n });\n // Swallow readline's echo of each keystroke; the question is already written.\n (rl as unknown as { _writeToOutput: (text: string) => void })._writeToOutput =\n () => {};\n try {\n return await new Promise<string>((resolve) => {\n rl.question('', resolve);\n });\n } finally {\n rl.close();\n process.stderr.write('\\n');\n }\n}\n"]}
1
+ {"version":3,"file":"apps.js","sourceRoot":"","sources":["../../src/commands/apps.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ProviderAppStatus } from '@ekanos/integration-schema';\n\nimport {\n type AppsApiResult,\n setProviderApp,\n showProviderApp,\n} from '../apps-api';\nimport type { StoredSession } from '../auth/credential-store';\nimport {\n type AuthEnvironment,\n refreshStoredSession,\n requireSession,\n resolveAuthEnvironment,\n} from '../auth/session';\nimport type { CliContext } from '../context';\nimport { authRequiredError, usageError, validationError } from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { resolveSourceTarget } from '../source-target';\nimport type { ResultRow } from '../ui';\nimport { promptYesNo } from './publish';\n\nexport const APPS_SUBCOMMANDS = ['show', 'set'] as const;\ntype AppsSubcommand = (typeof APPS_SUBCOMMANDS)[number];\n\nexport interface AppsArgs {\n host?: string;\n source?: string;\n subcommand?: string;\n product?: string;\n dir: string;\n env: Record<string, string | undefined>;\n stdinIsTTY?: boolean;\n /** Test seams for the questions `set` asks. */\n ask?: (question: string) => Promise<string>;\n askSecret?: (question: string) => Promise<string>;\n confirm?: (question: string) => Promise<boolean>;\n}\n\n/**\n * `apps` — a Source's provider apps: the OAuth client an integration signs\n * every team in through (Linear's, say). `show` says whether the Source has\n * one and which callback URL to register; `set` takes the client id and\n * secret from the Source admin at the terminal — never from a flag, a file\n * or a non-interactive run, so an agent driving the CLI never handles the\n * secret.\n */\nexport async function runApps(\n ctx: CliContext,\n args: AppsArgs,\n): Promise<ExitCode> {\n const subcommand = parseSubcommand(args.subcommand);\n const product = requireProduct(args.product, subcommand);\n\n if (subcommand === 'set' && !canPrompt(ctx, args)) {\n throw usageError(\n 'apps set takes the client secret at a terminal, never in a script or an agent run.',\n `Run it yourself in your terminal: npx @ekanos/cli apps set ${product}${args.source ? ` --source ${args.source}` : ''}`,\n );\n }\n\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n const resolved = await resolveSourceTarget(\n env,\n requireSession(env.store, env.host),\n args,\n );\n const client = new AppsClient(env, resolved.session);\n const source = resolved.source;\n\n const status = await client.call((token) =>\n showProviderApp(env.host, token, source, product),\n );\n\n if (subcommand === 'show') {\n return ctx.succeed({ host: env.host, ...status }, summary(ctx, status));\n }\n\n ctx.log(walkthrough(status).join('\\n'));\n if (status.configured) {\n const replace = await (args.confirm ?? promptYesNo)(\n `${status.source.slug} already has ${status.product.name} credentials. Replace them? [y/N]`,\n );\n if (!replace) {\n throw validationError(\n 'Nothing was changed.',\n `Run \"ekanos apps show ${product}\" to see the current state.`,\n );\n }\n }\n\n const clientId = (await (args.ask ?? promptLine)('Client ID: ')).trim();\n const clientSecret = (\n await (args.askSecret ?? promptHidden)('Client secret (hidden): ')\n ).trim();\n if (!clientId || !clientSecret) {\n throw validationError(\n 'Both the client ID and the client secret are needed.',\n `Run \"ekanos apps set ${product}\" again.`,\n );\n }\n\n const result = await client.call((token) =>\n setProviderApp(env.host, token, {\n source,\n product,\n clientId,\n clientSecret,\n }),\n );\n return ctx.succeed(\n { host: env.host, ...result },\n ctx.ui.result({\n title: result.replaced\n ? `Replaced the ${result.product.name} app`\n : `Set up the ${result.product.name} app`,\n rows: [\n ['Source', result.source.slug],\n ['Teams can connect', result.configured ? 'yes' : 'not yet'],\n ['Callback URLs', callbackUrls(result)],\n ],\n }),\n );\n}\n\n/**\n * How to create the app: the integration's own steps (`oauth.setup`, with the\n * callback URL filled in) when it declares them, else the generic three.\n */\nexport function walkthrough(status: ProviderAppStatus): string[] {\n const name = status.product.name;\n const lines = [`${name} app for ${status.source.slug}`];\n if (status.setup?.consoleUrl) {\n lines.push(` Open: ${status.setup.consoleUrl}`);\n }\n const steps = status.setup?.steps.length\n ? [\n ...status.setup.steps,\n `Check the app lists exactly: ${callbackUrls(status).join(', ')}`,\n ]\n : [\n `In ${name}, create an OAuth app (or open the one you use).`,\n `Register exactly these callback URLs: ${callbackUrls(status).join(', ')}`,\n ];\n steps.push(\n \"Enter its client ID and secret below. The secret is stored in Ekanos's vault and never shown again.\",\n );\n lines.push(...steps.map((step, index) => ` ${index + 1}. ${step}`));\n if (status.setup?.guideUrl) lines.push(` Guide: ${status.setup.guideUrl}`);\n return lines;\n}\n\nfunction summary(ctx: CliContext, status: ProviderAppStatus): string {\n const rows: ResultRow[] = [\n ['Source', status.source.slug],\n ...(status.setup\n ? ([\n [\n 'How to create it',\n [\n ...(status.setup.consoleUrl\n ? [`Open ${status.setup.consoleUrl}`]\n : []),\n ...status.setup.steps.map(\n (step, index) => `${index + 1}. ${step}`,\n ),\n ],\n ],\n ] as const)\n : []),\n [\n 'Set up',\n status.configured\n ? 'yes — teams can connect'\n : 'no — no team can connect yet',\n ],\n ['Callback URLs', callbackUrls(status)],\n ];\n return [\n ctx.ui.result({ title: `${status.product.name} app`, rows }),\n ...(status.configured\n ? []\n : [\n ctx.ui.hint(\n `Run \"ekanos apps set ${status.product.slug}\" in your terminal to enter its credentials.`,\n ),\n ]),\n ].join('\\n');\n}\n\nclass AppsClient {\n constructor(\n private readonly env: AuthEnvironment,\n private session: StoredSession,\n ) {}\n\n async call<T>(\n send: (accessToken: string) => Promise<AppsApiResult<T>>,\n ): Promise<T> {\n let result = await send(this.session.accessToken);\n if (result.status === 'unauthorized') {\n this.session = await refreshStoredSession(this.env, this.session);\n result = await send(this.session.accessToken);\n }\n if (result.status === 'ok') return result.data;\n throw authRequiredError(\n `The session for ${this.env.host} could not authorize the apps call.`,\n `Run \"ekanos login --host ${this.env.host}\" and retry.`,\n );\n }\n}\n\nfunction canPrompt(ctx: CliContext, args: AppsArgs): boolean {\n return !ctx.jsonMode && (args.stdinIsTTY ?? process.stdin.isTTY === true);\n}\n\nfunction parseSubcommand(value: string | undefined): AppsSubcommand {\n const match = APPS_SUBCOMMANDS.find((name) => name === value);\n if (match) return match;\n throw usageError(\n value ? `Unknown apps subcommand \"${value}\".` : 'apps needs a subcommand.',\n `Run one of: ${APPS_SUBCOMMANDS.map((name) => `ekanos apps ${name} <product>`).join(', ')}.`,\n );\n}\n\nfunction requireProduct(value: string | undefined, verb: string): string {\n const slug = value?.trim().toLowerCase();\n if (slug && /^[a-z0-9][a-z0-9-]*$/.test(slug)) return slug;\n throw usageError(\n value\n ? `\"${value}\" is not a product slug.`\n : `apps ${verb} needs a product.`,\n `Pass the product's slug, e.g. \"ekanos apps ${verb} linear\". \"ekanos catalog\" lists them.`,\n );\n}\n\nexport async function promptLine(question: string): Promise<string> {\n const readline = await import('node:readline/promises');\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stderr,\n });\n try {\n return await rl.question(question);\n } finally {\n rl.close();\n }\n}\n\n/** Read a line without echoing it: the secret never appears on screen or in scrollback. */\nexport async function promptHidden(question: string): Promise<string> {\n const readline = await import('node:readline');\n process.stderr.write(question);\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stderr,\n terminal: true,\n });\n // Swallow readline's echo of each keystroke; the question is already written.\n (rl as unknown as { _writeToOutput: (text: string) => void })._writeToOutput =\n () => {};\n try {\n return await new Promise<string>((resolve) => {\n rl.question('', resolve);\n });\n } finally {\n rl.close();\n process.stderr.write('\\n');\n }\n}\n\n/** Every redirect URI to register; an older host sends only the one. */\nfunction callbackUrls(status: ProviderAppStatus): string[] {\n return status.callbackUrls?.length\n ? status.callbackUrls\n : [status.callbackUrl];\n}\n"]}
@@ -0,0 +1,30 @@
1
+ import type { TeamIntegrationStatus } from '@ekanos/integration-schema';
2
+ import type { CliContext } from '../context.js';
3
+ import type { ExitCode } from '../exit-codes.js';
4
+ export declare const TEAMS_SUBCOMMANDS: readonly ['list', 'show', 'app', 'webhook'];
5
+ export interface TeamsArgs {
6
+ host?: string;
7
+ source?: string;
8
+ /** `list`, `show <team> <product>`, `app set <team> <product>`, `webhook <team> <product> [<id>]`. */
9
+ positionals: readonly string[];
10
+ connection?: string;
11
+ yes: boolean;
12
+ dir: string;
13
+ env: Record<string, string | undefined>;
14
+ stdinIsTTY?: boolean;
15
+ /** Test seams for the questions `app set` asks. */
16
+ ask?: (question: string) => Promise<string>;
17
+ askSecret?: (question: string) => Promise<string>;
18
+ }
19
+ /**
20
+ * `teams` — a Source admin's view of its teams' integration setup, the
21
+ * team-level counterpart of `apps`. `list` names the teams; `show` reads one
22
+ * team's setup of one product as a checklist; `app set` enters a team's own
23
+ * app (Zendesk's subdomain, client and signing secret) at the admin's
24
+ * terminal — never from a flag, a file or a non-interactive run, so an
25
+ * agent driving the CLI never handles a secret; `webhook` mints the URL the
26
+ * team's provider sends to, shown once.
27
+ */
28
+ export declare function runTeams(ctx: CliContext, args: TeamsArgs): Promise<ExitCode>;
29
+ /** `teams show`, read top to bottom like a setup checklist. */
30
+ export declare function checklist(status: TeamIntegrationStatus): string[];
@@ -0,0 +1,295 @@
1
+ import { refreshStoredSession, requireSession, resolveAuthEnvironment, } from '../auth/session.js';
2
+ import { authRequiredError, invalidStateError, usageError, validationError, } from '../errors.js';
3
+ import { resolveSourceTarget } from '../source-target.js';
4
+ import { listTeams, mintTeamWebhook, setTeamApp, showTeamIntegration, } from '../teams-api.js';
5
+ import { promptHidden, promptLine } from './apps.js';
6
+ export const TEAMS_SUBCOMMANDS = ['list', 'show', 'app', 'webhook'];
7
+ /**
8
+ * `teams` — a Source admin's view of its teams' integration setup, the
9
+ * team-level counterpart of `apps`. `list` names the teams; `show` reads one
10
+ * team's setup of one product as a checklist; `app set` enters a team's own
11
+ * app (Zendesk's subdomain, client and signing secret) at the admin's
12
+ * terminal — never from a flag, a file or a non-interactive run, so an
13
+ * agent driving the CLI never handles a secret; `webhook` mints the URL the
14
+ * team's provider sends to, shown once.
15
+ */
16
+ export async function runTeams(ctx, args) {
17
+ const [rawSubcommand, ...rest] = args.positionals;
18
+ const subcommand = parseSubcommand(rawSubcommand);
19
+ if (subcommand === 'app' && rest[0] !== 'set') {
20
+ throw usageError(rest[0]
21
+ ? `Unknown teams app subcommand "${rest[0]}".`
22
+ : 'teams app needs a subcommand.', 'Run "ekanos teams app set <team> <product>".');
23
+ }
24
+ const operands = subcommand === 'app' ? rest.slice(1) : rest;
25
+ if (subcommand === 'list' && operands.length > 0) {
26
+ throw usageError('teams list takes no arguments.', 'Run "ekanos teams list --source <slug>".');
27
+ }
28
+ const team = subcommand === 'list' ? '' : requireSlug(operands[0], 'team');
29
+ const product = subcommand === 'list' ? '' : requireSlug(operands[1], 'product');
30
+ const extra = operands.slice(subcommand === 'webhook' ? 3 : 2);
31
+ if (subcommand !== 'list' && extra.length > 0) {
32
+ throw usageError(`Unexpected argument "${extra[0]}".`, 'See "ekanos teams --help".');
33
+ }
34
+ if (subcommand === 'app' && !canPrompt(ctx, args)) {
35
+ throw usageError("teams app set takes the team's secrets at a terminal, never in a script or an agent run.", `Run it yourself in your terminal: npx @ekanos/cli teams app set ${team} ${product}${args.connection ? ` --connection ${args.connection}` : ''}${args.source ? ` --source ${args.source}` : ''}`);
36
+ }
37
+ const env = resolveAuthEnvironment(ctx, args.host, args.env, {
38
+ projectDir: args.dir,
39
+ });
40
+ const resolved = await resolveSourceTarget(env, requireSession(env.store, env.host), args);
41
+ const client = new TeamsClient(env, resolved.session);
42
+ const source = resolved.source;
43
+ if (subcommand === 'list') {
44
+ const list = await client.call((token) => listTeams(env.host, token, source));
45
+ return ctx.succeed(Object.assign({ host: env.host }, list), list.teams.length === 0
46
+ ? ctx.ui.result({
47
+ title: `No teams on ${list.source.slug}`,
48
+ tone: 'warn',
49
+ })
50
+ : ctx.ui.result({
51
+ title: `Teams on ${list.source.slug}`,
52
+ rows: list.teams.map((entry) => [
53
+ entry.slug,
54
+ `${entry.name} · ${entry.memberCount} member${entry.memberCount === 1 ? '' : 's'}`,
55
+ ]),
56
+ notes: [
57
+ '"ekanos teams show <team> <product>" shows a team\'s setup.',
58
+ ],
59
+ }));
60
+ }
61
+ const target = { source, team, product };
62
+ const status = await client.call((token) => showTeamIntegration(env.host, token, target));
63
+ if (subcommand === 'show') {
64
+ return ctx.succeed(Object.assign({ host: env.host }, status), checklist(status).join('\n'));
65
+ }
66
+ if (!status.enabled) {
67
+ throw invalidStateError(`"${product}" is not enabled on ${status.source.slug}.`, '"ekanos catalog" lists what this Source has; Ekanos enables a product for a Source.');
68
+ }
69
+ if (subcommand === 'app') {
70
+ return setApp(ctx, args, client, env.host, target, status);
71
+ }
72
+ return mintWebhook(ctx, args, client, env.host, target, status, operands[2]);
73
+ }
74
+ async function setApp(ctx, args, client, host, target, status) {
75
+ var _a, _b;
76
+ const app = chooseApp(status, args.connection);
77
+ ctx.log(appWalkthrough(status, app).join('\n'));
78
+ const fields = {};
79
+ for (const field of app.fields) {
80
+ const keep = field.set ? ' (set — Enter keeps it)' : '';
81
+ const question = field.sensitive
82
+ ? `${field.label}${keep} (hidden): `
83
+ : `${field.label}${keep}: `;
84
+ const value = (await (field.sensitive
85
+ ? ((_a = args.askSecret) !== null && _a !== void 0 ? _a : promptHidden)
86
+ : ((_b = args.ask) !== null && _b !== void 0 ? _b : promptLine))(question)).trim();
87
+ if (value)
88
+ fields[field.secretName] = value;
89
+ }
90
+ const missing = app.fields.filter((field) => !field.set && !fields[field.secretName]);
91
+ if (missing.length > 0) {
92
+ throw validationError(`Every field is needed: ${missing.map((field) => field.label).join(', ')}.`, `Run "ekanos teams app set ${target.team} ${target.product}" again.`);
93
+ }
94
+ if (Object.keys(fields).length === 0) {
95
+ throw validationError('Nothing was changed.', `Run "ekanos teams show ${target.team} ${target.product}" to see the current state.`);
96
+ }
97
+ const result = await client.call((token) => setTeamApp(host, token, target, {
98
+ connectionId: app.connectionId,
99
+ fields,
100
+ }));
101
+ const next = nextSteps(status, app, target);
102
+ return ctx.succeed(Object.assign({ host }, result), ctx.ui.result({
103
+ title: `Saved ${target.team}'s ${result.product.name} app`,
104
+ rows: [
105
+ ['Source', status.source.slug],
106
+ [
107
+ 'Fields',
108
+ result.fields.map((field) => `${mark(field.set)} ${field.label}`),
109
+ ],
110
+ ],
111
+ notes: next,
112
+ }));
113
+ }
114
+ async function mintWebhook(ctx, args, client, host, target, status, requested) {
115
+ const webhooks = status.teamApps.flatMap((app) => app.webhooks);
116
+ if (webhooks.length === 0) {
117
+ throw invalidStateError(`${status.product.name} has no webhook a team's app sends.`, `Run "ekanos teams show ${target.team} ${target.product}" to see its setup.`);
118
+ }
119
+ const webhook = requested
120
+ ? webhooks.find((entry) => entry.webhookId === requested)
121
+ : webhooks.length === 1
122
+ ? webhooks[0]
123
+ : undefined;
124
+ if (!webhook) {
125
+ throw usageError(requested
126
+ ? `${status.product.name} has no team-app webhook "${requested}".`
127
+ : `${status.product.name} has several webhooks; name one.`, `Run one of: ${webhooks.map((entry) => `ekanos teams webhook ${target.team} ${target.product} ${entry.webhookId}`).join(', ')}.`);
128
+ }
129
+ if (webhook.endpoint && !args.yes) {
130
+ throw invalidStateError(`${target.team} already has a live URL for ${webhook.webhookId} (${webhook.endpoint.tokenPrefix}…). A new one revokes it, and the provider's webhook stops working until you paste the new URL there.`, `Re-run with --yes when you are ready to update the provider: ekanos teams webhook ${target.team} ${target.product} ${webhook.webhookId} --yes`);
131
+ }
132
+ const minted = await client.call((token) => mintTeamWebhook(host, token, target, webhook.webhookId));
133
+ return ctx.succeed(Object.assign({ host }, minted), ctx.ui.result({
134
+ title: `Webhook URL for ${target.team} · ${webhook.webhookId}`,
135
+ tone: 'warn',
136
+ rows: [
137
+ ['URL', minted.url],
138
+ [
139
+ 'Paste it into',
140
+ `${status.product.name}'s webhook settings for this team`,
141
+ ],
142
+ ],
143
+ notes: [
144
+ 'This URL is shown once and cannot be read again. Paste it now.',
145
+ ...(minted.replaced
146
+ ? ['The previous URL is revoked: the provider must use this one.']
147
+ : []),
148
+ ],
149
+ }));
150
+ }
151
+ /** `teams show`, read top to bottom like a setup checklist. */
152
+ export function checklist(status) {
153
+ var _a, _b;
154
+ const name = status.product.name;
155
+ const lines = [
156
+ `${name} for team ${status.team.slug} (${status.team.name}) on ${status.source.slug}`,
157
+ ` ${mark(status.enabled)} Enabled on the Source`,
158
+ ];
159
+ if (!status.enabled) {
160
+ lines.push(' "ekanos catalog" lists what this Source has; Ekanos enables a product for a Source.');
161
+ return lines;
162
+ }
163
+ const target = { team: status.team.slug, product: status.product.slug };
164
+ for (const app of status.teamApps) {
165
+ lines.push(` Team app: ${app.label} (${app.connectionId})`);
166
+ if ((_a = app.setup) === null || _a === void 0 ? void 0 : _a.consoleUrl)
167
+ lines.push(` Open: ${app.setup.consoleUrl}`);
168
+ (_b = app.setup) === null || _b === void 0 ? void 0 : _b.steps.forEach((step, index) => lines.push(` ${index + 1}. ${step}`));
169
+ lines.push(' Callback URLs to register:');
170
+ lines.push(...callbackUrls(app).map((url) => ` ${url}`));
171
+ if (app.guideUrl)
172
+ lines.push(` Guide: ${app.guideUrl}`);
173
+ for (const field of app.fields) {
174
+ lines.push(` ${mark(field.set)} ${field.label}`);
175
+ }
176
+ if (app.fields.some((field) => !field.set)) {
177
+ lines.push(` → in your terminal: npx @ekanos/cli teams app set ${target.team} ${target.product} --connection ${app.connectionId} --source ${status.source.slug}`);
178
+ }
179
+ for (const webhook of app.webhooks) {
180
+ lines.push(webhook.endpoint
181
+ ? ` ${mark(true)} Webhook ${webhook.webhookId}: live URL ${webhook.endpoint.tokenPrefix}… since ${day(webhook.endpoint.createdAt)}${webhook.endpoint.lastUsedAt ? `, last delivery ${day(webhook.endpoint.lastUsedAt)}` : ', no delivery yet'}`
182
+ : ` ${mark(false)} Webhook ${webhook.webhookId}: no URL yet`);
183
+ if (!webhook.endpoint) {
184
+ lines.push(` → ekanos teams webhook ${target.team} ${target.product} ${webhook.webhookId} --source ${status.source.slug}`);
185
+ }
186
+ }
187
+ lines.push(app.connection
188
+ ? ` ${mark(true)} Connected ${day(app.connection.connectedAt)}${app.connection.connectedBy ? ` by ${who(app.connection.connectedBy)}` : ''}`
189
+ : ` ${mark(false)} Connected: a team member clicks Connect at ${status.connectUrl}`);
190
+ }
191
+ lines.push(status.activation
192
+ ? ` ${mark(true)} Activated for the team${status.activation.activatedAt ? ` ${day(status.activation.activatedAt)}` : ''}${status.activation.activatedBy ? ` by ${who(status.activation.activatedBy)}` : ''}`
193
+ : ` ${mark(false)} Activated: a team member connects at ${status.connectUrl}`);
194
+ lines.push(` Team page: ${status.connectUrl}`);
195
+ if (status.agentEvents.length > 0) {
196
+ lines.push(' Agent events it offers:');
197
+ lines.push(...status.agentEvents.map((event) => ` ${event.id} — ${event.label}`));
198
+ }
199
+ return lines;
200
+ }
201
+ function appWalkthrough(status, app) {
202
+ var _a, _b;
203
+ const lines = [
204
+ `${status.product.name} app for team ${status.team.slug} on ${status.source.slug}`,
205
+ ];
206
+ if ((_a = app.setup) === null || _a === void 0 ? void 0 : _a.consoleUrl)
207
+ lines.push(` Open: ${app.setup.consoleUrl}`);
208
+ const steps = ((_b = app.setup) === null || _b === void 0 ? void 0 : _b.steps.length)
209
+ ? [...app.setup.steps]
210
+ : [
211
+ `In ${status.product.name}, create an OAuth app for the team (or open the one it uses).`,
212
+ ];
213
+ steps.push(`Check the app lists exactly: ${callbackUrls(app).join(', ')}`, "Enter its values below. Secrets are stored in Ekanos's vault and never shown again.");
214
+ lines.push(...steps.map((step, index) => ` ${index + 1}. ${step}`));
215
+ if (app.guideUrl)
216
+ lines.push(` Guide: ${app.guideUrl}`);
217
+ return lines;
218
+ }
219
+ function nextSteps(status, app, target) {
220
+ return [
221
+ ...app.webhooks
222
+ .filter((webhook) => !webhook.endpoint)
223
+ .map((webhook) => `Next: "ekanos teams webhook ${target.team} ${target.product} ${webhook.webhookId}" for the provider's webhook URL.`),
224
+ ...(app.connection
225
+ ? []
226
+ : [`Then a team member clicks Connect at ${status.connectUrl}`]),
227
+ ];
228
+ }
229
+ function chooseApp(status, connection) {
230
+ const apps = status.teamApps;
231
+ if (apps.length === 0) {
232
+ throw invalidStateError(`${status.product.name} takes no team app: teams connect through the Source's app, or need none.`, `Run "ekanos apps show ${status.product.slug}" for the Source's app.`);
233
+ }
234
+ const app = connection
235
+ ? apps.find((entry) => entry.connectionId === connection)
236
+ : apps.length === 1
237
+ ? apps[0]
238
+ : apps.find((entry) => entry.connectionId === 'default');
239
+ if (app)
240
+ return app;
241
+ throw usageError(connection
242
+ ? `${status.product.name} has no team app "${connection}".`
243
+ : `${status.product.name} has several team apps; name one.`, `Pass --connection with one of: ${apps.map((entry) => entry.connectionId).join(', ')}.`);
244
+ }
245
+ class TeamsClient {
246
+ constructor(env, session) {
247
+ this.env = env;
248
+ this.session = session;
249
+ }
250
+ async call(send) {
251
+ let result = await send(this.session.accessToken);
252
+ if (result.status === 'unauthorized') {
253
+ this.session = await refreshStoredSession(this.env, this.session);
254
+ result = await send(this.session.accessToken);
255
+ }
256
+ if (result.status === 'ok')
257
+ return result.data;
258
+ throw authRequiredError(`The session for ${this.env.host} could not authorize the teams call.`, `Run "ekanos login --host ${this.env.host}" and retry.`);
259
+ }
260
+ }
261
+ function canPrompt(ctx, args) {
262
+ var _a;
263
+ return !ctx.jsonMode && ((_a = args.stdinIsTTY) !== null && _a !== void 0 ? _a : process.stdin.isTTY === true);
264
+ }
265
+ function parseSubcommand(value) {
266
+ const match = TEAMS_SUBCOMMANDS.find((name) => name === value);
267
+ if (match)
268
+ return match;
269
+ throw usageError(value
270
+ ? `Unknown teams subcommand "${value}".`
271
+ : 'teams needs a subcommand.', 'Run one of: ekanos teams list, ekanos teams show <team> <product>, ekanos teams app set <team> <product>, ekanos teams webhook <team> <product> [<webhook-id>].');
272
+ }
273
+ function requireSlug(value, what) {
274
+ const slug = value === null || value === void 0 ? void 0 : value.trim().toLowerCase();
275
+ if (slug && /^[a-z0-9][a-z0-9-]*$/.test(slug))
276
+ return slug;
277
+ throw usageError(value ? `"${value}" is not a ${what} slug.` : `Name the ${what}.`, what === 'team'
278
+ ? '"ekanos teams list" lists the teams, e.g. "ekanos teams show acme-support zendesk".'
279
+ : '"ekanos catalog" lists the products, e.g. "ekanos teams show acme-support zendesk".');
280
+ }
281
+ function mark(done) {
282
+ return done ? '✓' : '✗';
283
+ }
284
+ function day(iso) {
285
+ return iso.slice(0, 10);
286
+ }
287
+ function who(member) {
288
+ var _a, _b;
289
+ return (_b = (_a = member.email) !== null && _a !== void 0 ? _a : member.name) !== null && _b !== void 0 ? _b : member.userId;
290
+ }
291
+ /** Every redirect URI to register. */
292
+ function callbackUrls(app) {
293
+ return app.callbackUrls.length ? app.callbackUrls : [app.callbackUrl];
294
+ }
295
+ //# sourceMappingURL=teams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"teams.js","sourceRoot":"","sources":["../../src/commands/teams.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n TeamAppState,\n TeamIntegrationStatus,\n} from '@ekanos/integration-schema';\n\nimport type { StoredSession } from '../auth/credential-store';\nimport {\n type AuthEnvironment,\n refreshStoredSession,\n requireSession,\n resolveAuthEnvironment,\n} from '../auth/session';\nimport type { CliContext } from '../context';\nimport {\n authRequiredError,\n invalidStateError,\n usageError,\n validationError,\n} from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport { resolveSourceTarget } from '../source-target';\nimport {\n type TeamsApiResult,\n listTeams,\n mintTeamWebhook,\n setTeamApp,\n showTeamIntegration,\n} from '../teams-api';\nimport { promptHidden, promptLine } from './apps';\n\nexport const TEAMS_SUBCOMMANDS = ['list', 'show', 'app', 'webhook'] as const;\ntype TeamsSubcommand = (typeof TEAMS_SUBCOMMANDS)[number];\n\nexport interface TeamsArgs {\n host?: string;\n source?: string;\n /** `list`, `show <team> <product>`, `app set <team> <product>`, `webhook <team> <product> [<id>]`. */\n positionals: readonly string[];\n connection?: string;\n yes: boolean;\n dir: string;\n env: Record<string, string | undefined>;\n stdinIsTTY?: boolean;\n /** Test seams for the questions `app set` asks. */\n ask?: (question: string) => Promise<string>;\n askSecret?: (question: string) => Promise<string>;\n}\n\ninterface Target {\n source: string;\n team: string;\n product: string;\n}\n\n/**\n * `teams` — a Source admin's view of its teams' integration setup, the\n * team-level counterpart of `apps`. `list` names the teams; `show` reads one\n * team's setup of one product as a checklist; `app set` enters a team's own\n * app (Zendesk's subdomain, client and signing secret) at the admin's\n * terminal — never from a flag, a file or a non-interactive run, so an\n * agent driving the CLI never handles a secret; `webhook` mints the URL the\n * team's provider sends to, shown once.\n */\nexport async function runTeams(\n ctx: CliContext,\n args: TeamsArgs,\n): Promise<ExitCode> {\n const [rawSubcommand, ...rest] = args.positionals;\n const subcommand = parseSubcommand(rawSubcommand);\n\n if (subcommand === 'app' && rest[0] !== 'set') {\n throw usageError(\n rest[0]\n ? `Unknown teams app subcommand \"${rest[0]}\".`\n : 'teams app needs a subcommand.',\n 'Run \"ekanos teams app set <team> <product>\".',\n );\n }\n const operands = subcommand === 'app' ? rest.slice(1) : rest;\n\n if (subcommand === 'list' && operands.length > 0) {\n throw usageError(\n 'teams list takes no arguments.',\n 'Run \"ekanos teams list --source <slug>\".',\n );\n }\n\n const team = subcommand === 'list' ? '' : requireSlug(operands[0], 'team');\n const product =\n subcommand === 'list' ? '' : requireSlug(operands[1], 'product');\n const extra = operands.slice(subcommand === 'webhook' ? 3 : 2);\n if (subcommand !== 'list' && extra.length > 0) {\n throw usageError(\n `Unexpected argument \"${extra[0]}\".`,\n 'See \"ekanos teams --help\".',\n );\n }\n\n if (subcommand === 'app' && !canPrompt(ctx, args)) {\n throw usageError(\n \"teams app set takes the team's secrets at a terminal, never in a script or an agent run.\",\n `Run it yourself in your terminal: npx @ekanos/cli teams app set ${team} ${product}${args.connection ? ` --connection ${args.connection}` : ''}${args.source ? ` --source ${args.source}` : ''}`,\n );\n }\n\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n const resolved = await resolveSourceTarget(\n env,\n requireSession(env.store, env.host),\n args,\n );\n const client = new TeamsClient(env, resolved.session);\n const source = resolved.source;\n\n if (subcommand === 'list') {\n const list = await client.call((token) =>\n listTeams(env.host, token, source),\n );\n return ctx.succeed(\n { host: env.host, ...list },\n list.teams.length === 0\n ? ctx.ui.result({\n title: `No teams on ${list.source.slug}`,\n tone: 'warn',\n })\n : ctx.ui.result({\n title: `Teams on ${list.source.slug}`,\n rows: list.teams.map((entry) => [\n entry.slug,\n `${entry.name} · ${entry.memberCount} member${entry.memberCount === 1 ? '' : 's'}`,\n ]),\n notes: [\n '\"ekanos teams show <team> <product>\" shows a team\\'s setup.',\n ],\n }),\n );\n }\n\n const target: Target = { source, team, product };\n const status = await client.call((token) =>\n showTeamIntegration(env.host, token, target),\n );\n\n if (subcommand === 'show') {\n return ctx.succeed(\n { host: env.host, ...status },\n checklist(status).join('\\n'),\n );\n }\n\n if (!status.enabled) {\n throw invalidStateError(\n `\"${product}\" is not enabled on ${status.source.slug}.`,\n '\"ekanos catalog\" lists what this Source has; Ekanos enables a product for a Source.',\n );\n }\n\n if (subcommand === 'app') {\n return setApp(ctx, args, client, env.host, target, status);\n }\n return mintWebhook(ctx, args, client, env.host, target, status, operands[2]);\n}\n\nasync function setApp(\n ctx: CliContext,\n args: TeamsArgs,\n client: TeamsClient,\n host: string,\n target: Target,\n status: TeamIntegrationStatus,\n): Promise<ExitCode> {\n const app = chooseApp(status, args.connection);\n ctx.log(appWalkthrough(status, app).join('\\n'));\n\n const fields: Record<string, string> = {};\n for (const field of app.fields) {\n const keep = field.set ? ' (set — Enter keeps it)' : '';\n const question = field.sensitive\n ? `${field.label}${keep} (hidden): `\n : `${field.label}${keep}: `;\n const value = (\n await (\n field.sensitive\n ? (args.askSecret ?? promptHidden)\n : (args.ask ?? promptLine)\n )(question)\n ).trim();\n if (value) fields[field.secretName] = value;\n }\n\n const missing = app.fields.filter(\n (field) => !field.set && !fields[field.secretName],\n );\n if (missing.length > 0) {\n throw validationError(\n `Every field is needed: ${missing.map((field) => field.label).join(', ')}.`,\n `Run \"ekanos teams app set ${target.team} ${target.product}\" again.`,\n );\n }\n if (Object.keys(fields).length === 0) {\n throw validationError(\n 'Nothing was changed.',\n `Run \"ekanos teams show ${target.team} ${target.product}\" to see the current state.`,\n );\n }\n\n const result = await client.call((token) =>\n setTeamApp(host, token, target, {\n connectionId: app.connectionId,\n fields,\n }),\n );\n const next = nextSteps(status, app, target);\n return ctx.succeed(\n { host, ...result },\n ctx.ui.result({\n title: `Saved ${target.team}'s ${result.product.name} app`,\n rows: [\n ['Source', status.source.slug],\n [\n 'Fields',\n result.fields.map((field) => `${mark(field.set)} ${field.label}`),\n ],\n ],\n notes: next,\n }),\n );\n}\n\nasync function mintWebhook(\n ctx: CliContext,\n args: TeamsArgs,\n client: TeamsClient,\n host: string,\n target: Target,\n status: TeamIntegrationStatus,\n requested: string | undefined,\n): Promise<ExitCode> {\n const webhooks = status.teamApps.flatMap((app) => app.webhooks);\n if (webhooks.length === 0) {\n throw invalidStateError(\n `${status.product.name} has no webhook a team's app sends.`,\n `Run \"ekanos teams show ${target.team} ${target.product}\" to see its setup.`,\n );\n }\n const webhook = requested\n ? webhooks.find((entry) => entry.webhookId === requested)\n : webhooks.length === 1\n ? webhooks[0]\n : undefined;\n if (!webhook) {\n throw usageError(\n requested\n ? `${status.product.name} has no team-app webhook \"${requested}\".`\n : `${status.product.name} has several webhooks; name one.`,\n `Run one of: ${webhooks.map((entry) => `ekanos teams webhook ${target.team} ${target.product} ${entry.webhookId}`).join(', ')}.`,\n );\n }\n\n if (webhook.endpoint && !args.yes) {\n throw invalidStateError(\n `${target.team} already has a live URL for ${webhook.webhookId} (${webhook.endpoint.tokenPrefix}…). A new one revokes it, and the provider's webhook stops working until you paste the new URL there.`,\n `Re-run with --yes when you are ready to update the provider: ekanos teams webhook ${target.team} ${target.product} ${webhook.webhookId} --yes`,\n );\n }\n\n const minted = await client.call((token) =>\n mintTeamWebhook(host, token, target, webhook.webhookId),\n );\n return ctx.succeed(\n { host, ...minted },\n ctx.ui.result({\n title: `Webhook URL for ${target.team} · ${webhook.webhookId}`,\n tone: 'warn',\n rows: [\n ['URL', minted.url],\n [\n 'Paste it into',\n `${status.product.name}'s webhook settings for this team`,\n ],\n ],\n notes: [\n 'This URL is shown once and cannot be read again. Paste it now.',\n ...(minted.replaced\n ? ['The previous URL is revoked: the provider must use this one.']\n : []),\n ],\n }),\n );\n}\n\n/** `teams show`, read top to bottom like a setup checklist. */\nexport function checklist(status: TeamIntegrationStatus): string[] {\n const name = status.product.name;\n const lines = [\n `${name} for team ${status.team.slug} (${status.team.name}) on ${status.source.slug}`,\n ` ${mark(status.enabled)} Enabled on the Source`,\n ];\n if (!status.enabled) {\n lines.push(\n ' \"ekanos catalog\" lists what this Source has; Ekanos enables a product for a Source.',\n );\n return lines;\n }\n\n const target = { team: status.team.slug, product: status.product.slug };\n for (const app of status.teamApps) {\n lines.push(` Team app: ${app.label} (${app.connectionId})`);\n if (app.setup?.consoleUrl) lines.push(` Open: ${app.setup.consoleUrl}`);\n app.setup?.steps.forEach((step, index) =>\n lines.push(` ${index + 1}. ${step}`),\n );\n lines.push(' Callback URLs to register:');\n lines.push(...callbackUrls(app).map((url) => ` ${url}`));\n if (app.guideUrl) lines.push(` Guide: ${app.guideUrl}`);\n for (const field of app.fields) {\n lines.push(` ${mark(field.set)} ${field.label}`);\n }\n if (app.fields.some((field) => !field.set)) {\n lines.push(\n ` → in your terminal: npx @ekanos/cli teams app set ${target.team} ${target.product} --connection ${app.connectionId} --source ${status.source.slug}`,\n );\n }\n for (const webhook of app.webhooks) {\n lines.push(\n webhook.endpoint\n ? ` ${mark(true)} Webhook ${webhook.webhookId}: live URL ${webhook.endpoint.tokenPrefix}… since ${day(webhook.endpoint.createdAt)}${webhook.endpoint.lastUsedAt ? `, last delivery ${day(webhook.endpoint.lastUsedAt)}` : ', no delivery yet'}`\n : ` ${mark(false)} Webhook ${webhook.webhookId}: no URL yet`,\n );\n if (!webhook.endpoint) {\n lines.push(\n ` → ekanos teams webhook ${target.team} ${target.product} ${webhook.webhookId} --source ${status.source.slug}`,\n );\n }\n }\n lines.push(\n app.connection\n ? ` ${mark(true)} Connected ${day(app.connection.connectedAt)}${app.connection.connectedBy ? ` by ${who(app.connection.connectedBy)}` : ''}`\n : ` ${mark(false)} Connected: a team member clicks Connect at ${status.connectUrl}`,\n );\n }\n\n lines.push(\n status.activation\n ? ` ${mark(true)} Activated for the team${status.activation.activatedAt ? ` ${day(status.activation.activatedAt)}` : ''}${status.activation.activatedBy ? ` by ${who(status.activation.activatedBy)}` : ''}`\n : ` ${mark(false)} Activated: a team member connects at ${status.connectUrl}`,\n );\n lines.push(` Team page: ${status.connectUrl}`);\n if (status.agentEvents.length > 0) {\n lines.push(' Agent events it offers:');\n lines.push(\n ...status.agentEvents.map((event) => ` ${event.id} — ${event.label}`),\n );\n }\n return lines;\n}\n\nfunction appWalkthrough(\n status: TeamIntegrationStatus,\n app: TeamAppState,\n): string[] {\n const lines = [\n `${status.product.name} app for team ${status.team.slug} on ${status.source.slug}`,\n ];\n if (app.setup?.consoleUrl) lines.push(` Open: ${app.setup.consoleUrl}`);\n const steps = app.setup?.steps.length\n ? [...app.setup.steps]\n : [\n `In ${status.product.name}, create an OAuth app for the team (or open the one it uses).`,\n ];\n steps.push(\n `Check the app lists exactly: ${callbackUrls(app).join(', ')}`,\n \"Enter its values below. Secrets are stored in Ekanos's vault and never shown again.\",\n );\n lines.push(...steps.map((step, index) => ` ${index + 1}. ${step}`));\n if (app.guideUrl) lines.push(` Guide: ${app.guideUrl}`);\n return lines;\n}\n\nfunction nextSteps(\n status: TeamIntegrationStatus,\n app: TeamAppState,\n target: Target,\n): string[] {\n return [\n ...app.webhooks\n .filter((webhook) => !webhook.endpoint)\n .map(\n (webhook) =>\n `Next: \"ekanos teams webhook ${target.team} ${target.product} ${webhook.webhookId}\" for the provider's webhook URL.`,\n ),\n ...(app.connection\n ? []\n : [`Then a team member clicks Connect at ${status.connectUrl}`]),\n ];\n}\n\nfunction chooseApp(\n status: TeamIntegrationStatus,\n connection: string | undefined,\n): TeamAppState {\n const apps = status.teamApps;\n if (apps.length === 0) {\n throw invalidStateError(\n `${status.product.name} takes no team app: teams connect through the Source's app, or need none.`,\n `Run \"ekanos apps show ${status.product.slug}\" for the Source's app.`,\n );\n }\n const app = connection\n ? apps.find((entry) => entry.connectionId === connection)\n : apps.length === 1\n ? apps[0]\n : apps.find((entry) => entry.connectionId === 'default');\n if (app) return app;\n throw usageError(\n connection\n ? `${status.product.name} has no team app \"${connection}\".`\n : `${status.product.name} has several team apps; name one.`,\n `Pass --connection with one of: ${apps.map((entry) => entry.connectionId).join(', ')}.`,\n );\n}\n\nclass TeamsClient {\n constructor(\n private readonly env: AuthEnvironment,\n private session: StoredSession,\n ) {}\n\n async call<T>(\n send: (accessToken: string) => Promise<TeamsApiResult<T>>,\n ): Promise<T> {\n let result = await send(this.session.accessToken);\n if (result.status === 'unauthorized') {\n this.session = await refreshStoredSession(this.env, this.session);\n result = await send(this.session.accessToken);\n }\n if (result.status === 'ok') return result.data;\n throw authRequiredError(\n `The session for ${this.env.host} could not authorize the teams call.`,\n `Run \"ekanos login --host ${this.env.host}\" and retry.`,\n );\n }\n}\n\nfunction canPrompt(ctx: CliContext, args: TeamsArgs): boolean {\n return !ctx.jsonMode && (args.stdinIsTTY ?? process.stdin.isTTY === true);\n}\n\nfunction parseSubcommand(value: string | undefined): TeamsSubcommand {\n const match = TEAMS_SUBCOMMANDS.find((name) => name === value);\n if (match) return match;\n throw usageError(\n value\n ? `Unknown teams subcommand \"${value}\".`\n : 'teams needs a subcommand.',\n 'Run one of: ekanos teams list, ekanos teams show <team> <product>, ekanos teams app set <team> <product>, ekanos teams webhook <team> <product> [<webhook-id>].',\n );\n}\n\nfunction requireSlug(value: string | undefined, what: 'team' | 'product') {\n const slug = value?.trim().toLowerCase();\n if (slug && /^[a-z0-9][a-z0-9-]*$/.test(slug)) return slug;\n throw usageError(\n value ? `\"${value}\" is not a ${what} slug.` : `Name the ${what}.`,\n what === 'team'\n ? '\"ekanos teams list\" lists the teams, e.g. \"ekanos teams show acme-support zendesk\".'\n : '\"ekanos catalog\" lists the products, e.g. \"ekanos teams show acme-support zendesk\".',\n );\n}\n\nfunction mark(done: boolean): string {\n return done ? '✓' : '✗';\n}\n\nfunction day(iso: string): string {\n return iso.slice(0, 10);\n}\n\nfunction who(member: {\n name: string | null;\n email: string | null;\n userId: string;\n}) {\n return member.email ?? member.name ?? member.userId;\n}\n\n/** Every redirect URI to register. */\nfunction callbackUrls(app: TeamAppState): string[] {\n return app.callbackUrls.length ? app.callbackUrls : [app.callbackUrl];\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -16,7 +16,7 @@ export interface RunOptions {
16
16
  */
17
17
  stdinIsTTY?: boolean;
18
18
  confirm?: (question: string) => Promise<boolean>;
19
- /** Test-only answers for `apps set`'s questions — never set from real CLI wiring. */
19
+ /** Test-only answers for `apps set` / `teams app set` questions — never set from real CLI wiring. */
20
20
  ask?: (question: string) => Promise<string>;
21
21
  askSecret?: (question: string) => Promise<string>;
22
22
  /**
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { runLogout } from './commands/logout.js';
9
9
  import { runPublish } from './commands/publish.js';
10
10
  import { runSources } from './commands/sources.js';
11
11
  import { runStatus } from './commands/status.js';
12
+ import { runTeams } from './commands/teams.js';
12
13
  import { runTest } from './commands/test.js';
13
14
  import { runUpgrade } from './commands/upgrade.js';
14
15
  import { runUse } from './commands/use.js';
@@ -87,6 +88,13 @@ const COMMAND_SPECS = {
87
88
  source: { type: 'string' },
88
89
  dir: { type: 'string' },
89
90
  },
91
+ teams: {
92
+ host: { type: 'string' },
93
+ source: { type: 'string' },
94
+ dir: { type: 'string' },
95
+ connection: { type: 'string' },
96
+ yes: { type: 'boolean' },
97
+ },
90
98
  agents: {
91
99
  host: { type: 'string' },
92
100
  source: { type: 'string' },
@@ -125,6 +133,8 @@ const POSITIONAL_COMMANDS = {
125
133
  use: ['source-slug'],
126
134
  catalog: ['product-slug'],
127
135
  apps: ['subcommand', 'product-slug'],
136
+ // `app set <team> <product>` is the longest form: four tokens.
137
+ teams: ['subcommand', 'team-slug', 'product-slug', 'webhook-id'],
128
138
  agents: ['subcommand', 'key-or-path'],
129
139
  };
130
140
  const COMMANDS = Object.keys(COMMAND_SPECS);
@@ -154,6 +164,11 @@ const USAGE = [
154
164
  " apps The Source's app for an integration teams sign in through:",
155
165
  ' show <product> | set <product> (Source admin; takes the secret',
156
166
  ' at your terminal)',
167
+ " teams A team's integration setup, for the Source admin:",
168
+ ' list | show <team> <product> (a checklist: fields, callback',
169
+ ' URLs, webhooks, connect link) | app set <team> <product>',
170
+ ' [--connection <id>] (takes the secrets at your terminal) |',
171
+ ' webhook <team> <product> [<webhook-id>] [--yes] (URL shown once)',
157
172
  " agents The source's agent templates, held by Ekanos:",
158
173
  ' init [<dir>] [--claude|--no-claude] (skills + docs MCP for designing',
159
174
  ' agents with Claude; offers to add the docs to Claude Code)',
@@ -174,7 +189,7 @@ const USAGE = [
174
189
  ' bare "ekanos upgrade" MUTATES the project (installs the target versions,',
175
190
  ' verifies with validate + the test script, rolls back on any failure).',
176
191
  '',
177
- 'Host resolution (logout, whoami, status, sources, catalog, apps, agents, use, publish, upgrade):',
192
+ 'Host resolution (logout, whoami, status, sources, catalog, apps, teams, agents, use, publish, upgrade):',
178
193
  ' --host, else EKANOS_HOST, else the "host" field in ekanos.json, else the',
179
194
  ' sole stored login. "ekanos publish" saves host and source to ekanos.json.',
180
195
  ' "ekanos login" never reads ekanos.json for a host (--host, else',
@@ -346,6 +361,19 @@ export async function run(argv, options = {}) {
346
361
  askSecret: options.askSecret,
347
362
  confirm: options.confirm,
348
363
  });
364
+ case 'teams':
365
+ return await runTeams(ctx, {
366
+ host: parsed.flags.host,
367
+ source: parsed.flags.source,
368
+ positionals: parsed.positionals,
369
+ connection: parsed.flags.connection,
370
+ yes: parsed.flags.yes === true,
371
+ dir,
372
+ env,
373
+ stdinIsTTY: options.stdinIsTTY,
374
+ ask: options.ask,
375
+ askSecret: options.askSecret,
376
+ });
349
377
  case 'agents':
350
378
  return await runAgents(ctx, {
351
379
  host: parsed.flags.host,