twokey 1.0.0 → 1.0.2
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/README.md +17 -0
- package/bin/twokey.js +204 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -22,6 +22,23 @@ No TTS or text injection is implemented yet.
|
|
|
22
22
|
npm install twokey
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
Start the tool directly:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
twokey
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Default behavior: start the native desktop app in background.
|
|
32
|
+
|
|
33
|
+
Useful CLI options:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
twokey --help
|
|
37
|
+
twokey --cli
|
|
38
|
+
twokey --once "Erklaere kurz den Unterschied zwischen X11 und Wayland"
|
|
39
|
+
twokey --desktop
|
|
40
|
+
```
|
|
41
|
+
|
|
25
42
|
## Minimal Usage
|
|
26
43
|
|
|
27
44
|
```ts
|
package/bin/twokey.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import readline from "node:readline";
|
|
5
|
+
|
|
6
|
+
const VERSION = "1.0.2";
|
|
7
|
+
const DEFAULT_MODEL = process.env.TWOKEY_OLLAMA_MODEL || "qwen2.5:3b";
|
|
8
|
+
const DEFAULT_OLLAMA_URL = process.env.TWOKEY_OLLAMA_URL || "http://127.0.0.1:11434";
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
|
|
12
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
13
|
+
printHelp();
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
18
|
+
console.log(VERSION);
|
|
19
|
+
process.exit(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (args.includes("--desktop")) {
|
|
23
|
+
launchDesktopApp().then((started) => {
|
|
24
|
+
if (started) {
|
|
25
|
+
console.log("TwoKey desktop app started in background.");
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
console.error("No native desktop binary found in PATH.");
|
|
29
|
+
console.error("Install the .deb/.AppImage release and ensure 'twokey-ai' is available in PATH.");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const onceIndex = args.findIndex((value) => value === "--once");
|
|
35
|
+
if (onceIndex >= 0) {
|
|
36
|
+
const prompt = args.slice(onceIndex + 1).join(" ").trim();
|
|
37
|
+
if (!prompt) {
|
|
38
|
+
console.error("Missing prompt after --once");
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
runSinglePrompt(prompt).catch((error) => {
|
|
43
|
+
console.error(error.message || String(error));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
});
|
|
46
|
+
} else if (args.includes("--cli")) {
|
|
47
|
+
startRepl().catch((error) => {
|
|
48
|
+
console.error(error.message || String(error));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
});
|
|
51
|
+
} else {
|
|
52
|
+
launchDesktopApp().then((started) => {
|
|
53
|
+
if (started) {
|
|
54
|
+
console.log("TwoKey desktop app started in background.");
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.error("No native desktop binary found in PATH.");
|
|
59
|
+
console.error("Install the .deb/.AppImage release and ensure 'twokey-ai' is available in PATH.");
|
|
60
|
+
console.error("Use 'twokey --cli' to run terminal mode.");
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function runSinglePrompt(prompt) {
|
|
66
|
+
const answer = await askOllama(prompt);
|
|
67
|
+
console.log(answer);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function startRepl() {
|
|
71
|
+
console.log("TwoKey CLI");
|
|
72
|
+
console.log("Type your prompt. Commands: /help, /exit");
|
|
73
|
+
console.log(`Ollama endpoint: ${DEFAULT_OLLAMA_URL}`);
|
|
74
|
+
console.log(`Model: ${DEFAULT_MODEL}`);
|
|
75
|
+
|
|
76
|
+
const rl = readline.createInterface({
|
|
77
|
+
input: process.stdin,
|
|
78
|
+
output: process.stdout,
|
|
79
|
+
prompt: "twokey> ",
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
rl.prompt();
|
|
83
|
+
|
|
84
|
+
rl.on("line", async (line) => {
|
|
85
|
+
const input = line.trim();
|
|
86
|
+
if (!input) {
|
|
87
|
+
rl.prompt();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (input === "/exit" || input === "/quit") {
|
|
92
|
+
rl.close();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (input === "/help") {
|
|
97
|
+
printHelp();
|
|
98
|
+
rl.prompt();
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const answer = await askOllama(input);
|
|
104
|
+
console.log(`\n${answer}\n`);
|
|
105
|
+
} catch (error) {
|
|
106
|
+
console.error(error.message || String(error));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
rl.prompt();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
rl.on("close", () => {
|
|
113
|
+
console.log("bye");
|
|
114
|
+
process.exit(0);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function askOllama(prompt) {
|
|
119
|
+
const response = await fetch(`${DEFAULT_OLLAMA_URL.replace(/\/$/, "")}/api/chat`, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
},
|
|
124
|
+
body: JSON.stringify({
|
|
125
|
+
model: DEFAULT_MODEL,
|
|
126
|
+
stream: false,
|
|
127
|
+
messages: [
|
|
128
|
+
{
|
|
129
|
+
role: "system",
|
|
130
|
+
content: "You are TwoKey, a concise Linux assistant.",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
role: "user",
|
|
134
|
+
content: prompt,
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
options: {
|
|
138
|
+
temperature: 0.3,
|
|
139
|
+
num_predict: 384,
|
|
140
|
+
},
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
throw new Error(`Ollama request failed with HTTP ${response.status}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const payload = await response.json();
|
|
149
|
+
const content = payload?.message?.content?.trim();
|
|
150
|
+
if (!content) {
|
|
151
|
+
throw new Error("Ollama returned an empty response");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return content;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function launchDesktopApp() {
|
|
158
|
+
const candidates = [];
|
|
159
|
+
if (process.env.TWOKEY_DESKTOP_CMD) {
|
|
160
|
+
candidates.push(process.env.TWOKEY_DESKTOP_CMD);
|
|
161
|
+
}
|
|
162
|
+
candidates.push("twokey-ai", "twokey-desktop");
|
|
163
|
+
|
|
164
|
+
for (const command of candidates) {
|
|
165
|
+
const started = await spawnDetached(command);
|
|
166
|
+
if (started) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function spawnDetached(command) {
|
|
175
|
+
return new Promise((resolve) => {
|
|
176
|
+
const child = spawn(command, [], {
|
|
177
|
+
detached: true,
|
|
178
|
+
stdio: "ignore",
|
|
179
|
+
shell: false,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
child.once("spawn", () => {
|
|
183
|
+
child.unref();
|
|
184
|
+
resolve(true);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
child.once("error", () => {
|
|
188
|
+
resolve(false);
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function printHelp() {
|
|
194
|
+
console.log("twokey <command/options>");
|
|
195
|
+
console.log("");
|
|
196
|
+
console.log("Options:");
|
|
197
|
+
console.log(" --help, -h Show help");
|
|
198
|
+
console.log(" --version, -v Show version");
|
|
199
|
+
console.log(" --cli Start interactive terminal mode");
|
|
200
|
+
console.log(" --once <prompt> Send one prompt to Ollama and print response");
|
|
201
|
+
console.log(" --desktop Start native desktop app in background");
|
|
202
|
+
console.log("");
|
|
203
|
+
console.log("Without options, twokey starts the native desktop app in background.");
|
|
204
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "twokey",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Linux-first desktop AI assistant built with Tauri, React, and TypeScript.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
"main": "./lib/index.js",
|
|
16
16
|
"module": "./lib/index.js",
|
|
17
17
|
"types": "./lib/index.d.ts",
|
|
18
|
+
"bin": {
|
|
19
|
+
"twokey": "./bin/twokey.js"
|
|
20
|
+
},
|
|
18
21
|
"exports": {
|
|
19
22
|
".": {
|
|
20
23
|
"types": "./lib/index.d.ts",
|
|
@@ -23,6 +26,7 @@
|
|
|
23
26
|
}
|
|
24
27
|
},
|
|
25
28
|
"files": [
|
|
29
|
+
"bin",
|
|
26
30
|
"lib",
|
|
27
31
|
"README.md",
|
|
28
32
|
"LICENSE"
|