@ekanos/cli 0.9.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.
- package/dist/commands/apps.d.ts +3 -0
- package/dist/commands/apps.js +2 -2
- package/dist/commands/apps.js.map +1 -1
- package/dist/commands/teams.d.ts +30 -0
- package/dist/commands/teams.js +295 -0
- package/dist/commands/teams.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +29 -1
- package/dist/index.js.map +1 -1
- package/dist/teams-api.d.ts +43 -0
- package/dist/teams-api.js +99 -0
- package/dist/teams-api.js.map +1 -0
- package/package.json +2 -2
- package/templates/package.json.tmpl +4 -4
- package/templates/skills/build-agent/SKILL.md +17 -10
- package/templates/skills/build-agent/references/set-up-an-integration.md +133 -0
- package/templates/skills/build-agent/references/triggers.md +2 -1
- package/templates/skills/ekanos-platform/SKILL.md +7 -2
package/dist/commands/apps.d.ts
CHANGED
|
@@ -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>;
|
package/dist/commands/apps.js
CHANGED
|
@@ -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({
|
|
@@ -1 +1 @@
|
|
|
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\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\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"]}
|
|
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`
|
|
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,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\n\nimport { runAgents } from './commands/agents';\nimport { runApps } from './commands/apps';\nimport { runCatalog } from './commands/catalog';\nimport { runDev } from './commands/dev';\nimport { runInit } from './commands/init';\nimport { runLogin } from './commands/login';\nimport { runLogout } from './commands/logout';\nimport { runPublish } from './commands/publish';\nimport { runSources } from './commands/sources';\nimport { runStatus } from './commands/status';\nimport { runTest } from './commands/test';\nimport { runUpgrade } from './commands/upgrade';\nimport { runUse } from './commands/use';\nimport { runValidate } from './commands/validate';\nimport { runWhoami } from './commands/whoami';\nimport { CliContext, type WriteStream } from './context';\nimport { SDK_DOCS_URL, docsPage } from './docs-links';\nimport { toCliError, usageError } from './errors';\nimport { EXIT_CODES } from './exit-codes';\nimport { type FlagSpecs, parseArgv } from './parse-argv';\n\nexport { CliContext } from './context';\nexport { EXIT_CODES, ERROR_CODES } from './exit-codes';\nexport type { ExitCode, ErrorCode } from './exit-codes';\n\nconst GLOBAL_SPECS: FlagSpecs = {\n json: { type: 'boolean' },\n help: { type: 'boolean' },\n version: { type: 'boolean' },\n};\n\nconst COMMAND_SPECS: Record<string, FlagSpecs> = {\n init: {\n slug: { type: 'string' },\n dir: { type: 'string' },\n force: { type: 'boolean' },\n yes: { type: 'boolean' },\n // Repeatable: each occurrence appends one docs entry (URL or relative\n // path) to the scaffolded ekanos.json.\n docs: { type: 'string', repeatable: true },\n },\n validate: {\n dir: { type: 'string' },\n },\n dev: {\n dir: { type: 'string' },\n port: { type: 'string' },\n host: { type: 'string' },\n force: { type: 'boolean' },\n start: { type: 'boolean' },\n },\n test: {\n dir: { type: 'string' },\n // Skips the vitest JSON-reporter injection and the curated failures\n // appendix, reproducing the pre-appendix raw-passthrough behavior —\n // also the escape hatch if the vitest-detection heuristic ever guesses\n // wrong for a project. See `test-report.ts`.\n raw: { type: 'boolean' },\n },\n login: {\n host: { type: 'string' },\n force: { type: 'boolean' },\n claude: { type: 'boolean' },\n },\n logout: {\n host: { type: 'string' },\n all: { type: 'boolean' },\n },\n whoami: {\n host: { type: 'string' },\n },\n status: {\n host: { type: 'string' },\n dir: { type: 'string' },\n findings: { type: 'boolean' },\n all: { type: 'boolean' },\n watch: { type: 'boolean' },\n },\n sources: {\n host: { type: 'string' },\n dir: { type: 'string' },\n },\n catalog: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n },\n apps: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n },\n agents: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n // Not `--version`: that is global, and prints the CLI's own version.\n to: { type: 'string' },\n 'base-version': { type: 'string' },\n overwrite: { type: 'boolean' },\n out: { type: 'string' },\n yes: { type: 'boolean' },\n force: { type: 'boolean' },\n claude: { type: 'boolean' },\n },\n use: {\n host: { type: 'string' },\n dir: { type: 'string' },\n },\n publish: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n yes: { type: 'boolean' },\n },\n upgrade: {\n dir: { type: 'string' },\n host: { type: 'string' },\n check: { type: 'boolean' },\n yes: { type: 'boolean' },\n // `--no-verify` is the negation of this — parse-argv's `--no-<bool>`\n // convention (same as `--no-json`/`--no-start`), not a flag of its own.\n verify: { type: 'boolean' },\n },\n};\n\n/** Commands that take positional arguments, and what each means, in order. Every other command is positional-free. */\nconst POSITIONAL_COMMANDS: Record<string, readonly string[]> = {\n use: ['source-slug'],\n catalog: ['product-slug'],\n apps: ['subcommand', 'product-slug'],\n agents: ['subcommand', 'key-or-path'],\n};\n\nconst COMMANDS = Object.keys(COMMAND_SPECS);\n\nconst USAGE = [\n 'Usage: ekanos <command> [options]',\n '',\n 'Commands:',\n ' init Scaffold a new integration project into ./<slug> (--slug <slug> --yes)',\n ' validate Validate ekanos.json + the integration definition (--json)',\n ' dev Scaffold .ekanos/harness and run it with your own Next',\n ' test Run the project test script via its package manager (--json).',\n ' For a vitest test script, also renders a curated failures',\n ' appendix from a JSON reporter report; --raw skips that and',\n ' the reporter injection entirely.',\n ' login Authenticate against an Ekanos host in a browser (--host <url>;',\n ' offers to add the Ekanos docs to Claude Code: --claude|--no-claude)',\n ' logout Revoke and delete the stored session (--host <url> | --all)',\n ' whoami Print the identity the stored session authenticates as, plus',\n ' a compact list of the sources you hold a seat on',\n \" status Show login state and this project's submissions on the host\",\n ' (--findings for full gate findings, --all to expand other',\n ' integrations in the source, --watch to block until this',\n \" project's current version reaches a gate verdict)\",\n ' sources List every source you hold a developer/source-admin seat on',\n ' catalog What a source can already use, and how to set it up',\n ' (\"ekanos catalog [<product-slug>] --source <slug>\", --json)',\n \" apps The Source's app for an integration teams sign in through:\",\n ' show <product> | set <product> (Source admin; takes the secret',\n ' at your terminal)',\n \" agents The source's agent templates, held by Ekanos:\",\n ' init [<dir>] [--claude|--no-claude] (skills + docs MCP for designing',\n ' agents with Claude; offers to add the docs to Claude Code)',\n ' list | show <key> | pull <key> [--out <dir>]',\n ' apply <dir|file> [--base-version <n>] [--overwrite]',\n ' publish <key> [--to <n>] --yes | rollback <key> --to <n> --yes',\n ' use Set this project\\'s publish target (\"ekanos use <source-slug>\")',\n ' publish Validate, pack and submit the integration to an Ekanos host',\n ' upgrade Move this toolchain to a coherent target version, with',\n ' verify-and-rollback (--check --yes --no-verify --host <url>)',\n '',\n 'Keeping the toolchain current:',\n ' validate, dev and publish all check for a newer @ekanos/cli in the',\n ' background (never blocking, silenced by EKANOS_NO_UPDATE_CHECK) and warn',\n ' on stderr when one exists. Run \"ekanos upgrade --check\" any time a',\n ' validate finding smells like version skew (an unrecognized-key error you',\n ' are confident is correct) — it is read-only and safe to run freely. A',\n ' bare \"ekanos upgrade\" MUTATES the project (installs the target versions,',\n ' verifies with validate + the test script, rolls back on any failure).',\n '',\n 'Host resolution (logout, whoami, status, sources, catalog, apps, agents, use, publish, upgrade):',\n ' --host, else EKANOS_HOST, else the \"host\" field in ekanos.json, else the',\n ' sole stored login. \"ekanos publish\" saves host and source to ekanos.json.',\n ' \"ekanos login\" never reads ekanos.json for a host (--host, else',\n ' EKANOS_HOST, else the sole stored login) — it is the verb that creates',\n ' credentials, so a committed file must never choose where they go.',\n '',\n 'Publish targeting (see \"ekanos sources\" / \"ekanos use\"):',\n ' publish refuses (FORBIDDEN) a source you hold no seat on. With multiple',\n ' seats and an implicit (ekanos.json) source, a human run asks to confirm',\n ' the target; a scripted/JSON run needs --source or --yes to proceed —',\n ' it never prompts and never guesses.',\n '',\n 'Global options:',\n ' --json Emit a single JSON envelope on stdout (auto-on when piped);',\n ' --no-json forces the human-readable form',\n ' --version Print the CLI version',\n ' --help Show this help',\n].join('\\n');\n\nexport interface RunOptions {\n stdout?: WriteStream;\n stderr?: WriteStream;\n env?: Record<string, string | undefined>;\n cwd?: string;\n /**\n * Test-only override for `publish`'s interactive-confirm gate and `init`\n * / `login`'s clack wizard gate (`canWizard` in `wizard.ts`) — see\n * `PublishArgs` in `commands/publish.ts`. Never set this from real CLI\n * wiring; it exists so a test can exercise the human y/N confirm branch or\n * the wizard flow without a real terminal.\n */\n stdinIsTTY?: boolean;\n confirm?: (question: string) => Promise<boolean>;\n /** Test-only answers for `apps set`'s questions — never set from real CLI wiring. */\n ask?: (question: string) => Promise<string>;\n askSecret?: (question: string) => Promise<string>;\n /**\n * Test-only override for `status --watch`'s poll delay — see `StatusArgs`\n * in `commands/status.ts`. Never set this from real CLI wiring; it exists\n * so a test can exercise the poll loop without waiting 30s per iteration.\n */\n sleep?: (ms: number) => Promise<void>;\n /**\n * Test-only override for `status`'s AGE column (\"now\") — see `StatusArgs`\n * in `commands/status.ts`. Never set this from real CLI wiring; it exists\n * so a test can assert an exact `relativeTime` string deterministically.\n */\n now?: Date;\n}\n\n/**\n * The single entry point. Resolves the agent-native envelope once, dispatches\n * to a verb, and guarantees exactly one terminal emission (success or error).\n * Returns the process exit code — `bin.ts` is the only place that calls\n * `process.exit`, so this stays fully testable.\n */\nexport async function run(\n argv: readonly string[],\n options: RunOptions = {},\n): Promise<number> {\n const jsonFlag = scanJsonFlag(argv);\n const ctx = new CliContext({\n jsonFlag,\n stdout: options.stdout,\n stderr: options.stderr,\n env: options.env,\n });\n const cwd = options.cwd ?? process.cwd();\n const env = options.env ?? process.env;\n\n try {\n // `--version` is global and verb-independent, so it is answered before the\n // command is even resolved: `ekanos --version` and `ekanos validate\n // --version` both report the tool's version and exit 0.\n if (scanFlagPresence(argv, 'version')) {\n return emitVersion(ctx);\n }\n\n const { command, rest } = splitCommand(argv);\n\n // Top-level help / no command.\n if (!command) {\n if (scanFlagPresence(rest, 'help') || argv.length === 0) {\n return emitHelp(ctx);\n }\n // A leading token that is not a command (e.g. `--nonsense`).\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n error: 'unknown-command',\n commands: COMMANDS,\n usage: USAGE,\n });\n throw usageError(\n `Unknown command or flag \"${argv[0]}\".`,\n `Run one of: ${COMMANDS.join(', ')}. See \"ekanos --help\".`,\n );\n }\n\n if (!COMMANDS.includes(command)) {\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n error: 'unknown-command',\n given: command,\n commands: COMMANDS,\n usage: USAGE,\n });\n throw usageError(\n `Unknown command \"${command}\".`,\n `Run one of: ${COMMANDS.join(', ')}. See \"ekanos --help\".`,\n );\n }\n\n const specs: FlagSpecs = { ...GLOBAL_SPECS, ...COMMAND_SPECS[command] };\n const parsed = parseArgv(rest, specs);\n\n if (parsed.flags.help === true) {\n return emitCommandHelp(ctx, command);\n }\n\n const positionalNames = POSITIONAL_COMMANDS[command] ?? [];\n\n if (positionalNames.length === 0 && parsed.positionals.length > 0) {\n throw usageError(\n `\"${command}\" takes no positional arguments (got \"${parsed.positionals[0]}\").`,\n `Pass options as flags, e.g. \"--dir <path>\". See \"ekanos ${command} --help\".`,\n );\n }\n\n if (parsed.positionals.length > positionalNames.length) {\n const usage = positionalNames.map((name) => `<${name}>`).join(' ');\n throw usageError(\n `\"${command}\" takes at most ${positionalNames.length} positional ` +\n `argument${positionalNames.length === 1 ? '' : 's'} (${usage}), ` +\n `got ${parsed.positionals.length}.`,\n `e.g. \"ekanos ${command} ${usage}\". See \"ekanos ${command} --help\".`,\n );\n }\n\n const dir = (parsed.flags.dir as string | undefined) ?? cwd;\n\n switch (command) {\n case 'init':\n return await runInit(ctx, {\n slug: parsed.flags.slug as string | undefined,\n dir: parsed.flags.dir as string | undefined,\n cwd,\n force: parsed.flags.force === true,\n yes: parsed.flags.yes === true,\n docs: parsed.flags.docs as string[] | undefined,\n stdinIsTTY: options.stdinIsTTY,\n });\n case 'validate':\n return await runValidate(ctx, { dir, env });\n case 'dev':\n return await runDev(ctx, {\n dir,\n port: parsed.flags.port as string | undefined,\n host: parsed.flags.host as string | undefined,\n force: parsed.flags.force === true,\n start: parsed.flags.start as boolean | undefined,\n env,\n });\n case 'test':\n return await runTest(ctx, {\n dir,\n passthrough: parsed.passthrough,\n raw: parsed.flags.raw === true,\n });\n case 'login':\n return await runLogin(ctx, {\n host: parsed.flags.host as string | undefined,\n force: parsed.flags.force === true,\n claude: parsed.flags.claude as boolean | undefined,\n cwd,\n env,\n stdinIsTTY: options.stdinIsTTY,\n });\n case 'logout':\n return await runLogout(ctx, {\n host: parsed.flags.host as string | undefined,\n all: parsed.flags.all === true,\n cwd,\n env,\n });\n case 'whoami':\n return await runWhoami(ctx, {\n host: parsed.flags.host as string | undefined,\n cwd,\n env,\n });\n case 'status':\n return await runStatus(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n env,\n findings: parsed.flags.findings === true,\n all: parsed.flags.all === true,\n watch: parsed.flags.watch === true,\n sleep: options.sleep,\n now: options.now,\n });\n case 'sources':\n return await runSources(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n env,\n });\n case 'catalog':\n return await runCatalog(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n product: parsed.positionals[0],\n dir,\n env,\n });\n case 'apps':\n return await runApps(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n subcommand: parsed.positionals[0],\n product: parsed.positionals[1],\n dir,\n env,\n stdinIsTTY: options.stdinIsTTY,\n ask: options.ask,\n askSecret: options.askSecret,\n confirm: options.confirm,\n });\n case 'agents':\n return await runAgents(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n subcommand: parsed.positionals[0],\n target: parsed.positionals[1],\n to: parsed.flags.to as string | undefined,\n baseVersion: parsed.flags['base-version'] as string | undefined,\n overwrite: parsed.flags.overwrite === true,\n out: parsed.flags.out as string | undefined,\n yes: parsed.flags.yes === true,\n force: parsed.flags.force === true,\n claude: parsed.flags.claude as boolean | undefined,\n dir,\n env,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n case 'use':\n return await runUse(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n slug: parsed.positionals[0],\n env,\n });\n case 'publish':\n return await runPublish(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n dir,\n yes: parsed.flags.yes === true,\n env,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n case 'upgrade':\n return await runUpgrade(ctx, {\n dir,\n host: parsed.flags.host as string | undefined,\n env,\n check: parsed.flags.check === true,\n yes: parsed.flags.yes === true,\n noVerify: parsed.flags.verify === false,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n default:\n // Unreachable — COMMANDS gate above — but keeps the switch total.\n throw usageError(\n `Unknown command \"${command}\".`,\n `Run one of: ${COMMANDS.join(', ')}.`,\n );\n }\n } catch (error) {\n return ctx.fail(toCliError(error));\n }\n}\n\n/**\n * Presence of `--json` (or `--no-json`) BEFORE the first `--` passthrough.\n *\n * Tri-state on purpose. `undefined` means \"the user expressed no preference\",\n * which is what lets the context fall back to inferring the mode from a\n * non-TTY stdout or CLAUDECODE. Collapsing `--no-json` to `false` here would\n * make it indistinguishable from absence, and the flag could then never turn\n * JSON mode OFF in exactly the cases it exists for.\n */\nfunction scanJsonFlag(argv: readonly string[]): boolean | undefined {\n for (const token of argv) {\n if (token === '--') break;\n if (token === '--json') return true;\n if (token === '--no-json') return false;\n }\n return undefined;\n}\n\nfunction scanFlagPresence(argv: readonly string[], name: string): boolean {\n for (const token of argv) {\n if (token === '--') break;\n if (token === `--${name}`) return true;\n }\n return false;\n}\n\n/**\n * Split argv into the command (first bare token before `--`) and the rest\n * (everything else, order preserved, including global flags and the `--`\n * passthrough). Global flags are all booleans, so any bare token is the\n * command — no value-consumption ambiguity.\n */\nfunction splitCommand(argv: readonly string[]): {\n command: string | null;\n rest: string[];\n} {\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i]!;\n if (token === '--') break;\n if (!token.startsWith('-')) {\n return {\n command: token,\n rest: [...argv.slice(0, i), ...argv.slice(i + 1)],\n };\n }\n }\n return { command: null, rest: [...argv] };\n}\n\n/**\n * The CLI's own version, read from the package manifest at runtime rather than\n * baked in by the build — one source of truth, so a release bump cannot leave\n * `--version` reporting a stale number. The path resolves identically from\n * `src/index.ts` and from the emitted `dist/index.js`, both of which sit one\n * directory below the package root.\n */\nfunction readVersion(): string {\n const manifest = new URL('../package.json', import.meta.url);\n const parsed: unknown = JSON.parse(fs.readFileSync(manifest, 'utf8'));\n\n return typeof parsed === 'object' &&\n parsed !== null &&\n 'version' in parsed &&\n typeof parsed.version === 'string'\n ? parsed.version\n : '0.0.0';\n}\n\nfunction emitVersion(ctx: CliContext): number {\n const version = readVersion();\n\n return ctx.succeed({ name: 'ekanos', version }, version);\n}\n\nconst DOCS_FOOTER = `\\nDocs: ${SDK_DOCS_URL} (commands: ${docsPage('cli/commands')})`;\n\nfunction emitHelp(ctx: CliContext): number {\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n commands: COMMANDS,\n usage: USAGE,\n exitCodes: EXIT_CODES,\n });\n return ctx.succeed(\n { usage: USAGE, commands: COMMANDS, docs: SDK_DOCS_URL },\n `${USAGE}${DOCS_FOOTER}`,\n );\n}\n\nfunction emitCommandHelp(ctx: CliContext, command: string): number {\n const specs = COMMAND_SPECS[command] ?? {};\n const flags = Object.keys(specs).map((name) => `--${name}`);\n const help = `Usage: ekanos ${command} [${flags.join('] [')}]`;\n ctx.emitClaudeCodeHint({ tool: 'ekanos', command, flags });\n return ctx.succeed({ command, flags }, help);\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\n\nimport { runAgents } from './commands/agents';\nimport { runApps } from './commands/apps';\nimport { runCatalog } from './commands/catalog';\nimport { runDev } from './commands/dev';\nimport { runInit } from './commands/init';\nimport { runLogin } from './commands/login';\nimport { runLogout } from './commands/logout';\nimport { runPublish } from './commands/publish';\nimport { runSources } from './commands/sources';\nimport { runStatus } from './commands/status';\nimport { runTeams } from './commands/teams';\nimport { runTest } from './commands/test';\nimport { runUpgrade } from './commands/upgrade';\nimport { runUse } from './commands/use';\nimport { runValidate } from './commands/validate';\nimport { runWhoami } from './commands/whoami';\nimport { CliContext, type WriteStream } from './context';\nimport { SDK_DOCS_URL, docsPage } from './docs-links';\nimport { toCliError, usageError } from './errors';\nimport { EXIT_CODES } from './exit-codes';\nimport { type FlagSpecs, parseArgv } from './parse-argv';\n\nexport { CliContext } from './context';\nexport { EXIT_CODES, ERROR_CODES } from './exit-codes';\nexport type { ExitCode, ErrorCode } from './exit-codes';\n\nconst GLOBAL_SPECS: FlagSpecs = {\n json: { type: 'boolean' },\n help: { type: 'boolean' },\n version: { type: 'boolean' },\n};\n\nconst COMMAND_SPECS: Record<string, FlagSpecs> = {\n init: {\n slug: { type: 'string' },\n dir: { type: 'string' },\n force: { type: 'boolean' },\n yes: { type: 'boolean' },\n // Repeatable: each occurrence appends one docs entry (URL or relative\n // path) to the scaffolded ekanos.json.\n docs: { type: 'string', repeatable: true },\n },\n validate: {\n dir: { type: 'string' },\n },\n dev: {\n dir: { type: 'string' },\n port: { type: 'string' },\n host: { type: 'string' },\n force: { type: 'boolean' },\n start: { type: 'boolean' },\n },\n test: {\n dir: { type: 'string' },\n // Skips the vitest JSON-reporter injection and the curated failures\n // appendix, reproducing the pre-appendix raw-passthrough behavior —\n // also the escape hatch if the vitest-detection heuristic ever guesses\n // wrong for a project. See `test-report.ts`.\n raw: { type: 'boolean' },\n },\n login: {\n host: { type: 'string' },\n force: { type: 'boolean' },\n claude: { type: 'boolean' },\n },\n logout: {\n host: { type: 'string' },\n all: { type: 'boolean' },\n },\n whoami: {\n host: { type: 'string' },\n },\n status: {\n host: { type: 'string' },\n dir: { type: 'string' },\n findings: { type: 'boolean' },\n all: { type: 'boolean' },\n watch: { type: 'boolean' },\n },\n sources: {\n host: { type: 'string' },\n dir: { type: 'string' },\n },\n catalog: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n },\n apps: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n },\n teams: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n connection: { type: 'string' },\n yes: { type: 'boolean' },\n },\n agents: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n // Not `--version`: that is global, and prints the CLI's own version.\n to: { type: 'string' },\n 'base-version': { type: 'string' },\n overwrite: { type: 'boolean' },\n out: { type: 'string' },\n yes: { type: 'boolean' },\n force: { type: 'boolean' },\n claude: { type: 'boolean' },\n },\n use: {\n host: { type: 'string' },\n dir: { type: 'string' },\n },\n publish: {\n host: { type: 'string' },\n source: { type: 'string' },\n dir: { type: 'string' },\n yes: { type: 'boolean' },\n },\n upgrade: {\n dir: { type: 'string' },\n host: { type: 'string' },\n check: { type: 'boolean' },\n yes: { type: 'boolean' },\n // `--no-verify` is the negation of this — parse-argv's `--no-<bool>`\n // convention (same as `--no-json`/`--no-start`), not a flag of its own.\n verify: { type: 'boolean' },\n },\n};\n\n/** Commands that take positional arguments, and what each means, in order. Every other command is positional-free. */\nconst POSITIONAL_COMMANDS: Record<string, readonly string[]> = {\n use: ['source-slug'],\n catalog: ['product-slug'],\n apps: ['subcommand', 'product-slug'],\n // `app set <team> <product>` is the longest form: four tokens.\n teams: ['subcommand', 'team-slug', 'product-slug', 'webhook-id'],\n agents: ['subcommand', 'key-or-path'],\n};\n\nconst COMMANDS = Object.keys(COMMAND_SPECS);\n\nconst USAGE = [\n 'Usage: ekanos <command> [options]',\n '',\n 'Commands:',\n ' init Scaffold a new integration project into ./<slug> (--slug <slug> --yes)',\n ' validate Validate ekanos.json + the integration definition (--json)',\n ' dev Scaffold .ekanos/harness and run it with your own Next',\n ' test Run the project test script via its package manager (--json).',\n ' For a vitest test script, also renders a curated failures',\n ' appendix from a JSON reporter report; --raw skips that and',\n ' the reporter injection entirely.',\n ' login Authenticate against an Ekanos host in a browser (--host <url>;',\n ' offers to add the Ekanos docs to Claude Code: --claude|--no-claude)',\n ' logout Revoke and delete the stored session (--host <url> | --all)',\n ' whoami Print the identity the stored session authenticates as, plus',\n ' a compact list of the sources you hold a seat on',\n \" status Show login state and this project's submissions on the host\",\n ' (--findings for full gate findings, --all to expand other',\n ' integrations in the source, --watch to block until this',\n \" project's current version reaches a gate verdict)\",\n ' sources List every source you hold a developer/source-admin seat on',\n ' catalog What a source can already use, and how to set it up',\n ' (\"ekanos catalog [<product-slug>] --source <slug>\", --json)',\n \" apps The Source's app for an integration teams sign in through:\",\n ' show <product> | set <product> (Source admin; takes the secret',\n ' at your terminal)',\n \" teams A team's integration setup, for the Source admin:\",\n ' list | show <team> <product> (a checklist: fields, callback',\n ' URLs, webhooks, connect link) | app set <team> <product>',\n ' [--connection <id>] (takes the secrets at your terminal) |',\n ' webhook <team> <product> [<webhook-id>] [--yes] (URL shown once)',\n \" agents The source's agent templates, held by Ekanos:\",\n ' init [<dir>] [--claude|--no-claude] (skills + docs MCP for designing',\n ' agents with Claude; offers to add the docs to Claude Code)',\n ' list | show <key> | pull <key> [--out <dir>]',\n ' apply <dir|file> [--base-version <n>] [--overwrite]',\n ' publish <key> [--to <n>] --yes | rollback <key> --to <n> --yes',\n ' use Set this project\\'s publish target (\"ekanos use <source-slug>\")',\n ' publish Validate, pack and submit the integration to an Ekanos host',\n ' upgrade Move this toolchain to a coherent target version, with',\n ' verify-and-rollback (--check --yes --no-verify --host <url>)',\n '',\n 'Keeping the toolchain current:',\n ' validate, dev and publish all check for a newer @ekanos/cli in the',\n ' background (never blocking, silenced by EKANOS_NO_UPDATE_CHECK) and warn',\n ' on stderr when one exists. Run \"ekanos upgrade --check\" any time a',\n ' validate finding smells like version skew (an unrecognized-key error you',\n ' are confident is correct) — it is read-only and safe to run freely. A',\n ' bare \"ekanos upgrade\" MUTATES the project (installs the target versions,',\n ' verifies with validate + the test script, rolls back on any failure).',\n '',\n 'Host resolution (logout, whoami, status, sources, catalog, apps, teams, agents, use, publish, upgrade):',\n ' --host, else EKANOS_HOST, else the \"host\" field in ekanos.json, else the',\n ' sole stored login. \"ekanos publish\" saves host and source to ekanos.json.',\n ' \"ekanos login\" never reads ekanos.json for a host (--host, else',\n ' EKANOS_HOST, else the sole stored login) — it is the verb that creates',\n ' credentials, so a committed file must never choose where they go.',\n '',\n 'Publish targeting (see \"ekanos sources\" / \"ekanos use\"):',\n ' publish refuses (FORBIDDEN) a source you hold no seat on. With multiple',\n ' seats and an implicit (ekanos.json) source, a human run asks to confirm',\n ' the target; a scripted/JSON run needs --source or --yes to proceed —',\n ' it never prompts and never guesses.',\n '',\n 'Global options:',\n ' --json Emit a single JSON envelope on stdout (auto-on when piped);',\n ' --no-json forces the human-readable form',\n ' --version Print the CLI version',\n ' --help Show this help',\n].join('\\n');\n\nexport interface RunOptions {\n stdout?: WriteStream;\n stderr?: WriteStream;\n env?: Record<string, string | undefined>;\n cwd?: string;\n /**\n * Test-only override for `publish`'s interactive-confirm gate and `init`\n * / `login`'s clack wizard gate (`canWizard` in `wizard.ts`) — see\n * `PublishArgs` in `commands/publish.ts`. Never set this from real CLI\n * wiring; it exists so a test can exercise the human y/N confirm branch or\n * the wizard flow without a real terminal.\n */\n stdinIsTTY?: boolean;\n confirm?: (question: string) => Promise<boolean>;\n /** Test-only answers for `apps set` / `teams app set` questions — never set from real CLI wiring. */\n ask?: (question: string) => Promise<string>;\n askSecret?: (question: string) => Promise<string>;\n /**\n * Test-only override for `status --watch`'s poll delay — see `StatusArgs`\n * in `commands/status.ts`. Never set this from real CLI wiring; it exists\n * so a test can exercise the poll loop without waiting 30s per iteration.\n */\n sleep?: (ms: number) => Promise<void>;\n /**\n * Test-only override for `status`'s AGE column (\"now\") — see `StatusArgs`\n * in `commands/status.ts`. Never set this from real CLI wiring; it exists\n * so a test can assert an exact `relativeTime` string deterministically.\n */\n now?: Date;\n}\n\n/**\n * The single entry point. Resolves the agent-native envelope once, dispatches\n * to a verb, and guarantees exactly one terminal emission (success or error).\n * Returns the process exit code — `bin.ts` is the only place that calls\n * `process.exit`, so this stays fully testable.\n */\nexport async function run(\n argv: readonly string[],\n options: RunOptions = {},\n): Promise<number> {\n const jsonFlag = scanJsonFlag(argv);\n const ctx = new CliContext({\n jsonFlag,\n stdout: options.stdout,\n stderr: options.stderr,\n env: options.env,\n });\n const cwd = options.cwd ?? process.cwd();\n const env = options.env ?? process.env;\n\n try {\n // `--version` is global and verb-independent, so it is answered before the\n // command is even resolved: `ekanos --version` and `ekanos validate\n // --version` both report the tool's version and exit 0.\n if (scanFlagPresence(argv, 'version')) {\n return emitVersion(ctx);\n }\n\n const { command, rest } = splitCommand(argv);\n\n // Top-level help / no command.\n if (!command) {\n if (scanFlagPresence(rest, 'help') || argv.length === 0) {\n return emitHelp(ctx);\n }\n // A leading token that is not a command (e.g. `--nonsense`).\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n error: 'unknown-command',\n commands: COMMANDS,\n usage: USAGE,\n });\n throw usageError(\n `Unknown command or flag \"${argv[0]}\".`,\n `Run one of: ${COMMANDS.join(', ')}. See \"ekanos --help\".`,\n );\n }\n\n if (!COMMANDS.includes(command)) {\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n error: 'unknown-command',\n given: command,\n commands: COMMANDS,\n usage: USAGE,\n });\n throw usageError(\n `Unknown command \"${command}\".`,\n `Run one of: ${COMMANDS.join(', ')}. See \"ekanos --help\".`,\n );\n }\n\n const specs: FlagSpecs = { ...GLOBAL_SPECS, ...COMMAND_SPECS[command] };\n const parsed = parseArgv(rest, specs);\n\n if (parsed.flags.help === true) {\n return emitCommandHelp(ctx, command);\n }\n\n const positionalNames = POSITIONAL_COMMANDS[command] ?? [];\n\n if (positionalNames.length === 0 && parsed.positionals.length > 0) {\n throw usageError(\n `\"${command}\" takes no positional arguments (got \"${parsed.positionals[0]}\").`,\n `Pass options as flags, e.g. \"--dir <path>\". See \"ekanos ${command} --help\".`,\n );\n }\n\n if (parsed.positionals.length > positionalNames.length) {\n const usage = positionalNames.map((name) => `<${name}>`).join(' ');\n throw usageError(\n `\"${command}\" takes at most ${positionalNames.length} positional ` +\n `argument${positionalNames.length === 1 ? '' : 's'} (${usage}), ` +\n `got ${parsed.positionals.length}.`,\n `e.g. \"ekanos ${command} ${usage}\". See \"ekanos ${command} --help\".`,\n );\n }\n\n const dir = (parsed.flags.dir as string | undefined) ?? cwd;\n\n switch (command) {\n case 'init':\n return await runInit(ctx, {\n slug: parsed.flags.slug as string | undefined,\n dir: parsed.flags.dir as string | undefined,\n cwd,\n force: parsed.flags.force === true,\n yes: parsed.flags.yes === true,\n docs: parsed.flags.docs as string[] | undefined,\n stdinIsTTY: options.stdinIsTTY,\n });\n case 'validate':\n return await runValidate(ctx, { dir, env });\n case 'dev':\n return await runDev(ctx, {\n dir,\n port: parsed.flags.port as string | undefined,\n host: parsed.flags.host as string | undefined,\n force: parsed.flags.force === true,\n start: parsed.flags.start as boolean | undefined,\n env,\n });\n case 'test':\n return await runTest(ctx, {\n dir,\n passthrough: parsed.passthrough,\n raw: parsed.flags.raw === true,\n });\n case 'login':\n return await runLogin(ctx, {\n host: parsed.flags.host as string | undefined,\n force: parsed.flags.force === true,\n claude: parsed.flags.claude as boolean | undefined,\n cwd,\n env,\n stdinIsTTY: options.stdinIsTTY,\n });\n case 'logout':\n return await runLogout(ctx, {\n host: parsed.flags.host as string | undefined,\n all: parsed.flags.all === true,\n cwd,\n env,\n });\n case 'whoami':\n return await runWhoami(ctx, {\n host: parsed.flags.host as string | undefined,\n cwd,\n env,\n });\n case 'status':\n return await runStatus(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n env,\n findings: parsed.flags.findings === true,\n all: parsed.flags.all === true,\n watch: parsed.flags.watch === true,\n sleep: options.sleep,\n now: options.now,\n });\n case 'sources':\n return await runSources(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n env,\n });\n case 'catalog':\n return await runCatalog(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n product: parsed.positionals[0],\n dir,\n env,\n });\n case 'apps':\n return await runApps(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n subcommand: parsed.positionals[0],\n product: parsed.positionals[1],\n dir,\n env,\n stdinIsTTY: options.stdinIsTTY,\n ask: options.ask,\n askSecret: options.askSecret,\n confirm: options.confirm,\n });\n case 'teams':\n return await runTeams(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n positionals: parsed.positionals,\n connection: parsed.flags.connection as string | undefined,\n yes: parsed.flags.yes === true,\n dir,\n env,\n stdinIsTTY: options.stdinIsTTY,\n ask: options.ask,\n askSecret: options.askSecret,\n });\n case 'agents':\n return await runAgents(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n subcommand: parsed.positionals[0],\n target: parsed.positionals[1],\n to: parsed.flags.to as string | undefined,\n baseVersion: parsed.flags['base-version'] as string | undefined,\n overwrite: parsed.flags.overwrite === true,\n out: parsed.flags.out as string | undefined,\n yes: parsed.flags.yes === true,\n force: parsed.flags.force === true,\n claude: parsed.flags.claude as boolean | undefined,\n dir,\n env,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n case 'use':\n return await runUse(ctx, {\n host: parsed.flags.host as string | undefined,\n dir,\n slug: parsed.positionals[0],\n env,\n });\n case 'publish':\n return await runPublish(ctx, {\n host: parsed.flags.host as string | undefined,\n source: parsed.flags.source as string | undefined,\n dir,\n yes: parsed.flags.yes === true,\n env,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n case 'upgrade':\n return await runUpgrade(ctx, {\n dir,\n host: parsed.flags.host as string | undefined,\n env,\n check: parsed.flags.check === true,\n yes: parsed.flags.yes === true,\n noVerify: parsed.flags.verify === false,\n stdinIsTTY: options.stdinIsTTY,\n confirm: options.confirm,\n });\n default:\n // Unreachable — COMMANDS gate above — but keeps the switch total.\n throw usageError(\n `Unknown command \"${command}\".`,\n `Run one of: ${COMMANDS.join(', ')}.`,\n );\n }\n } catch (error) {\n return ctx.fail(toCliError(error));\n }\n}\n\n/**\n * Presence of `--json` (or `--no-json`) BEFORE the first `--` passthrough.\n *\n * Tri-state on purpose. `undefined` means \"the user expressed no preference\",\n * which is what lets the context fall back to inferring the mode from a\n * non-TTY stdout or CLAUDECODE. Collapsing `--no-json` to `false` here would\n * make it indistinguishable from absence, and the flag could then never turn\n * JSON mode OFF in exactly the cases it exists for.\n */\nfunction scanJsonFlag(argv: readonly string[]): boolean | undefined {\n for (const token of argv) {\n if (token === '--') break;\n if (token === '--json') return true;\n if (token === '--no-json') return false;\n }\n return undefined;\n}\n\nfunction scanFlagPresence(argv: readonly string[], name: string): boolean {\n for (const token of argv) {\n if (token === '--') break;\n if (token === `--${name}`) return true;\n }\n return false;\n}\n\n/**\n * Split argv into the command (first bare token before `--`) and the rest\n * (everything else, order preserved, including global flags and the `--`\n * passthrough). Global flags are all booleans, so any bare token is the\n * command — no value-consumption ambiguity.\n */\nfunction splitCommand(argv: readonly string[]): {\n command: string | null;\n rest: string[];\n} {\n for (let i = 0; i < argv.length; i++) {\n const token = argv[i]!;\n if (token === '--') break;\n if (!token.startsWith('-')) {\n return {\n command: token,\n rest: [...argv.slice(0, i), ...argv.slice(i + 1)],\n };\n }\n }\n return { command: null, rest: [...argv] };\n}\n\n/**\n * The CLI's own version, read from the package manifest at runtime rather than\n * baked in by the build — one source of truth, so a release bump cannot leave\n * `--version` reporting a stale number. The path resolves identically from\n * `src/index.ts` and from the emitted `dist/index.js`, both of which sit one\n * directory below the package root.\n */\nfunction readVersion(): string {\n const manifest = new URL('../package.json', import.meta.url);\n const parsed: unknown = JSON.parse(fs.readFileSync(manifest, 'utf8'));\n\n return typeof parsed === 'object' &&\n parsed !== null &&\n 'version' in parsed &&\n typeof parsed.version === 'string'\n ? parsed.version\n : '0.0.0';\n}\n\nfunction emitVersion(ctx: CliContext): number {\n const version = readVersion();\n\n return ctx.succeed({ name: 'ekanos', version }, version);\n}\n\nconst DOCS_FOOTER = `\\nDocs: ${SDK_DOCS_URL} (commands: ${docsPage('cli/commands')})`;\n\nfunction emitHelp(ctx: CliContext): number {\n ctx.emitClaudeCodeHint({\n tool: 'ekanos',\n commands: COMMANDS,\n usage: USAGE,\n exitCodes: EXIT_CODES,\n });\n return ctx.succeed(\n { usage: USAGE, commands: COMMANDS, docs: SDK_DOCS_URL },\n `${USAGE}${DOCS_FOOTER}`,\n );\n}\n\nfunction emitCommandHelp(ctx: CliContext, command: string): number {\n const specs = COMMAND_SPECS[command] ?? {};\n const flags = Object.keys(specs).map((name) => `--${name}`);\n const help = `Usage: ekanos ${command} [${flags.join('] [')}]`;\n ctx.emitClaudeCodeHint({ tool: 'ekanos', command, flags });\n return ctx.succeed({ command, flags }, help);\n}\n"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type TeamAppSetResult, type TeamIntegrationStatus, type TeamList, type TeamWebhookMintResult } from '@ekanos/integration-schema';
|
|
2
|
+
/**
|
|
3
|
+
* The CLI's client for a Source's teams and their integration setup — the
|
|
4
|
+
* team-level counterpart of `apps-api.ts`.
|
|
5
|
+
*
|
|
6
|
+
* GET /api/partner/teams?source=<slug> → TeamList
|
|
7
|
+
* GET /api/partner/teams/<team>/integrations/<product>?source=<slug> → TeamIntegrationStatus
|
|
8
|
+
* PUT …/integrations/<product>/app?source=<slug> { connectionId?, fields }
|
|
9
|
+
* → TeamAppSetResult
|
|
10
|
+
* POST …/integrations/<product>/webhooks/<id>?source=<slug> → TeamWebhookMintResult
|
|
11
|
+
*
|
|
12
|
+
* → 401 / 3xx refresh once and retry
|
|
13
|
+
* → 403 { code: "SOURCE_ADMIN_REQUIRED" | "MFA_REQUIRED" | "FORBIDDEN" }
|
|
14
|
+
* → 404 { code: "TEAM_NOT_FOUND" | "PRODUCT_NOT_ENTITLED" | "WEBHOOK_NOT_FOUND"
|
|
15
|
+
* | "CONNECTION_NOT_FOUND" | "SOURCE_NOT_FOUND" }
|
|
16
|
+
* → 409 { code: "NO_TEAM_APP" }
|
|
17
|
+
* → 400 { code: "INVALID_FIELDS" | "INVALID_REQUEST" }
|
|
18
|
+
*/
|
|
19
|
+
export type TeamsApiResult<T> = {
|
|
20
|
+
status: 'ok';
|
|
21
|
+
data: T;
|
|
22
|
+
} | {
|
|
23
|
+
status: 'unauthorized';
|
|
24
|
+
};
|
|
25
|
+
export declare function listTeams(host: string, accessToken: string, source: string): Promise<TeamsApiResult<TeamList>>;
|
|
26
|
+
export declare function showTeamIntegration(host: string, accessToken: string, target: {
|
|
27
|
+
source: string;
|
|
28
|
+
team: string;
|
|
29
|
+
product: string;
|
|
30
|
+
}): Promise<TeamsApiResult<TeamIntegrationStatus>>;
|
|
31
|
+
export declare function setTeamApp(host: string, accessToken: string, target: {
|
|
32
|
+
source: string;
|
|
33
|
+
team: string;
|
|
34
|
+
product: string;
|
|
35
|
+
}, body: {
|
|
36
|
+
connectionId?: string;
|
|
37
|
+
fields: Record<string, string>;
|
|
38
|
+
}): Promise<TeamsApiResult<TeamAppSetResult>>;
|
|
39
|
+
export declare function mintTeamWebhook(host: string, accessToken: string, target: {
|
|
40
|
+
source: string;
|
|
41
|
+
team: string;
|
|
42
|
+
product: string;
|
|
43
|
+
}, webhookId: string): Promise<TeamsApiResult<TeamWebhookMintResult>>;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { TeamAppSetResultSchema, TeamIntegrationStatusSchema, TeamListSchema, TeamWebhookMintResultSchema, } from '@ekanos/integration-schema';
|
|
2
|
+
import { request } from './auth/ekanos-api.js';
|
|
3
|
+
import { authRequiredError, forbiddenError, invalidStateError, networkError, notFoundError, usageError, validationError, } from './errors.js';
|
|
4
|
+
export function listTeams(host, accessToken, source) {
|
|
5
|
+
return call(host, accessToken, TeamListSchema, {
|
|
6
|
+
method: 'GET',
|
|
7
|
+
path: '/api/partner/teams',
|
|
8
|
+
source,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export function showTeamIntegration(host, accessToken, target) {
|
|
12
|
+
return call(host, accessToken, TeamIntegrationStatusSchema, {
|
|
13
|
+
method: 'GET',
|
|
14
|
+
path: integrationPath(target),
|
|
15
|
+
source: target.source,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
export function setTeamApp(host, accessToken, target, body) {
|
|
19
|
+
return call(host, accessToken, TeamAppSetResultSchema, {
|
|
20
|
+
method: 'PUT',
|
|
21
|
+
path: `${integrationPath(target)}/app`,
|
|
22
|
+
source: target.source,
|
|
23
|
+
body,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
export function mintTeamWebhook(host, accessToken, target, webhookId) {
|
|
27
|
+
return call(host, accessToken, TeamWebhookMintResultSchema, {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
path: `${integrationPath(target)}/webhooks/${encodeURIComponent(webhookId)}`,
|
|
30
|
+
source: target.source,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function integrationPath(target) {
|
|
34
|
+
return `/api/partner/teams/${encodeURIComponent(target.team)}/integrations/${encodeURIComponent(target.product)}`;
|
|
35
|
+
}
|
|
36
|
+
async function call(host, accessToken, schema, init) {
|
|
37
|
+
const response = await request(`${host}${init.path}?${new URLSearchParams({ source: init.source })}`, {
|
|
38
|
+
method: init.method,
|
|
39
|
+
headers: Object.assign({ accept: 'application/json', authorization: `Bearer ${accessToken}` }, (init.body === undefined
|
|
40
|
+
? {}
|
|
41
|
+
: { 'content-type': 'application/json' })),
|
|
42
|
+
body: init.body === undefined ? undefined : JSON.stringify(init.body),
|
|
43
|
+
});
|
|
44
|
+
if (response.status === 401 ||
|
|
45
|
+
(response.status >= 300 && response.status < 400)) {
|
|
46
|
+
return { status: 'unauthorized' };
|
|
47
|
+
}
|
|
48
|
+
const body = await readOptionalJson(response);
|
|
49
|
+
const code = typeof body.code === 'string' ? body.code : null;
|
|
50
|
+
const message = typeof body.error === 'string' && body.error ? body.error : null;
|
|
51
|
+
if (response.status === 403 && code === 'MFA_REQUIRED') {
|
|
52
|
+
throw authRequiredError(message !== null && message !== void 0 ? message : `${host} requires multi-factor authentication.`, 'Complete MFA in the browser, then run "ekanos login" again.');
|
|
53
|
+
}
|
|
54
|
+
if (response.status === 403) {
|
|
55
|
+
throw forbiddenError(message !== null && message !== void 0 ? message : 'Not permitted on this Source.', code === 'SOURCE_ADMIN_REQUIRED'
|
|
56
|
+
? "Only a Source admin manages its teams' integrations. Ask one to run it."
|
|
57
|
+
: 'Run "ekanos sources" to list the sources you hold a seat on.');
|
|
58
|
+
}
|
|
59
|
+
if (response.status === 404) {
|
|
60
|
+
throw notFoundError(message !== null && message !== void 0 ? message : 'Not found.', code === 'TEAM_NOT_FOUND'
|
|
61
|
+
? 'Run "ekanos teams list" to see the teams on this Source.'
|
|
62
|
+
: code === 'PRODUCT_NOT_ENTITLED'
|
|
63
|
+
? '"ekanos catalog" lists what this Source has; Ekanos enables a product for a Source.'
|
|
64
|
+
: code === 'WEBHOOK_NOT_FOUND' || code === 'CONNECTION_NOT_FOUND'
|
|
65
|
+
? 'Run "ekanos teams show <team> <product>" to see its team apps and webhooks.'
|
|
66
|
+
: 'Run "ekanos sources" to list the sources you hold a seat on.');
|
|
67
|
+
}
|
|
68
|
+
if (response.status === 409 && code === 'NO_TEAM_APP') {
|
|
69
|
+
throw invalidStateError(message !== null && message !== void 0 ? message : 'This integration takes no team app.', 'Teams connect through the Source\'s app: "ekanos apps show <product>".');
|
|
70
|
+
}
|
|
71
|
+
if (response.status === 400 && code === 'INVALID_FIELDS') {
|
|
72
|
+
throw validationError(message !== null && message !== void 0 ? message : 'The host refused the fields.', 'Run "ekanos teams app set <team> <product>" again.');
|
|
73
|
+
}
|
|
74
|
+
if (response.status === 400) {
|
|
75
|
+
throw usageError(message !== null && message !== void 0 ? message : 'The host refused the request.', 'See "ekanos teams --help".');
|
|
76
|
+
}
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw networkError(`${host}${init.path} responded ${response.status}.`, response.status >= 500
|
|
79
|
+
? 'The Ekanos deployment returned a server error. Retry shortly.'
|
|
80
|
+
: 'Confirm the host runs a build that includes the teams routes.');
|
|
81
|
+
}
|
|
82
|
+
const parsed = schema.safeParse(body.data);
|
|
83
|
+
if (!parsed.success) {
|
|
84
|
+
throw networkError(`${host}/api/partner/teams answered in a shape this CLI does not read.`, 'Run "ekanos upgrade" — the CLI and the host disagree on the teams contract.');
|
|
85
|
+
}
|
|
86
|
+
return { status: 'ok', data: parsed.data };
|
|
87
|
+
}
|
|
88
|
+
async function readOptionalJson(response) {
|
|
89
|
+
try {
|
|
90
|
+
const parsed = await response.json();
|
|
91
|
+
return typeof parsed === 'object' && parsed !== null
|
|
92
|
+
? parsed
|
|
93
|
+
: {};
|
|
94
|
+
}
|
|
95
|
+
catch (_a) {
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=teams-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teams-api.js","sourceRoot":"","sources":["../src/teams-api.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type TeamAppSetResult,\n TeamAppSetResultSchema,\n type TeamIntegrationStatus,\n TeamIntegrationStatusSchema,\n type TeamList,\n TeamListSchema,\n type TeamWebhookMintResult,\n TeamWebhookMintResultSchema,\n} from '@ekanos/integration-schema';\nimport type { z } from 'zod';\n\nimport { request } from './auth/ekanos-api';\nimport {\n authRequiredError,\n forbiddenError,\n invalidStateError,\n networkError,\n notFoundError,\n usageError,\n validationError,\n} from './errors';\n\n/**\n * The CLI's client for a Source's teams and their integration setup — the\n * team-level counterpart of `apps-api.ts`.\n *\n * GET /api/partner/teams?source=<slug> → TeamList\n * GET /api/partner/teams/<team>/integrations/<product>?source=<slug> → TeamIntegrationStatus\n * PUT …/integrations/<product>/app?source=<slug> { connectionId?, fields }\n * → TeamAppSetResult\n * POST …/integrations/<product>/webhooks/<id>?source=<slug> → TeamWebhookMintResult\n *\n * → 401 / 3xx refresh once and retry\n * → 403 { code: \"SOURCE_ADMIN_REQUIRED\" | \"MFA_REQUIRED\" | \"FORBIDDEN\" }\n * → 404 { code: \"TEAM_NOT_FOUND\" | \"PRODUCT_NOT_ENTITLED\" | \"WEBHOOK_NOT_FOUND\"\n * | \"CONNECTION_NOT_FOUND\" | \"SOURCE_NOT_FOUND\" }\n * → 409 { code: \"NO_TEAM_APP\" }\n * → 400 { code: \"INVALID_FIELDS\" | \"INVALID_REQUEST\" }\n */\n\nexport type TeamsApiResult<T> =\n | { status: 'ok'; data: T }\n | { status: 'unauthorized' };\n\nexport function listTeams(\n host: string,\n accessToken: string,\n source: string,\n): Promise<TeamsApiResult<TeamList>> {\n return call(host, accessToken, TeamListSchema, {\n method: 'GET',\n path: '/api/partner/teams',\n source,\n });\n}\n\nexport function showTeamIntegration(\n host: string,\n accessToken: string,\n target: { source: string; team: string; product: string },\n): Promise<TeamsApiResult<TeamIntegrationStatus>> {\n return call(host, accessToken, TeamIntegrationStatusSchema, {\n method: 'GET',\n path: integrationPath(target),\n source: target.source,\n });\n}\n\nexport function setTeamApp(\n host: string,\n accessToken: string,\n target: { source: string; team: string; product: string },\n body: { connectionId?: string; fields: Record<string, string> },\n): Promise<TeamsApiResult<TeamAppSetResult>> {\n return call(host, accessToken, TeamAppSetResultSchema, {\n method: 'PUT',\n path: `${integrationPath(target)}/app`,\n source: target.source,\n body,\n });\n}\n\nexport function mintTeamWebhook(\n host: string,\n accessToken: string,\n target: { source: string; team: string; product: string },\n webhookId: string,\n): Promise<TeamsApiResult<TeamWebhookMintResult>> {\n return call(host, accessToken, TeamWebhookMintResultSchema, {\n method: 'POST',\n path: `${integrationPath(target)}/webhooks/${encodeURIComponent(webhookId)}`,\n source: target.source,\n });\n}\n\nfunction integrationPath(target: { team: string; product: string }): string {\n return `/api/partner/teams/${encodeURIComponent(target.team)}/integrations/${encodeURIComponent(target.product)}`;\n}\n\nasync function call<T>(\n host: string,\n accessToken: string,\n schema: z.ZodType<T>,\n init: {\n method: 'GET' | 'PUT' | 'POST';\n path: string;\n source: string;\n body?: unknown;\n },\n): Promise<TeamsApiResult<T>> {\n const response = await request(\n `${host}${init.path}?${new URLSearchParams({ source: init.source })}`,\n {\n method: init.method,\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\n ...(init.body === undefined\n ? {}\n : { 'content-type': 'application/json' }),\n },\n body: init.body === undefined ? undefined : JSON.stringify(init.body),\n },\n );\n\n if (\n response.status === 401 ||\n (response.status >= 300 && response.status < 400)\n ) {\n return { status: 'unauthorized' };\n }\n\n const body = await readOptionalJson(response);\n const code = typeof body.code === 'string' ? body.code : null;\n const message =\n typeof body.error === 'string' && body.error ? body.error : null;\n\n if (response.status === 403 && code === 'MFA_REQUIRED') {\n throw authRequiredError(\n message ?? `${host} requires multi-factor authentication.`,\n 'Complete MFA in the browser, then run \"ekanos login\" again.',\n );\n }\n if (response.status === 403) {\n throw forbiddenError(\n message ?? 'Not permitted on this Source.',\n code === 'SOURCE_ADMIN_REQUIRED'\n ? \"Only a Source admin manages its teams' integrations. Ask one to run it.\"\n : 'Run \"ekanos sources\" to list the sources you hold a seat on.',\n );\n }\n if (response.status === 404) {\n throw notFoundError(\n message ?? 'Not found.',\n code === 'TEAM_NOT_FOUND'\n ? 'Run \"ekanos teams list\" to see the teams on this Source.'\n : code === 'PRODUCT_NOT_ENTITLED'\n ? '\"ekanos catalog\" lists what this Source has; Ekanos enables a product for a Source.'\n : code === 'WEBHOOK_NOT_FOUND' || code === 'CONNECTION_NOT_FOUND'\n ? 'Run \"ekanos teams show <team> <product>\" to see its team apps and webhooks.'\n : 'Run \"ekanos sources\" to list the sources you hold a seat on.',\n );\n }\n if (response.status === 409 && code === 'NO_TEAM_APP') {\n throw invalidStateError(\n message ?? 'This integration takes no team app.',\n 'Teams connect through the Source\\'s app: \"ekanos apps show <product>\".',\n );\n }\n if (response.status === 400 && code === 'INVALID_FIELDS') {\n throw validationError(\n message ?? 'The host refused the fields.',\n 'Run \"ekanos teams app set <team> <product>\" again.',\n );\n }\n if (response.status === 400) {\n throw usageError(\n message ?? 'The host refused the request.',\n 'See \"ekanos teams --help\".',\n );\n }\n if (!response.ok) {\n throw networkError(\n `${host}${init.path} responded ${response.status}.`,\n response.status >= 500\n ? 'The Ekanos deployment returned a server error. Retry shortly.'\n : 'Confirm the host runs a build that includes the teams routes.',\n );\n }\n\n const parsed = schema.safeParse(body.data);\n if (!parsed.success) {\n throw networkError(\n `${host}/api/partner/teams answered in a shape this CLI does not read.`,\n 'Run \"ekanos upgrade\" — the CLI and the host disagree on the teams contract.',\n );\n }\n return { status: 'ok', data: parsed.data };\n}\n\nasync function readOptionalJson(\n response: Response,\n): Promise<Record<string, unknown>> {\n try {\n const parsed: unknown = await response.json();\n return typeof parsed === 'object' && parsed !== null\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekanos/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The Ekanos partner toolchain CLI: scaffold, validate, and test an Ekanos integration against the published @ekanos packages. Agent-native — every verb speaks JSON with a stable exit-code taxonomy.",
|
|
6
6
|
"keywords": [
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@clack/prompts": "1.7.0",
|
|
38
|
-
"@ekanos/integration-schema": "0.
|
|
38
|
+
"@ekanos/integration-schema": "0.13.0",
|
|
39
39
|
"esbuild": "0.28.1",
|
|
40
40
|
"picocolors": "1.1.1"
|
|
41
41
|
},
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
"prepare": "node scripts/setup-hooks.mjs"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@ekanos/sdk": "^0.
|
|
16
|
-
"@ekanos/ui": "^0.
|
|
15
|
+
"@ekanos/sdk": "^0.13.0",
|
|
16
|
+
"@ekanos/ui": "^0.13.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@ekanos/cli": "^0.
|
|
20
|
-
"@ekanos/harness": "^0.7.
|
|
19
|
+
"@ekanos/cli": "^0.10.0",
|
|
20
|
+
"@ekanos/harness": "^0.7.4",
|
|
21
21
|
"@hookform/resolvers": "^5.2.2",
|
|
22
22
|
"@tailwindcss/postcss": "^4.3.3",
|
|
23
23
|
"@tanstack/react-query": "^5.101.4",
|
|
@@ -60,12 +60,17 @@ every `hint`. Look things up in the `ekanos-sdk-docs` MCP server
|
|
|
60
60
|
Tell the user this map in a few lines before the interview. Don't hide a gap
|
|
61
61
|
behind a plan that assumes it away.
|
|
62
62
|
|
|
63
|
-
## Setting up an
|
|
64
|
-
|
|
65
|
-
When
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
## Setting up an integration, concierge-style
|
|
64
|
+
|
|
65
|
+
When something the agent needs is not set up yet, offer to set it up with
|
|
66
|
+
the user now: "Zendesk isn't connected for any team yet; want me to walk you
|
|
67
|
+
through it? It takes about ten minutes." Then follow
|
|
68
|
+
`references/set-up-an-integration.md`. It covers where the connection should
|
|
69
|
+
live (each team through the Source's app, each team with its own app, or one
|
|
70
|
+
for the whole Source), the integration's own steps, one per message, and
|
|
71
|
+
checking each one with `ekanos teams show` before moving on. For the
|
|
72
|
+
Source's app itself, `references/app-setup.md` has the console walk-through.
|
|
73
|
+
Secrets are only ever entered at the user's terminal or in the app.
|
|
69
74
|
|
|
70
75
|
## 2. The interview
|
|
71
76
|
|
|
@@ -84,10 +89,12 @@ in parentheses are the recommendation when the user has no preference.
|
|
|
84
89
|
4. **What waits for a person.** Reads run on their own, changes wait for
|
|
85
90
|
approval — and any per-tool exceptions (a public reply to a customer
|
|
86
91
|
almost always waits). See `references/agent-json.md` → `policy`.
|
|
87
|
-
5. **Whose connections.** For each product and MCP server
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
92
|
+
5. **Whose connections.** For each product and MCP server, where the
|
|
93
|
+
connection lives (`references/set-up-an-integration.md` § 2): each team
|
|
94
|
+
connects its own, and it is `required` (the agent waits at "needs setup"
|
|
95
|
+
until the team connects it) or `optional` (its tools appear once
|
|
96
|
+
connected). One connection shared by every team is not available yet;
|
|
97
|
+
say so if they ask for it. Then offer to set up any that are not ready.
|
|
91
98
|
6. **What teams may add** — their own MCP servers, their own skills, more of
|
|
92
99
|
their own integrations, or narrowing (removing tools, more approvals). All
|
|
93
100
|
off unless there is a reason. Give one concrete example per knob.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Setting up an integration, concierge-style
|
|
2
|
+
|
|
3
|
+
The user wants an agent to use something (Zendesk, Linear, Slack…) and it is
|
|
4
|
+
not ready yet. Your job is to get it ready **with** them, the way a good
|
|
5
|
+
concierge would: you already know the way, you say what happens next and
|
|
6
|
+
why, you hand them exactly what to type or paste, and you check that each
|
|
7
|
+
step worked before moving on. They should never have to read a manual or
|
|
8
|
+
guess.
|
|
9
|
+
|
|
10
|
+
## 1. Find out where it stands (before saying anything)
|
|
11
|
+
|
|
12
|
+
1. `npx @ekanos/cli catalog <product> --source <slug> --json`. Its
|
|
13
|
+
`setupSteps` are the integration's own steps, already filled in with this
|
|
14
|
+
Source's exact callback URLs and scopes. Each step has an `actor`:
|
|
15
|
+
`ekanos` (an Ekanos super admin), `source_admin` (the user, if they hold
|
|
16
|
+
that seat), or `team`.
|
|
17
|
+
2. Which teams will use it: `npx @ekanos/cli teams list --source <slug> --json`.
|
|
18
|
+
3. For a team: `npx @ekanos/cli teams show <team> <product> --source <slug> --json`
|
|
19
|
+
shows what that team has already done (app fields entered, webhook URL
|
|
20
|
+
minted, connected, who connected and when) and the link to the team's page
|
|
21
|
+
in the app.
|
|
22
|
+
|
|
23
|
+
Then tell the user, in two or three lines, what is done, what is left, and
|
|
24
|
+
who does each remaining step. If a step needs Ekanos (the product is not
|
|
25
|
+
enabled for the Source), say so and stop there for that product; nobody else
|
|
26
|
+
can do it.
|
|
27
|
+
|
|
28
|
+
## 2. Decide where the connection lives
|
|
29
|
+
|
|
30
|
+
Explain the choice in plain words, recommend one, and let them pick. There
|
|
31
|
+
are three places, and **the team account is the default owner** of an
|
|
32
|
+
integration: its members, its data and its connections.
|
|
33
|
+
|
|
34
|
+
| Where | What it means | Who sets it up | Typical for |
|
|
35
|
+
|---|---|---|---|
|
|
36
|
+
| **Each team, through the Source's app** | One app, created once for the whole Source under the Source's own name. Each team then signs in through it. | The Source admin creates the app (`references/app-setup.md`, `ekanos apps set`); each team connects | Linear, Slack: products where every team is a separate workspace |
|
|
37
|
+
| **Each team, with its own app** | Each team creates its own app in the provider and enters its credentials | Each team, or the Source admin on the team's behalf with `ekanos teams` (below) | Zendesk: each bank or business has its own Zendesk |
|
|
38
|
+
| **One connection for the whole Source** | Every team's provided agent uses the Source's single connection, limited to what is routed to that team | The Source admin, once | One shared Zendesk for many teams. **Not available yet**: say so, and suggest a connection per team for now |
|
|
39
|
+
|
|
40
|
+
The agent records the choice in `agent.json` → `connections`:
|
|
41
|
+
`{ "zendesk": { "owner": "team", "team": "required" } }` means each team
|
|
42
|
+
uses its own Zendesk and the agent waits at "needs setup" until it is
|
|
43
|
+
connected (`references/agent-json.md`).
|
|
44
|
+
|
|
45
|
+
## 3. Walk it, one step per message
|
|
46
|
+
|
|
47
|
+
- **Say why before what.** "Zendesk needs to know where to send new tickets,
|
|
48
|
+
so we'll give it a webhook address from Ekanos." One sentence, then the step.
|
|
49
|
+
- **Exact values only.** Take URLs, field names and console paths from the
|
|
50
|
+
catalog or `teams show`, never from memory. Paste URLs exactly; providers
|
|
51
|
+
match them byte for byte.
|
|
52
|
+
- **Use the provider's words** for its screens ("Admin Center › Apps and
|
|
53
|
+
integrations › Webhooks"), and Ekanos's words for Ekanos.
|
|
54
|
+
- **Wait** for "done" or a question after each step. Then **check**: re-run
|
|
55
|
+
`teams show` (or `catalog`) and say what changed ("Ekanos sees the client ID
|
|
56
|
+
and secret now"). If the check disagrees with what they said, look at the
|
|
57
|
+
difference with them; don't move on.
|
|
58
|
+
- **Secrets never pass through the chat.** Client secrets and signing
|
|
59
|
+
secrets go in at the user's own terminal (`ekanos apps set`,
|
|
60
|
+
`ekanos teams app set`, with hidden input) or in the team's page in the app.
|
|
61
|
+
If they paste one into the chat anyway, tell them to rotate it in the
|
|
62
|
+
provider's console and enter the new one at the terminal.
|
|
63
|
+
- **Signing in is a person's click.** Connecting (the provider's consent
|
|
64
|
+
screen) happens in the browser, by someone on the team, from the link in
|
|
65
|
+
`teams show`. Tell them who should click it: the account the integration
|
|
66
|
+
acts as. A dedicated seat such as "Support agent" makes its replies easy
|
|
67
|
+
to recognise.
|
|
68
|
+
- **Close each product out**: one line on what now works ("New tickets in
|
|
69
|
+
Acme's Zendesk will start the support agent"), then move on to the next
|
|
70
|
+
product or back to the agent.
|
|
71
|
+
|
|
72
|
+
## 4. A team's own app, from the terminal
|
|
73
|
+
|
|
74
|
+
When the user is a Source admin setting things up for a team (common in a
|
|
75
|
+
pilot), the team-side steps can be done from the CLI:
|
|
76
|
+
|
|
77
|
+
1. `npx @ekanos/cli teams show <team> <product> --source <slug>`: the
|
|
78
|
+
callback URL to register and the integration's steps.
|
|
79
|
+
2. They create the app in the provider's console, step by step as above.
|
|
80
|
+
3. **In their own terminal:**
|
|
81
|
+
`npx @ekanos/cli teams app set <team> <product> --source <slug>`. It asks
|
|
82
|
+
for each field and hides the secret ones. It refuses to run inside Claude;
|
|
83
|
+
that is deliberate.
|
|
84
|
+
4. If the integration takes webhooks:
|
|
85
|
+
`npx @ekanos/cli teams webhook <team> <product> --source <slug>` prints the
|
|
86
|
+
team's webhook URL **once**. Have them paste it into the provider at that
|
|
87
|
+
moment. Running it again makes a new URL and the old one stops working, so
|
|
88
|
+
it asks for `--yes` when one already exists.
|
|
89
|
+
5. Someone on the team opens the link from `teams show` and clicks Connect.
|
|
90
|
+
6. `teams show` again: every step ✓.
|
|
91
|
+
|
|
92
|
+
## Worked example: Zendesk for one team
|
|
93
|
+
|
|
94
|
+
The steps below are what Zendesk's own setup says, in order. Take the URLs
|
|
95
|
+
from `teams show zendesk`.
|
|
96
|
+
|
|
97
|
+
1. **OAuth client.** Admin Center › Apps and integrations › APIs › OAuth
|
|
98
|
+
Clients › Add OAuth client. Client kind **Confidential**, Redirect URL =
|
|
99
|
+
the callback URL. Save; copy the Identifier and the Secret (shown once).
|
|
100
|
+
2. **Webhook address.** `ekanos teams webhook <team> zendesk`, then in Admin
|
|
101
|
+
Center › Apps and integrations › Webhooks › Create webhook › Trigger or
|
|
102
|
+
automation: that URL, POST, JSON, no authentication. Open it and Reveal
|
|
103
|
+
secret.
|
|
104
|
+
3. **Credentials into Ekanos.** `ekanos teams app set <team> zendesk` in
|
|
105
|
+
their terminal: subdomain, client ID, client secret, webhook signing
|
|
106
|
+
secret.
|
|
107
|
+
4. **Connect.** Someone on the team opens the link and connects, as the
|
|
108
|
+
Zendesk agent the integration should reply as.
|
|
109
|
+
5. **Zendesk triggers.** Objects and rules › Triggers: one for *Ticket is
|
|
110
|
+
Created* and one for a customer's public reply, each notifying the webhook
|
|
111
|
+
with the body from the steps. `"reply_as": "internal"` makes the agent's
|
|
112
|
+
answer an internal note a person reviews before the customer sees it.
|
|
113
|
+
Recommend that until the team trusts the agent; `"public"` lets it answer
|
|
114
|
+
the customer directly.
|
|
115
|
+
6. **The agent's trigger.** In the app, on the agent: *A ticket is created*
|
|
116
|
+
(and *A customer replies*), with any filters.
|
|
117
|
+
7. **Try it.** Open a test ticket as a customer and watch: the agent reads
|
|
118
|
+
the ticket, looks things up, and answers on it. If it wants to change the
|
|
119
|
+
ticket (status, priority, assignee), it leaves an internal note saying
|
|
120
|
+
what it asked for, and someone approves it in Ekanos.
|
|
121
|
+
|
|
122
|
+
## When something goes wrong
|
|
123
|
+
|
|
124
|
+
- **Redirect mismatch on Connect:** the callback URL in the provider differs
|
|
125
|
+
from `teams show`'s, often by a trailing slash or a custom domain. Fix it in
|
|
126
|
+
the provider.
|
|
127
|
+
- **Tickets don't start the agent:** `teams show` should list a live webhook
|
|
128
|
+
that has been used recently. If it has never been used, the provider is not
|
|
129
|
+
calling it: check the Zendesk trigger's conditions and that it notifies the
|
|
130
|
+
right webhook. If it has been used but nothing ran, check that the agent has
|
|
131
|
+
a trigger for that event and that the team connected the product.
|
|
132
|
+
- **A command says it needs a terminal:** it was run inside Claude. Ask them
|
|
133
|
+
to run it in a terminal tab themselves.
|
|
@@ -33,7 +33,8 @@ Guide: https://sdk.ekanos.dev/guides/linear-agent
|
|
|
33
33
|
**Zendesk** (`zendesk.ticket_created`, `zendesk.comment_added`; filters such
|
|
34
34
|
as brand, group, tags, organization) — when present in the catalog, each team
|
|
35
35
|
connects its own Zendesk (subdomain plus an OAuth client it creates in
|
|
36
|
-
Zendesk) and adds a Zendesk webhook pointing at its Ekanos endpoint
|
|
36
|
+
Zendesk) and adds a Zendesk webhook pointing at its Ekanos endpoint
|
|
37
|
+
(walk-through: `set-up-an-integration.md`, worked example). The agent
|
|
37
38
|
answers on the ticket as a public reply or an internal note — recommend
|
|
38
39
|
internal notes, or approval on public replies, until the team trusts it.
|
|
39
40
|
|
|
@@ -62,8 +62,12 @@ Ask them in this order; each catalog product reports where it stands
|
|
|
62
62
|
is an Ekanos super admin's step (`setupSteps` names it with actor
|
|
63
63
|
`ekanos`) — not something a Source admin or the CLI can do.
|
|
64
64
|
2. **Connected by a team** — a team (or a person, for per-person
|
|
65
|
-
integrations) signed in to the third party.
|
|
66
|
-
|
|
65
|
+
integrations) signed in to the third party. The team account owns the
|
|
66
|
+
connection: two teams using Linear each connect their own. A Source admin
|
|
67
|
+
can do a team's setup for it from the terminal (`ekanos teams show`,
|
|
68
|
+
`ekanos teams app set`, `ekanos teams webhook`), but signing in is a click
|
|
69
|
+
by someone on the team. One connection for the whole Source, shared by
|
|
70
|
+
every team's agents, is not available yet.
|
|
67
71
|
3. **Set up by the team** where needed — some integrations have each team
|
|
68
72
|
bring its own app (a team-provided app: client id, secret, webhook signing
|
|
69
73
|
secret entered in Ekanos). Those secrets are typed into the app, never
|
|
@@ -107,4 +111,5 @@ The `build-agent` skill walks through choosing between these and building one.
|
|
|
107
111
|
| Building or updating an integration | A developer, with `ekanos validate` / `ekanos publish` |
|
|
108
112
|
| Agents provided for every team | A Source admin, with `ekanos agents` |
|
|
109
113
|
| An integration's app for the whole Source (client ID and secret) | A Source admin, with `ekanos apps set` at their own terminal |
|
|
114
|
+
| A team's own app for an integration (client ID, secrets, webhook URL) | The team in the app, or a Source admin with `ekanos teams` |
|
|
110
115
|
| Connecting an integration; turning on a provided agent; its triggers | Each team, in the app |
|