@ekanos/cli 0.4.1 → 0.5.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 CHANGED
@@ -27,6 +27,8 @@ npx @ekanos/cli dev
27
27
  | `whoami` | Print the identity the stored session authenticates as, plus a compact `seats` list (slug + roles) from the same endpoint `sources` uses — degrades to `seats: null` rather than failing if that lookup is unavailable. |
28
28
  | `status` | The read verb: login state for the resolved host, this project's `ekanos.json`, and its submissions on the host (`GET /api/partner/submissions`). Never fails just because you are logged out — it reports `authenticated: false` instead. |
29
29
  | `sources` | List every Ekanos source you hold a developer or source-admin seat on (`GET /api/partner/sources`), with a marker on this project's current publish target. |
30
+ | `catalog` | `ekanos catalog [<product-slug>]` — what a source can already use (`GET /api/partner/catalog`): every product enabled, switched off or available to enable, with its tools, agent triggers and generated setup steps. Check it before building something the source may already have. |
31
+ | `agents` | `ekanos agents list | show | pull | apply | publish | rollback` — the source's agents, held by Ekanos (`/api/partner/agents`). `apply` stores a new version as a draft, `publish` makes a version the one every team runs, `rollback` publishes an earlier one. Either seat applies; only the source admin publishes.
30
32
  | `use` | `ekanos use <source-slug>` — set this project's publish target to a source you hold a seat on. Refuses (`forbidden`, with your actual seats in the hint) for a slug you don't hold a seat on, so a typo can't silently repoint a project. |
