@ekanos/cli 0.12.0 → 0.14.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 +25 -8
- package/dist/agents-api.d.ts +11 -2
- package/dist/agents-api.js +39 -3
- package/dist/agents-api.js.map +1 -1
- package/dist/apps-api.d.ts +3 -2
- package/dist/apps-api.js +3 -7
- package/dist/apps-api.js.map +1 -1
- package/dist/auth/ekanos-api.d.ts +16 -2
- package/dist/auth/ekanos-api.js +101 -15
- package/dist/auth/ekanos-api.js.map +1 -1
- package/dist/auth/host-trust.d.ts +46 -0
- package/dist/auth/host-trust.js +79 -0
- package/dist/auth/host-trust.js.map +1 -0
- package/dist/auth/session.d.ts +10 -1
- package/dist/auth/session.js +28 -28
- package/dist/auth/session.js.map +1 -1
- package/dist/commands/agents-edit.d.ts +6 -2
- package/dist/commands/agents-edit.js +57 -26
- package/dist/commands/agents-edit.js.map +1 -1
- package/dist/commands/agents.d.ts +13 -2
- package/dist/commands/agents.js +148 -36
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/apps.d.ts +15 -4
- package/dist/commands/apps.js +133 -51
- package/dist/commands/apps.js.map +1 -1
- package/dist/commands/catalog.js +7 -6
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/knowledge.d.ts +56 -0
- package/dist/commands/knowledge.js +456 -0
- package/dist/commands/knowledge.js.map +1 -0
- package/dist/commands/login.d.ts +4 -0
- package/dist/commands/login.js +30 -2
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/logout.d.ts +9 -1
- package/dist/commands/logout.js +56 -7
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/publish.js +3 -3
- package/dist/commands/publish.js.map +1 -1
- package/dist/commands/reviewers.d.ts +2 -0
- package/dist/commands/reviewers.js +7 -3
- package/dist/commands/reviewers.js.map +1 -1
- package/dist/commands/routes.d.ts +2 -0
- package/dist/commands/routes.js +42 -9
- package/dist/commands/routes.js.map +1 -1
- package/dist/commands/runs.d.ts +13 -2
- package/dist/commands/runs.js +96 -15
- package/dist/commands/runs.js.map +1 -1
- package/dist/commands/scope-notice.d.ts +21 -0
- package/dist/commands/scope-notice.js +35 -0
- package/dist/commands/scope-notice.js.map +1 -0
- package/dist/commands/skills.d.ts +7 -1
- package/dist/commands/skills.js +80 -24
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/sources.d.ts +4 -0
- package/dist/commands/sources.js +233 -5
- package/dist/commands/sources.js.map +1 -1
- package/dist/commands/status.js +5 -4
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/team-agent-readiness.d.ts +59 -0
- package/dist/commands/team-agent-readiness.js +341 -0
- package/dist/commands/team-agent-readiness.js.map +1 -0
- package/dist/commands/team-agents-agents.d.ts +24 -3
- package/dist/commands/team-agents-agents.js +336 -35
- package/dist/commands/team-agents-agents.js.map +1 -1
- package/dist/commands/team-agents-mcp.js +32 -3
- package/dist/commands/team-agents-mcp.js.map +1 -1
- package/dist/commands/team-agents-triggers.js +230 -54
- package/dist/commands/team-agents-triggers.js.map +1 -1
- package/dist/commands/team-agents.d.ts +3 -1
- package/dist/commands/team-agents.js +27 -7
- package/dist/commands/team-agents.js.map +1 -1
- package/dist/commands/teams-channel-install.d.ts +24 -0
- package/dist/commands/teams-channel-install.js +94 -0
- package/dist/commands/teams-channel-install.js.map +1 -0
- package/dist/commands/teams-connect.d.ts +66 -0
- package/dist/commands/teams-connect.js +397 -0
- package/dist/commands/teams-connect.js.map +1 -0
- package/dist/commands/teams-events.d.ts +12 -0
- package/dist/commands/teams-events.js +138 -0
- package/dist/commands/teams-events.js.map +1 -0
- package/dist/commands/teams-onboard.d.ts +16 -5
- package/dist/commands/teams-onboard.js +61 -36
- package/dist/commands/teams-onboard.js.map +1 -1
- package/dist/commands/teams-overview.d.ts +11 -0
- package/dist/commands/teams-overview.js +160 -0
- package/dist/commands/teams-overview.js.map +1 -0
- package/dist/commands/teams-scope.d.ts +10 -0
- package/dist/commands/teams-scope.js +109 -0
- package/dist/commands/teams-scope.js.map +1 -0
- package/dist/commands/teams.d.ts +64 -7
- package/dist/commands/teams.js +254 -60
- package/dist/commands/teams.js.map +1 -1
- package/dist/commands/upgrade.js +12 -2
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/commands/use.js +2 -2
- package/dist/commands/use.js.map +1 -1
- package/dist/commands/whoami.js +1 -1
- package/dist/commands/whoami.js.map +1 -1
- package/dist/connections-api.js +1 -1
- package/dist/connections-api.js.map +1 -1
- package/dist/delegate.js +11 -4
- package/dist/delegate.js.map +1 -1
- package/dist/harness-scaffold.js +12 -0
- package/dist/harness-scaffold.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +724 -152
- package/dist/index.js.map +1 -1
- package/dist/knowledge-api.d.ts +175 -0
- package/dist/knowledge-api.js +169 -0
- package/dist/knowledge-api.js.map +1 -0
- package/dist/open-url.d.ts +16 -0
- package/dist/open-url.js +73 -0
- package/dist/open-url.js.map +1 -0
- package/dist/parse-argv.d.ts +2 -0
- package/dist/parse-argv.js.map +1 -1
- package/dist/publish-api.d.ts +2 -1
- package/dist/publish-api.js +4 -3
- package/dist/publish-api.js.map +1 -1
- package/dist/pull-write.d.ts +27 -0
- package/dist/pull-write.js +60 -0
- package/dist/pull-write.js.map +1 -0
- package/dist/routes-api.d.ts +3 -0
- package/dist/routes-api.js +9 -4
- package/dist/routes-api.js.map +1 -1
- package/dist/runs-api.d.ts +7 -1
- package/dist/runs-api.js +21 -6
- package/dist/runs-api.js.map +1 -1
- package/dist/safe-fs.d.ts +2 -0
- package/dist/safe-fs.js +12 -6
- package/dist/safe-fs.js.map +1 -1
- package/dist/secret-file.d.ts +17 -0
- package/dist/secret-file.js +69 -0
- package/dist/secret-file.js.map +1 -0
- package/dist/skills-api.d.ts +10 -2
- package/dist/skills-api.js +37 -2
- package/dist/skills-api.js.map +1 -1
- package/dist/source-products-api.d.ts +63 -0
- package/dist/source-products-api.js +123 -0
- package/dist/source-products-api.js.map +1 -0
- package/dist/source-target.js +2 -1
- package/dist/source-target.js.map +1 -1
- package/dist/sources-api.d.ts +2 -1
- package/dist/sources-api.js +3 -2
- package/dist/sources-api.js.map +1 -1
- package/dist/team-connect-api.d.ts +286 -0
- package/dist/team-connect-api.js +107 -0
- package/dist/team-connect-api.js.map +1 -0
- package/dist/teams-api.d.ts +42 -1
- package/dist/teams-api.js +98 -20
- package/dist/teams-api.js.map +1 -1
- package/dist/toolchain-api.d.ts +9 -0
- package/dist/toolchain-api.js +17 -2
- package/dist/toolchain-api.js.map +1 -1
- package/dist/toolchain-resolve.d.ts +3 -0
- package/dist/toolchain-resolve.js +15 -1
- package/dist/toolchain-resolve.js.map +1 -1
- package/dist/toolchain.d.ts +6 -0
- package/dist/toolchain.js +19 -0
- package/dist/toolchain.js.map +1 -1
- package/dist/update-notice.js +2 -1
- package/dist/update-notice.js.map +1 -1
- package/package.json +2 -2
- package/templates/AGENTS.md.tmpl +6 -1
- package/templates/claude-skill.md.tmpl +5 -0
- package/templates/package.json.tmpl +4 -4
- package/templates/skills/build-agent/SKILL.md +115 -16
- package/templates/skills/build-agent/references/agent-json.md +26 -8
- package/templates/skills/build-agent/references/app-setup.md +2 -1
- package/templates/skills/build-agent/references/rollout.md +27 -4
- package/templates/skills/build-agent/references/set-up-an-integration.md +74 -15
- package/templates/skills/build-agent/references/skills.md +20 -1
- package/templates/skills/build-agent/references/triggers.md +84 -20
- package/templates/skills/build-integration/references/widget-design.md +3 -3
- package/templates/skills/ekanos-platform/SKILL.md +70 -17
- package/templates/skills/polish-widgets/references/patterns.md +2 -2
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
/** `pull`: overwrite local files that differ from the host's. */
|
|
16
|
+
force?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* `push --prune`: also delete the articles an earlier push of this folder
|
|
19
|
+
* made whose files are gone. Never an article written in the app, and
|
|
20
|
+
* never one edited there since the push.
|
|
21
|
+
*/
|
|
22
|
+
prune?: boolean;
|
|
23
|
+
/** `push --prune`: delete without asking (a script or agent run). */
|
|
24
|
+
yes?: boolean;
|
|
25
|
+
dir: string;
|
|
26
|
+
env: Record<string, string | undefined>;
|
|
27
|
+
stdinIsTTY?: boolean;
|
|
28
|
+
confirm?: (question: string) => Promise<boolean>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* `knowledge` — a Source's Knowledge base (plan 244): the General collection
|
|
32
|
+
* every team reads, and with `--team` one team's own collection. `push`
|
|
33
|
+
* turns a folder of markdown files into articles — one per `.md`, keyed by
|
|
34
|
+
* its path, so pushing the same folder again only changes what changed —
|
|
35
|
+
* and `pull` writes them back the same way. `search` runs as a team's
|
|
36
|
+
* agents do: that team's articles plus General.
|
|
37
|
+
*/
|
|
38
|
+
export declare function runKnowledge(ctx: CliContext, args: KnowledgeArgs): Promise<ExitCode>;
|
|
39
|
+
/**
|
|
40
|
+
* Which folder a push came from, as the host records it on each article
|
|
41
|
+
* (`synced_from`): the path as named from the project directory, so pushing
|
|
42
|
+
* the same folder from the same place is the same origin. `--prune` only
|
|
43
|
+
* ever removes articles of that origin.
|
|
44
|
+
*/
|
|
45
|
+
export declare function knowledgeOrigin(target: string, cwd: string): string;
|
|
46
|
+
/** Mirrors the host's `knowledgeSlug`: a file's path without `.md`. */
|
|
47
|
+
export declare function knowledgeSlugFromPath(relative: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Every markdown file under `target`, as articles. The title is the
|
|
50
|
+
* frontmatter `title:`, else the first `# heading` (taken out of the body so
|
|
51
|
+
* it is not shown twice), else the file name; tags are the frontmatter
|
|
52
|
+
* `tags:` (`[a, b]`, `a, b` or a `- a` list).
|
|
53
|
+
*/
|
|
54
|
+
export declare function readKnowledgeFolder(target: string, cwd: string): (KnowledgePushArticle & {
|
|
55
|
+
file: string;
|
|
56
|
+
})[];
|
|
@@ -0,0 +1,456 @@
|
|
|
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 { loginHint } from '../auth/host-trust.js';
|
|
15
|
+
import { refreshStoredSession, requireSession, resolveAuthEnvironment, } from '../auth/session.js';
|
|
16
|
+
import { authRequiredError, invalidStateError, notFoundError, usageError, validationError, } from '../errors.js';
|
|
17
|
+
import { KNOWLEDGE_PUSH_BATCH, KnowledgeListSchema, KnowledgePruneSchema, KnowledgePushSchema, KnowledgeSearchSchema, isSafeKnowledgeSlug, knowledgeCall, } from '../knowledge-api.js';
|
|
18
|
+
import { writePulledFiles } from '../pull-write.js';
|
|
19
|
+
import { classifyPath } from '../safe-fs.js';
|
|
20
|
+
import { resolveSourceTarget } from '../source-target.js';
|
|
21
|
+
import { pluralize } from '../ui.js';
|
|
22
|
+
import { promptYesNo } from './publish.js';
|
|
23
|
+
export const KNOWLEDGE_SUBCOMMANDS = [
|
|
24
|
+
'list',
|
|
25
|
+
'push',
|
|
26
|
+
'search',
|
|
27
|
+
'pull',
|
|
28
|
+
];
|
|
29
|
+
const USAGE = 'Run one of: ekanos knowledge list [--team <slug>] | push <dir> [--team <slug>] [--prune [--yes]] | search <query> --team <slug> | pull [--team <slug>] [--out <dir>] [--force].';
|
|
30
|
+
/**
|
|
31
|
+
* `knowledge` — a Source's Knowledge base (plan 244): the General collection
|
|
32
|
+
* every team reads, and with `--team` one team's own collection. `push`
|
|
33
|
+
* turns a folder of markdown files into articles — one per `.md`, keyed by
|
|
34
|
+
* its path, so pushing the same folder again only changes what changed —
|
|
35
|
+
* and `pull` writes them back the same way. `search` runs as a team's
|
|
36
|
+
* agents do: that team's articles plus General.
|
|
37
|
+
*/
|
|
38
|
+
export async function runKnowledge(ctx, args) {
|
|
39
|
+
var _a;
|
|
40
|
+
var _b, _c;
|
|
41
|
+
const subcommand = parseSubcommand(args.subcommand);
|
|
42
|
+
const team = ((_a = args.team) === null || _a === void 0 ? void 0 : _a.trim()) || undefined;
|
|
43
|
+
// Read the folder before any network call, so a bad one fails fast.
|
|
44
|
+
const files = subcommand === 'push'
|
|
45
|
+
? readKnowledgeFolder(requireTarget(args.target, 'push'), args.dir)
|
|
46
|
+
: null;
|
|
47
|
+
if (args.prune && subcommand !== 'push') {
|
|
48
|
+
throw usageError('--prune goes with knowledge push.', 'Run "ekanos knowledge push <dir> --prune".');
|
|
49
|
+
}
|
|
50
|
+
const origin = subcommand === 'push' ? knowledgeOrigin(args.target, args.dir) : null;
|
|
51
|
+
if (args.prune &&
|
|
52
|
+
classifyPath(path.resolve(args.dir, args.target)) !== 'directory') {
|
|
53
|
+
throw usageError('--prune needs a folder: it removes the articles whose files are gone from it.', 'Pass the folder you push, e.g. "ekanos knowledge push ./knowledge --prune".');
|
|
54
|
+
}
|
|
55
|
+
if (subcommand === 'search') {
|
|
56
|
+
requireTarget(args.target, 'search');
|
|
57
|
+
if (!team) {
|
|
58
|
+
throw usageError('knowledge search needs --team <slug>.', 'Search runs as that team’s agents see it: its articles plus the General collection.');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const env = resolveAuthEnvironment(ctx, args.host, args.env, {
|
|
62
|
+
projectDir: args.dir,
|
|
63
|
+
});
|
|
64
|
+
const resolved = await resolveSourceTarget(env, requireSession(env), args);
|
|
65
|
+
const client = new KnowledgeClient(env, resolved.session);
|
|
66
|
+
const source = resolved.source;
|
|
67
|
+
const send = (schema, init) => client.call((token) => knowledgeCall(env.host, token, schema, init));
|
|
68
|
+
const where = (slug) => (team ? `team ${team}` : `${slug} General`);
|
|
69
|
+
switch (subcommand) {
|
|
70
|
+
case 'list': {
|
|
71
|
+
const list = await send(KnowledgeListSchema, {
|
|
72
|
+
method: 'GET',
|
|
73
|
+
query: { source, team },
|
|
74
|
+
});
|
|
75
|
+
if (list.articles.length === 0) {
|
|
76
|
+
return ctx.succeed(Object.assign({ host: env.host }, list), ctx.ui.result({
|
|
77
|
+
title: `No Knowledge articles in ${where(list.source.slug)} yet`,
|
|
78
|
+
tone: 'warn',
|
|
79
|
+
notes: [
|
|
80
|
+
`"ekanos knowledge push <dir>${team ? ` --team ${team}` : ''}" stores a folder of .md files.`,
|
|
81
|
+
],
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
return ctx.succeed(Object.assign({ host: env.host }, list), [
|
|
85
|
+
ctx.ui.bold(`${pluralize(list.articles.length, 'article')} in ${where(list.source.slug)}`),
|
|
86
|
+
ctx.ui.table(['Slug', 'Title', 'Tags', 'Version', 'Updated'], list.articles.map((article) => [
|
|
87
|
+
article.slug,
|
|
88
|
+
article.title,
|
|
89
|
+
article.tags.join(', '),
|
|
90
|
+
`v${article.version}`,
|
|
91
|
+
article.updatedAt.slice(0, 10),
|
|
92
|
+
])),
|
|
93
|
+
].join('\n'));
|
|
94
|
+
}
|
|
95
|
+
case 'search': {
|
|
96
|
+
const query = args.target;
|
|
97
|
+
const found = await send(KnowledgeSearchSchema, {
|
|
98
|
+
method: 'GET',
|
|
99
|
+
path: '/search',
|
|
100
|
+
query: { source, team, q: query },
|
|
101
|
+
});
|
|
102
|
+
if (found.results.length === 0) {
|
|
103
|
+
return ctx.succeed(Object.assign({ host: env.host }, found), ctx.ui.result({
|
|
104
|
+
title: `Nothing in team ${team}'s Knowledge matches "${query}"`,
|
|
105
|
+
tone: 'warn',
|
|
106
|
+
notes: ['Try fewer or different words.'],
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
return ctx.succeed(Object.assign({ host: env.host }, found), ctx.ui.result({
|
|
110
|
+
title: `${found.results.length === 1 ? '1 match' : `${found.results.length} matches`} for "${query}" in team ${team}`,
|
|
111
|
+
rows: found.results.map((hit) => [
|
|
112
|
+
`${hit.title} (${hit.collection === 'general' ? 'General' : 'team'})`,
|
|
113
|
+
[hit.snippet.replace(/\s+/g, ' '), ctx.ui.dim(hit.slug)],
|
|
114
|
+
]),
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
case 'push': {
|
|
118
|
+
const articles = files;
|
|
119
|
+
const keep = articles.map((article) => article.slug);
|
|
120
|
+
const prune = (deleteSlugs) => send(KnowledgePruneSchema, {
|
|
121
|
+
method: 'POST',
|
|
122
|
+
path: '/prune',
|
|
123
|
+
query: {},
|
|
124
|
+
body: Object.assign(Object.assign(Object.assign({ source }, (team ? { team } : {})), { origin: origin, keep }), (deleteSlugs ? { delete: deleteSlugs } : {})),
|
|
125
|
+
});
|
|
126
|
+
// Ask (or refuse) before anything is written: what would go is known
|
|
127
|
+
// from the folder alone.
|
|
128
|
+
const preview = args.prune ? await prune() : null;
|
|
129
|
+
if (preview && preview.candidates.length > 0) {
|
|
130
|
+
await confirmPrune(ctx, args, {
|
|
131
|
+
candidates: preview.candidates,
|
|
132
|
+
where: where(preview.source.slug),
|
|
133
|
+
command: `ekanos knowledge push ${args.target}${team ? ` --team ${team}` : ''} --source ${source} --prune --yes`,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const results = [];
|
|
137
|
+
let pushed = null;
|
|
138
|
+
for (let start = 0; start < articles.length; start += KNOWLEDGE_PUSH_BATCH) {
|
|
139
|
+
const batch = articles.slice(start, start + KNOWLEDGE_PUSH_BATCH);
|
|
140
|
+
const response = await send(KnowledgePushSchema, {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
query: {},
|
|
143
|
+
body: Object.assign(Object.assign({ source }, (team ? { team } : {})), { origin: origin, articles: batch.map((_a) => {
|
|
144
|
+
var { file: _file } = _a, article = __rest(_a, ["file"]);
|
|
145
|
+
return article;
|
|
146
|
+
}) }),
|
|
147
|
+
});
|
|
148
|
+
pushed = response;
|
|
149
|
+
results.push(...response.results);
|
|
150
|
+
}
|
|
151
|
+
const pruned = preview && preview.candidates.length > 0
|
|
152
|
+
? await prune(preview.candidates.map((article) => article.slug))
|
|
153
|
+
: preview;
|
|
154
|
+
const count = (status) => results.filter((result) => result.status === status).length;
|
|
155
|
+
const refused = results.filter((result) => result.status === 'refused');
|
|
156
|
+
const pruneRows = pruned
|
|
157
|
+
? [
|
|
158
|
+
[
|
|
159
|
+
'Pruned',
|
|
160
|
+
pruned.deleted.length > 0
|
|
161
|
+
? pruned.deleted.map((article) => article.slug)
|
|
162
|
+
: '0',
|
|
163
|
+
],
|
|
164
|
+
...(pruned.edited.length > 0
|
|
165
|
+
? [
|
|
166
|
+
[
|
|
167
|
+
'Kept',
|
|
168
|
+
pruned.edited.map((article) => `${article.slug}: its file is gone, but it was edited in the app since it was pushed`),
|
|
169
|
+
],
|
|
170
|
+
]
|
|
171
|
+
: []),
|
|
172
|
+
]
|
|
173
|
+
: [];
|
|
174
|
+
return ctx.succeed(Object.assign({ host: env.host, team: team !== null && team !== void 0 ? team : null, origin,
|
|
175
|
+
results }, (pruned
|
|
176
|
+
? { pruned: pruned.deleted, keptEdited: pruned.edited }
|
|
177
|
+
: {})), ctx.ui.result({
|
|
178
|
+
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)}`,
|
|
179
|
+
tone: refused.length > 0 ? 'warn' : 'ok',
|
|
180
|
+
rows: [
|
|
181
|
+
['Created', String(count('created'))],
|
|
182
|
+
['Updated', String(count('updated'))],
|
|
183
|
+
['Unchanged', String(count('unchanged'))],
|
|
184
|
+
...(refused.length > 0
|
|
185
|
+
? [
|
|
186
|
+
[
|
|
187
|
+
'Refused',
|
|
188
|
+
refused.map((result) => { var _a; return `${result.slug}: ${(_a = result.message) !== null && _a !== void 0 ? _a : ''}`; }),
|
|
189
|
+
],
|
|
190
|
+
]
|
|
191
|
+
: []),
|
|
192
|
+
...pruneRows,
|
|
193
|
+
],
|
|
194
|
+
notes: [
|
|
195
|
+
team
|
|
196
|
+
? `Team ${team}'s agents with search_knowledge find these now.`
|
|
197
|
+
: "Every team's agents granted search_knowledge find these now, beside their team's own articles.",
|
|
198
|
+
],
|
|
199
|
+
}));
|
|
200
|
+
}
|
|
201
|
+
case 'pull': {
|
|
202
|
+
const list = await send(KnowledgeListSchema, {
|
|
203
|
+
method: 'GET',
|
|
204
|
+
query: { source, team, body: '1' },
|
|
205
|
+
});
|
|
206
|
+
const outDir = path.resolve(args.dir, (_c = args.out) !== null && _c !== void 0 ? _c : path.join('knowledge', team !== null && team !== void 0 ? team : 'general'));
|
|
207
|
+
const written = writePulledFiles({
|
|
208
|
+
projectDir: args.dir,
|
|
209
|
+
outDir,
|
|
210
|
+
files: knowledgeFiles(list.articles),
|
|
211
|
+
force: args.force === true,
|
|
212
|
+
rerun: `ekanos knowledge pull${team ? ` --team ${team}` : ''}`,
|
|
213
|
+
});
|
|
214
|
+
return ctx.succeed({ host: env.host, team: team !== null && team !== void 0 ? team : null, dir: outDir, files: written }, ctx.ui.result({
|
|
215
|
+
title: `Pulled ${pluralize(written.length, 'article')} from ${where(list.source.slug)}`,
|
|
216
|
+
rows: [['Into', outDir]],
|
|
217
|
+
notes: [
|
|
218
|
+
`Edit, then "ekanos knowledge push ${path.relative(args.dir, outDir) || '.'}${team ? ` --team ${team}` : ''}".`,
|
|
219
|
+
],
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Which folder a push came from, as the host records it on each article
|
|
226
|
+
* (`synced_from`): the path as named from the project directory, so pushing
|
|
227
|
+
* the same folder from the same place is the same origin. `--prune` only
|
|
228
|
+
* ever removes articles of that origin.
|
|
229
|
+
*/
|
|
230
|
+
export function knowledgeOrigin(target, cwd) {
|
|
231
|
+
const relative = path
|
|
232
|
+
.relative(cwd, path.resolve(cwd, target))
|
|
233
|
+
.split(path.sep)
|
|
234
|
+
.join('/');
|
|
235
|
+
return (relative || '.').slice(-300);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Pruning deletes articles. A person is asked, listing them; a script or
|
|
239
|
+
* agent run needs `--yes`, and is refused before anything is written.
|
|
240
|
+
*/
|
|
241
|
+
async function confirmPrune(ctx, args, target) {
|
|
242
|
+
var _a, _b;
|
|
243
|
+
if (args.yes)
|
|
244
|
+
return;
|
|
245
|
+
const list = target.candidates.map((article) => article.slug);
|
|
246
|
+
const what = `--prune would delete ${pluralize(list.length, 'article')} from ${target.where} whose files are gone: ${list.join(', ')}`;
|
|
247
|
+
const canPrompt = !ctx.jsonMode && ((_a = args.stdinIsTTY) !== null && _a !== void 0 ? _a : process.stdin.isTTY === true);
|
|
248
|
+
if (!canPrompt) {
|
|
249
|
+
throw invalidStateError(`${what}. Nothing was pushed or deleted.`, `Re-run with --yes: ${target.command}`);
|
|
250
|
+
}
|
|
251
|
+
const sure = await ((_b = args.confirm) !== null && _b !== void 0 ? _b : promptYesNo)(`${what}. Delete them? [y/N]`);
|
|
252
|
+
if (!sure) {
|
|
253
|
+
throw validationError('Nothing was pushed or deleted.', 'Run it again without --prune to push only.');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/** Every call refreshes the session once on a 401, then gives up. */
|
|
257
|
+
class KnowledgeClient {
|
|
258
|
+
constructor(env, session) {
|
|
259
|
+
this.env = env;
|
|
260
|
+
this.session = session;
|
|
261
|
+
}
|
|
262
|
+
async call(send) {
|
|
263
|
+
let result = await send(this.session.accessToken);
|
|
264
|
+
if (result.status === 'unauthorized') {
|
|
265
|
+
this.session = await refreshStoredSession(this.env, this.session);
|
|
266
|
+
result = await send(this.session.accessToken);
|
|
267
|
+
}
|
|
268
|
+
if (result.status === 'ok')
|
|
269
|
+
return result.data;
|
|
270
|
+
throw authRequiredError(`The session for ${this.env.host} could not authorize the knowledge call.`, loginHint(this.env));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
/** Mirrors the host's `knowledgeSlug`: a file's path without `.md`. */
|
|
274
|
+
export function knowledgeSlugFromPath(relative) {
|
|
275
|
+
return relative
|
|
276
|
+
.trim()
|
|
277
|
+
.toLowerCase()
|
|
278
|
+
.replace(/\\/g, '/')
|
|
279
|
+
.replace(/\.(md|markdown|mdx)$/, '')
|
|
280
|
+
.normalize('NFKD')
|
|
281
|
+
.replace(/[̀-ͯ]/g, '')
|
|
282
|
+
.replace(/[^a-z0-9._/-]+/g, '-')
|
|
283
|
+
.replace(/\/+/g, '/')
|
|
284
|
+
.replace(/-{2,}/g, '-')
|
|
285
|
+
.split('/')
|
|
286
|
+
.map((part) => part.replace(/^[-.]+|[-.]+$/g, ''))
|
|
287
|
+
.filter(Boolean)
|
|
288
|
+
.join('/')
|
|
289
|
+
.slice(0, 200);
|
|
290
|
+
}
|
|
291
|
+
const IGNORED = new Set(['node_modules', '.git']);
|
|
292
|
+
const MARKDOWN = /\.(md|markdown|mdx)$/i;
|
|
293
|
+
const BODY_MAX = 200000;
|
|
294
|
+
/**
|
|
295
|
+
* Every markdown file under `target`, as articles. The title is the
|
|
296
|
+
* frontmatter `title:`, else the first `# heading` (taken out of the body so
|
|
297
|
+
* it is not shown twice), else the file name; tags are the frontmatter
|
|
298
|
+
* `tags:` (`[a, b]`, `a, b` or a `- a` list).
|
|
299
|
+
*/
|
|
300
|
+
export function readKnowledgeFolder(target, cwd) {
|
|
301
|
+
const root = path.resolve(cwd, target);
|
|
302
|
+
const kind = classifyPath(root);
|
|
303
|
+
if (kind !== 'directory' && kind !== 'file') {
|
|
304
|
+
throw notFoundError(`No folder or file at ${root}.`, 'Pass a folder of .md files, e.g. "ekanos knowledge push ./knowledge".');
|
|
305
|
+
}
|
|
306
|
+
const found = [];
|
|
307
|
+
const walk = (dir) => {
|
|
308
|
+
for (const name of fs.readdirSync(dir).sort()) {
|
|
309
|
+
if (IGNORED.has(name) || name.startsWith('.'))
|
|
310
|
+
continue;
|
|
311
|
+
const full = path.join(dir, name);
|
|
312
|
+
const entry = classifyPath(full);
|
|
313
|
+
if (entry === 'directory')
|
|
314
|
+
walk(full);
|
|
315
|
+
else if (entry === 'file' && MARKDOWN.test(name))
|
|
316
|
+
found.push(full);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
if (kind === 'file') {
|
|
320
|
+
const name = path.basename(root);
|
|
321
|
+
if (name.startsWith('.') || !MARKDOWN.test(name)) {
|
|
322
|
+
throw validationError(`${root} is not a markdown file, so it was not pushed.`, 'Pass a .md, .markdown or .mdx file (not a dotfile), or a folder of them.');
|
|
323
|
+
}
|
|
324
|
+
found.push(root);
|
|
325
|
+
}
|
|
326
|
+
else
|
|
327
|
+
walk(root);
|
|
328
|
+
if (found.length === 0) {
|
|
329
|
+
throw validationError(`No .md files under ${root}.`, 'Each markdown file becomes one article.');
|
|
330
|
+
}
|
|
331
|
+
const base = kind === 'file' ? path.dirname(root) : root;
|
|
332
|
+
const seen = new Map();
|
|
333
|
+
return found.map((file) => {
|
|
334
|
+
const relative = path.relative(base, file).split(path.sep).join('/');
|
|
335
|
+
const markdown = fs.readFileSync(file, 'utf8');
|
|
336
|
+
const { fields, tags, body } = splitFrontmatter(markdown);
|
|
337
|
+
const slug = knowledgeSlugFromPath(fields.slug || relative);
|
|
338
|
+
if (!slug) {
|
|
339
|
+
throw validationError(`${file}: no usable slug.`, 'Rename the file with letters or digits.');
|
|
340
|
+
}
|
|
341
|
+
const clash = seen.get(slug);
|
|
342
|
+
if (clash) {
|
|
343
|
+
throw validationError(`${file} and ${clash} both become "${slug}".`, 'Rename one of them.');
|
|
344
|
+
}
|
|
345
|
+
seen.set(slug, file);
|
|
346
|
+
const heading = fields.title
|
|
347
|
+
? null
|
|
348
|
+
: body.match(/^[ \t]*#[ \t]+(.+?)[ \t]*#*[ \t]*$/m);
|
|
349
|
+
const title = (fields.title ||
|
|
350
|
+
(heading === null || heading === void 0 ? void 0 : heading[1]) ||
|
|
351
|
+
path.basename(file).replace(MARKDOWN, '').replace(/[-_]+/g, ' '))
|
|
352
|
+
.trim()
|
|
353
|
+
.slice(0, 300);
|
|
354
|
+
const text = (heading ? body.replace(heading[0], '') : body).replace(/^\s*\n/, '');
|
|
355
|
+
if (text.length > BODY_MAX) {
|
|
356
|
+
throw validationError(`${file} is longer than ${BODY_MAX.toLocaleString()} characters.`, 'Split it into smaller articles.');
|
|
357
|
+
}
|
|
358
|
+
return Object.assign({ file,
|
|
359
|
+
slug, title: title || slug, body: text }, (tags.length > 0 ? { tags: tags.slice(0, 30) } : {}));
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
/** One `.md` per article: its title and tags as frontmatter, then the body. */
|
|
363
|
+
function knowledgeFiles(articles) {
|
|
364
|
+
const unsafe = articles.filter((article) => !isSafeKnowledgeSlug(article.slug));
|
|
365
|
+
if (unsafe.length > 0) {
|
|
366
|
+
throw validationError(`Refusing to pull: the host sent ${unsafe.length === 1 ? 'a slug' : 'slugs'} that cannot be a file path (${unsafe.map((article) => JSON.stringify(article.slug)).join(', ')}).`, 'Nothing was written. A knowledge slug is lowercase letters, digits, ".", "_", "-" and "/" folders, with no "." or ".." folder.');
|
|
367
|
+
}
|
|
368
|
+
return articles.map((article) => {
|
|
369
|
+
var _a;
|
|
370
|
+
const frontmatter = [
|
|
371
|
+
'---',
|
|
372
|
+
`title: ${JSON.stringify(article.title)}`,
|
|
373
|
+
...(article.tags.length > 0
|
|
374
|
+
? [
|
|
375
|
+
`tags: [${article.tags.map((tag) => JSON.stringify(tag)).join(', ')}]`,
|
|
376
|
+
]
|
|
377
|
+
: []),
|
|
378
|
+
'---',
|
|
379
|
+
'',
|
|
380
|
+
].join('\n');
|
|
381
|
+
return {
|
|
382
|
+
path: `${article.slug}.md`,
|
|
383
|
+
contents: frontmatter + ((_a = article.body) !== null && _a !== void 0 ? _a : ''),
|
|
384
|
+
};
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
const FRONTMATTER = /^?---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
|
|
388
|
+
/** Top-level `title`, `slug` and `tags` only. */
|
|
389
|
+
function splitFrontmatter(markdown) {
|
|
390
|
+
const match = markdown.match(FRONTMATTER);
|
|
391
|
+
if (!match)
|
|
392
|
+
return { fields: {}, tags: [], body: markdown };
|
|
393
|
+
const fields = {};
|
|
394
|
+
const tags = [];
|
|
395
|
+
const lines = match[1].split(/\r?\n/);
|
|
396
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
397
|
+
const pair = lines[index].match(/^([A-Za-z0-9_-]+):[ \t]*(.*)$/);
|
|
398
|
+
if (!pair)
|
|
399
|
+
continue;
|
|
400
|
+
const key = pair[1].toLowerCase();
|
|
401
|
+
const value = pair[2].trim();
|
|
402
|
+
if (key === 'tags') {
|
|
403
|
+
if (value) {
|
|
404
|
+
tags.push(...value.replace(/^\[|\]$/g, '').split(','));
|
|
405
|
+
}
|
|
406
|
+
while (index + 1 < lines.length &&
|
|
407
|
+
/^[ \t]*-[ \t]+/.test(lines[index + 1])) {
|
|
408
|
+
tags.push(lines[index + 1].replace(/^[ \t]*-[ \t]+/, ''));
|
|
409
|
+
index += 1;
|
|
410
|
+
}
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
fields[key] = unquote(value);
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
fields,
|
|
417
|
+
tags: [...new Set(tags.map((tag) => unquote(tag.trim())).filter(Boolean))],
|
|
418
|
+
body: markdown.slice(match[0].length),
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
function unquote(value) {
|
|
422
|
+
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
423
|
+
try {
|
|
424
|
+
const parsed = JSON.parse(value);
|
|
425
|
+
if (typeof parsed === 'string')
|
|
426
|
+
return parsed;
|
|
427
|
+
}
|
|
428
|
+
catch (_a) {
|
|
429
|
+
// Not JSON-escaped: strip the quotes below.
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (value.length >= 2 &&
|
|
433
|
+
((value.startsWith('"') && value.endsWith('"')) ||
|
|
434
|
+
(value.startsWith("'") && value.endsWith("'")))) {
|
|
435
|
+
return value.slice(1, -1);
|
|
436
|
+
}
|
|
437
|
+
return value;
|
|
438
|
+
}
|
|
439
|
+
function parseSubcommand(value) {
|
|
440
|
+
const match = KNOWLEDGE_SUBCOMMANDS.find((name) => name === value);
|
|
441
|
+
if (match)
|
|
442
|
+
return match;
|
|
443
|
+
throw usageError(value
|
|
444
|
+
? `Unknown knowledge subcommand "${value}".`
|
|
445
|
+
: 'knowledge needs a subcommand.', USAGE);
|
|
446
|
+
}
|
|
447
|
+
function requireTarget(value, verb) {
|
|
448
|
+
if (value === null || value === void 0 ? void 0 : value.trim())
|
|
449
|
+
return value;
|
|
450
|
+
throw usageError(verb === 'push'
|
|
451
|
+
? 'knowledge push needs a folder of .md files.'
|
|
452
|
+
: 'knowledge search needs a query.', verb === 'push'
|
|
453
|
+
? 'e.g. "ekanos knowledge push ./knowledge --team first-bank".'
|
|
454
|
+
: 'e.g. "ekanos knowledge search \\"wire cutoff\\" --team first-bank".');
|
|
455
|
+
}
|
|
456
|
+
//# 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 { loginHint } from '../auth/host-trust';\nimport {\n type AuthEnvironment,\n refreshStoredSession,\n requireSession,\n resolveAuthEnvironment,\n} from '../auth/session';\nimport type { CliContext } from '../context';\nimport {\n authRequiredError,\n invalidStateError,\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 KnowledgePruneArticle,\n KnowledgePruneSchema,\n type KnowledgePushArticle,\n type KnowledgePushResult,\n KnowledgePushSchema,\n KnowledgeSearchSchema,\n isSafeKnowledgeSlug,\n knowledgeCall,\n} from '../knowledge-api';\nimport { type PulledFile, writePulledFiles } from '../pull-write';\nimport { classifyPath } from '../safe-fs';\nimport { resolveSourceTarget } from '../source-target';\nimport { type ResultRow, pluralize } from '../ui';\nimport { promptYesNo } from './publish';\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 /** `pull`: overwrite local files that differ from the host's. */\n force?: boolean;\n /**\n * `push --prune`: also delete the articles an earlier push of this folder\n * made whose files are gone. Never an article written in the app, and\n * never one edited there since the push.\n */\n prune?: boolean;\n /** `push --prune`: delete without asking (a script or agent run). */\n yes?: boolean;\n dir: string;\n env: Record<string, string | undefined>;\n stdinIsTTY?: boolean;\n confirm?: (question: string) => Promise<boolean>;\n}\n\nconst USAGE =\n 'Run one of: ekanos knowledge list [--team <slug>] | push <dir> [--team <slug>] [--prune [--yes]] | search <query> --team <slug> | pull [--team <slug>] [--out <dir>] [--force].';\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 (args.prune && subcommand !== 'push') {\n throw usageError(\n '--prune goes with knowledge push.',\n 'Run \"ekanos knowledge push <dir> --prune\".',\n );\n }\n const origin =\n subcommand === 'push' ? knowledgeOrigin(args.target!, args.dir) : null;\n if (\n args.prune &&\n classifyPath(path.resolve(args.dir, args.target!)) !== 'directory'\n ) {\n throw usageError(\n '--prune needs a folder: it removes the articles whose files are gone from it.',\n 'Pass the folder you push, e.g. \"ekanos knowledge push ./knowledge --prune\".',\n );\n }\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(env, requireSession(env), args);\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 keep = articles.map((article) => article.slug);\n const prune = (deleteSlugs?: string[]) =>\n send(KnowledgePruneSchema, {\n method: 'POST',\n path: '/prune',\n query: {},\n body: {\n source,\n ...(team ? { team } : {}),\n origin: origin!,\n keep,\n ...(deleteSlugs ? { delete: deleteSlugs } : {}),\n },\n });\n // Ask (or refuse) before anything is written: what would go is known\n // from the folder alone.\n const preview = args.prune ? await prune() : null;\n if (preview && preview.candidates.length > 0) {\n await confirmPrune(ctx, args, {\n candidates: preview.candidates,\n where: where(preview.source.slug),\n command: `ekanos knowledge push ${args.target}${team ? ` --team ${team}` : ''} --source ${source} --prune --yes`,\n });\n }\n\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 origin: origin!,\n articles: batch.map(({ file: _file, ...article }) => article),\n },\n });\n pushed = response;\n results.push(...response.results);\n }\n const pruned =\n preview && preview.candidates.length > 0\n ? await prune(preview.candidates.map((article) => article.slug))\n : preview;\n const count = (status: KnowledgePushResult['status']) =>\n results.filter((result) => result.status === status).length;\n const refused = results.filter((result) => result.status === 'refused');\n const pruneRows: ResultRow[] = pruned\n ? [\n [\n 'Pruned',\n pruned.deleted.length > 0\n ? pruned.deleted.map((article) => article.slug)\n : '0',\n ],\n ...(pruned.edited.length > 0\n ? ([\n [\n 'Kept',\n pruned.edited.map(\n (article) =>\n `${article.slug}: its file is gone, but it was edited in the app since it was pushed`,\n ),\n ],\n ] as const)\n : []),\n ]\n : [];\n return ctx.succeed(\n {\n host: env.host,\n team: team ?? null,\n origin,\n results,\n ...(pruned\n ? { pruned: pruned.deleted, keptEdited: pruned.edited }\n : {}),\n },\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 ...pruneRows,\n ],\n notes: [\n team\n ? `Team ${team}'s agents with search_knowledge find these now.`\n : \"Every team's agents granted search_knowledge find these now, beside their team's own articles.\",\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 = writePulledFiles({\n projectDir: args.dir,\n outDir,\n files: knowledgeFiles(list.articles),\n force: args.force === true,\n rerun: `ekanos knowledge pull${team ? ` --team ${team}` : ''}`,\n });\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/**\n * Which folder a push came from, as the host records it on each article\n * (`synced_from`): the path as named from the project directory, so pushing\n * the same folder from the same place is the same origin. `--prune` only\n * ever removes articles of that origin.\n */\nexport function knowledgeOrigin(target: string, cwd: string): string {\n const relative = path\n .relative(cwd, path.resolve(cwd, target))\n .split(path.sep)\n .join('/');\n return (relative || '.').slice(-300);\n}\n\n/**\n * Pruning deletes articles. A person is asked, listing them; a script or\n * agent run needs `--yes`, and is refused before anything is written.\n */\nasync function confirmPrune(\n ctx: CliContext,\n args: KnowledgeArgs,\n target: {\n candidates: KnowledgePruneArticle[];\n where: string;\n command: string;\n },\n) {\n if (args.yes) return;\n const list = target.candidates.map((article) => article.slug);\n const what = `--prune would delete ${pluralize(list.length, 'article')} from ${target.where} whose files are gone: ${list.join(', ')}`;\n const canPrompt =\n !ctx.jsonMode && (args.stdinIsTTY ?? process.stdin.isTTY === true);\n if (!canPrompt) {\n throw invalidStateError(\n `${what}. Nothing was pushed or deleted.`,\n `Re-run with --yes: ${target.command}`,\n );\n }\n const sure = await (args.confirm ?? promptYesNo)(\n `${what}. Delete them? [y/N]`,\n );\n if (!sure) {\n throw validationError(\n 'Nothing was pushed or deleted.',\n 'Run it again without --prune to push only.',\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 loginHint(this.env),\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') {\n const name = path.basename(root);\n if (name.startsWith('.') || !MARKDOWN.test(name)) {\n throw validationError(\n `${root} is not a markdown file, so it was not pushed.`,\n 'Pass a .md, .markdown or .mdx file (not a dotfile), or a folder of them.',\n );\n }\n 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\n/** One `.md` per article: its title and tags as frontmatter, then the body. */\nfunction knowledgeFiles(articles: KnowledgeArticle[]): PulledFile[] {\n const unsafe = articles.filter(\n (article) => !isSafeKnowledgeSlug(article.slug),\n );\n if (unsafe.length > 0) {\n throw validationError(\n `Refusing to pull: the host sent ${unsafe.length === 1 ? 'a slug' : 'slugs'} that cannot be a file path (${unsafe.map((article) => JSON.stringify(article.slug)).join(', ')}).`,\n 'Nothing was written. A knowledge slug is lowercase letters, digits, \".\", \"_\", \"-\" and \"/\" folders, with no \".\" or \"..\" folder.',\n );\n }\n return articles.map((article) => {\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 return {\n path: `${article.slug}.md`,\n contents: frontmatter + (article.body ?? ''),\n };\n });\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"]}
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -17,6 +17,10 @@ export interface LoginArgs {
|
|
|
17
17
|
claude?: boolean;
|
|
18
18
|
/** Test seam for the Claude Code offer's question. */
|
|
19
19
|
confirm?: (question: string) => Promise<boolean>;
|
|
20
|
+
/** `--trust-host`: sign in to a host you have never used without the question. */
|
|
21
|
+
trustHost?: boolean;
|
|
22
|
+
/** Test seam for the new-host question. */
|
|
23
|
+
confirmHost?: (question: string) => Promise<boolean>;
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
22
26
|
* `login` — authenticate this machine against an Ekanos deployment via the
|