@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.
Files changed (175) hide show
  1. package/README.md +25 -8
  2. package/dist/agents-api.d.ts +11 -2
  3. package/dist/agents-api.js +39 -3
  4. package/dist/agents-api.js.map +1 -1
  5. package/dist/apps-api.d.ts +3 -2
  6. package/dist/apps-api.js +3 -7
  7. package/dist/apps-api.js.map +1 -1
  8. package/dist/auth/ekanos-api.d.ts +16 -2
  9. package/dist/auth/ekanos-api.js +101 -15
  10. package/dist/auth/ekanos-api.js.map +1 -1
  11. package/dist/auth/host-trust.d.ts +46 -0
  12. package/dist/auth/host-trust.js +79 -0
  13. package/dist/auth/host-trust.js.map +1 -0
  14. package/dist/auth/session.d.ts +10 -1
  15. package/dist/auth/session.js +28 -28
  16. package/dist/auth/session.js.map +1 -1
  17. package/dist/commands/agents-edit.d.ts +6 -2
  18. package/dist/commands/agents-edit.js +57 -26
  19. package/dist/commands/agents-edit.js.map +1 -1
  20. package/dist/commands/agents.d.ts +13 -2
  21. package/dist/commands/agents.js +148 -36
  22. package/dist/commands/agents.js.map +1 -1
  23. package/dist/commands/apps.d.ts +15 -4
  24. package/dist/commands/apps.js +133 -51
  25. package/dist/commands/apps.js.map +1 -1
  26. package/dist/commands/catalog.js +7 -6
  27. package/dist/commands/catalog.js.map +1 -1
  28. package/dist/commands/knowledge.d.ts +56 -0
  29. package/dist/commands/knowledge.js +456 -0
  30. package/dist/commands/knowledge.js.map +1 -0
  31. package/dist/commands/login.d.ts +4 -0
  32. package/dist/commands/login.js +30 -2
  33. package/dist/commands/login.js.map +1 -1
  34. package/dist/commands/logout.d.ts +9 -1
  35. package/dist/commands/logout.js +56 -7
  36. package/dist/commands/logout.js.map +1 -1
  37. package/dist/commands/publish.js +3 -3
  38. package/dist/commands/publish.js.map +1 -1
  39. package/dist/commands/reviewers.d.ts +2 -0
  40. package/dist/commands/reviewers.js +7 -3
  41. package/dist/commands/reviewers.js.map +1 -1
  42. package/dist/commands/routes.d.ts +2 -0
  43. package/dist/commands/routes.js +42 -9
  44. package/dist/commands/routes.js.map +1 -1
  45. package/dist/commands/runs.d.ts +13 -2
  46. package/dist/commands/runs.js +96 -15
  47. package/dist/commands/runs.js.map +1 -1
  48. package/dist/commands/scope-notice.d.ts +21 -0
  49. package/dist/commands/scope-notice.js +35 -0
  50. package/dist/commands/scope-notice.js.map +1 -0
  51. package/dist/commands/skills.d.ts +7 -1
  52. package/dist/commands/skills.js +80 -24
  53. package/dist/commands/skills.js.map +1 -1
  54. package/dist/commands/sources.d.ts +4 -0
  55. package/dist/commands/sources.js +233 -5
  56. package/dist/commands/sources.js.map +1 -1
  57. package/dist/commands/status.js +5 -4
  58. package/dist/commands/status.js.map +1 -1
  59. package/dist/commands/team-agent-readiness.d.ts +59 -0
  60. package/dist/commands/team-agent-readiness.js +341 -0
  61. package/dist/commands/team-agent-readiness.js.map +1 -0
  62. package/dist/commands/team-agents-agents.d.ts +24 -3
  63. package/dist/commands/team-agents-agents.js +336 -35
  64. package/dist/commands/team-agents-agents.js.map +1 -1
  65. package/dist/commands/team-agents-mcp.js +32 -3
  66. package/dist/commands/team-agents-mcp.js.map +1 -1
  67. package/dist/commands/team-agents-triggers.js +230 -54
  68. package/dist/commands/team-agents-triggers.js.map +1 -1
  69. package/dist/commands/team-agents.d.ts +3 -1
  70. package/dist/commands/team-agents.js +27 -7
  71. package/dist/commands/team-agents.js.map +1 -1
  72. package/dist/commands/teams-channel-install.d.ts +24 -0
  73. package/dist/commands/teams-channel-install.js +94 -0
  74. package/dist/commands/teams-channel-install.js.map +1 -0
  75. package/dist/commands/teams-connect.d.ts +66 -0
  76. package/dist/commands/teams-connect.js +397 -0
  77. package/dist/commands/teams-connect.js.map +1 -0
  78. package/dist/commands/teams-events.d.ts +12 -0
  79. package/dist/commands/teams-events.js +138 -0
  80. package/dist/commands/teams-events.js.map +1 -0
  81. package/dist/commands/teams-onboard.d.ts +16 -5
  82. package/dist/commands/teams-onboard.js +61 -36
  83. package/dist/commands/teams-onboard.js.map +1 -1
  84. package/dist/commands/teams-overview.d.ts +11 -0
  85. package/dist/commands/teams-overview.js +160 -0
  86. package/dist/commands/teams-overview.js.map +1 -0
  87. package/dist/commands/teams-scope.d.ts +10 -0
  88. package/dist/commands/teams-scope.js +109 -0
  89. package/dist/commands/teams-scope.js.map +1 -0
  90. package/dist/commands/teams.d.ts +64 -7
  91. package/dist/commands/teams.js +254 -60
  92. package/dist/commands/teams.js.map +1 -1
  93. package/dist/commands/upgrade.js +12 -2
  94. package/dist/commands/upgrade.js.map +1 -1
  95. package/dist/commands/use.js +2 -2
  96. package/dist/commands/use.js.map +1 -1
  97. package/dist/commands/whoami.js +1 -1
  98. package/dist/commands/whoami.js.map +1 -1
  99. package/dist/connections-api.js +1 -1
  100. package/dist/connections-api.js.map +1 -1
  101. package/dist/delegate.js +11 -4
  102. package/dist/delegate.js.map +1 -1
  103. package/dist/harness-scaffold.js +12 -0
  104. package/dist/harness-scaffold.js.map +1 -1
  105. package/dist/index.d.ts +6 -0
  106. package/dist/index.js +724 -152
  107. package/dist/index.js.map +1 -1
  108. package/dist/knowledge-api.d.ts +175 -0
  109. package/dist/knowledge-api.js +169 -0
  110. package/dist/knowledge-api.js.map +1 -0
  111. package/dist/open-url.d.ts +16 -0
  112. package/dist/open-url.js +73 -0
  113. package/dist/open-url.js.map +1 -0
  114. package/dist/parse-argv.d.ts +2 -0
  115. package/dist/parse-argv.js.map +1 -1
  116. package/dist/publish-api.d.ts +2 -1
  117. package/dist/publish-api.js +4 -3
  118. package/dist/publish-api.js.map +1 -1
  119. package/dist/pull-write.d.ts +27 -0
  120. package/dist/pull-write.js +60 -0
  121. package/dist/pull-write.js.map +1 -0
  122. package/dist/routes-api.d.ts +3 -0
  123. package/dist/routes-api.js +9 -4
  124. package/dist/routes-api.js.map +1 -1
  125. package/dist/runs-api.d.ts +7 -1
  126. package/dist/runs-api.js +21 -6
  127. package/dist/runs-api.js.map +1 -1
  128. package/dist/safe-fs.d.ts +2 -0
  129. package/dist/safe-fs.js +12 -6
  130. package/dist/safe-fs.js.map +1 -1
  131. package/dist/secret-file.d.ts +17 -0
  132. package/dist/secret-file.js +69 -0
  133. package/dist/secret-file.js.map +1 -0
  134. package/dist/skills-api.d.ts +10 -2
  135. package/dist/skills-api.js +37 -2
  136. package/dist/skills-api.js.map +1 -1
  137. package/dist/source-products-api.d.ts +63 -0
  138. package/dist/source-products-api.js +123 -0
  139. package/dist/source-products-api.js.map +1 -0
  140. package/dist/source-target.js +2 -1
  141. package/dist/source-target.js.map +1 -1
  142. package/dist/sources-api.d.ts +2 -1
  143. package/dist/sources-api.js +3 -2
  144. package/dist/sources-api.js.map +1 -1
  145. package/dist/team-connect-api.d.ts +286 -0
  146. package/dist/team-connect-api.js +107 -0
  147. package/dist/team-connect-api.js.map +1 -0
  148. package/dist/teams-api.d.ts +42 -1
  149. package/dist/teams-api.js +98 -20
  150. package/dist/teams-api.js.map +1 -1
  151. package/dist/toolchain-api.d.ts +9 -0
  152. package/dist/toolchain-api.js +17 -2
  153. package/dist/toolchain-api.js.map +1 -1
  154. package/dist/toolchain-resolve.d.ts +3 -0
  155. package/dist/toolchain-resolve.js +15 -1
  156. package/dist/toolchain-resolve.js.map +1 -1
  157. package/dist/toolchain.d.ts +6 -0
  158. package/dist/toolchain.js +19 -0
  159. package/dist/toolchain.js.map +1 -1
  160. package/dist/update-notice.js +2 -1
  161. package/dist/update-notice.js.map +1 -1
  162. package/package.json +2 -2
  163. package/templates/AGENTS.md.tmpl +6 -1
  164. package/templates/claude-skill.md.tmpl +5 -0
  165. package/templates/package.json.tmpl +4 -4
  166. package/templates/skills/build-agent/SKILL.md +115 -16
  167. package/templates/skills/build-agent/references/agent-json.md +26 -8
  168. package/templates/skills/build-agent/references/app-setup.md +2 -1
  169. package/templates/skills/build-agent/references/rollout.md +27 -4
  170. package/templates/skills/build-agent/references/set-up-an-integration.md +74 -15
  171. package/templates/skills/build-agent/references/skills.md +20 -1
  172. package/templates/skills/build-agent/references/triggers.md +84 -20
  173. package/templates/skills/build-integration/references/widget-design.md +3 -3
  174. package/templates/skills/ekanos-platform/SKILL.md +70 -17
  175. 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":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAG9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAE7D;;;;;;;;;;;GAWG;AAEH,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAC3C,MAAM,cAAc,GAAG,KAAK,CAAC;AAC7B,MAAM,eAAe,GAAG,KAAK,CAAC;AAC9B,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACnC,kFAAkF;AAClF,MAAM,WAAW,GAAG,IAAI,CAAC;AAgBzB,SAAS,SAAS,CAAC,GAAuC;IACxD,OAAO,IAAI,CAAC,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,cAAc,CAAC,CAAC;AACtE,CAAC;AAED,SAAS,SAAS,CAAC,GAAuC;IACxD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;QACpD,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACxC,OAAO,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7C,CAAC;eAAO,CAAC;QACP,qEAAqE;QACrE,0DAA0D;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CACjB,GAAuC,EACvC,IAAe;IAEf,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;YACjC,SAAS,EAAE,IAAI;YACf,IAAI,EAAE,cAAc;SACrB,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;QAC3C,EAAE,CAAC,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC,CAAC;QACvE,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC;eAAO,CAAC;QACP,sEAAsE;QACtE,kBAAkB;IACpB,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,SAAS,GAAG,KAA2B,CAAC;IAC9C,OAAO,CACL,OAAO,SAAS,CAAC,SAAS,KAAK,QAAQ;QACvC,OAAO,SAAS,CAAC,MAAM,KAAK,QAAQ;QACpC,SAAS,CAAC,MAAM,KAAK,IAAI;QACzB,CAAC,SAAS,CAAC,MAAM,KAAK,MAAM,IAAI,SAAS,CAAC,MAAM,KAAK,KAAK,CAAC,CAC5D,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,qBAAqB,CAC5B,GAAuC;IAEvC,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,eAAe,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACpE,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,qBAAqB,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC;YAAE,OAAO,IAAI,CAAC;QACxE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,CAAC;QACtC,OAAO,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAE,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACvD,CAAC;eAAO,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAI,OAAmB,EAAE,EAAU;IACtD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,IAAI,CAAC;gBACf,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,EAAE,EAAE,CAAC,CAAC;QACP,wEAAwE;QACxE,iDAAiD;QACjD,OAAO,CAAC,IAAI,CACV,CAAC,KAAK,EAAE,EAAE;YACR,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,IAAI,CAAC;gBACf,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,CAAC,KAAK,CAAC,CAAC;YACjB,CAAC;QACH,CAAC,EACD,GAAG,EAAE;YACH,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,OAAO,GAAG,IAAI,CAAC;gBACf,YAAY,CAAC,KAAK,CAAC,CAAC;gBACpB,OAAO,CAAC,IAAI,CAAC,CAAC;YAChB,CAAC;QACH,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AASD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAA4B;IAE5B,IAAI,MAAM,CAAC,GAAG,CAAC,sBAAsB;QAAE,OAAO,IAAI,CAAC;IAEnD,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAEvB,IAAI,MAAyB,CAAC;IAC9B,IAAI,OAAsC,CAAC;IAE3C,IAAI,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,SAAS,GAAG,MAAM,EAAE,CAAC;QAC9C,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QACvB,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAC3B,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,GACR,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,OAAO;YAC3B,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE;YAChD,CAAC,CAAC,qBAAqB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAExC,MAAM,UAAU,GAAG,MAAM,YAAY,CACnC,sBAAsB,CAAC;YACrB,IAAI,EAAE,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,IAAI;YAChB,OAAO,EAAE,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,OAAO;YACtB,SAAS,EAAE,WAAW;SACvB,CAAC,EACF,WAAW,CACZ,CAAC;QAEF,IAAI,CAAC,UAAU,IAAI,UAAU,CAAC,MAAM,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAE3D,MAAM,GAAG,UAAU,CAAC,MAAM,CAAC;QAC3B,OAAO,GAAG,UAAU,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;QAExE,UAAU,CAAC,MAAM,CAAC,GAAG,EAAE;YACrB,SAAS,EAAE,GAAG;YACd,MAAM;YACN,OAAO;YACP,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;IACL,CAAC;IAED,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,IAAI,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAE3D,MAAM,MAAM,GACV,OAAO,IAAI,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;QAClD,CAAC,CAAC,aAAa;QACf,CAAC,CAAC,QAAQ,CAAC;IAEf,OAAO;QACL,MAAM;QACN,OAAO,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE;QACzB,MAAM;QACN,cAAc,EAAE,gBAAgB;KACjC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,GAAe,EACf,MAAkC;IAElC,IAAI,CAAC,MAAM;QAAE,OAAO;IAEpB,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC;QAClB,GAAG,CAAC,IAAI,CACN,MAAM,CAAC,MAAM,KAAK,aAAa;YAC7B,CAAC,CAAC,uBAAuB,MAAM,CAAC,OAAO,CAAC,GAAG,wBAAwB;gBAC/D,uBAAuB,MAAM,CAAC,MAAM,CAAC,GAAG,0BAA0B;YACtE,CAAC,CAAC,6CAA6C,MAAM,CAAC,OAAO,CAAC,GAAG,GAAG;gBAChE,KAAK,MAAM,CAAC,MAAM,CAAC,GAAG,0BAA0B,CACvD,CAAC;IACJ,CAAC;IAED,GAAG,CAAC,kBAAkB,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;AAChE,CAAC","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.12.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.15.0",