31
33
  | `publish` | Validate, pack and submit the integration to an Ekanos host — refusing (`forbidden`) a target you hold no seat on, and confirming an ambiguous multi-seat target before submitting. See [Publish target verification](#publish-target-verification). |
32
34
  | `upgrade` | Move this toolchain to a coherent target version, with verify-and-rollback. `--check` reports current vs. target and changes nothing; a bare `upgrade` installs, verifies (`validate` + the test script), and rolls back automatically on any failure. See [Keeping the toolchain current](#keeping-the-toolchain-current). |
@@ -419,7 +421,7 @@ ekanos logout
419
421
 
420
422
  ### Host resolution
421
423
 
422
- `logout`, `whoami`, `status`, `sources`, `use` and `publish` resolve the Ekanos
424
+ `logout`, `whoami`, `status`, `sources`, `catalog`, `agents`, `use` and `publish` resolve the Ekanos
423
425
  host in this order:
424
426
 
425
427
  1. the `--host` flag
@@ -0,0 +1,35 @@
1
+ import { type AgentTemplateApplyRequest, type AgentTemplateList, type AgentTemplateResult, type AgentTemplateShow } from '@ekanos/integration-schema';
2
+ /**
3
+ * The CLI's client for Source agent templates (plan 239.0 / 239.2).
4
+ *
5
+ * ---------------------------------------------------------------------------
6
+ * WIRE CONTRACT — the `AgentTemplate*` schemas in `@ekanos/integration-schema`,
7
+ * which the host's `/api/partner/agents` routes build and this parses.
8
+ *
9
+ * GET /api/partner/agents?source=<slug|id> → { ok, data: AgentTemplateList }
10
+ * GET /api/partner/agents/<key>?source=<slug|id> → { ok, data: AgentTemplateShow }
11
+ * POST /api/partner/agents AgentTemplateApplyRequest → { ok, data: AgentTemplateResult }
12
+ * POST /api/partner/agents/<key>/publish { source, version? } → { ok, data: AgentTemplateResult }
13
+ * POST /api/partner/agents/<key>/rollback { source, version } → { ok, data: AgentTemplateResult }
14
+ *
15
+ * → 401 / 3xx refresh once and retry
16
+ * → 400 { code: "INVALID_AGENT", issues: AgentTemplateIssue[] }
17
+ * → 403 { code: "MFA_REQUIRED" | "FORBIDDEN" | "SOURCE_ADMIN_REQUIRED" }
18
+ * → 404 { code: "SOURCE_NOT_FOUND" | "TEMPLATE_NOT_FOUND" | "VERSION_NOT_FOUND" }
19
+ * → 409 { code: "VERSION_CONFLICT", latestVersion }
20
+ * ---------------------------------------------------------------------------
21
+ */
22
+ export type AgentsApiResult<T> = {
23
+ status: 'ok';
24
+ data: T;
25
+ } | {
26
+ status: 'unauthorized';
27
+ };
28
+ export declare function listAgentTemplates(host: string, accessToken: string, source: string): Promise<AgentsApiResult<AgentTemplateList>>;
29
+ export declare function showAgentTemplate(host: string, accessToken: string, source: string, key: string): Promise<AgentsApiResult<AgentTemplateShow>>;
30
+ export declare function applyAgentTemplate(host: string, accessToken: string, body: AgentTemplateApplyRequest): Promise<AgentsApiResult<AgentTemplateResult>>;
31
+ export declare function publishAgentTemplate(host: string, accessToken: string, action: 'publish' | 'rollback', params: {
32
+ source: string;
33
+ key: string;
34
+ version?: number;
35
+ }): Promise<AgentsApiResult<AgentTemplateResult>>;
@@ -0,0 +1,115 @@
1
+ import { AgentTemplateIssueSchema, AgentTemplateListSchema, AgentTemplateResultSchema, AgentTemplateShowSchema, } from '@ekanos/integration-schema';
2
+ import { request } from './auth/ekanos-api.js';
3
+ import { authRequiredError, forbiddenError, invalidStateError, networkError, notFoundError, usageError, validationError, } from './errors.js';
4
+ const ROUTE = '/api/partner/agents';
5
+ export function listAgentTemplates(host, accessToken, source) {
6
+ return call(host, accessToken, AgentTemplateListSchema, {
7
+ method: 'GET',
8
+ path: `${ROUTE}?${new URLSearchParams({ source })}`,
9
+ subject: source,
10
+ });
11
+ }
12
+ export function showAgentTemplate(host, accessToken, source, key) {
13
+ return call(host, accessToken, AgentTemplateShowSchema, {
14
+ method: 'GET',
15
+ path: `${ROUTE}/${encodeURIComponent(key)}?${new URLSearchParams({ source })}`,
16
+ subject: key,
17
+ });
18
+ }
19
+ export function applyAgentTemplate(host, accessToken, body) {
20
+ return call(host, accessToken, AgentTemplateResultSchema, {
21
+ method: 'POST',
22
+ path: ROUTE,
23
+ body,
24
+ subject: body.key,
25
+ });
26
+ }
27
+ export function publishAgentTemplate(host, accessToken, action, params) {
28
+ return call(host, accessToken, AgentTemplateResultSchema, {
29
+ method: 'POST',
30
+ path: `${ROUTE}/${encodeURIComponent(params.key)}/${action}`,
31
+ body: { source: params.source, version: params.version },
32
+ subject: params.key,
33
+ });
34
+ }
35
+ async function call(host, accessToken, schema, init) {
36
+ const response = await request(`${host}${init.path}`, {
37
+ method: init.method,
38
+ headers: Object.assign({ accept: 'application/json', authorization: `Bearer ${accessToken}` }, (init.body === undefined
39
+ ? {}
40
+ : { 'content-type': 'application/json' })),
41
+ body: init.body === undefined ? undefined : JSON.stringify(init.body),
42
+ });
43
+ if (response.status === 401 || isRedirect(response.status)) {
44
+ return { status: 'unauthorized' };
45
+ }
46
+ const body = await readOptionalJson(response);
47
+ const code = str(body.code);
48
+ const message = str(body.error);
49
+ if (response.status === 400 && code === 'INVALID_AGENT') {
50
+ const issues = readIssues(body.issues);
51
+ throw validationError([
52
+ message !== null && message !== void 0 ? message : 'The host refused the agent.',
53
+ ...issues.map((issue) => ` ${issue.path}: ${issue.message}`),
54
+ ].join('\n'), `Fix the agent and apply again. "ekanos catalog" lists the products ` +
55
+ `and tools this Source has.`);
56
+ }
57
+ if (response.status === 400) {
58
+ throw usageError(message !== null && message !== void 0 ? message : 'The host refused the request.', 'See "ekanos agents --help" for each subcommand\'s arguments.');
59
+ }
60
+ if (response.status === 403 && code === 'MFA_REQUIRED') {
61
+ throw authRequiredError(message !== null && message !== void 0 ? message : `${host} requires multi-factor authentication.`, `Complete MFA in the browser, then run "ekanos login" again.`);
62
+ }
63
+ if (response.status === 403) {
64
+ throw forbiddenError(message !== null && message !== void 0 ? message : 'Not permitted on this Source.', code === 'SOURCE_ADMIN_REQUIRED'
65
+ ? `Only a Source admin publishes. Ask one to run it, or apply as a draft.`
66
+ : `Run "ekanos sources" to list the sources you hold a seat on.`);
67
+ }
68
+ if (response.status === 404) {
69
+ throw notFoundError(message !== null && message !== void 0 ? message : `"${init.subject}" was not found.`, code === 'SOURCE_NOT_FOUND'
70
+ ? `Run "ekanos sources" to list the sources you hold a seat on.`
71
+ : `Run "ekanos agents list" to see this Source's agents and versions.`);
72
+ }
73
+ if (response.status === 409 && code === 'VERSION_CONFLICT') {
74
+ const latest = typeof body.latestVersion === 'number' ? body.latestVersion : null;
75
+ throw invalidStateError(message !== null && message !== void 0 ? message : `"${init.subject}" changed since you read it` +
76
+ (latest ? ` (now version ${latest}).` : '.'), `Run "ekanos agents show ${init.subject}" to see the latest, then apply ` +
77
+ `again — or pass --overwrite to replace it.`);
78
+ }
79
+ if (!response.ok) {
80
+ throw networkError(`${host}${init.path.split('?')[0]} responded ${response.status}.`, response.status >= 500
81
+ ? `The Ekanos deployment returned a server error. Retry shortly.`
82
+ : `Confirm the host runs a build that includes the agents routes.`);
83
+ }
84
+ const parsed = schema.safeParse(body.data);
85
+ if (!parsed.success) {
86
+ throw networkError(`${host}${ROUTE} answered in a shape this CLI does not read.`, `Run "ekanos upgrade" — the CLI and the host disagree on the agents contract.`);
87
+ }
88
+ return { status: 'ok', data: parsed.data };
89
+ }
90
+ function readIssues(value) {
91
+ if (!Array.isArray(value))
92
+ return [];
93
+ return value.flatMap((issue) => {
94
+ const parsed = AgentTemplateIssueSchema.safeParse(issue);
95
+ return parsed.success ? [parsed.data] : [];
96
+ });
97
+ }
98
+ async function readOptionalJson(response) {
99
+ try {
100
+ const parsed = await response.json();
101
+ return typeof parsed === 'object' && parsed !== null
102
+ ? parsed
103
+ : {};
104
+ }
105
+ catch (_a) {
106
+ return {};
107
+ }
108
+ }
109
+ function isRedirect(status) {
110
+ return status >= 300 && status < 400;
111
+ }
112
+ function str(value) {
113
+ return typeof value === 'string' && value.length > 0 ? value : null;
114
+ }
115
+ //# sourceMappingURL=agents-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents-api.js","sourceRoot":"","sources":["../src/agents-api.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AgentTemplateApplyRequest,\n type AgentTemplateIssue,\n AgentTemplateIssueSchema,\n type AgentTemplateList,\n AgentTemplateListSchema,\n type AgentTemplateResult,\n AgentTemplateResultSchema,\n type AgentTemplateShow,\n AgentTemplateShowSchema,\n} from '@ekanos/integration-schema';\nimport type { z } from 'zod';\n\nimport { request } from './auth/ekanos-api';\nimport {\n authRequiredError,\n forbiddenError,\n invalidStateError,\n networkError,\n notFoundError,\n usageError,\n validationError,\n} from './errors';\n\n/**\n * The CLI's client for Source agent templates (plan 239.0 / 239.2).\n *\n * ---------------------------------------------------------------------------\n * WIRE CONTRACT — the `AgentTemplate*` schemas in `@ekanos/integration-schema`,\n * which the host's `/api/partner/agents` routes build and this parses.\n *\n * GET /api/partner/agents?source=<slug|id> → { ok, data: AgentTemplateList }\n * GET /api/partner/agents/<key>?source=<slug|id> → { ok, data: AgentTemplateShow }\n * POST /api/partner/agents AgentTemplateApplyRequest → { ok, data: AgentTemplateResult }\n * POST /api/partner/agents/<key>/publish { source, version? } → { ok, data: AgentTemplateResult }\n * POST /api/partner/agents/<key>/rollback { source, version } → { ok, data: AgentTemplateResult }\n *\n * → 401 / 3xx refresh once and retry\n * → 400 { code: \"INVALID_AGENT\", issues: AgentTemplateIssue[] }\n * → 403 { code: \"MFA_REQUIRED\" | \"FORBIDDEN\" | \"SOURCE_ADMIN_REQUIRED\" }\n * → 404 { code: \"SOURCE_NOT_FOUND\" | \"TEMPLATE_NOT_FOUND\" | \"VERSION_NOT_FOUND\" }\n * → 409 { code: \"VERSION_CONFLICT\", latestVersion }\n * ---------------------------------------------------------------------------\n */\n\nexport type AgentsApiResult<T> =\n | { status: 'ok'; data: T }\n | { status: 'unauthorized' };\n\nconst ROUTE = '/api/partner/agents';\n\nexport function listAgentTemplates(\n host: string,\n accessToken: string,\n source: string,\n): Promise<AgentsApiResult<AgentTemplateList>> {\n return call(host, accessToken, AgentTemplateListSchema, {\n method: 'GET',\n path: `${ROUTE}?${new URLSearchParams({ source })}`,\n subject: source,\n });\n}\n\nexport function showAgentTemplate(\n host: string,\n accessToken: string,\n source: string,\n key: string,\n): Promise<AgentsApiResult<AgentTemplateShow>> {\n return call(host, accessToken, AgentTemplateShowSchema, {\n method: 'GET',\n path: `${ROUTE}/${encodeURIComponent(key)}?${new URLSearchParams({ source })}`,\n subject: key,\n });\n}\n\nexport function applyAgentTemplate(\n host: string,\n accessToken: string,\n body: AgentTemplateApplyRequest,\n): Promise<AgentsApiResult<AgentTemplateResult>> {\n return call(host, accessToken, AgentTemplateResultSchema, {\n method: 'POST',\n path: ROUTE,\n body,\n subject: body.key,\n });\n}\n\nexport function publishAgentTemplate(\n host: string,\n accessToken: string,\n action: 'publish' | 'rollback',\n params: { source: string; key: string; version?: number },\n): Promise<AgentsApiResult<AgentTemplateResult>> {\n return call(host, accessToken, AgentTemplateResultSchema, {\n method: 'POST',\n path: `${ROUTE}/${encodeURIComponent(params.key)}/${action}`,\n body: { source: params.source, version: params.version },\n subject: params.key,\n });\n}\n\nasync function call<T>(\n host: string,\n accessToken: string,\n schema: z.ZodType<T>,\n init: {\n method: 'GET' | 'POST';\n path: string;\n body?: unknown;\n /** What a 404 is about, for the message. */\n subject: string;\n },\n): Promise<AgentsApiResult<T>> {\n const response = await request(`${host}${init.path}`, {\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 (response.status === 401 || isRedirect(response.status)) {\n return { status: 'unauthorized' };\n }\n\n const body = await readOptionalJson(response);\n const code = str(body.code);\n const message = str(body.error);\n\n if (response.status === 400 && code === 'INVALID_AGENT') {\n const issues = readIssues(body.issues);\n throw validationError(\n [\n message ?? 'The host refused the agent.',\n ...issues.map((issue) => ` ${issue.path}: ${issue.message}`),\n ].join('\\n'),\n `Fix the agent and apply again. \"ekanos catalog\" lists the products ` +\n `and tools this Source has.`,\n );\n }\n\n if (response.status === 400) {\n throw usageError(\n message ?? 'The host refused the request.',\n 'See \"ekanos agents --help\" for each subcommand\\'s arguments.',\n );\n }\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\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 publishes. Ask one to run it, or apply as a draft.`\n : `Run \"ekanos sources\" to list the sources you hold a seat on.`,\n );\n }\n\n if (response.status === 404) {\n throw notFoundError(\n message ?? `\"${init.subject}\" was not found.`,\n code === 'SOURCE_NOT_FOUND'\n ? `Run \"ekanos sources\" to list the sources you hold a seat on.`\n : `Run \"ekanos agents list\" to see this Source's agents and versions.`,\n );\n }\n\n if (response.status === 409 && code === 'VERSION_CONFLICT') {\n const latest =\n typeof body.latestVersion === 'number' ? body.latestVersion : null;\n throw invalidStateError(\n message ??\n `\"${init.subject}\" changed since you read it` +\n (latest ? ` (now version ${latest}).` : '.'),\n `Run \"ekanos agents show ${init.subject}\" to see the latest, then apply ` +\n `again — or pass --overwrite to replace it.`,\n );\n }\n\n if (!response.ok) {\n throw networkError(\n `${host}${init.path.split('?')[0]} 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 agents routes.`,\n );\n }\n\n const parsed = schema.safeParse(body.data);\n if (!parsed.success) {\n throw networkError(\n `${host}${ROUTE} answered in a shape this CLI does not read.`,\n `Run \"ekanos upgrade\" — the CLI and the host disagree on the agents contract.`,\n );\n }\n\n return { status: 'ok', data: parsed.data };\n}\n\nfunction readIssues(value: unknown): AgentTemplateIssue[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((issue) => {\n const parsed = AgentTemplateIssueSchema.safeParse(issue);\n return parsed.success ? [parsed.data] : [];\n });\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\nfunction isRedirect(status: number): boolean {\n return status >= 300 && status < 400;\n}\n\nfunction str(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n"]}
@@ -0,0 +1,26 @@
1
+ import { type SourceCatalog } from '@ekanos/integration-schema';
2
+ /**
3
+ * The CLI's client for the Source catalog (plan 239.0).
4
+ *
5
+ * ---------------------------------------------------------------------------
6
+ * WIRE CONTRACT — `SourceCatalogSchema` in `@ekanos/integration-schema`,
7
+ * which the host's `/api/partner/catalog` route builds and this parses.
8
+ *
9
+ * GET /api/partner/catalog?source=<slug|id>[&product=<slug>]
10
+ * [Authorization: Bearer <access_token>]
11
+ * → 200 { ok: true, data: SourceCatalog }
12
+ * → 401 / 3xx refresh once and retry
13
+ * → 403 { code: "MFA_REQUIRED" | "FORBIDDEN" }
14
+ * → 404 { code: "SOURCE_NOT_FOUND" | "PRODUCT_NOT_FOUND" }
15
+ * ---------------------------------------------------------------------------
16
+ */
17
+ export type FetchSourceCatalogResult = {
18
+ status: 'ok';
19
+ catalog: SourceCatalog;
20
+ } | {
21
+ status: 'unauthorized';
22
+ };
23
+ export declare function fetchSourceCatalog(host: string, accessToken: string, query: {
24
+ source: string;
25
+ product?: string;
26
+ }): Promise<FetchSourceCatalogResult>;
@@ -0,0 +1,61 @@
1
+ import { SourceCatalogSchema, } from '@ekanos/integration-schema';
2
+ import { request } from './auth/ekanos-api.js';
3
+ import { authRequiredError, forbiddenError, networkError, notFoundError, } from './errors.js';
4
+ export async function fetchSourceCatalog(host, accessToken, query) {
5
+ var _a;
6
+ const params = new URLSearchParams({ source: query.source });
7
+ if (query.product)
8
+ params.set('product', query.product);
9
+ const response = await request(`${host}/api/partner/catalog?${params.toString()}`, {
10
+ method: 'GET',
11
+ headers: {
12
+ accept: 'application/json',
13
+ authorization: `Bearer ${accessToken}`,
14
+ },
15
+ });
16
+ if (response.status === 401 || isRedirect(response.status)) {
17
+ return { status: 'unauthorized' };
18
+ }
19
+ const body = await readOptionalJson(response);
20
+ const code = str(body.code);
21
+ const message = str(body.error);
22
+ if (response.status === 403 && code === 'MFA_REQUIRED') {
23
+ throw authRequiredError(message !== null && message !== void 0 ? message : `${host} requires multi-factor authentication.`, `Complete MFA in the browser, then run "ekanos login" again.`);
24
+ }
25
+ if (response.status === 403) {
26
+ throw forbiddenError(message !== null && message !== void 0 ? message : `No seat on "${query.source}".`, `Run "ekanos sources" to list the sources you hold a seat on.`);
27
+ }
28
+ if (response.status === 404) {
29
+ throw notFoundError(message !== null && message !== void 0 ? message : `"${(_a = query.product) !== null && _a !== void 0 ? _a : query.source}" was not found.`, code === 'PRODUCT_NOT_FOUND'
30
+ ? `Run "ekanos catalog" to list the products this Source can use.`
31
+ : `Run "ekanos sources" to list the sources you hold a seat on.`);
32
+ }
33
+ if (!response.ok) {
34
+ throw networkError(`${host}/api/partner/catalog responded ${response.status}.`, response.status >= 500
35
+ ? `The Ekanos deployment returned a server error. Retry shortly.`
36
+ : `Confirm the host runs a build that includes the catalog route.`);
37
+ }
38
+ const parsed = SourceCatalogSchema.safeParse(body.data);
39
+ if (!parsed.success) {
40
+ throw networkError(`${host}/api/partner/catalog answered in a shape this CLI does not read.`, `Run "ekanos upgrade" — the CLI and the host disagree on the catalog contract.`);
41
+ }
42
+ return { status: 'ok', catalog: parsed.data };
43
+ }
44
+ async function readOptionalJson(response) {
45
+ try {
46
+ const parsed = await response.json();
47
+ return typeof parsed === 'object' && parsed !== null
48
+ ? parsed
49
+ : {};
50
+ }
51
+ catch (_a) {
52
+ return {};
53
+ }
54
+ }
55
+ function isRedirect(status) {
56
+ return status >= 300 && status < 400;
57
+ }
58
+ function str(value) {
59
+ return typeof value === 'string' && value.length > 0 ? value : null;
60
+ }
61
+ //# sourceMappingURL=catalog-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog-api.js","sourceRoot":"","sources":["../src/catalog-api.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type SourceCatalog,\n SourceCatalogSchema,\n} from '@ekanos/integration-schema';\n\nimport { request } from './auth/ekanos-api';\nimport {\n authRequiredError,\n forbiddenError,\n networkError,\n notFoundError,\n} from './errors';\n\n/**\n * The CLI's client for the Source catalog (plan 239.0).\n *\n * ---------------------------------------------------------------------------\n * WIRE CONTRACT — `SourceCatalogSchema` in `@ekanos/integration-schema`,\n * which the host's `/api/partner/catalog` route builds and this parses.\n *\n * GET /api/partner/catalog?source=<slug|id>[&product=<slug>]\n * [Authorization: Bearer <access_token>]\n * → 200 { ok: true, data: SourceCatalog }\n * → 401 / 3xx refresh once and retry\n * → 403 { code: \"MFA_REQUIRED\" | \"FORBIDDEN\" }\n * → 404 { code: \"SOURCE_NOT_FOUND\" | \"PRODUCT_NOT_FOUND\" }\n * ---------------------------------------------------------------------------\n */\n\nexport type FetchSourceCatalogResult =\n | { status: 'ok'; catalog: SourceCatalog }\n | { status: 'unauthorized' };\n\nexport async function fetchSourceCatalog(\n host: string,\n accessToken: string,\n query: { source: string; product?: string },\n): Promise<FetchSourceCatalogResult> {\n const params = new URLSearchParams({ source: query.source });\n if (query.product) params.set('product', query.product);\n\n const response = await request(\n `${host}/api/partner/catalog?${params.toString()}`,\n {\n method: 'GET',\n headers: {\n accept: 'application/json',\n authorization: `Bearer ${accessToken}`,\n },\n },\n );\n\n if (response.status === 401 || isRedirect(response.status)) {\n return { status: 'unauthorized' };\n }\n\n const body = await readOptionalJson(response);\n const code = str(body.code);\n const message = str(body.error);\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\n if (response.status === 403) {\n throw forbiddenError(\n message ?? `No seat on \"${query.source}\".`,\n `Run \"ekanos sources\" to list the sources you hold a seat on.`,\n );\n }\n\n if (response.status === 404) {\n throw notFoundError(\n message ?? `\"${query.product ?? query.source}\" was not found.`,\n code === 'PRODUCT_NOT_FOUND'\n ? `Run \"ekanos catalog\" to list the products this Source can use.`\n : `Run \"ekanos sources\" to list the sources you hold a seat on.`,\n );\n }\n\n if (!response.ok) {\n throw networkError(\n `${host}/api/partner/catalog 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 catalog route.`,\n );\n }\n\n const parsed = SourceCatalogSchema.safeParse(body.data);\n if (!parsed.success) {\n throw networkError(\n `${host}/api/partner/catalog answered in a shape this CLI does not read.`,\n `Run \"ekanos upgrade\" — the CLI and the host disagree on the catalog contract.`,\n );\n }\n\n return { status: 'ok', catalog: 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\nfunction isRedirect(status: number): boolean {\n return status >= 300 && status < 400;\n}\n\nfunction str(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n"]}
@@ -0,0 +1,49 @@
1
+ import { type AgentTemplateBody } from '@ekanos/integration-schema';
2
+ import type { CliContext } from '../context.js';
3
+ import type { ExitCode } from '../exit-codes.js';
4
+ export declare const AGENTS_SUBCOMMANDS: readonly ['list', 'show', 'pull', 'apply', 'publish', 'rollback'];
5
+ /** The file `pull` writes and `apply` reads, inside an agent's directory. */
6
+ export declare const AGENT_FILE = "agent.json";
7
+ /** Instructions as Markdown, beside it. Used when `agent.json` has none. */
8
+ export declare const AGENT_INSTRUCTIONS_FILE = "AGENT.md";
9
+ export interface AgentsArgs {
10
+ host?: string;
11
+ source?: string;
12
+ subcommand?: string;
13
+ /** A template key, or for `apply` a file or directory. */
14
+ target?: string;
15
+ /** `publish` / `rollback`: the version to make the one teams run. */
16
+ to?: string;
17
+ baseVersion?: string;
18
+ overwrite: boolean;
19
+ out?: string;
20
+ yes: boolean;
21
+ dir: string;
22
+ env: Record<string, string | undefined>;
23
+ stdinIsTTY?: boolean;
24
+ confirm?: (question: string) => Promise<boolean>;
25
+ }
26
+ /**
27
+ * `agents` — the Source's agent templates (plan 239.0 / 239.2). Ekanos holds
28
+ * them; nothing here is deployed. `apply` stores a new version (a draft
29
+ * until published), `publish` makes a version the one every team runs,
30
+ * `rollback` publishes an older one. `pull` writes a template out as
31
+ * `agent.json` + `AGENT.md` for review or Git, and `apply` reads the same
32
+ * layout back.
33
+ */
34
+ export declare function runAgents(ctx: CliContext, args: AgentsArgs): Promise<ExitCode>;
35
+ interface AgentInput {
36
+ key: string;
37
+ body: AgentTemplateBody;
38
+ baseVersion: number | null;
39
+ /** The agent.json read, when there was one, so the new version is recorded in it. */
40
+ file: string | null;
41
+ }
42
+ /**
43
+ * Read `apply`'s input: a directory holding `agent.json` (and optionally
44
+ * `AGENT.md`), or a `.json` file. The key is the file's `key`, else the
45
+ * directory's name. `baseVersion` in the file is the version it was pulled
46
+ * or last applied at — what the host checks for a concurrent change.
47
+ */
48
+ export declare function readAgentInput(target: string | undefined, cwd: string): AgentInput;
49
+ export {};