@m6d/cortex-cli 0.1.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 +175 -0
- package/bin/cortex.js +52 -0
- package/package.json +41 -0
- package/src/cc/client.ts +295 -0
- package/src/cc/flags.ts +135 -0
- package/src/cc/output.ts +77 -0
- package/src/cc/profiles.ts +105 -0
- package/src/cli.ts +56 -0
- package/src/commands/cc.ts +504 -0
- package/src/commands/graph.ts +229 -0
- package/src/commands/new.ts +333 -0
- package/src/commands/swagger.ts +261 -0
- package/src/config/load.ts +122 -0
- package/src/config/validate.ts +46 -0
- package/src/contracts/charts/chart-spec.ts +201 -0
- package/src/contracts/charts/index.ts +7 -0
- package/src/contracts/client-tools/index.ts +82 -0
- package/src/contracts/egress/index.ts +161 -0
- package/src/contracts/egress/scrub.ts +59 -0
- package/src/contracts/graph/clients/embed.ts +45 -0
- package/src/contracts/graph/clients/neo4j.ts +85 -0
- package/src/contracts/graph/helpers.ts +57 -0
- package/src/contracts/graph/index.ts +30 -0
- package/src/contracts/graph/schema.ts +65 -0
- package/src/contracts/graph/types.ts +125 -0
- package/src/contracts/markdown/index.ts +39 -0
- package/src/contracts/rich-text/index.ts +220 -0
- package/src/contracts/runtime/api.ts +328 -0
- package/src/contracts/runtime/auth.ts +41 -0
- package/src/contracts/runtime/dataset-sql-guide.ts +67 -0
- package/src/contracts/runtime/dataset-sql.ts +2522 -0
- package/src/contracts/runtime/datasets.fixtures.json +2849 -0
- package/src/contracts/runtime/datasets.ts +1391 -0
- package/src/contracts/runtime/grammar.ts +161 -0
- package/src/contracts/runtime/index.ts +14 -0
- package/src/contracts/runtime/signature.ts +237 -0
- package/src/contracts/runtime/sync.ts +172 -0
- package/src/contracts/runtime/triggers.ts +224 -0
- package/src/contracts/wire/index.ts +264 -0
- package/src/graph/expand-domains.ts +288 -0
- package/src/graph/generate-cypher.ts +201 -0
- package/src/graph/seed.ts +207 -0
- package/src/graph/validate.ts +78 -0
- package/src/scaffold/features.ts +190 -0
- package/src/scaffold/files.ts +411 -0
- package/src/scaffold/ports.ts +86 -0
- package/src/scaffold/templates/domains-README.md.txt +16 -0
- package/src/scaffold/templates/eslint.config.mjs.txt +20 -0
- package/src/scaffold/templates/fallowrc.jsonc.txt +42 -0
- package/src/scaffold/templates/gitignore.txt +19 -0
- package/src/scaffold/templates/index.ts.txt +18 -0
- package/src/scaffold/templates/prettier.config.mjs.txt +5 -0
- package/src/scaffold/templates/tsconfig.json.txt +25 -0
- package/src/swagger/extract-endpoints.ts +266 -0
- package/src/swagger/generated-block.ts +74 -0
- package/src/swagger/openapi-schema.ts +161 -0
- package/src/swagger/parse-response.ts +88 -0
- package/src/swagger/types.ts +25 -0
- package/src/ui/prompts.ts +125 -0
- package/src/ui/report.ts +107 -0
- package/tsconfig.json +46 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative types for knowledge graph seed data: endpoints carry
|
|
3
|
+
* params/body/response property arrays, services are concept associations.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type EndpointScalarType =
|
|
7
|
+
| "uuid"
|
|
8
|
+
| "number"
|
|
9
|
+
| "date"
|
|
10
|
+
| "datetime"
|
|
11
|
+
| "string"
|
|
12
|
+
| "boolean"
|
|
13
|
+
| "any"
|
|
14
|
+
| "object";
|
|
15
|
+
|
|
16
|
+
export type EndpointProperty = {
|
|
17
|
+
readonly name: string;
|
|
18
|
+
readonly required: boolean;
|
|
19
|
+
// NonNullable<unknown> keeps the literal union visible to autocomplete.
|
|
20
|
+
readonly type: EndpointScalarType | (string & NonNullable<unknown>);
|
|
21
|
+
readonly isArray?: boolean;
|
|
22
|
+
readonly properties?: readonly EndpointProperty[];
|
|
23
|
+
readonly description?: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export type ResponseKind = "object" | "array" | "paginated" | "file" | "none";
|
|
27
|
+
|
|
28
|
+
export type AutoGenerated = {
|
|
29
|
+
readonly params: readonly EndpointProperty[];
|
|
30
|
+
readonly body: readonly EndpointProperty[];
|
|
31
|
+
readonly response: readonly EndpointProperty[];
|
|
32
|
+
readonly successStatus: number;
|
|
33
|
+
readonly errorStatuses: readonly number[];
|
|
34
|
+
readonly responseKind?: ResponseKind;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type ConceptDef = {
|
|
38
|
+
readonly __brand: "concept";
|
|
39
|
+
name: string;
|
|
40
|
+
description: string;
|
|
41
|
+
aliases?: string[];
|
|
42
|
+
parentConcept?: ConceptDef;
|
|
43
|
+
governedBy?: RuleDef[];
|
|
44
|
+
metadata?: Record<string, unknown>;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type EndpointDef = {
|
|
48
|
+
readonly __brand: "endpoint";
|
|
49
|
+
name: string;
|
|
50
|
+
description: string;
|
|
51
|
+
path: string;
|
|
52
|
+
method: "GET" | "POST" | "PUT" | "DELETE";
|
|
53
|
+
propertiesDescriptions: Record<string, string>;
|
|
54
|
+
|
|
55
|
+
params: EndpointProperty[];
|
|
56
|
+
body: EndpointProperty[];
|
|
57
|
+
response: EndpointProperty[];
|
|
58
|
+
successStatus: number;
|
|
59
|
+
errorStatuses: number[];
|
|
60
|
+
responseKind: ResponseKind;
|
|
61
|
+
|
|
62
|
+
queries?: ConceptDef[];
|
|
63
|
+
mutates?: ConceptDef[];
|
|
64
|
+
|
|
65
|
+
returns?: { concept: ConceptDef; field?: string }[];
|
|
66
|
+
dependsOn?: {
|
|
67
|
+
endpoint: EndpointDef;
|
|
68
|
+
paramName: string;
|
|
69
|
+
fromField: string;
|
|
70
|
+
}[];
|
|
71
|
+
|
|
72
|
+
governedBy?: RuleDef[];
|
|
73
|
+
metadata?: Record<string, unknown>;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type EndpointInput = {
|
|
77
|
+
name: string;
|
|
78
|
+
path: string;
|
|
79
|
+
method: "GET" | "POST" | "PUT" | "DELETE";
|
|
80
|
+
description?: string;
|
|
81
|
+
autoGenerated?: AutoGenerated;
|
|
82
|
+
|
|
83
|
+
propertiesDescriptions?: Record<string, string>;
|
|
84
|
+
|
|
85
|
+
queries?: ConceptDef[];
|
|
86
|
+
mutates?: ConceptDef[];
|
|
87
|
+
|
|
88
|
+
returns?: { concept: ConceptDef; field?: string }[];
|
|
89
|
+
dependsOn?: {
|
|
90
|
+
endpoint: EndpointDef;
|
|
91
|
+
paramName: string;
|
|
92
|
+
fromField: string;
|
|
93
|
+
}[];
|
|
94
|
+
|
|
95
|
+
governedBy?: RuleDef[];
|
|
96
|
+
metadata?: Record<string, unknown>;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
export type ServiceDef = {
|
|
100
|
+
readonly __brand: "service";
|
|
101
|
+
name: string;
|
|
102
|
+
description: string;
|
|
103
|
+
builtInId: string;
|
|
104
|
+
belongsTo: ConceptDef;
|
|
105
|
+
governedBy?: RuleDef[];
|
|
106
|
+
metadata?: Record<string, unknown>;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export type RuleDef = {
|
|
110
|
+
readonly __brand: "rule";
|
|
111
|
+
name: string;
|
|
112
|
+
description: string;
|
|
113
|
+
metadata?: Record<string, unknown>;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export type DomainDef = {
|
|
117
|
+
readonly __brand: "domain";
|
|
118
|
+
name: string;
|
|
119
|
+
description: string;
|
|
120
|
+
concepts?: ConceptDef[];
|
|
121
|
+
endpoints?: EndpointDef[];
|
|
122
|
+
services?: ServiceDef[];
|
|
123
|
+
rules?: RuleDef[];
|
|
124
|
+
metadata?: Record<string, unknown>;
|
|
125
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* CommonMark fence tracking shared by the prompt rich-text pipeline and the
|
|
3
|
+
* widget's streamed-message segmenter.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Line-by-line fence state, following CommonMark's fence rules: a fence line
|
|
8
|
+
* is indented at most three spaces; an opening backtick fence's info string
|
|
9
|
+
* may not contain backticks; and a fence closes only on the same marker
|
|
10
|
+
* character, with at least the opening run's length, followed by nothing but
|
|
11
|
+
* whitespace — so a `~~~` line, a shorter same-marker run, or a ```` ```js ````
|
|
12
|
+
* info line inside a backtick fence all stay code content.
|
|
13
|
+
*/
|
|
14
|
+
export function fenceTracker() {
|
|
15
|
+
let fence: { marker: string; length: number } | null = null;
|
|
16
|
+
return (line: string) => {
|
|
17
|
+
const match = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line);
|
|
18
|
+
const delimiter = match?.[1];
|
|
19
|
+
const rest = match?.[2] ?? "";
|
|
20
|
+
if (delimiter) {
|
|
21
|
+
const marker = delimiter.charAt(0);
|
|
22
|
+
if (fence) {
|
|
23
|
+
if (
|
|
24
|
+
marker === fence.marker &&
|
|
25
|
+
delimiter.length >= fence.length &&
|
|
26
|
+
rest.trim() === ""
|
|
27
|
+
) {
|
|
28
|
+
fence = null;
|
|
29
|
+
return "close";
|
|
30
|
+
}
|
|
31
|
+
return "inside";
|
|
32
|
+
}
|
|
33
|
+
if (marker === "`" && rest.includes("`")) return "outside";
|
|
34
|
+
fence = { marker, length: delimiter.length };
|
|
35
|
+
return "open";
|
|
36
|
+
}
|
|
37
|
+
return fence ? "inside" : "outside";
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The grammar and rendering pipeline for user-authored rich text that reaches
|
|
3
|
+
* the LLM (agent prompts, service descriptions, skill bodies). Both sides of
|
|
4
|
+
* the wire share it: the console edits and validates these strings, the
|
|
5
|
+
* console's runtime API pre-renders them (mentions + heading demotion), and
|
|
6
|
+
* `@m6d/cortex-server` applies per-request variable values. Three token
|
|
7
|
+
* grammars live here:
|
|
8
|
+
*
|
|
9
|
+
* - Tool mentions `@[name](uuid)` and skill mentions `/[name](uuid)` — the
|
|
10
|
+
* uuid is authoritative; the inline name is only a display cache refreshed
|
|
11
|
+
* on save ({@link normalizeMentions}) and resolved live when text is
|
|
12
|
+
* rendered for the LLM ({@link renderMentions}).
|
|
13
|
+
* - Dataset mentions `#[name](name)` — a dataset's name is its identity, so
|
|
14
|
+
* the token carries it twice and renders to it as is.
|
|
15
|
+
* - Variables `{{name}}` — validated against a known list at authoring time
|
|
16
|
+
* ({@link analyzeVariables}) and substituted per request on the server
|
|
17
|
+
* ({@link substituteVariables}).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { fenceTracker } from "../markdown";
|
|
21
|
+
|
|
22
|
+
const UUID = String.raw`[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}`;
|
|
23
|
+
|
|
24
|
+
/** One tool mention token, unanchored and flagless — the editor's tokenizer anchors it, the helpers below add `g`. */
|
|
25
|
+
export const MENTION_PATTERN_SOURCE = String.raw`@\[([^\]\n]*)\]\((${UUID})\)`;
|
|
26
|
+
/** One skill mention token; `/` is the slash-command convention skills already carry elsewhere. */
|
|
27
|
+
export const SKILL_MENTION_PATTERN_SOURCE = String.raw`/\[([^\]\n]*)\]\((${UUID})\)`;
|
|
28
|
+
|
|
29
|
+
/** One dataset mention token; the id is the dataset's name, which is already its identity. */
|
|
30
|
+
export const DATASET_MENTION_PATTERN_SOURCE = String.raw`#\[([^\]\n]*)\]\(([a-zA-Z][a-zA-Z0-9_]{0,63})\)`;
|
|
31
|
+
|
|
32
|
+
const MENTION_PATTERN = new RegExp(MENTION_PATTERN_SOURCE, "g");
|
|
33
|
+
const SKILL_MENTION_PATTERN = new RegExp(SKILL_MENTION_PATTERN_SOURCE, "g");
|
|
34
|
+
const DATASET_MENTION_PATTERN = new RegExp(DATASET_MENTION_PATTERN_SOURCE, "g");
|
|
35
|
+
|
|
36
|
+
/** `{{name}}` or `{{/json/pointer}}` with inner whitespace tolerated: what substitution and analysis match. */
|
|
37
|
+
export const VARIABLE_PATTERN_SOURCE = String.raw`{{\s*([^{}]*?)\s*}}`;
|
|
38
|
+
/** The subset an editor turns into a chip: a non-empty, whitespace-free name. */
|
|
39
|
+
export const VARIABLE_TOKEN_SOURCE = String.raw`{{\s*([^{}\s]+)\s*}}`;
|
|
40
|
+
const VARIABLE_PATTERN = new RegExp(VARIABLE_PATTERN_SOURCE, "g");
|
|
41
|
+
|
|
42
|
+
/** Entity names by id for the two mention grammars; a missing map leaves that grammar's cached names in place. */
|
|
43
|
+
export type MentionNames = {
|
|
44
|
+
tools?: ReadonlyMap<string, string>;
|
|
45
|
+
skills?: ReadonlyMap<string, string>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const NO_NAMES: ReadonlyMap<string, string> = new Map();
|
|
49
|
+
|
|
50
|
+
function extractIds(text: string, pattern: RegExp) {
|
|
51
|
+
const ids: string[] = [];
|
|
52
|
+
for (const match of text.matchAll(pattern)) {
|
|
53
|
+
const id = match[2];
|
|
54
|
+
if (id && !ids.includes(id)) ids.push(id);
|
|
55
|
+
}
|
|
56
|
+
return ids;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function extractMentionedToolIds(text: string) {
|
|
60
|
+
return extractIds(text, MENTION_PATTERN);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function extractMentionedSkillIds(text: string) {
|
|
64
|
+
return extractIds(text, SKILL_MENTION_PATTERN);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** The dataset names a text mentions, in order of first appearance. */
|
|
68
|
+
export function extractMentionedDatasets(text: string) {
|
|
69
|
+
return extractIds(text, DATASET_MENTION_PATTERN);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Replaces each mention token with the entity's current name (falling back to the cached inline name). */
|
|
73
|
+
export function renderMentions(text: string, names: MentionNames) {
|
|
74
|
+
const tools = names.tools ?? NO_NAMES;
|
|
75
|
+
const skills = names.skills ?? NO_NAMES;
|
|
76
|
+
return text
|
|
77
|
+
.replace(MENTION_PATTERN, (_, cached: string, id: string) => tools.get(id) ?? cached)
|
|
78
|
+
.replace(SKILL_MENTION_PATTERN, (_, cached: string, id: string) => skills.get(id) ?? cached)
|
|
79
|
+
.replace(DATASET_MENTION_PATTERN, (_, _cached: string, name: string) => name);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Refreshes stale display names inside stored text, keeping the tokens intact. */
|
|
83
|
+
export function normalizeMentions(text: string, names: MentionNames) {
|
|
84
|
+
const tools = names.tools ?? NO_NAMES;
|
|
85
|
+
const skills = names.skills ?? NO_NAMES;
|
|
86
|
+
return text
|
|
87
|
+
.replace(MENTION_PATTERN, (token, _cached: string, id: string) => {
|
|
88
|
+
const name = tools.get(id);
|
|
89
|
+
return name === undefined ? token : `@[${name}](${id})`;
|
|
90
|
+
})
|
|
91
|
+
.replace(SKILL_MENTION_PATTERN, (token, _cached: string, id: string) => {
|
|
92
|
+
const name = skills.get(id);
|
|
93
|
+
return name === undefined ? token : `/[${name}](${id})`;
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Splits the `{{variables}}` referenced in a text into known and unknown ones. */
|
|
98
|
+
export function analyzeVariables<Variable extends string>(
|
|
99
|
+
text: string,
|
|
100
|
+
known: ReadonlyArray<Variable>,
|
|
101
|
+
) {
|
|
102
|
+
const referenced = new Set(
|
|
103
|
+
Array.from(text.matchAll(VARIABLE_PATTERN), ([, name]) => name?.trim()),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
variables: known.filter((variable) => referenced.has(variable)),
|
|
108
|
+
unknownVariables: [...referenced].filter(
|
|
109
|
+
(variable): variable is string =>
|
|
110
|
+
variable !== undefined &&
|
|
111
|
+
!known.some((knownVariable) => knownVariable === variable),
|
|
112
|
+
),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Fills `{{variables}}` with per-request values; unknown names stay verbatim. */
|
|
117
|
+
export function substituteVariables(text: string, values: Record<string, string>) {
|
|
118
|
+
return text.replace(VARIABLE_PATTERN, (match, name: string) => values[name] ?? match);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** ATX heading, CommonMark-style: up to three leading spaces, `#` run closed by space or line end. */
|
|
122
|
+
const ATX_HEADING = /^( {0,3})(#{1,6})(?=[ \t]|$)/;
|
|
123
|
+
|
|
124
|
+
/** Setext underline: a run of `=` or `-` alone on its line, up to three leading spaces. */
|
|
125
|
+
const SETEXT_UNDERLINE = /^ {0,3}(=+|-+)[ \t]*$/;
|
|
126
|
+
|
|
127
|
+
/** The skeleton owns `##` and `###`, so user headings start three levels below it. */
|
|
128
|
+
function demotedHashes(level: number) {
|
|
129
|
+
return "#".repeat(Math.min(level + 3, 6));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Whether a line can be the text of a setext heading — i.e. it is an ordinary
|
|
134
|
+
* paragraph line, not a blank, a fence, another heading, or the start of a
|
|
135
|
+
* different block (quote/list), in which case the underline below it is a
|
|
136
|
+
* thematic break or list content rather than a heading marker.
|
|
137
|
+
*/
|
|
138
|
+
function isSetextText(line: string) {
|
|
139
|
+
return (
|
|
140
|
+
line.trim() !== "" &&
|
|
141
|
+
!ATX_HEADING.test(line) &&
|
|
142
|
+
!SETEXT_UNDERLINE.test(line) &&
|
|
143
|
+
!/^ {0,3}(`{3,}|~{3,})/.test(line) &&
|
|
144
|
+
!/^ {0,3}(>|[-*+][ \t]|\d+[.)][ \t])/.test(line)
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Shifts user-authored headings down so they can never collide with the prompt
|
|
150
|
+
* skeleton, which owns `##` (sections) and `###` (card titles): `#` becomes
|
|
151
|
+
* `####`, deeper levels cap at `######`. Setext headings are rewritten as the
|
|
152
|
+
* equivalent demoted ATX heading, since two-level setext cannot express the
|
|
153
|
+
* shift. Fenced code is left alone.
|
|
154
|
+
*/
|
|
155
|
+
export function demoteHeadings(text: string) {
|
|
156
|
+
const lineState = fenceTracker();
|
|
157
|
+
const output: string[] = [];
|
|
158
|
+
for (const line of text.split("\n")) {
|
|
159
|
+
if (lineState(line) !== "outside") {
|
|
160
|
+
output.push(line);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const underline = SETEXT_UNDERLINE.exec(line)?.[1];
|
|
165
|
+
const previous = output[output.length - 1];
|
|
166
|
+
if (underline !== undefined && previous !== undefined && isSetextText(previous)) {
|
|
167
|
+
const level = underline.startsWith("=") ? 1 : 2;
|
|
168
|
+
output[output.length - 1] = `${demotedHashes(level)} ${previous.trim()}`;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
output.push(
|
|
173
|
+
line.replace(ATX_HEADING, (_, indent: string, hashes: string) => {
|
|
174
|
+
return `${indent}${demotedHashes(hashes.length)}`;
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
return output.join("\n");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The one render step for LLM-bound rich text: resolve mentions, then demote headings. */
|
|
182
|
+
export function renderRichText(text: string, names: MentionNames) {
|
|
183
|
+
return demoteHeadings(renderMentions(text, names));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Reduces rich text to plain prose for embeddings: mentions become entity
|
|
188
|
+
* names (cached inline names when no map is given) and markdown syntax is
|
|
189
|
+
* dropped.
|
|
190
|
+
* A regex stripper, not a markdown parser: nested emphasis or exotic
|
|
191
|
+
* constructs may leave residue; swap in a real parser if retrieval quality
|
|
192
|
+
* ever shows it.
|
|
193
|
+
*/
|
|
194
|
+
export function stripToPlaintext(text: string, names: MentionNames = {}) {
|
|
195
|
+
const lineState = fenceTracker();
|
|
196
|
+
return renderMentions(text, names)
|
|
197
|
+
.split("\n")
|
|
198
|
+
.filter((line) => {
|
|
199
|
+
const state = lineState(line);
|
|
200
|
+
return state !== "open" && state !== "close";
|
|
201
|
+
})
|
|
202
|
+
.map((line) =>
|
|
203
|
+
line
|
|
204
|
+
.replace(/^ {0,3}#{1,6}[ \t]+/, "")
|
|
205
|
+
.replace(SETEXT_UNDERLINE, "")
|
|
206
|
+
.replace(/^\s{0,3}(>\s?)+/, "")
|
|
207
|
+
.replace(/^(\s*)([-*+]|\d+[.)])\s+/, "$1")
|
|
208
|
+
.replace(/^\s*([-*_]\s*){3,}$/, ""),
|
|
209
|
+
)
|
|
210
|
+
.join("\n")
|
|
211
|
+
.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
212
|
+
.replace(/\[([^\]]*)\]\([^)]*\)/g, "$1")
|
|
213
|
+
.replace(/(\*\*|__)([^*_]+)\1/g, "$2")
|
|
214
|
+
.replace(/(^|\W)[*_]([^*_]+)[*_](?=\W|$)/gm, "$1$2")
|
|
215
|
+
.replace(/~~([^~]+)~~/g, "$1")
|
|
216
|
+
.replace(/`([^`]*)`/g, "$1")
|
|
217
|
+
.replace(/\\([\\*_#[\]()>!`~+.-])/g, "$1")
|
|
218
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
219
|
+
.trim();
|
|
220
|
+
}
|