@xevy/cli 0.1.3 → 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 +70 -19
- 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,6 +198,41 @@ 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
|
|
|
@@ -198,28 +240,37 @@ async function setup(opts) {
|
|
|
198
240
|
ensurePi();
|
|
199
241
|
|
|
200
242
|
// 2. Extensions:
|
|
201
|
-
// • xevy.ts
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
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",
|
|
206
253
|
"registerCommand",
|
|
207
254
|
]);
|
|
208
255
|
await downloadExtension(opts.base, "xevy-model.ts", PI_EXTENSION_DIR, [
|
|
209
256
|
"xevyModelExtension",
|
|
210
257
|
"registerCommand",
|
|
211
258
|
]);
|
|
259
|
+
await downloadExtension(opts.base, XEVY_EXTENSION_FILE, XEVY_EXTENSIONS_DIR, [
|
|
260
|
+
"EXTENSION_VERSION",
|
|
261
|
+
"registerCommand",
|
|
262
|
+
]);
|
|
212
263
|
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
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);
|
|
222
272
|
}
|
|
273
|
+
closePrompts();
|
|
223
274
|
writeCredentials(opts.base, token);
|
|
224
275
|
console.log(`✓ API key stored at ${CREDENTIALS_FILE} (key not shown).`);
|
|
225
276
|
|
package/package.json
CHANGED