@rejot-dev/thalo-cli 0.0.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/LICENSE +21 -0
- package/README.md +179 -0
- package/bin/run.js +2 -0
- package/dist/cli.js +175 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/actualize.js +124 -0
- package/dist/commands/actualize.js.map +1 -0
- package/dist/commands/check.js +271 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/format.js +220 -0
- package/dist/commands/format.js.map +1 -0
- package/dist/commands/init.js +192 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lsp.js +26 -0
- package/dist/commands/lsp.js.map +1 -0
- package/dist/commands/merge-driver.js +99 -0
- package/dist/commands/merge-driver.js.map +1 -0
- package/dist/commands/query.js +162 -0
- package/dist/commands/query.js.map +1 -0
- package/dist/commands/rules.js +104 -0
- package/dist/commands/rules.js.map +1 -0
- package/dist/commands/setup-merge-driver.js +210 -0
- package/dist/commands/setup-merge-driver.js.map +1 -0
- package/dist/files.js +145 -0
- package/dist/files.js.map +1 -0
- package/dist/mod.d.ts +1 -0
- package/dist/mod.js +31 -0
- package/dist/mod.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
|
|
5
|
+
//#region src/commands/init.ts
|
|
6
|
+
const ENTITIES_THALO = `{{TIMESTAMP}} define-entity journal "Personal thoughts, reflections, and experiences" ^journal
|
|
7
|
+
# Metadata
|
|
8
|
+
subject: string | link ; "Subject name/slug (use ^self for personal)"
|
|
9
|
+
type: string ; "idea, reflection, experience, doubt, question, etc."
|
|
10
|
+
mood?: string ; "Free text mood"
|
|
11
|
+
context?: string ; "What prompted this entry"
|
|
12
|
+
# Sections
|
|
13
|
+
Entry ; "The journal entry content"
|
|
14
|
+
|
|
15
|
+
{{TIMESTAMP}} define-entity opinion "Formed stances on topics" ^opinion
|
|
16
|
+
# Metadata
|
|
17
|
+
confidence: "high" | "medium" | "low"
|
|
18
|
+
supersedes?: link ; "Reference to previous stance"
|
|
19
|
+
related?: link[] ; "Related entries"
|
|
20
|
+
# Sections
|
|
21
|
+
Claim ; "Core opinion in 1-2 sentences"
|
|
22
|
+
Reasoning ; "Bullet points supporting the claim"
|
|
23
|
+
Caveats? ; "Edge cases, limitations, exceptions"
|
|
24
|
+
|
|
25
|
+
{{TIMESTAMP}} define-entity reference "External resources or local files" ^reference
|
|
26
|
+
# Metadata
|
|
27
|
+
url?: string ; "Full URL to external resource"
|
|
28
|
+
file?: string ; "Path to local file"
|
|
29
|
+
ref-type: "article" | "video" | "tweet" | "paper" | "book" | "other"
|
|
30
|
+
author?: string | link ; "Creator/author name"
|
|
31
|
+
published?: datetime ; "Publication date"
|
|
32
|
+
status?: "unread" | "read" | "processed" = "unread"
|
|
33
|
+
# Sections
|
|
34
|
+
Summary? ; "Brief summary of the content"
|
|
35
|
+
Key Takeaways? ; "Bullet points of main insights"
|
|
36
|
+
Related? ; "Links to related entries"
|
|
37
|
+
|
|
38
|
+
{{TIMESTAMP}} define-entity lore "Facts and insights about subjects or yourself" ^lore
|
|
39
|
+
# Metadata
|
|
40
|
+
type: "fact" | "insight" ; "fact = verifiable info, insight = learned wisdom"
|
|
41
|
+
subject: string | link ; "Subject name/slug (use ^self for personal lore)"
|
|
42
|
+
date?: date-range ; "Relevant date or date range"
|
|
43
|
+
# Sections
|
|
44
|
+
Description ; "The lore content"
|
|
45
|
+
|
|
46
|
+
{{TIMESTAMP}} define-entity me "Entity to allow for self-references" ^self
|
|
47
|
+
# Sections
|
|
48
|
+
Bio ; "Need at least one section"
|
|
49
|
+
`;
|
|
50
|
+
const AGENTS_MD = `# THALO - Thought And Lore Language
|
|
51
|
+
|
|
52
|
+
Entity schemas are defined in \`entities.thalo\`.
|
|
53
|
+
|
|
54
|
+
## Entry Syntax
|
|
55
|
+
|
|
56
|
+
\`\`\`
|
|
57
|
+
{timestamp} {directive} {entity} "Title" [^link-id] [#tags...]
|
|
58
|
+
{key}: {value}
|
|
59
|
+
...
|
|
60
|
+
|
|
61
|
+
# Section
|
|
62
|
+
{content}
|
|
63
|
+
|
|
64
|
+
\`\`\`
|
|
65
|
+
|
|
66
|
+
- **timestamp**: ISO 8601 local time with timezone (\`2026-01-05T15:30Z\`)
|
|
67
|
+
- **directive**: \`create\` or \`update\`
|
|
68
|
+
- **entity**: \`journal\`, \`opinion\`, \`reference\`, or \`lore\`
|
|
69
|
+
- **^link-id**: Optional explicit ID for cross-referencing
|
|
70
|
+
- **#tag**: Optional categorization tags
|
|
71
|
+
|
|
72
|
+
## Metadata
|
|
73
|
+
|
|
74
|
+
Metadata fields are indented key-value pairs. See \`entities.thalo\` for required/optional
|
|
75
|
+
fields per entity. Values can be:
|
|
76
|
+
|
|
77
|
+
- Strings: \`author: "Jane Doe"\` or unquoted \`author: Jane Doe\`
|
|
78
|
+
- Links: \`subject: ^self\` or \`related: ^my-other-entry\`
|
|
79
|
+
- Dates: \`published: 2023-03-16\`
|
|
80
|
+
- Date ranges: \`date: 2020 ~ 2021\`
|
|
81
|
+
|
|
82
|
+
## Sections
|
|
83
|
+
|
|
84
|
+
Content sections start with \`# SectionName\` (indented). **All content must be within a section.**
|
|
85
|
+
Each entity type defines which sections are required/optional in \`entities.thalo\`.
|
|
86
|
+
|
|
87
|
+
## Example
|
|
88
|
+
|
|
89
|
+
\`\`\`thalo
|
|
90
|
+
2026-01-05T16:00Z create opinion "TypeScript enums should be avoided" ^opinion-ts-enums #typescript
|
|
91
|
+
confidence: "high"
|
|
92
|
+
|
|
93
|
+
# Claim
|
|
94
|
+
TypeScript enums should be replaced with \`as const\` objects.
|
|
95
|
+
|
|
96
|
+
# Reasoning
|
|
97
|
+
- Enums generate runtime code
|
|
98
|
+
- \`as const\` provides the same type safety with zero overhead
|
|
99
|
+
|
|
100
|
+
\`\`\`
|
|
101
|
+
|
|
102
|
+
## Tips
|
|
103
|
+
|
|
104
|
+
- Run \`date -u +"%Y-%m-%dT%H:%MZ"\` to get the current timestamp
|
|
105
|
+
- Use \`thalo check\` to validate entries against schemas
|
|
106
|
+
`;
|
|
107
|
+
const PERSONAL_BIO_MD = `\`\`\`thalo
|
|
108
|
+
{{TIMESTAMP}} define-synthesis "Personal Bio" ^bio-synthesis #profile
|
|
109
|
+
sources: lore where subject = ^self
|
|
110
|
+
|
|
111
|
+
# Prompt
|
|
112
|
+
Write a narrative bio from the collected facts and insights.
|
|
113
|
+
Keep it professional but personable.
|
|
114
|
+
\`\`\`
|
|
115
|
+
|
|
116
|
+
# Personal Bio
|
|
117
|
+
`;
|
|
118
|
+
function initAction(ctx) {
|
|
119
|
+
const { options, args } = ctx;
|
|
120
|
+
const targetDir = args.length > 0 ? path.resolve(args[0]) : process.cwd();
|
|
121
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 16) + "Z";
|
|
122
|
+
const dryRun = options["dry-run"];
|
|
123
|
+
const force = options["force"];
|
|
124
|
+
if (dryRun) {
|
|
125
|
+
console.log(pc.dim("Dry run mode - no files will be created"));
|
|
126
|
+
console.log();
|
|
127
|
+
}
|
|
128
|
+
console.log(pc.bold("Initializing THALO..."));
|
|
129
|
+
console.log();
|
|
130
|
+
const files = [
|
|
131
|
+
{
|
|
132
|
+
path: "entities.thalo",
|
|
133
|
+
content: ENTITIES_THALO.replace(/\{\{TIMESTAMP\}\}/g, timestamp)
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
path: "AGENTS.md",
|
|
137
|
+
content: AGENTS_MD
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
path: "personal-bio.md",
|
|
141
|
+
content: PERSONAL_BIO_MD.replace(/\{\{TIMESTAMP\}\}/g, timestamp)
|
|
142
|
+
}
|
|
143
|
+
];
|
|
144
|
+
let createdCount = 0;
|
|
145
|
+
let warningCount = 0;
|
|
146
|
+
for (const file of files) {
|
|
147
|
+
const fullPath = path.join(targetDir, file.path);
|
|
148
|
+
const exists = fs.existsSync(fullPath);
|
|
149
|
+
if (exists && !force) {
|
|
150
|
+
console.log(`${pc.yellow("⚠")} ${pc.yellow("warning:")} ${file.path} already exists, skipping`);
|
|
151
|
+
warningCount++;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (exists && force) console.log(`${pc.yellow("⚠")} ${pc.yellow("overwriting:")} ${file.path}`);
|
|
155
|
+
if (!dryRun) fs.writeFileSync(fullPath, file.content, "utf-8");
|
|
156
|
+
console.log(`${pc.green("✓")} Created: ${pc.dim(file.path)}`);
|
|
157
|
+
createdCount++;
|
|
158
|
+
}
|
|
159
|
+
console.log();
|
|
160
|
+
if (warningCount > 0) console.log(`${pc.yellow("Warnings:")} ${warningCount} file(s) already exist`);
|
|
161
|
+
if (dryRun) console.log(pc.dim("Run without --dry-run to create files."));
|
|
162
|
+
else if (createdCount > 0) console.log(`${pc.green("✓")} Done! Created ${createdCount} file(s).`);
|
|
163
|
+
}
|
|
164
|
+
const initCommand = {
|
|
165
|
+
name: "init",
|
|
166
|
+
description: "Initialize THALO with entity definitions and documentation",
|
|
167
|
+
args: {
|
|
168
|
+
name: "directory",
|
|
169
|
+
description: "Target directory (defaults to current directory)",
|
|
170
|
+
required: false,
|
|
171
|
+
multiple: false
|
|
172
|
+
},
|
|
173
|
+
options: {
|
|
174
|
+
"dry-run": {
|
|
175
|
+
type: "boolean",
|
|
176
|
+
short: "n",
|
|
177
|
+
description: "Show what would be created without making changes",
|
|
178
|
+
default: false
|
|
179
|
+
},
|
|
180
|
+
force: {
|
|
181
|
+
type: "boolean",
|
|
182
|
+
short: "f",
|
|
183
|
+
description: "Overwrite existing files",
|
|
184
|
+
default: false
|
|
185
|
+
}
|
|
186
|
+
},
|
|
187
|
+
action: initAction
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
//#endregion
|
|
191
|
+
export { initCommand };
|
|
192
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","names":["initCommand: CommandDef"],"sources":["../../src/commands/init.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport pc from \"picocolors\";\nimport type { CommandDef, CommandContext } from \"../cli.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// File Templates\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst ENTITIES_THALO = `{{TIMESTAMP}} define-entity journal \"Personal thoughts, reflections, and experiences\" ^journal\n # Metadata\n subject: string | link ; \"Subject name/slug (use ^self for personal)\"\n type: string ; \"idea, reflection, experience, doubt, question, etc.\"\n mood?: string ; \"Free text mood\"\n context?: string ; \"What prompted this entry\"\n # Sections\n Entry ; \"The journal entry content\"\n\n{{TIMESTAMP}} define-entity opinion \"Formed stances on topics\" ^opinion\n # Metadata\n confidence: \"high\" | \"medium\" | \"low\"\n supersedes?: link ; \"Reference to previous stance\"\n related?: link[] ; \"Related entries\"\n # Sections\n Claim ; \"Core opinion in 1-2 sentences\"\n Reasoning ; \"Bullet points supporting the claim\"\n Caveats? ; \"Edge cases, limitations, exceptions\"\n\n{{TIMESTAMP}} define-entity reference \"External resources or local files\" ^reference\n # Metadata\n url?: string ; \"Full URL to external resource\"\n file?: string ; \"Path to local file\"\n ref-type: \"article\" | \"video\" | \"tweet\" | \"paper\" | \"book\" | \"other\"\n author?: string | link ; \"Creator/author name\"\n published?: datetime ; \"Publication date\"\n status?: \"unread\" | \"read\" | \"processed\" = \"unread\"\n # Sections\n Summary? ; \"Brief summary of the content\"\n Key Takeaways? ; \"Bullet points of main insights\"\n Related? ; \"Links to related entries\"\n\n{{TIMESTAMP}} define-entity lore \"Facts and insights about subjects or yourself\" ^lore\n # Metadata\n type: \"fact\" | \"insight\" ; \"fact = verifiable info, insight = learned wisdom\"\n subject: string | link ; \"Subject name/slug (use ^self for personal lore)\"\n date?: date-range ; \"Relevant date or date range\"\n # Sections\n Description ; \"The lore content\"\n\n{{TIMESTAMP}} define-entity me \"Entity to allow for self-references\" ^self\n # Sections\n Bio ; \"Need at least one section\"\n`;\n\nconst AGENTS_MD = `# THALO - Thought And Lore Language\n\nEntity schemas are defined in \\`entities.thalo\\`.\n\n## Entry Syntax\n\n\\`\\`\\`\n{timestamp} {directive} {entity} \"Title\" [^link-id] [#tags...]\n {key}: {value}\n ...\n\n # Section\n {content}\n\n\\`\\`\\`\n\n- **timestamp**: ISO 8601 local time with timezone (\\`2026-01-05T15:30Z\\`)\n- **directive**: \\`create\\` or \\`update\\`\n- **entity**: \\`journal\\`, \\`opinion\\`, \\`reference\\`, or \\`lore\\`\n- **^link-id**: Optional explicit ID for cross-referencing\n- **#tag**: Optional categorization tags\n\n## Metadata\n\nMetadata fields are indented key-value pairs. See \\`entities.thalo\\` for required/optional\nfields per entity. Values can be:\n\n- Strings: \\`author: \"Jane Doe\"\\` or unquoted \\`author: Jane Doe\\`\n- Links: \\`subject: ^self\\` or \\`related: ^my-other-entry\\`\n- Dates: \\`published: 2023-03-16\\`\n- Date ranges: \\`date: 2020 ~ 2021\\`\n\n## Sections\n\nContent sections start with \\`# SectionName\\` (indented). **All content must be within a section.**\nEach entity type defines which sections are required/optional in \\`entities.thalo\\`.\n\n## Example\n\n\\`\\`\\`thalo\n2026-01-05T16:00Z create opinion \"TypeScript enums should be avoided\" ^opinion-ts-enums #typescript\n confidence: \"high\"\n\n # Claim\n TypeScript enums should be replaced with \\`as const\\` objects.\n\n # Reasoning\n - Enums generate runtime code\n - \\`as const\\` provides the same type safety with zero overhead\n\n\\`\\`\\`\n\n## Tips\n\n- Run \\`date -u +\"%Y-%m-%dT%H:%MZ\"\\` to get the current timestamp\n- Use \\`thalo check\\` to validate entries against schemas\n`;\n\nconst PERSONAL_BIO_MD = `\\`\\`\\`thalo\n{{TIMESTAMP}} define-synthesis \"Personal Bio\" ^bio-synthesis #profile\n sources: lore where subject = ^self\n\n # Prompt\n Write a narrative bio from the collected facts and insights.\n Keep it professional but personable.\n\\`\\`\\`\n\n# Personal Bio\n`;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Init Action\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction initAction(ctx: CommandContext): void {\n const { options, args } = ctx;\n\n // Determine target directory\n const targetDir = args.length > 0 ? path.resolve(args[0]) : process.cwd();\n\n // Get current timestamp for entity definitions\n const now = new Date();\n const timestamp = now.toISOString().slice(0, 16) + \"Z\"; // YYYY-MM-DDTHH:MMZ\n\n const dryRun = options[\"dry-run\"] as boolean;\n const force = options[\"force\"] as boolean;\n\n if (dryRun) {\n console.log(pc.dim(\"Dry run mode - no files will be created\"));\n console.log();\n }\n\n console.log(pc.bold(\"Initializing THALO...\"));\n console.log();\n\n const files = [\n { path: \"entities.thalo\", content: ENTITIES_THALO.replace(/\\{\\{TIMESTAMP\\}\\}/g, timestamp) },\n { path: \"AGENTS.md\", content: AGENTS_MD },\n { path: \"personal-bio.md\", content: PERSONAL_BIO_MD.replace(/\\{\\{TIMESTAMP\\}\\}/g, timestamp) },\n ];\n\n let createdCount = 0;\n let warningCount = 0;\n\n for (const file of files) {\n const fullPath = path.join(targetDir, file.path);\n const exists = fs.existsSync(fullPath);\n\n if (exists && !force) {\n console.log(\n `${pc.yellow(\"⚠\")} ${pc.yellow(\"warning:\")} ${file.path} already exists, skipping`,\n );\n warningCount++;\n continue;\n }\n\n if (exists && force) {\n console.log(`${pc.yellow(\"⚠\")} ${pc.yellow(\"overwriting:\")} ${file.path}`);\n }\n\n if (!dryRun) {\n fs.writeFileSync(fullPath, file.content, \"utf-8\");\n }\n\n console.log(`${pc.green(\"✓\")} Created: ${pc.dim(file.path)}`);\n createdCount++;\n }\n\n // Summary\n console.log();\n if (warningCount > 0) {\n console.log(`${pc.yellow(\"Warnings:\")} ${warningCount} file(s) already exist`);\n }\n\n if (dryRun) {\n console.log(pc.dim(\"Run without --dry-run to create files.\"));\n } else if (createdCount > 0) {\n console.log(`${pc.green(\"✓\")} Done! Created ${createdCount} file(s).`);\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Command Definition\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const initCommand: CommandDef = {\n name: \"init\",\n description: \"Initialize THALO with entity definitions and documentation\",\n args: {\n name: \"directory\",\n description: \"Target directory (defaults to current directory)\",\n required: false,\n multiple: false,\n },\n options: {\n \"dry-run\": {\n type: \"boolean\",\n short: \"n\",\n description: \"Show what would be created without making changes\",\n default: false,\n },\n force: {\n type: \"boolean\",\n short: \"f\",\n description: \"Overwrite existing files\",\n default: false,\n },\n },\n action: initAction,\n};\n"],"mappings":";;;;;AASA,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CvB,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0DlB,MAAM,kBAAkB;;;;;;;;;;;AAgBxB,SAAS,WAAW,KAA2B;CAC7C,MAAM,EAAE,SAAS,SAAS;CAG1B,MAAM,YAAY,KAAK,SAAS,IAAI,KAAK,QAAQ,KAAK,GAAG,GAAG,QAAQ,KAAK;CAIzE,MAAM,6BADM,IAAI,MAAM,EACA,aAAa,CAAC,MAAM,GAAG,GAAG,GAAG;CAEnD,MAAM,SAAS,QAAQ;CACvB,MAAM,QAAQ,QAAQ;AAEtB,KAAI,QAAQ;AACV,UAAQ,IAAI,GAAG,IAAI,0CAA0C,CAAC;AAC9D,UAAQ,KAAK;;AAGf,SAAQ,IAAI,GAAG,KAAK,wBAAwB,CAAC;AAC7C,SAAQ,KAAK;CAEb,MAAM,QAAQ;EACZ;GAAE,MAAM;GAAkB,SAAS,eAAe,QAAQ,sBAAsB,UAAU;GAAE;EAC5F;GAAE,MAAM;GAAa,SAAS;GAAW;EACzC;GAAE,MAAM;GAAmB,SAAS,gBAAgB,QAAQ,sBAAsB,UAAU;GAAE;EAC/F;CAED,IAAI,eAAe;CACnB,IAAI,eAAe;AAEnB,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,WAAW,KAAK,KAAK,WAAW,KAAK,KAAK;EAChD,MAAM,SAAS,GAAG,WAAW,SAAS;AAEtC,MAAI,UAAU,CAAC,OAAO;AACpB,WAAQ,IACN,GAAG,GAAG,OAAO,IAAI,CAAC,GAAG,GAAG,OAAO,WAAW,CAAC,GAAG,KAAK,KAAK,2BACzD;AACD;AACA;;AAGF,MAAI,UAAU,MACZ,SAAQ,IAAI,GAAG,GAAG,OAAO,IAAI,CAAC,GAAG,GAAG,OAAO,eAAe,CAAC,GAAG,KAAK,OAAO;AAG5E,MAAI,CAAC,OACH,IAAG,cAAc,UAAU,KAAK,SAAS,QAAQ;AAGnD,UAAQ,IAAI,GAAG,GAAG,MAAM,IAAI,CAAC,YAAY,GAAG,IAAI,KAAK,KAAK,GAAG;AAC7D;;AAIF,SAAQ,KAAK;AACb,KAAI,eAAe,EACjB,SAAQ,IAAI,GAAG,GAAG,OAAO,YAAY,CAAC,GAAG,aAAa,wBAAwB;AAGhF,KAAI,OACF,SAAQ,IAAI,GAAG,IAAI,yCAAyC,CAAC;UACpD,eAAe,EACxB,SAAQ,IAAI,GAAG,GAAG,MAAM,IAAI,CAAC,iBAAiB,aAAa,WAAW;;AAQ1E,MAAaA,cAA0B;CACrC,MAAM;CACN,aAAa;CACb,MAAM;EACJ,MAAM;EACN,aAAa;EACb,UAAU;EACV,UAAU;EACX;CACD,SAAS;EACP,WAAW;GACT,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;GACV;EACF;CACD,QAAQ;CACT"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ProposedFeatures, createConnection } from "vscode-languageserver/node.js";
|
|
2
|
+
import { startServer } from "@rejot-dev/thalo-lsp";
|
|
3
|
+
|
|
4
|
+
//#region src/commands/lsp.ts
|
|
5
|
+
/**
|
|
6
|
+
* LSP command - starts the Language Server Protocol server
|
|
7
|
+
*
|
|
8
|
+
* This is used by editors (VS Code, etc.) to provide language features.
|
|
9
|
+
* Communication happens over stdio.
|
|
10
|
+
*/
|
|
11
|
+
const lspCommand = {
|
|
12
|
+
name: "lsp",
|
|
13
|
+
description: "Start the Language Server Protocol server (for editor integration)",
|
|
14
|
+
options: { stdio: {
|
|
15
|
+
type: "boolean",
|
|
16
|
+
description: "Use stdio transport (default, accepted for compatibility)",
|
|
17
|
+
default: false
|
|
18
|
+
} },
|
|
19
|
+
action: () => {
|
|
20
|
+
startServer(createConnection(ProposedFeatures.all, process.stdin, process.stdout));
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
export { lspCommand };
|
|
26
|
+
//# sourceMappingURL=lsp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lsp.js","names":["lspCommand: CommandDef","createLspConnection"],"sources":["../../src/commands/lsp.ts"],"sourcesContent":["import {\n createConnection as createLspConnection,\n ProposedFeatures,\n} from \"vscode-languageserver/node.js\";\nimport { startServer } from \"@rejot-dev/thalo-lsp\";\nimport type { CommandDef } from \"../cli.js\";\n\n/**\n * LSP command - starts the Language Server Protocol server\n *\n * This is used by editors (VS Code, etc.) to provide language features.\n * Communication happens over stdio.\n */\nexport const lspCommand: CommandDef = {\n name: \"lsp\",\n description: \"Start the Language Server Protocol server (for editor integration)\",\n options: {\n stdio: {\n type: \"boolean\",\n description: \"Use stdio transport (default, accepted for compatibility)\",\n default: false,\n },\n },\n action: () => {\n // Create connection explicitly with stdio streams\n // Note: --stdio is accepted for compatibility with vscode-languageclient but is always the default\n const connection = createLspConnection(ProposedFeatures.all, process.stdin, process.stdout);\n startServer(connection);\n },\n};\n"],"mappings":";;;;;;;;;;AAaA,MAAaA,aAAyB;CACpC,MAAM;CACN,aAAa;CACb,SAAS,EACP,OAAO;EACL,MAAM;EACN,aAAa;EACb,SAAS;EACV,EACF;CACD,cAAc;AAIZ,cADmBC,iBAAoB,iBAAiB,KAAK,QAAQ,OAAO,QAAQ,OAAO,CACpE;;CAE1B"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { mergeThaloFiles } from "@rejot-dev/thalo";
|
|
3
|
+
import * as fs from "node:fs";
|
|
4
|
+
|
|
5
|
+
//#region src/commands/merge-driver.ts
|
|
6
|
+
/**
|
|
7
|
+
* Git merge driver command
|
|
8
|
+
*
|
|
9
|
+
* Called by Git with: thalo merge-driver %O %A %B %P
|
|
10
|
+
* - %O = base file (common ancestor)
|
|
11
|
+
* - %A = ours file (current/local version) - MODIFIED IN PLACE
|
|
12
|
+
* - %B = theirs file (incoming version)
|
|
13
|
+
* - %P = path in repository (for display)
|
|
14
|
+
*
|
|
15
|
+
* Exit codes:
|
|
16
|
+
* - 0: Clean merge (no conflicts)
|
|
17
|
+
* - 1: Conflicts detected
|
|
18
|
+
* - 2: Fatal error (file read/write failure)
|
|
19
|
+
*/
|
|
20
|
+
async function mergeDriverAction(ctx) {
|
|
21
|
+
const { args, options } = ctx;
|
|
22
|
+
if (args.length < 3) {
|
|
23
|
+
console.error(pc.red("Error: merge-driver requires 3 file arguments"));
|
|
24
|
+
console.error("Usage: thalo merge-driver <base> <ours> <theirs> [<path>]");
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
const [basePath, oursPath, theirsPath] = args;
|
|
28
|
+
const repoPath = args[3] || oursPath;
|
|
29
|
+
let base, ours, theirs;
|
|
30
|
+
try {
|
|
31
|
+
[base, ours, theirs] = await Promise.all([
|
|
32
|
+
fs.promises.readFile(basePath, "utf-8"),
|
|
33
|
+
fs.promises.readFile(oursPath, "utf-8"),
|
|
34
|
+
fs.promises.readFile(theirsPath, "utf-8")
|
|
35
|
+
]);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
38
|
+
console.error(pc.red(`Error reading files: ${message}`));
|
|
39
|
+
process.exit(2);
|
|
40
|
+
}
|
|
41
|
+
const showBase = options["diff3"];
|
|
42
|
+
const result = mergeThaloFiles(base, ours, theirs, {
|
|
43
|
+
showBase,
|
|
44
|
+
markerStyle: showBase ? "diff3" : "git"
|
|
45
|
+
});
|
|
46
|
+
try {
|
|
47
|
+
await fs.promises.writeFile(oursPath, result.content, "utf-8");
|
|
48
|
+
} catch (err) {
|
|
49
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
50
|
+
console.error(pc.red(`Error writing merged file: ${message}`));
|
|
51
|
+
process.exit(2);
|
|
52
|
+
}
|
|
53
|
+
if (!options["quiet"]) if (result.success) {
|
|
54
|
+
console.log(pc.green(`✓ Auto-merged ${repoPath}`));
|
|
55
|
+
console.log(pc.dim(` ${result.stats.totalEntries} entries, ${result.stats.autoMerged} auto-merged`));
|
|
56
|
+
} else {
|
|
57
|
+
console.log(pc.yellow(`⚠ Conflicts in ${repoPath}`));
|
|
58
|
+
console.log(pc.dim(` ${result.conflicts.length} conflict(s) to resolve`));
|
|
59
|
+
if (options["verbose"]) {
|
|
60
|
+
console.log();
|
|
61
|
+
for (const conflict of result.conflicts) console.log(` ${pc.yellow("•")} ${conflict.message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
process.exit(result.success ? 0 : 1);
|
|
65
|
+
}
|
|
66
|
+
const mergeDriverCommand = {
|
|
67
|
+
name: "merge-driver",
|
|
68
|
+
description: "Git merge driver for thalo files (internal use)",
|
|
69
|
+
args: {
|
|
70
|
+
name: "files",
|
|
71
|
+
description: "Base, ours, theirs, and optional path",
|
|
72
|
+
required: true,
|
|
73
|
+
multiple: true
|
|
74
|
+
},
|
|
75
|
+
options: {
|
|
76
|
+
quiet: {
|
|
77
|
+
type: "boolean",
|
|
78
|
+
short: "q",
|
|
79
|
+
description: "Suppress output",
|
|
80
|
+
default: false
|
|
81
|
+
},
|
|
82
|
+
verbose: {
|
|
83
|
+
type: "boolean",
|
|
84
|
+
short: "v",
|
|
85
|
+
description: "Show detailed conflict information",
|
|
86
|
+
default: false
|
|
87
|
+
},
|
|
88
|
+
diff3: {
|
|
89
|
+
type: "boolean",
|
|
90
|
+
description: "Use diff3 conflict style (show base)",
|
|
91
|
+
default: false
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
action: mergeDriverAction
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
//#endregion
|
|
98
|
+
export { mergeDriverCommand };
|
|
99
|
+
//# sourceMappingURL=merge-driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-driver.js","names":["base: string","ours: string","theirs: string","mergeDriverCommand: CommandDef"],"sources":["../../src/commands/merge-driver.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport { mergeThaloFiles } from \"@rejot-dev/thalo\";\nimport type { CommandDef, CommandContext } from \"../cli.js\";\nimport pc from \"picocolors\";\n\n/**\n * Git merge driver command\n *\n * Called by Git with: thalo merge-driver %O %A %B %P\n * - %O = base file (common ancestor)\n * - %A = ours file (current/local version) - MODIFIED IN PLACE\n * - %B = theirs file (incoming version)\n * - %P = path in repository (for display)\n *\n * Exit codes:\n * - 0: Clean merge (no conflicts)\n * - 1: Conflicts detected\n * - 2: Fatal error (file read/write failure)\n */\nasync function mergeDriverAction(ctx: CommandContext): Promise<void> {\n const { args, options } = ctx;\n\n if (args.length < 3) {\n console.error(pc.red(\"Error: merge-driver requires 3 file arguments\"));\n console.error(\"Usage: thalo merge-driver <base> <ours> <theirs> [<path>]\");\n process.exit(2);\n }\n\n const [basePath, oursPath, theirsPath] = args;\n const repoPath = args[3] || oursPath;\n\n let base: string, ours: string, theirs: string;\n try {\n [base, ours, theirs] = await Promise.all([\n fs.promises.readFile(basePath, \"utf-8\"),\n fs.promises.readFile(oursPath, \"utf-8\"),\n fs.promises.readFile(theirsPath, \"utf-8\"),\n ]);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(pc.red(`Error reading files: ${message}`));\n process.exit(2);\n }\n\n const showBase = options[\"diff3\"] as boolean;\n const result = mergeThaloFiles(base, ours, theirs, {\n showBase,\n markerStyle: showBase ? \"diff3\" : \"git\",\n });\n\n try {\n await fs.promises.writeFile(oursPath, result.content, \"utf-8\");\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(pc.red(`Error writing merged file: ${message}`));\n process.exit(2);\n }\n\n if (!options[\"quiet\"]) {\n if (result.success) {\n console.log(pc.green(`✓ Auto-merged ${repoPath}`));\n console.log(\n pc.dim(` ${result.stats.totalEntries} entries, ${result.stats.autoMerged} auto-merged`),\n );\n } else {\n console.log(pc.yellow(`⚠ Conflicts in ${repoPath}`));\n console.log(pc.dim(` ${result.conflicts.length} conflict(s) to resolve`));\n\n if (options[\"verbose\"]) {\n console.log();\n for (const conflict of result.conflicts) {\n console.log(` ${pc.yellow(\"•\")} ${conflict.message}`);\n }\n }\n }\n }\n\n process.exit(result.success ? 0 : 1);\n}\n\nexport const mergeDriverCommand: CommandDef = {\n name: \"merge-driver\",\n description: \"Git merge driver for thalo files (internal use)\",\n args: {\n name: \"files\",\n description: \"Base, ours, theirs, and optional path\",\n required: true,\n multiple: true,\n },\n options: {\n quiet: {\n type: \"boolean\",\n short: \"q\",\n description: \"Suppress output\",\n default: false,\n },\n verbose: {\n type: \"boolean\",\n short: \"v\",\n description: \"Show detailed conflict information\",\n default: false,\n },\n diff3: {\n type: \"boolean\",\n description: \"Use diff3 conflict style (show base)\",\n default: false,\n },\n },\n action: mergeDriverAction,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAmBA,eAAe,kBAAkB,KAAoC;CACnE,MAAM,EAAE,MAAM,YAAY;AAE1B,KAAI,KAAK,SAAS,GAAG;AACnB,UAAQ,MAAM,GAAG,IAAI,gDAAgD,CAAC;AACtE,UAAQ,MAAM,4DAA4D;AAC1E,UAAQ,KAAK,EAAE;;CAGjB,MAAM,CAAC,UAAU,UAAU,cAAc;CACzC,MAAM,WAAW,KAAK,MAAM;CAE5B,IAAIA,MAAcC,MAAcC;AAChC,KAAI;AACF,GAAC,MAAM,MAAM,UAAU,MAAM,QAAQ,IAAI;GACvC,GAAG,SAAS,SAAS,UAAU,QAAQ;GACvC,GAAG,SAAS,SAAS,UAAU,QAAQ;GACvC,GAAG,SAAS,SAAS,YAAY,QAAQ;GAC1C,CAAC;UACK,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,UAAQ,MAAM,GAAG,IAAI,wBAAwB,UAAU,CAAC;AACxD,UAAQ,KAAK,EAAE;;CAGjB,MAAM,WAAW,QAAQ;CACzB,MAAM,SAAS,gBAAgB,MAAM,MAAM,QAAQ;EACjD;EACA,aAAa,WAAW,UAAU;EACnC,CAAC;AAEF,KAAI;AACF,QAAM,GAAG,SAAS,UAAU,UAAU,OAAO,SAAS,QAAQ;UACvD,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,UAAQ,MAAM,GAAG,IAAI,8BAA8B,UAAU,CAAC;AAC9D,UAAQ,KAAK,EAAE;;AAGjB,KAAI,CAAC,QAAQ,SACX,KAAI,OAAO,SAAS;AAClB,UAAQ,IAAI,GAAG,MAAM,iBAAiB,WAAW,CAAC;AAClD,UAAQ,IACN,GAAG,IAAI,KAAK,OAAO,MAAM,aAAa,YAAY,OAAO,MAAM,WAAW,cAAc,CACzF;QACI;AACL,UAAQ,IAAI,GAAG,OAAO,kBAAkB,WAAW,CAAC;AACpD,UAAQ,IAAI,GAAG,IAAI,KAAK,OAAO,UAAU,OAAO,yBAAyB,CAAC;AAE1E,MAAI,QAAQ,YAAY;AACtB,WAAQ,KAAK;AACb,QAAK,MAAM,YAAY,OAAO,UAC5B,SAAQ,IAAI,KAAK,GAAG,OAAO,IAAI,CAAC,GAAG,SAAS,UAAU;;;AAM9D,SAAQ,KAAK,OAAO,UAAU,IAAI,EAAE;;AAGtC,MAAaC,qBAAiC;CAC5C,MAAM;CACN,aAAa;CACb,MAAM;EACJ,MAAM;EACN,aAAa;EACb,UAAU;EACV,UAAU;EACX;CACD,SAAS;EACP,OAAO;GACL,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;GACV;EACD,SAAS;GACP,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACF;CACD,QAAQ;CACT"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { loadWorkspace, relativePath, resolveFiles } from "../files.js";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import { formatQueryResultRaw, isCheckpointError, isQueryValidationError, parseCheckpoint, runQuery } from "@rejot-dev/thalo";
|
|
4
|
+
import { NotInGitRepoError, createChangeTracker } from "@rejot-dev/thalo/change-tracker/node";
|
|
5
|
+
|
|
6
|
+
//#region src/commands/query.ts
|
|
7
|
+
/**
|
|
8
|
+
* Format entry with colors for default display
|
|
9
|
+
*/
|
|
10
|
+
function formatEntryDefault(entry) {
|
|
11
|
+
const lines = [];
|
|
12
|
+
const tags = entry.tags.map((t) => `#${t}`).join(" ");
|
|
13
|
+
lines.push(`${pc.dim(entry.timestamp)} ${pc.cyan(entry.entity)} ${pc.bold(entry.title)}${entry.linkId ? " " + pc.green(`^${entry.linkId}`) : ""}${tags ? " " + pc.yellow(tags) : ""}`);
|
|
14
|
+
lines.push(` ${pc.dim(`${relativePath(entry.file)}:${entry.startLine}-${entry.endLine}`)}`);
|
|
15
|
+
return lines.join("\n");
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Format entry for JSON output
|
|
19
|
+
*/
|
|
20
|
+
function formatEntryJson(entry) {
|
|
21
|
+
return {
|
|
22
|
+
file: relativePath(entry.file),
|
|
23
|
+
timestamp: entry.timestamp,
|
|
24
|
+
entity: entry.entity,
|
|
25
|
+
title: entry.title,
|
|
26
|
+
tags: entry.tags,
|
|
27
|
+
link: entry.linkId,
|
|
28
|
+
location: {
|
|
29
|
+
startLine: entry.startLine,
|
|
30
|
+
endLine: entry.endLine
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async function queryAction(ctx) {
|
|
35
|
+
const { options, args } = ctx;
|
|
36
|
+
const queryStr = args[0];
|
|
37
|
+
if (!queryStr) {
|
|
38
|
+
console.error(pc.red("Error: Query string is required"));
|
|
39
|
+
console.error(`\nUsage: thalo query "<query>"`);
|
|
40
|
+
console.error(`\nExamples:`);
|
|
41
|
+
console.error(` thalo query 'lore'`);
|
|
42
|
+
console.error(` thalo query 'lore where #career'`);
|
|
43
|
+
console.error(` thalo query 'opinion where subject = "topic"'`);
|
|
44
|
+
process.exit(2);
|
|
45
|
+
}
|
|
46
|
+
const format = options["json"] ? "json" : options["format"] || "default";
|
|
47
|
+
if (format === "json") process.env["NO_COLOR"] = "1";
|
|
48
|
+
const limitStr = options["limit"];
|
|
49
|
+
const limit = limitStr ? parseInt(limitStr, 10) : void 0;
|
|
50
|
+
const since = options["since"];
|
|
51
|
+
const targetPaths = args.slice(1);
|
|
52
|
+
const files = await resolveFiles(targetPaths.length > 0 ? targetPaths : ["."]);
|
|
53
|
+
if (files.length === 0) {
|
|
54
|
+
console.log("No .thalo or .md files found.");
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
const workspace = await loadWorkspace(files);
|
|
58
|
+
let tracker;
|
|
59
|
+
if (since) {
|
|
60
|
+
if (parseCheckpoint(since)?.type === "git") try {
|
|
61
|
+
tracker = await createChangeTracker({ cwd: process.cwd() });
|
|
62
|
+
} catch (err) {
|
|
63
|
+
if (err instanceof NotInGitRepoError) {
|
|
64
|
+
console.error(pc.red(`Error: Cannot use git checkpoint "${since}" - not in a git repository`));
|
|
65
|
+
console.error(pc.dim(`Directory: ${err.cwd}`));
|
|
66
|
+
} else {
|
|
67
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
68
|
+
console.error(pc.red(`Error: Failed to create git tracker for checkpoint "${since}"`));
|
|
69
|
+
console.error(pc.dim(message));
|
|
70
|
+
}
|
|
71
|
+
process.exit(2);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const result = await runQuery(workspace, queryStr, {
|
|
75
|
+
limit,
|
|
76
|
+
includeRawText: format === "raw",
|
|
77
|
+
since,
|
|
78
|
+
tracker
|
|
79
|
+
});
|
|
80
|
+
if (!result) {
|
|
81
|
+
console.error(pc.red(`Error: Invalid query syntax`));
|
|
82
|
+
console.error(pc.dim(`Query: ${queryStr}`));
|
|
83
|
+
process.exit(2);
|
|
84
|
+
}
|
|
85
|
+
if (isQueryValidationError(result)) {
|
|
86
|
+
console.error(pc.red(`Error: ${result.message}`));
|
|
87
|
+
process.exit(2);
|
|
88
|
+
}
|
|
89
|
+
if (isCheckpointError(result)) {
|
|
90
|
+
console.error(pc.red(`Error: ${result.message}`));
|
|
91
|
+
process.exit(2);
|
|
92
|
+
}
|
|
93
|
+
if (format === "json") {
|
|
94
|
+
console.log(JSON.stringify({
|
|
95
|
+
query: result.queryString,
|
|
96
|
+
count: result.totalCount,
|
|
97
|
+
results: result.entries.map(formatEntryJson)
|
|
98
|
+
}, null, 2));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (format === "raw") {
|
|
102
|
+
const lines = formatQueryResultRaw(result);
|
|
103
|
+
for (const line of lines) console.log(line);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
console.log();
|
|
107
|
+
console.log(`Query: ${pc.cyan(result.queryString)}`);
|
|
108
|
+
console.log(`Found: ${pc.bold(String(result.totalCount))} entries`);
|
|
109
|
+
if (result.entries.length > 0) {
|
|
110
|
+
console.log();
|
|
111
|
+
for (const entry of result.entries) console.log(formatEntryDefault(entry));
|
|
112
|
+
}
|
|
113
|
+
if (limit && result.totalCount > limit) {
|
|
114
|
+
console.log();
|
|
115
|
+
console.log(pc.dim(`(showing ${limit} of ${result.totalCount} results)`));
|
|
116
|
+
}
|
|
117
|
+
console.log();
|
|
118
|
+
}
|
|
119
|
+
const queryCommand = {
|
|
120
|
+
name: "query",
|
|
121
|
+
description: "Query entries by entity type, tags, links, or metadata",
|
|
122
|
+
usage: "\"<query>\" [paths...]",
|
|
123
|
+
args: {
|
|
124
|
+
name: "query",
|
|
125
|
+
description: "Query string (e.g., 'lore where #career', 'opinion where subject = \"topic\"')",
|
|
126
|
+
required: true,
|
|
127
|
+
multiple: true
|
|
128
|
+
},
|
|
129
|
+
options: {
|
|
130
|
+
format: {
|
|
131
|
+
type: "string",
|
|
132
|
+
short: "f",
|
|
133
|
+
description: "Output format",
|
|
134
|
+
choices: [
|
|
135
|
+
"default",
|
|
136
|
+
"json",
|
|
137
|
+
"raw"
|
|
138
|
+
],
|
|
139
|
+
default: "default"
|
|
140
|
+
},
|
|
141
|
+
json: {
|
|
142
|
+
type: "boolean",
|
|
143
|
+
description: "Output as JSON (shorthand for --format json)",
|
|
144
|
+
default: false
|
|
145
|
+
},
|
|
146
|
+
limit: {
|
|
147
|
+
type: "string",
|
|
148
|
+
short: "n",
|
|
149
|
+
description: "Maximum number of results to show"
|
|
150
|
+
},
|
|
151
|
+
since: {
|
|
152
|
+
type: "string",
|
|
153
|
+
short: "s",
|
|
154
|
+
description: "Only show entries since checkpoint (ts:2026-01-10T15:00Z or git:abc123)"
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
action: queryAction
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
//#endregion
|
|
161
|
+
export { queryCommand };
|
|
162
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","names":["lines: string[]","queryCommand: CommandDef"],"sources":["../../src/commands/query.ts"],"sourcesContent":["import {\n runQuery,\n formatQueryResultRaw,\n isQueryValidationError,\n isCheckpointError,\n type QueryEntryInfo,\n} from \"@rejot-dev/thalo\";\nimport { createChangeTracker, NotInGitRepoError } from \"@rejot-dev/thalo/change-tracker/node\";\nimport { parseCheckpoint } from \"@rejot-dev/thalo\";\nimport pc from \"picocolors\";\nimport type { CommandDef, CommandContext } from \"../cli.js\";\nimport { resolveFiles, loadWorkspace, relativePath } from \"../files.js\";\n\ntype OutputFormat = \"default\" | \"json\" | \"raw\";\n\n/**\n * Format entry with colors for default display\n */\nfunction formatEntryDefault(entry: QueryEntryInfo): string {\n const lines: string[] = [];\n const tags = entry.tags.map((t) => `#${t}`).join(\" \");\n\n lines.push(\n `${pc.dim(entry.timestamp)} ${pc.cyan(entry.entity)} ${pc.bold(entry.title)}${entry.linkId ? \" \" + pc.green(`^${entry.linkId}`) : \"\"}${tags ? \" \" + pc.yellow(tags) : \"\"}`,\n );\n lines.push(` ${pc.dim(`${relativePath(entry.file)}:${entry.startLine}-${entry.endLine}`)}`);\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Format entry for JSON output\n */\nfunction formatEntryJson(entry: QueryEntryInfo): object {\n return {\n file: relativePath(entry.file),\n timestamp: entry.timestamp,\n entity: entry.entity,\n title: entry.title,\n tags: entry.tags,\n link: entry.linkId,\n location: {\n startLine: entry.startLine,\n endLine: entry.endLine,\n },\n };\n}\n\nasync function queryAction(ctx: CommandContext): Promise<void> {\n const { options, args } = ctx;\n\n // Get query string\n const queryStr = args[0];\n if (!queryStr) {\n console.error(pc.red(\"Error: Query string is required\"));\n console.error(`\\nUsage: thalo query \"<query>\"`);\n console.error(`\\nExamples:`);\n console.error(` thalo query 'lore'`);\n console.error(` thalo query 'lore where #career'`);\n console.error(` thalo query 'opinion where subject = \"topic\"'`);\n process.exit(2);\n }\n\n // Handle format - --json flag overrides --format option\n const jsonFlag = options[\"json\"] as boolean;\n const format = jsonFlag ? \"json\" : (options[\"format\"] as OutputFormat) || \"default\";\n if (format === \"json\") {\n process.env[\"NO_COLOR\"] = \"1\";\n }\n\n // Handle limit\n const limitStr = options[\"limit\"] as string | undefined;\n const limit = limitStr ? parseInt(limitStr, 10) : undefined;\n\n // Handle since checkpoint\n const since = options[\"since\"] as string | undefined;\n\n // Determine target paths\n const targetPaths = args.slice(1);\n const searchPaths = targetPaths.length > 0 ? targetPaths : [\".\"];\n\n // Collect and load files\n const files = await resolveFiles(searchPaths);\n if (files.length === 0) {\n console.log(\"No .thalo or .md files found.\");\n process.exit(0);\n }\n\n const workspace = await loadWorkspace(files);\n\n // Create tracker if git checkpoint is used\n let tracker;\n if (since) {\n const marker = parseCheckpoint(since);\n if (marker?.type === \"git\") {\n try {\n tracker = await createChangeTracker({ cwd: process.cwd() });\n } catch (err) {\n if (err instanceof NotInGitRepoError) {\n console.error(\n pc.red(`Error: Cannot use git checkpoint \"${since}\" - not in a git repository`),\n );\n console.error(pc.dim(`Directory: ${err.cwd}`));\n } else {\n const message = err instanceof Error ? err.message : String(err);\n console.error(pc.red(`Error: Failed to create git tracker for checkpoint \"${since}\"`));\n console.error(pc.dim(message));\n }\n process.exit(2);\n }\n }\n }\n\n // Execute query using shared command\n const result = await runQuery(workspace, queryStr, {\n limit,\n includeRawText: format === \"raw\",\n since,\n tracker,\n });\n\n if (!result) {\n console.error(pc.red(`Error: Invalid query syntax`));\n console.error(pc.dim(`Query: ${queryStr}`));\n process.exit(2);\n }\n\n // Handle validation errors\n if (isQueryValidationError(result)) {\n console.error(pc.red(`Error: ${result.message}`));\n process.exit(2);\n }\n\n // Handle checkpoint errors\n if (isCheckpointError(result)) {\n console.error(pc.red(`Error: ${result.message}`));\n process.exit(2);\n }\n\n // Output results\n if (format === \"json\") {\n console.log(\n JSON.stringify(\n {\n query: result.queryString,\n count: result.totalCount,\n results: result.entries.map(formatEntryJson),\n },\n null,\n 2,\n ),\n );\n return;\n }\n\n if (format === \"raw\") {\n // Output raw entry text using shared formatter\n const lines = formatQueryResultRaw(result);\n for (const line of lines) {\n console.log(line);\n }\n return;\n }\n\n // Default format with colors\n console.log();\n console.log(`Query: ${pc.cyan(result.queryString)}`);\n console.log(`Found: ${pc.bold(String(result.totalCount))} entries`);\n\n if (result.entries.length > 0) {\n console.log();\n for (const entry of result.entries) {\n console.log(formatEntryDefault(entry));\n }\n }\n\n if (limit && result.totalCount > limit) {\n console.log();\n console.log(pc.dim(`(showing ${limit} of ${result.totalCount} results)`));\n }\n\n console.log();\n}\n\nexport const queryCommand: CommandDef = {\n name: \"query\",\n description: \"Query entries by entity type, tags, links, or metadata\",\n usage: '\"<query>\" [paths...]',\n args: {\n name: \"query\",\n description: \"Query string (e.g., 'lore where #career', 'opinion where subject = \\\"topic\\\"')\",\n required: true,\n multiple: true,\n },\n options: {\n format: {\n type: \"string\",\n short: \"f\",\n description: \"Output format\",\n choices: [\"default\", \"json\", \"raw\"],\n default: \"default\",\n },\n json: {\n type: \"boolean\",\n description: \"Output as JSON (shorthand for --format json)\",\n default: false,\n },\n limit: {\n type: \"string\",\n short: \"n\",\n description: \"Maximum number of results to show\",\n },\n since: {\n type: \"string\",\n short: \"s\",\n description: \"Only show entries since checkpoint (ts:2026-01-10T15:00Z or git:abc123)\",\n },\n },\n action: queryAction,\n};\n"],"mappings":";;;;;;;;;AAkBA,SAAS,mBAAmB,OAA+B;CACzD,MAAMA,QAAkB,EAAE;CAC1B,MAAM,OAAO,MAAM,KAAK,KAAK,MAAM,IAAI,IAAI,CAAC,KAAK,IAAI;AAErD,OAAM,KACJ,GAAG,GAAG,IAAI,MAAM,UAAU,CAAC,GAAG,GAAG,KAAK,MAAM,OAAO,CAAC,GAAG,GAAG,KAAK,MAAM,MAAM,GAAG,MAAM,SAAS,MAAM,GAAG,MAAM,IAAI,MAAM,SAAS,GAAG,KAAK,OAAO,MAAM,GAAG,OAAO,KAAK,GAAG,KACvK;AACD,OAAM,KAAK,KAAK,GAAG,IAAI,GAAG,aAAa,MAAM,KAAK,CAAC,GAAG,MAAM,UAAU,GAAG,MAAM,UAAU,GAAG;AAE5F,QAAO,MAAM,KAAK,KAAK;;;;;AAMzB,SAAS,gBAAgB,OAA+B;AACtD,QAAO;EACL,MAAM,aAAa,MAAM,KAAK;EAC9B,WAAW,MAAM;EACjB,QAAQ,MAAM;EACd,OAAO,MAAM;EACb,MAAM,MAAM;EACZ,MAAM,MAAM;EACZ,UAAU;GACR,WAAW,MAAM;GACjB,SAAS,MAAM;GAChB;EACF;;AAGH,eAAe,YAAY,KAAoC;CAC7D,MAAM,EAAE,SAAS,SAAS;CAG1B,MAAM,WAAW,KAAK;AACtB,KAAI,CAAC,UAAU;AACb,UAAQ,MAAM,GAAG,IAAI,kCAAkC,CAAC;AACxD,UAAQ,MAAM,iCAAiC;AAC/C,UAAQ,MAAM,cAAc;AAC5B,UAAQ,MAAM,uBAAuB;AACrC,UAAQ,MAAM,qCAAqC;AACnD,UAAQ,MAAM,kDAAkD;AAChE,UAAQ,KAAK,EAAE;;CAKjB,MAAM,SADW,QAAQ,UACC,SAAU,QAAQ,aAA8B;AAC1E,KAAI,WAAW,OACb,SAAQ,IAAI,cAAc;CAI5B,MAAM,WAAW,QAAQ;CACzB,MAAM,QAAQ,WAAW,SAAS,UAAU,GAAG,GAAG;CAGlD,MAAM,QAAQ,QAAQ;CAGtB,MAAM,cAAc,KAAK,MAAM,EAAE;CAIjC,MAAM,QAAQ,MAAM,aAHA,YAAY,SAAS,IAAI,cAAc,CAAC,IAAI,CAGnB;AAC7C,KAAI,MAAM,WAAW,GAAG;AACtB,UAAQ,IAAI,gCAAgC;AAC5C,UAAQ,KAAK,EAAE;;CAGjB,MAAM,YAAY,MAAM,cAAc,MAAM;CAG5C,IAAI;AACJ,KAAI,OAEF;MADe,gBAAgB,MAAM,EACzB,SAAS,MACnB,KAAI;AACF,aAAU,MAAM,oBAAoB,EAAE,KAAK,QAAQ,KAAK,EAAE,CAAC;WACpD,KAAK;AACZ,OAAI,eAAe,mBAAmB;AACpC,YAAQ,MACN,GAAG,IAAI,qCAAqC,MAAM,6BAA6B,CAChF;AACD,YAAQ,MAAM,GAAG,IAAI,cAAc,IAAI,MAAM,CAAC;UACzC;IACL,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,YAAQ,MAAM,GAAG,IAAI,uDAAuD,MAAM,GAAG,CAAC;AACtF,YAAQ,MAAM,GAAG,IAAI,QAAQ,CAAC;;AAEhC,WAAQ,KAAK,EAAE;;;CAMrB,MAAM,SAAS,MAAM,SAAS,WAAW,UAAU;EACjD;EACA,gBAAgB,WAAW;EAC3B;EACA;EACD,CAAC;AAEF,KAAI,CAAC,QAAQ;AACX,UAAQ,MAAM,GAAG,IAAI,8BAA8B,CAAC;AACpD,UAAQ,MAAM,GAAG,IAAI,UAAU,WAAW,CAAC;AAC3C,UAAQ,KAAK,EAAE;;AAIjB,KAAI,uBAAuB,OAAO,EAAE;AAClC,UAAQ,MAAM,GAAG,IAAI,UAAU,OAAO,UAAU,CAAC;AACjD,UAAQ,KAAK,EAAE;;AAIjB,KAAI,kBAAkB,OAAO,EAAE;AAC7B,UAAQ,MAAM,GAAG,IAAI,UAAU,OAAO,UAAU,CAAC;AACjD,UAAQ,KAAK,EAAE;;AAIjB,KAAI,WAAW,QAAQ;AACrB,UAAQ,IACN,KAAK,UACH;GACE,OAAO,OAAO;GACd,OAAO,OAAO;GACd,SAAS,OAAO,QAAQ,IAAI,gBAAgB;GAC7C,EACD,MACA,EACD,CACF;AACD;;AAGF,KAAI,WAAW,OAAO;EAEpB,MAAM,QAAQ,qBAAqB,OAAO;AAC1C,OAAK,MAAM,QAAQ,MACjB,SAAQ,IAAI,KAAK;AAEnB;;AAIF,SAAQ,KAAK;AACb,SAAQ,IAAI,UAAU,GAAG,KAAK,OAAO,YAAY,GAAG;AACpD,SAAQ,IAAI,UAAU,GAAG,KAAK,OAAO,OAAO,WAAW,CAAC,CAAC,UAAU;AAEnE,KAAI,OAAO,QAAQ,SAAS,GAAG;AAC7B,UAAQ,KAAK;AACb,OAAK,MAAM,SAAS,OAAO,QACzB,SAAQ,IAAI,mBAAmB,MAAM,CAAC;;AAI1C,KAAI,SAAS,OAAO,aAAa,OAAO;AACtC,UAAQ,KAAK;AACb,UAAQ,IAAI,GAAG,IAAI,YAAY,MAAM,MAAM,OAAO,WAAW,WAAW,CAAC;;AAG3E,SAAQ,KAAK;;AAGf,MAAaC,eAA2B;CACtC,MAAM;CACN,aAAa;CACb,OAAO;CACP,MAAM;EACJ,MAAM;EACN,aAAa;EACb,UAAU;EACV,UAAU;EACX;CACD,SAAS;EACP,QAAQ;GACN,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;IAAC;IAAW;IAAQ;IAAM;GACnC,SAAS;GACV;EACD,MAAM;GACJ,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACD,OAAO;GACL,MAAM;GACN,OAAO;GACP,aAAa;GACd;EACF;CACD,QAAQ;CACT"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
import { RULE_CATEGORIES, allRules } from "@rejot-dev/thalo";
|
|
3
|
+
|
|
4
|
+
//#region src/commands/rules.ts
|
|
5
|
+
const severityColor = {
|
|
6
|
+
error: pc.red,
|
|
7
|
+
warning: pc.yellow,
|
|
8
|
+
info: pc.cyan
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Group rules by category and sort them
|
|
12
|
+
*/
|
|
13
|
+
function groupRulesByCategory(rules) {
|
|
14
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
15
|
+
const sortedCategories = Object.keys(RULE_CATEGORIES).sort((a, b) => RULE_CATEGORIES[a].order - RULE_CATEGORIES[b].order);
|
|
16
|
+
for (const cat of sortedCategories) grouped.set(cat, []);
|
|
17
|
+
for (const rule of rules) {
|
|
18
|
+
const existing = grouped.get(rule.category) ?? [];
|
|
19
|
+
existing.push(rule);
|
|
20
|
+
grouped.set(rule.category, existing);
|
|
21
|
+
}
|
|
22
|
+
for (const [cat, catRules] of grouped) grouped.set(cat, catRules.sort((a, b) => a.code.localeCompare(b.code)));
|
|
23
|
+
return grouped;
|
|
24
|
+
}
|
|
25
|
+
function listAction(ctx) {
|
|
26
|
+
const { options } = ctx;
|
|
27
|
+
let filteredRules = allRules;
|
|
28
|
+
if (options["severity"]) filteredRules = allRules.filter((r) => r.defaultSeverity === options["severity"]);
|
|
29
|
+
if (options["category"]) filteredRules = filteredRules.filter((r) => r.category === options["category"]);
|
|
30
|
+
if (options["json"]) {
|
|
31
|
+
const output = filteredRules.map((r) => ({
|
|
32
|
+
code: r.code,
|
|
33
|
+
name: r.name,
|
|
34
|
+
description: r.description,
|
|
35
|
+
category: r.category,
|
|
36
|
+
defaultSeverity: r.defaultSeverity
|
|
37
|
+
}));
|
|
38
|
+
console.log(JSON.stringify(output, null, 2));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(pc.bold("Available Rules"));
|
|
43
|
+
const maxCodeLen = Math.max(...filteredRules.map((r) => r.code.length));
|
|
44
|
+
const grouped = groupRulesByCategory(filteredRules);
|
|
45
|
+
for (const [category, rules] of grouped) {
|
|
46
|
+
if (rules.length === 0) continue;
|
|
47
|
+
const { label } = RULE_CATEGORIES[category];
|
|
48
|
+
console.log();
|
|
49
|
+
console.log(pc.cyan(pc.bold(` ${label}`)));
|
|
50
|
+
console.log();
|
|
51
|
+
for (const rule of rules) {
|
|
52
|
+
const color = severityColor[rule.defaultSeverity] ?? pc.dim;
|
|
53
|
+
const code = rule.code.padEnd(maxCodeLen);
|
|
54
|
+
const severity = color(rule.defaultSeverity.padEnd(7));
|
|
55
|
+
console.log(` ${pc.bold(code)} ${severity} ${pc.dim(rule.description)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
console.log();
|
|
59
|
+
console.log(pc.dim(`Total: ${filteredRules.length} rules`));
|
|
60
|
+
console.log();
|
|
61
|
+
}
|
|
62
|
+
const listSubcommand = {
|
|
63
|
+
name: "list",
|
|
64
|
+
description: "List all available rules",
|
|
65
|
+
options: {
|
|
66
|
+
severity: {
|
|
67
|
+
type: "string",
|
|
68
|
+
short: "s",
|
|
69
|
+
description: "Filter by severity level",
|
|
70
|
+
choices: [
|
|
71
|
+
"error",
|
|
72
|
+
"warning",
|
|
73
|
+
"info"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
category: {
|
|
77
|
+
type: "string",
|
|
78
|
+
short: "c",
|
|
79
|
+
description: "Filter by category",
|
|
80
|
+
choices: [
|
|
81
|
+
"instance",
|
|
82
|
+
"link",
|
|
83
|
+
"schema",
|
|
84
|
+
"metadata",
|
|
85
|
+
"content"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
json: {
|
|
89
|
+
type: "boolean",
|
|
90
|
+
description: "Output as JSON",
|
|
91
|
+
default: false
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
action: listAction
|
|
95
|
+
};
|
|
96
|
+
const rulesCommand = {
|
|
97
|
+
name: "rules",
|
|
98
|
+
description: "Manage and inspect linting rules",
|
|
99
|
+
subcommands: { list: listSubcommand }
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
//#endregion
|
|
103
|
+
export { rulesCommand };
|
|
104
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","names":["listSubcommand: CommandDef","rulesCommand: CommandDef"],"sources":["../../src/commands/rules.ts"],"sourcesContent":["import { allRules, RULE_CATEGORIES, type RuleCategory, type Rule } from \"@rejot-dev/thalo\";\nimport pc from \"picocolors\";\nimport type { CommandDef, CommandContext } from \"../cli.js\";\n\ntype SeverityKey = \"error\" | \"warning\" | \"info\";\n\nconst severityColor = {\n error: pc.red,\n warning: pc.yellow,\n info: pc.cyan,\n} as const;\n\n/**\n * Group rules by category and sort them\n */\nfunction groupRulesByCategory(rules: Rule[]): Map<RuleCategory, Rule[]> {\n const grouped = new Map<RuleCategory, Rule[]>();\n\n // Initialize groups in order\n const sortedCategories = (Object.keys(RULE_CATEGORIES) as RuleCategory[]).sort(\n (a, b) => RULE_CATEGORIES[a].order - RULE_CATEGORIES[b].order,\n );\n\n for (const cat of sortedCategories) {\n grouped.set(cat, []);\n }\n\n // Group rules\n for (const rule of rules) {\n const existing = grouped.get(rule.category) ?? [];\n existing.push(rule);\n grouped.set(rule.category, existing);\n }\n\n // Sort rules within each category alphabetically by code\n for (const [cat, catRules] of grouped) {\n grouped.set(\n cat,\n catRules.sort((a, b) => a.code.localeCompare(b.code)),\n );\n }\n\n return grouped;\n}\n\nfunction listAction(ctx: CommandContext): void {\n const { options } = ctx;\n\n // Filter by severity if specified\n let filteredRules = allRules;\n if (options[\"severity\"]) {\n filteredRules = allRules.filter((r) => r.defaultSeverity === options[\"severity\"]);\n }\n\n // Filter by category if specified\n if (options[\"category\"]) {\n filteredRules = filteredRules.filter((r) => r.category === options[\"category\"]);\n }\n\n if (options[\"json\"]) {\n const output = filteredRules.map((r) => ({\n code: r.code,\n name: r.name,\n description: r.description,\n category: r.category,\n defaultSeverity: r.defaultSeverity,\n }));\n console.log(JSON.stringify(output, null, 2));\n return;\n }\n\n console.log();\n console.log(pc.bold(\"Available Rules\"));\n\n const maxCodeLen = Math.max(...filteredRules.map((r) => r.code.length));\n const grouped = groupRulesByCategory(filteredRules);\n\n for (const [category, rules] of grouped) {\n if (rules.length === 0) {\n continue;\n }\n\n const { label } = RULE_CATEGORIES[category];\n console.log();\n console.log(pc.cyan(pc.bold(` ${label}`)));\n console.log();\n\n for (const rule of rules) {\n const color = severityColor[rule.defaultSeverity as SeverityKey] ?? pc.dim;\n const code = rule.code.padEnd(maxCodeLen);\n const severity = color(rule.defaultSeverity.padEnd(7));\n console.log(` ${pc.bold(code)} ${severity} ${pc.dim(rule.description)}`);\n }\n }\n\n console.log();\n console.log(pc.dim(`Total: ${filteredRules.length} rules`));\n console.log();\n}\n\nconst listSubcommand: CommandDef = {\n name: \"list\",\n description: \"List all available rules\",\n options: {\n severity: {\n type: \"string\",\n short: \"s\",\n description: \"Filter by severity level\",\n choices: [\"error\", \"warning\", \"info\"],\n },\n category: {\n type: \"string\",\n short: \"c\",\n description: \"Filter by category\",\n choices: [\"instance\", \"link\", \"schema\", \"metadata\", \"content\"],\n },\n json: {\n type: \"boolean\",\n description: \"Output as JSON\",\n default: false,\n },\n },\n action: listAction,\n};\n\nexport const rulesCommand: CommandDef = {\n name: \"rules\",\n description: \"Manage and inspect linting rules\",\n subcommands: {\n list: listSubcommand,\n },\n};\n"],"mappings":";;;;AAMA,MAAM,gBAAgB;CACpB,OAAO,GAAG;CACV,SAAS,GAAG;CACZ,MAAM,GAAG;CACV;;;;AAKD,SAAS,qBAAqB,OAA0C;CACtE,MAAM,0BAAU,IAAI,KAA2B;CAG/C,MAAM,mBAAoB,OAAO,KAAK,gBAAgB,CAAoB,MACvE,GAAG,MAAM,gBAAgB,GAAG,QAAQ,gBAAgB,GAAG,MACzD;AAED,MAAK,MAAM,OAAO,iBAChB,SAAQ,IAAI,KAAK,EAAE,CAAC;AAItB,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,WAAW,QAAQ,IAAI,KAAK,SAAS,IAAI,EAAE;AACjD,WAAS,KAAK,KAAK;AACnB,UAAQ,IAAI,KAAK,UAAU,SAAS;;AAItC,MAAK,MAAM,CAAC,KAAK,aAAa,QAC5B,SAAQ,IACN,KACA,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC,CACtD;AAGH,QAAO;;AAGT,SAAS,WAAW,KAA2B;CAC7C,MAAM,EAAE,YAAY;CAGpB,IAAI,gBAAgB;AACpB,KAAI,QAAQ,YACV,iBAAgB,SAAS,QAAQ,MAAM,EAAE,oBAAoB,QAAQ,YAAY;AAInF,KAAI,QAAQ,YACV,iBAAgB,cAAc,QAAQ,MAAM,EAAE,aAAa,QAAQ,YAAY;AAGjF,KAAI,QAAQ,SAAS;EACnB,MAAM,SAAS,cAAc,KAAK,OAAO;GACvC,MAAM,EAAE;GACR,MAAM,EAAE;GACR,aAAa,EAAE;GACf,UAAU,EAAE;GACZ,iBAAiB,EAAE;GACpB,EAAE;AACH,UAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,EAAE,CAAC;AAC5C;;AAGF,SAAQ,KAAK;AACb,SAAQ,IAAI,GAAG,KAAK,kBAAkB,CAAC;CAEvC,MAAM,aAAa,KAAK,IAAI,GAAG,cAAc,KAAK,MAAM,EAAE,KAAK,OAAO,CAAC;CACvE,MAAM,UAAU,qBAAqB,cAAc;AAEnD,MAAK,MAAM,CAAC,UAAU,UAAU,SAAS;AACvC,MAAI,MAAM,WAAW,EACnB;EAGF,MAAM,EAAE,UAAU,gBAAgB;AAClC,UAAQ,KAAK;AACb,UAAQ,IAAI,GAAG,KAAK,GAAG,KAAK,KAAK,QAAQ,CAAC,CAAC;AAC3C,UAAQ,KAAK;AAEb,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,QAAQ,cAAc,KAAK,oBAAmC,GAAG;GACvE,MAAM,OAAO,KAAK,KAAK,OAAO,WAAW;GACzC,MAAM,WAAW,MAAM,KAAK,gBAAgB,OAAO,EAAE,CAAC;AACtD,WAAQ,IAAI,OAAO,GAAG,KAAK,KAAK,CAAC,IAAI,SAAS,IAAI,GAAG,IAAI,KAAK,YAAY,GAAG;;;AAIjF,SAAQ,KAAK;AACb,SAAQ,IAAI,GAAG,IAAI,UAAU,cAAc,OAAO,QAAQ,CAAC;AAC3D,SAAQ,KAAK;;AAGf,MAAMA,iBAA6B;CACjC,MAAM;CACN,aAAa;CACb,SAAS;EACP,UAAU;GACR,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;IAAC;IAAS;IAAW;IAAO;GACtC;EACD,UAAU;GACR,MAAM;GACN,OAAO;GACP,aAAa;GACb,SAAS;IAAC;IAAY;IAAQ;IAAU;IAAY;IAAU;GAC/D;EACD,MAAM;GACJ,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACF;CACD,QAAQ;CACT;AAED,MAAaC,eAA2B;CACtC,MAAM;CACN,aAAa;CACb,aAAa,EACX,MAAM,gBACP;CACF"}
|