@ekanos/cli 0.5.0 → 0.6.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 +1 -1
- package/dist/commands/agents-init.d.ts +33 -0
- package/dist/commands/agents-init.js +113 -0
- package/dist/commands/agents-init.js.map +1 -0
- package/dist/commands/agents.d.ts +3 -1
- package/dist/commands/agents.js +14 -5
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/catalog.js +3 -2
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +2 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/pack.js +7 -4
- package/dist/pack.js.map +1 -1
- package/dist/project-checks.d.ts +6 -0
- package/dist/project-checks.js +11 -0
- package/dist/project-checks.js.map +1 -1
- package/package.json +1 -1
- package/templates/agents-claude.md.tmpl +14 -0
- package/templates/claude-skill.md.tmpl +4 -8
- package/templates/package.json.tmpl +1 -1
- package/templates/skills/build-agent/SKILL.md +110 -0
- package/templates/skills/build-agent/references/agent-json.md +85 -0
- package/templates/skills/build-agent/references/rollout.md +53 -0
- package/templates/skills/build-agent/references/triggers.md +47 -0
- package/templates/skills/ekanos-platform/SKILL.md +98 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-checks.js","sourceRoot":"","sources":["../src/project-checks.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { type EkanosProject, isDocUrl } from './project';\n\n/**\n * Every vitest/vite config basename `ekanos init` might have scaffolded (or a\n * partner might have renamed to). Exported so `pack.ts` can pack exactly the\n * same set it checks here — one list, so packing and validating this project\n * property can never disagree on what \"the test config\" even is.\n */\nexport const VITEST_CONFIG_NAMES = [\n 'vitest.config.ts',\n 'vitest.config.mts',\n 'vitest.config.cts',\n 'vitest.config.js',\n 'vitest.config.mjs',\n 'vitest.config.cjs',\n 'vite.config.ts',\n 'vite.config.mts',\n 'vite.config.js',\n 'vite.config.mjs',\n];\n\n/** The exact block the hint quotes — the one config that makes the SDK load. */\nconst REQUIRED_INLINE_BLOCK = [\n 'test: {',\n ' server: {',\n ' deps: {',\n \" inline: ['@ekanos/sdk'],\",\n ' },',\n ' },',\n '}',\n].join('\\n');\n\n/**\n * The host's own compiler-strictness overlay a project's tsconfig should\n * already satisfy — mirrors the exact flags (`HOST_TSC_FLAGS`) the gate's\n * own SECOND `tsc --noEmit` pass checks, read from the host's real tsconfig\n * (source of truth for the values themselves). Hardcoded here for the same\n * reason it is hardcoded there: this package ships to a partner's local\n * machine, so it cannot read a file inside the host's own repo at runtime.\n * Rules-as-data via `/api/partner/toolchain` supersedes this hardcoded copy\n * later — until then, keep both copies in sync by hand.\n */\nexport const HOST_COMPILER_RULES: Record<string, boolean> = {\n strict: true,\n noUncheckedIndexedAccess: true,\n};\n\n/** Matches `tsconfig.json`, `tsconfig.build.json`, etc. — mirrors the pattern\n * `pack.ts` and the gate's `typecheck.ts` use to find a project's tsconfig. */\nconst TSCONFIG_PATTERN = /^tsconfig(\\.[^./]+)?\\.json$/;\n\n/**\n * Project-level integrity checks that read files in the project root (next to\n * ekanos.json), NOT the integration definition. These are deliberately kept\n * out of the schema package's definition collectors: the definition is valid or\n * not on its own; whether the partner's TEST runner is configured to load the\n * published SDK is a property of THIS project.\n *\n * The highest-value check: a partner's vitest must inline `@ekanos/sdk`. The\n * published SDK dist imports extensionless `next/link`, so without\n * `test.server.deps.inline: ['@ekanos/sdk']` vitest fails to load the SDK, the\n * test file silently imports nothing, and vitest reports green over zero tests.\n *\n * The check is conditional: if the project declares NO vitest/vite config at\n * all, there is nothing to assert (the `test` verb would surface a missing\n * runner on its own). If a config IS present, it MUST inline the SDK.\n */\nexport function collectProjectFindings(projectDir: string): Finding[] {\n const configFile = VITEST_CONFIG_NAMES.map((name) =>\n path.join(projectDir, name),\n ).find((candidate) => fs.existsSync(candidate));\n\n if (!configFile) return [];\n\n const source = fs.readFileSync(configFile, 'utf8');\n if (vitestInlinesSdk(source)) return [];\n\n return [\n {\n check: 'project.vitest-inline-sdk',\n severity: 'error',\n file: configFile,\n message:\n `${path.basename(configFile)} does not inline @ekanos/sdk. The ` +\n `published SDK dist imports extensionless \"next/link\", so without ` +\n `test.server.deps.inline including \"@ekanos/sdk\", vitest cannot load ` +\n `the SDK — the test file imports nothing and vitest reports green ` +\n `over zero tests.`,\n hint:\n `Add this to your vitest config's defineConfig so the SDK is ` +\n `transformed for the test runner:\\n\\n${REQUIRED_INLINE_BLOCK}`,\n },\n ];\n}\n\n/**\n * Advisory check on the `docs` array: a relative entry that resolves to\n * nothing on disk gets a WARNING — never an error, because a missing doc file\n * must not block a publish (the gate refuses only error-severity findings).\n * URLs are never checked: `validate` does no network, ever, and an agent\n * dereferences them at authoring time instead.\n */\nexport function collectDocsFindings(\n project: EkanosProject,\n projectDir: string,\n): Finding[] {\n const findings: Finding[] = [];\n\n for (const entry of project.docs ?? []) {\n if (isDocUrl(entry)) continue;\n\n const resolved = path.resolve(projectDir, entry);\n if (fs.existsSync(resolved)) continue;\n\n findings.push({\n check: 'docs/missing-file',\n severity: 'warn',\n file: resolved,\n message:\n `ekanos.json lists \"${entry}\" under \"docs\", but no file exists ` +\n `there (resolved to ${resolved}).`,\n hint:\n `Create ${entry}, or fix the path in ekanos.json's \"docs\" array so ` +\n 'it points at real API documentation.',\n });\n }\n\n return findings;\n}\n\n/**\n * WARN when the project's tsconfig does not already set the host's\n * compiler-strictness overlay (`HOST_COMPILER_RULES`). This exists because a\n * project can pass its own `checks/typecheck` cleanly, publish, and only THEN\n * discover `checks/typecheck-host-rules` (the gate's second, stricter pass)\n * fails on the exact same source — e.g. `noUncheckedIndexedAccess` catching a\n * possibly-`undefined` array/record access `strict` alone allows. WARN, not\n * ERROR: this check reads a partner's local tsconfig, not the gate's own\n * verdict, so it must never block a publish that is actually fine — a\n * project whose tsconfig extends something odd, or omits `compilerOptions`\n * intentionally, still gets to publish and find out from the real gate.\n * `publish` escalates this specific check to ERROR locally (see its\n * severity-escalation list) so a submission that WOULD fail the gate never\n * gets uploaded in the first place.\n *\n * Absent tsconfig is not this check's concern — `checks/typecheck` (and its\n * host-rules sibling) already report that loudly on their own.\n */\nexport function collectHostCompilerRulesFindings(\n projectDir: string,\n): Finding[] {\n const tsconfigPath = findTsconfigPath(projectDir);\n if (!tsconfigPath) return [];\n\n const raw = fs.readFileSync(tsconfigPath, 'utf8');\n const tsconfigName = path.basename(tsconfigPath);\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(stripJsonComments(raw));\n } catch {\n // An unparseable tsconfig is a real defect, but not one THIS check can\n // reason about — `tsc` itself will refuse it far more usefully. info\n // severity: surfaced, but never blocks anything.\n return [\n {\n check: 'project.host-compiler-rules',\n severity: 'info',\n file: tsconfigPath,\n message:\n `${tsconfigName} could not be parsed as JSON (even tolerating ` +\n `comments and trailing commas), so this check could not verify it ` +\n `sets the host's compiler rules ` +\n `(${Object.keys(HOST_COMPILER_RULES).join(', ')}).`,\n hint: `Fix ${tsconfigName}'s syntax, then re-run validate.`,\n },\n ];\n }\n\n const compilerOptions =\n typeof parsed === 'object' &&\n parsed !== null &&\n typeof (parsed as { compilerOptions?: unknown }).compilerOptions ===\n 'object' &&\n (parsed as { compilerOptions?: unknown }).compilerOptions !== null\n ? ((parsed as { compilerOptions: Record<string, unknown> })\n .compilerOptions ?? {})\n : {};\n\n const missing = Object.entries(HOST_COMPILER_RULES)\n .filter(([name, required]) => compilerOptions[name] !== required)\n .map(([name]) => name);\n\n if (missing.length === 0) return [];\n\n return [\n {\n check: 'project.host-compiler-rules',\n severity: 'warn',\n file: tsconfigPath,\n message:\n `${tsconfigName} does not set ${missing.join(' and ')} in ` +\n `compilerOptions. The gate re-checks your submission under the ` +\n `host's own stricter compiler rules — a project that typechecks ` +\n `cleanly under its own laxer tsconfig can still fail that pass.`,\n hint:\n `Add ${missing.map((name) => `\"${name}\": true`).join(', ')} to ` +\n `compilerOptions in ${tsconfigName}.`,\n },\n ];\n}\n\n/** `tsconfig.json` if present (matching what a bare `tsc --noEmit` would\n * auto-discover), else the first `tsconfig.*.json` found. */\nfunction findTsconfigPath(projectDir: string): string | undefined {\n let names: string[];\n try {\n names = fs\n .readdirSync(projectDir, { withFileTypes: true })\n .filter((entry) => entry.isFile() && TSCONFIG_PATTERN.test(entry.name))\n .map((entry) => entry.name);\n } catch {\n return undefined;\n }\n if (names.length === 0) return undefined;\n const name = names.includes('tsconfig.json')\n ? 'tsconfig.json'\n : [...names].sort()[0];\n return name ? path.join(projectDir, name) : undefined;\n}\n\n/**\n * A minimal, string-literal-aware strip of `//` and `/* *\\/` comments —\n * tsconfig.json is conventionally JSONC, but nothing in this package's\n * dependency graph parses it as such. Not a full JSONC parser: it does not\n * touch trailing commas beyond the one cleanup pass below, and it makes no\n * attempt to validate the result — `JSON.parse` does that, and a project\n * with genuinely malformed JSON falls through to this check's `info`-severity\n * catch, never a crash.\n */\nfunction stripJsonComments(source: string): string {\n let out = '';\n let inString = false;\n let quote = '';\n\n for (let i = 0; i < source.length; i++) {\n const char = source[i] ?? '';\n\n if (inString) {\n out += char;\n if (char === '\\\\') {\n out += source[++i] ?? '';\n continue;\n }\n if (char === quote) inString = false;\n continue;\n }\n\n if (char === '\"' || char === \"'\") {\n inString = true;\n quote = char;\n out += char;\n continue;\n }\n\n if (char === '/' && source[i + 1] === '/') {\n while (i < source.length && source[i] !== '\\n') i++;\n out += '\\n';\n continue;\n }\n\n if (char === '/' && source[i + 1] === '*') {\n i += 2;\n while (\n i < source.length &&\n !(source[i] === '*' && source[i + 1] === '/')\n ) {\n i++;\n }\n i++; // consume the closing '/'\n continue;\n }\n\n out += char;\n }\n\n // Trailing commas before a closing brace/bracket.\n return out.replace(/,(\\s*[}\\]])/g, '$1');\n}\n\n/**\n * True when the config source contains an `inline: [ … ]` array that lists\n * `@ekanos/sdk`. Textual on purpose — evaluating a partner's config module\n * would run arbitrary code; a lint-style scan does not.\n */\nfunction vitestInlinesSdk(source: string): boolean {\n const inlineArrayPattern = /inline\\s*:\\s*\\[([\\s\\S]*?)\\]/g;\n for (\n let match = inlineArrayPattern.exec(source);\n match !== null;\n match = inlineArrayPattern.exec(source)\n ) {\n if (/['\"]@ekanos\\/sdk['\"]/.test(match[1] ?? '')) return true;\n }\n return false;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"project-checks.js","sourceRoot":"","sources":["../src/project-checks.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Finding } from '@ekanos/integration-schema';\nimport * as fs from 'node:fs';\nimport * as path from 'node:path';\n\nimport { type EkanosProject, isDocUrl } from './project';\n\n/**\n * Every vitest/vite config basename `ekanos init` might have scaffolded (or a\n * partner might have renamed to). Exported so `pack.ts` can pack exactly the\n * same set it checks here — one list, so packing and validating this project\n * property can never disagree on what \"the test config\" even is.\n */\nexport const VITEST_CONFIG_NAMES = [\n 'vitest.config.ts',\n 'vitest.config.mts',\n 'vitest.config.cts',\n 'vitest.config.js',\n 'vitest.config.mjs',\n 'vitest.config.cjs',\n 'vite.config.ts',\n 'vite.config.mts',\n 'vite.config.js',\n 'vite.config.mjs',\n];\n\n/**\n * The ESLint flat-config basenames the hosted gate's `checks/capability-lint`\n * looks for in the ARCHIVE — the same four, so a project that lints locally\n * is never refused for a config `ekanos publish` left behind.\n */\nexport const ESLINT_CONFIG_NAMES = [\n 'eslint.config.mjs',\n 'eslint.config.js',\n 'eslint.config.cjs',\n 'eslint.config.ts',\n];\n\n/** The exact block the hint quotes — the one config that makes the SDK load. */\nconst REQUIRED_INLINE_BLOCK = [\n 'test: {',\n ' server: {',\n ' deps: {',\n \" inline: ['@ekanos/sdk'],\",\n ' },',\n ' },',\n '}',\n].join('\\n');\n\n/**\n * The host's own compiler-strictness overlay a project's tsconfig should\n * already satisfy — mirrors the exact flags (`HOST_TSC_FLAGS`) the gate's\n * own SECOND `tsc --noEmit` pass checks, read from the host's real tsconfig\n * (source of truth for the values themselves). Hardcoded here for the same\n * reason it is hardcoded there: this package ships to a partner's local\n * machine, so it cannot read a file inside the host's own repo at runtime.\n * Rules-as-data via `/api/partner/toolchain` supersedes this hardcoded copy\n * later — until then, keep both copies in sync by hand.\n */\nexport const HOST_COMPILER_RULES: Record<string, boolean> = {\n strict: true,\n noUncheckedIndexedAccess: true,\n};\n\n/** Matches `tsconfig.json`, `tsconfig.build.json`, etc. — mirrors the pattern\n * `pack.ts` and the gate's `typecheck.ts` use to find a project's tsconfig. */\nconst TSCONFIG_PATTERN = /^tsconfig(\\.[^./]+)?\\.json$/;\n\n/**\n * Project-level integrity checks that read files in the project root (next to\n * ekanos.json), NOT the integration definition. These are deliberately kept\n * out of the schema package's definition collectors: the definition is valid or\n * not on its own; whether the partner's TEST runner is configured to load the\n * published SDK is a property of THIS project.\n *\n * The highest-value check: a partner's vitest must inline `@ekanos/sdk`. The\n * published SDK dist imports extensionless `next/link`, so without\n * `test.server.deps.inline: ['@ekanos/sdk']` vitest fails to load the SDK, the\n * test file silently imports nothing, and vitest reports green over zero tests.\n *\n * The check is conditional: if the project declares NO vitest/vite config at\n * all, there is nothing to assert (the `test` verb would surface a missing\n * runner on its own). If a config IS present, it MUST inline the SDK.\n */\nexport function collectProjectFindings(projectDir: string): Finding[] {\n const configFile = VITEST_CONFIG_NAMES.map((name) =>\n path.join(projectDir, name),\n ).find((candidate) => fs.existsSync(candidate));\n\n if (!configFile) return [];\n\n const source = fs.readFileSync(configFile, 'utf8');\n if (vitestInlinesSdk(source)) return [];\n\n return [\n {\n check: 'project.vitest-inline-sdk',\n severity: 'error',\n file: configFile,\n message:\n `${path.basename(configFile)} does not inline @ekanos/sdk. The ` +\n `published SDK dist imports extensionless \"next/link\", so without ` +\n `test.server.deps.inline including \"@ekanos/sdk\", vitest cannot load ` +\n `the SDK — the test file imports nothing and vitest reports green ` +\n `over zero tests.`,\n hint:\n `Add this to your vitest config's defineConfig so the SDK is ` +\n `transformed for the test runner:\\n\\n${REQUIRED_INLINE_BLOCK}`,\n },\n ];\n}\n\n/**\n * Advisory check on the `docs` array: a relative entry that resolves to\n * nothing on disk gets a WARNING — never an error, because a missing doc file\n * must not block a publish (the gate refuses only error-severity findings).\n * URLs are never checked: `validate` does no network, ever, and an agent\n * dereferences them at authoring time instead.\n */\nexport function collectDocsFindings(\n project: EkanosProject,\n projectDir: string,\n): Finding[] {\n const findings: Finding[] = [];\n\n for (const entry of project.docs ?? []) {\n if (isDocUrl(entry)) continue;\n\n const resolved = path.resolve(projectDir, entry);\n if (fs.existsSync(resolved)) continue;\n\n findings.push({\n check: 'docs/missing-file',\n severity: 'warn',\n file: resolved,\n message:\n `ekanos.json lists \"${entry}\" under \"docs\", but no file exists ` +\n `there (resolved to ${resolved}).`,\n hint:\n `Create ${entry}, or fix the path in ekanos.json's \"docs\" array so ` +\n 'it points at real API documentation.',\n });\n }\n\n return findings;\n}\n\n/**\n * WARN when the project's tsconfig does not already set the host's\n * compiler-strictness overlay (`HOST_COMPILER_RULES`). This exists because a\n * project can pass its own `checks/typecheck` cleanly, publish, and only THEN\n * discover `checks/typecheck-host-rules` (the gate's second, stricter pass)\n * fails on the exact same source — e.g. `noUncheckedIndexedAccess` catching a\n * possibly-`undefined` array/record access `strict` alone allows. WARN, not\n * ERROR: this check reads a partner's local tsconfig, not the gate's own\n * verdict, so it must never block a publish that is actually fine — a\n * project whose tsconfig extends something odd, or omits `compilerOptions`\n * intentionally, still gets to publish and find out from the real gate.\n * `publish` escalates this specific check to ERROR locally (see its\n * severity-escalation list) so a submission that WOULD fail the gate never\n * gets uploaded in the first place.\n *\n * Absent tsconfig is not this check's concern — `checks/typecheck` (and its\n * host-rules sibling) already report that loudly on their own.\n */\nexport function collectHostCompilerRulesFindings(\n projectDir: string,\n): Finding[] {\n const tsconfigPath = findTsconfigPath(projectDir);\n if (!tsconfigPath) return [];\n\n const raw = fs.readFileSync(tsconfigPath, 'utf8');\n const tsconfigName = path.basename(tsconfigPath);\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(stripJsonComments(raw));\n } catch {\n // An unparseable tsconfig is a real defect, but not one THIS check can\n // reason about — `tsc` itself will refuse it far more usefully. info\n // severity: surfaced, but never blocks anything.\n return [\n {\n check: 'project.host-compiler-rules',\n severity: 'info',\n file: tsconfigPath,\n message:\n `${tsconfigName} could not be parsed as JSON (even tolerating ` +\n `comments and trailing commas), so this check could not verify it ` +\n `sets the host's compiler rules ` +\n `(${Object.keys(HOST_COMPILER_RULES).join(', ')}).`,\n hint: `Fix ${tsconfigName}'s syntax, then re-run validate.`,\n },\n ];\n }\n\n const compilerOptions =\n typeof parsed === 'object' &&\n parsed !== null &&\n typeof (parsed as { compilerOptions?: unknown }).compilerOptions ===\n 'object' &&\n (parsed as { compilerOptions?: unknown }).compilerOptions !== null\n ? ((parsed as { compilerOptions: Record<string, unknown> })\n .compilerOptions ?? {})\n : {};\n\n const missing = Object.entries(HOST_COMPILER_RULES)\n .filter(([name, required]) => compilerOptions[name] !== required)\n .map(([name]) => name);\n\n if (missing.length === 0) return [];\n\n return [\n {\n check: 'project.host-compiler-rules',\n severity: 'warn',\n file: tsconfigPath,\n message:\n `${tsconfigName} does not set ${missing.join(' and ')} in ` +\n `compilerOptions. The gate re-checks your submission under the ` +\n `host's own stricter compiler rules — a project that typechecks ` +\n `cleanly under its own laxer tsconfig can still fail that pass.`,\n hint:\n `Add ${missing.map((name) => `\"${name}\": true`).join(', ')} to ` +\n `compilerOptions in ${tsconfigName}.`,\n },\n ];\n}\n\n/** `tsconfig.json` if present (matching what a bare `tsc --noEmit` would\n * auto-discover), else the first `tsconfig.*.json` found. */\nfunction findTsconfigPath(projectDir: string): string | undefined {\n let names: string[];\n try {\n names = fs\n .readdirSync(projectDir, { withFileTypes: true })\n .filter((entry) => entry.isFile() && TSCONFIG_PATTERN.test(entry.name))\n .map((entry) => entry.name);\n } catch {\n return undefined;\n }\n if (names.length === 0) return undefined;\n const name = names.includes('tsconfig.json')\n ? 'tsconfig.json'\n : [...names].sort()[0];\n return name ? path.join(projectDir, name) : undefined;\n}\n\n/**\n * A minimal, string-literal-aware strip of `//` and `/* *\\/` comments —\n * tsconfig.json is conventionally JSONC, but nothing in this package's\n * dependency graph parses it as such. Not a full JSONC parser: it does not\n * touch trailing commas beyond the one cleanup pass below, and it makes no\n * attempt to validate the result — `JSON.parse` does that, and a project\n * with genuinely malformed JSON falls through to this check's `info`-severity\n * catch, never a crash.\n */\nfunction stripJsonComments(source: string): string {\n let out = '';\n let inString = false;\n let quote = '';\n\n for (let i = 0; i < source.length; i++) {\n const char = source[i] ?? '';\n\n if (inString) {\n out += char;\n if (char === '\\\\') {\n out += source[++i] ?? '';\n continue;\n }\n if (char === quote) inString = false;\n continue;\n }\n\n if (char === '\"' || char === \"'\") {\n inString = true;\n quote = char;\n out += char;\n continue;\n }\n\n if (char === '/' && source[i + 1] === '/') {\n while (i < source.length && source[i] !== '\\n') i++;\n out += '\\n';\n continue;\n }\n\n if (char === '/' && source[i + 1] === '*') {\n i += 2;\n while (\n i < source.length &&\n !(source[i] === '*' && source[i + 1] === '/')\n ) {\n i++;\n }\n i++; // consume the closing '/'\n continue;\n }\n\n out += char;\n }\n\n // Trailing commas before a closing brace/bracket.\n return out.replace(/,(\\s*[}\\]])/g, '$1');\n}\n\n/**\n * True when the config source contains an `inline: [ … ]` array that lists\n * `@ekanos/sdk`. Textual on purpose — evaluating a partner's config module\n * would run arbitrary code; a lint-style scan does not.\n */\nfunction vitestInlinesSdk(source: string): boolean {\n const inlineArrayPattern = /inline\\s*:\\s*\\[([\\s\\S]*?)\\]/g;\n for (\n let match = inlineArrayPattern.exec(source);\n match !== null;\n match = inlineArrayPattern.exec(source)\n ) {\n if (/['\"]@ekanos\\/sdk['\"]/.test(match[1] ?? '')) return true;\n }\n return false;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekanos/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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": [
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Ekanos agents
|
|
2
|
+
|
|
3
|
+
This folder is where we design and change our Ekanos managed agents with the
|
|
4
|
+
`ekanos` CLI (`npx ekanos agents …`). The agents themselves live in Ekanos;
|
|
5
|
+
`agents/<key>/` holds a copy for review (`agent.json` + `AGENT.md`).
|
|
6
|
+
|
|
7
|
+
- To create or change an agent, use the `build-agent` skill: it checks what
|
|
8
|
+
the Source already has (`npx ekanos catalog`), interviews the user, then
|
|
9
|
+
drafts, applies and — only on the user's yes — publishes.
|
|
10
|
+
- For how Sources, team accounts, seats, integrations and connections fit
|
|
11
|
+
together, use the `ekanos-platform` skill.
|
|
12
|
+
- Ekanos docs are in the `ekanos-sdk-docs` MCP server (https://sdk.ekanos.dev).
|
|
13
|
+
Look things up there rather than guessing.
|
|
14
|
+
- Never ask for or write a secret; teams enter those in the Ekanos app.
|
|
@@ -34,14 +34,10 @@ already does what the user wants, propose setting it up (walk its
|
|
|
34
34
|
|
|
35
35
|
## Managed agents live in Ekanos
|
|
36
36
|
|
|
37
|
-
An agent is not code in this repo
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
lists them. Decide with the user, per product and MCP server, whether each
|
|
42
|
-
team connects its own (`required` or `optional`), and what teams may add
|
|
43
|
-
(`teamCustomization`, all off unless turned on). Show the user the applied draft and wait for their yes before
|
|
44
|
-
`publish --yes`; never `--overwrite` a version conflict you have not read.
|
|
37
|
+
An agent is not code in this repo. To create or change one — or decide who
|
|
38
|
+
gets it — use the `build-agent` skill; for how Sources, team accounts, seats
|
|
39
|
+
and connections fit together, the `ekanos-platform` skill. A folder that only
|
|
40
|
+
manages agents (no integration) is set up with `npx ekanos agents init`.
|
|
45
41
|
|
|
46
42
|
## The loop
|
|
47
43
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: build-agent
|
|
3
|
+
description: Interview-driven workflow for creating, changing and rolling out an Ekanos managed agent with `ekanos agents` — e.g. "create a customer support agent that uses Linear and Zendesk", "an agent that triages tickets", "update our support agent's instructions", "roll this agent out to every team", "roll back the agent". Use for any request to build or change an agent, its tools, approvals, connections or rollout. Not for building an integration (that is build-integration).
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Build an Ekanos managed agent
|
|
7
|
+
|
|
8
|
+
The user describes what they want in a sentence ("a customer support agent
|
|
9
|
+
that uses Linear, Zendesk and our Mintlify docs as the source of truth"). Turn
|
|
10
|
+
that into an agent Ekanos runs, by interviewing them — one topic at a time,
|
|
11
|
+
in plain words, with a recommendation each time — then drafting, applying and
|
|
12
|
+
(on their yes) publishing it. Read the `ekanos-platform` skill first if you
|
|
13
|
+
have not: Sources vs teams, seats and connections underlie every question
|
|
14
|
+
here.
|
|
15
|
+
|
|
16
|
+
Run the CLI as `npx ekanos … --json` and read the envelope
|
|
17
|
+
(`{ ok, data }` / `{ ok: false, error: { code, message, hint } }`); follow
|
|
18
|
+
every `hint`. Look things up in the `ekanos-sdk-docs` MCP server
|
|
19
|
+
(https://sdk.ekanos.dev) rather than guessing.
|
|
20
|
+
|
|
21
|
+
## Hard rules
|
|
22
|
+
|
|
23
|
+
- 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.
|
|
28
|
+
- Never `--overwrite` a version conflict (exit 7) you have not shown the user:
|
|
29
|
+
someone else changed the agent; pull it and reconcile.
|
|
30
|
+
- Use product and tool names exactly as the catalog lists them.
|
|
31
|
+
|
|
32
|
+
## 1. Ground yourself (before asking anything)
|
|
33
|
+
|
|
34
|
+
1. `npx ekanos sources --json` — which Sources, which seats. More than one?
|
|
35
|
+
Ask which Source this is for and pass `--source <slug>` on every command
|
|
36
|
+
after. A `developer` seat can apply drafts but not publish; say so early.
|
|
37
|
+
2. `npx ekanos catalog --source <slug> --json` — every product the Source
|
|
38
|
+
has, its tools, its **agent events** (what can start an agent) and its
|
|
39
|
+
`setupSteps`.
|
|
40
|
+
3. `npx ekanos agents list --source <slug> --json` — an agent that already
|
|
41
|
+
does this should be changed, not duplicated.
|
|
42
|
+
4. Map the request onto the catalog. For each thing the user named, say
|
|
43
|
+
which of these it is:
|
|
44
|
+
- **Ready** — the product is `enabled` and has the tools/events needed.
|
|
45
|
+
- **Not set up yet** — offered but not enabled, or needing a step from
|
|
46
|
+
someone else (`setupSteps` says who: `ekanos` = an Ekanos super admin).
|
|
47
|
+
- **Missing** — no product does it. Say so plainly. Options: build the
|
|
48
|
+
integration (the `build-integration` skill), use a remote MCP server
|
|
49
|
+
instead if the service offers one (common for docs and knowledge bases:
|
|
50
|
+
a Mintlify docs site serves one at `https://<docs-host>/mcp`), or ship
|
|
51
|
+
the agent without it and add it in a later version.
|
|
52
|
+
|
|
53
|
+
Tell the user this map in a few lines before the interview. Don't hide a gap
|
|
54
|
+
behind a plan that assumes it away.
|
|
55
|
+
|
|
56
|
+
## 2. The interview
|
|
57
|
+
|
|
58
|
+
One topic per message. Summarize what you heard before moving on. Defaults
|
|
59
|
+
in parentheses are the recommendation when the user has no preference.
|
|
60
|
+
|
|
61
|
+
1. **The job.** What should it do, for whom, and what does a good outcome
|
|
62
|
+
look like? What must it never do? Ask for one or two real examples (a
|
|
63
|
+
ticket, an issue) — they make the instructions concrete.
|
|
64
|
+
2. **Who gets it** — see `references/rollout.md`. Every team, or one team's
|
|
65
|
+
own? (Every team, when more than one team does this work the same way.)
|
|
66
|
+
Explain what a team sees and does, how updates reach teams, pinning and
|
|
67
|
+
rollback.
|
|
68
|
+
3. **What it can touch.** Which products and tools; reads vs changes.
|
|
69
|
+
(Reads only to start; add writes once it has earned trust.)
|
|
70
|
+
4. **What waits for a person.** Reads run on their own, changes wait for
|
|
71
|
+
approval — and any per-tool exceptions (a public reply to a customer
|
|
72
|
+
almost always waits). See `references/agent-json.md` → `policy`.
|
|
73
|
+
5. **Whose connections.** For each product and MCP server: each team
|
|
74
|
+
connects its own — `required` (the agent waits at "needs setup" until the
|
|
75
|
+
team connects it) or `optional` (its tools appear once connected). One
|
|
76
|
+
connection shared by every team is not available yet.
|
|
77
|
+
6. **What teams may add** — their own MCP servers, their own skills, more of
|
|
78
|
+
their own integrations, or narrowing (removing tools, more approvals). All
|
|
79
|
+
off unless there is a reason. Give one concrete example per knob.
|
|
80
|
+
7. **What starts it** — see `references/triggers.md`. Triggers belong to each
|
|
81
|
+
team and are set after turning the agent on; say what each team will have
|
|
82
|
+
to set up (an @mention, a label, a new ticket, a schedule) and what it
|
|
83
|
+
needs connected first.
|
|
84
|
+
|
|
85
|
+
## 3. Draft, apply, publish
|
|
86
|
+
|
|
87
|
+
1. Write `agents/<key>/agent.json` (the body; field guide in
|
|
88
|
+
`references/agent-json.md`) and `agents/<key>/AGENT.md` (the
|
|
89
|
+
instructions). `<key>` is lowercase-hyphenated, e.g. `support-triage`.
|
|
90
|
+
The instructions say: the job, the sources of truth and when to consult
|
|
91
|
+
each, what to do when unsure (ask, or hand to a person), tone, and what
|
|
92
|
+
never to do. Show the user both files and walk through them.
|
|
93
|
+
2. On their go: `npx ekanos agents apply agents/<key> --source <slug> --json`.
|
|
94
|
+
A refusal (exit 3) lists each problem with its path in the body — explain
|
|
95
|
+
and fix them together. The stored version is a **draft**: no team sees it.
|
|
96
|
+
3. `npx ekanos agents show <key> --source <slug>` — review the stored draft
|
|
97
|
+
with the user.
|
|
98
|
+
4. Ask: publish this version to every team now? Only on yes:
|
|
99
|
+
`npx ekanos agents publish <key> --source <slug> --to <n> --yes`.
|
|
100
|
+
5. Hand over the team-side checklist (`references/triggers.md` has the
|
|
101
|
+
per-integration parts): find it under Agents → "Provided for your team",
|
|
102
|
+
Turn on, connect what it needs, confirm access, add triggers. Suggest one
|
|
103
|
+
team pilots it first, and plan a first test run with them.
|
|
104
|
+
|
|
105
|
+
## Changing it later
|
|
106
|
+
|
|
107
|
+
`npx ekanos agents pull <key> --source <slug>` writes the latest version to
|
|
108
|
+
`agents/<key>/`; edit, `apply` (a new draft), `show`, then `publish` on the
|
|
109
|
+
user's yes. `rollback <key> --to <n> --yes` republishes an earlier version.
|
|
110
|
+
Teams that pinned a version stay on it and are told a newer one exists.
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# `agent.json` — the agent's body
|
|
2
|
+
|
|
3
|
+
`agents/<key>/agent.json` plus `agents/<key>/AGENT.md` (the instructions, used
|
|
4
|
+
when `agent.json` has no `instructions`). `ekanos agents pull` writes this
|
|
5
|
+
layout and records `baseVersion`; `apply` reads it back and refuses a
|
|
6
|
+
conflicting change unless `--overwrite`. Unknown fields are refused.
|
|
7
|
+
|
|
8
|
+
```json
|
|
9
|
+
{
|
|
10
|
+
"name": "Support triage",
|
|
11
|
+
"description": "Triages new support tickets and drafts replies.",
|
|
12
|
+
"model": null,
|
|
13
|
+
"maxTurns": 50,
|
|
14
|
+
"tools": { "mode": "custom", "products": ["zendesk"], "tools": ["linear_list_team_issues"] },
|
|
15
|
+
"policy": {
|
|
16
|
+
"reads": "auto",
|
|
17
|
+
"writes": "approve",
|
|
18
|
+
"overrides": { "zendesk_add_internal_note": "auto" }
|
|
19
|
+
},
|
|
20
|
+
"skills": [],
|
|
21
|
+
"connections": {
|
|
22
|
+
"zendesk": { "owner": "team", "team": "required" },
|
|
23
|
+
"linear": { "owner": "team", "team": "optional" }
|
|
24
|
+
},
|
|
25
|
+
"mcpServers": [
|
|
26
|
+
{
|
|
27
|
+
"key": "docs",
|
|
28
|
+
"name": "Product docs",
|
|
29
|
+
"url": "https://docs.example.com/mcp",
|
|
30
|
+
"auth": "none",
|
|
31
|
+
"owner": "team",
|
|
32
|
+
"team": "required",
|
|
33
|
+
"toolEffects": { "search": "read" }
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"teamCustomization": { "mcpServers": false, "skills": false, "integrations": false, "narrow": false }
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The tool names above are illustrations. Take real ones from
|
|
41
|
+
`ekanos catalog --json`.
|
|
42
|
+
|
|
43
|
+
## Fields
|
|
44
|
+
|
|
45
|
+
- **`name`** (≤80), **`description`** (≤500, or null).
|
|
46
|
+
- **`instructions`** — or `AGENT.md`. Up to 20,000 characters.
|
|
47
|
+
- **`model`** — a model id, or `null` for the house default (recommended
|
|
48
|
+
unless the user has a reason).
|
|
49
|
+
- **`maxTurns`** — 1–100, default 50: the most tool-use steps in one run.
|
|
50
|
+
- **`tools`** — what it may call at all:
|
|
51
|
+
- `read_only`: every read tool the team has connected.
|
|
52
|
+
- `all`: every tool, reads and changes.
|
|
53
|
+
- `custom`: whole `products` (slugs) plus single `tools` (names).
|
|
54
|
+
A tool it must never call is simply not granted — there is no deny list.
|
|
55
|
+
Extra team integrations (`teamCustomization.integrations`) need `custom`.
|
|
56
|
+
- **`policy`** — for each granted tool, `auto` (runs on its own) or
|
|
57
|
+
`approve` (waits for a person in the app). `reads`/`writes` set the
|
|
58
|
+
defaults by the tool's effect; `overrides` set single tools by name. An
|
|
59
|
+
undeclared tool effect counts as a write. MCP tools are named
|
|
60
|
+
`mcp__<server key>__<tool>` in `overrides`.
|
|
61
|
+
- **`skills`** — skill slugs the Source or the platform already has.
|
|
62
|
+
- **`connections`** — by product slug: `{ "owner": "team", "team": "required" | "optional" }`.
|
|
63
|
+
A product with no entry is `team`/`optional`. `owner: "source"` (one
|
|
64
|
+
connection for every team) is refused today.
|
|
65
|
+
- **`mcpServers`** — remote MCP servers (streamable HTTP, `https` only, up to
|
|
66
|
+
10): `key` (lowercase, ≤31), `name`, `url`, `auth` (`none` | `bearer` |
|
|
67
|
+
`oauth`, default `oauth`), `owner`/`team` as for connections, and
|
|
68
|
+
`toolEffects` (which tools only read; the rest count as writes). Turning
|
|
69
|
+
the agent on adds a team-owned server to the team; `auth: "none"` needs no
|
|
70
|
+
sign-in, others ask the team to sign in.
|
|
71
|
+
- **`teamCustomization`** — what teams may add, all `false` by default:
|
|
72
|
+
- `mcpServers` — attach their own MCP servers (their tools ask first until
|
|
73
|
+
the team marks one as read-only),
|
|
74
|
+
- `skills` — attach their own skills,
|
|
75
|
+
- `integrations` — grant more of their own connected products/tools
|
|
76
|
+
(needs `tools.mode: "custom"`),
|
|
77
|
+
- `narrow` — remove tools or make more of them wait for approval. Never
|
|
78
|
+
loosens.
|
|
79
|
+
|
|
80
|
+
## What the host checks on `apply`
|
|
81
|
+
|
|
82
|
+
Every product and tool must be in the Source's catalog, every skill must
|
|
83
|
+
exist for the Source, the model must be known, connection keys must be
|
|
84
|
+
catalog products. Each refusal comes back with a `path` into the body
|
|
85
|
+
(`tools.tools.0`, `skills.2`) — fix that field.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Who gets the agent: rollout options
|
|
2
|
+
|
|
3
|
+
Two shapes. Recommend one, and say why in terms of the user's teams.
|
|
4
|
+
|
|
5
|
+
## Provided for every team (an agent template)
|
|
6
|
+
|
|
7
|
+
Built with `ekanos agents` by a Source admin.
|
|
8
|
+
|
|
9
|
+
- **Defined once.** Instructions, model, tools, approvals, skills, which
|
|
10
|
+
connections it uses, and what teams may add.
|
|
11
|
+
- **Drafts are private.** `apply` stores a numbered version; no team sees it
|
|
12
|
+
until it is published.
|
|
13
|
+
- **Publishing reaches every team in the Source.** Each sees it under Agents →
|
|
14
|
+
"Provided for your team". There is no list of chosen teams: every team can
|
|
15
|
+
see it, and each decides whether to turn it on.
|
|
16
|
+
- **Turning it on** makes the team's own copy. The person who turns it on
|
|
17
|
+
becomes its owner; the agent uses that person's and that team's
|
|
18
|
+
connections, so a team that has not connected a `required` product sees
|
|
19
|
+
"needs setup" instead of a working agent.
|
|
20
|
+
- **Updates.** Each publish updates every team that follows the latest
|
|
21
|
+
version. A team can **pin** a version instead; it stays there and is told
|
|
22
|
+
when a newer one is published.
|
|
23
|
+
- **Rollback.** `ekanos agents rollback <key> --to <n> --yes` publishes an
|
|
24
|
+
earlier version to every team that follows it.
|
|
25
|
+
- **What stays the team's:** whether it is on, its triggers, its own
|
|
26
|
+
connections, its run history, and whatever `teamCustomization` lets it add.
|
|
27
|
+
- **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.
|
|
31
|
+
|
|
32
|
+
Good for: the same job done by several teams; a central owner who improves
|
|
33
|
+
it over time; consistent behaviour and approvals.
|
|
34
|
+
|
|
35
|
+
Piloting: publish, then have one team turn it on first. Teams that never
|
|
36
|
+
turn it on are unaffected. Or keep it as an applied draft until the pilot
|
|
37
|
+
plan is ready.
|
|
38
|
+
|
|
39
|
+
## A team's own agent
|
|
40
|
+
|
|
41
|
+
Built by a team in the app (Agents → New). Only that team has it; the Source
|
|
42
|
+
cannot update or roll it back, and nothing about it is visible to other teams.
|
|
43
|
+
|
|
44
|
+
Good for: one team's own process; experiments a team owns.
|
|
45
|
+
|
|
46
|
+
## Questions that decide it
|
|
47
|
+
|
|
48
|
+
- How many teams do this work? The same way?
|
|
49
|
+
- Who should own improvements — one central person, or each team?
|
|
50
|
+
- Must every team follow the same approval rules (e.g. customer-facing
|
|
51
|
+
replies always reviewed)?
|
|
52
|
+
- Do teams need to add their own tools or knowledge, and should they be
|
|
53
|
+
allowed to? (`teamCustomization`, all off by default.)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# What starts the agent: triggers
|
|
2
|
+
|
|
3
|
+
Triggers belong to each team. They are not part of the agent template: a team
|
|
4
|
+
adds them on its copy (Agents → the agent → Triggers) after turning it on.
|
|
5
|
+
Your job in the interview is to say which triggers each team will want, and
|
|
6
|
+
what it must connect first, so the team-side checklist is complete.
|
|
7
|
+
|
|
8
|
+
## Kinds a team can add
|
|
9
|
+
|
|
10
|
+
- **An integration's event** — any event a connected integration declares
|
|
11
|
+
(the catalog lists them per product as `agentEvents`). The team picks the
|
|
12
|
+
event and fills in its filter (the fields the event declares — e.g. labels,
|
|
13
|
+
a team, a group, tags). When the integration also has a **channel**, the
|
|
14
|
+
agent answers where the event happened (on the issue, on the ticket), and
|
|
15
|
+
that reply tool runs on its own by default.
|
|
16
|
+
- **Schedule** — a cron and time zone, with "what to do each time".
|
|
17
|
+
- **Webhook** — a URL any system can call, with "what to do with each
|
|
18
|
+
request".
|
|
19
|
+
- **Slack** — chosen channels and/or direct messages in a connected Slack.
|
|
20
|
+
|
|
21
|
+
## Integrations with agent events
|
|
22
|
+
|
|
23
|
+
Check `ekanos catalog <product> --source <slug> --json` for what is actually
|
|
24
|
+
there; these are the known shapes.
|
|
25
|
+
|
|
26
|
+
**Linear** (`linear.mentioned`, `linear.assigned`, `linear.issue_labeled`) —
|
|
27
|
+
the agent becomes a teammate in Linear: @mention it, assign it an issue, or
|
|
28
|
+
apply a label. Each team needs two things in the app: each person's own
|
|
29
|
+
Linear connection (for the tools), and the team's own Linear app for the
|
|
30
|
+
agent (client id, secret and webhook signing secret, entered in Ekanos).
|
|
31
|
+
Guide: https://sdk.ekanos.dev/guides/linear-agent
|
|
32
|
+
|
|
33
|
+
**Zendesk** (`zendesk.ticket_created`, `zendesk.comment_added`; filters such
|
|
34
|
+
as brand, group, tags, organization) — when present in the catalog, each team
|
|
35
|
+
connects its own Zendesk (subdomain plus an OAuth client it creates in
|
|
36
|
+
Zendesk) and adds a Zendesk webhook pointing at its Ekanos endpoint. The agent
|
|
37
|
+
answers on the ticket as a public reply or an internal note — recommend
|
|
38
|
+
internal notes, or approval on public replies, until the team trusts it.
|
|
39
|
+
|
|
40
|
+
## Checklist to hand each team
|
|
41
|
+
|
|
42
|
+
1. Agents → "Provided for your team" → Turn on.
|
|
43
|
+
2. Connect every `required` product and sign in to any MCP server that needs
|
|
44
|
+
it (the agent shows what is missing).
|
|
45
|
+
3. Do each integration's own setup (team app, webhook) from its guide.
|
|
46
|
+
4. Confirm access if asked.
|
|
47
|
+
5. Add the triggers above, then run one real example and review it.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ekanos-platform
|
|
3
|
+
description: How Ekanos is organized — Sources, team accounts, seats and roles, products (integrations and experiences), connections, and managed agents — and which of those the ekanos CLI can change. Use whenever a request involves who gets something (one team or every team), who can set it up, whether something is "connected" or "enabled", or explaining Ekanos to the user before building an integration or an agent.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# How Ekanos is organized
|
|
7
|
+
|
|
8
|
+
Explain these in plain words when a decision depends on them. Docs:
|
|
9
|
+
https://sdk.ekanos.dev/concepts/sources-and-teams (or search the
|
|
10
|
+
`ekanos-sdk-docs` MCP server). Never guess a rule that is not here or in the
|
|
11
|
+
docs — ask, or check the CLI.
|
|
12
|
+
|
|
13
|
+
## Sources and team accounts
|
|
14
|
+
|
|
15
|
+
- A **Source** is one Ekanos customer's whole deployment: its brand, domain,
|
|
16
|
+
users and catalog. Ekanos is white-label, so the people inside a Source see
|
|
17
|
+
only their own brand.
|
|
18
|
+
- A Source holds many **team accounts**. A team is where the work happens:
|
|
19
|
+
its members, its data, its connections, its agents. Teams in the same Source
|
|
20
|
+
do not see each other's data.
|
|
21
|
+
- Team members have one of three team roles — **owner**, **admin**,
|
|
22
|
+
**member** — which govern that team only. Being a team owner gives no
|
|
23
|
+
authority over the Source.
|
|
24
|
+
- **"Source" is admin vocabulary.** Team members never see the word. When you
|
|
25
|
+
describe what a team will see, say "provided for your team", never "the
|
|
26
|
+
Source's agent". Use "Source" freely with the person you are helping only
|
|
27
|
+
if they are a Source admin or developer (they are, if the CLI works for
|
|
28
|
+
them).
|
|
29
|
+
|
|
30
|
+
## Seats: who can use the CLI on a Source
|
|
31
|
+
|
|
32
|
+
`npx ekanos sources --json` lists the user's seats.
|
|
33
|
+
|
|
34
|
+
| Seat | Can |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `developer` | Submit integrations; read the catalog; apply agent **drafts** |
|
|
37
|
+
| `source_admin` | Everything a developer can, plus **publish** agents to every team and manage the Source's roster |
|
|
38
|
+
|
|
39
|
+
Only an Ekanos super admin grants seats. If the user lacks the seat a step
|
|
40
|
+
needs, say which and stop — there is no way around it from the CLI.
|
|
41
|
+
|
|
42
|
+
## Products: integrations and experiences
|
|
43
|
+
|
|
44
|
+
Everything in the catalog (`npx ekanos catalog --source <slug> --json`) is a
|
|
45
|
+
**product** of one of two kinds:
|
|
46
|
+
|
|
47
|
+
- **Integration** — a connection to a third party (Linear, Zendesk, Slack…).
|
|
48
|
+
Brings tools an agent can call, and may declare **agent events** that start
|
|
49
|
+
agents (`linear.mentioned`, `zendesk.ticket_created`…).
|
|
50
|
+
- **Experience** — an Ekanos-native capability (Agents, Workflows, Home…).
|
|
51
|
+
Never "connected"; it is simply on or off for the Source.
|
|
52
|
+
|
|
53
|
+
## Is it usable? Four separate facts
|
|
54
|
+
|
|
55
|
+
Ask them in this order; each catalog product reports where it stands
|
|
56
|
+
(`availability` and `setupSteps`).
|
|
57
|
+
|
|
58
|
+
1. **Given to the Source** — `availability` is `enabled` (on), `turned-off`
|
|
59
|
+
(given, but a Source setting or plan has it off) or `available` (Ekanos
|
|
60
|
+
offers it; this Source has not been given it). A product missing from the
|
|
61
|
+
catalog altogether is not offered to this Source. Giving a Source a product
|
|
62
|
+
is an Ekanos super admin's step (`setupSteps` names it with actor
|
|
63
|
+
`ekanos`) — not something a Source admin or the CLI can do.
|
|
64
|
+
2. **Connected by a team** — a team (or a person, for per-person
|
|
65
|
+
integrations) signed in to the third party. Connections belong to a team,
|
|
66
|
+
never to the Source: two teams using Linear each connect their own.
|
|
67
|
+
3. **Set up by the team** where needed — some integrations have each team
|
|
68
|
+
bring its own app (a team-provided app: client id, secret, webhook signing
|
|
69
|
+
secret entered in Ekanos). Those secrets are typed into the app, never
|
|
70
|
+
into a chat or a file.
|
|
71
|
+
4. **Used by an agent** — granted in the agent's tools.
|
|
72
|
+
|
|
73
|
+
A product that is "enabled" is not therefore "connected". Say which one you
|
|
74
|
+
mean.
|
|
75
|
+
|
|
76
|
+
## Managed agents
|
|
77
|
+
|
|
78
|
+
An agent is **data held by Ekanos**, not code: instructions, a model, the
|
|
79
|
+
tools it may use, which calls wait for a person's approval, and skills. It is
|
|
80
|
+
changed through the CLI or the app, with no deploy.
|
|
81
|
+
|
|
82
|
+
- **Provided for every team** — a Source admin defines it once with
|
|
83
|
+
`ekanos agents` (a template). Each team sees it under Agents → "Provided for
|
|
84
|
+
your team" and turns it on; it then runs on that team's own connections.
|
|
85
|
+
Publishing a new version updates every team that follows it.
|
|
86
|
+
- **A team's own** — built by a team in the app (Agents → New). Only that team
|
|
87
|
+
has it, and the Source cannot update it.
|
|
88
|
+
|
|
89
|
+
The `build-agent` skill walks through choosing between these and building one.
|
|
90
|
+
|
|
91
|
+
## Where each thing is done
|
|
92
|
+
|
|
93
|
+
| Done by | Where |
|
|
94
|
+
|---|---|
|
|
95
|
+
| Making a product available to a Source; granting seats | Ekanos super admin, in Ekanos's admin |
|
|
96
|
+
| Building or updating an integration | A developer, with `ekanos validate` / `ekanos publish` |
|
|
97
|
+
| Agents provided for every team | A Source admin, with `ekanos agents` |
|
|
98
|
+
| Connecting an integration; turning on a provided agent; its triggers | Each team, in the app |
|