@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.
@@ -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"}