@scrappycoco/cli 0.5.0 → 0.6.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 +8 -7
- package/dist/index.js +100 -34
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,12 +17,10 @@ the live catalog, and tells you when to reload or restart your agent. Browser
|
|
|
17
17
|
authorization is not complete until the terminal confirms that the credential
|
|
18
18
|
was saved and the catalog check succeeded.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
CLI-only users without an installed skill are not modified. Set
|
|
25
|
-
`SCRAPPYCOCO_DISABLE_SKILL_AUTO_UPDATE=1` to opt out.
|
|
20
|
+
Ordinary commands never mutate installed agent instructions. Setup installs
|
|
21
|
+
the matching skill release. Run `scrappycoco skill status` to inspect the
|
|
22
|
+
active target and `scrappycoco skill update` to explicitly install a newer
|
|
23
|
+
hash-verified release, then reload or restart the agent.
|
|
26
24
|
|
|
27
25
|
When the CLI runs on a remote or headless host whose `127.0.0.1` is not the
|
|
28
26
|
browser's localhost, keep the command running and use the manual callback
|
|
@@ -42,6 +40,8 @@ Individual commands remain available:
|
|
|
42
40
|
|
|
43
41
|
```sh
|
|
44
42
|
npx --yes @scrappycoco/cli@latest auth login
|
|
43
|
+
npx --yes @scrappycoco/cli@latest skill status
|
|
44
|
+
npx --yes @scrappycoco/cli@latest skill update
|
|
45
45
|
npx --yes @scrappycoco/cli@latest doctor --json
|
|
46
46
|
npx --yes @scrappycoco/cli@latest catalog list --available --json
|
|
47
47
|
npx --yes @scrappycoco/cli@latest catalog inspect web.extract_content --json
|
|
@@ -74,7 +74,8 @@ compact execution summary remains on stdout.
|
|
|
74
74
|
|
|
75
75
|
For a long run, add `--detach` to return a job ID immediately, then finish with
|
|
76
76
|
`scrappycoco jobs wait <job-id> --output results.json`. Use
|
|
77
|
-
`scrappycoco jobs get <job-id>` for a single status check
|
|
77
|
+
`scrappycoco jobs get <job-id>` for a single status check or
|
|
78
|
+
`scrappycoco jobs cancel <job-id>` to stop it. Set
|
|
78
79
|
`SCRAPPYCOCO_JOB_TIMEOUT_MS` to change the default 20-minute foreground wait.
|
|
79
80
|
Run `scrappycoco doctor --json` only when authentication, connectivity, or
|
|
80
81
|
installation is unclear.
|
package/dist/index.js
CHANGED
|
@@ -576,6 +576,13 @@ var ApiClient = class {
|
|
|
576
576
|
job
|
|
577
577
|
);
|
|
578
578
|
}
|
|
579
|
+
if (job.status === "cancelled") {
|
|
580
|
+
throw new CliError(
|
|
581
|
+
`Job ${job.job_id} was cancelled.`,
|
|
582
|
+
EXIT.cancelled,
|
|
583
|
+
job
|
|
584
|
+
);
|
|
585
|
+
}
|
|
579
586
|
const remainingBeforeDelay = deadline - Date.now();
|
|
580
587
|
if (remainingBeforeDelay <= 0) throw jobTimeoutError(job, timeoutMs);
|
|
581
588
|
await new Promise((resolve) => setTimeout(resolve, Math.min(delayMs, remainingBeforeDelay)));
|
|
@@ -869,30 +876,66 @@ function installedSkillCandidates() {
|
|
|
869
876
|
join2(homedir2(), ".cursor", "skills", SKILL_NAME, "SKILL.md")
|
|
870
877
|
];
|
|
871
878
|
}
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
+
function configuredTargetPath(agent) {
|
|
880
|
+
const directory = agent === "codex" ? ".codex" : agent === "claude-code" ? ".claude" : agent === "cursor" ? ".cursor" : ".agents";
|
|
881
|
+
return join2(homedir2(), directory, "skills", SKILL_NAME, "SKILL.md");
|
|
882
|
+
}
|
|
883
|
+
async function pathExists(path) {
|
|
884
|
+
try {
|
|
885
|
+
await access(path);
|
|
886
|
+
return true;
|
|
887
|
+
} catch {
|
|
888
|
+
return false;
|
|
879
889
|
}
|
|
880
|
-
return false;
|
|
881
890
|
}
|
|
882
|
-
async function
|
|
891
|
+
async function activeSkillTarget() {
|
|
892
|
+
const configured = configuredTargetPath(await detectSkillInstallerAgent());
|
|
893
|
+
if (await pathExists(configured)) return configured;
|
|
894
|
+
const shared = configuredTargetPath("universal");
|
|
895
|
+
if (await pathExists(shared)) return shared;
|
|
896
|
+
return configured;
|
|
897
|
+
}
|
|
898
|
+
async function isSkillInstalled() {
|
|
899
|
+
return pathExists(await activeSkillTarget());
|
|
900
|
+
}
|
|
901
|
+
async function loadSkillReleaseState() {
|
|
883
902
|
try {
|
|
884
903
|
const state = JSON.parse(
|
|
885
904
|
await readFile3(skillReleaseStatePath(), "utf8")
|
|
886
905
|
);
|
|
887
|
-
return
|
|
906
|
+
return {
|
|
907
|
+
targets: Object.fromEntries(
|
|
908
|
+
Object.entries(state.targets || {}).filter(
|
|
909
|
+
([path, value]) => Boolean(path) && typeof value?.digest === "string" && SHA256_DIGEST.test(value.digest)
|
|
910
|
+
)
|
|
911
|
+
)
|
|
912
|
+
};
|
|
888
913
|
} catch {
|
|
889
|
-
return
|
|
914
|
+
return { targets: {} };
|
|
890
915
|
}
|
|
891
916
|
}
|
|
917
|
+
async function loadInstalledDigest() {
|
|
918
|
+
const target = await activeSkillTarget();
|
|
919
|
+
const entry = (await loadSkillReleaseState()).targets[target];
|
|
920
|
+
return entry?.digest || null;
|
|
921
|
+
}
|
|
892
922
|
async function installedSkillDiagnostics() {
|
|
923
|
+
const target = await activeSkillTarget();
|
|
924
|
+
const state = await loadSkillReleaseState();
|
|
925
|
+
const installedTargets = (await Promise.all(
|
|
926
|
+
installedSkillCandidates().map(async (path) => ({
|
|
927
|
+
path,
|
|
928
|
+
installed: await pathExists(path)
|
|
929
|
+
}))
|
|
930
|
+
)).filter((item) => item.installed);
|
|
893
931
|
return {
|
|
894
|
-
installed:
|
|
895
|
-
digest:
|
|
932
|
+
installed: installedTargets.some((item) => item.path === target),
|
|
933
|
+
digest: state.targets[target]?.digest || null,
|
|
934
|
+
target,
|
|
935
|
+
targets: installedTargets.map(({ path }) => ({
|
|
936
|
+
path,
|
|
937
|
+
digest: state.targets[path]?.digest || null
|
|
938
|
+
}))
|
|
896
939
|
};
|
|
897
940
|
}
|
|
898
941
|
async function writePrivateJson2(path, value) {
|
|
@@ -908,10 +951,23 @@ async function writePrivateJson2(path, value) {
|
|
|
908
951
|
}
|
|
909
952
|
}
|
|
910
953
|
async function saveInstalledDigest(digest) {
|
|
911
|
-
await
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
954
|
+
const target = await activeSkillTarget();
|
|
955
|
+
const state = await loadSkillReleaseState();
|
|
956
|
+
await writePrivateJson2(
|
|
957
|
+
skillReleaseStatePath(),
|
|
958
|
+
recordTargetDigest(state, target, digest)
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
function recordTargetDigest(state, target, digest, updatedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
962
|
+
return {
|
|
963
|
+
targets: {
|
|
964
|
+
...state.targets,
|
|
965
|
+
[target]: {
|
|
966
|
+
digest,
|
|
967
|
+
updated_at: updatedAt
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
};
|
|
915
971
|
}
|
|
916
972
|
async function fetchPublishedSkillDigest(fetcher = fetch) {
|
|
917
973
|
const response = await fetcher(SKILL_INDEX_URL, {
|
|
@@ -922,7 +978,7 @@ async function fetchPublishedSkillDigest(fetcher = fetch) {
|
|
|
922
978
|
throw new Error(`Skill release check returned HTTP ${response.status}.`);
|
|
923
979
|
}
|
|
924
980
|
const index = await response.json();
|
|
925
|
-
const entry = index.skills?.find((
|
|
981
|
+
const entry = index.skills?.find((skill2) => skill2.name === SKILL_NAME);
|
|
926
982
|
if (!entry || typeof entry.digest !== "string" || !SHA256_DIGEST.test(entry.digest)) {
|
|
927
983
|
throw new Error("Skill release index did not contain a valid Scrappycoco digest.");
|
|
928
984
|
}
|
|
@@ -993,13 +1049,7 @@ async function withSkillUpdateLock(operation) {
|
|
|
993
1049
|
await rm2(path, { force: true });
|
|
994
1050
|
}
|
|
995
1051
|
}
|
|
996
|
-
function
|
|
997
|
-
return ["1", "true", "yes"].includes(
|
|
998
|
-
(process.env.SCRAPPYCOCO_DISABLE_SKILL_AUTO_UPDATE || "").toLowerCase()
|
|
999
|
-
);
|
|
1000
|
-
}
|
|
1001
|
-
async function autoUpdateInstalledSkill() {
|
|
1002
|
-
if (autoUpdateDisabled()) return { status: "disabled" };
|
|
1052
|
+
async function updateInstalledSkill() {
|
|
1003
1053
|
let result;
|
|
1004
1054
|
try {
|
|
1005
1055
|
result = await withSkillUpdateLock(() => checkAndInstallSkillUpdate());
|
|
@@ -1060,10 +1110,6 @@ program.command("setup").description("Authenticate, install the Scrappycoco skil
|
|
|
1060
1110
|
await rememberInstalledSkillRelease();
|
|
1061
1111
|
await emit(result, globals(command).json || false);
|
|
1062
1112
|
});
|
|
1063
|
-
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
1064
|
-
if (actionCommand.name() === "setup") return;
|
|
1065
|
-
await autoUpdateInstalledSkill();
|
|
1066
|
-
});
|
|
1067
1113
|
function splitScraperId(value) {
|
|
1068
1114
|
const separator = value.indexOf(".");
|
|
1069
1115
|
if (separator <= 0 || separator === value.length - 1) {
|
|
@@ -1131,9 +1177,6 @@ async function emitExecution(response, options, command) {
|
|
|
1131
1177
|
}
|
|
1132
1178
|
if (Array.isArray(response.items)) {
|
|
1133
1179
|
summary.item_count = response.items.length;
|
|
1134
|
-
summary.failed_item_count = response.items.filter(
|
|
1135
|
-
(item) => item && typeof item === "object" && item.status === "failed"
|
|
1136
|
-
).length;
|
|
1137
1180
|
}
|
|
1138
1181
|
if (Array.isArray(summary.routes)) {
|
|
1139
1182
|
summary.routes = summary.routes.map((value) => {
|
|
@@ -1271,6 +1314,20 @@ auth.command("logout").action(async (_options, command) => {
|
|
|
1271
1314
|
await clearRefreshToken2();
|
|
1272
1315
|
await emit({ authenticated: false }, globals(command).json || false);
|
|
1273
1316
|
});
|
|
1317
|
+
var skill = program.command("skill").description("Inspect or explicitly update the installed skill");
|
|
1318
|
+
skill.command("status").action(async (_options, command) => {
|
|
1319
|
+
await emit(await installedSkillDiagnostics(), globals(command).json || false);
|
|
1320
|
+
});
|
|
1321
|
+
skill.command("update").action(async (_options, command) => {
|
|
1322
|
+
const result = await updateInstalledSkill();
|
|
1323
|
+
await emit(
|
|
1324
|
+
result.status === "failed" ? { status: result.status, error: result.error.message } : result,
|
|
1325
|
+
globals(command).json || false
|
|
1326
|
+
);
|
|
1327
|
+
if (result.status === "failed" || result.status === "unavailable") {
|
|
1328
|
+
process.exitCode = EXIT.api;
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1274
1331
|
program.command("doctor").description("Check the CLI, authentication, installed skill, API, and live catalog").action(async (_options, command) => {
|
|
1275
1332
|
const usingApiKey = Boolean(process.env.SCRAPPYCOCO_API_KEY);
|
|
1276
1333
|
const usingOAuth = !usingApiKey && Boolean(await loadRefreshToken());
|
|
@@ -1287,7 +1344,7 @@ program.command("doctor").description("Check the CLI, authentication, installed
|
|
|
1287
1344
|
catalogError = error instanceof Error ? error.message : String(error);
|
|
1288
1345
|
}
|
|
1289
1346
|
}
|
|
1290
|
-
const
|
|
1347
|
+
const skill2 = await installedSkillDiagnostics();
|
|
1291
1348
|
const ok = authentication.configured && catalogError === null;
|
|
1292
1349
|
await emit(
|
|
1293
1350
|
{
|
|
@@ -1306,7 +1363,7 @@ program.command("doctor").description("Check the CLI, authentication, installed
|
|
|
1306
1363
|
available_capabilities: catalog2.length,
|
|
1307
1364
|
error: catalogError
|
|
1308
1365
|
},
|
|
1309
|
-
skill,
|
|
1366
|
+
skill: skill2,
|
|
1310
1367
|
next_action: ok ? "Scrappycoco is ready." : authentication.configured ? "Check the API connection, then run doctor again." : "Run `scrappycoco setup`."
|
|
1311
1368
|
},
|
|
1312
1369
|
globals(command).json || false
|
|
@@ -1421,6 +1478,15 @@ jobs.command("wait <job-id>").description("Wait for a queued job and return its
|
|
|
1421
1478
|
command
|
|
1422
1479
|
);
|
|
1423
1480
|
});
|
|
1481
|
+
jobs.command("cancel <job-id>").description("Cancel a pending or running queued job").action(async (jobId, _options, command) => {
|
|
1482
|
+
await emit(
|
|
1483
|
+
await client(command).post(
|
|
1484
|
+
`/jobs/${encodeURIComponent(jobId)}/cancel`,
|
|
1485
|
+
{}
|
|
1486
|
+
),
|
|
1487
|
+
globals(command).json || false
|
|
1488
|
+
);
|
|
1489
|
+
});
|
|
1424
1490
|
var providers = program.command("providers", { hidden: true }).description("Legacy provider commands");
|
|
1425
1491
|
providers.command("list").option("--available", "only include available provider-capability routes").action(async (options, command) => {
|
|
1426
1492
|
await emit(
|