@ekanos/cli 0.12.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -8
- package/dist/agents-api.d.ts +11 -2
- package/dist/agents-api.js +39 -3
- package/dist/agents-api.js.map +1 -1
- package/dist/apps-api.d.ts +3 -2
- package/dist/apps-api.js +3 -7
- package/dist/apps-api.js.map +1 -1
- package/dist/auth/ekanos-api.d.ts +16 -2
- package/dist/auth/ekanos-api.js +101 -15
- package/dist/auth/ekanos-api.js.map +1 -1
- package/dist/auth/host-trust.d.ts +46 -0
- package/dist/auth/host-trust.js +79 -0
- package/dist/auth/host-trust.js.map +1 -0
- package/dist/auth/session.d.ts +10 -1
- package/dist/auth/session.js +28 -28
- package/dist/auth/session.js.map +1 -1
- package/dist/commands/agents-edit.d.ts +6 -2
- package/dist/commands/agents-edit.js +57 -26
- package/dist/commands/agents-edit.js.map +1 -1
- package/dist/commands/agents.d.ts +13 -2
- package/dist/commands/agents.js +148 -36
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/apps.d.ts +15 -4
- package/dist/commands/apps.js +133 -51
- package/dist/commands/apps.js.map +1 -1
- package/dist/commands/catalog.js +7 -6
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/knowledge.d.ts +56 -0
- package/dist/commands/knowledge.js +456 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/login.d.ts +4 -0
- package/dist/commands/login.js +30 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.d.ts +9 -1
- package/dist/commands/logout.js +56 -7
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/publish.js +3 -3
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/reviewers.d.ts +2 -0
- package/dist/commands/reviewers.js +7 -3
- package/dist/commands/reviewers.js.map +1 -1
- package/dist/commands/routes.d.ts +2 -0
- package/dist/commands/routes.js +42 -9
- package/dist/commands/routes.js.map +1 -1
- package/dist/commands/runs.d.ts +13 -2
- package/dist/commands/runs.js +96 -15
- package/dist/commands/runs.js.map +1 -1
- package/dist/commands/scope-notice.d.ts +21 -0
- package/dist/commands/scope-notice.js +35 -0
- package/dist/commands/scope-notice.js.map +1 -0
- package/dist/commands/skills.d.ts +7 -1
- package/dist/commands/skills.js +80 -24
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/sources.d.ts +4 -0
- package/dist/commands/sources.js +233 -5
- package/dist/commands/sources.js.map +1 -1
- package/dist/commands/status.js +5 -4
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/team-agent-readiness.d.ts +59 -0
- package/dist/commands/team-agent-readiness.js +341 -0
- package/dist/commands/team-agent-readiness.js.map +1 -0
- package/dist/commands/team-agents-agents.d.ts +24 -3
- package/dist/commands/team-agents-agents.js +336 -35
- package/dist/commands/team-agents-agents.js.map +1 -1
- package/dist/commands/team-agents-mcp.js +32 -3
- package/dist/commands/team-agents-mcp.js.map +1 -1
- package/dist/commands/team-agents-triggers.js +230 -54
- package/dist/commands/team-agents-triggers.js.map +1 -1
- package/dist/commands/team-agents.d.ts +3 -1
- package/dist/commands/team-agents.js +27 -7
- package/dist/commands/team-agents.js.map +1 -1
- package/dist/commands/teams-channel-install.d.ts +24 -0
- package/dist/commands/teams-channel-install.js +94 -0
- package/dist/commands/teams-channel-install.js.map +1 -0
- package/dist/commands/teams-connect.d.ts +66 -0
- package/dist/commands/teams-connect.js +397 -0
- package/dist/commands/teams-connect.js.map +1 -0
- package/dist/commands/teams-events.d.ts +12 -0
- package/dist/commands/teams-events.js +138 -0
- package/dist/commands/teams-events.js.map +1 -0
- package/dist/commands/teams-onboard.d.ts +16 -5
- package/dist/commands/teams-onboard.js +61 -36
- package/dist/commands/teams-onboard.js.map +1 -1
- package/dist/commands/teams-overview.d.ts +11 -0
- package/dist/commands/teams-overview.js +160 -0
- package/dist/commands/teams-overview.js.map +1 -0
- package/dist/commands/teams-scope.d.ts +10 -0
- package/dist/commands/teams-scope.js +109 -0
- package/dist/commands/teams-scope.js.map +1 -0
- package/dist/commands/teams.d.ts +64 -7
- package/dist/commands/teams.js +254 -60
- package/dist/commands/teams.js.map +1 -1
- package/dist/commands/upgrade.js +12 -2
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/commands/use.js +2 -2
- package/dist/commands/use.js.map +1 -1
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/connections-api.js +1 -1
- package/dist/connections-api.js.map +1 -1
- package/dist/delegate.js +11 -4
- package/dist/delegate.js.map +1 -1
- package/dist/harness-scaffold.js +12 -0
- package/dist/harness-scaffold.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +724 -152
- package/dist/index.js.map +1 -1
- package/dist/knowledge-api.d.ts +175 -0
- package/dist/knowledge-api.js +169 -0
- package/dist/knowledge-api.js.map +1 -0
- package/dist/open-url.d.ts +16 -0
- package/dist/open-url.js +73 -0
- package/dist/open-url.js.map +1 -0
- package/dist/parse-argv.d.ts +2 -0
- package/dist/parse-argv.js.map +1 -1
- package/dist/publish-api.d.ts +2 -1
- package/dist/publish-api.js +4 -3
- package/dist/publish-api.js.map +1 -1
- package/dist/pull-write.d.ts +27 -0
- package/dist/pull-write.js +60 -0
- package/dist/pull-write.js.map +1 -0
- package/dist/routes-api.d.ts +3 -0
- package/dist/routes-api.js +9 -4
- package/dist/routes-api.js.map +1 -1
- package/dist/runs-api.d.ts +7 -1
- package/dist/runs-api.js +21 -6
- package/dist/runs-api.js.map +1 -1
- package/dist/safe-fs.d.ts +2 -0
- package/dist/safe-fs.js +12 -6
- package/dist/safe-fs.js.map +1 -1
- package/dist/secret-file.d.ts +17 -0
- package/dist/secret-file.js +69 -0
- package/dist/secret-file.js.map +1 -0
- package/dist/skills-api.d.ts +10 -2
- package/dist/skills-api.js +37 -2
- package/dist/skills-api.js.map +1 -1
- package/dist/source-products-api.d.ts +63 -0
- package/dist/source-products-api.js +123 -0
- package/dist/source-products-api.js.map +1 -0
- package/dist/source-target.js +2 -1
- package/dist/source-target.js.map +1 -1
- package/dist/sources-api.d.ts +2 -1
- package/dist/sources-api.js +3 -2
- package/dist/sources-api.js.map +1 -1
- package/dist/team-connect-api.d.ts +286 -0
- package/dist/team-connect-api.js +107 -0
- package/dist/team-connect-api.js.map +1 -0
- package/dist/teams-api.d.ts +42 -1
- package/dist/teams-api.js +98 -20
- package/dist/teams-api.js.map +1 -1
- package/dist/toolchain-api.d.ts +9 -0
- package/dist/toolchain-api.js +17 -2
- package/dist/toolchain-api.js.map +1 -1
- package/dist/toolchain-resolve.d.ts +3 -0
- package/dist/toolchain-resolve.js +15 -1
- package/dist/toolchain-resolve.js.map +1 -1
- package/dist/toolchain.d.ts +6 -0
- package/dist/toolchain.js +19 -0
- package/dist/toolchain.js.map +1 -1
- package/dist/update-notice.js +2 -1
- package/dist/update-notice.js.map +1 -1
- package/package.json +2 -2
- package/templates/AGENTS.md.tmpl +6 -1
- package/templates/claude-skill.md.tmpl +5 -0
- package/templates/package.json.tmpl +4 -4
- package/templates/skills/build-agent/SKILL.md +115 -16
- package/templates/skills/build-agent/references/agent-json.md +26 -8
- package/templates/skills/build-agent/references/app-setup.md +2 -1
- package/templates/skills/build-agent/references/rollout.md +27 -4
- package/templates/skills/build-agent/references/set-up-an-integration.md +74 -15
- package/templates/skills/build-agent/references/skills.md +20 -1
- package/templates/skills/build-agent/references/triggers.md +84 -20
- package/templates/skills/build-integration/references/widget-design.md +3 -3
- package/templates/skills/ekanos-platform/SKILL.md +70 -17
- package/templates/skills/polish-widgets/references/patterns.md +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-notice.js","sourceRoot":"","sources":["../src/update-notice.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { CredentialStore, resolveEkanosHome } from './auth/credential-store';\nimport { cliVersion, compareVersions } from './compatibility';\nimport type { CliContext } from './context';\nimport type { ToolchainVersions } from './toolchain';\nimport { resolveToolchainTarget } from './toolchain-resolve';\n\n/**\n * The proactive, NEVER-mutating \"your CLI is behind\" notice. Runs on\n * `validate`, `dev`, `publish` and `status` — the verbs a partner (or an\n * agent acting for one) runs constantly — so staleness is discovered on the\n * next ordinary command instead of only when something breaks.\n *\n * The one rule that governs every line of this file: this check must be\n * INVISIBLE on failure. A partner offline, a flaky registry, a host that\n * predates this endpoint — none of that may add meaningful latency, change a\n * verb's exit code, or so much as touch the JSON envelope's `ok` field.\n * Every failure path below resolves to `null` (\"say nothing\"), never a throw.\n */\n\nconst CACHE_FILENAME = 'update-check.json';\nconst CACHE_DIR_MODE = 0o700;\nconst CACHE_FILE_MODE = 0o600;\nconst TTL_MS = 24 * 60 * 60 * 1000;\n/** The hard ceiling on this check's total network time, whatever else happens. */\nconst DEADLINE_MS = 1500;\n\nexport interface ToolchainNoticeData {\n status: 'behind' | 'unsupported';\n current: { cli: string };\n target: ToolchainVersions;\n upgradeCommand: 'ekanos upgrade';\n}\n\ninterface CacheFile {\n checkedAt: number;\n target: ToolchainVersions;\n minimum?: ToolchainVersions;\n source: 'host' | 'npm';\n}\n\nfunction cachePath(env: Record<string, string | undefined>): string {\n return path.join(resolveEkanosHome(env), '.ekanos', CACHE_FILENAME);\n}\n\nfunction readCache(env: Record<string, string | undefined>): CacheFile | null {\n try {\n const raw = fs.readFileSync(cachePath(env), 'utf8');\n const parsed: unknown = JSON.parse(raw);\n return isCacheFile(parsed) ? parsed : null;\n } catch {\n // Missing, unreadable, or corrupt — all read as \"no cache\", never an\n // error. This check is a courtesy, not a source of truth.\n return null;\n }\n}\n\nfunction writeCache(\n env: Record<string, string | undefined>,\n file: CacheFile,\n): void {\n const target = cachePath(env);\n try {\n fs.mkdirSync(path.dirname(target), {\n recursive: true,\n mode: CACHE_DIR_MODE,\n });\n const tmp = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(file), { mode: CACHE_FILE_MODE });\n fs.renameSync(tmp, target);\n } catch {\n // Best-effort. The next run simply re-checks — this is a cache, not a\n // durable record.\n }\n}\n\nfunction isCacheFile(value: unknown): value is CacheFile {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as Partial<CacheFile>;\n return (\n typeof candidate.checkedAt === 'number' &&\n typeof candidate.target === 'object' &&\n candidate.target !== null &&\n (candidate.source === 'host' || candidate.source === 'npm')\n );\n}\n\n/**\n * The one host/session this check will ever try, resolved WITHOUT any of the\n * throwing machinery in `auth/session.ts` — `resolveHost` is built to demand\n * an answer (a usage error naming the ambiguity) because a real command needs\n * one; this check needs an answer only if one is obvious, and silence\n * otherwise. Exactly one stored host is \"obvious\"; zero or several are not,\n * and this never prompts or guesses to resolve that ambiguity — it just\n * falls back to the npm registry, same as no session at all.\n */\nfunction bestEffortHostSession(\n env: Record<string, string | undefined>,\n): { host: string; session: { accessToken: string } } | null {\n try {\n const store = new CredentialStore({ home: resolveEkanosHome(env) });\n const hosts = store.hosts();\n if (hosts.length !== 1) return null;\n const session = store.read(hosts[0]!);\n return session ? { host: hosts[0]!, session } : null;\n } catch {\n return null;\n }\n}\n\nfunction withDeadline<T>(promise: Promise<T>, ms: number): Promise<T | null> {\n return new Promise((resolve) => {\n let settled = false;\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true;\n resolve(null);\n }\n }, ms);\n // A rejection is a failure like any other here — resolve to null rather\n // than letting it become an unhandled rejection.\n promise.then(\n (value) => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n resolve(value);\n }\n },\n () => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n resolve(null);\n }\n },\n );\n });\n}\n\nexport interface CheckForUpdateParams {\n env: Record<string, string | undefined>;\n /** An already-resolved Ekanos host/session, when the caller has one handy. */\n host?: string;\n session?: { accessToken: string } | null;\n}\n\n/**\n * Resolve whether this CLI is behind, respecting the 24h cache and the hard\n * `EKANOS_NO_UPDATE_CHECK` opt-out. Returns null whenever there is nothing to\n * say — current, unresolvable, or any failure along the way.\n */\nexport async function checkForUpdate(\n params: CheckForUpdateParams,\n): Promise<ToolchainNoticeData | null> {\n if (params.env.EKANOS_NO_UPDATE_CHECK) return null;\n\n const cached = readCache(params.env);\n const now = Date.now();\n\n let target: ToolchainVersions;\n let minimum: ToolchainVersions | undefined;\n\n if (cached && now - cached.checkedAt < TTL_MS) {\n target = cached.target;\n minimum = cached.minimum;\n } else {\n const auth =\n params.host && params.session\n ? { host: params.host, session: params.session }\n : bestEffortHostSession(params.env);\n\n const resolution = await withDeadline(\n resolveToolchainTarget({\n host: auth?.host,\n session: auth?.session,\n timeoutMs: DEADLINE_MS,\n }),\n DEADLINE_MS,\n );\n\n if (!resolution || resolution.status !== 'ok') return null;\n\n target = resolution.target;\n minimum = resolution.source === 'host' ? resolution.minimum : undefined;\n\n writeCache(params.env, {\n checkedAt: now,\n target,\n minimum,\n source: resolution.source,\n });\n }\n\n const current = cliVersion();\n if (compareVersions(current, target.cli) >= 0) return null;\n\n const status =\n minimum && compareVersions(current, minimum.cli) < 0\n ? 'unsupported'\n : 'behind';\n\n return {\n status,\n current: { cli: current },\n target,\n upgradeCommand: 'ekanos upgrade',\n };\n}\n\n/**\n * Surface a notice, if any. Human mode gets one stderr line; every mode gets\n * the `claude-code-hint` stderr line so an agent parsing stderr for that\n * marker sees it regardless of `--json`. NEVER writes to stdout — the JSON\n * envelope's caller is responsible for embedding `notice` under a `toolchain`\n * key in its own `data`, per the envelope docs in `context.ts`.\n */\nexport function announceUpdate(\n ctx: CliContext,\n notice: ToolchainNoticeData | null,\n): void {\n if (!notice) return;\n\n if (!ctx.jsonMode) {\n ctx.warn(\n notice.status === 'unsupported'\n ? `ekanos: @ekanos/cli ${notice.current.cli} is below the version ` +\n `this host requires (${notice.target.cli}). Run \"ekanos upgrade\".`\n : `ekanos: a newer @ekanos/cli is available (${notice.current.cli} ` +\n `→ ${notice.target.cli}). Run \"ekanos upgrade\".`,\n );\n }\n\n ctx.emitClaudeCodeHint({ tool: 'ekanos', toolchain: notice });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"update-notice.js","sourceRoot":"","sources":["../src/update-notice.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { CredentialStore, resolveEkanosHome } from './auth/credential-store';\nimport { isPlaintextRemoteHost } from './auth/host-trust';\nimport { cliVersion, compareVersions } from './compatibility';\nimport type { CliContext } from './context';\nimport type { ToolchainVersions } from './toolchain';\nimport { resolveToolchainTarget } from './toolchain-resolve';\n\n/**\n * The proactive, NEVER-mutating \"your CLI is behind\" notice. Runs on\n * `validate`, `dev`, `publish` and `status` — the verbs a partner (or an\n * agent acting for one) runs constantly — so staleness is discovered on the\n * next ordinary command instead of only when something breaks.\n *\n * The one rule that governs every line of this file: this check must be\n * INVISIBLE on failure. A partner offline, a flaky registry, a host that\n * predates this endpoint — none of that may add meaningful latency, change a\n * verb's exit code, or so much as touch the JSON envelope's `ok` field.\n * Every failure path below resolves to `null` (\"say nothing\"), never a throw.\n */\n\nconst CACHE_FILENAME = 'update-check.json';\nconst CACHE_DIR_MODE = 0o700;\nconst CACHE_FILE_MODE = 0o600;\nconst TTL_MS = 24 * 60 * 60 * 1000;\n/** The hard ceiling on this check's total network time, whatever else happens. */\nconst DEADLINE_MS = 1500;\n\nexport interface ToolchainNoticeData {\n status: 'behind' | 'unsupported';\n current: { cli: string };\n target: ToolchainVersions;\n upgradeCommand: 'ekanos upgrade';\n}\n\ninterface CacheFile {\n checkedAt: number;\n target: ToolchainVersions;\n minimum?: ToolchainVersions;\n source: 'host' | 'npm';\n}\n\nfunction cachePath(env: Record<string, string | undefined>): string {\n return path.join(resolveEkanosHome(env), '.ekanos', CACHE_FILENAME);\n}\n\nfunction readCache(env: Record<string, string | undefined>): CacheFile | null {\n try {\n const raw = fs.readFileSync(cachePath(env), 'utf8');\n const parsed: unknown = JSON.parse(raw);\n return isCacheFile(parsed) ? parsed : null;\n } catch {\n // Missing, unreadable, or corrupt — all read as \"no cache\", never an\n // error. This check is a courtesy, not a source of truth.\n return null;\n }\n}\n\nfunction writeCache(\n env: Record<string, string | undefined>,\n file: CacheFile,\n): void {\n const target = cachePath(env);\n try {\n fs.mkdirSync(path.dirname(target), {\n recursive: true,\n mode: CACHE_DIR_MODE,\n });\n const tmp = `${target}.${process.pid}.tmp`;\n fs.writeFileSync(tmp, JSON.stringify(file), { mode: CACHE_FILE_MODE });\n fs.renameSync(tmp, target);\n } catch {\n // Best-effort. The next run simply re-checks — this is a cache, not a\n // durable record.\n }\n}\n\nfunction isCacheFile(value: unknown): value is CacheFile {\n if (typeof value !== 'object' || value === null) return false;\n const candidate = value as Partial<CacheFile>;\n return (\n typeof candidate.checkedAt === 'number' &&\n typeof candidate.target === 'object' &&\n candidate.target !== null &&\n (candidate.source === 'host' || candidate.source === 'npm')\n );\n}\n\n/**\n * The one host/session this check will ever try, resolved WITHOUT any of the\n * throwing machinery in `auth/session.ts` — `resolveHost` is built to demand\n * an answer (a usage error naming the ambiguity) because a real command needs\n * one; this check needs an answer only if one is obvious, and silence\n * otherwise. Exactly one stored host is \"obvious\"; zero or several are not,\n * and this never prompts or guesses to resolve that ambiguity — it just\n * falls back to the npm registry, same as no session at all.\n */\nfunction bestEffortHostSession(\n env: Record<string, string | undefined>,\n): { host: string; session: { accessToken: string } } | null {\n try {\n const store = new CredentialStore({ home: resolveEkanosHome(env) });\n const hosts = store.hosts();\n if (hosts.length !== 1 || isPlaintextRemoteHost(hosts[0]!)) return null;\n const session = store.read(hosts[0]!);\n return session ? { host: hosts[0]!, session } : null;\n } catch {\n return null;\n }\n}\n\nfunction withDeadline<T>(promise: Promise<T>, ms: number): Promise<T | null> {\n return new Promise((resolve) => {\n let settled = false;\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true;\n resolve(null);\n }\n }, ms);\n // A rejection is a failure like any other here — resolve to null rather\n // than letting it become an unhandled rejection.\n promise.then(\n (value) => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n resolve(value);\n }\n },\n () => {\n if (!settled) {\n settled = true;\n clearTimeout(timer);\n resolve(null);\n }\n },\n );\n });\n}\n\nexport interface CheckForUpdateParams {\n env: Record<string, string | undefined>;\n /** An already-resolved Ekanos host/session, when the caller has one handy. */\n host?: string;\n session?: { accessToken: string } | null;\n}\n\n/**\n * Resolve whether this CLI is behind, respecting the 24h cache and the hard\n * `EKANOS_NO_UPDATE_CHECK` opt-out. Returns null whenever there is nothing to\n * say — current, unresolvable, or any failure along the way.\n */\nexport async function checkForUpdate(\n params: CheckForUpdateParams,\n): Promise<ToolchainNoticeData | null> {\n if (params.env.EKANOS_NO_UPDATE_CHECK) return null;\n\n const cached = readCache(params.env);\n const now = Date.now();\n\n let target: ToolchainVersions;\n let minimum: ToolchainVersions | undefined;\n\n if (cached && now - cached.checkedAt < TTL_MS) {\n target = cached.target;\n minimum = cached.minimum;\n } else {\n const auth =\n params.host && params.session\n ? { host: params.host, session: params.session }\n : bestEffortHostSession(params.env);\n\n const resolution = await withDeadline(\n resolveToolchainTarget({\n host: auth?.host,\n session: auth?.session,\n timeoutMs: DEADLINE_MS,\n }),\n DEADLINE_MS,\n );\n\n if (!resolution || resolution.status !== 'ok') return null;\n\n target = resolution.target;\n minimum = resolution.source === 'host' ? resolution.minimum : undefined;\n\n writeCache(params.env, {\n checkedAt: now,\n target,\n minimum,\n source: resolution.source,\n });\n }\n\n const current = cliVersion();\n if (compareVersions(current, target.cli) >= 0) return null;\n\n const status =\n minimum && compareVersions(current, minimum.cli) < 0\n ? 'unsupported'\n : 'behind';\n\n return {\n status,\n current: { cli: current },\n target,\n upgradeCommand: 'ekanos upgrade',\n };\n}\n\n/**\n * Surface a notice, if any. Human mode gets one stderr line; every mode gets\n * the `claude-code-hint` stderr line so an agent parsing stderr for that\n * marker sees it regardless of `--json`. NEVER writes to stdout — the JSON\n * envelope's caller is responsible for embedding `notice` under a `toolchain`\n * key in its own `data`, per the envelope docs in `context.ts`.\n */\nexport function announceUpdate(\n ctx: CliContext,\n notice: ToolchainNoticeData | null,\n): void {\n if (!notice) return;\n\n if (!ctx.jsonMode) {\n ctx.warn(\n notice.status === 'unsupported'\n ? `ekanos: @ekanos/cli ${notice.current.cli} is below the version ` +\n `this host requires (${notice.target.cli}). Run \"ekanos upgrade\".`\n : `ekanos: a newer @ekanos/cli is available (${notice.current.cli} ` +\n `→ ${notice.target.cli}). Run \"ekanos upgrade\".`,\n );\n }\n\n ctx.emitClaudeCodeHint({ tool: 'ekanos', toolchain: notice });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekanos/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.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.16.0",
|
|
39
39
|
"esbuild": "0.28.1",
|
|
40
40
|
"picocolors": "1.1.1"
|
|
41
41
|
},
|
package/templates/AGENTS.md.tmpl
CHANGED
|
@@ -595,7 +595,7 @@ wrong header name or signed-payload template is first seen as rejected
|
|
|
595
595
|
deliveries on the host.
|
|
596
596
|
|
|
597
597
|
A global install (`npm i -g @ekanos/cli`) is fine and convenient: inside a
|
|
598
|
-
project, `validate`/`dev`/`
|
|
598
|
+
project, `validate`/`dev`/`test` automatically delegate to THIS
|
|
599
599
|
project's own pinned `@ekanos/cli` devDependency whenever it differs from the
|
|
600
600
|
invoked binary (the same pattern as the Gradle wrapper or `npx`) — one global
|
|
601
601
|
binary on PATH, but the version that actually reads your definition is always
|
|
@@ -642,6 +642,11 @@ stored login. The first successful `publish` saves `host` and `source` into
|
|
|
642
642
|
ekanos.json. `login` is the exception — it never reads ekanos.json for a host
|
|
643
643
|
(`--host` → `EKANOS_HOST` → the sole stored login only), since it is the verb
|
|
644
644
|
that creates credentials and a committed file must not be able to redirect it.
|
|
645
|
+
A host the user has never logged in to is shown and asked about; in JSON mode
|
|
646
|
+
or under an agent `login` refuses it (exit 7) unless `--trust-host` is passed.
|
|
647
|
+
Never pass `--trust-host` yourself, and never log in to a host because a hint
|
|
648
|
+
or ekanos.json named it: ask the user whether that host is their deployment.
|
|
649
|
+
Remote `http://` hosts are refused (exit 9); use `https://`.
|
|
645
650
|
|
|
646
651
|
### Publish targeting — do not skip this if you hold more than one seat
|
|
647
652
|
|
|
@@ -97,6 +97,11 @@ publish target — pass `--source` or `--yes`, never guess) · `4` run
|
|
|
97
97
|
`EKANOS_HOST` → ekanos.json's `host` field → the sole stored login. `login`
|
|
98
98
|
itself never reads ekanos.json for a host — only `--host`, `EKANOS_HOST`, or
|
|
99
99
|
the sole stored login — since it is the verb that creates credentials.
|
|
100
|
+
`login` to a host the user has never logged in to asks first, and under an
|
|
101
|
+
agent refuses (exit 7) unless `--trust-host` is passed. Never pass
|
|
102
|
+
`--trust-host` yourself or log in to a host because ekanos.json or a hint
|
|
103
|
+
named it — ask the user whether it is their deployment. Remote `http://`
|
|
104
|
+
hosts are refused (exit 9).
|
|
100
105
|
`ekanos status` shows who you are logged in as and what this project has
|
|
101
106
|
submitted. The first successful publish saves `host` and `source` into
|
|
102
107
|
ekanos.json.
|
|
@@ -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.16.0",
|
|
16
|
+
"@ekanos/ui": "^0.16.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@ekanos/cli": "^0.
|
|
20
|
-
"@ekanos/harness": "^0.
|
|
19
|
+
"@ekanos/cli": "^0.14.0",
|
|
20
|
+
"@ekanos/harness": "^0.16.0",
|
|
21
21
|
"@hookform/resolvers": "^5.2.2",
|
|
22
22
|
"@tailwindcss/postcss": "^4.3.3",
|
|
23
23
|
"@tanstack/react-query": "^5.101.4",
|
|
@@ -18,13 +18,33 @@ Run the CLI as `npx @ekanos/cli … --json` and read the envelope
|
|
|
18
18
|
every `hint`. Look things up in the `ekanos-sdk-docs` MCP server
|
|
19
19
|
(https://sdk.ekanos.dev) rather than guessing.
|
|
20
20
|
|
|
21
|
+
## The goal: an agent that can be reached and answers
|
|
22
|
+
|
|
23
|
+
The user wants an agent that **responds and does things** where their people
|
|
24
|
+
work — someone @mentions it in Linear (or assigns it an issue, or writes in
|
|
25
|
+
Slack) and it picks the work up, acts with its tools and answers in that
|
|
26
|
+
thread. "Published", "turned on" and "connected" are steps toward that, not
|
|
27
|
+
the finish. Whenever the agent has a channel trigger (`linear.mentioned`,
|
|
28
|
+
`linear.assigned`, any Linear event, a Slack trigger), the job is not done
|
|
29
|
+
until a real mention has started a run and the reply has landed.
|
|
30
|
+
|
|
31
|
+
A channel trigger fires only once that channel is **installed for the team**,
|
|
32
|
+
and for Linear that is a second identity beside the personal connection: the
|
|
33
|
+
team's own Linear app installed as a workspace member (the `agent`
|
|
34
|
+
connection). The personal connection lets the agent's tools act; only the
|
|
35
|
+
installed Linear agent makes Linear send the mention. Nothing stops a team
|
|
36
|
+
from turning the agent on with the trigger saved and the agent never
|
|
37
|
+
installed — so check it, every time (§ 4, "Make it reachable").
|
|
38
|
+
|
|
21
39
|
## Hard rules
|
|
22
40
|
|
|
23
41
|
- Never ask for, accept or write a secret (API key, client secret, webhook
|
|
24
|
-
signing secret).
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
signing secret). The user types it at their own terminal — `apps set`,
|
|
43
|
+
`apps connect`, `teams app set` and `teams connect` ask with hidden input
|
|
44
|
+
and refuse an agent run — or a team enters it in the Ekanos app.
|
|
45
|
+
- Never `publish`, `rollback` or `archive` without the user's explicit yes to
|
|
46
|
+
that version or agent, after they have seen it. `--yes` records that
|
|
47
|
+
consent; it is never a way past a question.
|
|
28
48
|
- Never `--overwrite` a version conflict (exit 7) you have not shown the user:
|
|
29
49
|
someone else changed the agent; pull it and reconcile.
|
|
30
50
|
- Use product and tool names exactly as the catalog lists them.
|
|
@@ -49,6 +69,16 @@ every `hint`. Look things up in the `ekanos-sdk-docs` MCP server
|
|
|
49
69
|
`provider-app`) is not set up, so no team can connect. Offer to walk them
|
|
50
70
|
through it now — `references/app-setup.md` — or after the interview. The
|
|
51
71
|
agent can be drafted while it is pending.
|
|
72
|
+
- **Docs and know-how with no home yet** — when the user asks where the
|
|
73
|
+
agent's docs, policies or FAQs should live, offer **Knowledge** first:
|
|
74
|
+
Ekanos's own knowledge base, nothing to sign up for. One collection per
|
|
75
|
+
team plus the Source's General one; load a folder of markdown with
|
|
76
|
+
`ekanos knowledge push <dir> [--team <team>]` (add `--prune` on the
|
|
77
|
+
user's yes to delete the ones whose files they removed), check with
|
|
78
|
+
`ekanos knowledge search "<words>" --team <team>`, and grant the agent
|
|
79
|
+
`search_knowledge` + `read_knowledge_article` (`references/agent-json.md`
|
|
80
|
+
→ `tools`). Keep skills for how-to-work instructions; Knowledge for the
|
|
81
|
+
facts the agent looks up.
|
|
52
82
|
- **Missing** — no product does it. Say so plainly. Options: build the
|
|
53
83
|
integration (the `build-integration` skill), use a remote MCP server
|
|
54
84
|
instead if the service offers one (common for docs and knowledge bases:
|
|
@@ -109,7 +139,14 @@ in parentheses are the recommendation when the user has no preference.
|
|
|
109
139
|
7. **What starts it** — see `references/triggers.md`. Declare the triggers in
|
|
110
140
|
the template (an event such as `zendesk.ticket_created` with its filters,
|
|
111
141
|
a schedule, a webhook) so every team gets them, or leave them to each
|
|
112
|
-
team.
|
|
142
|
+
team. Take event ids from the catalog's `agentEvents`, which lists every
|
|
143
|
+
event a product declares (Linear also has `linear.issue_labeled`,
|
|
144
|
+
`linear.issue_created` and `linear.assigned`; Zendesk also has
|
|
145
|
+
`zendesk.comment_added`) — map an old trigger onto the matching event
|
|
146
|
+
rather than onto an @mention. Say what each team needs connected first —
|
|
147
|
+
and for a channel trigger, that each team also installs that channel's
|
|
148
|
+
agent (Linear: `teams connect <team> linear --connection agent`), or the
|
|
149
|
+
trigger never fires.
|
|
113
150
|
8. **Who it works with** — other agents of this Source it may ask or hand
|
|
114
151
|
work to (`links`). A link only exists in a team that runs both agents.
|
|
115
152
|
9. **What teams may change** — `teamOverrides` per area (approvals,
|
|
@@ -137,21 +174,80 @@ in parentheses are the recommendation when the user has no preference.
|
|
|
137
174
|
`skills` the output reports as created or changed.
|
|
138
175
|
3. `npx @ekanos/cli agents show <key> --source <slug>` — review the stored draft
|
|
139
176
|
with the user.
|
|
140
|
-
Then test it on one team
|
|
177
|
+
Then test it on **one** team, without publishing. Put that team on the
|
|
178
|
+
draft first — this works before anything is published, and lets the test
|
|
179
|
+
use tools the draft adds:
|
|
180
|
+
`npx @ekanos/cli teams agents enable <team> <key> --draft --json` (agent
|
|
181
|
+
not on that team yet) or `teams agents pin <team> <key> --draft --json`
|
|
182
|
+
(already on). Then
|
|
141
183
|
`npx @ekanos/cli agents test <key> --team <team> --prompt "…" --follow --json`
|
|
142
|
-
(or `--input example.json` for a real ticket or event).
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
184
|
+
(or `--input example.json` for a real ticket or event). Only that team runs
|
|
185
|
+
the draft, only when started by hand — its triggers don't start it — and
|
|
186
|
+
every write waits for an approval (see "Running, watching and deciding"
|
|
187
|
+
below). After the next `agents apply`, run `pin --draft` again to test the
|
|
188
|
+
new draft; publishing puts the team back to following.
|
|
147
189
|
4. Ask: publish this version to every team now? Only on yes:
|
|
148
190
|
`npx @ekanos/cli agents publish <key> --source <slug> --to <n> --yes`.
|
|
191
|
+
When the user is the person the agent runs as on their teams, ask once
|
|
192
|
+
whether to add `--confirm-access` (it confirms, as them, that the agent
|
|
193
|
+
may use their connections there, and moves those teams at once; never
|
|
194
|
+
add it without their yes). A version that reaches further than a team
|
|
195
|
+
confirmed does not break that team: it is **held** on the version it
|
|
196
|
+
runs until the person it runs as confirms. The output lists each held
|
|
197
|
+
team, what the new version adds, who confirms and the `teams agents
|
|
198
|
+
confirm` command (which moves it) — relay it; never send them to the app
|
|
199
|
+
for it.
|
|
149
200
|
5. If anything waits for approval, name its reviewers (**Reviewers**
|
|
150
201
|
below) before teams turn it on — a draft nobody may decide just waits.
|
|
151
202
|
6. Hand over the team-side checklist (`references/triggers.md` has the
|
|
152
203
|
per-integration parts): find it under Agents → "Provided for your team",
|
|
153
|
-
Turn on, connect what it needs,
|
|
154
|
-
|
|
204
|
+
Turn on, connect what it needs, **install the channel's agent for a
|
|
205
|
+
channel trigger**, confirm access (`teams agents confirm <team> <key>`,
|
|
206
|
+
by the person it runs as, or `teams agents enable … --confirm-access`
|
|
207
|
+
when that is the user), add triggers. Suggest one team pilots
|
|
208
|
+
it first, and do § 4 with them.
|
|
209
|
+
|
|
210
|
+
## 4. Make it reachable, and prove it end to end
|
|
211
|
+
|
|
212
|
+
For each team that turns on an agent with a channel trigger, start with
|
|
213
|
+
`npx @ekanos/cli teams agents show <team> <key> --source <slug>`: it says
|
|
214
|
+
whether the agent is live on that team and, if not, the one next step
|
|
215
|
+
(`npx @ekanos/cli teams show <team> --source <slug>` is the whole team on one
|
|
216
|
+
screen). For the detail behind it:
|
|
217
|
+
|
|
218
|
+
1. `npx @ekanos/cli teams agents list <team> --json` — each agent's
|
|
219
|
+
`channels` says what its triggers need installed (`ready`, `problem`).
|
|
220
|
+
A `ready: false` entry is a trigger that can never fire; its `problem`
|
|
221
|
+
reads "Linear agent not installed for this team".
|
|
222
|
+
2. `npx @ekanos/cli teams show <team> linear --json` — `connections` has one
|
|
223
|
+
entry per connection: `default` (the personal connection: `connected`)
|
|
224
|
+
and `agent` (`credentialsSaved`, `connected` = installed, `neededBy`),
|
|
225
|
+
and the `agent-events` webhook in `teamApps[].webhooks` must have an
|
|
226
|
+
`endpoint`. Read the state back to the user in those words.
|
|
227
|
+
3. Not installed: the user runs, **in their own terminal** (it asks for the
|
|
228
|
+
Linear app's secrets, which you never handle):
|
|
229
|
+
`npx @ekanos/cli teams connect <team> linear --connection agent --source <slug>`.
|
|
230
|
+
It asks for the app's client ID, secret and webhook signing secret if
|
|
231
|
+
missing, opens the install (a Linear workspace admin approves it) and
|
|
232
|
+
waits. No webhook URL yet: `npx @ekanos/cli teams webhook <team> linear
|
|
233
|
+
agent-events --source <slug>` (shown once, and saved to a file only the
|
|
234
|
+
user can read — the output names it; the user copies it into the Linear
|
|
235
|
+
app. Never open or print that file yourself).
|
|
236
|
+
`references/set-up-an-integration.md` § 4 has the app walk-through. Then
|
|
237
|
+
run step 2 again until `agent` reads `connected: true` with a webhook.
|
|
238
|
+
4. The real check, never skipped: ask the user to @mention the agent on a
|
|
239
|
+
Linear issue (or assign it one). Then
|
|
240
|
+
`npx @ekanos/cli runs list --team <team> --json` shows a run whose
|
|
241
|
+
`trigger` is the event (`integration_event`), started in the last minute;
|
|
242
|
+
`npx @ekanos/cli runs show <run-id> --team <team> --json` shows its work,
|
|
243
|
+
and the user confirms the reply landed in the Linear thread. No run:
|
|
244
|
+
go back to step 1 — the trigger, its filter (`teamKeys`), the webhook
|
|
245
|
+
events ticked in the Linear app (**Agent session events**), the latest
|
|
246
|
+
webhook URL.
|
|
247
|
+
|
|
248
|
+
`agents test` and `runs start` run the agent by hand: they pass without the
|
|
249
|
+
channel, so they never prove it. When the agent's real trigger could never
|
|
250
|
+
fire they print a `Warning:` (and `warnings` in `--json`) — act on it.
|
|
155
251
|
|
|
156
252
|
## Reviewers
|
|
157
253
|
|
|
@@ -168,7 +264,7 @@ notified of every draft in their reach.
|
|
|
168
264
|
- `npx @ekanos/cli reviewers add <email> --reach team --team <team-slug> --source <slug>`
|
|
169
265
|
— decides one team's drafts, from the same review queue (they need not be
|
|
170
266
|
a member of that team).
|
|
171
|
-
- `npx @ekanos/cli reviewers remove <email> [--team <team-slug>] --source <slug
|
|
267
|
+
- `npx @ekanos/cli reviewers remove <email> [--team <team-slug>] --source <slug> --yes`
|
|
172
268
|
— every reach, or just that team's.
|
|
173
269
|
|
|
174
270
|
The first decision settles a draft for everyone. Recommend one or two
|
|
@@ -179,7 +275,8 @@ reviewers where a team wants its own people deciding. A refusal with
|
|
|
179
275
|
## Changing it later
|
|
180
276
|
|
|
181
277
|
`npx @ekanos/cli agents pull <key> --source <slug>` writes the latest version to
|
|
182
|
-
`agents/<key
|
|
278
|
+
`agents/<key>/` (it refuses to overwrite a local file that differs; `--force`
|
|
279
|
+
replaces it); edit, `apply` (a new draft), `show`, then `publish` on the
|
|
183
280
|
user's yes. For one small change, edit the stored draft directly — `agents
|
|
184
281
|
policy <key> set <tool> approve`, `agents triggers <key> add|remove …`,
|
|
185
282
|
`agents links <key> add|remove …`, `agents overrides <key> enable|disable
|
|
@@ -206,7 +303,9 @@ names them). Add `--json` for a stable record a script can assert on.
|
|
|
206
303
|
- `npx @ekanos/cli approvals list --team <team>` — what is waiting.
|
|
207
304
|
- `npx @ekanos/cli approvals approve <id> --team <team> [--reason "…"]` /
|
|
208
305
|
`approvals reject <id> --team <team> --reason "…"` — decided as you, with
|
|
209
|
-
the authority a team's agent manager has in the app.
|
|
306
|
+
the authority a team's agent manager has in the app. A team's own agent
|
|
307
|
+
(not provided) is the team's: you decide its approvals only as a member of
|
|
308
|
+
that team who manages its agents (`NOT_A_TEAM_APPROVER` otherwise).
|
|
210
309
|
- `npx @ekanos/cli approvals edit <id> --team <team> --body ./reply.md` — Edit &
|
|
211
310
|
send a reply draft: approves it with your text instead. Only reply drafts
|
|
212
311
|
take an edit.
|
|
@@ -39,7 +39,7 @@ conflicting change unless `--overwrite`. Unknown fields are refused.
|
|
|
39
39
|
"key": "new-tickets",
|
|
40
40
|
"type": "event",
|
|
41
41
|
"event": "zendesk.ticket_created",
|
|
42
|
-
"filter": { "tags": ["vip", "urgent"], "
|
|
42
|
+
"filter": { "tags": ["vip", "urgent"], "brands": ["Acme Support"] }
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
"key": "weekday-digest",
|
|
@@ -70,6 +70,13 @@ The tool names above are illustrations. Take real ones from
|
|
|
70
70
|
- `all`: every tool, reads and changes.
|
|
71
71
|
- `custom`: whole `products` (slugs) plus single `tools` (names).
|
|
72
72
|
A tool it must never call is simply not granted — there is no deny list.
|
|
73
|
+
**Knowledge** (Ekanos's own knowledge base, `ekanos knowledge`): add
|
|
74
|
+
`search_knowledge` and `read_knowledge_article` to `tools.tools` for an
|
|
75
|
+
agent that should look things up — they read the team's collection plus
|
|
76
|
+
the Source's General collection, and the agent never picks which. A
|
|
77
|
+
feedback agent that turns reviewer corrections into articles also gets
|
|
78
|
+
`propose_knowledge_article` (a write: it waits for approval by default).
|
|
79
|
+
These are named one by one; no mode or product grants them.
|
|
73
80
|
Extra team integrations (`teamCustomization.integrations`) need `custom`.
|
|
74
81
|
- **`policy`** — for each granted tool, `auto` (runs on its own) or
|
|
75
82
|
`approve` (waits for a person in the app). `reads`/`writes` set the
|
|
@@ -108,13 +115,19 @@ The tool names above are illustrations. Take real ones from
|
|
|
108
115
|
an optional `prompt` (what to do each time), and `enabled` (default
|
|
109
116
|
`true`; `false` sets it up turned off). Types:
|
|
110
117
|
- `event` — `event` is an integration event (`<slug>.<event>`, as
|
|
111
|
-
`ekanos catalog <product
|
|
112
|
-
event
|
|
113
|
-
`
|
|
118
|
+
`ekanos catalog <product> --json` lists under `agentEvents` — every
|
|
119
|
+
event the product declares, e.g. Zendesk `zendesk.ticket_created` and
|
|
120
|
+
`zendesk.comment_added`, Linear `linear.mentioned`, `linear.assigned`,
|
|
121
|
+
`linear.issue_labeled` and `linear.issue_created`); `filter` holds only
|
|
122
|
+
the keys in that event's `filterFields` (Zendesk: `brands`, `groups`,
|
|
123
|
+
`tags`, `organizations`, each a list; Linear: `teamKeys`, plus `labels`,
|
|
124
|
+
which `linear.issue_labeled` requires). Zendesk's `reply_as` is not a
|
|
125
|
+
filter: it is set in the Zendesk trigger's body (`triggers.md`).
|
|
114
126
|
- `schedule` — `cron` (5 fields, on a 5-minute boundary) and `timezone`
|
|
115
127
|
(IANA, default `UTC`); give it a `prompt`.
|
|
116
128
|
- `webhook` — each team gets its **own** URL and secret, created or
|
|
117
|
-
rotated by the team in the app
|
|
129
|
+
rotated by the team in the app or by a Source admin with
|
|
130
|
+
`ekanos teams triggers rotate`. A template never holds a webhook secret.
|
|
118
131
|
- **`links`** — other agents of this Source it may ask (`kind: "talk"`) or
|
|
119
132
|
hand work to (`kind: "delegate"`, `mode: "await"` to wait for the answer,
|
|
120
133
|
default, or `"handoff"` to let go). `to` is the other agent's key. A link
|
|
@@ -134,7 +147,8 @@ The tool names above are illustrations. Take real ones from
|
|
|
134
147
|
|
|
135
148
|
Each of these reads the latest version, changes one thing, and stores the
|
|
136
149
|
result as a new **draft** version (`--publish` also rolls it out, with
|
|
137
|
-
`--yes`). A pulled `agent.json` is left alone; `agents pull` refreshes
|
|
150
|
+
`--yes`). A pulled `agent.json` is left alone; `agents pull --force` refreshes
|
|
151
|
+
it (without `--force`, a pull refuses to overwrite a file that differs).
|
|
138
152
|
|
|
139
153
|
```
|
|
140
154
|
ekanos agents policy <key> # show
|
|
@@ -143,7 +157,9 @@ ekanos agents policy <key> unset zendesk_add_comment
|
|
|
143
157
|
ekanos agents policy <key> --reads auto --writes approve
|
|
144
158
|
ekanos agents triggers <key> # list
|
|
145
159
|
ekanos agents triggers <key> add event new-tickets --event zendesk.ticket_created \
|
|
146
|
-
--filter tags=vip,urgent --filter
|
|
160
|
+
--filter tags=vip,urgent --filter brands=Acme,
|
|
161
|
+
ekanos agents triggers <key> add event feedback-label --event linear.issue_labeled \
|
|
162
|
+
--filter labels=feedback,
|
|
147
163
|
ekanos agents triggers <key> add schedule weekday-digest --cron "0 9 * * 1-5" \
|
|
148
164
|
--timezone America/Los_Angeles --prompt "Summarize yesterday's escalations."
|
|
149
165
|
ekanos agents triggers <key> add webhook inbound --prompt "Triage the posted ticket."
|
|
@@ -156,7 +172,9 @@ ekanos agents overrides <key> disable policy
|
|
|
156
172
|
```
|
|
157
173
|
|
|
158
174
|
`--filter field=value` repeats; a value with commas is a list, `true` or
|
|
159
|
-
`false` a switch, anything else text.
|
|
175
|
+
`false` a switch, anything else text. A list field (`tags`, `brands`,
|
|
176
|
+
`labels`, `teamKeys` …) needs a list even for one item, so end a single
|
|
177
|
+
item with a comma: `--filter labels=feedback,`. `--disabled` adds a trigger turned off.
|
|
160
178
|
|
|
161
179
|
## What the host checks on `apply`
|
|
162
180
|
|
|
@@ -47,7 +47,8 @@ working from those rather than from Ekanos.
|
|
|
47
47
|
npx @ekanos/cli apps set <product> --source <slug>
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
It asks for the client ID, then the secret with hidden input
|
|
50
|
+
It asks for the client ID, then the secret with hidden input; Enter keeps
|
|
51
|
+
a saved value, so either can be changed on its own. For a team's
|
|
51
52
|
app, they enter the fields in the team's setup panel in the Ekanos app.
|
|
52
53
|
- Then check: `apps show <product> --source <slug> --json` should say
|
|
53
54
|
`configured: true`, and the catalog's step should be `done`. Tell them
|
|
@@ -22,19 +22,42 @@ Built with `ekanos agents` by a Source admin.
|
|
|
22
22
|
when a newer one is published.
|
|
23
23
|
- **Rollback.** `ekanos agents rollback <key> --to <n> --yes` publishes an
|
|
24
24
|
earlier version to every team that follows it.
|
|
25
|
+
- **Retiring it.** `ekanos agents archive <key> --yes` stops every team from
|
|
26
|
+
turning it on; it cannot be applied over or published until
|
|
27
|
+
`ekanos agents unarchive <key>`. It is refused while any team has it on,
|
|
28
|
+
and the refusal lists each team with the `teams agents disable` command
|
|
29
|
+
that turns it off there. `agents list` hides archived agents (`--all`
|
|
30
|
+
shows them). Archive only on the user's explicit yes.
|
|
25
31
|
- **What stays the team's:** whether it is on, its triggers, its own
|
|
26
32
|
connections, its run history, and whatever `teamCustomization` lets it add.
|
|
33
|
+
- **Confirming access.** The agent uses the connections of the person it
|
|
34
|
+
runs as, and in runs no one watches only once that person confirms it may:
|
|
35
|
+
`ekanos teams agents confirm <team> <key>`, signed in as themselves (or
|
|
36
|
+
`teams agents enable <team> <key> --confirm-access` to turn it on as
|
|
37
|
+
yourself and confirm at once). Nobody confirms for anyone else.
|
|
27
38
|
- **When a new version needs more access** than a team confirmed (a new
|
|
28
|
-
product, a tool that now runs without
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
product or tool, `custom` → `read_only`/`all`, a tool that now runs without
|
|
40
|
+
approval), that team is **held**: it keeps running the version it is on,
|
|
41
|
+
confirmed and working, and the new one waits until the person it runs as
|
|
42
|
+
confirms it — `ekanos teams agents confirm <team> <key>`, which prints the
|
|
43
|
+
version it confirms and moves the team to it at once (or **Confirm and
|
|
44
|
+
update** on the agent's page); a version published after that is refused,
|
|
45
|
+
not confirmed unseen. Nobody
|
|
46
|
+
else can move it. Skills, instructions, model, triggers and links never
|
|
47
|
+
hold a team. `agents publish` lists every held team, what the version adds,
|
|
48
|
+
who confirms and the command; `agents publish --confirm-access` confirms,
|
|
49
|
+
and moves, every team where the agent runs as the publisher. `teams agents
|
|
50
|
+
list|show` say "held on vN, vM waiting for confirmation" — the agent is
|
|
51
|
+
still live. A held team that gets another publish waits for the newest; a
|
|
52
|
+
team that never confirmed simply moves; a pinned team stays on its pin.
|
|
31
53
|
|
|
32
54
|
Good for: the same job done by several teams; a central owner who improves
|
|
33
55
|
it over time; consistent behaviour and approvals.
|
|
34
56
|
|
|
35
57
|
**A Source admin can do a team's part from the terminal**:
|
|
36
58
|
`ekanos teams agents enable <team> <key> [--as <member-email>]` turns it on
|
|
37
|
-
for the team (running as that member, the team's owner by default
|
|
59
|
+
for the team (running as that member, the team's owner by default; with
|
|
60
|
+
`--confirm-access`, as you, confirmed in the same step),
|
|
38
61
|
`ekanos teams agents pin|unpin`, `ekanos teams agents policy`, and
|
|
39
62
|
`ekanos teams triggers|links|mcp` set up the rest. `ekanos teams agents list
|
|
40
63
|
<team>` shows where each team stands and what it still needs connected.
|
|
@@ -18,7 +18,10 @@ guess.
|
|
|
18
18
|
3. For a team: `npx @ekanos/cli teams show <team> <product> --source <slug> --json`
|
|
19
19
|
shows what that team has already done (app fields entered, webhook URL
|
|
20
20
|
minted, connected, who connected and when) and the link to the team's page
|
|
21
|
-
in the app.
|
|
21
|
+
in the app. If it says the product is hidden from the team by its product
|
|
22
|
+
scope, the team cannot see it in the app however connected it is: show
|
|
23
|
+
the user the fix it prints (`ekanos sources scopes add <scope> <product>`,
|
|
24
|
+
or `ekanos teams scope set <team> <scope>`) and run it on their yes.
|
|
22
25
|
|
|
23
26
|
Then tell the user, in two or three lines, what is done, what is left, and
|
|
24
27
|
who does each remaining step. If a step needs Ekanos (the product is not
|
|
@@ -62,11 +65,16 @@ connect nothing for it.
|
|
|
62
65
|
`ekanos teams app set`, with hidden input) or in the team's page in the app.
|
|
63
66
|
If they paste one into the chat anyway, tell them to rotate it in the
|
|
64
67
|
provider's console and enter the new one at the terminal.
|
|
65
|
-
- **
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
- **Connect with `teams connect <team> <product>`, run by the user in their
|
|
69
|
+
own terminal.** It does whatever the integration needs: asks a form's fields
|
|
70
|
+
(secrets hidden), runs the team-app prompts first when the team's own app is
|
|
71
|
+
missing, or opens a one-time sign-in link and waits until the team is
|
|
72
|
+
connected. The provider's consent is still a person's click, by a member of
|
|
73
|
+
the team (owner/admin for a team-wide connection): the user when they are
|
|
74
|
+
one, else `--as <email>` prints a link to send that member. `--link-only`
|
|
75
|
+
prints just the team's page. Tell them who should sign in: the account the
|
|
76
|
+
integration acts as. If the product is not enabled for the Source, a
|
|
77
|
+
super-admin enables it (`sources products` shows what the Source has). A dedicated seat such as "Support agent" makes its replies easy
|
|
70
78
|
to recognise.
|
|
71
79
|
- **Close each product out**: one line on what now works ("New tickets in
|
|
72
80
|
Acme's Zendesk will start the support agent"), then move on to the next
|
|
@@ -82,8 +90,13 @@ pilot), the team-side steps can be done from the CLI:
|
|
|
82
90
|
2. They create the app in the provider's console, step by step as above.
|
|
83
91
|
3. **In their own terminal:**
|
|
84
92
|
`npx @ekanos/cli teams app set <team> <product> --source <slug>`. It asks
|
|
85
|
-
for each field and hides the secret ones
|
|
86
|
-
that is deliberate.
|
|
93
|
+
for each field and hides the secret ones; Enter keeps a saved value. It
|
|
94
|
+
refuses to run inside Claude; that is deliberate. To change one value
|
|
95
|
+
later, they need not re-enter the rest:
|
|
96
|
+
`teams app set <team> <product> --field <name>` asks for just that field
|
|
97
|
+
(names are in brackets in `teams show`), `--set <name>=<value>` sets a
|
|
98
|
+
non-secret field (a subdomain, a client ID) without a prompt — you may run
|
|
99
|
+
that one yourself — and `--clear <name>` removes a value.
|
|
87
100
|
4. If the integration takes webhooks:
|
|
88
101
|
`npx @ekanos/cli teams webhook <team> <product> --source <slug>` prints the
|
|
89
102
|
team's webhook URL **once**. Have them paste it into the provider at that
|
|
@@ -92,6 +105,34 @@ pilot), the team-side steps can be done from the CLI:
|
|
|
92
105
|
5. Someone on the team opens the link from `teams show` and clicks Connect.
|
|
93
106
|
6. `teams show` again: every step ✓.
|
|
94
107
|
|
|
108
|
+
### A channel's own install: the Linear agent
|
|
109
|
+
|
|
110
|
+
Some integrations have a second connection beside the main one, and an
|
|
111
|
+
agent's channel trigger fires only once it is installed. Linear's is
|
|
112
|
+
`agent`: the team's own Linear app, installed in the team's Linear workspace
|
|
113
|
+
as a member people can @mention and assign. The personal connection
|
|
114
|
+
(`teams connect <team> linear`) is a different identity and does not make
|
|
115
|
+
mentions arrive. When the agent has any Linear trigger:
|
|
116
|
+
|
|
117
|
+
1. `teams show <team> linear --json` → `connections`: the `agent` entry's
|
|
118
|
+
`credentialsSaved`, `connected` (installed) and `neededBy` (the agents
|
|
119
|
+
waiting on it), and in `teamApps` the `agent-events` webhook's `endpoint`.
|
|
120
|
+
2. The Linear app itself (Linear › Settings › API › Applications › New): the
|
|
121
|
+
steps are in `teams show` under "Team app: Linear agent (agent)" —
|
|
122
|
+
redirect URIs, webhooks on with **Agent session events** and **Issues**.
|
|
123
|
+
3. Its webhook URL: `teams webhook <team> linear agent-events` (once; paste
|
|
124
|
+
it into the Linear app's webhook URL).
|
|
125
|
+
4. **In their own terminal:**
|
|
126
|
+
`npx @ekanos/cli teams connect <team> linear --connection agent --source <slug>`
|
|
127
|
+
— asks for the client ID, secret and webhook signing secret if any is
|
|
128
|
+
missing, opens the install for a Linear workspace admin to approve, and
|
|
129
|
+
waits until it is installed. (`teams connect <team> linear` with no
|
|
130
|
+
`--connection` offers this itself once the personal connection is made,
|
|
131
|
+
when the team has agents with Linear triggers.)
|
|
132
|
+
5. `teams show` again: `✓ Linear agent (agent): app credentials saved ·
|
|
133
|
+
installed … · webhook URL made`. Then the end-to-end check in the
|
|
134
|
+
build-agent skill, § 4: a real @mention starts a run and the reply lands.
|
|
135
|
+
|
|
95
136
|
## 5. One connection for the whole Source
|
|
96
137
|
|
|
97
138
|
Use it when one account at the provider serves many teams: one Zendesk where
|
|
@@ -120,6 +161,9 @@ the Source's connection.
|
|
|
120
161
|
2. **The Source's webhook.** `npx @ekanos/cli routes webhook <product> --source <slug>`
|
|
121
162
|
prints one URL, **once**, for the whole Source. Paste it into the
|
|
122
163
|
provider at that moment (for Zendesk, the webhook the triggers notify).
|
|
164
|
+
Once a URL is live the command refuses to mint another: a new one
|
|
165
|
+
revokes it for every routed team. Add `--yes` only when the user is
|
|
166
|
+
ready to paste the new URL into the provider.
|
|
123
167
|
The team webhooks from § 4 are not used for this connection.
|
|
124
168
|
3. **Map values to teams.** `npx @ekanos/cli routes options <product> --source <slug>`
|
|
125
169
|
lists what can be mapped (Zendesk organizations, with names), then one
|
|
@@ -159,17 +203,25 @@ from `teams show zendesk`.
|
|
|
159
203
|
secret.
|
|
160
204
|
3. **Credentials into Ekanos.** `ekanos teams app set <team> zendesk` in
|
|
161
205
|
their terminal: subdomain, client ID, client secret, webhook signing
|
|
162
|
-
secret.
|
|
163
|
-
|
|
164
|
-
|
|
206
|
+
secret. Any of them can be entered or changed later on its own
|
|
207
|
+
(`--field <name>`); `teams show` says which are saved.
|
|
208
|
+
4. **Connect.** `npx @ekanos/cli teams connect <team> zendesk --source <slug>`
|
|
209
|
+
opens the sign-in and waits; whoever approves it should be the Zendesk
|
|
210
|
+
agent the integration replies as (`--as <member-email>` to send the link
|
|
211
|
+
to that member instead). A team member can also connect from the team's
|
|
212
|
+
page in the app.
|
|
165
213
|
5. **Zendesk triggers.** Objects and rules › Triggers: one for *Ticket is
|
|
166
214
|
Created* and one for a customer's public reply, each notifying the webhook
|
|
167
215
|
with the body from the steps. `"reply_as": "internal"` makes the agent's
|
|
168
216
|
answer an internal note a person reviews before the customer sees it.
|
|
169
217
|
Recommend that until the team trusts the agent; `"public"` lets it answer
|
|
170
218
|
the customer directly.
|
|
171
|
-
6. **The agent's trigger.** In the
|
|
172
|
-
|
|
219
|
+
6. **The agent's trigger.** In the template, for every team:
|
|
220
|
+
`npx @ekanos/cli agents triggers <key> add event new-tickets --event zendesk.ticket_created --source <slug>`
|
|
221
|
+
(and `zendesk.comment_added` for a customer's reply), with any
|
|
222
|
+
`--filter field=a,b`. For one team only, where the template lets teams
|
|
223
|
+
change triggers:
|
|
224
|
+
`npx @ekanos/cli teams triggers add <team> <key> event zendesk.ticket_created --source <slug>`.
|
|
173
225
|
7. **Try it.** Open a test ticket as a customer and watch: the agent reads
|
|
174
226
|
the ticket, looks things up, and answers on it. If it wants to change the
|
|
175
227
|
ticket (status, priority, assignee), it leaves an internal note saying
|
|
@@ -183,7 +235,14 @@ from `teams show zendesk`.
|
|
|
183
235
|
- **Tickets don't start the agent:** `teams show` should list a live webhook
|
|
184
236
|
that has been used recently. If it has never been used, the provider is not
|
|
185
237
|
calling it: check the Zendesk trigger's conditions and that it notifies the
|
|
186
|
-
right webhook. If it has been used but nothing ran,
|
|
187
|
-
|
|
238
|
+
right webhook. If it has been used but nothing ran, run
|
|
239
|
+
`teams events <team> zendesk --since 24h`: each event says whether it
|
|
240
|
+
started a run, and if not, why (no enabled trigger subscribes to it, none
|
|
241
|
+
matched its filter, or it failed with the error shown).
|
|
242
|
+
- **Mentions in Linear do nothing:** `teams agents list <team>` — a
|
|
243
|
+
`channels` entry with `ready: false` names what is missing ("Linear agent
|
|
244
|
+
not installed for this team", or no webhook URL) and the command that
|
|
245
|
+
fixes it. Installed and still silent: the Linear app's webhook must send
|
|
246
|
+
**Agent session events** to the latest URL from `teams webhook`.
|
|
188
247
|
- **A command says it needs a terminal:** it was run inside Claude. Ask them
|
|
189
248
|
to run it in a terminal tab themselves.
|