@ekanos/cli 0.11.0 → 0.13.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/apps-api.d.ts +3 -2
- package/dist/apps-api.js +1 -5
- package/dist/apps-api.js.map +1 -1
- package/dist/commands/agents-edit.js +1 -1
- package/dist/commands/agents-edit.js.map +1 -1
- package/dist/commands/apps.d.ts +19 -6
- package/dist/commands/apps.js +366 -43
- package/dist/commands/apps.js.map +1 -1
- package/dist/commands/knowledge.d.ts +37 -0
- package/dist/commands/knowledge.js +364 -0
- package/dist/commands/knowledge.js.map +1 -0
- 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/commands/sources.d.ts +4 -0
- package/dist/commands/sources.js +115 -1
- package/dist/commands/sources.js.map +1 -1
- package/dist/commands/team-agents-triggers.js +2 -1
- package/dist/commands/team-agents-triggers.js.map +1 -1
- package/dist/commands/teams-connect.d.ts +25 -0
- package/dist/commands/teams-connect.js +242 -0
- package/dist/commands/teams-connect.js.map +1 -0
- package/dist/commands/teams-onboard.d.ts +5 -2
- package/dist/commands/teams-onboard.js +12 -40
- package/dist/commands/teams-onboard.js.map +1 -1
- package/dist/commands/teams.d.ts +60 -5
- package/dist/commands/teams.js +141 -46
- package/dist/commands/teams.js.map +1 -1
- 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 +90 -6
- package/dist/index.js.map +1 -1
- package/dist/knowledge-api.d.ts +129 -0
- package/dist/knowledge-api.js +138 -0
- package/dist/knowledge-api.js.map +1 -0
- package/dist/routes-api.d.ts +105 -0
- package/dist/routes-api.js +147 -0
- package/dist/routes-api.js.map +1 -0
- package/dist/source-products-api.d.ts +59 -0
- package/dist/source-products-api.js +113 -0
- package/dist/source-products-api.js.map +1 -0
- package/dist/team-connect-api.d.ts +244 -0
- package/dist/team-connect-api.js +95 -0
- package/dist/team-connect-api.js.map +1 -0
- package/dist/teams-api.d.ts +1 -0
- package/dist/teams-api.js +1 -1
- package/dist/teams-api.js.map +1 -1
- package/package.json +2 -2
- package/templates/package.json.tmpl +4 -4
- package/templates/skills/build-agent/SKILL.md +18 -3
- package/templates/skills/build-agent/references/agent-json.md +29 -8
- package/templates/skills/build-agent/references/app-setup.md +2 -1
- package/templates/skills/build-agent/references/set-up-an-integration.md +76 -10
- package/templates/skills/build-agent/references/skills.md +7 -4
- package/templates/skills/build-agent/references/triggers.md +44 -15
- package/templates/skills/ekanos-platform/SKILL.md +40 -8
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const ArticleSchema: z.ZodObject<{
|
|
3
|
+
id: z.ZodString;
|
|
4
|
+
slug: z.ZodString;
|
|
5
|
+
title: z.ZodString;
|
|
6
|
+
tags: z.ZodArray<z.ZodString>;
|
|
7
|
+
version: z.ZodNumber;
|
|
8
|
+
updatedAt: z.ZodString;
|
|
9
|
+
body: z.ZodOptional<z.ZodString>;
|
|
10
|
+
}, z.core.$strip>;
|
|
11
|
+
export type KnowledgeArticle = z.infer<typeof ArticleSchema>;
|
|
12
|
+
export declare const KnowledgeListSchema: z.ZodObject<{
|
|
13
|
+
source: z.ZodObject<{
|
|
14
|
+
id: z.ZodString;
|
|
15
|
+
slug: z.ZodString;
|
|
16
|
+
name: z.ZodNullable<z.ZodString>;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
team: z.ZodNullable<z.ZodObject<{
|
|
19
|
+
slug: z.ZodString;
|
|
20
|
+
name: z.ZodString;
|
|
21
|
+
}, z.core.$strip>>;
|
|
22
|
+
articles: z.ZodArray<z.ZodObject<{
|
|
23
|
+
id: z.ZodString;
|
|
24
|
+
slug: z.ZodString;
|
|
25
|
+
title: z.ZodString;
|
|
26
|
+
tags: z.ZodArray<z.ZodString>;
|
|
27
|
+
version: z.ZodNumber;
|
|
28
|
+
updatedAt: z.ZodString;
|
|
29
|
+
body: z.ZodOptional<z.ZodString>;
|
|
30
|
+
}, z.core.$strip>>;
|
|
31
|
+
}, z.core.$strip>;
|
|
32
|
+
export type KnowledgeList = z.infer<typeof KnowledgeListSchema>;
|
|
33
|
+
declare const PushResultSchema: z.ZodObject<{
|
|
34
|
+
slug: z.ZodString;
|
|
35
|
+
id: z.ZodOptional<z.ZodString>;
|
|
36
|
+
status: z.ZodEnum<{
|
|
37
|
+
created: "created";
|
|
38
|
+
refused: "refused";
|
|
39
|
+
unchanged: "unchanged";
|
|
40
|
+
updated: "updated";
|
|
41
|
+
}>;
|
|
42
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
43
|
+
message: z.ZodOptional<z.ZodString>;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
export type KnowledgePushResult = z.infer<typeof PushResultSchema>;
|
|
46
|
+
export declare const KnowledgePushSchema: z.ZodObject<{
|
|
47
|
+
source: z.ZodObject<{
|
|
48
|
+
id: z.ZodString;
|
|
49
|
+
slug: z.ZodString;
|
|
50
|
+
name: z.ZodNullable<z.ZodString>;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
team: z.ZodNullable<z.ZodObject<{
|
|
53
|
+
slug: z.ZodString;
|
|
54
|
+
name: z.ZodString;
|
|
55
|
+
}, z.core.$strip>>;
|
|
56
|
+
results: z.ZodArray<z.ZodObject<{
|
|
57
|
+
slug: z.ZodString;
|
|
58
|
+
id: z.ZodOptional<z.ZodString>;
|
|
59
|
+
status: z.ZodEnum<{
|
|
60
|
+
created: "created";
|
|
61
|
+
refused: "refused";
|
|
62
|
+
unchanged: "unchanged";
|
|
63
|
+
updated: "updated";
|
|
64
|
+
}>;
|
|
65
|
+
version: z.ZodOptional<z.ZodNumber>;
|
|
66
|
+
message: z.ZodOptional<z.ZodString>;
|
|
67
|
+
}, z.core.$strip>>;
|
|
68
|
+
}, z.core.$strip>;
|
|
69
|
+
export type KnowledgePush = z.infer<typeof KnowledgePushSchema>;
|
|
70
|
+
declare const HitSchema: z.ZodObject<{
|
|
71
|
+
id: z.ZodString;
|
|
72
|
+
slug: z.ZodString;
|
|
73
|
+
title: z.ZodString;
|
|
74
|
+
tags: z.ZodArray<z.ZodString>;
|
|
75
|
+
collection: z.ZodEnum<{
|
|
76
|
+
general: "general";
|
|
77
|
+
team: "team";
|
|
78
|
+
}>;
|
|
79
|
+
snippet: z.ZodString;
|
|
80
|
+
rank: z.ZodNumber;
|
|
81
|
+
updatedAt: z.ZodString;
|
|
82
|
+
}, z.core.$strip>;
|
|
83
|
+
export type KnowledgeHit = z.infer<typeof HitSchema>;
|
|
84
|
+
export declare const KnowledgeSearchSchema: z.ZodObject<{
|
|
85
|
+
source: z.ZodObject<{
|
|
86
|
+
id: z.ZodString;
|
|
87
|
+
slug: z.ZodString;
|
|
88
|
+
name: z.ZodNullable<z.ZodString>;
|
|
89
|
+
}, z.core.$strip>;
|
|
90
|
+
team: z.ZodNullable<z.ZodObject<{
|
|
91
|
+
slug: z.ZodString;
|
|
92
|
+
name: z.ZodString;
|
|
93
|
+
}, z.core.$strip>>;
|
|
94
|
+
query: z.ZodString;
|
|
95
|
+
results: z.ZodArray<z.ZodObject<{
|
|
96
|
+
id: z.ZodString;
|
|
97
|
+
slug: z.ZodString;
|
|
98
|
+
title: z.ZodString;
|
|
99
|
+
tags: z.ZodArray<z.ZodString>;
|
|
100
|
+
collection: z.ZodEnum<{
|
|
101
|
+
general: "general";
|
|
102
|
+
team: "team";
|
|
103
|
+
}>;
|
|
104
|
+
snippet: z.ZodString;
|
|
105
|
+
rank: z.ZodNumber;
|
|
106
|
+
updatedAt: z.ZodString;
|
|
107
|
+
}, z.core.$strip>>;
|
|
108
|
+
}, z.core.$strip>;
|
|
109
|
+
export interface KnowledgePushArticle {
|
|
110
|
+
slug: string;
|
|
111
|
+
title: string;
|
|
112
|
+
body: string;
|
|
113
|
+
tags?: string[];
|
|
114
|
+
}
|
|
115
|
+
/** Articles per POST; the host refuses more. */
|
|
116
|
+
export declare const KNOWLEDGE_PUSH_BATCH = 50;
|
|
117
|
+
export type KnowledgeApiResult<T> = {
|
|
118
|
+
status: 'ok';
|
|
119
|
+
data: T;
|
|
120
|
+
} | {
|
|
121
|
+
status: 'unauthorized';
|
|
122
|
+
};
|
|
123
|
+
export declare function knowledgeCall<T>(host: string, accessToken: string, schema: z.ZodType<T>, init: {
|
|
124
|
+
method: 'GET' | 'POST';
|
|
125
|
+
path?: string;
|
|
126
|
+
query: Record<string, string | undefined>;
|
|
127
|
+
body?: unknown;
|
|
128
|
+
}): Promise<KnowledgeApiResult<T>>;
|
|
129
|
+
export {};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { request } from './auth/ekanos-api.js';
|
|
3
|
+
import { authRequiredError, forbiddenError, networkError, notFoundError, usageError, } from './errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* The CLI's client for a Source's Knowledge (plan 244): the General
|
|
6
|
+
* collection every team reads, and each team's own.
|
|
7
|
+
*
|
|
8
|
+
* GET /api/partner/knowledge?source=<slug>[&team=<slug>][&body=1]
|
|
9
|
+
* POST /api/partner/knowledge { source, team?, articles: [...] }
|
|
10
|
+
* GET /api/partner/knowledge/search?source=<slug>&team=<slug>&q=<query>[&limit=<n>]
|
|
11
|
+
*
|
|
12
|
+
* → 401 / 3xx refresh once and retry
|
|
13
|
+
* → 403 { code: "SOURCE_ADMIN_REQUIRED" | "MFA_REQUIRED" | "FORBIDDEN" }
|
|
14
|
+
* → 404 { code: "TEAM_NOT_FOUND" | "SOURCE_NOT_FOUND" }
|
|
15
|
+
* → 400 { code: "INVALID_KNOWLEDGE" | "TEAM_REQUIRED" | "QUERY_REQUIRED" }
|
|
16
|
+
*
|
|
17
|
+
* The contract's shapes live here rather than in `@ekanos/integration-schema`,
|
|
18
|
+
* so the knowledge commands ship with the CLI alone.
|
|
19
|
+
*/
|
|
20
|
+
const SourceRefSchema = z.object({
|
|
21
|
+
id: z.string(),
|
|
22
|
+
slug: z.string(),
|
|
23
|
+
name: z.string().nullable(),
|
|
24
|
+
});
|
|
25
|
+
const TeamRefSchema = z
|
|
26
|
+
.object({ slug: z.string(), name: z.string() })
|
|
27
|
+
.nullable();
|
|
28
|
+
const ArticleSchema = z.object({
|
|
29
|
+
id: z.string(),
|
|
30
|
+
slug: z.string(),
|
|
31
|
+
title: z.string(),
|
|
32
|
+
tags: z.array(z.string()),
|
|
33
|
+
version: z.number(),
|
|
34
|
+
updatedAt: z.string(),
|
|
35
|
+
body: z.string().optional(),
|
|
36
|
+
});
|
|
37
|
+
export const KnowledgeListSchema = z.object({
|
|
38
|
+
source: SourceRefSchema,
|
|
39
|
+
team: TeamRefSchema,
|
|
40
|
+
articles: z.array(ArticleSchema),
|
|
41
|
+
});
|
|
42
|
+
const PushResultSchema = z.object({
|
|
43
|
+
slug: z.string(),
|
|
44
|
+
id: z.string().optional(),
|
|
45
|
+
status: z.enum(['created', 'updated', 'unchanged', 'refused']),
|
|
46
|
+
version: z.number().optional(),
|
|
47
|
+
message: z.string().optional(),
|
|
48
|
+
});
|
|
49
|
+
export const KnowledgePushSchema = z.object({
|
|
50
|
+
source: SourceRefSchema,
|
|
51
|
+
team: TeamRefSchema,
|
|
52
|
+
results: z.array(PushResultSchema),
|
|
53
|
+
});
|
|
54
|
+
const HitSchema = z.object({
|
|
55
|
+
id: z.string(),
|
|
56
|
+
slug: z.string(),
|
|
57
|
+
title: z.string(),
|
|
58
|
+
tags: z.array(z.string()),
|
|
59
|
+
collection: z.enum(['team', 'general']),
|
|
60
|
+
snippet: z.string(),
|
|
61
|
+
rank: z.number(),
|
|
62
|
+
updatedAt: z.string(),
|
|
63
|
+
});
|
|
64
|
+
export const KnowledgeSearchSchema = z.object({
|
|
65
|
+
source: SourceRefSchema,
|
|
66
|
+
team: TeamRefSchema,
|
|
67
|
+
query: z.string(),
|
|
68
|
+
results: z.array(HitSchema),
|
|
69
|
+
});
|
|
70
|
+
/** Articles per POST; the host refuses more. */
|
|
71
|
+
export const KNOWLEDGE_PUSH_BATCH = 50;
|
|
72
|
+
const BASE = '/api/partner/knowledge';
|
|
73
|
+
export async function knowledgeCall(host, accessToken, schema, init) {
|
|
74
|
+
var _a;
|
|
75
|
+
const query = Object.fromEntries(Object.entries(init.query).filter((entry) => entry[1] !== undefined));
|
|
76
|
+
const url = `${host}${BASE}${(_a = init.path) !== null && _a !== void 0 ? _a : ''}?${new URLSearchParams(query)}`;
|
|
77
|
+
const response = await request(url, {
|
|
78
|
+
method: init.method,
|
|
79
|
+
headers: Object.assign({ accept: 'application/json', authorization: `Bearer ${accessToken}` }, (init.body === undefined
|
|
80
|
+
? {}
|
|
81
|
+
: { 'content-type': 'application/json' })),
|
|
82
|
+
body: init.body === undefined ? undefined : JSON.stringify(init.body),
|
|
83
|
+
});
|
|
84
|
+
if (response.status === 401 ||
|
|
85
|
+
(response.status >= 300 && response.status < 400)) {
|
|
86
|
+
return { status: 'unauthorized' };
|
|
87
|
+
}
|
|
88
|
+
const body = await readOptionalJson(response);
|
|
89
|
+
const code = typeof body.code === 'string' ? body.code : null;
|
|
90
|
+
const message = typeof body.error === 'string' && body.error ? body.error : null;
|
|
91
|
+
if (response.status === 403 && code === 'MFA_REQUIRED') {
|
|
92
|
+
throw authRequiredError(message !== null && message !== void 0 ? message : `${host} requires multi-factor authentication.`, 'Complete MFA in the browser, then run "ekanos login" again.');
|
|
93
|
+
}
|
|
94
|
+
if (response.status === 403) {
|
|
95
|
+
throw forbiddenError(message !== null && message !== void 0 ? message : 'Not permitted on this Source.', code === 'SOURCE_ADMIN_REQUIRED'
|
|
96
|
+
? 'Only a Source admin manages Knowledge from the CLI. Ask one to run it.'
|
|
97
|
+
: 'Run "ekanos sources" to list the sources you hold a seat on.');
|
|
98
|
+
}
|
|
99
|
+
if (response.status === 404) {
|
|
100
|
+
throw notFoundError(message !== null && message !== void 0 ? message : 'Not found.', code === 'TEAM_NOT_FOUND'
|
|
101
|
+
? 'Run "ekanos teams list" to see the teams on this Source.'
|
|
102
|
+
: 'Run "ekanos sources" to list the sources you hold a seat on.');
|
|
103
|
+
}
|
|
104
|
+
if (response.status === 400) {
|
|
105
|
+
const issues = Array.isArray(body.issues)
|
|
106
|
+
? body.issues
|
|
107
|
+
.map((issue) => `${String(issue.path)}: ${String(issue.message)}`)
|
|
108
|
+
.join('; ')
|
|
109
|
+
: null;
|
|
110
|
+
throw usageError([message !== null && message !== void 0 ? message : 'The host refused the request.', issues]
|
|
111
|
+
.filter(Boolean)
|
|
112
|
+
.join(' — '), code === 'TEAM_REQUIRED'
|
|
113
|
+
? 'Pass --team <slug>: search runs as that team sees it.'
|
|
114
|
+
: 'See "ekanos knowledge --help".');
|
|
115
|
+
}
|
|
116
|
+
if (!response.ok) {
|
|
117
|
+
throw networkError(`${host}${BASE} responded ${response.status}.`, response.status >= 500
|
|
118
|
+
? 'The Ekanos deployment returned a server error. Retry shortly.'
|
|
119
|
+
: 'Confirm the host runs a build that includes the knowledge route.');
|
|
120
|
+
}
|
|
121
|
+
const parsed = schema.safeParse(body.data);
|
|
122
|
+
if (!parsed.success) {
|
|
123
|
+
throw networkError(`${host}${BASE} answered in a shape this CLI does not read.`, 'Run "ekanos upgrade" — the CLI and the host disagree on the knowledge contract.');
|
|
124
|
+
}
|
|
125
|
+
return { status: 'ok', data: parsed.data };
|
|
126
|
+
}
|
|
127
|
+
async function readOptionalJson(response) {
|
|
128
|
+
try {
|
|
129
|
+
const parsed = await response.json();
|
|
130
|
+
return typeof parsed === 'object' && parsed !== null
|
|
131
|
+
? parsed
|
|
132
|
+
: {};
|
|
133
|
+
}
|
|
134
|
+
catch (_a) {
|
|
135
|
+
return {};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=knowledge-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge-api.js","sourceRoot":"","sources":["../src/knowledge-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 Knowledge (plan 244): the General\n * collection every team reads, and each team's own.\n *\n * GET /api/partner/knowledge?source=<slug>[&team=<slug>][&body=1]\n * POST /api/partner/knowledge { source, team?, articles: [...] }\n * GET /api/partner/knowledge/search?source=<slug>&team=<slug>&q=<query>[&limit=<n>]\n *\n * → 401 / 3xx refresh once and retry\n * → 403 { code: \"SOURCE_ADMIN_REQUIRED\" | \"MFA_REQUIRED\" | \"FORBIDDEN\" }\n * → 404 { code: \"TEAM_NOT_FOUND\" | \"SOURCE_NOT_FOUND\" }\n * → 400 { code: \"INVALID_KNOWLEDGE\" | \"TEAM_REQUIRED\" | \"QUERY_REQUIRED\" }\n *\n * The contract's shapes live here rather than in `@ekanos/integration-schema`,\n * so the knowledge 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 TeamRefSchema = z\n .object({ slug: z.string(), name: z.string() })\n .nullable();\n\nconst ArticleSchema = z.object({\n id: z.string(),\n slug: z.string(),\n title: z.string(),\n tags: z.array(z.string()),\n version: z.number(),\n updatedAt: z.string(),\n body: z.string().optional(),\n});\nexport type KnowledgeArticle = z.infer<typeof ArticleSchema>;\n\nexport const KnowledgeListSchema = z.object({\n source: SourceRefSchema,\n team: TeamRefSchema,\n articles: z.array(ArticleSchema),\n});\nexport type KnowledgeList = z.infer<typeof KnowledgeListSchema>;\n\nconst PushResultSchema = z.object({\n slug: z.string(),\n id: z.string().optional(),\n status: z.enum(['created', 'updated', 'unchanged', 'refused']),\n version: z.number().optional(),\n message: z.string().optional(),\n});\nexport type KnowledgePushResult = z.infer<typeof PushResultSchema>;\n\nexport const KnowledgePushSchema = z.object({\n source: SourceRefSchema,\n team: TeamRefSchema,\n results: z.array(PushResultSchema),\n});\nexport type KnowledgePush = z.infer<typeof KnowledgePushSchema>;\n\nconst HitSchema = z.object({\n id: z.string(),\n slug: z.string(),\n title: z.string(),\n tags: z.array(z.string()),\n collection: z.enum(['team', 'general']),\n snippet: z.string(),\n rank: z.number(),\n updatedAt: z.string(),\n});\nexport type KnowledgeHit = z.infer<typeof HitSchema>;\n\nexport const KnowledgeSearchSchema = z.object({\n source: SourceRefSchema,\n team: TeamRefSchema,\n query: z.string(),\n results: z.array(HitSchema),\n});\n\nexport interface KnowledgePushArticle {\n slug: string;\n title: string;\n body: string;\n tags?: string[];\n}\n\n/** Articles per POST; the host refuses more. */\nexport const KNOWLEDGE_PUSH_BATCH = 50;\n\nexport type KnowledgeApiResult<T> =\n | { status: 'ok'; data: T }\n | { status: 'unauthorized' };\n\nconst BASE = '/api/partner/knowledge';\n\nexport async function knowledgeCall<T>(\n host: string,\n accessToken: string,\n schema: z.ZodType<T>,\n init: {\n method: 'GET' | 'POST';\n path?: string;\n query: Record<string, string | undefined>;\n body?: unknown;\n },\n): Promise<KnowledgeApiResult<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 Knowledge from the CLI. 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 : 'Run \"ekanos sources\" to list the sources you hold a seat on.',\n );\n }\n if (response.status === 400) {\n const issues = Array.isArray(body.issues)\n ? (body.issues as { path?: unknown; message?: unknown }[])\n .map((issue) => `${String(issue.path)}: ${String(issue.message)}`)\n .join('; ')\n : null;\n throw usageError(\n [message ?? 'The host refused the request.', issues]\n .filter(Boolean)\n .join(' — '),\n code === 'TEAM_REQUIRED'\n ? 'Pass --team <slug>: search runs as that team sees it.'\n : 'See \"ekanos knowledge --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 knowledge 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 knowledge 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"]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
declare const RouteSchema: z.ZodObject<{
|
|
3
|
+
product: z.ZodString;
|
|
4
|
+
key: z.ZodString;
|
|
5
|
+
value: z.ZodString;
|
|
6
|
+
label: z.ZodNullable<z.ZodString>;
|
|
7
|
+
team: z.ZodObject<{
|
|
8
|
+
id: z.ZodString;
|
|
9
|
+
slug: z.ZodNullable<z.ZodString>;
|
|
10
|
+
name: z.ZodString;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
updatedAt: z.ZodString;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
export type SourceEventRoute = z.infer<typeof RouteSchema>;
|
|
15
|
+
export declare const RouteListSchema: z.ZodObject<{
|
|
16
|
+
source: z.ZodObject<{
|
|
17
|
+
id: z.ZodString;
|
|
18
|
+
slug: z.ZodString;
|
|
19
|
+
name: z.ZodNullable<z.ZodString>;
|
|
20
|
+
}, z.core.$strip>;
|
|
21
|
+
routes: z.ZodArray<z.ZodObject<{
|
|
22
|
+
product: z.ZodString;
|
|
23
|
+
key: z.ZodString;
|
|
24
|
+
value: z.ZodString;
|
|
25
|
+
label: z.ZodNullable<z.ZodString>;
|
|
26
|
+
team: z.ZodObject<{
|
|
27
|
+
id: z.ZodString;
|
|
28
|
+
slug: z.ZodNullable<z.ZodString>;
|
|
29
|
+
name: z.ZodString;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
updatedAt: z.ZodString;
|
|
32
|
+
}, z.core.$strip>>;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
export declare const RouteSetSchema: z.ZodObject<{
|
|
35
|
+
route: z.ZodObject<{
|
|
36
|
+
product: z.ZodString;
|
|
37
|
+
key: z.ZodString;
|
|
38
|
+
value: z.ZodString;
|
|
39
|
+
label: z.ZodNullable<z.ZodString>;
|
|
40
|
+
team: z.ZodObject<{
|
|
41
|
+
id: z.ZodString;
|
|
42
|
+
slug: z.ZodNullable<z.ZodString>;
|
|
43
|
+
name: z.ZodString;
|
|
44
|
+
}, z.core.$strip>;
|
|
45
|
+
updatedAt: z.ZodString;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
replaced: z.ZodBoolean;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
export declare const RouteRemoveSchema: z.ZodObject<{
|
|
50
|
+
removed: z.ZodNumber;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
export declare const RouteOptionsSchema: z.ZodObject<{
|
|
53
|
+
key: z.ZodString;
|
|
54
|
+
options: z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
55
|
+
value: z.ZodString;
|
|
56
|
+
label: z.ZodString;
|
|
57
|
+
}, z.core.$strip>>>;
|
|
58
|
+
}, z.core.$strip>;
|
|
59
|
+
export declare const RouteWebhookSchema: z.ZodObject<{
|
|
60
|
+
webhookId: z.ZodString;
|
|
61
|
+
url: z.ZodString;
|
|
62
|
+
signature: z.ZodNullable<z.ZodObject<{
|
|
63
|
+
header: z.ZodString;
|
|
64
|
+
secretName: z.ZodString;
|
|
65
|
+
}, z.core.$strip>>;
|
|
66
|
+
}, z.core.$strip>;
|
|
67
|
+
declare const UnroutedEventSchema: z.ZodObject<{
|
|
68
|
+
id: z.ZodString;
|
|
69
|
+
product: z.ZodString;
|
|
70
|
+
eventId: z.ZodString;
|
|
71
|
+
routeKey: z.ZodNullable<z.ZodString>;
|
|
72
|
+
routeValue: z.ZodNullable<z.ZodString>;
|
|
73
|
+
createdAt: z.ZodString;
|
|
74
|
+
}, z.core.$strip>;
|
|
75
|
+
export type UnroutedEvent = z.infer<typeof UnroutedEventSchema>;
|
|
76
|
+
export declare const UnroutedListSchema: z.ZodObject<{
|
|
77
|
+
events: z.ZodArray<z.ZodObject<{
|
|
78
|
+
id: z.ZodString;
|
|
79
|
+
product: z.ZodString;
|
|
80
|
+
eventId: z.ZodString;
|
|
81
|
+
routeKey: z.ZodNullable<z.ZodString>;
|
|
82
|
+
routeValue: z.ZodNullable<z.ZodString>;
|
|
83
|
+
createdAt: z.ZodString;
|
|
84
|
+
}, z.core.$strip>>;
|
|
85
|
+
}, z.core.$strip>;
|
|
86
|
+
export declare const ReplaySchema: z.ZodObject<{
|
|
87
|
+
status: z.ZodEnum<{
|
|
88
|
+
"already-routed": "already-routed";
|
|
89
|
+
routed: "routed";
|
|
90
|
+
unrouted: "unrouted";
|
|
91
|
+
}>;
|
|
92
|
+
}, z.core.$strip>;
|
|
93
|
+
export type RoutesApiResult<T> = {
|
|
94
|
+
status: 'ok';
|
|
95
|
+
data: T;
|
|
96
|
+
} | {
|
|
97
|
+
status: 'unauthorized';
|
|
98
|
+
};
|
|
99
|
+
export declare function routesCall<T>(host: string, accessToken: string, schema: z.ZodType<T>, init: {
|
|
100
|
+
method: 'GET' | 'PUT' | 'POST' | 'DELETE';
|
|
101
|
+
path?: string;
|
|
102
|
+
query: Record<string, string | undefined>;
|
|
103
|
+
body?: unknown;
|
|
104
|
+
}): Promise<RoutesApiResult<T>>;
|
|
105
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { request } from './auth/ekanos-api.js';
|
|
3
|
+
import { authRequiredError, forbiddenError, networkError, notFoundError, usageError, } from './errors.js';
|
|
4
|
+
/**
|
|
5
|
+
* The CLI's client for a Source's event routing (plan 241.4): which team each
|
|
6
|
+
* value of an integration's route key (Zendesk's organization) belongs to,
|
|
7
|
+
* the Source's webhook endpoint, and the events no team is mapped to yet.
|
|
8
|
+
*
|
|
9
|
+
* GET /api/partner/routes?source=<slug>[&product=<slug>]
|
|
10
|
+
* PUT /api/partner/routes/<product>?source=<slug> { value, team, label? }
|
|
11
|
+
* DELETE /api/partner/routes/<product>?source=<slug>&value=<value>
|
|
12
|
+
* GET /api/partner/routes/<product>/options?source=<slug>
|
|
13
|
+
* POST /api/partner/routes/<product>/webhook?source=<slug>[&webhook=<id>]
|
|
14
|
+
* GET /api/partner/routes/unrouted?source=<slug>[&product=<slug>]
|
|
15
|
+
* POST /api/partner/routes/unrouted/<id>/replay?source=<slug>
|
|
16
|
+
*
|
|
17
|
+
* → 401 / 3xx refresh once and retry
|
|
18
|
+
* → 403 { code: "SOURCE_ADMIN_REQUIRED" | "MFA_REQUIRED" | "FORBIDDEN" }
|
|
19
|
+
* → 404 { code: "PRODUCT_NOT_ENTITLED" | "TEAM_NOT_FOUND" | "ROUTE_NOT_FOUND"
|
|
20
|
+
* | "EVENT_NOT_FOUND" | "SOURCE_NOT_FOUND" }
|
|
21
|
+
* → 409 { code: "NO_ROUTING" | "NO_WEBHOOK" }
|
|
22
|
+
*
|
|
23
|
+
* The contract's shapes live here rather than in `@ekanos/integration-schema`,
|
|
24
|
+
* so the routing commands ship with the CLI alone.
|
|
25
|
+
*/
|
|
26
|
+
const SourceRefSchema = z.object({
|
|
27
|
+
id: z.string(),
|
|
28
|
+
slug: z.string(),
|
|
29
|
+
name: z.string().nullable(),
|
|
30
|
+
});
|
|
31
|
+
const RouteSchema = z.object({
|
|
32
|
+
product: z.string(),
|
|
33
|
+
key: z.string(),
|
|
34
|
+
value: z.string(),
|
|
35
|
+
label: z.string().nullable(),
|
|
36
|
+
team: z.object({
|
|
37
|
+
id: z.string(),
|
|
38
|
+
slug: z.string().nullable(),
|
|
39
|
+
name: z.string(),
|
|
40
|
+
}),
|
|
41
|
+
updatedAt: z.string(),
|
|
42
|
+
});
|
|
43
|
+
export const RouteListSchema = z.object({
|
|
44
|
+
source: SourceRefSchema,
|
|
45
|
+
routes: z.array(RouteSchema),
|
|
46
|
+
});
|
|
47
|
+
export const RouteSetSchema = z.object({
|
|
48
|
+
route: RouteSchema,
|
|
49
|
+
replaced: z.boolean(),
|
|
50
|
+
});
|
|
51
|
+
export const RouteRemoveSchema = z.object({ removed: z.number() });
|
|
52
|
+
export const RouteOptionsSchema = z.object({
|
|
53
|
+
key: z.string(),
|
|
54
|
+
options: z
|
|
55
|
+
.array(z.object({ value: z.string(), label: z.string() }))
|
|
56
|
+
.nullable(),
|
|
57
|
+
});
|
|
58
|
+
export const RouteWebhookSchema = z.object({
|
|
59
|
+
webhookId: z.string(),
|
|
60
|
+
url: z.string(),
|
|
61
|
+
signature: z
|
|
62
|
+
.object({ header: z.string(), secretName: z.string() })
|
|
63
|
+
.nullable(),
|
|
64
|
+
});
|
|
65
|
+
const UnroutedEventSchema = z.object({
|
|
66
|
+
id: z.string(),
|
|
67
|
+
product: z.string(),
|
|
68
|
+
eventId: z.string(),
|
|
69
|
+
routeKey: z.string().nullable(),
|
|
70
|
+
routeValue: z.string().nullable(),
|
|
71
|
+
createdAt: z.string(),
|
|
72
|
+
});
|
|
73
|
+
export const UnroutedListSchema = z.object({
|
|
74
|
+
events: z.array(UnroutedEventSchema),
|
|
75
|
+
});
|
|
76
|
+
export const ReplaySchema = z.object({
|
|
77
|
+
status: z.enum(['routed', 'already-routed', 'unrouted']),
|
|
78
|
+
});
|
|
79
|
+
const BASE = '/api/partner/routes';
|
|
80
|
+
export async function routesCall(host, accessToken, schema, init) {
|
|
81
|
+
var _a;
|
|
82
|
+
const query = Object.fromEntries(Object.entries(init.query).filter((entry) => entry[1] !== undefined));
|
|
83
|
+
const url = `${host}${BASE}${(_a = init.path) !== null && _a !== void 0 ? _a : ''}?${new URLSearchParams(query)}`;
|
|
84
|
+
const response = await request(url, {
|
|
85
|
+
method: init.method,
|
|
86
|
+
headers: Object.assign({ accept: 'application/json', authorization: `Bearer ${accessToken}` }, (init.body === undefined
|
|
87
|
+
? {}
|
|
88
|
+
: { 'content-type': 'application/json' })),
|
|
89
|
+
body: init.body === undefined ? undefined : JSON.stringify(init.body),
|
|
90
|
+
});
|
|
91
|
+
if (response.status === 401 ||
|
|
92
|
+
(response.status >= 300 && response.status < 400)) {
|
|
93
|
+
return { status: 'unauthorized' };
|
|
94
|
+
}
|
|
95
|
+
const body = await readOptionalJson(response);
|
|
96
|
+
const code = typeof body.code === 'string' ? body.code : null;
|
|
97
|
+
const message = typeof body.error === 'string' && body.error ? body.error : null;
|
|
98
|
+
if (response.status === 403 && code === 'MFA_REQUIRED') {
|
|
99
|
+
throw authRequiredError(message !== null && message !== void 0 ? message : `${host} requires multi-factor authentication.`, 'Complete MFA in the browser, then run "ekanos login" again.');
|
|
100
|
+
}
|
|
101
|
+
if (response.status === 403) {
|
|
102
|
+
throw forbiddenError(message !== null && message !== void 0 ? message : 'Not permitted on this Source.', code === 'SOURCE_ADMIN_REQUIRED'
|
|
103
|
+
? "Only a Source admin manages the Source's routing. Ask one to run it."
|
|
104
|
+
: 'Run "ekanos sources" to list the sources you hold a seat on.');
|
|
105
|
+
}
|
|
106
|
+
if (response.status === 404) {
|
|
107
|
+
throw notFoundError(message !== null && message !== void 0 ? message : 'Not found.', code === 'TEAM_NOT_FOUND'
|
|
108
|
+
? 'Run "ekanos teams list" to see the teams on this Source.'
|
|
109
|
+
: code === 'ROUTE_NOT_FOUND'
|
|
110
|
+
? '"ekanos routes list" shows the mapped values.'
|
|
111
|
+
: code === 'EVENT_NOT_FOUND'
|
|
112
|
+
? '"ekanos routes unrouted" lists the events waiting for a mapping.'
|
|
113
|
+
: code === 'PRODUCT_NOT_ENTITLED'
|
|
114
|
+
? 'Run "ekanos catalog" to see what the Source has.'
|
|
115
|
+
: 'Run "ekanos sources" to list the sources you hold a seat on.');
|
|
116
|
+
}
|
|
117
|
+
if (response.status === 409) {
|
|
118
|
+
throw usageError(message !== null && message !== void 0 ? message : 'The integration does not route events.', code === 'NO_WEBHOOK'
|
|
119
|
+
? 'Name the webhook: "ekanos routes webhook <product> --webhook <id>".'
|
|
120
|
+
: 'Only an integration that declares a Source connection and a route key routes events.');
|
|
121
|
+
}
|
|
122
|
+
if (response.status === 400) {
|
|
123
|
+
throw usageError(message !== null && message !== void 0 ? message : 'The host refused the request.', 'See "ekanos routes --help".');
|
|
124
|
+
}
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
throw networkError(`${host}${BASE} responded ${response.status}.`, response.status >= 500
|
|
127
|
+
? 'The Ekanos deployment returned a server error. Retry shortly.'
|
|
128
|
+
: 'Confirm the host runs a build that includes the routes route.');
|
|
129
|
+
}
|
|
130
|
+
const parsed = schema.safeParse(body.data);
|
|
131
|
+
if (!parsed.success) {
|
|
132
|
+
throw networkError(`${host}${BASE} answered in a shape this CLI does not read.`, 'Run "ekanos upgrade" — the CLI and the host disagree on the routes contract.');
|
|
133
|
+
}
|
|
134
|
+
return { status: 'ok', data: parsed.data };
|
|
135
|
+
}
|
|
136
|
+
async function readOptionalJson(response) {
|
|
137
|
+
try {
|
|
138
|
+
const parsed = await response.json();
|
|
139
|
+
return typeof parsed === 'object' && parsed !== null
|
|
140
|
+
? parsed
|
|
141
|
+
: {};
|
|
142
|
+
}
|
|
143
|
+
catch (_a) {
|
|
144
|
+
return {};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=routes-api.js.map
|
|
@@ -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"]}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const SourceProductSchema: z.ZodObject<{
|
|
3
|
+
slug: z.ZodString;
|
|
4
|
+
name: z.ZodString;
|
|
5
|
+
kind: z.ZodEnum<{
|
|
6
|
+
experience: "experience";
|
|
7
|
+
integration: "integration";
|
|
8
|
+
}>;
|
|
9
|
+
enabled: z.ZodBoolean;
|
|
10
|
+
lever: z.ZodEnum<{
|
|
11
|
+
entitlement: "entitlement";
|
|
12
|
+
flag: "flag";
|
|
13
|
+
plan: "plan";
|
|
14
|
+
}>;
|
|
15
|
+
registered: z.ZodBoolean;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
export type SourceProduct = z.infer<typeof SourceProductSchema>;
|
|
18
|
+
export type SourceProductsApiResult<T> = {
|
|
19
|
+
status: 'ok';
|
|
20
|
+
data: T;
|
|
21
|
+
} | {
|
|
22
|
+
status: 'unauthorized';
|
|
23
|
+
};
|
|
24
|
+
export declare function listSourceProducts(host: string, token: string, source: string): Promise<SourceProductsApiResult<{
|
|
25
|
+
source: {
|
|
26
|
+
id: string;
|
|
27
|
+
slug: string;
|
|
28
|
+
name: string | null;
|
|
29
|
+
};
|
|
30
|
+
products: {
|
|
31
|
+
slug: string;
|
|
32
|
+
name: string;
|
|
33
|
+
kind: "experience" | "integration";
|
|
34
|
+
enabled: boolean;
|
|
35
|
+
lever: "entitlement" | "flag" | "plan";
|
|
36
|
+
registered: boolean;
|
|
37
|
+
}[];
|
|
38
|
+
canEnable: boolean;
|
|
39
|
+
}>>;
|
|
40
|
+
export declare function setSourceProduct(host: string, token: string, input: {
|
|
41
|
+
source: string;
|
|
42
|
+
product: string;
|
|
43
|
+
enabled: boolean;
|
|
44
|
+
}): Promise<SourceProductsApiResult<{
|
|
45
|
+
source: {
|
|
46
|
+
id: string;
|
|
47
|
+
slug: string;
|
|
48
|
+
name: string | null;
|
|
49
|
+
};
|
|
50
|
+
product: {
|
|
51
|
+
slug: string;
|
|
52
|
+
name: string;
|
|
53
|
+
kind: "experience" | "integration";
|
|
54
|
+
enabled: boolean;
|
|
55
|
+
lever: "entitlement" | "flag" | "plan";
|
|
56
|
+
registered: boolean;
|
|
57
|
+
};
|
|
58
|
+
changed: boolean;
|
|
59
|
+
}>>;
|