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.
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/package.json +33 -0
- 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();
|