@xevy/cli 0.1.3 → 0.1.6
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 +9 -12
- package/bin/xevy.mjs +90 -55
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,9 +10,13 @@ npx @xevy/cli # open pi with the Xevy extension loaded
|
|
|
10
10
|
## `xevy setup`
|
|
11
11
|
|
|
12
12
|
1. Installs the **pi agent** if it isn't already on your PATH.
|
|
13
|
-
2.
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
2. Installs the Xevy extensions:
|
|
14
|
+
- **`xevy-core.ts`** (shared UI + setup/context), **`xevy-model.ts`** (the
|
|
15
|
+
`xevy/*` models), and **`xevy-mcp.ts`** (the MCP→pi tool bridge) into pi's
|
|
16
|
+
**global** dir `~/.pi/agent/extensions/`, so they load in *every* pi session.
|
|
17
|
+
- **`xevy.ts`** (tools + workflow commands) into **`~/.xevy/extensions/`**.
|
|
18
|
+
3. Asks for your **API key** (reuses an already-saved one if present) and prints a
|
|
19
|
+
**direct link to the API-keys tab** to generate one, then stores it in
|
|
16
20
|
`~/.config/xevy/credentials` (mode `600`, never printed).
|
|
17
21
|
|
|
18
22
|
The API-keys link opens **Settings → Profile → API keys** directly:
|
|
@@ -20,21 +24,14 @@ The API-keys link opens **Settings → Profile → API keys** directly:
|
|
|
20
24
|
|
|
21
25
|
## `xevy`
|
|
22
26
|
|
|
23
|
-
Opens pi, loading
|
|
24
|
-
|
|
27
|
+
Opens pi, loading `~/.xevy/extensions` via `pi -e …` (pi also auto-loads the
|
|
28
|
+
global dir, so core + model + MCP come along). Extra arguments are forwarded to pi:
|
|
25
29
|
|
|
26
30
|
```bash
|
|
27
31
|
xevy # interactive pi session with Xevy
|
|
28
32
|
xevy "summarise this repo" # forwards the prompt to pi
|
|
29
33
|
```
|
|
30
34
|
|
|
31
|
-
## `xevy pi` (alternative)
|
|
32
|
-
|
|
33
|
-
Installs the split **model + MCP** extensions (`xevy-model.ts`, `xevy-mcp.ts`)
|
|
34
|
-
into pi's own extensions dir and authenticates over **OAuth** (`/login xevy`) —
|
|
35
|
-
no API key. Use this instead of `setup` if you prefer OAuth and the hosted
|
|
36
|
-
`xevy/*` models + MCP tool bridge.
|
|
37
|
-
|
|
38
35
|
## Options
|
|
39
36
|
|
|
40
37
|
```
|
package/bin/xevy.mjs
CHANGED
|
@@ -2,15 +2,14 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @xevy/cli — the `xevy` command.
|
|
4
4
|
*
|
|
5
|
-
* xevy setup Install the pi agent (if missing)
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* xevy pi
|
|
9
|
-
* extensions dir, authenticated via OAuth.
|
|
5
|
+
* xevy setup Install the pi agent (if missing); install the Xevy extensions
|
|
6
|
+
* (core + model + MCP globally, xevy.ts into ~/.xevy/extensions);
|
|
7
|
+
* and store your API key.
|
|
8
|
+
* xevy Open pi with the Xevy extensions.
|
|
10
9
|
*
|
|
11
|
-
* `xevy setup`
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* `xevy setup` fetches the extensions from the server and asks for an API key
|
|
11
|
+
* (with a direct link to the exact page to generate one), stored in
|
|
12
|
+
* ~/.config/xevy/credentials. Then `xevy` launches pi with them.
|
|
14
13
|
*/
|
|
15
14
|
import { spawnSync } from "node:child_process";
|
|
16
15
|
import fs from "node:fs";
|
|
@@ -40,12 +39,9 @@ const CREDENTIALS_DIR = path.join(
|
|
|
40
39
|
);
|
|
41
40
|
const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials");
|
|
42
41
|
|
|
43
|
-
//
|
|
42
|
+
// pi's global extensions dir — core + model + MCP are installed here so they
|
|
43
|
+
// load in every pi session.
|
|
44
44
|
const PI_EXTENSION_DIR = path.join(os.homedir(), ".pi", "agent", "extensions");
|
|
45
|
-
const PI_EXTENSIONS = [
|
|
46
|
-
{ file: "xevy-model.ts", marker: "xevyModelExtension" },
|
|
47
|
-
{ file: "xevy-mcp.ts", marker: "xevyMcpExtension" },
|
|
48
|
-
];
|
|
49
45
|
|
|
50
46
|
function apiKeysUrl(base) {
|
|
51
47
|
// Direct link to the API keys tab (Profile → API keys), reachable without a
|
|
@@ -71,8 +67,7 @@ function usage() {
|
|
|
71
67
|
console.log(`Xevy CLI
|
|
72
68
|
|
|
73
69
|
xevy Open pi with the Xevy extensions in ~/.xevy/extensions
|
|
74
|
-
xevy setup [--base u] Install pi (if missing) + the Xevy
|
|
75
|
-
xevy pi [--base u] (alt) Install the split model + MCP extensions over OAuth
|
|
70
|
+
xevy setup [--base u] Install pi (if missing) + the Xevy extensions, store your API key
|
|
76
71
|
|
|
77
72
|
Options:
|
|
78
73
|
--base <url> Xevy host (default: XEVY_BASE or ${DEFAULT_BASE})
|
|
@@ -95,9 +90,9 @@ function parseArgv(argv) {
|
|
|
95
90
|
if (first === "-h" || first === "--help" || first === "help") return { command: "help", base };
|
|
96
91
|
if (first === "-v" || first === "--version") return { command: "version", base };
|
|
97
92
|
|
|
98
|
-
if (first === "setup"
|
|
93
|
+
if (first === "setup") {
|
|
99
94
|
args.shift();
|
|
100
|
-
const opts = { command:
|
|
95
|
+
const opts = { command: "setup", base, piArgs: [] };
|
|
101
96
|
while (args.length > 0) {
|
|
102
97
|
const a = args.shift();
|
|
103
98
|
if (a === "--base") {
|
|
@@ -106,7 +101,7 @@ function parseArgv(argv) {
|
|
|
106
101
|
opts.base = v.replace(/\/$/, "");
|
|
107
102
|
continue;
|
|
108
103
|
}
|
|
109
|
-
die(`Unknown option for \`xevy
|
|
104
|
+
die(`Unknown option for \`xevy setup\`: ${a}. Try \`xevy --help\`.`);
|
|
110
105
|
}
|
|
111
106
|
return opts;
|
|
112
107
|
}
|
|
@@ -167,15 +162,22 @@ async function downloadExtension(base, file, destDir, markers) {
|
|
|
167
162
|
return dest;
|
|
168
163
|
}
|
|
169
164
|
|
|
165
|
+
// One shared readline interface for the whole prompt sequence. Creating a fresh
|
|
166
|
+
// interface per question drops buffered stdin (the next answer is lost), so we
|
|
167
|
+
// reuse one and close it explicitly when prompting is done.
|
|
168
|
+
let _rl;
|
|
170
169
|
function promptLine(question) {
|
|
170
|
+
if (!_rl) _rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
171
171
|
return new Promise((resolve) => {
|
|
172
|
-
|
|
173
|
-
rl.question(question, (answer) => {
|
|
174
|
-
rl.close();
|
|
175
|
-
resolve(String(answer || "").trim());
|
|
176
|
-
});
|
|
172
|
+
_rl.question(question, (answer) => resolve(String(answer || "").trim()));
|
|
177
173
|
});
|
|
178
174
|
}
|
|
175
|
+
function closePrompts() {
|
|
176
|
+
if (_rl) {
|
|
177
|
+
_rl.close();
|
|
178
|
+
_rl = undefined;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
179
181
|
|
|
180
182
|
function writeCredentials(base, token) {
|
|
181
183
|
fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
@@ -191,6 +193,41 @@ function writeCredentials(base, token) {
|
|
|
191
193
|
}
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
/** Return the stored xevy_pk_ token from the credentials file, or null. */
|
|
197
|
+
function readStoredToken() {
|
|
198
|
+
try {
|
|
199
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) return null;
|
|
200
|
+
for (const line of fs.readFileSync(CREDENTIALS_FILE, "utf-8").split(/\r?\n/)) {
|
|
201
|
+
const trimmed = line.trim();
|
|
202
|
+
if (trimmed.startsWith("token=")) {
|
|
203
|
+
const value = trimmed.slice("token=".length).trim();
|
|
204
|
+
return value.startsWith(API_KEY_PREFIX) ? value : null;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
} catch {
|
|
208
|
+
// ignore unreadable credentials
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function isYes(answer, defaultYes = true) {
|
|
214
|
+
const a = String(answer || "").trim().toLowerCase();
|
|
215
|
+
if (!a) return defaultYes;
|
|
216
|
+
return a === "y" || a === "yes";
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function promptNewApiKey(base) {
|
|
220
|
+
console.log("\nGet your API key (this link opens the API keys tab directly):");
|
|
221
|
+
console.log(` ${apiKeysUrl(base)}`);
|
|
222
|
+
console.log(" Sign in if prompted, then click “Create API key” and copy it.\n");
|
|
223
|
+
const token = await promptLine(`Paste your Xevy API key (${API_KEY_PREFIX}…): `);
|
|
224
|
+
if (!token) die("API key is required. Re-run `xevy setup` when you have one.");
|
|
225
|
+
if (!token.startsWith(API_KEY_PREFIX)) {
|
|
226
|
+
die(`That doesn't look like a Xevy API key (expected '${API_KEY_PREFIX}…'). Not saved.`);
|
|
227
|
+
}
|
|
228
|
+
return token;
|
|
229
|
+
}
|
|
230
|
+
|
|
194
231
|
async function setup(opts) {
|
|
195
232
|
console.log("Setting up Xevy for pi\n");
|
|
196
233
|
|
|
@@ -198,33 +235,48 @@ async function setup(opts) {
|
|
|
198
235
|
ensurePi();
|
|
199
236
|
|
|
200
237
|
// 2. Extensions:
|
|
201
|
-
// • xevy.ts
|
|
202
|
-
//
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
238
|
+
// • xevy-core.ts → pi's GLOBAL extensions dir (shared base: ask_question
|
|
239
|
+
// tool + setup/context). Global so it loads in EVERY pi session and is
|
|
240
|
+
// always present for xevy.ts (which requires it). Global-only avoids a
|
|
241
|
+
// double-load: if it were also in ~/.xevy it'd register ask_question twice.
|
|
242
|
+
// • xevy-model.ts → GLOBAL too, so the xevy/* models are available in every
|
|
243
|
+
// pi session (plain `pi` included), not just via `xevy`.
|
|
244
|
+
// • xevy-mcp.ts → GLOBAL too — the MCP→pi tool bridge (auth reused from
|
|
245
|
+
// the model login), available in every pi session.
|
|
246
|
+
// • xevy.ts → ~/.xevy/extensions (tools/workflow; loaded by `xevy`,
|
|
247
|
+
// which also auto-loads the global dir → core + model + MCP come along).
|
|
248
|
+
await downloadExtension(opts.base, "xevy-core.ts", PI_EXTENSION_DIR, [
|
|
249
|
+
"xevyCoreExtension",
|
|
206
250
|
"registerCommand",
|
|
207
251
|
]);
|
|
208
252
|
await downloadExtension(opts.base, "xevy-model.ts", PI_EXTENSION_DIR, [
|
|
209
253
|
"xevyModelExtension",
|
|
210
254
|
"registerCommand",
|
|
211
255
|
]);
|
|
256
|
+
await downloadExtension(opts.base, "xevy-mcp.ts", PI_EXTENSION_DIR, [
|
|
257
|
+
"xevyMcpExtension",
|
|
258
|
+
"registerCommand",
|
|
259
|
+
]);
|
|
260
|
+
await downloadExtension(opts.base, XEVY_EXTENSION_FILE, XEVY_EXTENSIONS_DIR, [
|
|
261
|
+
"EXTENSION_VERSION",
|
|
262
|
+
"registerCommand",
|
|
263
|
+
]);
|
|
212
264
|
|
|
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.`);
|
|
265
|
+
// 3. API key — reuse the saved one if present, otherwise ask for a new one.
|
|
266
|
+
const existing = readStoredToken();
|
|
267
|
+
let token;
|
|
268
|
+
if (existing) {
|
|
269
|
+
const reuse = await promptLine(`An API key is already saved (…${existing.slice(-4)}). Reuse it? [Y/n]: `);
|
|
270
|
+
token = isYes(reuse) ? existing : await promptNewApiKey(opts.base);
|
|
271
|
+
} else {
|
|
272
|
+
token = await promptNewApiKey(opts.base);
|
|
222
273
|
}
|
|
274
|
+
closePrompts();
|
|
223
275
|
writeCredentials(opts.base, token);
|
|
224
276
|
console.log(`✓ API key stored at ${CREDENTIALS_FILE} (key not shown).`);
|
|
225
277
|
|
|
226
278
|
console.log("\n✓ Setup complete.");
|
|
227
|
-
console.log(" • Xevy models (xevy/auto, xevy/fast, …) are available in any pi session — no /login needed.");
|
|
279
|
+
console.log(" • Xevy models (xevy/auto, xevy/fast, …) + MCP tools are available in any pi session — no /login needed.");
|
|
228
280
|
console.log(" • Run `xevy` to open pi with the Xevy tools + models loaded.");
|
|
229
281
|
if (opts.base !== DEFAULT_BASE) console.log(` • Host: ${opts.base}`);
|
|
230
282
|
}
|
|
@@ -253,29 +305,12 @@ function open(opts) {
|
|
|
253
305
|
process.exit(r.status ?? 0);
|
|
254
306
|
}
|
|
255
307
|
|
|
256
|
-
async function piSetup(opts) {
|
|
257
|
-
console.log("Setting up the pi agent + Xevy extensions (model + MCP)\n");
|
|
258
|
-
|
|
259
|
-
ensurePi();
|
|
260
|
-
for (const ext of PI_EXTENSIONS) {
|
|
261
|
-
await downloadExtension(opts.base, ext.file, PI_EXTENSION_DIR, [ext.marker, "registerCommand"]);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
console.log("\n✓ pi + Xevy extensions ready.");
|
|
265
|
-
console.log(" • Sign in once: /login xevy — authenticates both the models and the MCP tools.");
|
|
266
|
-
console.log(" • Xevy models: xevy/auto, xevy/fast, xevy/advance, xevy/min, xevy/max, xevy/vision.");
|
|
267
|
-
console.log(" • MCP tools bridge automatically on session start (or run /xevy-mcp).");
|
|
268
|
-
console.log(" • If pi is already running, use /reload to load the extensions.");
|
|
269
|
-
if (opts.base !== DEFAULT_BASE) console.log(` • Source host: ${opts.base}`);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
308
|
async function main() {
|
|
273
309
|
const opts = parseArgv(process.argv.slice(2));
|
|
274
310
|
|
|
275
311
|
if (opts.command === "help") return usage();
|
|
276
312
|
if (opts.command === "version") return console.log(readCliVersion());
|
|
277
313
|
if (opts.command === "setup") return setup(opts);
|
|
278
|
-
if (opts.command === "pi") return piSetup(opts);
|
|
279
314
|
if (opts.command === "open") return open(opts);
|
|
280
315
|
}
|
|
281
316
|
|
package/package.json
CHANGED