@scrappycoco/cli 0.6.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.
Files changed (2) hide show
  1. package/dist/index.js +116 -74
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1138,77 +1138,7 @@ async function emitExecution(response, options, command) {
1138
1138
  const jsonMode = globals(command).json || false;
1139
1139
  if (options.output) {
1140
1140
  await emit(response, jsonMode, options.output, formatRecords(records, options.format));
1141
- const summary = { ...response };
1142
- delete summary.records;
1143
- delete summary.items;
1144
- delete summary.normalized_schema;
1145
- summary.truncated_count = records.filter((record) => {
1146
- const metadata = record.metadata;
1147
- return metadata && typeof metadata === "object" && !Array.isArray(metadata) && metadata.truncated === true;
1148
- }).length;
1149
- if (summary.usage) {
1150
- summary.usage = selectFields(summary.usage, [
1151
- "billing_status",
1152
- "payg_charge_usd_exact",
1153
- "provider_cost_usd_exact",
1154
- "unresolved_cost_count"
1155
- ]);
1156
- }
1157
- if (Array.isArray(summary.attempts)) {
1158
- summary.attempts = summary.attempts.map((attempt) => selectFields(attempt, [
1159
- "provider",
1160
- "status",
1161
- "result_count",
1162
- "latency_ms",
1163
- "estimated_cost_usd",
1164
- "error"
1165
- ]));
1166
- }
1167
- const providerResults = summary.provider_results;
1168
- if (providerResults && typeof providerResults === "object" && !Array.isArray(providerResults)) {
1169
- const counts = Object.fromEntries(
1170
- Object.entries(providerResults).map(([provider, values]) => [
1171
- provider,
1172
- Array.isArray(values) ? values.length : 0
1173
- ])
1174
- );
1175
- if (Object.keys(counts).length) summary.provider_result_counts = counts;
1176
- delete summary.provider_results;
1177
- }
1178
- if (Array.isArray(response.items)) {
1179
- summary.item_count = response.items.length;
1180
- }
1181
- if (Array.isArray(summary.routes)) {
1182
- summary.routes = summary.routes.map((value) => {
1183
- if (!value || typeof value !== "object" || Array.isArray(value)) return value;
1184
- const route = { ...value };
1185
- const routeResults = route.provider_results;
1186
- if (routeResults && typeof routeResults === "object" && !Array.isArray(routeResults)) {
1187
- const counts = Object.fromEntries(
1188
- Object.entries(routeResults).map(([provider, values]) => [
1189
- provider,
1190
- Array.isArray(values) ? values.length : 0
1191
- ])
1192
- );
1193
- if (Object.keys(counts).length) route.provider_result_counts = counts;
1194
- delete route.provider_results;
1195
- }
1196
- if (Array.isArray(route.attempts)) {
1197
- route.attempts = route.attempts.map((attempt) => selectFields(attempt, [
1198
- "provider",
1199
- "status",
1200
- "result_count",
1201
- "latency_ms",
1202
- "estimated_cost_usd",
1203
- "error"
1204
- ]));
1205
- }
1206
- return route;
1207
- });
1208
- }
1209
- for (const field of ["cursor", "monitor"]) {
1210
- if (summary[field] === null) delete summary[field];
1211
- }
1141
+ const summary = executionSummary(response);
1212
1142
  summary.output = {
1213
1143
  path: options.output,
1214
1144
  format: options.format,
@@ -1221,6 +1151,96 @@ async function emitExecution(response, options, command) {
1221
1151
  }
1222
1152
  await emit(response, jsonMode);
1223
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
+ }
1224
1244
  async function executeQueuedJob(path, payload, options, command) {
1225
1245
  if (options.detach && options.output) {
1226
1246
  throw new CliError(
@@ -1389,13 +1409,13 @@ scrapers.command("inspect <scraper-id>").action(async (scraperId, _options, comm
1389
1409
  );
1390
1410
  });
1391
1411
  var catalog = program.command("catalog").description("Inspect capabilities, provider schemas, options, and pricing");
1392
- catalog.command("list").option("--source <source>", "filter by web, x, reddit, or filings").option("--provider <provider>", "filter by provider implementation").option("--available", "only include available providers").action(async (options, command) => {
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) => {
1393
1413
  const query = new URLSearchParams();
1394
1414
  if (options.source) query.set("source", options.source);
1395
1415
  if (options.provider) query.set("provider", options.provider);
1396
1416
  if (options.available) query.set("available_only", "true");
1397
1417
  const items = await client(command).get(`/scrapers${query.size ? `?${query}` : ""}`);
1398
- if (globals(command).json) await emit(items, true);
1418
+ if (globals(command).json) await emit(options.full ? items : compactCatalog(items), true);
1399
1419
  else process.stdout.write(formatCatalog(items));
1400
1420
  });
1401
1421
  catalog.command("inspect <capability-id>").action(async (capabilityId, _options, command) => {
@@ -1539,7 +1559,7 @@ discoveries.command("delete <discovery-id>").requiredOption("--yes", "confirm pe
1539
1559
  await client(command).delete(`/discoveries/${encodeURIComponent(discoveryId)}`);
1540
1560
  await emit({ deleted: true, discovery_id: discoveryId }, globals(command).json || false);
1541
1561
  });
1542
- 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) => {
1543
1563
  const selected = Number(Boolean(options.file)) + Number(Boolean(options.test)) + Number(Boolean(options.update)) + Number(Boolean(options.finalize));
1544
1564
  if (selected !== 1) {
1545
1565
  throw new CliError(
@@ -1547,6 +1567,9 @@ program.command("discover").description("Save, sample-test, or finalize an agent
1547
1567
  EXIT.usage
1548
1568
  );
1549
1569
  }
1570
+ if (options.output && !options.test) {
1571
+ throw new CliError("--output is supported only with --test.", EXIT.usage);
1572
+ }
1550
1573
  if (options.file) {
1551
1574
  await emit(
1552
1575
  await client(command).post("/discoveries", await readJsonFile(options.file)),
@@ -1581,6 +1604,25 @@ program.command("discover").description("Save, sample-test, or finalize an agent
1581
1604
  { input, limit: 25 },
1582
1605
  options.idempotencyKey || randomUUID4()
1583
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
+ }
1584
1626
  await emit(response, globals(command).json || false);
1585
1627
  });
1586
1628
  program.configureOutput({ writeErr: (text) => process.stderr.write(text) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrappycoco/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "CLI for Scrappycoco scraper discovery and execution",
5
5
  "type": "module",
6
6
  "bin": {