@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.
Files changed (2) hide show
  1. package/bin/xevy.mjs +77 -16
  2. 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
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
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 + the extension
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
- // 2. API key — with a direct link to the exact page to generate one.
205
- console.log("\nGet your API key (this link opens the API keys tab directly):");
206
- console.log(` ${apiKeysUrl(opts.base)}`);
207
- console.log(" Sign in if prompted, then click “Create API key” and copy it.\n");
208
-
209
- const token = await promptLine(`Paste your Xevy API key (${API_KEY_PREFIX}…): `);
210
- if (!token) die("API key is required. Re-run `xevy setup` when you have one.");
211
- if (!token.startsWith(API_KEY_PREFIX)) {
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(" • Run `xevy` to open pi with the Xevy extension loaded.");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xevy/cli",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "Xevy CLI — `xevy setup` installs the pi agent + the Xevy pi extension and stores your API key; `xevy` opens pi with it.",
5
5
  "type": "module",
6
6
  "keywords": [