@siteoshq/cli 1.12.0 → 1.13.0
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 +7 -1
- package/dist/cli.js +120 -3
- package/dist/cli.js.map +1 -1
- package/package.json +20 -18
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# SiteOS CLI
|
|
2
2
|
|
|
3
3
|
`@siteoshq/cli` exposes one `siteos` binary for Auth, common Projects, Pulse, Cookie, Forms,
|
|
4
|
-
Search, Trace, SEO/GEO and Integrations. This source is version 1.
|
|
4
|
+
Search, Trace, SEO/GEO and Integrations. This source is version 1.13.0; source changes require a separate release to reach npm.
|
|
5
5
|
Node.js 22 or newer is required.
|
|
6
6
|
|
|
7
7
|
## Install and authenticate
|
|
@@ -205,6 +205,10 @@ Research and GSC Insights use these commands with a matching server release:
|
|
|
205
205
|
siteos seo research summary --json
|
|
206
206
|
siteos seo research history --kind keywords --json
|
|
207
207
|
siteos seo research show <run-id> --json
|
|
208
|
+
siteos seo research serp show <run-id> --keyword "website analytics" --json
|
|
209
|
+
siteos seo research serp ensure <run-id> --keyword "website analytics" --json
|
|
210
|
+
siteos seo research serp wait <run-id> --keyword "website analytics" --json
|
|
211
|
+
siteos seo research serp export <run-id> --keyword "website analytics" --format json --output ./serp-report.json --json
|
|
208
212
|
siteos seo research plan --input ./research-request.json --json
|
|
209
213
|
siteos seo research run --input ./research-request.json --idempotency-key <retry-key> --json
|
|
210
214
|
siteos seo research wait <run-id> --json
|
|
@@ -219,6 +223,8 @@ Research supports keywords, Domain Overview (`domain`), rankings, backlinks, bra
|
|
|
219
223
|
Domain Overview collects domain totals, up to 100 ranking keywords, pages and competitors for a market.
|
|
220
224
|
`--kind domain` includes legacy `competitors` history; existing `competitors` inputs keep their original two-part plan.
|
|
221
225
|
New request example: `{"kind":"domain","target":"example.com","country":"US","language":"en"}`.
|
|
226
|
+
SERP `ensure` collects at most one saved snapshot per returned keyword and Keyword Research report. Repeat calls reuse the saved job, including failures. SERP show/wait/export only read; market and device come from the parent report.
|
|
227
|
+
|
|
222
228
|
Reading/planning/saving spends no research credits. Launch uses Organization credits and the same
|
|
223
229
|
server admission as the interface. Keep the retry key after an uncertain response; no paid retries
|
|
224
230
|
are automatic. Wait exit 3 means pending; exit 4 failed/cancelled. Partial evidence remains readable.
|
package/dist/cli.js
CHANGED
|
@@ -10516,6 +10516,10 @@ var RESEARCH_HELP = `
|
|
|
10516
10516
|
siteos seo research wait <run-id> [--timeout <seconds>] [--environment <slug>] [--json]
|
|
10517
10517
|
siteos seo research cancel <run-id> [--environment <slug>] [--json]
|
|
10518
10518
|
siteos seo research export <run-id> --format <json|csv> --output <new-file> [--environment <slug>] [--json]
|
|
10519
|
+
siteos seo research serp show <run-id> --keyword <word> [--environment <slug>] [--json]
|
|
10520
|
+
siteos seo research serp ensure <run-id> --keyword <word> [--environment <slug>] [--json]
|
|
10521
|
+
siteos seo research serp wait <run-id> --keyword <word> [--timeout <seconds>] [--environment <slug>] [--json]
|
|
10522
|
+
siteos seo research serp export <run-id> --keyword <word> --format <json|csv> --output <new-file> [--environment <slug>] [--json]
|
|
10519
10523
|
siteos seo research saved list --kind <kind> [--environment <slug>] [--json]
|
|
10520
10524
|
siteos seo research saved save --input <request.json> --name <name> [--id <saved-id>] [--environment <slug>] [--json]
|
|
10521
10525
|
siteos seo research saved remove <saved-id> [--environment <slug>] [--json]
|
|
@@ -10529,6 +10533,7 @@ Markets: US, GB, ES, DE, FR, CA, AU. Languages: en, es, de, fr, pt, it, nl, ru.
|
|
|
10529
10533
|
Platforms: chat_gpt, claude, gemini, perplexity. Brand lookup uses brandPlatform chat_gpt|google and brandMatch domain|brand.
|
|
10530
10534
|
Plan validates the request and shows planned parts and available research credits without enqueueing work.
|
|
10531
10535
|
Run consumes Organization research credits through the same worker as the interface. Preserve the retry key after an uncertain response.
|
|
10536
|
+
SERP ensure collects one saved organic snapshot for a keyword returned by the selected completed Keyword Research report. It uses research credits once; repeating the same report/word returns the saved job, including failures. Show/wait/export only read and never spend. New reports can collect fresh snapshots.
|
|
10532
10537
|
History returns the latest 30 checks per kind; show/export can address older retained run IDs. Saved checks are limited to 100 per resource.
|
|
10533
10538
|
Wait defaults to 120 seconds (maximum 900); exit 3 means still pending, exit 4 means failed or cancelled. Partial evidence stays accessible.
|
|
10534
10539
|
Exports preserve scope, dates and partial state. CSV stores one dataset row as JSON per row, including failed parts; it never converts missing metrics to zero.
|
|
@@ -10569,6 +10574,7 @@ async function runSeoResearchCommand(options) {
|
|
|
10569
10574
|
json: { type: "boolean" },
|
|
10570
10575
|
environment: { type: "string" },
|
|
10571
10576
|
kind: { type: "string" },
|
|
10577
|
+
keyword: { type: "string" },
|
|
10572
10578
|
input: { type: "string" },
|
|
10573
10579
|
name: { type: "string" },
|
|
10574
10580
|
id: { type: "string" },
|
|
@@ -10578,9 +10584,13 @@ async function runSeoResearchCommand(options) {
|
|
|
10578
10584
|
output: { type: "string" }
|
|
10579
10585
|
}
|
|
10580
10586
|
});
|
|
10581
|
-
const action = positionals[0]
|
|
10582
|
-
const id = positionals[action.
|
|
10587
|
+
const action = ["saved", "serp"].includes(positionals[0] ?? "") ? positionals.slice(0, 2).join(" ") : positionals[0] ?? "";
|
|
10588
|
+
const id = positionals[action.includes(" ") ? 2 : 1];
|
|
10583
10589
|
const operations = {
|
|
10590
|
+
"serp show": { args: 3, flags: ["keyword"] },
|
|
10591
|
+
"serp ensure": { args: 3, flags: ["keyword"] },
|
|
10592
|
+
"serp wait": { args: 3, flags: ["keyword", "timeout"] },
|
|
10593
|
+
"serp export": { args: 3, flags: ["keyword", "format", "output"] },
|
|
10584
10594
|
summary: { args: 1, flags: [] },
|
|
10585
10595
|
status: { args: 1, flags: ["kind"] },
|
|
10586
10596
|
history: { args: 1, flags: ["kind"] },
|
|
@@ -10601,6 +10611,10 @@ async function runSeoResearchCommand(options) {
|
|
|
10601
10611
|
throw new Error(
|
|
10602
10612
|
"Invalid research operation or flags. Run siteos seo --help."
|
|
10603
10613
|
);
|
|
10614
|
+
if (action.startsWith("serp ") && (!values.keyword?.trim() || values.keyword.length > 200))
|
|
10615
|
+
throw new Error(
|
|
10616
|
+
"Provide --keyword with 1 to 200 characters from the selected keyword report."
|
|
10617
|
+
);
|
|
10604
10618
|
if (values.kind === "domain") values.kind = "competitors";
|
|
10605
10619
|
if (operation.flags.includes("kind") && !kinds.includes(values.kind))
|
|
10606
10620
|
throw new Error("Choose a documented --kind.");
|
|
@@ -10613,7 +10627,7 @@ async function runSeoResearchCommand(options) {
|
|
|
10613
10627
|
const timeout = Number(values.timeout ?? 120);
|
|
10614
10628
|
if (!Number.isInteger(timeout) || timeout < 1 || timeout > 900)
|
|
10615
10629
|
throw new Error("Use a wait timeout between 1 and 900 seconds.");
|
|
10616
|
-
if (
|
|
10630
|
+
if (["export", "serp export"].includes(action) && (!values.output || !["json", "csv"].includes(values.format ?? "")))
|
|
10617
10631
|
throw new Error(
|
|
10618
10632
|
"Export requires --format json|csv and --output for a new file."
|
|
10619
10633
|
);
|
|
@@ -10665,6 +10679,109 @@ async function runSeoResearchCommand(options) {
|
|
|
10665
10679
|
);
|
|
10666
10680
|
const query = `?kind=${encodeURIComponent(values.kind ?? "rankings")}`;
|
|
10667
10681
|
let record;
|
|
10682
|
+
if (action.startsWith("serp ")) {
|
|
10683
|
+
const snapshotSchema = z19.object({
|
|
10684
|
+
id: z19.string(),
|
|
10685
|
+
organizationId: z19.literal(client.organizationId),
|
|
10686
|
+
resourceId: z19.literal(client.resourceId),
|
|
10687
|
+
sourceRunId: z19.literal(id),
|
|
10688
|
+
request: requestSchema,
|
|
10689
|
+
keywordKey: z19.literal(
|
|
10690
|
+
values.keyword.normalize("NFKC").trim().toLowerCase().replace(/\s+/gu, " ")
|
|
10691
|
+
),
|
|
10692
|
+
state: z19.enum([
|
|
10693
|
+
"queued",
|
|
10694
|
+
"running",
|
|
10695
|
+
"completed",
|
|
10696
|
+
"failed",
|
|
10697
|
+
"cancelled"
|
|
10698
|
+
]),
|
|
10699
|
+
dataset: z19.object({
|
|
10700
|
+
type: z19.literal("serp"),
|
|
10701
|
+
keyword: z19.string(),
|
|
10702
|
+
country: z19.string(),
|
|
10703
|
+
language: z19.string(),
|
|
10704
|
+
device: z19.enum(["desktop", "mobile"]),
|
|
10705
|
+
observedAt: z19.string(),
|
|
10706
|
+
rows: z19.array(
|
|
10707
|
+
z19.object({
|
|
10708
|
+
position: z19.number(),
|
|
10709
|
+
title: z19.string(),
|
|
10710
|
+
url: z19.string(),
|
|
10711
|
+
domain: z19.string(),
|
|
10712
|
+
description: z19.string()
|
|
10713
|
+
})
|
|
10714
|
+
)
|
|
10715
|
+
}).nullable()
|
|
10716
|
+
}).passthrough();
|
|
10717
|
+
const suffix = `/runs/${encodeURIComponent(id)}/serp`;
|
|
10718
|
+
const read = () => get(`${suffix}?keyword=${encodeURIComponent(values.keyword)}`);
|
|
10719
|
+
record = action === "serp ensure" ? await get(suffix, "run", { keyword: values.keyword }) : await read();
|
|
10720
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
10721
|
+
let snapshot = record.serp == null ? null : snapshotSchema.parse(record.serp);
|
|
10722
|
+
if (action === "serp ensure" && !snapshot)
|
|
10723
|
+
throw new Error(
|
|
10724
|
+
"The server did not return the admitted SERP snapshot."
|
|
10725
|
+
);
|
|
10726
|
+
while (action === "serp wait" && snapshot && ["queued", "running"].includes(snapshot.state)) {
|
|
10727
|
+
if (Date.now() >= deadline)
|
|
10728
|
+
return {
|
|
10729
|
+
exitCode: 3,
|
|
10730
|
+
stdout: JSON.stringify({ ...record, timedOut: true }, null, 2)
|
|
10731
|
+
};
|
|
10732
|
+
await setTimeout2(Math.min(2e3, Math.max(0, deadline - Date.now())));
|
|
10733
|
+
record = await read();
|
|
10734
|
+
snapshot = record.serp == null ? null : snapshotSchema.parse(record.serp);
|
|
10735
|
+
}
|
|
10736
|
+
if (action === "serp export") {
|
|
10737
|
+
if (!snapshot)
|
|
10738
|
+
throw new Error("No saved SERP exists for this report and keyword.");
|
|
10739
|
+
const data = snapshot.dataset;
|
|
10740
|
+
const content = values.format === "json" ? JSON.stringify(record, null, 2) : [
|
|
10741
|
+
[
|
|
10742
|
+
"report_id",
|
|
10743
|
+
"keyword",
|
|
10744
|
+
"country",
|
|
10745
|
+
"language",
|
|
10746
|
+
"device",
|
|
10747
|
+
"state",
|
|
10748
|
+
"observed_at",
|
|
10749
|
+
"error",
|
|
10750
|
+
"position",
|
|
10751
|
+
"title",
|
|
10752
|
+
"url",
|
|
10753
|
+
"description"
|
|
10754
|
+
],
|
|
10755
|
+
...(data?.rows.length ? data.rows : [null]).map((row) => [
|
|
10756
|
+
id,
|
|
10757
|
+
values.keyword,
|
|
10758
|
+
data?.country,
|
|
10759
|
+
data?.language,
|
|
10760
|
+
data?.device,
|
|
10761
|
+
snapshot.state,
|
|
10762
|
+
data?.observedAt,
|
|
10763
|
+
snapshot.error,
|
|
10764
|
+
row?.position,
|
|
10765
|
+
row?.title,
|
|
10766
|
+
row?.url,
|
|
10767
|
+
row?.description
|
|
10768
|
+
])
|
|
10769
|
+
].map((row) => row.map(csvCell).join(",")).join("\r\n");
|
|
10770
|
+
const output = await writeSeoReport(options, values.output, content);
|
|
10771
|
+
record = {
|
|
10772
|
+
contractVersion: 1,
|
|
10773
|
+
runId: id,
|
|
10774
|
+
keyword: values.keyword,
|
|
10775
|
+
state: snapshot.state,
|
|
10776
|
+
format: values.format,
|
|
10777
|
+
output
|
|
10778
|
+
};
|
|
10779
|
+
}
|
|
10780
|
+
return {
|
|
10781
|
+
exitCode: action === "serp wait" && (!snapshot || ["failed", "cancelled"].includes(snapshot.state)) ? 4 : 0,
|
|
10782
|
+
stdout: JSON.stringify(record, null, 2)
|
|
10783
|
+
};
|
|
10784
|
+
}
|
|
10668
10785
|
if (action === "plan" || action === "run" || action === "saved save") {
|
|
10669
10786
|
const plan = await get("/plan", "read", input);
|
|
10670
10787
|
resourceSchema.parse(plan.resource);
|