@xyleapp/cli 0.3.0 → 0.4.1
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 +1 -1
- package/bin/xyle.mjs +1 -1
- package/package.json +1 -1
- package/src/api.mjs +8 -2
- package/src/auth.mjs +6 -6
- package/src/commands.mjs +22 -1
package/README.md
CHANGED
|
@@ -60,7 +60,7 @@ All data commands accept `--json` for machine-readable output.
|
|
|
60
60
|
|
|
61
61
|
| Environment Variable | Default | Description |
|
|
62
62
|
| -------------------- | ------------------------ | -------------------- |
|
|
63
|
-
| `SEO_BASE` | `
|
|
63
|
+
| `SEO_BASE` | `https://api.xyle.app` | API base URL |
|
|
64
64
|
| `AGENT_API_KEY` | `local-agent-secret-key` | Fallback API key |
|
|
65
65
|
|
|
66
66
|
Credentials are stored in `~/.config/xyle/credentials.json` (shared with the Python CLI).
|
package/bin/xyle.mjs
CHANGED
package/package.json
CHANGED
package/src/api.mjs
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
* Uses native fetch (Node 18+). Zero external dependencies.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { getApiKey } from "./auth.mjs";
|
|
6
|
+
import { getApiKey, getCredentials } from "./auth.mjs";
|
|
7
7
|
|
|
8
|
-
const SEO_BASE = process.env.SEO_BASE || "
|
|
8
|
+
const SEO_BASE = process.env.SEO_BASE || "https://api.xyle.app";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Build auth headers, preferring personal API key over shared env key.
|
|
@@ -96,6 +96,12 @@ export function syncGsc(site) {
|
|
|
96
96
|
return request("POST", "/admin/sync", { params: { site } });
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
export function listSites() {
|
|
100
|
+
const creds = getCredentials();
|
|
101
|
+
const email = creds?.email || null;
|
|
102
|
+
return request("GET", "/admin/sites", { params: { email } });
|
|
103
|
+
}
|
|
104
|
+
|
|
99
105
|
/**
|
|
100
106
|
* Fetch the OAuth auth URL from the API.
|
|
101
107
|
*/
|
package/src/auth.mjs
CHANGED
|
@@ -124,8 +124,8 @@ export async function runLoginFlow(seoBase) {
|
|
|
124
124
|
);
|
|
125
125
|
|
|
126
126
|
settled = true;
|
|
127
|
-
server.close();
|
|
128
|
-
|
|
127
|
+
server.close(() => resolve(creds));
|
|
128
|
+
server.closeAllConnections();
|
|
129
129
|
} else if (status === "error") {
|
|
130
130
|
res.writeHead(400, { "Content-Type": "text/html" });
|
|
131
131
|
res.end(
|
|
@@ -136,9 +136,9 @@ export async function runLoginFlow(seoBase) {
|
|
|
136
136
|
);
|
|
137
137
|
|
|
138
138
|
settled = true;
|
|
139
|
-
server.close();
|
|
140
139
|
console.error(`\x1b[31mLogin failed: ${message}\x1b[0m`);
|
|
141
|
-
resolve(null);
|
|
140
|
+
server.close(() => resolve(null));
|
|
141
|
+
server.closeAllConnections();
|
|
142
142
|
} else {
|
|
143
143
|
res.writeHead(404);
|
|
144
144
|
res.end();
|
|
@@ -155,9 +155,9 @@ export async function runLoginFlow(seoBase) {
|
|
|
155
155
|
setTimeout(() => {
|
|
156
156
|
if (!settled) {
|
|
157
157
|
settled = true;
|
|
158
|
-
server.close();
|
|
159
158
|
console.error("\x1b[31mLogin timed out (2 minutes). Try again.\x1b[0m");
|
|
160
|
-
resolve(null);
|
|
159
|
+
server.close(() => resolve(null));
|
|
160
|
+
server.closeAllConnections();
|
|
161
161
|
}
|
|
162
162
|
}, 120_000);
|
|
163
163
|
});
|
package/src/commands.mjs
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
getRewriteSuggestions,
|
|
15
15
|
crawlPage,
|
|
16
16
|
syncGsc,
|
|
17
|
+
listSites,
|
|
17
18
|
SEO_BASE,
|
|
18
19
|
} from "./api.mjs";
|
|
19
20
|
import { getCredentials, clearCredentials, runLoginFlow } from "./auth.mjs";
|
|
@@ -220,12 +221,32 @@ export function registerCommands(program) {
|
|
|
220
221
|
}
|
|
221
222
|
});
|
|
222
223
|
|
|
224
|
+
// --- sites ---
|
|
225
|
+
program
|
|
226
|
+
.command("sites")
|
|
227
|
+
.description("List all connected sites for your account")
|
|
228
|
+
.option("--json", "Output as JSON")
|
|
229
|
+
.action(async (opts) => {
|
|
230
|
+
try {
|
|
231
|
+
const data = await listSites();
|
|
232
|
+
if (opts.json) {
|
|
233
|
+
console.log(printJson(data));
|
|
234
|
+
} else if (!data || data.length === 0) {
|
|
235
|
+
console.log("\x1b[33mNo sites connected. Use the dashboard or `xyle sync --site <domain>` to add one.\x1b[0m");
|
|
236
|
+
} else {
|
|
237
|
+
console.log(printTable(data, ["domain", "synced_queries", "gsc_url", "created_at"]));
|
|
238
|
+
}
|
|
239
|
+
} catch (e) {
|
|
240
|
+
handleError(e);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
223
244
|
// --- login ---
|
|
224
245
|
program
|
|
225
246
|
.command("login")
|
|
226
247
|
.description("Authenticate with Google for Search Console access")
|
|
227
248
|
.action(async () => {
|
|
228
|
-
const seoBase = process.env.SEO_BASE || "
|
|
249
|
+
const seoBase = process.env.SEO_BASE || "https://api.xyle.app";
|
|
229
250
|
|
|
230
251
|
const existing = getCredentials();
|
|
231
252
|
if (existing && existing.authenticated) {
|