@ekanos/cli 0.11.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/apps.d.ts +6 -2
- package/dist/commands/apps.js +249 -4
- package/dist/commands/apps.js.map +1 -1
- package/dist/commands/routes.d.ts +23 -0
- package/dist/commands/routes.js +277 -0
- package/dist/commands/routes.js.map +1 -0
- package/dist/connections-api.d.ts +125 -0
- package/dist/connections-api.js +156 -0
- package/dist/connections-api.js.map +1 -0
- package/dist/index.js +30 -2
- package/dist/index.js.map +1 -1
- package/dist/routes-api.d.ts +105 -0
- package/dist/routes-api.js +147 -0
- package/dist/routes-api.js.map +1 -0
- package/package.json +2 -2
- package/templates/package.json.tmpl +4 -4
- package/templates/skills/build-agent/SKILL.md +4 -2
- package/templates/skills/build-agent/references/agent-json.md +7 -2
- package/templates/skills/build-agent/references/set-up-an-integration.md +57 -2
- package/templates/skills/build-agent/references/skills.md +7 -4
- package/templates/skills/ekanos-platform/SKILL.md +8 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"routes-api.js","sourceRoot":"","sources":["../src/routes-api.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod';\n\nimport { request } from './auth/ekanos-api';\nimport {\n authRequiredError,\n forbiddenError,\n networkError,\n notFoundError,\n usageError,\n} from './errors';\n\n/**\n * The CLI's client for a Source's event routing (plan 241.4): which team each\n * value of an integration's route key (Zendesk's organization) belongs to,\n * the Source's webhook endpoint, and the events no team is mapped to yet.\n *\n * GET /api/partner/routes?source=<slug>[&product=<slug>]\n * PUT /api/partner/routes/<product>?source=<slug> { value, team, label? }\n * DELETE /api/partner/routes/<product>?source=<slug>&value=<value>\n * GET /api/partner/routes/<product>/options?source=<slug>\n * POST /api/partner/routes/<product>/webhook?source=<slug>[&webhook=<id>]\n * GET /api/partner/routes/unrouted?source=<slug>[&product=<slug>]\n * POST /api/partner/routes/unrouted/<id>/replay?source=<slug>\n *\n * → 401 / 3xx refresh once and retry\n * → 403 { code: \"SOURCE_ADMIN_REQUIRED\" | \"MFA_REQUIRED\" | \"FORBIDDEN\" }\n * → 404 { code: \"PRODUCT_NOT_ENTITLED\" | \"TEAM_NOT_FOUND\" | \"ROUTE_NOT_FOUND\"\n * | \"EVENT_NOT_FOUND\" | \"SOURCE_NOT_FOUND\" }\n * → 409 { code: \"NO_ROUTING\" | \"NO_WEBHOOK\" }\n *\n * The contract's shapes live here rather than in `@ekanos/integration-schema`,\n * so the routing commands ship with the CLI alone.\n */\n\nconst SourceRefSchema = z.object({\n id: z.string(),\n slug: z.string(),\n name: z.string().nullable(),\n});\n\nconst RouteSchema = z.object({\n product: z.string(),\n key: z.string(),\n value: z.string(),\n label: z.string().nullable(),\n team: z.object({\n id: z.string(),\n slug: z.string().nullable(),\n name: z.string(),\n }),\n updatedAt: z.string(),\n});\nexport type SourceEventRoute = z.infer<typeof RouteSchema>;\n\nexport const RouteListSchema = z.object({\n source: SourceRefSchema,\n routes: z.array(RouteSchema),\n});\n\nexport const RouteSetSchema = z.object({\n route: RouteSchema,\n replaced: z.boolean(),\n});\n\nexport const RouteRemoveSchema = z.object({ removed: z.number() });\n\nexport const RouteOptionsSchema = z.object({\n key: z.string(),\n options: z\n .array(z.object({ value: z.string(), label: z.string() }))\n .nullable(),\n});\n\nexport const RouteWebhookSchema = z.object({\n webhookId: z.string(),\n url: z.string(),\n signature: z\n .object({ header: z.string(), secretName: z.string() })\n .nullable(),\n});\n\nconst UnroutedEventSchema = z.object({\n id: z.string(),\n product: z.string(),\n eventId: z.string(),\n routeKey: z.string().nullable(),\n routeValue: z.string().nullable(),\n createdAt: z.string(),\n});\nexport type UnroutedEvent = z.infer<typeof UnroutedEventSchema>;\n\nexport const UnroutedListSchema = z.object({\n events: z.array(UnroutedEventSchema),\n});\n\nexport const ReplaySchema = z.object({\n status: z.enum(['routed', 'already-routed', 'unrouted']),\n});\n\nexport type RoutesApiResult<T> =\n | { status: 'ok'; data: T }\n | { status: 'unauthorized' };\n\nconst BASE = '/api/partner/routes';\n\nexport async function routesCall<T>(\n host: string,\n accessToken: string,\n schema: z.ZodType<T>,\n init: {\n method: 'GET' | 'PUT' | 'POST' | 'DELETE';\n path?: string;\n query: Record<string, string | undefined>;\n body?: unknown;\n },\n): Promise<RoutesApiResult<T>> {\n const query = Object.fromEntries(\n Object.entries(init.query).filter(\n (entry): entry is [string, string] => entry[1] !== undefined,\n ),\n );\n const url = `${host}${BASE}${init.path ?? ''}?${new URLSearchParams(query)}`;\n const response = await request(url, {\n method: init.method,\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\n ...(init.body === undefined\n ? {}\n : { 'content-type': 'application/json' }),\n },\n body: init.body === undefined ? undefined : JSON.stringify(init.body),\n });\n\n if (\n response.status === 401 ||\n (response.status >= 300 && response.status < 400)\n ) {\n return { status: 'unauthorized' };\n }\n\n const body = await readOptionalJson(response);\n const code = typeof body.code === 'string' ? body.code : null;\n const message =\n typeof body.error === 'string' && body.error ? body.error : null;\n\n if (response.status === 403 && code === 'MFA_REQUIRED') {\n throw authRequiredError(\n message ?? `${host} requires multi-factor authentication.`,\n 'Complete MFA in the browser, then run \"ekanos login\" again.',\n );\n }\n if (response.status === 403) {\n throw forbiddenError(\n message ?? 'Not permitted on this Source.',\n code === 'SOURCE_ADMIN_REQUIRED'\n ? \"Only a Source admin manages the Source's routing. Ask one to run it.\"\n : 'Run \"ekanos sources\" to list the sources you hold a seat on.',\n );\n }\n if (response.status === 404) {\n throw notFoundError(\n message ?? 'Not found.',\n code === 'TEAM_NOT_FOUND'\n ? 'Run \"ekanos teams list\" to see the teams on this Source.'\n : code === 'ROUTE_NOT_FOUND'\n ? '\"ekanos routes list\" shows the mapped values.'\n : code === 'EVENT_NOT_FOUND'\n ? '\"ekanos routes unrouted\" lists the events waiting for a mapping.'\n : code === 'PRODUCT_NOT_ENTITLED'\n ? 'Run \"ekanos catalog\" to see what the Source has.'\n : 'Run \"ekanos sources\" to list the sources you hold a seat on.',\n );\n }\n if (response.status === 409) {\n throw usageError(\n message ?? 'The integration does not route events.',\n code === 'NO_WEBHOOK'\n ? 'Name the webhook: \"ekanos routes webhook <product> --webhook <id>\".'\n : 'Only an integration that declares a Source connection and a route key routes events.',\n );\n }\n if (response.status === 400) {\n throw usageError(\n message ?? 'The host refused the request.',\n 'See \"ekanos routes --help\".',\n );\n }\n if (!response.ok) {\n throw networkError(\n `${host}${BASE} responded ${response.status}.`,\n response.status >= 500\n ? 'The Ekanos deployment returned a server error. Retry shortly.'\n : 'Confirm the host runs a build that includes the routes route.',\n );\n }\n\n const parsed = schema.safeParse(body.data);\n if (!parsed.success) {\n throw networkError(\n `${host}${BASE} answered in a shape this CLI does not read.`,\n 'Run \"ekanos upgrade\" — the CLI and the host disagree on the routes contract.',\n );\n }\n return { status: 'ok', data: parsed.data };\n}\n\nasync function readOptionalJson(\n response: Response,\n): Promise<Record<string, unknown>> {\n try {\n const parsed: unknown = await response.json();\n return typeof parsed === 'object' && parsed !== null\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ekanos/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "The Ekanos partner toolchain CLI: scaffold, validate, and test an Ekanos integration against the published @ekanos packages. Agent-native — every verb speaks JSON with a stable exit-code taxonomy.",
|
|
6
6
|
"keywords": [
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@clack/prompts": "1.7.0",
|
|
38
|
-
"@ekanos/integration-schema": "0.
|
|
38
|
+
"@ekanos/integration-schema": "0.15.0",
|
|
39
39
|
"esbuild": "0.28.1",
|
|
40
40
|
"picocolors": "1.1.1"
|
|
41
41
|
},
|
|
@@ -12,12 +12,12 @@
|
|
|
12
12
|
"prepare": "node scripts/setup-hooks.mjs"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@ekanos/sdk": "^0.
|
|
16
|
-
"@ekanos/ui": "^0.
|
|
15
|
+
"@ekanos/sdk": "^0.15.0",
|
|
16
|
+
"@ekanos/ui": "^0.15.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@ekanos/cli": "^0.
|
|
20
|
-
"@ekanos/harness": "^0.
|
|
19
|
+
"@ekanos/cli": "^0.12.0",
|
|
20
|
+
"@ekanos/harness": "^0.15.0",
|
|
21
21
|
"@hookform/resolvers": "^5.2.2",
|
|
22
22
|
"@tailwindcss/postcss": "^4.3.3",
|
|
23
23
|
"@tanstack/react-query": "^5.101.4",
|
|
@@ -99,8 +99,10 @@ in parentheses are the recommendation when the user has no preference.
|
|
|
99
99
|
connection lives (`references/set-up-an-integration.md` § 2): each team
|
|
100
100
|
connects its own, and it is `required` (the agent waits at "needs setup"
|
|
101
101
|
until the team connects it) or `optional` (its tools appear once
|
|
102
|
-
connected).
|
|
103
|
-
|
|
102
|
+
connected). Or, for integrations that support it, the Source connects it
|
|
103
|
+
once for every team (`owner: "source"`): each team's agent then works only
|
|
104
|
+
inside what is routed to that team (`references/set-up-an-integration.md`
|
|
105
|
+
§ 5). Then offer to set up any that are not ready.
|
|
104
106
|
6. **What teams may add** — their own MCP servers, their own skills, more of
|
|
105
107
|
their own integrations, or narrowing (removing tools, more approvals). All
|
|
106
108
|
off unless there is a reason. Give one concrete example per knob.
|
|
@@ -80,8 +80,13 @@ The tool names above are illustrations. Take real ones from
|
|
|
80
80
|
folders in `agents/<key>/skills/<slug>/` are pushed and added here by
|
|
81
81
|
`agents apply` (see `skills.md`).
|
|
82
82
|
- **`connections`** — by product slug: `{ "owner": "team", "team": "required" | "optional" }`.
|
|
83
|
-
A product with no entry is `team`/`optional`. `owner: "source"`
|
|
84
|
-
connection for every team
|
|
83
|
+
A product with no entry is `team`/`optional`. `{ "owner": "source" }` is
|
|
84
|
+
one connection the Source admin makes for every team (`ekanos apps
|
|
85
|
+
connect`), used only by this provided agent and limited per team by the
|
|
86
|
+
Source's routing (`ekanos routes`); `apply` refuses it for an integration
|
|
87
|
+
that does not declare `sourceConnection`. Teams cannot add a Source-owned
|
|
88
|
+
product in their overlay. `mcpServers` with `owner: "source"` are still
|
|
89
|
+
refused.
|
|
85
90
|
- **`mcpServers`** — remote MCP servers (streamable HTTP, `https` only, up to
|
|
86
91
|
10): `key` (lowercase, ≤31), `name`, `url`, `auth` (`none` | `bearer` |
|
|
87
92
|
`oauth`, default `oauth`), `owner`/`team` as for connections, and
|
|
@@ -35,12 +35,14 @@ integration: its members, its data and its connections.
|
|
|
35
35
|
|---|---|---|---|
|
|
36
36
|
| **Each team, through the Source's app** | One app, created once for the whole Source under the Source's own name. Each team then signs in through it. | The Source admin creates the app (`references/app-setup.md`, `ekanos apps set`); each team connects | Linear, Slack: products where every team is a separate workspace |
|
|
37
37
|
| **Each team, with its own app** | Each team creates its own app in the provider and enters its credentials | Each team, or the Source admin on the team's behalf with `ekanos teams` (below) | Zendesk: each bank or business has its own Zendesk |
|
|
38
|
-
| **One connection for the whole Source** | Every team's provided agent uses the Source's single connection, limited to
|
|
38
|
+
| **One connection for the whole Source** | Every team's provided agent uses the Source's single connection, limited to the records routed to that team (for Zendesk, the tickets of the organizations mapped to it) | The Source admin, once, plus a routing table | One shared Zendesk for many teams (§ 5). Only for integrations that declare it (Zendesk from 0.2.0; `agents apply` refuses the others), and only for **provided agents**: a team's own agents, widgets and assistant never use it |
|
|
39
39
|
|
|
40
40
|
The agent records the choice in `agent.json` → `connections`:
|
|
41
41
|
`{ "zendesk": { "owner": "team", "team": "required" } }` means each team
|
|
42
42
|
uses its own Zendesk and the agent waits at "needs setup" until it is
|
|
43
|
-
connected (`references/agent-json.md`).
|
|
43
|
+
connected (`references/agent-json.md`). `{ "zendesk": { "owner": "source" } }`
|
|
44
|
+
means every team's copy of the agent uses the Source's one Zendesk; teams
|
|
45
|
+
connect nothing for it.
|
|
44
46
|
|
|
45
47
|
## 3. Walk it, one step per message
|
|
46
48
|
|
|
@@ -90,6 +92,59 @@ pilot), the team-side steps can be done from the CLI:
|
|
|
90
92
|
5. Someone on the team opens the link from `teams show` and clicks Connect.
|
|
91
93
|
6. `teams show` again: every step ✓.
|
|
92
94
|
|
|
95
|
+
## 5. One connection for the whole Source
|
|
96
|
+
|
|
97
|
+
Use it when one account at the provider serves many teams: one Zendesk where
|
|
98
|
+
each bank is an organization. The Source admin connects once; events are
|
|
99
|
+
routed to teams by a value inside them (for Zendesk, the ticket's
|
|
100
|
+
**organization**); each team's provided agent then works only inside what is
|
|
101
|
+
routed to that team. Say all three parts before starting, and say plainly
|
|
102
|
+
what it does **not** do: a team's own agents, widgets and assistant never see
|
|
103
|
+
the Source's connection.
|
|
104
|
+
|
|
105
|
+
1. **Connect, once, as the Source.** For an integration where each customer
|
|
106
|
+
brings its own app (Zendesk), first create that app in the shared account,
|
|
107
|
+
exactly as in the worked example below (OAuth client, Confidential), but
|
|
108
|
+
once, for the Source. Then, **in their own terminal**:
|
|
109
|
+
`npx @ekanos/cli apps connect <product> --source <slug>`. It asks for the
|
|
110
|
+
app's fields it doesn't have yet (Zendesk: subdomain, client ID, client
|
|
111
|
+
secret, webhook signing secret; secrets hidden, never as flags, and it
|
|
112
|
+
refuses to run inside Claude), prints the redirect URL to register and the
|
|
113
|
+
consent link, then waits up to ten minutes until it is connected. Whoever
|
|
114
|
+
clicks the link is the account the integration acts as for every team, so
|
|
115
|
+
suggest a dedicated seat ("Support agent"). `apps list` shows every
|
|
116
|
+
integration the Source can connect this way. Check with
|
|
117
|
+
`npx @ekanos/cli apps show <product> --source <slug>`: connected, by whom,
|
|
118
|
+
when. In the app the same lives in the Source admin area under
|
|
119
|
+
**Shared integrations › Connections**.
|
|
120
|
+
2. **The Source's webhook.** `npx @ekanos/cli routes webhook <product> --source <slug>`
|
|
121
|
+
prints one URL, **once**, for the whole Source. Paste it into the
|
|
122
|
+
provider at that moment (for Zendesk, the webhook the triggers notify).
|
|
123
|
+
The team webhooks from § 4 are not used for this connection.
|
|
124
|
+
3. **Map values to teams.** `npx @ekanos/cli routes options <product> --source <slug>`
|
|
125
|
+
lists what can be mapped (Zendesk organizations, with names), then one
|
|
126
|
+
line per team:
|
|
127
|
+
`npx @ekanos/cli routes set <product> <value> <team> --source <slug>`.
|
|
128
|
+
`routes list` shows the table. One value goes to exactly one team.
|
|
129
|
+
4. **The agent.** In `agent.json`, `connections.<product>` is
|
|
130
|
+
`{ "owner": "source" }`; `agents apply` accepts it only when the
|
|
131
|
+
integration supports a Source connection. Teams turn the agent on as
|
|
132
|
+
usual; they connect nothing for that product.
|
|
133
|
+
5. **Try it.** A ticket from a mapped organization starts that team's agent
|
|
134
|
+
only. A ticket from an organization nobody mapped starts **nothing**: the
|
|
135
|
+
integration leaves its own fixed note (Zendesk: an internal note saying
|
|
136
|
+
no team has it yet), and it is listed by
|
|
137
|
+
`npx @ekanos/cli routes unrouted <product> --source <slug>`. Map the
|
|
138
|
+
value, then `routes replay <event-id>` to send it on once.
|
|
139
|
+
|
|
140
|
+
What is enforced, so you can say it accurately: the host routes each event
|
|
141
|
+
to exactly one team or to none (never to a default team), and gives a
|
|
142
|
+
team's agent only that team's mapped values (`ctx.routing`); the
|
|
143
|
+
integration's tools keep to them (for Zendesk, reads are limited to the
|
|
144
|
+
team's organizations, and anything that reads or changes one ticket first
|
|
145
|
+
checks its organization and refuses otherwise). The agent's policy,
|
|
146
|
+
approvals and audit apply exactly as on a team connection.
|
|
147
|
+
|
|
93
148
|
## Worked example: Zendesk for one team
|
|
94
149
|
|
|
95
150
|
The steps below are what Zendesk's own setup says, in order. Take the URLs
|
|
@@ -70,9 +70,12 @@ whose `teamCustomization.skills` is on.
|
|
|
70
70
|
|
|
71
71
|
## Limits today
|
|
72
72
|
|
|
73
|
-
- Prompt skills only
|
|
74
|
-
|
|
75
|
-
`
|
|
76
|
-
|
|
73
|
+
- Prompt skills only. At run time the agent gets the whole folder: loading
|
|
74
|
+
the skill returns `SKILL.md`'s instructions plus the list of every other
|
|
75
|
+
file (`references/`, `assets/`, …), and it reads one on demand
|
|
76
|
+
(`read_skill_file`) when the instructions point to it — so keep `SKILL.md`
|
|
77
|
+
short and name the reference to open, e.g. `references/policy-2026.md`.
|
|
78
|
+
Text files are read in parts of about 12,000 characters; binary files are
|
|
79
|
+
listed but cannot be read as text.
|
|
77
80
|
- A `scripts/` folder is refused: executable skills need the in-app review.
|
|
78
81
|
- Up to 100 files, 1 MB per file, 3 MB per skill.
|
|
@@ -97,8 +97,12 @@ Ask them in this order; each catalog product reports where it stands
|
|
|
97
97
|
connection: two teams using Linear each connect their own. A Source admin
|
|
98
98
|
can do a team's setup for it from the terminal (`ekanos teams show`,
|
|
99
99
|
`ekanos teams app set`, `ekanos teams webhook`), but signing in is a click
|
|
100
|
-
by someone on the team.
|
|
101
|
-
|
|
100
|
+
by someone on the team. For integrations that support it, a Source admin
|
|
101
|
+
can instead connect **once for the whole Source** (`ekanos apps connect`),
|
|
102
|
+
for provided agents only: events are routed to teams by a value inside
|
|
103
|
+
them (`ekanos routes`, Zendesk by organization), and each team's agent
|
|
104
|
+
works only inside its own routed values. A team never sees that
|
|
105
|
+
connection.
|
|
102
106
|
3. **Set up by the team** where needed — some integrations have each team
|
|
103
107
|
bring its own app (a team-provided app: client id, secret, webhook signing
|
|
104
108
|
secret entered in Ekanos). Those secrets are typed into the app, never
|
|
@@ -213,6 +217,8 @@ is refused (`USER_NOT_IN_SOURCE`): `ekanos teams list` shows the teams.
|
|
|
213
217
|
| Skills (SKILL.md folders) for every team, or one team's own | A Source admin, with `ekanos skills push` (`--team <slug>` for one team) |
|
|
214
218
|
| Who decides agent drafts that wait for a person | A Source admin, with `ekanos reviewers` |
|
|
215
219
|
| An integration's app for the whole Source (client ID and secret) | A Source admin, with `ekanos apps set` at their own terminal |
|
|
220
|
+
| One connection for the whole Source, used by provided agents in every team | A Source admin, with `ekanos apps connect` (status: `ekanos apps show`), or **Shared integrations › Connections** in the Source admin area |
|
|
221
|
+
| Which team gets which events on that connection (Zendesk organization → team) | A Source admin, with `ekanos routes` (`webhook`, `options`, `set`, `list`, `remove`, `unrouted`, `replay`), or **Shared integrations › Routing** in the Source admin area |
|
|
216
222
|
| A team's own app for an integration (client ID, secrets, webhook URL) | The team in the app, or a Source admin with `ekanos teams` |
|
|
217
223
|
| A provided agent's triggers, links and approvals | A Source admin, in the template (`ekanos agents triggers / links / policy / overrides`); teams change them only where `teamOverrides` allows |
|
|
218
224
|
| Creating a team, inviting its members, changing their roles | A Source admin, with `ekanos teams create` / `ekanos teams members` (or the team's owner in the app) |
|