twokey 1.0.2 → 1.0.3
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 +1 -0
- package/bin/twokey.js +73 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,7 @@ twokey
|
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
Default behavior: start the native desktop app in background.
|
|
32
|
+
If no desktop binary is installed yet, `twokey` attempts to download an AppImage from the latest GitHub release into `~/.local/share/twokey/bin/` and starts it.
|
|
32
33
|
|
|
33
34
|
Useful CLI options:
|
|
34
35
|
|
package/bin/twokey.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import os from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
4
7
|
import readline from "node:readline";
|
|
5
8
|
|
|
6
|
-
const VERSION = "1.0.
|
|
9
|
+
const VERSION = "1.0.3";
|
|
7
10
|
const DEFAULT_MODEL = process.env.TWOKEY_OLLAMA_MODEL || "qwen2.5:3b";
|
|
8
11
|
const DEFAULT_OLLAMA_URL = process.env.TWOKEY_OLLAMA_URL || "http://127.0.0.1:11434";
|
|
12
|
+
const LATEST_RELEASE_API = "https://api.github.com/repos/meinzeug/twokey/releases/latest";
|
|
13
|
+
const APPIMAGE_DIR = path.join(os.homedir(), ".local", "share", "twokey", "bin");
|
|
14
|
+
const APPIMAGE_PATH = path.join(APPIMAGE_DIR, "twokey-ai.AppImage");
|
|
9
15
|
|
|
10
16
|
const args = process.argv.slice(2);
|
|
11
17
|
|
|
@@ -55,8 +61,8 @@ if (onceIndex >= 0) {
|
|
|
55
61
|
process.exit(0);
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
console.error("
|
|
59
|
-
console.error("
|
|
64
|
+
console.error("Could not start desktop app.");
|
|
65
|
+
console.error("Tried system binaries and auto-download from GitHub Releases.");
|
|
60
66
|
console.error("Use 'twokey --cli' to run terminal mode.");
|
|
61
67
|
process.exit(1);
|
|
62
68
|
});
|
|
@@ -159,7 +165,7 @@ async function launchDesktopApp() {
|
|
|
159
165
|
if (process.env.TWOKEY_DESKTOP_CMD) {
|
|
160
166
|
candidates.push(process.env.TWOKEY_DESKTOP_CMD);
|
|
161
167
|
}
|
|
162
|
-
candidates.push("twokey-ai", "twokey-desktop");
|
|
168
|
+
candidates.push("twokey-ai", "twokey-desktop", APPIMAGE_PATH);
|
|
163
169
|
|
|
164
170
|
for (const command of candidates) {
|
|
165
171
|
const started = await spawnDetached(command);
|
|
@@ -168,9 +174,71 @@ async function launchDesktopApp() {
|
|
|
168
174
|
}
|
|
169
175
|
}
|
|
170
176
|
|
|
177
|
+
try {
|
|
178
|
+
const downloaded = await ensureLocalAppImage();
|
|
179
|
+
if (downloaded) {
|
|
180
|
+
return spawnDetached(APPIMAGE_PATH);
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
|
|
171
186
|
return false;
|
|
172
187
|
}
|
|
173
188
|
|
|
189
|
+
async function ensureLocalAppImage() {
|
|
190
|
+
try {
|
|
191
|
+
await fs.promises.access(APPIMAGE_PATH, fs.constants.X_OK);
|
|
192
|
+
return true;
|
|
193
|
+
} catch {
|
|
194
|
+
// Not installed yet.
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
await fs.promises.mkdir(APPIMAGE_DIR, { recursive: true });
|
|
198
|
+
const assetUrl = await resolveLatestAppImageUrl();
|
|
199
|
+
if (!assetUrl) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const response = await fetch(assetUrl, {
|
|
204
|
+
headers: {
|
|
205
|
+
"User-Agent": "twokey-cli",
|
|
206
|
+
Accept: "application/octet-stream",
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
if (!response.ok) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const data = Buffer.from(await response.arrayBuffer());
|
|
215
|
+
await fs.promises.writeFile(APPIMAGE_PATH, data, { mode: 0o755 });
|
|
216
|
+
|
|
217
|
+
await fs.promises.chmod(APPIMAGE_PATH, 0o755);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function resolveLatestAppImageUrl() {
|
|
222
|
+
const response = await fetch(LATEST_RELEASE_API, {
|
|
223
|
+
headers: {
|
|
224
|
+
"User-Agent": "twokey-cli",
|
|
225
|
+
Accept: "application/vnd.github+json",
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (!response.ok) {
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const payload = await response.json();
|
|
234
|
+
const assets = Array.isArray(payload.assets) ? payload.assets : [];
|
|
235
|
+
const appImage = assets.find(
|
|
236
|
+
(asset) => typeof asset?.name === "string" && asset.name.endsWith(".AppImage") && asset.name.includes("amd64"),
|
|
237
|
+
) || assets.find((asset) => typeof asset?.name === "string" && asset.name.endsWith(".AppImage"));
|
|
238
|
+
|
|
239
|
+
return appImage?.browser_download_url ?? null;
|
|
240
|
+
}
|
|
241
|
+
|
|
174
242
|
function spawnDetached(command) {
|
|
175
243
|
return new Promise((resolve) => {
|
|
176
244
|
const child = spawn(command, [], {
|
|
@@ -201,4 +269,5 @@ function printHelp() {
|
|
|
201
269
|
console.log(" --desktop Start native desktop app in background");
|
|
202
270
|
console.log("");
|
|
203
271
|
console.log("Without options, twokey starts the native desktop app in background.");
|
|
272
|
+
console.log("If no desktop binary is installed, twokey tries to download an AppImage from latest GitHub release.");
|
|
204
273
|
}
|