@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,37 @@
|
|
|
1
|
+
import type { CliContext } from '../context.js';
|
|
2
|
+
import type { ExitCode } from '../exit-codes.js';
|
|
3
|
+
import { type KnowledgePushArticle } from '../knowledge-api.js';
|
|
4
|
+
export declare const KNOWLEDGE_SUBCOMMANDS: readonly ['list', 'push', 'search', 'pull'];
|
|
5
|
+
export interface KnowledgeArgs {
|
|
6
|
+
host?: string;
|
|
7
|
+
source?: string;
|
|
8
|
+
subcommand?: string;
|
|
9
|
+
/** `push <dir>`, `search <query>`. */
|
|
10
|
+
target?: string;
|
|
11
|
+
/** A team's slug: that team's collection instead of the Source's General one. */
|
|
12
|
+
team?: string;
|
|
13
|
+
/** `pull`: where to write (default `knowledge/<team|general>`). */
|
|
14
|
+
out?: string;
|
|
15
|
+
dir: string;
|
|
16
|
+
env: Record<string, string | undefined>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* `knowledge` — a Source's Knowledge base (plan 244): the General collection
|
|
20
|
+
* every team reads, and with `--team` one team's own collection. `push`
|
|
21
|
+
* turns a folder of markdown files into articles — one per `.md`, keyed by
|
|
22
|
+
* its path, so pushing the same folder again only changes what changed —
|
|
23
|
+
* and `pull` writes them back the same way. `search` runs as a team's
|
|
24
|
+
* agents do: that team's articles plus General.
|
|
25
|
+
*/
|
|
26
|
+
export declare function runKnowledge(ctx: CliContext, args: KnowledgeArgs): Promise<ExitCode>;
|
|
27
|
+
/** Mirrors the host's `knowledgeSlug`: a file's path without `.md`. */
|
|
28
|
+
export declare function knowledgeSlugFromPath(relative: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Every markdown file under `target`, as articles. The title is the
|
|
31
|
+
* frontmatter `title:`, else the first `# heading` (taken out of the body so
|
|
32
|
+
* it is not shown twice), else the file name; tags are the frontmatter
|
|
33
|
+
* `tags:` (`[a, b]`, `a, b` or a `- a` list).
|
|
34
|
+
*/
|
|
35
|
+
export declare function readKnowledgeFolder(target: string, cwd: string): (KnowledgePushArticle & {
|
|
36
|
+
file: string;
|
|
37
|
+
})[];
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
var __rest = (this && this.__rest) || function (s, e) {
|
|
2
|
+
var t = {};
|
|
3
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
|
4
|
+
t[p] = s[p];
|
|
5
|
+
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
|
6
|
+
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
|
7
|
+
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
|
8
|
+
t[p[i]] = s[p[i]];
|
|
9
|
+
}
|
|
10
|
+
return t;
|
|
11
|
+
};
|
|
12
|
+
import * as fs from 'node:fs';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import { refreshStoredSession, requireSession, resolveAuthEnvironment, } from '../auth/session.js';
|
|
15
|
+
import { authRequiredError, notFoundError, usageError, validationError, } from '../errors.js';
|
|
16
|
+
import { KNOWLEDGE_PUSH_BATCH, KnowledgeListSchema, KnowledgePushSchema, KnowledgeSearchSchema, knowledgeCall, } from '../knowledge-api.js';
|
|
17
|
+
import { classifyPath } from '../safe-fs.js';
|
|
18
|
+
import { resolveSourceTarget } from '../source-target.js';
|
|
19
|
+
import { pluralize } from '../ui.js';
|
|
20
|
+
export const KNOWLEDGE_SUBCOMMANDS = [
|
|
21
|
+
'list',
|
|
22
|
+
'push',
|
|
23
|
+
'search',
|
|
24
|
+
'pull',
|
|
25
|
+
];
|
|
26
|
+
const USAGE = 'Run one of: ekanos knowledge list [--team <slug>] | push <dir> [--team <slug>] | search <query> --team <slug> | pull [--team <slug>] [--out <dir>].';
|
|
27
|
+
/**
|
|
28
|
+
* `knowledge` — a Source's Knowledge base (plan 244): the General collection
|
|
29
|
+
* every team reads, and with `--team` one team's own collection. `push`
|
|
30
|
+
* turns a folder of markdown files into articles — one per `.md`, keyed by
|
|
31
|
+
* its path, so pushing the same folder again only changes what changed —
|
|
32
|
+
* and `pull` writes them back the same way. `search` runs as a team's
|
|
33
|
+
* agents do: that team's articles plus General.
|
|
34
|
+
*/
|
|
35
|
+
export async function runKnowledge(ctx, args) {
|
|
36
|
+
var _a;
|
|
37
|
+
var _b, _c;
|
|
38
|
+
const subcommand = parseSubcommand(args.subcommand);
|
|
39
|
+
const team = ((_a = args.team) === null || _a === void 0 ? void 0 : _a.trim()) || undefined;
|
|
40
|
+
// Read the folder before any network call, so a bad one fails fast.
|
|
41
|
+
const files = subcommand === 'push'
|
|
42
|
+
? readKnowledgeFolder(requireTarget(args.target, 'push'), args.dir)
|
|
43
|
+
: null;
|
|
44
|
+
if (subcommand === 'search') {
|
|
45
|
+
requireTarget(args.target, 'search');
|
|
46
|
+
if (!team) {
|
|
47
|
+
throw usageError('knowledge search needs --team <slug>.', 'Search runs as that team’s agents see it: its articles plus the General collection.');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const env = resolveAuthEnvironment(ctx, args.host, args.env, {
|
|
51
|
+
projectDir: args.dir,
|
|
52
|
+
});
|
|
53
|
+
const resolved = await resolveSourceTarget(env, requireSession(env.store, env.host), args);
|
|
54
|
+
const client = new KnowledgeClient(env, resolved.session);
|
|
55
|
+
const source = resolved.source;
|
|
56
|
+
const send = (schema, init) => client.call((token) => knowledgeCall(env.host, token, schema, init));
|
|
57
|
+
const where = (slug) => (team ? `team ${team}` : `${slug} General`);
|
|
58
|
+
switch (subcommand) {
|
|
59
|
+
case 'list': {
|
|
60
|
+
const list = await send(KnowledgeListSchema, {
|
|
61
|
+
method: 'GET',
|
|
62
|
+
query: { source, team },
|
|
63
|
+
});
|
|
64
|
+
if (list.articles.length === 0) {
|
|
65
|
+
return ctx.succeed(Object.assign({ host: env.host }, list), ctx.ui.result({
|
|
66
|
+
title: `No Knowledge articles in ${where(list.source.slug)} yet`,
|
|
67
|
+
tone: 'warn',
|
|
68
|
+
notes: [
|
|
69
|
+
`"ekanos knowledge push <dir>${team ? ` --team ${team}` : ''}" stores a folder of .md files.`,
|
|
70
|
+
],
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
return ctx.succeed(Object.assign({ host: env.host }, list), [
|
|
74
|
+
ctx.ui.bold(`${pluralize(list.articles.length, 'article')} in ${where(list.source.slug)}`),
|
|
75
|
+
ctx.ui.table(['Slug', 'Title', 'Tags', 'Version', 'Updated'], list.articles.map((article) => [
|
|
76
|
+
article.slug,
|
|
77
|
+
article.title,
|
|
78
|
+
article.tags.join(', '),
|
|
79
|
+
`v${article.version}`,
|
|
80
|
+
article.updatedAt.slice(0, 10),
|
|
81
|
+
])),
|
|
82
|
+
].join('\n'));
|
|
83
|
+
}
|
|
84
|
+
case 'search': {
|
|
85
|
+
const query = args.target;
|
|
86
|
+
const found = await send(KnowledgeSearchSchema, {
|
|
87
|
+
method: 'GET',
|
|
88
|
+
path: '/search',
|
|
89
|
+
query: { source, team, q: query },
|
|
90
|
+
});
|
|
91
|
+
if (found.results.length === 0) {
|
|
92
|
+
return ctx.succeed(Object.assign({ host: env.host }, found), ctx.ui.result({
|
|
93
|
+
title: `Nothing in team ${team}'s Knowledge matches "${query}"`,
|
|
94
|
+
tone: 'warn',
|
|
95
|
+
notes: ['Try fewer or different words.'],
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
return ctx.succeed(Object.assign({ host: env.host }, found), ctx.ui.result({
|
|
99
|
+
title: `${found.results.length === 1 ? '1 match' : `${found.results.length} matches`} for "${query}" in team ${team}`,
|
|
100
|
+
rows: found.results.map((hit) => [
|
|
101
|
+
`${hit.title} (${hit.collection === 'general' ? 'General' : 'team'})`,
|
|
102
|
+
[hit.snippet.replace(/\s+/g, ' '), ctx.ui.dim(hit.slug)],
|
|
103
|
+
]),
|
|
104
|
+
}));
|
|
105
|
+
}
|
|
106
|
+
case 'push': {
|
|
107
|
+
const articles = files;
|
|
108
|
+
const results = [];
|
|
109
|
+
let pushed = null;
|
|
110
|
+
for (let start = 0; start < articles.length; start += KNOWLEDGE_PUSH_BATCH) {
|
|
111
|
+
const batch = articles.slice(start, start + KNOWLEDGE_PUSH_BATCH);
|
|
112
|
+
const response = await send(KnowledgePushSchema, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
query: {},
|
|
115
|
+
body: Object.assign(Object.assign({ source }, (team ? { team } : {})), { articles: batch.map((_a) => {
|
|
116
|
+
var { file: _file } = _a, article = __rest(_a, ["file"]);
|
|
117
|
+
return article;
|
|
118
|
+
}) }),
|
|
119
|
+
});
|
|
120
|
+
pushed = response;
|
|
121
|
+
results.push(...response.results);
|
|
122
|
+
}
|
|
123
|
+
const count = (status) => results.filter((result) => result.status === status).length;
|
|
124
|
+
const refused = results.filter((result) => result.status === 'refused');
|
|
125
|
+
return ctx.succeed({ host: env.host, team: team !== null && team !== void 0 ? team : null, results }, ctx.ui.result({
|
|
126
|
+
title: `Pushed ${pluralize(results.length, 'article')} to ${where((_b = pushed === null || pushed === void 0 ? void 0 : pushed.source.slug) !== null && _b !== void 0 ? _b : source)}`,
|
|
127
|
+
tone: refused.length > 0 ? 'warn' : 'ok',
|
|
128
|
+
rows: [
|
|
129
|
+
['Created', String(count('created'))],
|
|
130
|
+
['Updated', String(count('updated'))],
|
|
131
|
+
['Unchanged', String(count('unchanged'))],
|
|
132
|
+
...(refused.length > 0
|
|
133
|
+
? [
|
|
134
|
+
[
|
|
135
|
+
'Refused',
|
|
136
|
+
refused.map((result) => { var _a; return `${result.slug}: ${(_a = result.message) !== null && _a !== void 0 ? _a : ''}`; }),
|
|
137
|
+
],
|
|
138
|
+
]
|
|
139
|
+
: []),
|
|
140
|
+
],
|
|
141
|
+
notes: [
|
|
142
|
+
team
|
|
143
|
+
? `Team ${team}'s agents with search_knowledge find these now.`
|
|
144
|
+
: 'Every team on the Source reads the General collection.',
|
|
145
|
+
],
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
case 'pull': {
|
|
149
|
+
const list = await send(KnowledgeListSchema, {
|
|
150
|
+
method: 'GET',
|
|
151
|
+
query: { source, team, body: '1' },
|
|
152
|
+
});
|
|
153
|
+
const outDir = path.resolve(args.dir, (_c = args.out) !== null && _c !== void 0 ? _c : path.join('knowledge', team !== null && team !== void 0 ? team : 'general'));
|
|
154
|
+
const written = writeKnowledgeFolder(outDir, list.articles);
|
|
155
|
+
return ctx.succeed({ host: env.host, team: team !== null && team !== void 0 ? team : null, dir: outDir, files: written }, ctx.ui.result({
|
|
156
|
+
title: `Pulled ${pluralize(written.length, 'article')} from ${where(list.source.slug)}`,
|
|
157
|
+
rows: [['Into', outDir]],
|
|
158
|
+
notes: [
|
|
159
|
+
`Edit, then "ekanos knowledge push ${path.relative(args.dir, outDir) || '.'}${team ? ` --team ${team}` : ''}".`,
|
|
160
|
+
],
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Every call refreshes the session once on a 401, then gives up. */
|
|
166
|
+
class KnowledgeClient {
|
|
167
|
+
constructor(env, session) {
|
|
168
|
+
this.env = env;
|
|
169
|
+
this.session = session;
|
|
170
|
+
}
|
|
171
|
+
async call(send) {
|
|
172
|
+
let result = await send(this.session.accessToken);
|
|
173
|
+
if (result.status === 'unauthorized') {
|
|
174
|
+
this.session = await refreshStoredSession(this.env, this.session);
|
|
175
|
+
result = await send(this.session.accessToken);
|
|
176
|
+
}
|
|
177
|
+
if (result.status === 'ok')
|
|
178
|
+
return result.data;
|
|
179
|
+
throw authRequiredError(`The session for ${this.env.host} could not authorize the knowledge call.`, `Run "ekanos login --host ${this.env.host}" and retry.`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Mirrors the host's `knowledgeSlug`: a file's path without `.md`. */
|
|
183
|
+
export function knowledgeSlugFromPath(relative) {
|
|
184
|
+
return relative
|
|
185
|
+
.trim()
|
|
186
|
+
.toLowerCase()
|
|
187
|
+
.replace(/\\/g, '/')
|
|
188
|
+
.replace(/\.(md|markdown|mdx)$/, '')
|
|
189
|
+
.normalize('NFKD')
|
|
190
|
+
.replace(/[̀-ͯ]/g, '')
|
|
191
|
+
.replace(/[^a-z0-9._/-]+/g, '-')
|
|
192
|
+
.replace(/\/+/g, '/')
|
|
193
|
+
.replace(/-{2,}/g, '-')
|
|
194
|
+
.split('/')
|
|
195
|
+
.map((part) => part.replace(/^[-.]+|[-.]+$/g, ''))
|
|
196
|
+
.filter(Boolean)
|
|
197
|
+
.join('/')
|
|
198
|
+
.slice(0, 200);
|
|
199
|
+
}
|
|
200
|
+
const IGNORED = new Set(['node_modules', '.git']);
|
|
201
|
+
const MARKDOWN = /\.(md|markdown|mdx)$/i;
|
|
202
|
+
const BODY_MAX = 200000;
|
|
203
|
+
/**
|
|
204
|
+
* Every markdown file under `target`, as articles. The title is the
|
|
205
|
+
* frontmatter `title:`, else the first `# heading` (taken out of the body so
|
|
206
|
+
* it is not shown twice), else the file name; tags are the frontmatter
|
|
207
|
+
* `tags:` (`[a, b]`, `a, b` or a `- a` list).
|
|
208
|
+
*/
|
|
209
|
+
export function readKnowledgeFolder(target, cwd) {
|
|
210
|
+
const root = path.resolve(cwd, target);
|
|
211
|
+
const kind = classifyPath(root);
|
|
212
|
+
if (kind !== 'directory' && kind !== 'file') {
|
|
213
|
+
throw notFoundError(`No folder or file at ${root}.`, 'Pass a folder of .md files, e.g. "ekanos knowledge push ./knowledge".');
|
|
214
|
+
}
|
|
215
|
+
const found = [];
|
|
216
|
+
const walk = (dir) => {
|
|
217
|
+
for (const name of fs.readdirSync(dir).sort()) {
|
|
218
|
+
if (IGNORED.has(name) || name.startsWith('.'))
|
|
219
|
+
continue;
|
|
220
|
+
const full = path.join(dir, name);
|
|
221
|
+
const entry = classifyPath(full);
|
|
222
|
+
if (entry === 'directory')
|
|
223
|
+
walk(full);
|
|
224
|
+
else if (entry === 'file' && MARKDOWN.test(name))
|
|
225
|
+
found.push(full);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
if (kind === 'file')
|
|
229
|
+
found.push(root);
|
|
230
|
+
else
|
|
231
|
+
walk(root);
|
|
232
|
+
if (found.length === 0) {
|
|
233
|
+
throw validationError(`No .md files under ${root}.`, 'Each markdown file becomes one article.');
|
|
234
|
+
}
|
|
235
|
+
const base = kind === 'file' ? path.dirname(root) : root;
|
|
236
|
+
const seen = new Map();
|
|
237
|
+
return found.map((file) => {
|
|
238
|
+
const relative = path.relative(base, file).split(path.sep).join('/');
|
|
239
|
+
const markdown = fs.readFileSync(file, 'utf8');
|
|
240
|
+
const { fields, tags, body } = splitFrontmatter(markdown);
|
|
241
|
+
const slug = knowledgeSlugFromPath(fields.slug || relative);
|
|
242
|
+
if (!slug) {
|
|
243
|
+
throw validationError(`${file}: no usable slug.`, 'Rename the file with letters or digits.');
|
|
244
|
+
}
|
|
245
|
+
const clash = seen.get(slug);
|
|
246
|
+
if (clash) {
|
|
247
|
+
throw validationError(`${file} and ${clash} both become "${slug}".`, 'Rename one of them.');
|
|
248
|
+
}
|
|
249
|
+
seen.set(slug, file);
|
|
250
|
+
const heading = fields.title
|
|
251
|
+
? null
|
|
252
|
+
: body.match(/^[ \t]*#[ \t]+(.+?)[ \t]*#*[ \t]*$/m);
|
|
253
|
+
const title = (fields.title ||
|
|
254
|
+
(heading === null || heading === void 0 ? void 0 : heading[1]) ||
|
|
255
|
+
path.basename(file).replace(MARKDOWN, '').replace(/[-_]+/g, ' '))
|
|
256
|
+
.trim()
|
|
257
|
+
.slice(0, 300);
|
|
258
|
+
const text = (heading ? body.replace(heading[0], '') : body).replace(/^\s*\n/, '');
|
|
259
|
+
if (text.length > BODY_MAX) {
|
|
260
|
+
throw validationError(`${file} is longer than ${BODY_MAX.toLocaleString()} characters.`, 'Split it into smaller articles.');
|
|
261
|
+
}
|
|
262
|
+
return Object.assign({ file,
|
|
263
|
+
slug, title: title || slug, body: text }, (tags.length > 0 ? { tags: tags.slice(0, 30) } : {}));
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
function writeKnowledgeFolder(outDir, articles) {
|
|
267
|
+
var _a;
|
|
268
|
+
const written = [];
|
|
269
|
+
for (const article of articles) {
|
|
270
|
+
const target = path.resolve(outDir, ...`${article.slug}.md`.split('/'));
|
|
271
|
+
if (!target.startsWith(outDir + path.sep)) {
|
|
272
|
+
throw validationError(`Refusing to write ${article.slug} outside ${outDir}.`, 'The host sent a slug that leaves the folder.');
|
|
273
|
+
}
|
|
274
|
+
const kind = classifyPath(target);
|
|
275
|
+
if (kind !== 'missing' && kind !== 'file') {
|
|
276
|
+
throw validationError(`Refusing to write through ${target} (${kind}).`, 'Remove it, or pass --out <dir>.');
|
|
277
|
+
}
|
|
278
|
+
const frontmatter = [
|
|
279
|
+
'---',
|
|
280
|
+
`title: ${JSON.stringify(article.title)}`,
|
|
281
|
+
...(article.tags.length > 0
|
|
282
|
+
? [
|
|
283
|
+
`tags: [${article.tags.map((tag) => JSON.stringify(tag)).join(', ')}]`,
|
|
284
|
+
]
|
|
285
|
+
: []),
|
|
286
|
+
'---',
|
|
287
|
+
'',
|
|
288
|
+
].join('\n');
|
|
289
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
290
|
+
fs.writeFileSync(target, frontmatter + ((_a = article.body) !== null && _a !== void 0 ? _a : ''));
|
|
291
|
+
written.push(target);
|
|
292
|
+
}
|
|
293
|
+
return written;
|
|
294
|
+
}
|
|
295
|
+
const FRONTMATTER = /^?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
|
|
296
|
+
/** Top-level `title`, `slug` and `tags` only. */
|
|
297
|
+
function splitFrontmatter(markdown) {
|
|
298
|
+
const match = markdown.match(FRONTMATTER);
|
|
299
|
+
if (!match)
|
|
300
|
+
return { fields: {}, tags: [], body: markdown };
|
|
301
|
+
const fields = {};
|
|
302
|
+
const tags = [];
|
|
303
|
+
const lines = match[1].split(/\r?\n/);
|
|
304
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
305
|
+
const pair = lines[index].match(/^([A-Za-z0-9_-]+):[ \t]*(.*)$/);
|
|
306
|
+
if (!pair)
|
|
307
|
+
continue;
|
|
308
|
+
const key = pair[1].toLowerCase();
|
|
309
|
+
const value = pair[2].trim();
|
|
310
|
+
if (key === 'tags') {
|
|
311
|
+
if (value) {
|
|
312
|
+
tags.push(...value.replace(/^\[|\]$/g, '').split(','));
|
|
313
|
+
}
|
|
314
|
+
while (index + 1 < lines.length &&
|
|
315
|
+
/^[ \t]*-[ \t]+/.test(lines[index + 1])) {
|
|
316
|
+
tags.push(lines[index + 1].replace(/^[ \t]*-[ \t]+/, ''));
|
|
317
|
+
index += 1;
|
|
318
|
+
}
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
fields[key] = unquote(value);
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
fields,
|
|
325
|
+
tags: [...new Set(tags.map((tag) => unquote(tag.trim())).filter(Boolean))],
|
|
326
|
+
body: markdown.slice(match[0].length),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function unquote(value) {
|
|
330
|
+
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
331
|
+
try {
|
|
332
|
+
const parsed = JSON.parse(value);
|
|
333
|
+
if (typeof parsed === 'string')
|
|
334
|
+
return parsed;
|
|
335
|
+
}
|
|
336
|
+
catch (_a) {
|
|
337
|
+
// Not JSON-escaped: strip the quotes below.
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (value.length >= 2 &&
|
|
341
|
+
((value.startsWith('"') && value.endsWith('"')) ||
|
|
342
|
+
(value.startsWith("'") && value.endsWith("'")))) {
|
|
343
|
+
return value.slice(1, -1);
|
|
344
|
+
}
|
|
345
|
+
return value;
|
|
346
|
+
}
|
|
347
|
+
function parseSubcommand(value) {
|
|
348
|
+
const match = KNOWLEDGE_SUBCOMMANDS.find((name) => name === value);
|
|
349
|
+
if (match)
|
|
350
|
+
return match;
|
|
351
|
+
throw usageError(value
|
|
352
|
+
? `Unknown knowledge subcommand "${value}".`
|
|
353
|
+
: 'knowledge needs a subcommand.', USAGE);
|
|
354
|
+
}
|
|
355
|
+
function requireTarget(value, verb) {
|
|
356
|
+
if (value === null || value === void 0 ? void 0 : value.trim())
|
|
357
|
+
return value;
|
|
358
|
+
throw usageError(verb === 'push'
|
|
359
|
+
? 'knowledge push needs a folder of .md files.'
|
|
360
|
+
: 'knowledge search needs a query.', verb === 'push'
|
|
361
|
+
? 'e.g. "ekanos knowledge push ./knowledge --team first-bank".'
|
|
362
|
+
: 'e.g. "ekanos knowledge search \\"wire cutoff\\" --team first-bank".');
|
|
363
|
+
}
|
|
364
|
+
//# sourceMappingURL=knowledge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge.js","sourceRoot":"","sources":["../../src/commands/knowledge.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'node:fs';\nimport * as path from 'node:path';\nimport type { z } from 'zod';\n\nimport type { StoredSession } from '../auth/credential-store';\nimport {\n type AuthEnvironment,\n refreshStoredSession,\n requireSession,\n resolveAuthEnvironment,\n} from '../auth/session';\nimport type { CliContext } from '../context';\nimport {\n authRequiredError,\n notFoundError,\n usageError,\n validationError,\n} from '../errors';\nimport type { ExitCode } from '../exit-codes';\nimport {\n KNOWLEDGE_PUSH_BATCH,\n type KnowledgeApiResult,\n type KnowledgeArticle,\n KnowledgeListSchema,\n type KnowledgePushArticle,\n type KnowledgePushResult,\n KnowledgePushSchema,\n KnowledgeSearchSchema,\n knowledgeCall,\n} from '../knowledge-api';\nimport { classifyPath } from '../safe-fs';\nimport { resolveSourceTarget } from '../source-target';\nimport { pluralize } from '../ui';\n\nexport const KNOWLEDGE_SUBCOMMANDS = [\n 'list',\n 'push',\n 'search',\n 'pull',\n] as const;\ntype KnowledgeSubcommand = (typeof KNOWLEDGE_SUBCOMMANDS)[number];\n\nexport interface KnowledgeArgs {\n host?: string;\n source?: string;\n subcommand?: string;\n /** `push <dir>`, `search <query>`. */\n target?: string;\n /** A team's slug: that team's collection instead of the Source's General one. */\n team?: string;\n /** `pull`: where to write (default `knowledge/<team|general>`). */\n out?: string;\n dir: string;\n env: Record<string, string | undefined>;\n}\n\nconst USAGE =\n 'Run one of: ekanos knowledge list [--team <slug>] | push <dir> [--team <slug>] | search <query> --team <slug> | pull [--team <slug>] [--out <dir>].';\n\n/**\n * `knowledge` — a Source's Knowledge base (plan 244): the General collection\n * every team reads, and with `--team` one team's own collection. `push`\n * turns a folder of markdown files into articles — one per `.md`, keyed by\n * its path, so pushing the same folder again only changes what changed —\n * and `pull` writes them back the same way. `search` runs as a team's\n * agents do: that team's articles plus General.\n */\nexport async function runKnowledge(\n ctx: CliContext,\n args: KnowledgeArgs,\n): Promise<ExitCode> {\n const subcommand = parseSubcommand(args.subcommand);\n const team = args.team?.trim() || undefined;\n\n // Read the folder before any network call, so a bad one fails fast.\n const files =\n subcommand === 'push'\n ? readKnowledgeFolder(requireTarget(args.target, 'push'), args.dir)\n : null;\n if (subcommand === 'search') {\n requireTarget(args.target, 'search');\n if (!team) {\n throw usageError(\n 'knowledge search needs --team <slug>.',\n 'Search runs as that team’s agents see it: its articles plus the General collection.',\n );\n }\n }\n\n const env = resolveAuthEnvironment(ctx, args.host, args.env, {\n projectDir: args.dir,\n });\n const resolved = await resolveSourceTarget(\n env,\n requireSession(env.store, env.host),\n args,\n );\n const client = new KnowledgeClient(env, resolved.session);\n const source = resolved.source;\n const send = <T>(\n schema: z.ZodType<T>,\n init: Parameters<typeof knowledgeCall>[3],\n ) => client.call((token) => knowledgeCall(env.host, token, schema, init));\n const where = (slug: string) => (team ? `team ${team}` : `${slug} General`);\n\n switch (subcommand) {\n case 'list': {\n const list = await send(KnowledgeListSchema, {\n method: 'GET',\n query: { source, team },\n });\n if (list.articles.length === 0) {\n return ctx.succeed(\n { host: env.host, ...list },\n ctx.ui.result({\n title: `No Knowledge articles in ${where(list.source.slug)} yet`,\n tone: 'warn',\n notes: [\n `\"ekanos knowledge push <dir>${team ? ` --team ${team}` : ''}\" stores a folder of .md files.`,\n ],\n }),\n );\n }\n return ctx.succeed(\n { host: env.host, ...list },\n [\n ctx.ui.bold(\n `${pluralize(list.articles.length, 'article')} in ${where(list.source.slug)}`,\n ),\n ctx.ui.table(\n ['Slug', 'Title', 'Tags', 'Version', 'Updated'],\n list.articles.map((article) => [\n article.slug,\n article.title,\n article.tags.join(', '),\n `v${article.version}`,\n article.updatedAt.slice(0, 10),\n ]),\n ),\n ].join('\\n'),\n );\n }\n\n case 'search': {\n const query = args.target!;\n const found = await send(KnowledgeSearchSchema, {\n method: 'GET',\n path: '/search',\n query: { source, team, q: query },\n });\n if (found.results.length === 0) {\n return ctx.succeed(\n { host: env.host, ...found },\n ctx.ui.result({\n title: `Nothing in team ${team}'s Knowledge matches \"${query}\"`,\n tone: 'warn',\n notes: ['Try fewer or different words.'],\n }),\n );\n }\n return ctx.succeed(\n { host: env.host, ...found },\n ctx.ui.result({\n title: `${found.results.length === 1 ? '1 match' : `${found.results.length} matches`} for \"${query}\" in team ${team}`,\n rows: found.results.map((hit) => [\n `${hit.title} (${hit.collection === 'general' ? 'General' : 'team'})`,\n [hit.snippet.replace(/\\s+/g, ' '), ctx.ui.dim(hit.slug)],\n ]),\n }),\n );\n }\n\n case 'push': {\n const articles = files!;\n const results: KnowledgePushResult[] = [];\n let pushed: { source: { slug: string } } | null = null;\n for (\n let start = 0;\n start < articles.length;\n start += KNOWLEDGE_PUSH_BATCH\n ) {\n const batch = articles.slice(start, start + KNOWLEDGE_PUSH_BATCH);\n const response = await send(KnowledgePushSchema, {\n method: 'POST',\n query: {},\n body: {\n source,\n ...(team ? { team } : {}),\n articles: batch.map(({ file: _file, ...article }) => article),\n },\n });\n pushed = response;\n results.push(...response.results);\n }\n const count = (status: KnowledgePushResult['status']) =>\n results.filter((result) => result.status === status).length;\n const refused = results.filter((result) => result.status === 'refused');\n return ctx.succeed(\n { host: env.host, team: team ?? null, results },\n ctx.ui.result({\n title: `Pushed ${pluralize(results.length, 'article')} to ${where(pushed?.source.slug ?? source)}`,\n tone: refused.length > 0 ? 'warn' : 'ok',\n rows: [\n ['Created', String(count('created'))],\n ['Updated', String(count('updated'))],\n ['Unchanged', String(count('unchanged'))],\n ...(refused.length > 0\n ? ([\n [\n 'Refused',\n refused.map(\n (result) => `${result.slug}: ${result.message ?? ''}`,\n ),\n ],\n ] as const)\n : []),\n ],\n notes: [\n team\n ? `Team ${team}'s agents with search_knowledge find these now.`\n : 'Every team on the Source reads the General collection.',\n ],\n }),\n );\n }\n\n case 'pull': {\n const list = await send(KnowledgeListSchema, {\n method: 'GET',\n query: { source, team, body: '1' },\n });\n const outDir = path.resolve(\n args.dir,\n args.out ?? path.join('knowledge', team ?? 'general'),\n );\n const written = writeKnowledgeFolder(outDir, list.articles);\n return ctx.succeed(\n { host: env.host, team: team ?? null, dir: outDir, files: written },\n ctx.ui.result({\n title: `Pulled ${pluralize(written.length, 'article')} from ${where(list.source.slug)}`,\n rows: [['Into', outDir]],\n notes: [\n `Edit, then \"ekanos knowledge push ${path.relative(args.dir, outDir) || '.'}${team ? ` --team ${team}` : ''}\".`,\n ],\n }),\n );\n }\n }\n}\n\n/** Every call refreshes the session once on a 401, then gives up. */\nclass KnowledgeClient {\n constructor(\n private readonly env: AuthEnvironment,\n private session: StoredSession,\n ) {}\n\n async call<T>(\n send: (accessToken: string) => Promise<KnowledgeApiResult<T>>,\n ): Promise<T> {\n let result = await send(this.session.accessToken);\n if (result.status === 'unauthorized') {\n this.session = await refreshStoredSession(this.env, this.session);\n result = await send(this.session.accessToken);\n }\n if (result.status === 'ok') return result.data;\n throw authRequiredError(\n `The session for ${this.env.host} could not authorize the knowledge call.`,\n `Run \"ekanos login --host ${this.env.host}\" and retry.`,\n );\n }\n}\n\n/** Mirrors the host's `knowledgeSlug`: a file's path without `.md`. */\nexport function knowledgeSlugFromPath(relative: string): string {\n return relative\n .trim()\n .toLowerCase()\n .replace(/\\\\/g, '/')\n .replace(/\\.(md|markdown|mdx)$/, '')\n .normalize('NFKD')\n .replace(/[̀-ͯ]/g, '')\n .replace(/[^a-z0-9._/-]+/g, '-')\n .replace(/\\/+/g, '/')\n .replace(/-{2,}/g, '-')\n .split('/')\n .map((part) => part.replace(/^[-.]+|[-.]+$/g, ''))\n .filter(Boolean)\n .join('/')\n .slice(0, 200);\n}\n\nconst IGNORED = new Set(['node_modules', '.git']);\nconst MARKDOWN = /\\.(md|markdown|mdx)$/i;\nconst BODY_MAX = 200_000;\n\n/**\n * Every markdown file under `target`, as articles. The title is the\n * frontmatter `title:`, else the first `# heading` (taken out of the body so\n * it is not shown twice), else the file name; tags are the frontmatter\n * `tags:` (`[a, b]`, `a, b` or a `- a` list).\n */\nexport function readKnowledgeFolder(\n target: string,\n cwd: string,\n): (KnowledgePushArticle & { file: string })[] {\n const root = path.resolve(cwd, target);\n const kind = classifyPath(root);\n if (kind !== 'directory' && kind !== 'file') {\n throw notFoundError(\n `No folder or file at ${root}.`,\n 'Pass a folder of .md files, e.g. \"ekanos knowledge push ./knowledge\".',\n );\n }\n\n const found: string[] = [];\n const walk = (dir: string) => {\n for (const name of fs.readdirSync(dir).sort()) {\n if (IGNORED.has(name) || name.startsWith('.')) continue;\n const full = path.join(dir, name);\n const entry = classifyPath(full);\n if (entry === 'directory') walk(full);\n else if (entry === 'file' && MARKDOWN.test(name)) found.push(full);\n }\n };\n if (kind === 'file') found.push(root);\n else walk(root);\n\n if (found.length === 0) {\n throw validationError(\n `No .md files under ${root}.`,\n 'Each markdown file becomes one article.',\n );\n }\n\n const base = kind === 'file' ? path.dirname(root) : root;\n const seen = new Map<string, string>();\n return found.map((file) => {\n const relative = path.relative(base, file).split(path.sep).join('/');\n const markdown = fs.readFileSync(file, 'utf8');\n const { fields, tags, body } = splitFrontmatter(markdown);\n const slug = knowledgeSlugFromPath(fields.slug || relative);\n if (!slug) {\n throw validationError(\n `${file}: no usable slug.`,\n 'Rename the file with letters or digits.',\n );\n }\n const clash = seen.get(slug);\n if (clash) {\n throw validationError(\n `${file} and ${clash} both become \"${slug}\".`,\n 'Rename one of them.',\n );\n }\n seen.set(slug, file);\n\n const heading = fields.title\n ? null\n : body.match(/^[ \\t]*#[ \\t]+(.+?)[ \\t]*#*[ \\t]*$/m);\n const title = (\n fields.title ||\n heading?.[1] ||\n path.basename(file).replace(MARKDOWN, '').replace(/[-_]+/g, ' ')\n )\n .trim()\n .slice(0, 300);\n const text = (heading ? body.replace(heading[0], '') : body).replace(\n /^\\s*\\n/,\n '',\n );\n if (text.length > BODY_MAX) {\n throw validationError(\n `${file} is longer than ${BODY_MAX.toLocaleString()} characters.`,\n 'Split it into smaller articles.',\n );\n }\n return {\n file,\n slug,\n title: title || slug,\n body: text,\n ...(tags.length > 0 ? { tags: tags.slice(0, 30) } : {}),\n };\n });\n}\n\nfunction writeKnowledgeFolder(\n outDir: string,\n articles: KnowledgeArticle[],\n): string[] {\n const written: string[] = [];\n for (const article of articles) {\n const target = path.resolve(outDir, ...`${article.slug}.md`.split('/'));\n if (!target.startsWith(outDir + path.sep)) {\n throw validationError(\n `Refusing to write ${article.slug} outside ${outDir}.`,\n 'The host sent a slug that leaves the folder.',\n );\n }\n const kind = classifyPath(target);\n if (kind !== 'missing' && kind !== 'file') {\n throw validationError(\n `Refusing to write through ${target} (${kind}).`,\n 'Remove it, or pass --out <dir>.',\n );\n }\n const frontmatter = [\n '---',\n `title: ${JSON.stringify(article.title)}`,\n ...(article.tags.length > 0\n ? [\n `tags: [${article.tags.map((tag) => JSON.stringify(tag)).join(', ')}]`,\n ]\n : []),\n '---',\n '',\n ].join('\\n');\n fs.mkdirSync(path.dirname(target), { recursive: true });\n fs.writeFileSync(target, frontmatter + (article.body ?? ''));\n written.push(target);\n }\n return written;\n}\n\nconst FRONTMATTER = /^?---[ \\t]*\\r?\\n([\\s\\S]*?)\\r?\\n---[ \\t]*(?:\\r?\\n|$)/;\n\n/** Top-level `title`, `slug` and `tags` only. */\nfunction splitFrontmatter(markdown: string): {\n fields: Record<string, string>;\n tags: string[];\n body: string;\n} {\n const match = markdown.match(FRONTMATTER);\n if (!match) return { fields: {}, tags: [], body: markdown };\n const fields: Record<string, string> = {};\n const tags: string[] = [];\n const lines = match[1]!.split(/\\r?\\n/);\n for (let index = 0; index < lines.length; index += 1) {\n const pair = lines[index]!.match(/^([A-Za-z0-9_-]+):[ \\t]*(.*)$/);\n if (!pair) continue;\n const key = pair[1]!.toLowerCase();\n const value = pair[2]!.trim();\n if (key === 'tags') {\n if (value) {\n tags.push(...value.replace(/^\\[|\\]$/g, '').split(','));\n }\n while (\n index + 1 < lines.length &&\n /^[ \\t]*-[ \\t]+/.test(lines[index + 1]!)\n ) {\n tags.push(lines[index + 1]!.replace(/^[ \\t]*-[ \\t]+/, ''));\n index += 1;\n }\n continue;\n }\n fields[key] = unquote(value);\n }\n return {\n fields,\n tags: [...new Set(tags.map((tag) => unquote(tag.trim())).filter(Boolean))],\n body: markdown.slice(match[0].length),\n };\n}\n\nfunction unquote(value: string): string {\n if (value.length >= 2 && value.startsWith('\"') && value.endsWith('\"')) {\n try {\n const parsed: unknown = JSON.parse(value);\n if (typeof parsed === 'string') return parsed;\n } catch {\n // Not JSON-escaped: strip the quotes below.\n }\n }\n if (\n value.length >= 2 &&\n ((value.startsWith('\"') && value.endsWith('\"')) ||\n (value.startsWith(\"'\") && value.endsWith(\"'\")))\n ) {\n return value.slice(1, -1);\n }\n return value;\n}\n\nfunction parseSubcommand(value: string | undefined): KnowledgeSubcommand {\n const match = KNOWLEDGE_SUBCOMMANDS.find((name) => name === value);\n if (match) return match;\n throw usageError(\n value\n ? `Unknown knowledge subcommand \"${value}\".`\n : 'knowledge needs a subcommand.',\n USAGE,\n );\n}\n\nfunction requireTarget(value: string | undefined, verb: 'push' | 'search') {\n if (value?.trim()) return value;\n throw usageError(\n verb === 'push'\n ? 'knowledge push needs a folder of .md files.'\n : 'knowledge search needs a query.',\n verb === 'push'\n ? 'e.g. \"ekanos knowledge push ./knowledge --team first-bank\".'\n : 'e.g. \"ekanos knowledge search \\\\\"wire cutoff\\\\\" --team first-bank\".',\n );\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CliContext } from '../context.js';
|
|
2
|
+
import type { ExitCode } from '../exit-codes.js';
|
|
3
|
+
export declare const ROUTES_SUBCOMMANDS: readonly ['list', 'options', 'set', 'remove', 'webhook', 'unrouted', 'replay'];
|
|
4
|
+
export interface RoutesArgs {
|
|
5
|
+
host?: string;
|
|
6
|
+
source?: string;
|
|
7
|
+
/** `list [product]`, `set <product> <value> <team>`, `replay <event-id>`, … */
|
|
8
|
+
positionals: readonly string[];
|
|
9
|
+
label?: string;
|
|
10
|
+
webhook?: string;
|
|
11
|
+
dir: string;
|
|
12
|
+
env: Record<string, string | undefined>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* `routes` — a Source admin's routing of a Source connection's events to
|
|
16
|
+
* teams (plan 241.4). One Zendesk connected for the whole Source sends every
|
|
17
|
+
* ticket to one webhook; each ticket's organization decides which team's
|
|
18
|
+
* agents see it. `set` maps a value to a team, `remove` unmaps it, `options`
|
|
19
|
+
* lists the values the integration knows (Zendesk's organizations),
|
|
20
|
+
* `webhook` mints the Source's endpoint URL (shown once), and `unrouted` /
|
|
21
|
+
* `replay` deal with events no team was mapped to — they started nothing.
|
|
22
|
+
*/
|
|
23
|
+
export declare function runRoutes(ctx: CliContext, args: RoutesArgs): Promise<ExitCode>;
|