@scrappycoco/cli 0.5.0 → 0.7.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 +216 -108
- 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) {
|
|
@@ -1092,80 +1138,7 @@ async function emitExecution(response, options, command) {
|
|
|
1092
1138
|
const jsonMode = globals(command).json || false;
|
|
1093
1139
|
if (options.output) {
|
|
1094
1140
|
await emit(response, jsonMode, options.output, formatRecords(records, options.format));
|
|
1095
|
-
const summary =
|
|
1096
|
-
delete summary.records;
|
|
1097
|
-
delete summary.items;
|
|
1098
|
-
delete summary.normalized_schema;
|
|
1099
|
-
summary.truncated_count = records.filter((record) => {
|
|
1100
|
-
const metadata = record.metadata;
|
|
1101
|
-
return metadata && typeof metadata === "object" && !Array.isArray(metadata) && metadata.truncated === true;
|
|
1102
|
-
}).length;
|
|
1103
|
-
if (summary.usage) {
|
|
1104
|
-
summary.usage = selectFields(summary.usage, [
|
|
1105
|
-
"billing_status",
|
|
1106
|
-
"payg_charge_usd_exact",
|
|
1107
|
-
"provider_cost_usd_exact",
|
|
1108
|
-
"unresolved_cost_count"
|
|
1109
|
-
]);
|
|
1110
|
-
}
|
|
1111
|
-
if (Array.isArray(summary.attempts)) {
|
|
1112
|
-
summary.attempts = summary.attempts.map((attempt) => selectFields(attempt, [
|
|
1113
|
-
"provider",
|
|
1114
|
-
"status",
|
|
1115
|
-
"result_count",
|
|
1116
|
-
"latency_ms",
|
|
1117
|
-
"estimated_cost_usd",
|
|
1118
|
-
"error"
|
|
1119
|
-
]));
|
|
1120
|
-
}
|
|
1121
|
-
const providerResults = summary.provider_results;
|
|
1122
|
-
if (providerResults && typeof providerResults === "object" && !Array.isArray(providerResults)) {
|
|
1123
|
-
const counts = Object.fromEntries(
|
|
1124
|
-
Object.entries(providerResults).map(([provider, values]) => [
|
|
1125
|
-
provider,
|
|
1126
|
-
Array.isArray(values) ? values.length : 0
|
|
1127
|
-
])
|
|
1128
|
-
);
|
|
1129
|
-
if (Object.keys(counts).length) summary.provider_result_counts = counts;
|
|
1130
|
-
delete summary.provider_results;
|
|
1131
|
-
}
|
|
1132
|
-
if (Array.isArray(response.items)) {
|
|
1133
|
-
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
|
-
}
|
|
1138
|
-
if (Array.isArray(summary.routes)) {
|
|
1139
|
-
summary.routes = summary.routes.map((value) => {
|
|
1140
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
1141
|
-
const route = { ...value };
|
|
1142
|
-
const routeResults = route.provider_results;
|
|
1143
|
-
if (routeResults && typeof routeResults === "object" && !Array.isArray(routeResults)) {
|
|
1144
|
-
const counts = Object.fromEntries(
|
|
1145
|
-
Object.entries(routeResults).map(([provider, values]) => [
|
|
1146
|
-
provider,
|
|
1147
|
-
Array.isArray(values) ? values.length : 0
|
|
1148
|
-
])
|
|
1149
|
-
);
|
|
1150
|
-
if (Object.keys(counts).length) route.provider_result_counts = counts;
|
|
1151
|
-
delete route.provider_results;
|
|
1152
|
-
}
|
|
1153
|
-
if (Array.isArray(route.attempts)) {
|
|
1154
|
-
route.attempts = route.attempts.map((attempt) => selectFields(attempt, [
|
|
1155
|
-
"provider",
|
|
1156
|
-
"status",
|
|
1157
|
-
"result_count",
|
|
1158
|
-
"latency_ms",
|
|
1159
|
-
"estimated_cost_usd",
|
|
1160
|
-
"error"
|
|
1161
|
-
]));
|
|
1162
|
-
}
|
|
1163
|
-
return route;
|
|
1164
|
-
});
|
|
1165
|
-
}
|
|
1166
|
-
for (const field of ["cursor", "monitor"]) {
|
|
1167
|
-
if (summary[field] === null) delete summary[field];
|
|
1168
|
-
}
|
|
1141
|
+
const summary = executionSummary(response);
|
|
1169
1142
|
summary.output = {
|
|
1170
1143
|
path: options.output,
|
|
1171
1144
|
format: options.format,
|
|
@@ -1178,6 +1151,96 @@ async function emitExecution(response, options, command) {
|
|
|
1178
1151
|
}
|
|
1179
1152
|
await emit(response, jsonMode);
|
|
1180
1153
|
}
|
|
1154
|
+
function executionSummary(response) {
|
|
1155
|
+
const records = "records" in response && Array.isArray(response.records) ? response.records : response.items;
|
|
1156
|
+
const summary = { ...response };
|
|
1157
|
+
delete summary.records;
|
|
1158
|
+
delete summary.items;
|
|
1159
|
+
delete summary.normalized_schema;
|
|
1160
|
+
summary.truncated_count = records.filter((record) => {
|
|
1161
|
+
const metadata = record.metadata;
|
|
1162
|
+
return metadata && typeof metadata === "object" && !Array.isArray(metadata) && metadata.truncated === true;
|
|
1163
|
+
}).length;
|
|
1164
|
+
if (summary.usage) {
|
|
1165
|
+
summary.usage = selectFields(summary.usage, [
|
|
1166
|
+
"billing_status",
|
|
1167
|
+
"payg_charge_usd_exact",
|
|
1168
|
+
"provider_cost_usd_exact",
|
|
1169
|
+
"unresolved_cost_count"
|
|
1170
|
+
]);
|
|
1171
|
+
}
|
|
1172
|
+
if (Array.isArray(summary.attempts)) {
|
|
1173
|
+
summary.attempts = summary.attempts.map((attempt) => selectFields(attempt, [
|
|
1174
|
+
"provider",
|
|
1175
|
+
"status",
|
|
1176
|
+
"result_count",
|
|
1177
|
+
"latency_ms",
|
|
1178
|
+
"estimated_cost_usd",
|
|
1179
|
+
"error"
|
|
1180
|
+
]));
|
|
1181
|
+
}
|
|
1182
|
+
const providerResults = summary.provider_results;
|
|
1183
|
+
if (providerResults && typeof providerResults === "object" && !Array.isArray(providerResults)) {
|
|
1184
|
+
const counts = Object.fromEntries(
|
|
1185
|
+
Object.entries(providerResults).map(([provider, values]) => [
|
|
1186
|
+
provider,
|
|
1187
|
+
Array.isArray(values) ? values.length : 0
|
|
1188
|
+
])
|
|
1189
|
+
);
|
|
1190
|
+
if (Object.keys(counts).length) summary.provider_result_counts = counts;
|
|
1191
|
+
delete summary.provider_results;
|
|
1192
|
+
}
|
|
1193
|
+
if (Array.isArray(response.items)) summary.item_count = response.items.length;
|
|
1194
|
+
if (Array.isArray(summary.routes)) {
|
|
1195
|
+
summary.routes = summary.routes.map((value) => {
|
|
1196
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
1197
|
+
const route = { ...value };
|
|
1198
|
+
const routeResults = route.provider_results;
|
|
1199
|
+
if (routeResults && typeof routeResults === "object" && !Array.isArray(routeResults)) {
|
|
1200
|
+
const counts = Object.fromEntries(
|
|
1201
|
+
Object.entries(routeResults).map(([provider, values]) => [
|
|
1202
|
+
provider,
|
|
1203
|
+
Array.isArray(values) ? values.length : 0
|
|
1204
|
+
])
|
|
1205
|
+
);
|
|
1206
|
+
if (Object.keys(counts).length) route.provider_result_counts = counts;
|
|
1207
|
+
delete route.provider_results;
|
|
1208
|
+
}
|
|
1209
|
+
if (Array.isArray(route.attempts)) {
|
|
1210
|
+
route.attempts = route.attempts.map((attempt) => selectFields(attempt, [
|
|
1211
|
+
"provider",
|
|
1212
|
+
"status",
|
|
1213
|
+
"result_count",
|
|
1214
|
+
"latency_ms",
|
|
1215
|
+
"estimated_cost_usd",
|
|
1216
|
+
"error"
|
|
1217
|
+
]));
|
|
1218
|
+
}
|
|
1219
|
+
return route;
|
|
1220
|
+
});
|
|
1221
|
+
}
|
|
1222
|
+
for (const field of ["cursor", "monitor"]) {
|
|
1223
|
+
if (summary[field] === null) delete summary[field];
|
|
1224
|
+
}
|
|
1225
|
+
return summary;
|
|
1226
|
+
}
|
|
1227
|
+
function compactCatalog(items) {
|
|
1228
|
+
return items.map((item) => ({
|
|
1229
|
+
id: item.id,
|
|
1230
|
+
source: item.source,
|
|
1231
|
+
capability: item.capability,
|
|
1232
|
+
label: item.label,
|
|
1233
|
+
description: item.description,
|
|
1234
|
+
execution_mode: item.execution_mode,
|
|
1235
|
+
providers: Array.isArray(item.providers) ? item.providers.map((provider) => selectFields(provider, [
|
|
1236
|
+
"id",
|
|
1237
|
+
"label",
|
|
1238
|
+
"available",
|
|
1239
|
+
"reason",
|
|
1240
|
+
"pricing"
|
|
1241
|
+
])) : []
|
|
1242
|
+
}));
|
|
1243
|
+
}
|
|
1181
1244
|
async function executeQueuedJob(path, payload, options, command) {
|
|
1182
1245
|
if (options.detach && options.output) {
|
|
1183
1246
|
throw new CliError(
|
|
@@ -1271,6 +1334,20 @@ auth.command("logout").action(async (_options, command) => {
|
|
|
1271
1334
|
await clearRefreshToken2();
|
|
1272
1335
|
await emit({ authenticated: false }, globals(command).json || false);
|
|
1273
1336
|
});
|
|
1337
|
+
var skill = program.command("skill").description("Inspect or explicitly update the installed skill");
|
|
1338
|
+
skill.command("status").action(async (_options, command) => {
|
|
1339
|
+
await emit(await installedSkillDiagnostics(), globals(command).json || false);
|
|
1340
|
+
});
|
|
1341
|
+
skill.command("update").action(async (_options, command) => {
|
|
1342
|
+
const result = await updateInstalledSkill();
|
|
1343
|
+
await emit(
|
|
1344
|
+
result.status === "failed" ? { status: result.status, error: result.error.message } : result,
|
|
1345
|
+
globals(command).json || false
|
|
1346
|
+
);
|
|
1347
|
+
if (result.status === "failed" || result.status === "unavailable") {
|
|
1348
|
+
process.exitCode = EXIT.api;
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1274
1351
|
program.command("doctor").description("Check the CLI, authentication, installed skill, API, and live catalog").action(async (_options, command) => {
|
|
1275
1352
|
const usingApiKey = Boolean(process.env.SCRAPPYCOCO_API_KEY);
|
|
1276
1353
|
const usingOAuth = !usingApiKey && Boolean(await loadRefreshToken());
|
|
@@ -1287,7 +1364,7 @@ program.command("doctor").description("Check the CLI, authentication, installed
|
|
|
1287
1364
|
catalogError = error instanceof Error ? error.message : String(error);
|
|
1288
1365
|
}
|
|
1289
1366
|
}
|
|
1290
|
-
const
|
|
1367
|
+
const skill2 = await installedSkillDiagnostics();
|
|
1291
1368
|
const ok = authentication.configured && catalogError === null;
|
|
1292
1369
|
await emit(
|
|
1293
1370
|
{
|
|
@@ -1306,7 +1383,7 @@ program.command("doctor").description("Check the CLI, authentication, installed
|
|
|
1306
1383
|
available_capabilities: catalog2.length,
|
|
1307
1384
|
error: catalogError
|
|
1308
1385
|
},
|
|
1309
|
-
skill,
|
|
1386
|
+
skill: skill2,
|
|
1310
1387
|
next_action: ok ? "Scrappycoco is ready." : authentication.configured ? "Check the API connection, then run doctor again." : "Run `scrappycoco setup`."
|
|
1311
1388
|
},
|
|
1312
1389
|
globals(command).json || false
|
|
@@ -1332,13 +1409,13 @@ scrapers.command("inspect <scraper-id>").action(async (scraperId, _options, comm
|
|
|
1332
1409
|
);
|
|
1333
1410
|
});
|
|
1334
1411
|
var catalog = program.command("catalog").description("Inspect capabilities, provider schemas, options, and pricing");
|
|
1335
|
-
catalog.command("list").option("--source <source>", "filter by web, x, reddit, or filings").option("--provider <provider>", "filter by provider implementation").option("--available", "only include
|
|
1412
|
+
catalog.command("list").option("--source <source>", "filter by web, x, reddit, or filings").option("--provider <provider>", "filter by provider implementation").option("--available", "only include configured providers; this is not a live health check").option("--full", "include input/output and provider option schemas in JSON output").action(async (options, command) => {
|
|
1336
1413
|
const query = new URLSearchParams();
|
|
1337
1414
|
if (options.source) query.set("source", options.source);
|
|
1338
1415
|
if (options.provider) query.set("provider", options.provider);
|
|
1339
1416
|
if (options.available) query.set("available_only", "true");
|
|
1340
1417
|
const items = await client(command).get(`/scrapers${query.size ? `?${query}` : ""}`);
|
|
1341
|
-
if (globals(command).json) await emit(items, true);
|
|
1418
|
+
if (globals(command).json) await emit(options.full ? items : compactCatalog(items), true);
|
|
1342
1419
|
else process.stdout.write(formatCatalog(items));
|
|
1343
1420
|
});
|
|
1344
1421
|
catalog.command("inspect <capability-id>").action(async (capabilityId, _options, command) => {
|
|
@@ -1421,6 +1498,15 @@ jobs.command("wait <job-id>").description("Wait for a queued job and return its
|
|
|
1421
1498
|
command
|
|
1422
1499
|
);
|
|
1423
1500
|
});
|
|
1501
|
+
jobs.command("cancel <job-id>").description("Cancel a pending or running queued job").action(async (jobId, _options, command) => {
|
|
1502
|
+
await emit(
|
|
1503
|
+
await client(command).post(
|
|
1504
|
+
`/jobs/${encodeURIComponent(jobId)}/cancel`,
|
|
1505
|
+
{}
|
|
1506
|
+
),
|
|
1507
|
+
globals(command).json || false
|
|
1508
|
+
);
|
|
1509
|
+
});
|
|
1424
1510
|
var providers = program.command("providers", { hidden: true }).description("Legacy provider commands");
|
|
1425
1511
|
providers.command("list").option("--available", "only include available provider-capability routes").action(async (options, command) => {
|
|
1426
1512
|
await emit(
|
|
@@ -1473,7 +1559,7 @@ discoveries.command("delete <discovery-id>").requiredOption("--yes", "confirm pe
|
|
|
1473
1559
|
await client(command).delete(`/discoveries/${encodeURIComponent(discoveryId)}`);
|
|
1474
1560
|
await emit({ deleted: true, discovery_id: discoveryId }, globals(command).json || false);
|
|
1475
1561
|
});
|
|
1476
|
-
program.command("discover").description("Save, sample-test, or finalize an agent-authored configuration").option("-f, --file <path>", "create from agent-authored discovery JSON").option("--id <discovery-id>", "existing discovery ID").option("--test", "run a representative provider sample test").option("--input <json>", "sample runtime input JSON").option("--update <path>", "replace fields or configuration from agent-authored JSON").option("--finalize", "mark the current explicit configuration finalized").option("--idempotency-key <key>", "stable sample retry key").action(async (options, command) => {
|
|
1562
|
+
program.command("discover").description("Save, sample-test, or finalize an agent-authored configuration").option("-f, --file <path>", "create from agent-authored discovery JSON").option("--id <discovery-id>", "existing discovery ID").option("--test", "run a representative provider sample test").option("--input <json>", "sample runtime input JSON").option("--update <path>", "replace fields or configuration from agent-authored JSON").option("--finalize", "mark the current explicit configuration finalized").option("--idempotency-key <key>", "stable sample retry key").option("-o, --output <path>", "write complete sample-test evidence to a JSON file").action(async (options, command) => {
|
|
1477
1563
|
const selected = Number(Boolean(options.file)) + Number(Boolean(options.test)) + Number(Boolean(options.update)) + Number(Boolean(options.finalize));
|
|
1478
1564
|
if (selected !== 1) {
|
|
1479
1565
|
throw new CliError(
|
|
@@ -1481,6 +1567,9 @@ program.command("discover").description("Save, sample-test, or finalize an agent
|
|
|
1481
1567
|
EXIT.usage
|
|
1482
1568
|
);
|
|
1483
1569
|
}
|
|
1570
|
+
if (options.output && !options.test) {
|
|
1571
|
+
throw new CliError("--output is supported only with --test.", EXIT.usage);
|
|
1572
|
+
}
|
|
1484
1573
|
if (options.file) {
|
|
1485
1574
|
await emit(
|
|
1486
1575
|
await client(command).post("/discoveries", await readJsonFile(options.file)),
|
|
@@ -1515,6 +1604,25 @@ program.command("discover").description("Save, sample-test, or finalize an agent
|
|
|
1515
1604
|
{ input, limit: 25 },
|
|
1516
1605
|
options.idempotencyKey || randomUUID4()
|
|
1517
1606
|
);
|
|
1607
|
+
if (options.output) {
|
|
1608
|
+
await emit(
|
|
1609
|
+
response,
|
|
1610
|
+
true,
|
|
1611
|
+
options.output,
|
|
1612
|
+
`${JSON.stringify(response, null, 2)}
|
|
1613
|
+
`
|
|
1614
|
+
);
|
|
1615
|
+
const summary = executionSummary(response);
|
|
1616
|
+
summary.output = {
|
|
1617
|
+
path: options.output,
|
|
1618
|
+
format: "json",
|
|
1619
|
+
kind: "discovery_evidence"
|
|
1620
|
+
};
|
|
1621
|
+
await emit(summary, globals(command).json || false);
|
|
1622
|
+
process.stderr.write(`Saved complete Discovery evidence to ${options.output}
|
|
1623
|
+
`);
|
|
1624
|
+
return;
|
|
1625
|
+
}
|
|
1518
1626
|
await emit(response, globals(command).json || false);
|
|
1519
1627
|
});
|
|
1520
1628
|
program.configureOutput({ writeErr: (text) => process.stderr.write(text) });
|