@xevy/cli 0.1.0 → 0.1.2
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 +27 -48
- package/bin/xevy.mjs +162 -146
- package/package.json +4 -10
package/README.md
CHANGED
|
@@ -1,71 +1,50 @@
|
|
|
1
1
|
# @xevy/cli
|
|
2
2
|
|
|
3
|
-
The **`xevy`** command —
|
|
3
|
+
The **`xevy`** command — set up and launch the [pi](https://github.com/badlogic/pi-mono) agent with Xevy.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npx @xevy/cli setup
|
|
6
|
+
npx @xevy/cli setup # one-time: install pi + the Xevy extension + your API key
|
|
7
|
+
npx @xevy/cli # open pi with the Xevy extension loaded
|
|
7
8
|
```
|
|
8
9
|
|
|
9
|
-
`xevy setup`
|
|
10
|
-
into your user-global agent config, for [Cursor, Claude Code, and 50+ other
|
|
11
|
-
agents](https://github.com/vercel-labs/skills#supported-agents).
|
|
10
|
+
## `xevy setup`
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
1. Installs the **pi agent** if it isn't already on your PATH.
|
|
13
|
+
2. Downloads the Xevy pi extension (`xevy.ts`) into **`~/.xevy/extensions/`**.
|
|
14
|
+
3. Asks for your **API key** and prints a **direct link to the API-keys tab** so
|
|
15
|
+
you can generate one in a couple of clicks, then stores it in
|
|
16
|
+
`~/.config/xevy/credentials` (mode `600`, never printed).
|
|
14
17
|
|
|
15
|
-
The
|
|
16
|
-
|
|
17
|
-
store on disk.
|
|
18
|
+
The API-keys link opens **Settings → Profile → API keys** directly:
|
|
19
|
+
`https://xevy.ai/settings?section=profile&tab=api`.
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
- **Claude Code:** browser sign-in on the first MCP call
|
|
21
|
+
## `xevy`
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- ✅ Installs skills globally (`~/.cursor/skills`, `~/.claude/skills`, `~/.agents/skills`, …)
|
|
25
|
-
- ✅ Writes the MCP server to your agent's **global** config
|
|
26
|
-
- ❌ No per-repo files, no project detection, no repo linking
|
|
27
|
-
|
|
28
|
-
To link a specific repository to a Xevy project later, run the bundled
|
|
29
|
-
`xevy-setup` skill from inside your agent (it uses the MCP tools over OAuth).
|
|
30
|
-
|
|
31
|
-
## Install permanently
|
|
23
|
+
Opens pi, loading every `*.ts` extension in `~/.xevy/extensions` (`pi -e …`).
|
|
24
|
+
Any extra arguments are forwarded to pi:
|
|
32
25
|
|
|
33
26
|
```bash
|
|
34
|
-
|
|
35
|
-
xevy
|
|
27
|
+
xevy # interactive pi session with Xevy
|
|
28
|
+
xevy "summarise this repo" # forwards the prompt to pi
|
|
36
29
|
```
|
|
37
30
|
|
|
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
38
|
## Options
|
|
39
39
|
|
|
40
40
|
```
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
-
|
|
44
|
-
--base <url> Xevy host (default: XEVY_BASE or https://xevy.ai)
|
|
45
|
-
--skills-only Install skills only (skip MCP)
|
|
46
|
-
--mcp-only Configure the OAuth MCP server only (skip skills)
|
|
47
|
-
--copy Copy skill files instead of symlinking
|
|
48
|
-
-v, --version Print the CLI version
|
|
49
|
-
-h, --help Show help
|
|
41
|
+
--base <url> Xevy host (default: XEVY_BASE or https://xevy.ai)
|
|
42
|
+
-v, --version Print the CLI version
|
|
43
|
+
-h, --help Show help
|
|
50
44
|
```
|
|
51
45
|
|
|
52
|
-
### Examples
|
|
53
|
-
|
|
54
46
|
```bash
|
|
55
|
-
npx @xevy/cli setup
|
|
56
|
-
npx @xevy/cli setup -a cursor # Cursor only
|
|
57
|
-
npx @xevy/cli setup --mcp-only # just the OAuth MCP
|
|
58
|
-
XEVY_BASE=https://xevy.vyte.dev npx @xevy/cli setup # non-default host
|
|
47
|
+
XEVY_BASE=https://xevy.vyte.dev npx @xevy/cli setup
|
|
59
48
|
```
|
|
60
49
|
|
|
61
|
-
## How it works
|
|
62
|
-
|
|
63
|
-
Under the hood `xevy setup` drives two well-known primitives:
|
|
64
|
-
|
|
65
|
-
- [`npx skills`](https://github.com/vercel-labs/skills) — installs the skills
|
|
66
|
-
- [`npx add-mcp`](https://github.com/neondatabase/add-mcp) — writes the MCP config
|
|
67
|
-
|
|
68
|
-
Skills are sourced from the pinned [`@xevy/skills`](https://www.npmjs.com/package/@xevy/skills)
|
|
69
|
-
dependency, so this CLI stays in step with the published skills.
|
|
70
|
-
|
|
71
50
|
Docs: <https://xevy.ai/docs/mcp>
|
package/bin/xevy.mjs
CHANGED
|
@@ -2,41 +2,56 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @xevy/cli — the `xevy` command.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* xevy setup Install the pi agent (if missing), download the Xevy pi
|
|
6
|
+
* extension into ~/.xevy/extensions, and store your API key.
|
|
7
|
+
* xevy Open pi with the Xevy extensions from ~/.xevy/extensions.
|
|
8
|
+
* xevy pi (alt) Install the split model + MCP extensions into pi's own
|
|
9
|
+
* extensions dir, authenticated via OAuth.
|
|
9
10
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Skills are sourced from the pinned `@xevy/skills` dependency, so this CLI
|
|
14
|
-
* stays in step with the published skills without duplicating them.
|
|
15
|
-
*
|
|
16
|
-
* Usage:
|
|
17
|
-
* npx @xevy/cli setup Install skills + MCP globally (OAuth)
|
|
18
|
-
* npx @xevy/cli setup -a cursor Only a specific agent (repeatable)
|
|
19
|
-
* npx @xevy/cli setup --mcp-only Configure the OAuth MCP server only
|
|
20
|
-
* npx @xevy/cli --version
|
|
11
|
+
* `xevy setup` is the default flow: it fetches `xevy.ts` from the server and
|
|
12
|
+
* asks for an API key (with a direct link to the exact page to generate one),
|
|
13
|
+
* stored in ~/.config/xevy/credentials. Then `xevy` launches pi with it.
|
|
21
14
|
*/
|
|
22
15
|
import { spawnSync } from "node:child_process";
|
|
23
|
-
import { createRequire } from "node:module";
|
|
24
16
|
import fs from "node:fs";
|
|
25
17
|
import os from "node:os";
|
|
26
18
|
import path from "node:path";
|
|
19
|
+
import readline from "node:readline";
|
|
27
20
|
import { fileURLToPath } from "node:url";
|
|
28
21
|
|
|
29
|
-
const require = createRequire(import.meta.url);
|
|
30
22
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
31
23
|
const pkgRoot = path.resolve(here, "..");
|
|
32
24
|
|
|
33
25
|
const DEFAULT_BASE = "https://xevy.ai";
|
|
34
|
-
const DEFAULT_AGENTS = ["cursor", "claude-code"];
|
|
35
26
|
const isWindows = process.platform === "win32";
|
|
36
27
|
|
|
37
28
|
const PI_PACKAGE = "@mariozechner/pi-coding-agent";
|
|
29
|
+
const PI_BIN = isWindows ? "pi.cmd" : "pi";
|
|
30
|
+
|
|
31
|
+
// Where `xevy setup` installs the extension and `xevy` loads it from.
|
|
32
|
+
const XEVY_EXTENSIONS_DIR = path.join(os.homedir(), ".xevy", "extensions");
|
|
33
|
+
const XEVY_EXTENSION_FILE = "xevy.ts";
|
|
34
|
+
const API_KEY_PREFIX = "xevy_pk_";
|
|
35
|
+
|
|
36
|
+
// Credentials file the xevy.ts extension reads (baseUrl + token).
|
|
37
|
+
const CREDENTIALS_DIR = path.join(
|
|
38
|
+
process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config"),
|
|
39
|
+
"xevy",
|
|
40
|
+
);
|
|
41
|
+
const CREDENTIALS_FILE = path.join(CREDENTIALS_DIR, "credentials");
|
|
42
|
+
|
|
43
|
+
// `xevy pi` (alt flow) installs these into pi's own extensions dir over OAuth.
|
|
38
44
|
const PI_EXTENSION_DIR = path.join(os.homedir(), ".pi", "agent", "extensions");
|
|
39
|
-
const
|
|
45
|
+
const PI_EXTENSIONS = [
|
|
46
|
+
{ file: "xevy-model.ts", marker: "xevyModelExtension" },
|
|
47
|
+
{ file: "xevy-mcp.ts", marker: "xevyMcpExtension" },
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
function apiKeysUrl(base) {
|
|
51
|
+
// Direct link to the API keys tab (Profile → API keys), reachable without a
|
|
52
|
+
// project via the root Settings app.
|
|
53
|
+
return `${base}/settings?section=profile&tab=api`;
|
|
54
|
+
}
|
|
40
55
|
|
|
41
56
|
function readCliVersion() {
|
|
42
57
|
try {
|
|
@@ -53,144 +68,65 @@ function die(msg) {
|
|
|
53
68
|
}
|
|
54
69
|
|
|
55
70
|
function usage() {
|
|
56
|
-
console.log(`Xevy CLI
|
|
71
|
+
console.log(`Xevy CLI
|
|
57
72
|
|
|
58
|
-
xevy
|
|
59
|
-
xevy
|
|
73
|
+
xevy Open pi with the Xevy extensions in ~/.xevy/extensions
|
|
74
|
+
xevy setup [--base u] Install pi (if missing) + the Xevy pi extension, store your API key
|
|
75
|
+
xevy pi [--base u] (alt) Install the split model + MCP extensions over OAuth
|
|
60
76
|
|
|
61
77
|
Options:
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
--mcp-only Configure the OAuth MCP server only (skip skills)
|
|
66
|
-
--copy Copy skill files instead of symlinking
|
|
67
|
-
-v, --version Print the CLI version
|
|
68
|
-
-h, --help Show this help
|
|
69
|
-
|
|
70
|
-
Notes:
|
|
71
|
-
• Everything installs to your user-global scope — no per-repo files, no
|
|
72
|
-
project detection or linking.
|
|
73
|
-
• The MCP server signs in with OAuth from inside your agent on first
|
|
74
|
-
connect. There is no API key to paste.
|
|
78
|
+
--base <url> Xevy host (default: XEVY_BASE or ${DEFAULT_BASE})
|
|
79
|
+
-v, --version Print the CLI version
|
|
80
|
+
-h, --help Show this help
|
|
75
81
|
|
|
76
82
|
Examples:
|
|
77
83
|
npx @xevy/cli setup
|
|
78
|
-
npx @xevy/cli
|
|
79
|
-
npx @xevy/cli pi
|
|
84
|
+
npx @xevy/cli
|
|
80
85
|
XEVY_BASE=https://xevy.vyte.dev npx @xevy/cli setup`);
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
function parseArgv(argv) {
|
|
84
|
-
const
|
|
85
|
-
command: "setup",
|
|
86
|
-
agents: [],
|
|
87
|
-
base: process.env.XEVY_BASE?.replace(/\/$/, "") || DEFAULT_BASE,
|
|
88
|
-
skillsOnly: false,
|
|
89
|
-
mcpOnly: false,
|
|
90
|
-
copy: false,
|
|
91
|
-
};
|
|
92
|
-
|
|
89
|
+
const base = process.env.XEVY_BASE?.replace(/\/$/, "") || DEFAULT_BASE;
|
|
93
90
|
const args = [...argv];
|
|
94
|
-
if (args.length > 0 && !args[0].startsWith("-")) {
|
|
95
|
-
const cmd = args.shift();
|
|
96
|
-
if (cmd === "setup" || cmd === "pi") {
|
|
97
|
-
opts.command = cmd;
|
|
98
|
-
} else if (cmd === "help") {
|
|
99
|
-
opts.command = "help";
|
|
100
|
-
return opts;
|
|
101
|
-
} else {
|
|
102
|
-
die(`Unknown command: ${cmd}. Try \`xevy --help\`.`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
91
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
92
|
+
if (args.length === 0) return { command: "open", base, piArgs: [] };
|
|
93
|
+
|
|
94
|
+
const first = args[0];
|
|
95
|
+
if (first === "-h" || first === "--help" || first === "help") return { command: "help", base };
|
|
96
|
+
if (first === "-v" || first === "--version") return { command: "version", base };
|
|
97
|
+
|
|
98
|
+
if (first === "setup" || first === "pi") {
|
|
99
|
+
args.shift();
|
|
100
|
+
const opts = { command: first, base, piArgs: [] };
|
|
101
|
+
while (args.length > 0) {
|
|
102
|
+
const a = args.shift();
|
|
103
|
+
if (a === "--base") {
|
|
104
|
+
const v = args.shift();
|
|
105
|
+
if (!v) die("Missing value for --base");
|
|
106
|
+
opts.base = v.replace(/\/$/, "");
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
die(`Unknown option for \`xevy ${first}\`: ${a}. Try \`xevy --help\`.`);
|
|
121
110
|
}
|
|
122
|
-
|
|
123
|
-
if (a === "--mcp-only") { opts.mcpOnly = true; continue; }
|
|
124
|
-
if (a === "--copy") { opts.copy = true; continue; }
|
|
125
|
-
die(`Unknown option: ${a}. Try \`xevy --help\`.`);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (opts.skillsOnly && opts.mcpOnly) {
|
|
129
|
-
die("--skills-only and --mcp-only are mutually exclusive.");
|
|
111
|
+
return opts;
|
|
130
112
|
}
|
|
131
|
-
if (opts.agents.length === 0) opts.agents = [...DEFAULT_AGENTS];
|
|
132
|
-
return opts;
|
|
133
|
-
}
|
|
134
113
|
|
|
135
|
-
|
|
136
|
-
return
|
|
114
|
+
// Anything else → open pi, forwarding the args through.
|
|
115
|
+
return { command: "open", base, piArgs: args };
|
|
137
116
|
}
|
|
138
117
|
|
|
139
118
|
function run(cmd, args, { label } = {}) {
|
|
140
119
|
const title = label || [cmd, ...args].join(" ");
|
|
141
120
|
console.log(`→ ${title}`);
|
|
142
121
|
const command = isWindows && cmd === "npx" ? "npx.cmd" : cmd;
|
|
143
|
-
const
|
|
144
|
-
const r = spawnSync(command, finalArgs, { stdio: "inherit", shell: isWindows, env: process.env });
|
|
122
|
+
const r = spawnSync(command, args, { stdio: "inherit", shell: isWindows, env: process.env });
|
|
145
123
|
if (r.error) die(r.error.message);
|
|
146
124
|
if (r.status !== 0) die(`Command failed (${r.status}): ${title}`);
|
|
147
125
|
return r;
|
|
148
126
|
}
|
|
149
127
|
|
|
150
|
-
/** Resolve the on-disk root of the pinned @xevy/skills dependency. */
|
|
151
|
-
function resolveSkillsRoot() {
|
|
152
|
-
try {
|
|
153
|
-
return path.dirname(require.resolve("@xevy/skills/package.json"));
|
|
154
|
-
} catch {
|
|
155
|
-
die("Could not locate the bundled @xevy/skills package. Reinstall @xevy/cli and try again.");
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function installSkills(opts) {
|
|
160
|
-
const skillsRoot = resolveSkillsRoot();
|
|
161
|
-
const args = ["--yes", "skills", "add", skillsRoot, "-g", "-y", "-s", "*"];
|
|
162
|
-
if (opts.copy) args.push("--copy");
|
|
163
|
-
for (const a of opts.agents) args.push("-a", a);
|
|
164
|
-
run("npx", args, { label: "npx skills add (Xevy skills, global)" });
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function installMcp(opts) {
|
|
168
|
-
const mcpUrl = `${opts.base}/mcp`;
|
|
169
|
-
const args = ["--yes", "add-mcp", mcpUrl, "-n", "xevy", "-g", "-y"];
|
|
170
|
-
for (const a of opts.agents) args.push("-a", a);
|
|
171
|
-
run("npx", args, { label: `npx add-mcp ${mcpUrl} (OAuth, global)` });
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function setup(opts) {
|
|
175
|
-
console.log("Setting up Xevy (global, OAuth — no API key)\n");
|
|
176
|
-
|
|
177
|
-
if (!opts.mcpOnly) installSkills(opts);
|
|
178
|
-
if (!opts.skillsOnly) installMcp(opts);
|
|
179
|
-
|
|
180
|
-
console.log("\n✓ Xevy setup complete.");
|
|
181
|
-
if (!opts.skillsOnly) {
|
|
182
|
-
console.log(" • Complete OAuth in your agent on first connect:");
|
|
183
|
-
console.log(" Cursor: Settings → MCP → xevy → connect");
|
|
184
|
-
console.log(" Claude Code: browser sign-in on first MCP call");
|
|
185
|
-
}
|
|
186
|
-
if (opts.base !== DEFAULT_BASE) {
|
|
187
|
-
console.log(` • Host: ${opts.base} (set XEVY_BASE in your agent env too, then restart).`);
|
|
188
|
-
}
|
|
189
|
-
console.log(` • Docs: ${opts.base}/docs/mcp`);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
128
|
function hasPi() {
|
|
193
|
-
const r = spawnSync(
|
|
129
|
+
const r = spawnSync(PI_BIN, ["--version"], { stdio: "ignore", shell: isWindows });
|
|
194
130
|
return !r.error && r.status === 0;
|
|
195
131
|
}
|
|
196
132
|
|
|
@@ -202,13 +138,13 @@ function ensurePi() {
|
|
|
202
138
|
console.log(`→ pi agent not found — installing ${PI_PACKAGE} globally…`);
|
|
203
139
|
run("npm", ["install", "-g", PI_PACKAGE], { label: `npm install -g ${PI_PACKAGE}` });
|
|
204
140
|
if (!hasPi()) {
|
|
205
|
-
die("pi is still not on PATH after install. Ensure your npm global bin dir is on PATH, then re-run
|
|
141
|
+
die("pi is still not on PATH after install. Ensure your npm global bin dir is on PATH, then re-run.");
|
|
206
142
|
}
|
|
207
143
|
}
|
|
208
144
|
|
|
209
|
-
async function
|
|
210
|
-
const url = `${
|
|
211
|
-
console.log(`→ Downloading
|
|
145
|
+
async function downloadExtension(base, file, destDir, markers) {
|
|
146
|
+
const url = `${base}/${file}`;
|
|
147
|
+
console.log(`→ Downloading ${url}`);
|
|
212
148
|
|
|
213
149
|
let source;
|
|
214
150
|
try {
|
|
@@ -220,28 +156,107 @@ async function installModelExtension(opts) {
|
|
|
220
156
|
}
|
|
221
157
|
|
|
222
158
|
// Guard against a wrong --base returning the SPA HTML instead of the extension.
|
|
223
|
-
if (!
|
|
224
|
-
die(`Downloaded ${url} does not look like the
|
|
159
|
+
if (!markers.every((m) => source.includes(m))) {
|
|
160
|
+
die(`Downloaded ${url} does not look like the ${file} extension (wrong --base?).`);
|
|
225
161
|
}
|
|
226
162
|
|
|
227
|
-
fs.mkdirSync(
|
|
228
|
-
const dest = path.join(
|
|
163
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
164
|
+
const dest = path.join(destDir, file);
|
|
229
165
|
fs.writeFileSync(dest, source, "utf-8");
|
|
230
166
|
console.log(`✓ Installed ${dest}`);
|
|
167
|
+
return dest;
|
|
231
168
|
}
|
|
232
169
|
|
|
233
|
-
|
|
234
|
-
|
|
170
|
+
function promptLine(question) {
|
|
171
|
+
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
|
+
});
|
|
178
|
+
}
|
|
235
179
|
|
|
180
|
+
function writeCredentials(base, token) {
|
|
181
|
+
fs.mkdirSync(CREDENTIALS_DIR, { recursive: true });
|
|
182
|
+
fs.writeFileSync(
|
|
183
|
+
CREDENTIALS_FILE,
|
|
184
|
+
`baseUrl=${base}\ntoken=${token}\nupdatedAt=${new Date().toISOString()}\n`,
|
|
185
|
+
{ mode: 0o600 },
|
|
186
|
+
);
|
|
187
|
+
try {
|
|
188
|
+
fs.chmodSync(CREDENTIALS_FILE, 0o600);
|
|
189
|
+
} catch {
|
|
190
|
+
// best-effort on platforms without chmod
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function setup(opts) {
|
|
195
|
+
console.log("Setting up Xevy for pi\n");
|
|
196
|
+
|
|
197
|
+
// 1. pi + the extension
|
|
236
198
|
ensurePi();
|
|
237
|
-
await
|
|
199
|
+
await downloadExtension(opts.base, XEVY_EXTENSION_FILE, XEVY_EXTENSIONS_DIR, [
|
|
200
|
+
"EXTENSION_VERSION",
|
|
201
|
+
"registerCommand",
|
|
202
|
+
]);
|
|
203
|
+
|
|
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.`);
|
|
213
|
+
}
|
|
214
|
+
writeCredentials(opts.base, token);
|
|
215
|
+
console.log(`✓ API key stored at ${CREDENTIALS_FILE} (key not shown).`);
|
|
238
216
|
|
|
239
|
-
console.log("\n✓
|
|
240
|
-
console.log(" •
|
|
241
|
-
console.log(
|
|
242
|
-
|
|
243
|
-
|
|
217
|
+
console.log("\n✓ Setup complete.");
|
|
218
|
+
console.log(" • Run `xevy` to open pi with the Xevy extension loaded.");
|
|
219
|
+
if (opts.base !== DEFAULT_BASE) console.log(` • Host: ${opts.base}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function open(opts) {
|
|
223
|
+
if (!hasPi()) {
|
|
224
|
+
die("pi is not installed. Run `xevy setup` first.");
|
|
244
225
|
}
|
|
226
|
+
let files = [];
|
|
227
|
+
if (fs.existsSync(XEVY_EXTENSIONS_DIR)) {
|
|
228
|
+
files = fs
|
|
229
|
+
.readdirSync(XEVY_EXTENSIONS_DIR)
|
|
230
|
+
.filter((f) => f.endsWith(".ts"))
|
|
231
|
+
.map((f) => path.join(XEVY_EXTENSIONS_DIR, f));
|
|
232
|
+
}
|
|
233
|
+
if (files.length === 0) {
|
|
234
|
+
die(`No Xevy extensions in ${XEVY_EXTENSIONS_DIR}. Run \`xevy setup\` first.`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const piArgs = [];
|
|
238
|
+
for (const f of files) piArgs.push("-e", f);
|
|
239
|
+
piArgs.push(...opts.piArgs);
|
|
240
|
+
|
|
241
|
+
const r = spawnSync(PI_BIN, piArgs, { stdio: "inherit", shell: isWindows, env: process.env });
|
|
242
|
+
if (r.error) die(r.error.message);
|
|
243
|
+
process.exit(r.status ?? 0);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function piSetup(opts) {
|
|
247
|
+
console.log("Setting up the pi agent + Xevy extensions (model + MCP)\n");
|
|
248
|
+
|
|
249
|
+
ensurePi();
|
|
250
|
+
for (const ext of PI_EXTENSIONS) {
|
|
251
|
+
await downloadExtension(opts.base, ext.file, PI_EXTENSION_DIR, [ext.marker, "registerCommand"]);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log("\n✓ pi + Xevy extensions ready.");
|
|
255
|
+
console.log(" • Sign in once: /login xevy — authenticates both the models and the MCP tools.");
|
|
256
|
+
console.log(" • Xevy models: xevy/auto, xevy/fast, xevy/advance, xevy/min, xevy/max, xevy/vision.");
|
|
257
|
+
console.log(" • MCP tools bridge automatically on session start (or run /xevy-mcp).");
|
|
258
|
+
console.log(" • If pi is already running, use /reload to load the extensions.");
|
|
259
|
+
if (opts.base !== DEFAULT_BASE) console.log(` • Source host: ${opts.base}`);
|
|
245
260
|
}
|
|
246
261
|
|
|
247
262
|
async function main() {
|
|
@@ -251,6 +266,7 @@ async function main() {
|
|
|
251
266
|
if (opts.command === "version") return console.log(readCliVersion());
|
|
252
267
|
if (opts.command === "setup") return setup(opts);
|
|
253
268
|
if (opts.command === "pi") return piSetup(opts);
|
|
269
|
+
if (opts.command === "open") return open(opts);
|
|
254
270
|
}
|
|
255
271
|
|
|
256
272
|
main().catch((error) => die(error instanceof Error ? error.message : String(error)));
|
package/package.json
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xevy/cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Xevy CLI —
|
|
3
|
+
"version": "0.1.2",
|
|
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": [
|
|
7
7
|
"xevy",
|
|
8
8
|
"cli",
|
|
9
9
|
"setup",
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"agent-skills",
|
|
13
|
-
"cursor",
|
|
14
|
-
"claude-code",
|
|
10
|
+
"pi",
|
|
11
|
+
"pi-agent",
|
|
15
12
|
"sdlc"
|
|
16
13
|
],
|
|
17
14
|
"homepage": "https://xevy.ai/docs/mcp",
|
|
@@ -26,9 +23,6 @@
|
|
|
26
23
|
"engines": {
|
|
27
24
|
"node": ">=18"
|
|
28
25
|
},
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"@xevy/skills": "^0.1.18"
|
|
31
|
-
},
|
|
32
26
|
"publishConfig": {
|
|
33
27
|
"access": "public"
|
|
34
28
|
}
|