write-helper 1.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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/package.json +33 -0
  4. package/src/index.js +197 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marvin
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,105 @@
1
+ # WriteHelper (wh)
2
+
3
+ A CLI tool to improve, translate, extend, and continue text using SAP AI Core.
4
+
5
+ ## Prerequisites
6
+
7
+ - Node.js >= 20
8
+ - Access to an SAP AI Core instance with an orchestration deployment
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ git clone <repo-url> && cd WriteHelper
14
+ npm install
15
+ npm link
16
+ ```
17
+
18
+ This makes the `wh` command available globally.
19
+
20
+ ## Uninstall
21
+
22
+ ```bash
23
+ npm unlink -g write-helper
24
+ rm -rf ~/.wh
25
+ ```
26
+
27
+ ## Configuration
28
+
29
+ Before using the tool, configure your AI Core credentials. Pick one of the following:
30
+
31
+ ### Interactive setup
32
+
33
+ ```bash
34
+ wh config
35
+ ```
36
+
37
+ You will be prompted for:
38
+ - AI Core Service URL
39
+ - Client ID
40
+ - Client Secret
41
+ - Auth URL (token endpoint base)
42
+
43
+ Credentials are saved to `~/.wh/config.json`.
44
+
45
+ ### Import from environment variable
46
+
47
+ If you already have `AICORE_SERVICE_KEY` set (the full service key JSON):
48
+
49
+ ```bash
50
+ wh config --env
51
+ ```
52
+
53
+ ### Environment variable only
54
+
55
+ You can also skip `wh config` entirely and just export the variable:
56
+
57
+ ```bash
58
+ export AICORE_SERVICE_KEY='{"clientid":"...","clientsecret":"...","url":"...","serviceurls":{"AI_API_URL":"..."}}'
59
+ ```
60
+
61
+ ## Usage
62
+
63
+ ```
64
+ wh <command> [options] "your text"
65
+ ```
66
+
67
+ ### Commands
68
+
69
+ | Command | Description |
70
+ |-------------|--------------------------------------------------|
71
+ | `improve` | Fix grammar, spelling, and clarity (keeps tone) |
72
+ | `translate` | Translate text to a target language |
73
+ | `extend` | Elaborate and expand text with more detail |
74
+ | `continue` | Continue writing from where the text left off |
75
+ | `config` | Configure AI Core credentials |
76
+
77
+ ### Options
78
+
79
+ | Option | Applies to | Description | Default |
80
+ |---------------------|-------------|--------------------------------------|----------|
81
+ | `-m, --model <name>`| all commands| Model to use | `gpt-4o` |
82
+ | `-l, --lang <code>` | `translate` | Target language (e.g. `de`, `fr`) | required |
83
+ | `-e, --env` | `config` | Import credentials from env variable | |
84
+
85
+ ### Examples
86
+
87
+ ```bash
88
+ # Improve text
89
+ wh improve "i think we should reconsider this descision"
90
+
91
+ # Translate to German
92
+ wh translate --lang=de "Hello, how are you?"
93
+
94
+ # Extend a short message into a longer one
95
+ wh extend "We should migrate to the new API"
96
+
97
+ # Continue writing from an existing paragraph
98
+ wh continue "Dear team, I wanted to follow up on our discussion from last week."
99
+
100
+ # Use a different model
101
+ wh improve --model=gpt-4o-mini "some text with erors"
102
+
103
+ # Pipe output to clipboard (macOS)
104
+ wh improve "some text" | pbcopy
105
+ ```
package/package.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "name": "write-helper",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool to improve, correct, and translate text using SAP AI Core",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "schmarvinius",
8
+ "keywords": [
9
+ "cli",
10
+ "writing",
11
+ "text",
12
+ "improve",
13
+ "translate",
14
+ "ai",
15
+ "sap-ai-core"
16
+ ],
17
+ "files": [
18
+ "src"
19
+ ],
20
+ "bin": {
21
+ "wh": "./src/index.js"
22
+ },
23
+ "scripts": {
24
+ "start": "node src/index.js"
25
+ },
26
+ "engines": {
27
+ "node": ">=20"
28
+ },
29
+ "dependencies": {
30
+ "@sap-ai-sdk/orchestration": "^1",
31
+ "commander": "^13"
32
+ }
33
+ }
package/src/index.js ADDED
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { homedir } from 'node:os';
7
+ import { createInterface } from 'node:readline/promises';
8
+
9
+ const CONFIG_DIR = join(homedir(), '.wh');
10
+ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
11
+
12
+ function loadConfig() {
13
+ if (!existsSync(CONFIG_FILE)) return null;
14
+ return JSON.parse(readFileSync(CONFIG_FILE, 'utf-8'));
15
+ }
16
+
17
+ function saveConfig(config) {
18
+ mkdirSync(CONFIG_DIR, { recursive: true });
19
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n');
20
+ }
21
+
22
+ function ensureCredentials() {
23
+ if (process.env.AICORE_SERVICE_KEY) return;
24
+ const config = loadConfig();
25
+ if (!config?.serviceKey) {
26
+ console.error('No AI Core credentials configured. Run "wh config" first or set AICORE_SERVICE_KEY env variable.');
27
+ process.exit(1);
28
+ }
29
+ process.env.AICORE_SERVICE_KEY = JSON.stringify(config.serviceKey);
30
+ }
31
+
32
+ async function importOrchestrationClient() {
33
+ ensureCredentials();
34
+ const { setLogLevel } = await import('@sap-cloud-sdk/util');
35
+ setLogLevel('warn', 'context');
36
+ const { OrchestrationClient } = await import('@sap-ai-sdk/orchestration');
37
+ return OrchestrationClient;
38
+ }
39
+
40
+ const IMPROVE_SYSTEM_PROMPT =
41
+ 'You are a professional writing assistant. Your task is to improve, correct, and rewrite the given text.\n\n' +
42
+ 'Rules:\n' +
43
+ '- Fix grammar, spelling, and punctuation errors\n' +
44
+ '- Improve clarity and readability\n' +
45
+ '- Do NOT change the tone or voice of the text. If it is casual, keep it casual. If it is formal, keep it formal. Do not make it more formal or casual than it already is\n' +
46
+ '- Never use em dashes. Use commas, semicolons, periods, or parentheses instead\n' +
47
+ '- Do not add new information or change the meaning\n' +
48
+ '- Output ONLY the improved text, nothing else. No explanations, no quotes, no prefixes';
49
+
50
+ const EXTEND_SYSTEM_PROMPT =
51
+ 'You are a professional writing assistant. Your task is to elaborate and expand on the given text, making it longer and more detailed.\n\n' +
52
+ 'Rules:\n' +
53
+ '- Keep the same message, meaning, and intent\n' +
54
+ '- Match the original tone and voice exactly. Do NOT change the tone\n' +
55
+ '- Add more detail, examples, or supporting points where appropriate\n' +
56
+ '- Never use em dashes. Use commas, semicolons, periods, or parentheses instead\n' +
57
+ '- Output ONLY the expanded text, nothing else. No explanations, no quotes, no prefixes';
58
+
59
+ const CONTINUE_SYSTEM_PROMPT =
60
+ 'You are a professional writing assistant. Your task is to continue writing from where the given text left off.\n\n' +
61
+ 'Rules:\n' +
62
+ '- Continue naturally from the end of the text\n' +
63
+ '- Match the original tone, voice, and style exactly. Do NOT change the tone\n' +
64
+ '- Stay on topic and maintain coherence with the original text\n' +
65
+ '- Never use em dashes. Use commas, semicolons, periods, or parentheses instead\n' +
66
+ '- Output ONLY the continuation (new text), nothing else. No explanations, no quotes, no prefixes';
67
+
68
+ async function run(systemPrompt, userText, model) {
69
+ const OrchestrationClient = await importOrchestrationClient();
70
+ const client = new OrchestrationClient({
71
+ llm: {
72
+ model_name: model
73
+ }
74
+ });
75
+
76
+ const response = await client.chatCompletion({
77
+ messages: [
78
+ { role: 'system', content: systemPrompt },
79
+ { role: 'user', content: userText }
80
+ ]
81
+ });
82
+
83
+ return response.getContent();
84
+ }
85
+
86
+ const program = new Command();
87
+
88
+ program
89
+ .name('wh')
90
+ .description('CLI tool to improve and translate text using SAP AI Core')
91
+ .version('1.0.0');
92
+
93
+ program
94
+ .command('improve')
95
+ .description('Improve, correct, and rewrite text while preserving tone')
96
+ .argument('<text>', 'text to improve')
97
+ .option('-m, --model <name>', 'model to use', 'gpt-4o')
98
+ .action(async (text, opts) => {
99
+ try {
100
+ const result = await run(IMPROVE_SYSTEM_PROMPT, text, opts.model);
101
+ console.log(result);
102
+ } catch (err) {
103
+ console.error('Error:', err.message);
104
+ process.exit(1);
105
+ }
106
+ });
107
+
108
+ program
109
+ .command('translate')
110
+ .description('Translate text to a target language')
111
+ .argument('<text>', 'text to translate')
112
+ .requiredOption('-l, --lang <code>', 'target language (e.g. de, fr, es)')
113
+ .option('-m, --model <name>', 'model to use', 'gpt-4o')
114
+ .action(async (text, opts) => {
115
+ try {
116
+ const systemPrompt =
117
+ `You are a professional translator. Translate the given text to ${opts.lang}.\n\n` +
118
+ 'Rules:\n' +
119
+ '- Produce a natural, fluent translation, not a word-for-word literal one\n' +
120
+ '- Preserve the original tone and register\n' +
121
+ '- Never use em dashes. Use commas, semicolons, periods, or parentheses instead\n' +
122
+ '- Output ONLY the translated text, nothing else. No explanations, no quotes, no prefixes';
123
+ const result = await run(systemPrompt, text, opts.model);
124
+ console.log(result);
125
+ } catch (err) {
126
+ console.error('Error:', err.message);
127
+ process.exit(1);
128
+ }
129
+ });
130
+
131
+ program
132
+ .command('extend')
133
+ .description('Elaborate and expand text to make it longer and more detailed')
134
+ .argument('<text>', 'text to extend')
135
+ .option('-m, --model <name>', 'model to use', 'gpt-4o')
136
+ .action(async (text, opts) => {
137
+ try {
138
+ const result = await run(EXTEND_SYSTEM_PROMPT, text, opts.model);
139
+ console.log(result);
140
+ } catch (err) {
141
+ console.error('Error:', err.message);
142
+ process.exit(1);
143
+ }
144
+ });
145
+
146
+ program
147
+ .command('continue')
148
+ .description('Continue writing from where the text left off')
149
+ .argument('<text>', 'text to continue from')
150
+ .option('-m, --model <name>', 'model to use', 'gpt-4o')
151
+ .action(async (text, opts) => {
152
+ try {
153
+ const result = await run(CONTINUE_SYSTEM_PROMPT, text, opts.model);
154
+ console.log(result);
155
+ } catch (err) {
156
+ console.error('Error:', err.message);
157
+ process.exit(1);
158
+ }
159
+ });
160
+
161
+ program
162
+ .command('config')
163
+ .description('Configure AI Core credentials')
164
+ .option('-e, --env', 'import credentials from AICORE_SERVICE_KEY env variable')
165
+ .action(async (opts) => {
166
+ try {
167
+ let serviceKey;
168
+ if (opts.env) {
169
+ if (!process.env.AICORE_SERVICE_KEY) {
170
+ console.error('AICORE_SERVICE_KEY environment variable is not set.');
171
+ process.exit(1);
172
+ }
173
+ serviceKey = JSON.parse(process.env.AICORE_SERVICE_KEY);
174
+ console.log('Imported credentials from AICORE_SERVICE_KEY.');
175
+ } else {
176
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
177
+ const url = await rl.question('AI Core Service URL: ');
178
+ const clientid = await rl.question('Client ID: ');
179
+ const clientsecret = await rl.question('Client Secret: ');
180
+ const authUrl = await rl.question('Auth URL (token endpoint base): ');
181
+ rl.close();
182
+ serviceKey = {
183
+ serviceurls: { AI_API_URL: url },
184
+ clientid,
185
+ clientsecret,
186
+ url: authUrl
187
+ };
188
+ }
189
+ saveConfig({ serviceKey });
190
+ console.log(`Credentials saved to ${CONFIG_FILE}`);
191
+ } catch (err) {
192
+ console.error('Error:', err.message);
193
+ process.exit(1);
194
+ }
195
+ });
196
+
197
+ program.parse();