Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 198 additions & 0 deletions cli-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -17096,6 +17096,204 @@
"sourceFile": "indeed/search.js",
"navigateBefore": false
},
{
"site": "instacart",
"name": "categories",
"description": "Visible Instacart collection links for a retailer",
"access": "read",
"domain": "www.instacart.com",
"strategy": "ui",
"browser": true,
"args": [
{
"name": "retailer",
"type": "str",
"required": true,
"positional": true,
"help": "Retailer slug, for example sprouts or costco"
},
{
"name": "limit",
"type": "int",
"default": 10,
"required": false,
"help": "Maximum collections to return (1-30)"
}
],
"columns": [
"rank",
"slug",
"name",
"url"
],
"type": "js",
"modulePath": "instacart/categories.js",
"sourceFile": "instacart/categories.js",
"navigateBefore": true,
"siteSession": "persistent"
},
{
"site": "instacart",
"name": "collection",
"description": "Visible Instacart product cards from a retailer collection",
"access": "read",
"domain": "www.instacart.com",
"strategy": "ui",
"browser": true,
"args": [
{
"name": "retailer",
"type": "str",
"required": true,
"positional": true,
"help": "Retailer slug, for example sprouts or costco"
},
{
"name": "collection",
"type": "str",
"required": true,
"positional": true,
"help": "Collection slug, for example produce or fresh-fruits"
},
{
"name": "limit",
"type": "int",
"default": 10,
"required": false,
"help": "Maximum products to return (1-30)"
}
],
"columns": [
"rank",
"productId",
"title",
"priceText",
"originalPriceText",
"discount",
"size",
"stock",
"url"
],
"type": "js",
"modulePath": "instacart/collection.js",
"sourceFile": "instacart/collection.js",
"navigateBefore": true,
"siteSession": "persistent"
},
{
"site": "instacart",
"name": "product",
"description": "Visible Instacart product detail by product URL or id",
"access": "read",
"domain": "www.instacart.com",
"strategy": "ui",
"browser": true,
"args": [
{
"name": "product",
"type": "str",
"required": true,
"positional": true,
"help": "Instacart product URL or numeric product id"
},
{
"name": "retailer",
"type": "string",
"default": "",
"required": false,
"help": "Retailer slug required when product is a numeric id, for example sprouts"
}
],
"columns": [
"productId",
"title",
"priceText",
"originalPriceText",
"discount",
"size",
"stock",
"retailer",
"url"
],
"type": "js",
"modulePath": "instacart/product.js",
"sourceFile": "instacart/product.js",
"navigateBefore": true,
"siteSession": "persistent"
},
{
"site": "instacart",
"name": "storefront",
"description": "Visible Instacart product cards from a retailer storefront",
"access": "read",
"domain": "www.instacart.com",
"strategy": "ui",
"browser": true,
"args": [
{
"name": "retailer",
"type": "str",
"required": true,
"positional": true,
"help": "Retailer slug, for example sprouts or costco"
},
{
"name": "limit",
"type": "int",
"default": 10,
"required": false,
"help": "Maximum products to return (1-30)"
}
],
"columns": [
"rank",
"productId",
"title",
"priceText",
"originalPriceText",
"discount",
"size",
"stock",
"url"
],
"type": "js",
"modulePath": "instacart/storefront.js",
"sourceFile": "instacart/storefront.js",
"navigateBefore": true,
"siteSession": "persistent"
},
{
"site": "instacart",
"name": "stores",
"description": "Visible Instacart nearby stores from the public marketplace page",
"access": "read",
"domain": "www.instacart.com",
"strategy": "ui",
"browser": true,
"args": [
{
"name": "limit",
"type": "int",
"default": 10,
"required": false,
"help": "Maximum stores to return (1-30)"
}
],
"columns": [
"rank",
"slug",
"name",
"delivery",
"pickup",
"tags",
"url"
],
"type": "js",
"modulePath": "instacart/stores.js",
"sourceFile": "instacart/stores.js",
"navigateBefore": true,
"siteSession": "persistent"
},
{
"site": "instagram",
"name": "collection-create",
Expand Down
38 changes: 38 additions & 0 deletions clis/instacart/categories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { AuthRequiredError, CommandExecutionError, EmptyResultError } from '@agentrhq/webcmd/errors';
import { cli, Strategy } from '@agentrhq/webcmd/registry';
import { BASE_URL, HOST, buildExtractCollectionsScript, gotoInstacartPage, normalizeRetailer, parseLimit } from './utils.js';

cli({
site: 'instacart',
name: 'categories',
access: 'read',
description: 'Visible Instacart collection links for a retailer',
domain: HOST,
strategy: Strategy.UI,
siteSession: 'persistent',
args: [
{ name: 'retailer', positional: true, required: true, help: 'Retailer slug, for example sprouts or costco' },
{ name: 'limit', type: 'int', default: 10, help: 'Maximum collections to return (1-30)' },
],
columns: ['rank', 'slug', 'name', 'url'],
func: async (page, kwargs) => {
const retailer = normalizeRetailer(kwargs.retailer);
const limit = parseLimit(kwargs.limit);
await gotoInstacartPage(page, `${BASE_URL}/store/${retailer}/storefront`, 2500);
await page.wait({ selector: `a[href*="/store/${retailer}/collections/"]`, timeout: 8 }).catch(async () => {
await page.wait(3);
});
const rows = await page.evaluate(buildExtractCollectionsScript(retailer, limit));
if (!Array.isArray(rows)) {
throw new CommandExecutionError('Instacart categories extraction returned an unreadable response');
}
const pageText = await page.evaluate('(() => String(document.body?.innerText || document.body?.textContent || "").slice(0, 2000))()');
if (/log in to continue|sign up to continue|verify you are human|captcha/i.test(String(pageText))) {
throw new AuthRequiredError(HOST, 'Instacart requires browser access. Open Instacart in CloakBrowser, clear any prompt, then rerun.');
}
if (!rows.length) {
throw new EmptyResultError('instacart categories', `No visible collection links were found for retailer "${retailer}". Try a retailer from \`webcmd instacart stores\`.`);
}
return rows;
},
});
68 changes: 68 additions & 0 deletions clis/instacart/collection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { AuthRequiredError, CommandExecutionError, EmptyResultError } from '@agentrhq/webcmd/errors';
import { cli, Strategy } from '@agentrhq/webcmd/registry';
import { BASE_URL, HOST, buildExtractProductsScript, gotoInstacartPage, normalizeCollection, normalizeRetailer, parseLimit } from './utils.js';

async function ensureCollectionRoute(page, retailer, collection) {
const path = `/store/${retailer}/collections/${collection}`;
const currentUrl = await page.evaluate('window.location.href').catch(() => '');
if (String(currentUrl).includes(path)) return;

await gotoInstacartPage(page, `${BASE_URL}/store/${retailer}/storefront`, 2500);
await page.wait({ selector: `a[href*="${path}"]`, timeout: 8 }).catch(async () => {
await page.wait(3);
});
const clicked = await page.evaluate(`(() => {
const path = ${JSON.stringify(path)};
const link = Array.from(document.querySelectorAll('a[href*="/collections/"]')).find((node) => {
try { return new URL(node.getAttribute('href') || '', location.href).pathname === path; } catch { return false; }
});
if (!link) return false;
link.click();
return true;
})()`);
if (clicked) {
await page.wait(3);
}
const nextUrl = await page.evaluate('window.location.href').catch(() => '');
if (!String(nextUrl).includes(path)) {
throw new CommandExecutionError(`Instacart did not navigate to collection "${retailer}/${collection}"`);
}
}

cli({
site: 'instacart',
name: 'collection',
access: 'read',
description: 'Visible Instacart product cards from a retailer collection',
domain: HOST,
strategy: Strategy.UI,
siteSession: 'persistent',
args: [
{ name: 'retailer', positional: true, required: true, help: 'Retailer slug, for example sprouts or costco' },
{ name: 'collection', positional: true, required: true, help: 'Collection slug, for example produce or fresh-fruits' },
{ name: 'limit', type: 'int', default: 10, help: 'Maximum products to return (1-30)' },
],
columns: ['rank', 'productId', 'title', 'priceText', 'originalPriceText', 'discount', 'size', 'stock', 'url'],
func: async (page, kwargs) => {
const retailer = normalizeRetailer(kwargs.retailer);
const collection = normalizeCollection(kwargs.collection);
const limit = parseLimit(kwargs.limit);
await gotoInstacartPage(page, `${BASE_URL}/store/${retailer}/collections/${collection}`, 2500);
await ensureCollectionRoute(page, retailer, collection);
await page.wait({ selector: 'a[href*="/products/"]', timeout: 8 }).catch(async () => {
await page.wait(3);
});
const rows = await page.evaluate(buildExtractProductsScript(limit));
if (!Array.isArray(rows)) {
throw new CommandExecutionError('Instacart collection extraction returned an unreadable response');
}
const pageText = await page.evaluate('(() => String(document.body?.innerText || document.body?.textContent || "").slice(0, 2000))()');
if (/log in to continue|sign up to continue|verify you are human|captcha/i.test(String(pageText))) {
throw new AuthRequiredError(HOST, 'Instacart requires browser access. Open Instacart in CloakBrowser, clear any prompt, then rerun.');
}
if (!rows.length) {
throw new EmptyResultError('instacart collection', `No visible product cards were found for "${retailer}/${collection}". Try a collection from \`webcmd instacart categories ${retailer}\`.`);
}
return rows;
},
});
Loading
Loading