@geolonia/yuuhitsu 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +164 -0
  3. package/dist/cli/commands/translate.d.ts +3 -0
  4. package/dist/cli/commands/translate.d.ts.map +1 -0
  5. package/dist/cli/commands/translate.js +56 -0
  6. package/dist/cli/commands/translate.js.map +1 -0
  7. package/dist/cli/index.d.ts +5 -0
  8. package/dist/cli/index.d.ts.map +1 -0
  9. package/dist/cli/index.js +33 -0
  10. package/dist/cli/index.js.map +1 -0
  11. package/dist/config.d.ts +15 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +52 -0
  14. package/dist/config.js.map +1 -0
  15. package/dist/errors.d.ts +6 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +19 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/logger.d.ts +16 -0
  20. package/dist/logger.d.ts.map +1 -0
  21. package/dist/logger.js +17 -0
  22. package/dist/logger.js.map +1 -0
  23. package/dist/provider/claude.d.ts +9 -0
  24. package/dist/provider/claude.d.ts.map +1 -0
  25. package/dist/provider/claude.js +69 -0
  26. package/dist/provider/claude.js.map +1 -0
  27. package/dist/provider/gemini.d.ts +9 -0
  28. package/dist/provider/gemini.d.ts.map +1 -0
  29. package/dist/provider/gemini.js +77 -0
  30. package/dist/provider/gemini.js.map +1 -0
  31. package/dist/provider/index.d.ts +6 -0
  32. package/dist/provider/index.d.ts.map +1 -0
  33. package/dist/provider/index.js +16 -0
  34. package/dist/provider/index.js.map +1 -0
  35. package/dist/provider/interface.d.ts +30 -0
  36. package/dist/provider/interface.d.ts.map +1 -0
  37. package/dist/provider/interface.js +2 -0
  38. package/dist/provider/interface.js.map +1 -0
  39. package/dist/provider/ollama.d.ts +9 -0
  40. package/dist/provider/ollama.d.ts.map +1 -0
  41. package/dist/provider/ollama.js +60 -0
  42. package/dist/provider/ollama.js.map +1 -0
  43. package/dist/tasks/stream.d.ts +7 -0
  44. package/dist/tasks/stream.d.ts.map +1 -0
  45. package/dist/tasks/stream.js +13 -0
  46. package/dist/tasks/stream.js.map +1 -0
  47. package/dist/tasks/translate.d.ts +19 -0
  48. package/dist/tasks/translate.d.ts.map +1 -0
  49. package/dist/tasks/translate.js +101 -0
  50. package/dist/tasks/translate.js.map +1 -0
  51. package/package.json +40 -0
  52. package/src/templates/fix-links.md +7 -0
  53. package/src/templates/generate-docs.md +14 -0
  54. package/src/templates/generate-tests.md +14 -0
  55. package/src/templates/research.md +16 -0
  56. package/src/templates/sync-docs.md +11 -0
  57. package/src/templates/translate.md +18 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Geolonia Inc.
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,164 @@
1
+ # yuuhitsu (右筆)
2
+
3
+ AI-powered document operations CLI
4
+
5
+ ## Overview
6
+
7
+ **yuuhitsu** (右筆, meaning "secretary" or "scribe" in feudal Japan) is a command-line tool that automates document operations using AI. The name refers to scribes who served feudal lords, writing and managing official documents on their behalf — this tool serves engineers in the same way, handling translation, documentation generation, and document synchronization.
8
+
9
+ ### Key Capabilities
10
+
11
+ - **Markdown Translation**: Translate documents while preserving structure, code blocks, and formatting
12
+ - **Multi-Provider Support**: Switch between Claude (Anthropic), Gemini (Google), and Ollama (local) with a single config line change
13
+ - **Streaming Output**: See translation progress in real-time
14
+ - **Dry-Run Mode**: Preview operations without making API calls
15
+ - **Prompt Templates**: Customize AI behavior with configurable templates
16
+
17
+ ## Features
18
+
19
+ ### Translation (Available Now)
20
+
21
+ - Translate Markdown documents between languages
22
+ - Preserve document structure (headings, links, code blocks, tables)
23
+ - Support for large files with automatic chunking
24
+ - Real-time streaming output
25
+ - Retry logic with exponential backoff for API failures
26
+
27
+ ### Coming Soon
28
+
29
+ - **generate-docs**: Generate documentation from source code or specifications
30
+ - **sync-docs**: Convert external Markdown to VitePress-compatible format
31
+ - **research**: Perform web research and generate structured reports
32
+ - **fix-links**: Detect and fix dead links in documentation
33
+ - **generate-tests**: Generate test scaffolding from source code
34
+
35
+ ## Quick Start
36
+
37
+ ### Installation
38
+
39
+ ```bash
40
+ npm install -g yuuhitsu
41
+ ```
42
+
43
+ ### Basic Usage
44
+
45
+ ```bash
46
+ # Translate a document to Japanese
47
+ yuuhitsu translate --input README.md --lang ja
48
+
49
+ # Translate to English
50
+ yuuhitsu translate --input docs.md --lang en --output docs.en.md
51
+
52
+ # Preview without API calls
53
+ yuuhitsu translate --input README.md --lang ja --dry-run
54
+
55
+ # Use a specific config file
56
+ yuuhitsu translate --input README.md --lang ja --config ./custom.config.yaml
57
+ ```
58
+
59
+ ## Configuration
60
+
61
+ Create a `yuuhitsu.config.yaml` file in your project root:
62
+
63
+ ```yaml
64
+ # AI Provider Selection
65
+ provider: claude # Options: claude, gemini, ollama
66
+ model: claude-sonnet-4-5-20250929
67
+
68
+ # Optional Settings
69
+ outputDir: ./translated
70
+ templates: ./templates
71
+ log:
72
+ enabled: true
73
+ path: ./yuuhitsu.log
74
+ ```
75
+
76
+ ### Environment Variables
77
+
78
+ Create a `.env` file or set environment variables for API authentication:
79
+
80
+ ```bash
81
+ # For Claude (Anthropic)
82
+ ANTHROPIC_API_KEY=your_api_key_here
83
+
84
+ # For Gemini (Google)
85
+ GOOGLE_API_KEY=your_api_key_here
86
+
87
+ # Ollama requires no API key (runs locally)
88
+ ```
89
+
90
+ ### Supported Providers
91
+
92
+ | Provider | SDK | Environment Variable | Use Case |
93
+ |----------|-----|---------------------|----------|
94
+ | Claude | `@anthropic-ai/sdk` | `ANTHROPIC_API_KEY` | High-quality translation, research tasks |
95
+ | Gemini | `@google/genai` | `GOOGLE_API_KEY` | Fast processing, cost-effective |
96
+ | Ollama | `openai` (compatible) | *(none)* | Local execution, privacy, offline use |
97
+
98
+ ## Commands
99
+
100
+ ### `translate`
101
+
102
+ Translate Markdown documents between languages.
103
+
104
+ **Options:**
105
+
106
+ - `--input <path>` (required): Input Markdown file path
107
+ - `--output <path>`: Output file path (defaults to input file with `.{lang}.md` suffix)
108
+ - `--lang <code>` (required): Target language code (e.g., `en`, `ja`, `zh`, `es`)
109
+ - `--provider <name>`: Override config provider (claude, gemini, ollama)
110
+ - `--model <name>`: Override config model
111
+ - `--dry-run`: Show what would be done without making API calls
112
+ - `--stream`: Enable streaming output (default: true)
113
+ - `--config <path>`: Config file path (default: `./yuuhitsu.config.yaml`)
114
+ - `--verbose`: Enable verbose output
115
+
116
+ **Example:**
117
+
118
+ ```bash
119
+ yuuhitsu translate \
120
+ --input ./docs/guide.md \
121
+ --output ./docs/guide.ja.md \
122
+ --lang ja \
123
+ --provider claude \
124
+ --model claude-sonnet-4-5-20250929
125
+ ```
126
+
127
+ ## Development
128
+
129
+ ```bash
130
+ # Clone the repository
131
+ git clone https://github.com/geolonia/yuuhitsu.git
132
+ cd yuuhitsu
133
+
134
+ # Install dependencies
135
+ npm install
136
+
137
+ # Run tests
138
+ npm test
139
+
140
+ # Build the project
141
+ npm run build
142
+
143
+ # Run locally (development)
144
+ npm run dev -- translate --input test.md --lang ja
145
+ ```
146
+
147
+ ### Running Tests
148
+
149
+ ```bash
150
+ # Run all tests
151
+ npm test
152
+
153
+ # Watch mode
154
+ npm run test:watch
155
+
156
+ # Type checking
157
+ npm run lint
158
+ ```
159
+
160
+ ## License
161
+
162
+ MIT — See [LICENSE](./LICENSE)
163
+
164
+ Copyright (c) 2026 Geolonia Inc.
@@ -0,0 +1,3 @@
1
+ import { Command } from "commander";
2
+ export declare const translateCommand: Command;
3
+ //# sourceMappingURL=translate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/translate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,eAAO,MAAM,gBAAgB,SA8DzB,CAAC"}
@@ -0,0 +1,56 @@
1
+ import { Command } from "commander";
2
+ import { existsSync } from "fs";
3
+ import chalk from "chalk";
4
+ import { loadConfig } from "../../config.js";
5
+ import { createProvider } from "../../provider/index.js";
6
+ import { translateFile } from "../../tasks/translate.js";
7
+ import { formatError, AppError } from "../../errors.js";
8
+ export const translateCommand = new Command("translate")
9
+ .description("Translate a Markdown document to another language")
10
+ .requiredOption("--input <file>", "Input Markdown file")
11
+ .requiredOption("--lang <code>", "Target language code (e.g., ja, en, zh, ko)")
12
+ .option("--output <file>", "Output file path (default: <input>.<lang>.md)")
13
+ .action(async (opts, cmd) => {
14
+ const globalOpts = cmd.parent?.opts() ?? {};
15
+ const configPath = globalOpts.config ?? "./yuuhitsu.config.yaml";
16
+ const dryRun = globalOpts.dryRun ?? false;
17
+ const verbose = globalOpts.verbose ?? false;
18
+ try {
19
+ // Validate input file exists
20
+ if (!existsSync(opts.input)) {
21
+ throw new AppError(`Input file not found: ${opts.input}`, "Check the file path and try again.");
22
+ }
23
+ // Load config
24
+ const config = await loadConfig(configPath);
25
+ if (verbose) {
26
+ process.stderr.write(`${chalk.gray(`Provider: ${config.provider}, Model: ${config.model}`)}\n`);
27
+ }
28
+ // Dry-run mode
29
+ if (dryRun) {
30
+ const outputPath = opts.output || `${opts.input.replace(/\.md$/, "")}.${opts.lang}.md`;
31
+ process.stdout.write(`${chalk.cyan("[dry-run]")} Would translate:\n` +
32
+ ` Input: ${opts.input}\n` +
33
+ ` Output: ${outputPath}\n` +
34
+ ` Language: ${opts.lang}\n` +
35
+ ` Provider: ${config.provider}\n` +
36
+ ` Model: ${config.model}\n`);
37
+ return;
38
+ }
39
+ // Create provider
40
+ const provider = createProvider(config.provider, config.model);
41
+ // Execute translation
42
+ const result = await translateFile({
43
+ provider,
44
+ inputPath: opts.input,
45
+ outputPath: opts.output,
46
+ targetLang: opts.lang,
47
+ });
48
+ process.stdout.write(`${chalk.green("✓")} Translated to ${result.outputPath}\n` +
49
+ ` Tokens: ${result.usage.totalTokens} (${result.chunks} chunk${result.chunks > 1 ? "s" : ""})\n`);
50
+ }
51
+ catch (err) {
52
+ process.stderr.write(formatError(err) + "\n");
53
+ process.exit(1);
54
+ }
55
+ });
56
+ //# sourceMappingURL=translate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.js","sourceRoot":"","sources":["../../../src/cli/commands/translate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAChC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAExD,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,OAAO,CAAC,WAAW,CAAC;KACrD,WAAW,CAAC,mDAAmD,CAAC;KAChE,cAAc,CAAC,gBAAgB,EAAE,qBAAqB,CAAC;KACvD,cAAc,CAAC,eAAe,EAAE,6CAA6C,CAAC;KAC9E,MAAM,CAAC,iBAAiB,EAAE,+CAA+C,CAAC;KAC1E,MAAM,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE;IAC1B,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAC5C,MAAM,UAAU,GAAW,UAAU,CAAC,MAAM,IAAI,wBAAwB,CAAC;IACzE,MAAM,MAAM,GAAY,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC;IACnD,MAAM,OAAO,GAAY,UAAU,CAAC,OAAO,IAAI,KAAK,CAAC;IAErD,IAAI,CAAC;QACH,6BAA6B;QAC7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,QAAQ,CAChB,yBAAyB,IAAI,CAAC,KAAK,EAAE,EACrC,oCAAoC,CACrC,CAAC;QACJ,CAAC;QAED,cAAc;QACd,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,UAAU,CAAC,CAAC;QAE5C,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,QAAQ,YAAY,MAAM,CAAC,KAAK,EAAE,CAAC,IAAI,CAC1E,CAAC;QACJ,CAAC;QAED,eAAe;QACf,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,IAAI,KAAK,CAAC;YACvF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,qBAAqB;gBAC/C,eAAe,IAAI,CAAC,KAAK,IAAI;gBAC7B,eAAe,UAAU,IAAI;gBAC7B,eAAe,IAAI,CAAC,IAAI,IAAI;gBAC5B,eAAe,MAAM,CAAC,QAAQ,IAAI;gBAClC,eAAe,MAAM,CAAC,KAAK,IAAI,CAChC,CAAC;YACF,OAAO;QACT,CAAC;QAED,kBAAkB;QAClB,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QAE/D,sBAAsB;QACtB,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC;YACjC,QAAQ;YACR,SAAS,EAAE,IAAI,CAAC,KAAK;YACrB,UAAU,EAAE,IAAI,CAAC,MAAM;YACvB,UAAU,EAAE,IAAI,CAAC,IAAI;SACtB,CAAC,CAAC;QAEH,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,kBAAkB,MAAM,CAAC,UAAU,IAAI;YAC1D,aAAa,MAAM,CAAC,KAAK,CAAC,WAAW,KAAK,MAAM,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAClG,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ declare const program: Command;
4
+ export { program };
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoBpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAoB9B,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { readFileSync } from "fs";
4
+ import { join, dirname } from "path";
5
+ import { fileURLToPath } from "url";
6
+ import { formatError } from "../errors.js";
7
+ import { translateCommand } from "./commands/translate.js";
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ function getVersion() {
10
+ try {
11
+ const pkg = JSON.parse(readFileSync(join(__dirname, "../../package.json"), "utf-8"));
12
+ return pkg.version;
13
+ }
14
+ catch {
15
+ return "0.0.0";
16
+ }
17
+ }
18
+ const program = new Command();
19
+ program
20
+ .name("yuuhitsu")
21
+ .description("右筆 (Yuuhitsu) - AI-powered document operations CLI")
22
+ .version(getVersion())
23
+ .option("--config <path>", "Config file path", "./yuuhitsu.config.yaml")
24
+ .option("--dry-run", "Show what would be done without making API calls")
25
+ .option("--verbose", "Enable verbose output");
26
+ // Register commands
27
+ program.addCommand(translateCommand);
28
+ program.parseAsync(process.argv).catch((err) => {
29
+ process.stderr.write(formatError(err) + "\n");
30
+ process.exit(1);
31
+ });
32
+ export { program };
33
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,SAAS,UAAU;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CACpB,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAAE,OAAO,CAAC,CAC7D,CAAC;QACF,OAAO,GAAG,CAAC,OAAO,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,UAAU,CAAC;KAChB,WAAW,CACV,oDAAoD,CACrD;KACA,OAAO,CAAC,UAAU,EAAE,CAAC;KACrB,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,EAAE,wBAAwB,CAAC;KACvE,MAAM,CAAC,WAAW,EAAE,kDAAkD,CAAC;KACvE,MAAM,CAAC,WAAW,EAAE,uBAAuB,CAAC,CAAC;AAEhD,oBAAoB;AACpB,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AAErC,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IAC7C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEH,OAAO,EAAE,OAAO,EAAE,CAAC"}
@@ -0,0 +1,15 @@
1
+ declare const SUPPORTED_PROVIDERS: readonly ["claude", "gemini", "ollama"];
2
+ export type ProviderName = (typeof SUPPORTED_PROVIDERS)[number];
3
+ export interface AppConfig {
4
+ provider: ProviderName;
5
+ model: string;
6
+ templates?: string;
7
+ outputDir?: string;
8
+ log?: {
9
+ enabled?: boolean;
10
+ path?: string;
11
+ };
12
+ }
13
+ export declare function loadConfig(configPath: string, envDir?: string): Promise<AppConfig>;
14
+ export {};
15
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAKA,QAAA,MAAM,mBAAmB,yCAA0C,CAAC;AACpE,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,YAAY,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE;QACJ,OAAO,CAAC,EAAE,OAAO,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC;CACH;AAED,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,SAAS,CAAC,CAmDpB"}
package/dist/config.js ADDED
@@ -0,0 +1,52 @@
1
+ import { readFileSync } from "fs";
2
+ import { parse } from "yaml";
3
+ import { config as dotenvConfig } from "dotenv";
4
+ import { join } from "path";
5
+ const SUPPORTED_PROVIDERS = ["claude", "gemini", "ollama"];
6
+ export async function loadConfig(configPath, envDir) {
7
+ // Load .env - from envDir if provided, otherwise from cwd
8
+ if (envDir) {
9
+ dotenvConfig({ path: join(envDir, ".env") });
10
+ }
11
+ else {
12
+ dotenvConfig(); // Loads from cwd by default
13
+ }
14
+ let content;
15
+ try {
16
+ content = readFileSync(configPath, "utf-8");
17
+ }
18
+ catch (err) {
19
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
20
+ throw new Error(`Config file not found: ${configPath}`);
21
+ }
22
+ throw err;
23
+ }
24
+ const raw = parse(content);
25
+ if (!raw || typeof raw !== "object") {
26
+ throw new Error("Config file is empty or invalid YAML");
27
+ }
28
+ const provider = raw.provider;
29
+ if (!SUPPORTED_PROVIDERS.includes(provider)) {
30
+ throw new Error(`Unsupported provider: "${provider}". Supported providers: ${SUPPORTED_PROVIDERS.join(", ")}`);
31
+ }
32
+ const model = raw.model;
33
+ if (!model || typeof model !== "string") {
34
+ throw new Error("Config must specify a model");
35
+ }
36
+ const config = {
37
+ provider,
38
+ model,
39
+ };
40
+ if (raw.templates)
41
+ config.templates = raw.templates;
42
+ if (raw.outputDir)
43
+ config.outputDir = raw.outputDir;
44
+ if (raw.log) {
45
+ config.log = {
46
+ enabled: raw.log.enabled ?? false,
47
+ path: raw.log.path,
48
+ };
49
+ }
50
+ return config;
51
+ }
52
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,MAAM,mBAAmB,GAAG,CAAC,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAU,CAAC;AAcpE,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,UAAkB,EAClB,MAAe;IAEf,0DAA0D;IAC1D,IAAI,MAAM,EAAE,CAAC;QACX,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;IAC/C,CAAC;SAAM,CAAC;QACN,YAAY,EAAE,CAAC,CAAC,4BAA4B;IAC9C,CAAC;IAED,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAI,GAAG,YAAY,KAAK,IAAI,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACnE,MAAM,IAAI,KAAK,CAAC,0BAA0B,UAAU,EAAE,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAE3B,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;IAC9B,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5C,MAAM,IAAI,KAAK,CACb,0BAA0B,QAAQ,2BAA2B,mBAAmB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9F,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC;IACxB,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAC;IACjD,CAAC;IAED,MAAM,MAAM,GAAc;QACxB,QAAQ;QACR,KAAK;KACN,CAAC;IAEF,IAAI,GAAG,CAAC,SAAS;QAAE,MAAM,CAAC,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC;IACpD,IAAI,GAAG,CAAC,SAAS;QAAE,MAAM,CAAC,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC;IACpD,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;QACZ,MAAM,CAAC,GAAG,GAAG;YACX,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,OAAO,IAAI,KAAK;YACjC,IAAI,EAAE,GAAG,CAAC,GAAG,CAAC,IAAI;SACnB,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare class AppError extends Error {
2
+ readonly hint: string;
3
+ constructor(message: string, hint: string);
4
+ }
5
+ export declare function formatError(error: unknown): string;
6
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAEA,qBAAa,QAAS,SAAQ,KAAK;IACjC,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAK1C;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQlD"}
package/dist/errors.js ADDED
@@ -0,0 +1,19 @@
1
+ import chalk from "chalk";
2
+ export class AppError extends Error {
3
+ hint;
4
+ constructor(message, hint) {
5
+ super(message);
6
+ this.name = "AppError";
7
+ this.hint = hint;
8
+ }
9
+ }
10
+ export function formatError(error) {
11
+ if (error instanceof AppError) {
12
+ return `${chalk.red("Error:")} ${error.message}\n\n${chalk.yellow("Hint:")} ${error.hint}`;
13
+ }
14
+ if (error instanceof Error) {
15
+ return `${chalk.red("Error:")} ${error.message}`;
16
+ }
17
+ return `${chalk.red("Error:")} ${String(error)}`;
18
+ }
19
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,OAAO,QAAS,SAAQ,KAAK;IACjB,IAAI,CAAS;IAE7B,YAAY,OAAe,EAAE,IAAY;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,OAAO,OAAO,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7F,CAAC;IACD,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;IACnD,CAAC;IACD,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AACnD,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface LogEntry {
2
+ provider: string;
3
+ model: string;
4
+ taskType: string;
5
+ inputTokens: number;
6
+ outputTokens: number;
7
+ latencyMs: number;
8
+ success: boolean;
9
+ error?: string;
10
+ }
11
+ export declare class ExecutionLogger {
12
+ private logPath;
13
+ constructor(logPath: string);
14
+ log(entry: LogEntry): void;
15
+ }
16
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,MAAM;IAK3B,GAAG,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI;CAO3B"}
package/dist/logger.js ADDED
@@ -0,0 +1,17 @@
1
+ import { appendFileSync, mkdirSync } from "fs";
2
+ import { dirname } from "path";
3
+ export class ExecutionLogger {
4
+ logPath;
5
+ constructor(logPath) {
6
+ this.logPath = logPath;
7
+ mkdirSync(dirname(logPath), { recursive: true });
8
+ }
9
+ log(entry) {
10
+ const record = {
11
+ timestamp: new Date().toISOString(),
12
+ ...entry,
13
+ };
14
+ appendFileSync(this.logPath, JSON.stringify(record) + "\n", "utf-8");
15
+ }
16
+ }
17
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/C,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAa/B,MAAM,OAAO,eAAe;IAClB,OAAO,CAAS;IAExB,YAAY,OAAe;QACzB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,GAAG,CAAC,KAAe;QACjB,MAAM,MAAM,GAAG;YACb,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,GAAG,KAAK;SACT,CAAC;QACF,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;IACvE,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ import type { AIProvider, ChatRequest, ChatResponse, StreamChunk } from "./interface.js";
2
+ export declare class ClaudeProvider implements AIProvider {
3
+ private client;
4
+ private model;
5
+ constructor(model: string);
6
+ chat(request: ChatRequest): Promise<ChatResponse>;
7
+ chatStream(request: ChatRequest): AsyncIterable<StreamChunk>;
8
+ }
9
+ //# sourceMappingURL=claude.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/provider/claude.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAExB,qBAAa,cAAe,YAAW,UAAU;IAC/C,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,KAAK,CAAS;gBAEV,KAAK,EAAE,MAAM;IAYnB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAiChD,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,aAAa,CAAC,WAAW,CAAC;CA6BpE"}
@@ -0,0 +1,69 @@
1
+ import Anthropic from "@anthropic-ai/sdk";
2
+ export class ClaudeProvider {
3
+ client;
4
+ model;
5
+ constructor(model) {
6
+ const apiKey = process.env.ANTHROPIC_API_KEY;
7
+ if (!apiKey) {
8
+ throw new Error("ANTHROPIC_API_KEY environment variable is not set. " +
9
+ "Get your API key at https://console.anthropic.com/settings/keys");
10
+ }
11
+ this.client = new Anthropic({ apiKey });
12
+ this.model = model;
13
+ }
14
+ async chat(request) {
15
+ const systemMessage = request.messages.find((m) => m.role === "system");
16
+ const userMessages = request.messages
17
+ .filter((m) => m.role !== "system")
18
+ .map((m) => ({
19
+ role: m.role,
20
+ content: m.content,
21
+ }));
22
+ const response = await this.client.messages.create({
23
+ model: request.model || this.model,
24
+ max_tokens: request.maxTokens ?? 4096,
25
+ ...(systemMessage ? { system: systemMessage.content } : {}),
26
+ messages: userMessages,
27
+ ...(request.temperature !== undefined
28
+ ? { temperature: request.temperature }
29
+ : {}),
30
+ });
31
+ const textBlock = response.content.find((b) => b.type === "text");
32
+ return {
33
+ content: textBlock?.text ?? "",
34
+ model: response.model,
35
+ usage: {
36
+ promptTokens: response.usage.input_tokens,
37
+ completionTokens: response.usage.output_tokens,
38
+ totalTokens: response.usage.input_tokens + response.usage.output_tokens,
39
+ },
40
+ finishReason: response.stop_reason ?? "unknown",
41
+ };
42
+ }
43
+ async *chatStream(request) {
44
+ const systemMessage = request.messages.find((m) => m.role === "system");
45
+ const userMessages = request.messages
46
+ .filter((m) => m.role !== "system")
47
+ .map((m) => ({
48
+ role: m.role,
49
+ content: m.content,
50
+ }));
51
+ const stream = this.client.messages.stream({
52
+ model: request.model || this.model,
53
+ max_tokens: request.maxTokens ?? 4096,
54
+ ...(systemMessage ? { system: systemMessage.content } : {}),
55
+ messages: userMessages,
56
+ ...(request.temperature !== undefined
57
+ ? { temperature: request.temperature }
58
+ : {}),
59
+ });
60
+ for await (const event of stream) {
61
+ if (event.type === "content_block_delta" &&
62
+ event.delta.type === "text_delta") {
63
+ yield { content: event.delta.text, done: false };
64
+ }
65
+ }
66
+ yield { content: "", done: true };
67
+ }
68
+ }
69
+ //# sourceMappingURL=claude.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/provider/claude.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { AIProvider, ChatRequest, ChatResponse, StreamChunk } from "./interface.js";
2
+ export declare class GeminiProvider implements AIProvider {
3
+ private client;
4
+ private model;
5
+ constructor(model: string);
6
+ chat(request: ChatRequest): Promise<ChatResponse>;
7
+ chatStream(request: ChatRequest): AsyncIterable<StreamChunk>;
8
+ }
9
+ //# sourceMappingURL=gemini.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.d.ts","sourceRoot":"","sources":["../../src/provider/gemini.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAExB,qBAAa,cAAe,YAAW,UAAU;IAC/C,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,KAAK,CAAS;gBAEV,KAAK,EAAE,MAAM;IAYnB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IAuChD,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,aAAa,CAAC,WAAW,CAAC;CA+BpE"}
@@ -0,0 +1,77 @@
1
+ import { GoogleGenAI } from "@google/genai";
2
+ export class GeminiProvider {
3
+ client;
4
+ model;
5
+ constructor(model) {
6
+ const apiKey = process.env.GOOGLE_API_KEY;
7
+ if (!apiKey) {
8
+ throw new Error("GOOGLE_API_KEY environment variable is not set. " +
9
+ "Get your API key at https://aistudio.google.com/apikey");
10
+ }
11
+ this.client = new GoogleGenAI({ apiKey });
12
+ this.model = model;
13
+ }
14
+ async chat(request) {
15
+ const systemMessage = request.messages.find((m) => m.role === "system");
16
+ const userMessages = request.messages.filter((m) => m.role !== "system");
17
+ // Build contents for Gemini
18
+ const contents = userMessages.map((m) => ({
19
+ role: m.role === "assistant" ? "model" : "user",
20
+ parts: [{ text: m.content }],
21
+ }));
22
+ const response = await this.client.models.generateContent({
23
+ model: request.model || this.model,
24
+ contents,
25
+ ...(systemMessage
26
+ ? { config: { systemInstruction: systemMessage.content } }
27
+ : {}),
28
+ ...(request.temperature !== undefined
29
+ ? { config: { temperature: request.temperature } }
30
+ : {}),
31
+ ...(request.maxTokens
32
+ ? { config: { maxOutputTokens: request.maxTokens } }
33
+ : {}),
34
+ });
35
+ const text = response.text ?? "";
36
+ const usage = response.usageMetadata;
37
+ return {
38
+ content: text,
39
+ model: request.model || this.model,
40
+ usage: {
41
+ promptTokens: usage?.promptTokenCount ?? 0,
42
+ completionTokens: usage?.candidatesTokenCount ?? 0,
43
+ totalTokens: usage?.totalTokenCount ?? 0,
44
+ },
45
+ finishReason: response.candidates?.[0]?.finishReason ?? "unknown",
46
+ };
47
+ }
48
+ async *chatStream(request) {
49
+ const systemMessage = request.messages.find((m) => m.role === "system");
50
+ const userMessages = request.messages.filter((m) => m.role !== "system");
51
+ const contents = userMessages.map((m) => ({
52
+ role: m.role === "assistant" ? "model" : "user",
53
+ parts: [{ text: m.content }],
54
+ }));
55
+ const response = await this.client.models.generateContentStream({
56
+ model: request.model || this.model,
57
+ contents,
58
+ ...(systemMessage
59
+ ? { config: { systemInstruction: systemMessage.content } }
60
+ : {}),
61
+ ...(request.temperature !== undefined
62
+ ? { config: { temperature: request.temperature } }
63
+ : {}),
64
+ ...(request.maxTokens
65
+ ? { config: { maxOutputTokens: request.maxTokens } }
66
+ : {}),
67
+ });
68
+ for await (const chunk of response) {
69
+ const text = chunk.text ?? "";
70
+ if (text) {
71
+ yield { content: text, done: false };
72
+ }
73
+ }
74
+ yield { content: "", done: true };
75
+ }
76
+ }
77
+ //# sourceMappingURL=gemini.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../src/provider/gemini.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { ProviderName } from "../config.js";
2
+ import type { AIProvider } from "./interface.js";
3
+ export declare function createProvider(provider: ProviderName, model: string): AIProvider;
4
+ export type { AIProvider } from "./interface.js";
5
+ export type { ChatMessage, ChatRequest, ChatResponse, StreamChunk, } from "./interface.js";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/provider/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAKjD,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,GACZ,UAAU,CAaZ;AAED,YAAY,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EACV,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,GACZ,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { ClaudeProvider } from "./claude.js";
2
+ import { GeminiProvider } from "./gemini.js";
3
+ import { OllamaProvider } from "./ollama.js";
4
+ export function createProvider(provider, model) {
5
+ switch (provider) {
6
+ case "claude":
7
+ return new ClaudeProvider(model);
8
+ case "gemini":
9
+ return new GeminiProvider(model);
10
+ case "ollama":
11
+ return new OllamaProvider(model);
12
+ default:
13
+ throw new Error(`Unsupported provider: "${provider}". Supported providers: claude, gemini, ollama`);
14
+ }
15
+ }
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/provider/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE7C,MAAM,UAAU,cAAc,CAC5B,QAAsB,EACtB,KAAa;IAEb,QAAQ,QAAQ,EAAE,CAAC;QACjB,KAAK,QAAQ;YACX,OAAO,IAAI,cAAc,CAAC,KAAK,CAAC,CAAC;QACnC,KAAK,QAAQ;YACX,OAAO,IAAI,cAAc,CAAC,KAAK,CAAC,CAAC;QACnC,KAAK,QAAQ;YACX,OAAO,IAAI,cAAc,CAAC,KAAK,CAAC,CAAC;QACnC;YACE,MAAM,IAAI,KAAK,CACb,0BAA0B,QAAQ,gDAAgD,CACnF,CAAC;IACN,CAAC;AACH,CAAC"}
@@ -0,0 +1,30 @@
1
+ export interface ChatMessage {
2
+ role: "system" | "user" | "assistant";
3
+ content: string;
4
+ }
5
+ export interface ChatRequest {
6
+ model: string;
7
+ messages: ChatMessage[];
8
+ temperature?: number;
9
+ maxTokens?: number;
10
+ stream?: boolean;
11
+ }
12
+ export interface ChatResponse {
13
+ content: string;
14
+ model: string;
15
+ usage: {
16
+ promptTokens: number;
17
+ completionTokens: number;
18
+ totalTokens: number;
19
+ };
20
+ finishReason: string;
21
+ }
22
+ export interface StreamChunk {
23
+ content: string;
24
+ done: boolean;
25
+ }
26
+ export interface AIProvider {
27
+ chat(request: ChatRequest): Promise<ChatResponse>;
28
+ chatStream(request: ChatRequest): AsyncIterable<StreamChunk>;
29
+ }
30
+ //# sourceMappingURL=interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/provider/interface.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAClD,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,aAAa,CAAC,WAAW,CAAC,CAAC;CAC9D"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/provider/interface.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import type { AIProvider, ChatRequest, ChatResponse, StreamChunk } from "./interface.js";
2
+ export declare class OllamaProvider implements AIProvider {
3
+ private client;
4
+ private model;
5
+ constructor(model: string, baseUrl?: string);
6
+ chat(request: ChatRequest): Promise<ChatResponse>;
7
+ chatStream(request: ChatRequest): AsyncIterable<StreamChunk>;
8
+ }
9
+ //# sourceMappingURL=ollama.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.d.ts","sourceRoot":"","sources":["../../src/provider/ollama.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACZ,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAExB,qBAAa,cAAe,YAAW,UAAU;IAC/C,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;gBAEV,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM;IAQrC,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IA4BhD,UAAU,CAAC,OAAO,EAAE,WAAW,GAAG,aAAa,CAAC,WAAW,CAAC;CAwBpE"}
@@ -0,0 +1,60 @@
1
+ import OpenAI from "openai";
2
+ export class OllamaProvider {
3
+ client;
4
+ model;
5
+ constructor(model, baseUrl) {
6
+ this.client = new OpenAI({
7
+ baseURL: baseUrl ?? "http://localhost:11434/v1",
8
+ apiKey: "ollama", // Ollama doesn't need a real key but the SDK requires one
9
+ });
10
+ this.model = model;
11
+ }
12
+ async chat(request) {
13
+ const messages = request.messages.map((m) => ({
14
+ role: m.role,
15
+ content: m.content,
16
+ }));
17
+ const response = await this.client.chat.completions.create({
18
+ model: request.model || this.model,
19
+ messages,
20
+ ...(request.temperature !== undefined
21
+ ? { temperature: request.temperature }
22
+ : {}),
23
+ ...(request.maxTokens ? { max_tokens: request.maxTokens } : {}),
24
+ });
25
+ const choice = response.choices[0];
26
+ return {
27
+ content: choice?.message?.content ?? "",
28
+ model: response.model,
29
+ usage: {
30
+ promptTokens: response.usage?.prompt_tokens ?? 0,
31
+ completionTokens: response.usage?.completion_tokens ?? 0,
32
+ totalTokens: response.usage?.total_tokens ?? 0,
33
+ },
34
+ finishReason: choice?.finish_reason ?? "unknown",
35
+ };
36
+ }
37
+ async *chatStream(request) {
38
+ const messages = request.messages.map((m) => ({
39
+ role: m.role,
40
+ content: m.content,
41
+ }));
42
+ const stream = await this.client.chat.completions.create({
43
+ model: request.model || this.model,
44
+ messages,
45
+ stream: true,
46
+ ...(request.temperature !== undefined
47
+ ? { temperature: request.temperature }
48
+ : {}),
49
+ ...(request.maxTokens ? { max_tokens: request.maxTokens } : {}),
50
+ });
51
+ for await (const chunk of stream) {
52
+ const content = chunk.choices[0]?.delta?.content ?? "";
53
+ if (content) {
54
+ yield { content, done: false };
55
+ }
56
+ }
57
+ yield { content: "", done: true };
58
+ }
59
+ }
60
+ //# sourceMappingURL=ollama.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ollama.js","sourceRoot":"","sources":["../../src/provider/ollama.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import type { AIProvider, ChatRequest } from "../provider/interface.js";
2
+ export interface StreamOptions {
3
+ onChunk?: (chunk: string) => void;
4
+ onDone?: () => void;
5
+ }
6
+ export declare function streamResponse(provider: AIProvider, request: ChatRequest, options?: StreamOptions): Promise<string>;
7
+ //# sourceMappingURL=stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/tasks/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAe,MAAM,0BAA0B,CAAC;AAErF,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,wBAAsB,cAAc,CAClC,QAAQ,EAAE,UAAU,EACpB,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,aAAa,GACtB,OAAO,CAAC,MAAM,CAAC,CAajB"}
@@ -0,0 +1,13 @@
1
+ export async function streamResponse(provider, request, options) {
2
+ let fullContent = "";
3
+ for await (const chunk of provider.chatStream(request)) {
4
+ if (chunk.done) {
5
+ options?.onDone?.();
6
+ break;
7
+ }
8
+ fullContent += chunk.content;
9
+ options?.onChunk?.(chunk.content);
10
+ }
11
+ return fullContent;
12
+ }
13
+ //# sourceMappingURL=stream.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.js","sourceRoot":"","sources":["../../src/tasks/stream.ts"],"names":[],"mappings":"AAOA,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,QAAoB,EACpB,OAAoB,EACpB,OAAuB;IAEvB,IAAI,WAAW,GAAG,EAAE,CAAC;IAErB,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACvD,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YACf,OAAO,EAAE,MAAM,EAAE,EAAE,CAAC;YACpB,MAAM;QACR,CAAC;QACD,WAAW,IAAI,KAAK,CAAC,OAAO,CAAC;QAC7B,OAAO,EAAE,OAAO,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { AIProvider } from "../provider/interface.js";
2
+ export interface TranslateOptions {
3
+ provider: AIProvider;
4
+ inputPath: string;
5
+ outputPath?: string;
6
+ targetLang: string;
7
+ templateContent?: string;
8
+ }
9
+ export interface TranslateResult {
10
+ outputPath: string;
11
+ usage: {
12
+ promptTokens: number;
13
+ completionTokens: number;
14
+ totalTokens: number;
15
+ };
16
+ chunks: number;
17
+ }
18
+ export declare function translateFile(options: TranslateOptions): Promise<TranslateResult>;
19
+ //# sourceMappingURL=translate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../../src/tasks/translate.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,0BAA0B,CAAC;AAIxE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,UAAU,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,MAAM,CAAC;QACzB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,MAAM,EAAE,MAAM,CAAC;CAChB;AAsED,wBAAsB,aAAa,CACjC,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CAoD1B"}
@@ -0,0 +1,101 @@
1
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
2
+ import { dirname, basename, extname, join } from "path";
3
+ const CHUNK_SIZE = 50 * 1024; // 50KB
4
+ const DEFAULT_TEMPLATE = `You are a professional translator. Translate the following Markdown document to {{targetLanguage}}.
5
+
6
+ Rules:
7
+ - Preserve all Markdown formatting (headings, links, code blocks, tables, lists)
8
+ - Do not translate code blocks, URLs, or file paths
9
+ - Do not translate frontmatter keys (only translate values where appropriate)
10
+ - Maintain the same document structure
11
+ - Produce natural, fluent text in the target language
12
+
13
+ Additional rules for Japanese translation:
14
+ - Use full-width punctuation: 。、?! (not .,?!)
15
+ - Add half-width spaces around English words and numbers (e.g., "Vela とは", "NGSIv2 は", "3 つの")
16
+ - Use natural Japanese terms for technical words where appropriate (e.g., "registration" → "登録", "subscription" → "サブスクリプション")
17
+ - Keep product names, proper nouns, and abbreviations unchanged (e.g., Vela, FIWARE, NGSIv2, NGSI-LD, MCP)`;
18
+ function buildPrompt(content, targetLang, templateContent) {
19
+ const template = templateContent || DEFAULT_TEMPLATE;
20
+ const systemPrompt = template
21
+ .replace(/\{\{targetLanguage\}\}/g, targetLang)
22
+ .replace(/\{\{content\}\}/g, "");
23
+ return [
24
+ { role: "system", content: systemPrompt },
25
+ { role: "user", content },
26
+ ];
27
+ }
28
+ function resolveOutputPath(inputPath, targetLang, outputPath) {
29
+ if (outputPath)
30
+ return outputPath;
31
+ const dir = dirname(inputPath);
32
+ const ext = extname(inputPath);
33
+ const base = basename(inputPath, ext);
34
+ return join(dir, `${base}.${targetLang}${ext}`);
35
+ }
36
+ function splitIntoChunks(content) {
37
+ if (content.length <= CHUNK_SIZE) {
38
+ return [content];
39
+ }
40
+ const chunks = [];
41
+ const lines = content.split("\n");
42
+ let currentChunk = "";
43
+ for (const line of lines) {
44
+ if (currentChunk.length + line.length + 1 > CHUNK_SIZE && currentChunk.length > 0) {
45
+ chunks.push(currentChunk);
46
+ currentChunk = line + "\n";
47
+ }
48
+ else {
49
+ currentChunk += line + "\n";
50
+ }
51
+ }
52
+ if (currentChunk.trim().length > 0) {
53
+ chunks.push(currentChunk);
54
+ }
55
+ return chunks;
56
+ }
57
+ export async function translateFile(options) {
58
+ const { provider, inputPath, targetLang, templateContent } = options;
59
+ // Read input file
60
+ let content;
61
+ try {
62
+ content = readFileSync(inputPath, "utf-8");
63
+ }
64
+ catch (err) {
65
+ if (err instanceof Error && "code" in err && err.code === "ENOENT") {
66
+ throw new Error(`Input file not found: ${inputPath}`);
67
+ }
68
+ throw err;
69
+ }
70
+ // Check for empty file
71
+ if (content.trim().length === 0) {
72
+ throw new Error(`Input file is empty: ${inputPath}`);
73
+ }
74
+ const resolvedOutput = resolveOutputPath(inputPath, targetLang, options.outputPath);
75
+ // Ensure output directory exists
76
+ mkdirSync(dirname(resolvedOutput), { recursive: true });
77
+ // Split into chunks if needed
78
+ const chunks = splitIntoChunks(content);
79
+ const translatedParts = [];
80
+ let totalUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
81
+ for (const chunk of chunks) {
82
+ const messages = buildPrompt(chunk, targetLang, templateContent);
83
+ const response = await provider.chat({
84
+ model: "",
85
+ messages,
86
+ });
87
+ translatedParts.push(response.content);
88
+ totalUsage.promptTokens += response.usage.promptTokens;
89
+ totalUsage.completionTokens += response.usage.completionTokens;
90
+ totalUsage.totalTokens += response.usage.totalTokens;
91
+ }
92
+ const translatedContent = translatedParts.join("");
93
+ // Write output
94
+ writeFileSync(resolvedOutput, translatedContent, "utf-8");
95
+ return {
96
+ outputPath: resolvedOutput,
97
+ usage: totalUsage,
98
+ chunks: chunks.length,
99
+ };
100
+ }
101
+ //# sourceMappingURL=translate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.js","sourceRoot":"","sources":["../../src/tasks/translate.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@geolonia/yuuhitsu",
3
+ "version": "0.1.0",
4
+ "description": "右筆 (Yuuhitsu) - AI-powered document operations CLI. Translate, generate, and sync documents using Claude, Gemini, or Ollama.",
5
+ "type": "module",
6
+ "bin": {
7
+ "yuuhitsu": "./dist/cli/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "prepare": "tsc",
12
+ "dev": "tsx src/cli/index.ts",
13
+ "test": "vitest run",
14
+ "test:watch": "vitest",
15
+ "lint": "tsc --noEmit"
16
+ },
17
+ "keywords": ["ai", "cli", "translation", "documentation"],
18
+ "license": "MIT",
19
+ "files": [
20
+ "dist",
21
+ "src/templates",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "dependencies": {
26
+ "@anthropic-ai/sdk": "^0.39.0",
27
+ "@google/genai": "^0.7.0",
28
+ "openai": "^4.77.0",
29
+ "commander": "^13.0.0",
30
+ "yaml": "^2.7.0",
31
+ "dotenv": "^16.4.0",
32
+ "chalk": "^5.4.0"
33
+ },
34
+ "devDependencies": {
35
+ "vitest": "^3.0.0",
36
+ "typescript": "^5.7.0",
37
+ "tsx": "^4.19.0",
38
+ "@types/node": "^22.0.0"
39
+ }
40
+ }
@@ -0,0 +1,7 @@
1
+ You are a link correction assistant. Given the following broken link and its context, suggest the correct URL or path.
2
+
3
+ Broken link: {{brokenLink}}
4
+ Context: {{context}}
5
+ File path: {{filePath}}
6
+
7
+ Respond with only the corrected link.
@@ -0,0 +1,14 @@
1
+ You are a technical documentation writer. Generate comprehensive documentation for the following source code.
2
+
3
+ Output format: {{format}}
4
+
5
+ Rules:
6
+ - Include a brief description of the module/file purpose
7
+ - Document all exported functions, classes, and interfaces
8
+ - Include parameter descriptions and return types
9
+ - Provide usage examples where helpful
10
+ - Use clear, concise language
11
+
12
+ Source code:
13
+
14
+ {{content}}
@@ -0,0 +1,14 @@
1
+ You are a test engineer. Generate a test suite for the following source code.
2
+
3
+ Framework: {{framework}}
4
+
5
+ Rules:
6
+ - Create a describe block for each exported function/class
7
+ - Write it blocks covering normal cases, edge cases, and error cases
8
+ - Use meaningful test descriptions
9
+ - Include necessary imports
10
+ - Mock external dependencies where appropriate
11
+
12
+ Source code:
13
+
14
+ {{content}}
@@ -0,0 +1,16 @@
1
+ You are a research analyst. Research the following topic and produce a structured report.
2
+
3
+ Topic: {{query}}
4
+
5
+ Output format:
6
+ ## Summary
7
+ (Brief overview of findings)
8
+
9
+ ## Analysis
10
+ (Detailed analysis and comparison)
11
+
12
+ ## Recommendations
13
+ (Actionable recommendations)
14
+
15
+ ## Sources
16
+ (List sources used)
@@ -0,0 +1,11 @@
1
+ Convert the following Markdown document to VitePress-compatible format.
2
+
3
+ Rules:
4
+ - Add VitePress frontmatter (title, description)
5
+ - Ensure heading hierarchy starts at h1
6
+ - Convert any non-standard Markdown to VitePress-compatible syntax
7
+ - Preserve all content and links
8
+
9
+ Document:
10
+
11
+ {{content}}
@@ -0,0 +1,18 @@
1
+ You are a professional translator. Translate the following Markdown document to {{targetLanguage}}.
2
+
3
+ Rules:
4
+ - Preserve all Markdown formatting (headings, links, code blocks, tables, lists)
5
+ - Do not translate code blocks, URLs, or file paths
6
+ - Do not translate frontmatter keys (only translate values where appropriate)
7
+ - Maintain the same document structure
8
+ - Produce natural, fluent text in the target language
9
+
10
+ Additional rules for Japanese translation:
11
+ - Use full-width punctuation: 。、?! (not .,?!)
12
+ - Add half-width spaces around English words and numbers (e.g., "Vela とは", "NGSIv2 は", "3 つの")
13
+ - Use natural Japanese terms for technical words where appropriate (e.g., "registration" → "登録", "subscription" → "サブスクリプション")
14
+ - Keep product names, proper nouns, and abbreviations unchanged (e.g., Vela, FIWARE, NGSIv2, NGSI-LD, MCP)
15
+
16
+ Document to translate:
17
+
18
+ {{content}}