@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
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 - present ReJot Nederland B.V., and individual contributors.
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# thalo-cli
|
|
2
|
+
|
|
3
|
+
A command-line linter for thalo files. Runs diagnostics on `.thalo` and `.md` files containing thalo
|
|
4
|
+
syntax.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
# From the monorepo root
|
|
10
|
+
pnpm install
|
|
11
|
+
pnpm exec turbo run build --filter=@rejot-dev/thalo-cli
|
|
12
|
+
|
|
13
|
+
# Run directly
|
|
14
|
+
node apps/thalo-cli/dist/mod.js --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
thalo [options] [files or directories...]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Basic Examples
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Check all files in a directory
|
|
27
|
+
thalo notes/
|
|
28
|
+
|
|
29
|
+
# Check specific files
|
|
30
|
+
thalo file.thalo journal.md
|
|
31
|
+
|
|
32
|
+
# Check current directory
|
|
33
|
+
thalo
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Options
|
|
37
|
+
|
|
38
|
+
| Option | Description |
|
|
39
|
+
| --------------------- | ------------------------------------------------------ |
|
|
40
|
+
| `-h, --help` | Show help message |
|
|
41
|
+
| `-v, --version` | Show version number |
|
|
42
|
+
| `-q, --quiet` | Only show errors, suppress warnings and info |
|
|
43
|
+
| `-f, --format <fmt>` | Output format: `default`, `json`, `compact`, `github` |
|
|
44
|
+
| `--no-color` | Disable colored output |
|
|
45
|
+
| `--severity <level>` | Minimum severity to report: `error`, `warning`, `info` |
|
|
46
|
+
| `--max-warnings <n>` | Exit with error if warnings exceed threshold |
|
|
47
|
+
| `--rule <rule>=<sev>` | Set rule severity (can be repeated) |
|
|
48
|
+
| `--list-rules` | List all available rules |
|
|
49
|
+
| `-w, --watch` | Watch files for changes and re-run |
|
|
50
|
+
|
|
51
|
+
## Output Formats
|
|
52
|
+
|
|
53
|
+
### Default
|
|
54
|
+
|
|
55
|
+
Colored output with file location, severity, rule code, and message:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
/path/to/file.md:1:1 ERROR [unknown-entity] Unknown entity type 'lore'.
|
|
59
|
+
/path/to/file.md:3:11 WARNING [unresolved-link] Unresolved link '^self'.
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### JSON (`-f json`)
|
|
63
|
+
|
|
64
|
+
Structured JSON output for tooling integration:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"files": 11,
|
|
69
|
+
"issues": 70,
|
|
70
|
+
"errors": 54,
|
|
71
|
+
"warnings": 16,
|
|
72
|
+
"info": 0,
|
|
73
|
+
"diagnostics": [
|
|
74
|
+
{
|
|
75
|
+
"file": "/path/to/file.md",
|
|
76
|
+
"line": 1,
|
|
77
|
+
"column": 1,
|
|
78
|
+
"endLine": 9,
|
|
79
|
+
"endColumn": 1,
|
|
80
|
+
"severity": "error",
|
|
81
|
+
"code": "unknown-entity",
|
|
82
|
+
"message": "Unknown entity type 'lore'."
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Compact (`-f compact`)
|
|
89
|
+
|
|
90
|
+
Minimal single-line format:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
/path/to/file.md:1:1: E [unknown-entity] Unknown entity type 'lore'.
|
|
94
|
+
/path/to/file.md:3:11: W [unresolved-link] Unresolved link '^self'.
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### GitHub Actions (`-f github`)
|
|
98
|
+
|
|
99
|
+
Workflow commands for GitHub Actions annotations:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
::error file=/path/to/file.md,line=1,col=1,endLine=9,endColumn=1,title=unknown-entity::Unknown entity type 'lore'.
|
|
103
|
+
::warning file=/path/to/file.md,line=3,col=11,endLine=3,endColumn=17,title=unresolved-link::Unresolved link '^self'.
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Rules
|
|
107
|
+
|
|
108
|
+
List all available rules with `--list-rules`:
|
|
109
|
+
|
|
110
|
+
| Rule | Default | Description |
|
|
111
|
+
| -------------------------- | ------- | -------------------------------- |
|
|
112
|
+
| `unknown-entity` | error | Unknown entity type used |
|
|
113
|
+
| `missing-required-field` | error | Required metadata field missing |
|
|
114
|
+
| `unknown-field` | warning | Unknown metadata field used |
|
|
115
|
+
| `invalid-field-type` | error | Field value has wrong type |
|
|
116
|
+
| `missing-required-section` | error | Required section missing |
|
|
117
|
+
| `unknown-section` | warning | Unknown section used |
|
|
118
|
+
| `unresolved-link` | warning | Link reference not found |
|
|
119
|
+
| `duplicate-link-id` | error | Same link ID used multiple times |
|
|
120
|
+
|
|
121
|
+
### Configuring Rules
|
|
122
|
+
|
|
123
|
+
Override rule severities with `--rule`:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# Disable a rule
|
|
127
|
+
thalo --rule unknown-entity=off notes/
|
|
128
|
+
|
|
129
|
+
# Change severity
|
|
130
|
+
thalo --rule unresolved-link=error notes/
|
|
131
|
+
|
|
132
|
+
# Multiple rules
|
|
133
|
+
thalo --rule unknown-entity=off --rule unknown-field=off notes/
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Valid severities: `error`, `warning`, `info`, `off`
|
|
137
|
+
|
|
138
|
+
## CI Integration
|
|
139
|
+
|
|
140
|
+
### GitHub Actions
|
|
141
|
+
|
|
142
|
+
```yaml
|
|
143
|
+
- name: Lint thalo files
|
|
144
|
+
run: thalo -f github notes/
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Warning Threshold
|
|
148
|
+
|
|
149
|
+
Fail if too many warnings:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
thalo --max-warnings 10 notes/
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### JSON Report
|
|
156
|
+
|
|
157
|
+
Generate a report file:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
thalo -f json notes/ > thalo-report.json
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Watch Mode
|
|
164
|
+
|
|
165
|
+
Re-run on file changes:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
thalo -w notes/
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Press `Ctrl+C` to exit.
|
|
172
|
+
|
|
173
|
+
## Exit Codes
|
|
174
|
+
|
|
175
|
+
| Code | Meaning |
|
|
176
|
+
| ---- | ------------------------------------- |
|
|
177
|
+
| `0` | No errors |
|
|
178
|
+
| `1` | Errors found or max warnings exceeded |
|
|
179
|
+
| `2` | Invalid arguments |
|
package/bin/run.js
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
|
|
4
|
+
//#region src/cli.ts
|
|
5
|
+
const VERSION = "0.1.0";
|
|
6
|
+
/**
|
|
7
|
+
* Convert our option definitions to node:util parseArgs config
|
|
8
|
+
*/
|
|
9
|
+
function toParseArgsConfig(options) {
|
|
10
|
+
const config = {};
|
|
11
|
+
for (const [name, def] of Object.entries(options)) {
|
|
12
|
+
const optConfig = { type: def.type };
|
|
13
|
+
if (def.short) optConfig.short = def.short;
|
|
14
|
+
if (def.default !== void 0) optConfig.default = def.default;
|
|
15
|
+
if (def.multiple !== void 0) optConfig.multiple = def.multiple;
|
|
16
|
+
config[name] = optConfig;
|
|
17
|
+
}
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Global options available to all commands
|
|
22
|
+
*/
|
|
23
|
+
const globalOptions = {
|
|
24
|
+
help: {
|
|
25
|
+
type: "boolean",
|
|
26
|
+
short: "h",
|
|
27
|
+
description: "Show help for the command",
|
|
28
|
+
default: false
|
|
29
|
+
},
|
|
30
|
+
version: {
|
|
31
|
+
type: "boolean",
|
|
32
|
+
short: "V",
|
|
33
|
+
description: "Show version number",
|
|
34
|
+
default: false
|
|
35
|
+
},
|
|
36
|
+
"no-color": {
|
|
37
|
+
type: "boolean",
|
|
38
|
+
description: "Disable colored output",
|
|
39
|
+
default: false
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Generate help text for a command
|
|
44
|
+
*/
|
|
45
|
+
function generateHelp(command, commandPath = []) {
|
|
46
|
+
const lines = [];
|
|
47
|
+
const fullCommandName = commandPath.length > 0 ? commandPath.join(" ") : "thalo";
|
|
48
|
+
lines.push("");
|
|
49
|
+
lines.push(pc.bold(command.description));
|
|
50
|
+
lines.push("");
|
|
51
|
+
lines.push(pc.bold("USAGE"));
|
|
52
|
+
let usageLine = ` ${fullCommandName}`;
|
|
53
|
+
if (command.subcommands && Object.keys(command.subcommands).length > 0) usageLine += " <command>";
|
|
54
|
+
if (command.options && Object.keys(command.options).length > 0) usageLine += " [options]";
|
|
55
|
+
if (command.args) {
|
|
56
|
+
const argName = command.args.multiple ? `<${command.args.name}...>` : `<${command.args.name}>`;
|
|
57
|
+
usageLine += command.args.required ? ` ${argName}` : ` [${argName}]`;
|
|
58
|
+
}
|
|
59
|
+
if (command.usage) usageLine = ` ${fullCommandName} ${command.usage}`;
|
|
60
|
+
lines.push(usageLine);
|
|
61
|
+
lines.push("");
|
|
62
|
+
if (command.subcommands && Object.keys(command.subcommands).length > 0) {
|
|
63
|
+
lines.push(pc.bold("COMMANDS"));
|
|
64
|
+
const subcommandNames = Object.keys(command.subcommands);
|
|
65
|
+
const maxLen = Math.max(...subcommandNames.map((n) => n.length));
|
|
66
|
+
for (const [name, subcmd] of Object.entries(command.subcommands)) {
|
|
67
|
+
const paddedName = name.padEnd(maxLen + 2);
|
|
68
|
+
lines.push(` ${pc.cyan(paddedName)}${subcmd.description}`);
|
|
69
|
+
}
|
|
70
|
+
lines.push("");
|
|
71
|
+
lines.push(` Run '${fullCommandName} <command> --help' for more information on a command.`);
|
|
72
|
+
lines.push("");
|
|
73
|
+
}
|
|
74
|
+
const allOptions = {
|
|
75
|
+
...globalOptions,
|
|
76
|
+
...command.options
|
|
77
|
+
};
|
|
78
|
+
if (Object.keys(allOptions).length > 0) {
|
|
79
|
+
lines.push(pc.bold("OPTIONS"));
|
|
80
|
+
const optionEntries = Object.entries(allOptions);
|
|
81
|
+
const optionStrings = optionEntries.map(([name, def]) => {
|
|
82
|
+
return `${def.short ? `-${def.short}, ` : " "}${`--${name}`}${def.type === "string" ? ` <${def.choices ? def.choices.join("|") : "value"}>` : ""}`;
|
|
83
|
+
});
|
|
84
|
+
const maxOptLen = Math.max(...optionStrings.map((s) => s.length));
|
|
85
|
+
for (let i = 0; i < optionEntries.length; i++) {
|
|
86
|
+
const [, def] = optionEntries[i];
|
|
87
|
+
const optStr = optionStrings[i].padEnd(maxOptLen + 2);
|
|
88
|
+
let description = def.description;
|
|
89
|
+
if (def.default !== void 0 && def.default !== false && def.default !== "") description += pc.dim(` (default: ${def.default})`);
|
|
90
|
+
lines.push(` ${pc.dim(optStr)}${description}`);
|
|
91
|
+
}
|
|
92
|
+
lines.push("");
|
|
93
|
+
}
|
|
94
|
+
return lines.join("\n");
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Find a command by traversing the path
|
|
98
|
+
*/
|
|
99
|
+
function findCommand(root, args) {
|
|
100
|
+
let current = root;
|
|
101
|
+
const commandPath = ["thalo"];
|
|
102
|
+
const remainingArgs = [...args];
|
|
103
|
+
while (remainingArgs.length > 0) {
|
|
104
|
+
const nextArg = remainingArgs[0];
|
|
105
|
+
if (nextArg.startsWith("-")) break;
|
|
106
|
+
if (current.subcommands && current.subcommands[nextArg]) {
|
|
107
|
+
current = current.subcommands[nextArg];
|
|
108
|
+
commandPath.push(nextArg);
|
|
109
|
+
remainingArgs.shift();
|
|
110
|
+
} else break;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
command: current,
|
|
114
|
+
commandPath,
|
|
115
|
+
remainingArgs
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Parse and run a command
|
|
120
|
+
*/
|
|
121
|
+
function runCli(rootCommand, argv = process.argv.slice(2)) {
|
|
122
|
+
const { command, commandPath, remainingArgs } = findCommand(rootCommand, argv);
|
|
123
|
+
const parseArgsOptions = toParseArgsConfig({
|
|
124
|
+
...globalOptions,
|
|
125
|
+
...command.options
|
|
126
|
+
});
|
|
127
|
+
let parsed;
|
|
128
|
+
try {
|
|
129
|
+
parsed = parseArgs({
|
|
130
|
+
args: remainingArgs,
|
|
131
|
+
options: parseArgsOptions,
|
|
132
|
+
allowPositionals: true,
|
|
133
|
+
strict: true
|
|
134
|
+
});
|
|
135
|
+
} catch (err) {
|
|
136
|
+
if (err instanceof Error) {
|
|
137
|
+
console.error(pc.red(`Error: ${err.message}`));
|
|
138
|
+
console.error(`\nRun '${commandPath.join(" ")} --help' for usage information.`);
|
|
139
|
+
}
|
|
140
|
+
process.exit(2);
|
|
141
|
+
}
|
|
142
|
+
const { values, positionals } = parsed;
|
|
143
|
+
if (values["no-color"]) process.env["NO_COLOR"] = "1";
|
|
144
|
+
if (values["version"] && commandPath.length === 1) {
|
|
145
|
+
console.log(`thalo v${VERSION}`);
|
|
146
|
+
process.exit(0);
|
|
147
|
+
}
|
|
148
|
+
if (values["help"]) {
|
|
149
|
+
console.log(generateHelp(command, commandPath));
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
if (command.subcommands && Object.keys(command.subcommands).length > 0 && !command.action) {
|
|
153
|
+
console.log(generateHelp(command, commandPath));
|
|
154
|
+
process.exit(0);
|
|
155
|
+
}
|
|
156
|
+
if (command.action) {
|
|
157
|
+
const ctx = {
|
|
158
|
+
options: values,
|
|
159
|
+
args: positionals,
|
|
160
|
+
command,
|
|
161
|
+
commandPath
|
|
162
|
+
};
|
|
163
|
+
Promise.resolve(command.action(ctx)).catch((err) => {
|
|
164
|
+
console.error(pc.red(`Error: ${err instanceof Error ? err.message : err}`));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
});
|
|
167
|
+
} else {
|
|
168
|
+
console.log(generateHelp(command, commandPath));
|
|
169
|
+
process.exit(0);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
//#endregion
|
|
174
|
+
export { runCli };
|
|
175
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","names":["config: ParseArgsConfig[\"options\"]","optConfig: NonNullable<ParseArgsConfig[\"options\"]>[string]","globalOptions: Record<string, OptionDef>","lines: string[]","parsed: ReturnType<typeof parseArgs>","ctx: CommandContext"],"sources":["../src/cli.ts"],"sourcesContent":["import { parseArgs, type ParseArgsConfig } from \"node:util\";\nimport pc from \"picocolors\";\n\nconst VERSION = \"0.1.0\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Option definition for a command\n */\nexport interface OptionDef {\n type: \"boolean\" | \"string\";\n short?: string;\n description: string;\n default?: string | boolean;\n choices?: string[];\n multiple?: boolean;\n}\n\n/**\n * Command definition\n */\nexport interface CommandDef {\n name: string;\n description: string;\n usage?: string;\n options?: Record<string, OptionDef>;\n args?: {\n name: string;\n description: string;\n required?: boolean;\n multiple?: boolean;\n };\n subcommands?: Record<string, CommandDef>;\n action?: (ctx: CommandContext) => void | Promise<void>;\n}\n\n/**\n * Parsed command context passed to action handlers\n */\nexport interface CommandContext {\n options: Record<string, string | boolean | string[]>;\n args: string[];\n command: CommandDef;\n commandPath: string[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Framework\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Convert our option definitions to node:util parseArgs config\n */\nfunction toParseArgsConfig(options: Record<string, OptionDef>): ParseArgsConfig[\"options\"] {\n const config: ParseArgsConfig[\"options\"] = {};\n\n for (const [name, def] of Object.entries(options)) {\n const optConfig: NonNullable<ParseArgsConfig[\"options\"]>[string] = {\n type: def.type,\n };\n\n if (def.short) {\n optConfig.short = def.short;\n }\n\n if (def.default !== undefined) {\n optConfig.default = def.default;\n }\n\n if (def.multiple !== undefined) {\n optConfig.multiple = def.multiple;\n }\n\n config[name] = optConfig;\n }\n\n return config;\n}\n\n/**\n * Global options available to all commands\n */\nconst globalOptions: Record<string, OptionDef> = {\n help: {\n type: \"boolean\",\n short: \"h\",\n description: \"Show help for the command\",\n default: false,\n },\n version: {\n type: \"boolean\",\n short: \"V\",\n description: \"Show version number\",\n default: false,\n },\n \"no-color\": {\n type: \"boolean\",\n description: \"Disable colored output\",\n default: false,\n },\n};\n\n/**\n * Generate help text for a command\n */\nexport function generateHelp(command: CommandDef, commandPath: string[] = []): string {\n const lines: string[] = [];\n const fullCommandName = commandPath.length > 0 ? commandPath.join(\" \") : \"thalo\";\n\n // Header\n lines.push(\"\");\n lines.push(pc.bold(command.description));\n lines.push(\"\");\n\n // Usage\n lines.push(pc.bold(\"USAGE\"));\n let usageLine = ` ${fullCommandName}`;\n\n if (command.subcommands && Object.keys(command.subcommands).length > 0) {\n usageLine += \" <command>\";\n }\n\n if (command.options && Object.keys(command.options).length > 0) {\n usageLine += \" [options]\";\n }\n\n if (command.args) {\n const argName = command.args.multiple ? `<${command.args.name}...>` : `<${command.args.name}>`;\n usageLine += command.args.required ? ` ${argName}` : ` [${argName}]`;\n }\n\n if (command.usage) {\n usageLine = ` ${fullCommandName} ${command.usage}`;\n }\n\n lines.push(usageLine);\n lines.push(\"\");\n\n // Subcommands\n if (command.subcommands && Object.keys(command.subcommands).length > 0) {\n lines.push(pc.bold(\"COMMANDS\"));\n\n const subcommandNames = Object.keys(command.subcommands);\n const maxLen = Math.max(...subcommandNames.map((n) => n.length));\n\n for (const [name, subcmd] of Object.entries(command.subcommands)) {\n const paddedName = name.padEnd(maxLen + 2);\n lines.push(` ${pc.cyan(paddedName)}${subcmd.description}`);\n }\n\n lines.push(\"\");\n lines.push(` Run '${fullCommandName} <command> --help' for more information on a command.`);\n lines.push(\"\");\n }\n\n // Options\n const allOptions = { ...globalOptions, ...command.options };\n if (Object.keys(allOptions).length > 0) {\n lines.push(pc.bold(\"OPTIONS\"));\n\n const optionEntries = Object.entries(allOptions);\n const optionStrings = optionEntries.map(([name, def]) => {\n const shortFlag = def.short ? `-${def.short}, ` : \" \";\n const longFlag = `--${name}`;\n const valueHint =\n def.type === \"string\" ? ` <${def.choices ? def.choices.join(\"|\") : \"value\"}>` : \"\";\n return `${shortFlag}${longFlag}${valueHint}`;\n });\n const maxOptLen = Math.max(...optionStrings.map((s) => s.length));\n\n for (let i = 0; i < optionEntries.length; i++) {\n const [, def] = optionEntries[i];\n const optStr = optionStrings[i].padEnd(maxOptLen + 2);\n let description = def.description;\n\n if (def.default !== undefined && def.default !== false && def.default !== \"\") {\n description += pc.dim(` (default: ${def.default})`);\n }\n\n lines.push(` ${pc.dim(optStr)}${description}`);\n }\n\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Find a command by traversing the path\n */\nfunction findCommand(\n root: CommandDef,\n args: string[],\n): { command: CommandDef; commandPath: string[]; remainingArgs: string[] } {\n let current = root;\n const commandPath = [\"thalo\"];\n const remainingArgs = [...args];\n\n while (remainingArgs.length > 0) {\n const nextArg = remainingArgs[0];\n\n // Stop if it's an option\n if (nextArg.startsWith(\"-\")) {\n break;\n }\n\n // Check if it's a subcommand\n if (current.subcommands && current.subcommands[nextArg]) {\n current = current.subcommands[nextArg];\n commandPath.push(nextArg);\n remainingArgs.shift();\n } else {\n // Not a subcommand, must be a positional arg\n break;\n }\n }\n\n return { command: current, commandPath, remainingArgs };\n}\n\n/**\n * Parse and run a command\n */\nexport function runCli(rootCommand: CommandDef, argv: string[] = process.argv.slice(2)): void {\n // Find the target command\n const { command, commandPath, remainingArgs } = findCommand(rootCommand, argv);\n\n // Merge global options with command options\n const allOptions = { ...globalOptions, ...command.options };\n const parseArgsOptions = toParseArgsConfig(allOptions);\n\n // Parse arguments\n let parsed: ReturnType<typeof parseArgs>;\n try {\n parsed = parseArgs({\n args: remainingArgs,\n options: parseArgsOptions,\n allowPositionals: true,\n strict: true,\n });\n } catch (err) {\n if (err instanceof Error) {\n console.error(pc.red(`Error: ${err.message}`));\n console.error(`\\nRun '${commandPath.join(\" \")} --help' for usage information.`);\n }\n process.exit(2);\n }\n\n const { values, positionals } = parsed;\n\n // Handle no-color by setting env var (picocolors respects NO_COLOR)\n if (values[\"no-color\"]) {\n process.env[\"NO_COLOR\"] = \"1\";\n }\n\n // Handle version (only at root level)\n if (values[\"version\"] && commandPath.length === 1) {\n console.log(`thalo v${VERSION}`);\n process.exit(0);\n }\n\n // Handle help\n if (values[\"help\"]) {\n console.log(generateHelp(command, commandPath));\n process.exit(0);\n }\n\n // If command has subcommands but no action and no subcommand was specified, show help\n if (command.subcommands && Object.keys(command.subcommands).length > 0 && !command.action) {\n console.log(generateHelp(command, commandPath));\n process.exit(0);\n }\n\n // Run the command action\n if (command.action) {\n const ctx: CommandContext = {\n options: values as Record<string, string | boolean | string[]>,\n args: positionals,\n command,\n commandPath,\n };\n\n Promise.resolve(command.action(ctx)).catch((err) => {\n console.error(pc.red(`Error: ${err instanceof Error ? err.message : err}`));\n process.exit(1);\n });\n } else {\n console.log(generateHelp(command, commandPath));\n process.exit(0);\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { loadFullWorkspace, relativePath } from "../files.js";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import { DEFAULT_INSTRUCTIONS_TEMPLATE, generateInstructions, generateTimestamp, runActualize } from "@rejot-dev/thalo";
|
|
4
|
+
import { formatCheckpoint } from "@rejot-dev/thalo/change-tracker";
|
|
5
|
+
import { UncommittedChangesError, createChangeTracker } from "@rejot-dev/thalo/change-tracker/node";
|
|
6
|
+
|
|
7
|
+
//#region src/commands/actualize.ts
|
|
8
|
+
/**
|
|
9
|
+
* Format a synthesis that is up to date.
|
|
10
|
+
*/
|
|
11
|
+
function formatUpToDate(synthesis) {
|
|
12
|
+
return pc.green(`✓ ${relativePath(synthesis.file)}: ${synthesis.title} - up to date`);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Format a synthesis with pending entries.
|
|
16
|
+
*/
|
|
17
|
+
function formatSynthesisWithEntries(synthesis, instructionsTemplate) {
|
|
18
|
+
const lines = [];
|
|
19
|
+
lines.push("");
|
|
20
|
+
lines.push(pc.bold(pc.cyan(`=== Synthesis: ${synthesis.title} (${relativePath(synthesis.file)}) ===`)));
|
|
21
|
+
lines.push(`Target: ${pc.yellow(`^${synthesis.linkId}`)}`);
|
|
22
|
+
lines.push(`Sources: ${synthesis.sources.join(", ")}`);
|
|
23
|
+
if (synthesis.lastCheckpoint) {
|
|
24
|
+
const markerDisplay = synthesis.lastCheckpoint.type === "git" ? `git:${synthesis.lastCheckpoint.value.slice(0, 7)}` : formatCheckpoint(synthesis.lastCheckpoint);
|
|
25
|
+
lines.push(`Last checkpoint: ${pc.dim(markerDisplay)}`);
|
|
26
|
+
}
|
|
27
|
+
lines.push("");
|
|
28
|
+
lines.push(pc.bold("--- User Prompt ---"));
|
|
29
|
+
lines.push(synthesis.prompt || pc.dim("(no prompt defined)"));
|
|
30
|
+
lines.push("");
|
|
31
|
+
lines.push(pc.bold(`--- Changed Entries (${synthesis.entries.length}) ---`));
|
|
32
|
+
for (const entry of synthesis.entries) {
|
|
33
|
+
lines.push("");
|
|
34
|
+
lines.push(entry.rawText);
|
|
35
|
+
}
|
|
36
|
+
const checkpointValue = formatCheckpoint(synthesis.currentCheckpoint);
|
|
37
|
+
const instructions = generateInstructions(instructionsTemplate, {
|
|
38
|
+
file: relativePath(synthesis.file),
|
|
39
|
+
linkId: synthesis.linkId,
|
|
40
|
+
checkpoint: checkpointValue,
|
|
41
|
+
timestamp: generateTimestamp()
|
|
42
|
+
});
|
|
43
|
+
lines.push("");
|
|
44
|
+
lines.push(pc.bold("--- Instructions ---"));
|
|
45
|
+
lines.push(instructions);
|
|
46
|
+
lines.push("");
|
|
47
|
+
lines.push(pc.dim("─".repeat(60)));
|
|
48
|
+
return lines.join("\n");
|
|
49
|
+
}
|
|
50
|
+
async function actualizeAction(ctx) {
|
|
51
|
+
const { args, options } = ctx;
|
|
52
|
+
const instructionsTemplate = options["instructions"] || DEFAULT_INSTRUCTIONS_TEMPLATE;
|
|
53
|
+
const force = options["force"];
|
|
54
|
+
const { workspace, files } = await loadFullWorkspace();
|
|
55
|
+
if (files.length === 0) {
|
|
56
|
+
console.log("No .thalo or .md files found.");
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
const tracker = await createChangeTracker({
|
|
60
|
+
cwd: process.cwd(),
|
|
61
|
+
force
|
|
62
|
+
});
|
|
63
|
+
let result;
|
|
64
|
+
try {
|
|
65
|
+
result = await runActualize(workspace, {
|
|
66
|
+
targetLinkIds: args.length > 0 ? args : void 0,
|
|
67
|
+
tracker
|
|
68
|
+
});
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if (error instanceof UncommittedChangesError) {
|
|
71
|
+
console.error(pc.red("Error: Source files have uncommitted changes:"));
|
|
72
|
+
for (const file of error.files) console.error(pc.yellow(` - ${file}`));
|
|
73
|
+
console.error();
|
|
74
|
+
console.error(pc.dim("Commit your changes or use --force to proceed anyway."));
|
|
75
|
+
console.error(pc.dim("Note: Using --force may cause already-processed entries to appear again."));
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
if (result.trackerType === "git") console.log(pc.dim("Using git-based change tracking"));
|
|
81
|
+
for (const id of result.notFoundLinkIds) console.error(pc.yellow(`Warning: No synthesis found with link ID: ^${id}`));
|
|
82
|
+
if (args.length > 0 && result.syntheses.length === 0 && result.notFoundLinkIds.length > 0) process.exit(1);
|
|
83
|
+
if (result.syntheses.length === 0) {
|
|
84
|
+
console.log(pc.dim("No synthesis definitions found."));
|
|
85
|
+
process.exit(0);
|
|
86
|
+
}
|
|
87
|
+
let hasOutput = false;
|
|
88
|
+
for (const synthesis of result.syntheses) if (synthesis.isUpToDate) console.log(formatUpToDate(synthesis));
|
|
89
|
+
else {
|
|
90
|
+
hasOutput = true;
|
|
91
|
+
console.log(formatSynthesisWithEntries(synthesis, instructionsTemplate));
|
|
92
|
+
}
|
|
93
|
+
if (!hasOutput) {
|
|
94
|
+
console.log();
|
|
95
|
+
console.log(pc.green("All syntheses are up to date."));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const actualizeCommand = {
|
|
99
|
+
name: "actualize",
|
|
100
|
+
description: "Output prompts and entries for pending synthesis updates",
|
|
101
|
+
args: {
|
|
102
|
+
name: "links",
|
|
103
|
+
description: "Link IDs of synthesis definitions to actualize (e.g., ^my-synthesis). If omitted, all syntheses are checked.",
|
|
104
|
+
required: false,
|
|
105
|
+
multiple: true
|
|
106
|
+
},
|
|
107
|
+
options: {
|
|
108
|
+
instructions: {
|
|
109
|
+
type: "string",
|
|
110
|
+
short: "i",
|
|
111
|
+
description: "Custom instructions template. Use placeholders: {file}, {linkId}, {checkpoint}"
|
|
112
|
+
},
|
|
113
|
+
force: {
|
|
114
|
+
type: "boolean",
|
|
115
|
+
short: "f",
|
|
116
|
+
description: "Proceed even if source files have uncommitted changes. May cause duplicate processing."
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
action: actualizeAction
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
//#endregion
|
|
123
|
+
export { actualizeCommand };
|
|
124
|
+
//# sourceMappingURL=actualize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actualize.js","names":["lines: string[]","actualizeCommand: CommandDef"],"sources":["../../src/commands/actualize.ts"],"sourcesContent":["import {\n runActualize,\n generateInstructions,\n generateTimestamp,\n DEFAULT_INSTRUCTIONS_TEMPLATE,\n type SynthesisOutputInfo,\n} from \"@rejot-dev/thalo\";\nimport { formatCheckpoint } from \"@rejot-dev/thalo/change-tracker\";\nimport { createChangeTracker, UncommittedChangesError } from \"@rejot-dev/thalo/change-tracker/node\";\nimport pc from \"picocolors\";\nimport type { CommandDef, CommandContext } from \"../cli.js\";\nimport { loadFullWorkspace, relativePath } from \"../files.js\";\n\n/**\n * Format a synthesis that is up to date.\n */\nfunction formatUpToDate(synthesis: SynthesisOutputInfo): string {\n return pc.green(`✓ ${relativePath(synthesis.file)}: ${synthesis.title} - up to date`);\n}\n\n/**\n * Format a synthesis with pending entries.\n */\nfunction formatSynthesisWithEntries(\n synthesis: SynthesisOutputInfo,\n instructionsTemplate: string,\n): string {\n const lines: string[] = [];\n\n // Header\n lines.push(\"\");\n lines.push(\n pc.bold(pc.cyan(`=== Synthesis: ${synthesis.title} (${relativePath(synthesis.file)}) ===`)),\n );\n lines.push(`Target: ${pc.yellow(`^${synthesis.linkId}`)}`);\n lines.push(`Sources: ${synthesis.sources.join(\", \")}`);\n\n if (synthesis.lastCheckpoint) {\n const markerDisplay =\n synthesis.lastCheckpoint.type === \"git\"\n ? `git:${synthesis.lastCheckpoint.value.slice(0, 7)}`\n : formatCheckpoint(synthesis.lastCheckpoint);\n lines.push(`Last checkpoint: ${pc.dim(markerDisplay)}`);\n }\n\n // Prompt\n lines.push(\"\");\n lines.push(pc.bold(\"--- User Prompt ---\"));\n lines.push(synthesis.prompt || pc.dim(\"(no prompt defined)\"));\n\n // Entries\n lines.push(\"\");\n lines.push(pc.bold(`--- Changed Entries (${synthesis.entries.length}) ---`));\n for (const entry of synthesis.entries) {\n lines.push(\"\");\n lines.push(entry.rawText);\n }\n\n // Instructions\n const checkpointValue = formatCheckpoint(synthesis.currentCheckpoint);\n const instructions = generateInstructions(instructionsTemplate, {\n file: relativePath(synthesis.file),\n linkId: synthesis.linkId,\n checkpoint: checkpointValue,\n timestamp: generateTimestamp(),\n });\n\n lines.push(\"\");\n lines.push(pc.bold(\"--- Instructions ---\"));\n lines.push(instructions);\n lines.push(\"\");\n lines.push(pc.dim(\"─\".repeat(60)));\n\n return lines.join(\"\\n\");\n}\n\nasync function actualizeAction(ctx: CommandContext): Promise<void> {\n const { args, options } = ctx;\n const instructionsTemplate = (options[\"instructions\"] as string) || DEFAULT_INSTRUCTIONS_TEMPLATE;\n const force = options[\"force\"] as boolean;\n\n // Load full workspace from CWD\n const { workspace, files } = await loadFullWorkspace();\n if (files.length === 0) {\n console.log(\"No .thalo or .md files found.\");\n process.exit(0);\n }\n\n // Create change tracker (auto-detects git)\n const tracker = await createChangeTracker({ cwd: process.cwd(), force });\n\n // Run actualize command\n let result;\n try {\n result = await runActualize(workspace, {\n targetLinkIds: args.length > 0 ? args : undefined,\n tracker,\n });\n } catch (error) {\n if (error instanceof UncommittedChangesError) {\n console.error(pc.red(\"Error: Source files have uncommitted changes:\"));\n for (const file of error.files) {\n console.error(pc.yellow(` - ${file}`));\n }\n console.error();\n console.error(pc.dim(\"Commit your changes or use --force to proceed anyway.\"));\n console.error(\n pc.dim(\"Note: Using --force may cause already-processed entries to appear again.\"),\n );\n process.exit(1);\n }\n throw error;\n }\n\n // Show tracker type\n if (result.trackerType === \"git\") {\n console.log(pc.dim(\"Using git-based change tracking\"));\n }\n\n // Warn about not found link IDs\n for (const id of result.notFoundLinkIds) {\n console.error(pc.yellow(`Warning: No synthesis found with link ID: ^${id}`));\n }\n\n // Exit early if we filtered to specific IDs but found none\n if (args.length > 0 && result.syntheses.length === 0 && result.notFoundLinkIds.length > 0) {\n process.exit(1);\n }\n\n if (result.syntheses.length === 0) {\n console.log(pc.dim(\"No synthesis definitions found.\"));\n process.exit(0);\n }\n\n // Output results\n let hasOutput = false;\n for (const synthesis of result.syntheses) {\n if (synthesis.isUpToDate) {\n console.log(formatUpToDate(synthesis));\n } else {\n hasOutput = true;\n console.log(formatSynthesisWithEntries(synthesis, instructionsTemplate));\n }\n }\n\n if (!hasOutput) {\n console.log();\n console.log(pc.green(\"All syntheses are up to date.\"));\n }\n}\n\nexport const actualizeCommand: CommandDef = {\n name: \"actualize\",\n description: \"Output prompts and entries for pending synthesis updates\",\n args: {\n name: \"links\",\n description:\n \"Link IDs of synthesis definitions to actualize (e.g., ^my-synthesis). If omitted, all syntheses are checked.\",\n required: false,\n multiple: true,\n },\n options: {\n instructions: {\n type: \"string\",\n short: \"i\",\n description: \"Custom instructions template. Use placeholders: {file}, {linkId}, {checkpoint}\",\n },\n force: {\n type: \"boolean\",\n short: \"f\",\n description:\n \"Proceed even if source files have uncommitted changes. May cause duplicate processing.\",\n },\n },\n action: actualizeAction,\n};\n"],"mappings":"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"}
|