@xevy/cli 0.1.2 → 0.1.5
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/bin/xevy.mjs +77 -16
- package/package.json +1 -1
package/bin/xevy.mjs
CHANGED
|
@@ -167,15 +167,22 @@ async function downloadExtension(base, file, destDir, markers) {
|
|
|
167
167
|
return dest;
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
+
// One shared readline interface for the whole prompt sequence. Creating a fresh
|
|
171
|
+
// interface per question drops buffered stdin (the next answer is lost), so we
|
|
172
|
+
// reuse one and close it explicitly when prompting is done.
|
|
173
|
+
let _rl;
|
|
170
174
|
function promptLine(question) {
|
|
175
|
+
if (!_rl) _rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
171
176
|
return new Promise((resolve) => {
|
|
172
|
-
|
|
173
|
-
rl.question(question, (answer) => {
|
|
174
|
-
rl.close();
|
|
175
|
-
resolve(String(answer || "").trim());
|
|
176
|
-
});
|
|
177
|
+
_rl.question(question, (answer) => resolve(String(answer || "").trim()));
|
|
177
178
|
});
|
|
178
179
|
}
|
|
180
|
+
function closePrompts() {
|
|
181
|
+
if (_rl) {
|
|
182
|
+
_rl.close();
|
|
183
|
+
_rl = undefined;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
179
186
|
|
|
180
187
|
function writeCredentials(base, token) {
|
|
181
188
|
fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
@@ -191,31 +198,85 @@ function writeCredentials(base, token) {
|
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
200
|
|
|
201
|
+
/** Return the stored xevy_pk_ token from the credentials file, or null. */
|
|
202
|
+
function readStoredToken() {
|
|
203
|
+
try {
|
|
204
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) return null;
|
|
205
|
+
for (const line of fs.readFileSync(CREDENTIALS_FILE, "utf-8").split(/\r?\n/)) {
|
|
206
|
+
const trimmed = line.trim();
|
|
207
|
+
if (trimmed.startsWith("token=")) {
|
|
208
|
+
const value = trimmed.slice("token=".length).trim();
|
|
209
|
+
return value.startsWith(API_KEY_PREFIX) ? value : null;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} catch {
|
|
213
|
+
// ignore unreadable credentials
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function isYes(answer, defaultYes = true) {
|
|
219
|
+
const a = String(answer || "").trim().toLowerCase();
|
|
220
|
+
if (!a) return defaultYes;
|
|
221
|
+
return a === "y" || a === "yes";
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async function promptNewApiKey(base) {
|
|
225
|
+
console.log("\nGet your API key (this link opens the API keys tab directly):");
|
|
226
|
+
console.log(` ${apiKeysUrl(base)}`);
|
|
227
|
+
console.log(" Sign in if prompted, then click “Create API key” and copy it.\n");
|
|
228
|
+
const token = await promptLine(`Paste your Xevy API key (${API_KEY_PREFIX}…): `);
|
|
229
|
+
if (!token) die("API key is required. Re-run `xevy setup` when you have one.");
|
|
230
|
+
if (!token.startsWith(API_KEY_PREFIX)) {
|
|
231
|
+
die(`That doesn't look like a Xevy API key (expected '${API_KEY_PREFIX}…'). Not saved.`);
|
|
232
|
+
}
|
|
233
|
+
return token;
|
|
234
|
+
}
|
|
235
|
+
|
|
194
236
|
async function setup(opts) {
|
|
195
237
|
console.log("Setting up Xevy for pi\n");
|
|
196
238
|
|
|
197
|
-
// 1. pi
|
|
239
|
+
// 1. Ensure pi is installed (installs it if missing).
|
|
198
240
|
ensurePi();
|
|
241
|
+
|
|
242
|
+
// 2. Extensions:
|
|
243
|
+
// • xevy-core.ts → pi's GLOBAL extensions dir (shared base: ask_question
|
|
244
|
+
// tool + setup/context). Global so it loads in EVERY pi session and is
|
|
245
|
+
// always present for xevy.ts (which requires it). Global-only avoids a
|
|
246
|
+
// double-load: if it were also in ~/.xevy it'd register ask_question twice.
|
|
247
|
+
// • xevy-model.ts → GLOBAL too, so the xevy/* models are available in every
|
|
248
|
+
// pi session (plain `pi` included), not just via `xevy`.
|
|
249
|
+
// • xevy.ts → ~/.xevy/extensions (tools/workflow; loaded by `xevy`,
|
|
250
|
+
// which also auto-loads the global dir → core + model come along).
|
|
251
|
+
await downloadExtension(opts.base, "xevy-core.ts", PI_EXTENSION_DIR, [
|
|
252
|
+
"xevyCoreExtension",
|
|
253
|
+
"registerCommand",
|
|
254
|
+
]);
|
|
255
|
+
await downloadExtension(opts.base, "xevy-model.ts", PI_EXTENSION_DIR, [
|
|
256
|
+
"xevyModelExtension",
|
|
257
|
+
"registerCommand",
|
|
258
|
+
]);
|
|
199
259
|
await downloadExtension(opts.base, XEVY_EXTENSION_FILE, XEVY_EXTENSIONS_DIR, [
|
|
200
260
|
"EXTENSION_VERSION",
|
|
201
261
|
"registerCommand",
|
|
202
262
|
]);
|
|
203
263
|
|
|
204
|
-
//
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
die(`That doesn't look like a Xevy API key (expected '${API_KEY_PREFIX}…'). Not saved.`);
|
|
264
|
+
// 3. API key — reuse the saved one if present, otherwise ask for a new one.
|
|
265
|
+
const existing = readStoredToken();
|
|
266
|
+
let token;
|
|
267
|
+
if (existing) {
|
|
268
|
+
const reuse = await promptLine(`An API key is already saved (…${existing.slice(-4)}). Reuse it? [Y/n]: `);
|
|
269
|
+
token = isYes(reuse) ? existing : await promptNewApiKey(opts.base);
|
|
270
|
+
} else {
|
|
271
|
+
token = await promptNewApiKey(opts.base);
|
|
213
272
|
}
|
|
273
|
+
closePrompts();
|
|
214
274
|
writeCredentials(opts.base, token);
|
|
215
275
|
console.log(`✓ API key stored at ${CREDENTIALS_FILE} (key not shown).`);
|
|
216
276
|
|
|
217
277
|
console.log("\n✓ Setup complete.");
|
|
218
|
-
console.log(" •
|
|
278
|
+
console.log(" • Xevy models (xevy/auto, xevy/fast, …) are available in any pi session — no /login needed.");
|
|
279
|
+
console.log(" • Run `xevy` to open pi with the Xevy tools + models loaded.");
|
|
219
280
|
if (opts.base !== DEFAULT_BASE) console.log(` • Host: ${opts.base}`);
|
|
220
281
|
}
|
|
221
282
|
|
package/package.json
CHANGED