38
+ "@ekanos/integration-schema": "0.16.0",
39
39
  "esbuild": "0.28.1",
40
40
  "picocolors": "1.1.1"
41
41
  },
@@ -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`/`publish`/`test` automatically delegate to THIS
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.15.0",
16
- "@ekanos/ui": "^0.15.0"
15
+ "@ekanos/sdk": "^0.16.0",
16
+ "@ekanos/ui": "^0.16.0"
17
17
  },
18
18
  "devDependencies": {
19
- "@ekanos/cli": "^0.12.0",
20
- "@ekanos/harness": "^0.15.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). Teams enter those in the Ekanos app.
25
- - Never `publish` or `rollback` without the user's explicit yes to that
26
- version, after they have seen it. `--yes` records that consent; it is never
27
- a way past a question.
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. Say what each team needs connected first.
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 that has turned the agent on, without publishing:
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). It runs the latest
143
- version on that team as a test run — same tools, approvals and audit; the
144
- team's current agent still bounds what it may do, so a draft can only do
145
- less. A write waits for an approval like any run (see "Running, watching
146
- and deciding" below).
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, confirm access, add triggers. Suggest one
154
- team pilots it first, and plan a first test run with them.
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>/`; edit, `apply` (a new draft), `show`, then `publish` on the
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"], "brand": "123", "reply_as": "internal_note" }
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>` lists under `agentEvents`); `filter` holds the
112
- event's declared filter fields (Zendesk: `organization`, `brand`,
113
- `group`, `tags`, `reply_as` …).
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. A template never holds a webhook secret.
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 it.
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 brand=123 --filter reply_as=internal_note
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. `--disabled` adds a trigger turned off.
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. For a team's
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 approval), the team's owner is asked
29
- to confirm again; until then the agent keeps only what needs no new
30
- confirmation.
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
- - **Signing in is a person's click.** Connecting (the provider's consent
66
- screen) happens in the browser, by someone on the team, from the link in
67
- `teams show` (or just the link and whether it is connected yet:
68
- `teams connect <team> <product>`). Tell them who should click it: the account the integration
69
- acts as. A dedicated seat such as "Support agent" makes its replies easy
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. It refuses to run inside Claude;
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
- 4. **Connect.** Someone on the team opens the link and connects, as the
164
- Zendesk agent the integration should reply as.
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 app, on the agent: *A ticket is created*
172
- (and *A customer replies*), with any filters.
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, check that the agent has
187
- a trigger for that event and that the team connected the product.
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.