@enerlence/suntropy-cli 0.12.0 → 0.14.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/dist/bin/suntropy.js +176 -9
- package/dist/bin/suntropy.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/suntropy.js
CHANGED
|
@@ -1872,7 +1872,7 @@ function registerInventoryCommands(program2) {
|
|
|
1872
1872
|
import { Option } from "commander";
|
|
1873
1873
|
import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, existsSync as existsSync2 } from "fs";
|
|
1874
1874
|
|
|
1875
|
-
// node_modules/uuid/dist/esm-node/rng.js
|
|
1875
|
+
// ../suntropy-cli/node_modules/uuid/dist/esm-node/rng.js
|
|
1876
1876
|
import crypto from "crypto";
|
|
1877
1877
|
var rnds8Pool = new Uint8Array(256);
|
|
1878
1878
|
var poolPtr = rnds8Pool.length;
|
|
@@ -1884,16 +1884,16 @@ function rng() {
|
|
|
1884
1884
|
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
1885
1885
|
}
|
|
1886
1886
|
|
|
1887
|
-
// node_modules/uuid/dist/esm-node/regex.js
|
|
1887
|
+
// ../suntropy-cli/node_modules/uuid/dist/esm-node/regex.js
|
|
1888
1888
|
var regex_default = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i;
|
|
1889
1889
|
|
|
1890
|
-
// node_modules/uuid/dist/esm-node/validate.js
|
|
1890
|
+
// ../suntropy-cli/node_modules/uuid/dist/esm-node/validate.js
|
|
1891
1891
|
function validate(uuid) {
|
|
1892
1892
|
return typeof uuid === "string" && regex_default.test(uuid);
|
|
1893
1893
|
}
|
|
1894
1894
|
var validate_default = validate;
|
|
1895
1895
|
|
|
1896
|
-
// node_modules/uuid/dist/esm-node/stringify.js
|
|
1896
|
+
// ../suntropy-cli/node_modules/uuid/dist/esm-node/stringify.js
|
|
1897
1897
|
var byteToHex = [];
|
|
1898
1898
|
for (let i = 0; i < 256; ++i) {
|
|
1899
1899
|
byteToHex.push((i + 256).toString(16).substr(1));
|
|
@@ -1907,7 +1907,7 @@ function stringify(arr, offset = 0) {
|
|
|
1907
1907
|
}
|
|
1908
1908
|
var stringify_default = stringify;
|
|
1909
1909
|
|
|
1910
|
-
// node_modules/uuid/dist/esm-node/v4.js
|
|
1910
|
+
// ../suntropy-cli/node_modules/uuid/dist/esm-node/v4.js
|
|
1911
1911
|
function v4(options, buf, offset) {
|
|
1912
1912
|
options = options || {};
|
|
1913
1913
|
const rnds = options.random || (options.rng || rng)();
|
|
@@ -4645,6 +4645,21 @@ async function call(client, method, path, options = {}) {
|
|
|
4645
4645
|
});
|
|
4646
4646
|
return res.data?.data;
|
|
4647
4647
|
}
|
|
4648
|
+
async function callMultipart(client, path, filePath, payload, params) {
|
|
4649
|
+
const FormData = (await import("form-data")).default;
|
|
4650
|
+
const form = new FormData();
|
|
4651
|
+
form.append("file", readFileSync6(filePath), { filename: filePath.split("/").pop() });
|
|
4652
|
+
if (payload) form.append("payload", JSON.stringify(payload));
|
|
4653
|
+
const res = await client.request({
|
|
4654
|
+
method: "post",
|
|
4655
|
+
url: path,
|
|
4656
|
+
params: dropEmpty(params),
|
|
4657
|
+
data: form,
|
|
4658
|
+
headers: form.getHeaders(),
|
|
4659
|
+
maxBodyLength: Infinity
|
|
4660
|
+
});
|
|
4661
|
+
return res.data?.data;
|
|
4662
|
+
}
|
|
4648
4663
|
function dropEmpty(params) {
|
|
4649
4664
|
if (!params) return void 0;
|
|
4650
4665
|
return Object.fromEntries(
|
|
@@ -4753,7 +4768,7 @@ function buildArea(opts) {
|
|
|
4753
4768
|
return { type: "polygon", coordinates: parsed };
|
|
4754
4769
|
}
|
|
4755
4770
|
function registerSatvoltCampaignCommands(satvolt) {
|
|
4756
|
-
const campaigns = satvolt.command("campaigns").description("Satvolt campaigns: list, create from an area, start, reset, resume, usage, logs and funnel.");
|
|
4771
|
+
const campaigns = satvolt.command("campaigns").description("Satvolt campaigns: list, create from an area, start, pause, cancel, reset, resume, usage, logs and funnel.");
|
|
4757
4772
|
campaigns.command("list").description(
|
|
4758
4773
|
'List campaigns, newest first.\nExamples:\n suntropy satvolt campaigns list --state completed,inProgress\n suntropy satvolt campaigns list --search "Alcobendas" --format human'
|
|
4759
4774
|
).option("--limit <n>", "Max results (max 200)", "50").option("--offset <n>", "Skip results", "0").option("--search <text>", "Match name, region or input address").option("--state <states>", "Comma-separated campaign states (see: satvolt catalog states)").option("--source <source>", "maps | excel | campaign").action(async (opts) => {
|
|
@@ -4831,6 +4846,94 @@ search query and lead limit come from it; any flag you pass wins.
|
|
|
4831
4846
|
outputError(satvoltError(err));
|
|
4832
4847
|
}
|
|
4833
4848
|
});
|
|
4849
|
+
campaigns.command("estimate").summary("Count the businesses a campaign would find over an area, without creating it.").description(
|
|
4850
|
+
'Preview of FIND_LEADS: how many businesses Google Maps returns inside the area with\nthe given business groups (nearby search) or text query, plus a sample to inspect.\nNothing is created and no credits are spent. Use it to iterate the filters before\n`campaigns create`: check `places.atLeast`, `byType` and the `sample` names, then\ntighten or widen --business-groups until the sample looks like the target.\n\nThe count is a minimum: the preview has a request budget and is cached (7 days) per\narea, groups and query; `places.exhaustive:false` means dense zones were left out.\nA text query matches place NAMES, not categories ("manufactura" only finds businesses\ncalled "Manufacturas \u2026"): for a category, use business groups without --search-query.\n\nArea (exactly one): --circle <lat,lng> --radius <m> | --bounds <nwLat,nwLng,seLat,seLng>\n| --polygon <json|@file|->. Base (optional): --template or --from-campaign give the\ngroups and query when not passed explicitly.\n\nExamples:\n suntropy satvolt campaigns estimate --circle 43.3934,-3.8445 --radius 1500 \\\n --business-groups businesses --format human\n suntropy satvolt campaigns estimate --circle 43.3934,-3.8445 --radius 1500 \\\n --business-groups industrial_logistics,automotive --sample 50\n suntropy satvolt campaigns estimate --bounds 40.45,-3.70,40.44,-3.68 --template "Industria"'
|
|
4851
|
+
).option("--circle <lat,lng>", "Circle center").option("--radius <meters>", "Circle radius in meters").option("--bounds <nwLat,nwLng,seLat,seLng>", "Rectangle corners").option("--polygon <json>", "Polygon points, GeoJSON, @file or - for stdin").option("--template <idOrName>", "Take business groups and query from a campaign template").option("--from-campaign <id>", "Take business groups and query from another Maps campaign").option("--business-groups <ids>", "Comma-separated group ids or raw Google Places types. See: satvolt catalog business-groups").option("--search-query <text>", "Places text search with this query instead of nearby search (matches names, not categories)").option("--sample <n>", "Sample places to return (1-100, default 20)").option("--offset <n>", "Skip this many sample places (to page through them)").action(async (opts) => {
|
|
4852
|
+
const global = getGlobalOpts17(campaigns);
|
|
4853
|
+
try {
|
|
4854
|
+
const body = { area: buildArea(opts) };
|
|
4855
|
+
if (opts.template && opts.fromCampaign) throw new Error("Use either --template or --from-campaign, not both");
|
|
4856
|
+
if (opts.template) body.templateId = opts.template;
|
|
4857
|
+
if (opts.fromCampaign) body.fromCampaignId = parseId(opts.fromCampaign, "--from-campaign");
|
|
4858
|
+
if (opts.businessGroups) body.businessGroups = opts.businessGroups.split(",").map((s) => s.trim()).filter(Boolean);
|
|
4859
|
+
if (opts.searchQuery) body.searchQuery = opts.searchQuery;
|
|
4860
|
+
if (opts.sample !== void 0) body.sample = parseIntOption(opts.sample, "--sample");
|
|
4861
|
+
if (opts.offset !== void 0) body.offset = parseIntOption(opts.offset, "--offset");
|
|
4862
|
+
const data = await call(satvoltClient(global, 12e4), "post", "/campaigns/estimate", { data: body });
|
|
4863
|
+
output(data, global);
|
|
4864
|
+
} catch (err) {
|
|
4865
|
+
outputError(satvoltError(err));
|
|
4866
|
+
}
|
|
4867
|
+
});
|
|
4868
|
+
const columnFlag = (value) => value ? { column: value } : void 0;
|
|
4869
|
+
const columnsList = (value) => value ? value.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
4870
|
+
campaigns.command("excel-preview <file>").summary("Headers and first rows of an Excel, to decide the column mapping.").description(
|
|
4871
|
+
'Reads the first sheet of an .xlsx (headers in row 1) and returns `headers`,\n`sampleRows` and `totalRows`, without creating anything. Run it before\n`create-from-excel` to see which column holds the name, the address parts,\nthe coordinates ("lat,lng"), the phone, the website or the email.\n\nExample:\n suntropy satvolt campaigns excel-preview empresas.xlsx --sample 10 --format human'
|
|
4872
|
+
).option("--sample <n>", "Rows to return (1-50, default 5)").action(async (file, opts) => {
|
|
4873
|
+
const global = getGlobalOpts17(campaigns);
|
|
4874
|
+
try {
|
|
4875
|
+
const data = await callMultipart(satvoltClient(global), "/campaigns/excel/preview", file, void 0, {
|
|
4876
|
+
sampleSize: opts.sample !== void 0 ? parseIntOption(opts.sample, "--sample") : void 0
|
|
4877
|
+
});
|
|
4878
|
+
output(data, global);
|
|
4879
|
+
} catch (err) {
|
|
4880
|
+
outputError(satvoltError(err));
|
|
4881
|
+
}
|
|
4882
|
+
});
|
|
4883
|
+
campaigns.command("excel-geocode-test <file>").summary("Geocode the first rows with the chosen address columns, before creating.").description(
|
|
4884
|
+
'When the Excel has no coordinates, the campaign geocodes each row from the\ncolumns you choose (concatenated with commas). This runs that geocoding on the\nfirst rows only and shows the query, the coordinates and the formatted address\nfound, so you can check the columns are right before paying for every lead.\nCosts one Google geocoding request per sampled row.\n\nExample:\n suntropy satvolt campaigns excel-geocode-test empresas.xlsx \\\n --columns "Direcci\xF3n,CP,Municipio" --region Cantabria --sample 5 --format human'
|
|
4885
|
+
).requiredOption("--columns <headers>", "Comma-separated Excel headers that form the address, in order").option("--sample <n>", "Rows to test (1-25, default 5)").option("--region <text>", "Region appended to every query to disambiguate (province, country)").action(async (file, opts) => {
|
|
4886
|
+
const global = getGlobalOpts17(campaigns);
|
|
4887
|
+
try {
|
|
4888
|
+
const data = await callMultipart(satvoltClient(global, 12e4), "/campaigns/excel/geocode-test", file, {
|
|
4889
|
+
columns: columnsList(opts.columns),
|
|
4890
|
+
sampleSize: opts.sample !== void 0 ? parseIntOption(opts.sample, "--sample") : void 0,
|
|
4891
|
+
region: opts.region
|
|
4892
|
+
});
|
|
4893
|
+
output(data, global);
|
|
4894
|
+
} catch (err) {
|
|
4895
|
+
outputError(satvoltError(err));
|
|
4896
|
+
}
|
|
4897
|
+
});
|
|
4898
|
+
campaigns.command("create-from-excel <file>").summary("Create a campaign whose leads come from an Excel (no Maps search).").description(
|
|
4899
|
+
'Imports the rows of the first sheet as leads (one row = one lead) and builds the\npipeline on them. The campaign stays queued unless --start is passed, and it\ncannot be extended later: the leads are fixed at creation.\n\nMapping (see `excel-preview` for the headers):\n --name-column <h> required: commercial name of the business\n --coordinates-column <h> column with "lat,lng" \u2014 OR \u2014\n --geocode-columns <h1,h2> address columns to geocode per lead (GEOCODE_ADDRESS,\n 10 credits per lead; test them with excel-geocode-test)\n --address-columns <h1,h2> address shown on the lead (joined with ", ")\n --phone-column, --url-column, --email-column, --type-column <h>\n --country <text> literal applied to every row\n --mapping <json|@file> full columnMapping object instead of the flags above\n\nPipeline: --template, --from-campaign or --steps, as in `campaigns create`.\n\nExamples:\n suntropy satvolt campaigns create-from-excel empresas.xlsx --name "Clientes CRM" \\\n --name-column Empresa --geocode-columns "Direcci\xF3n,CP,Municipio" --region Cantabria \\\n --template "Industria" --max-leads 100\n suntropy satvolt campaigns create-from-excel leads.xlsx --name "Con coordenadas" \\\n --name-column Nombre --coordinates-column Coordenadas --phone-column Tel\xE9fono \\\n --steps @steps.json'
|
|
4900
|
+
).requiredOption("--name <name>", "Campaign name").option("--name-column <header>", "Column with the commercial name (required unless --mapping)").option("--coordinates-column <header>", 'Column with "lat,lng" coordinates').option("--geocode-columns <headers>", "Comma-separated address columns to geocode when there are no coordinates").option("--address-columns <headers>", "Comma-separated columns joined as the lead address").option("--phone-column <header>", "Column with the phone").option("--url-column <header>", "Column with the website").option("--email-column <header>", "Column with the email (stored in fullData.importMetadata.email)").option("--type-column <header>", "Column with the business type (googlePlacesType)").option("--country <text>", "Country applied to every lead").option("--mapping <json>", "columnMapping as JSON, @file or - (overrides the *-column flags)").option("--template <idOrName>", "Base the pipeline on a campaign template").option("--from-campaign <id>", "Copy the pipeline of another campaign").option("--steps <json>", "LEAD steps as JSON array, @file or -").option("--max-leads <n>", "Import only the first n rows").option("--region <text>", "Region of the leads (also biases the geocoding)").option("--description <text>", "Natural language description of the configuration").option("--start", "Start the pipeline right after creating it (spends credits)").action(async (file, opts) => {
|
|
4901
|
+
const global = getGlobalOpts17(campaigns);
|
|
4902
|
+
try {
|
|
4903
|
+
const columnMapping = opts.mapping ? readJsonArg(opts.mapping, "--mapping") : {
|
|
4904
|
+
commercialName: columnFlag(opts.nameColumn),
|
|
4905
|
+
coordinates: columnFlag(opts.coordinatesColumn),
|
|
4906
|
+
address: columnsList(opts.addressColumns)?.map((column) => ({ column })),
|
|
4907
|
+
phone: columnFlag(opts.phoneColumn),
|
|
4908
|
+
url: columnFlag(opts.urlColumn),
|
|
4909
|
+
email: columnFlag(opts.emailColumn),
|
|
4910
|
+
googlePlacesType: columnFlag(opts.typeColumn),
|
|
4911
|
+
country: opts.country ? { literal: opts.country } : void 0
|
|
4912
|
+
};
|
|
4913
|
+
if (!columnMapping.commercialName) throw new Error("--name-column is required (or a --mapping with commercialName)");
|
|
4914
|
+
const geocodeColumns = columnsList(opts.geocodeColumns);
|
|
4915
|
+
if (!columnMapping.coordinates && !geocodeColumns) {
|
|
4916
|
+
throw new Error("Pass --coordinates-column <header> or --geocode-columns <h1,h2,...>");
|
|
4917
|
+
}
|
|
4918
|
+
if (opts.template && opts.fromCampaign) throw new Error("Use either --template or --from-campaign, not both");
|
|
4919
|
+
const payload = {
|
|
4920
|
+
name: opts.name,
|
|
4921
|
+
columnMapping,
|
|
4922
|
+
geocoding: geocodeColumns ? { enabled: true, columns: geocodeColumns } : void 0,
|
|
4923
|
+
templateId: opts.template,
|
|
4924
|
+
fromCampaignId: opts.fromCampaign ? parseId(opts.fromCampaign, "--from-campaign") : void 0,
|
|
4925
|
+
steps: opts.steps ? readJsonArg(opts.steps, "--steps") : void 0,
|
|
4926
|
+
maxLeads: opts.maxLeads !== void 0 ? parseIntOption(opts.maxLeads, "--max-leads") : void 0,
|
|
4927
|
+
region: opts.region,
|
|
4928
|
+
description: opts.description,
|
|
4929
|
+
start: opts.start ? true : void 0
|
|
4930
|
+
};
|
|
4931
|
+
const data = await callMultipart(satvoltClient(global, 3e5), "/campaigns/from-excel", file, payload);
|
|
4932
|
+
output(data, global);
|
|
4933
|
+
} catch (err) {
|
|
4934
|
+
outputError(satvoltError(err));
|
|
4935
|
+
}
|
|
4936
|
+
});
|
|
4834
4937
|
campaigns.command("delete <campaignId>").summary("Delete a campaign and everything it generated (irreversible, --yes).").description(
|
|
4835
4938
|
"Delete a campaign with its sectors, leads, step executions, pipeline configuration\nand queued jobs. Irreversible. Export tables of the campaign stop working.\nExample:\n suntropy satvolt campaigns delete 63 --yes"
|
|
4836
4939
|
).option("--yes", "Confirm the deletion (required)").action(async (campaignId, opts) => {
|
|
@@ -4852,6 +4955,40 @@ search query and lead limit come from it; any flag you pass wins.
|
|
|
4852
4955
|
outputError(satvoltError(err));
|
|
4853
4956
|
}
|
|
4854
4957
|
});
|
|
4958
|
+
campaigns.command("pause <campaignId>").summary("Pause a running campaign: nothing else runs or is charged until unpause.").description(
|
|
4959
|
+
"Pause a running campaign. Queued work is withdrawn and steps already in flight\nfinish without queuing their successors, so no more credits are spent. Results\nof async steps whose webhook arrives meanwhile are kept. Only a running\ncampaign (inProgress, sectorized, leadsFound, analyzed) can be paused;\notherwise 409 INVALID_CAMPAIGN_STATE.\nExample:\n suntropy satvolt campaigns pause 72"
|
|
4960
|
+
).action(async (campaignId) => {
|
|
4961
|
+
const global = getGlobalOpts17(campaigns);
|
|
4962
|
+
try {
|
|
4963
|
+
const data = await call(satvoltClient(global, 12e4), "post", `/campaigns/${parseId(campaignId, "campaignId")}/pause`);
|
|
4964
|
+
output(data, global);
|
|
4965
|
+
} catch (err) {
|
|
4966
|
+
outputError(satvoltError(err));
|
|
4967
|
+
}
|
|
4968
|
+
});
|
|
4969
|
+
campaigns.command("unpause <campaignId>").summary("Resume a paused campaign where it stopped (no step is run twice).").description(
|
|
4970
|
+
"Resume a paused campaign where it stopped: every lead that is not finished gets\nits next pending step queued, and steps already executed are neither re-run\nnor charged again. Only a paused campaign can be unpaused. This is not\n`resume`, which appends a NEW step to a finished campaign.\nExample:\n suntropy satvolt campaigns unpause 72"
|
|
4971
|
+
).action(async (campaignId) => {
|
|
4972
|
+
const global = getGlobalOpts17(campaigns);
|
|
4973
|
+
try {
|
|
4974
|
+
const data = await call(satvoltClient(global, 12e4), "post", `/campaigns/${parseId(campaignId, "campaignId")}/unpause`);
|
|
4975
|
+
output(data, global);
|
|
4976
|
+
} catch (err) {
|
|
4977
|
+
outputError(satvoltError(err));
|
|
4978
|
+
}
|
|
4979
|
+
});
|
|
4980
|
+
campaigns.command("cancel <campaignId>").summary("Cancel a running or paused campaign for good, keeping its leads and data (--yes).").description(
|
|
4981
|
+
"Cancel a running, paused or queued campaign. Irreversible: it cannot be unpaused\nor started again (reset relaunches it from scratch, deleting the leads). Leads\nand the data already enriched are kept, can be exported and are still charged.\nExample:\n suntropy satvolt campaigns cancel 72 --yes"
|
|
4982
|
+
).option("--yes", "Confirm the cancellation (required)").action(async (campaignId, opts) => {
|
|
4983
|
+
const global = getGlobalOpts17(campaigns);
|
|
4984
|
+
try {
|
|
4985
|
+
if (!opts.yes) throw new Error("Cancelling a campaign is irreversible. Re-run with --yes to confirm.");
|
|
4986
|
+
const data = await call(satvoltClient(global, 12e4), "post", `/campaigns/${parseId(campaignId, "campaignId")}/cancel`);
|
|
4987
|
+
output(data, global);
|
|
4988
|
+
} catch (err) {
|
|
4989
|
+
outputError(satvoltError(err));
|
|
4990
|
+
}
|
|
4991
|
+
});
|
|
4855
4992
|
campaigns.command("reset <campaignId>").summary("Relaunch a campaign from scratch: deletes its leads and results (--yes).").description(
|
|
4856
4993
|
"Relaunch a campaign from scratch: deletes its sectors, leads and processing data\nand returns it to queued, keeping the configuration. Excel/campaign-sourced\ncampaigns keep their imported leads. Irreversible.\nExample:\n suntropy satvolt campaigns reset 59 --yes --start"
|
|
4857
4994
|
).option("--start", "Start the pipeline again after resetting").option("--yes", "Confirm the reset (required)").action(async (campaignId, opts) => {
|
|
@@ -5719,13 +5856,40 @@ Typical flow:
|
|
|
5719
5856
|
satvolt export-tables create <id> ... \xB7 satvolt export-tables export <tableId>
|
|
5720
5857
|
satvolt campaigns resume <id> --action AI_AGENT --config @step.json
|
|
5721
5858
|
satvolt templates create --name <n> --from-campaign <id> \xB7 campaigns create --template <n>
|
|
5722
|
-
satvolt campaigns extend <id> --max-leads N \xB7 leads run-step <id> <leadId> <step
|
|
5859
|
+
satvolt campaigns extend <id> --max-leads N \xB7 leads run-step <id> <leadId> <step>
|
|
5860
|
+
satvolt campaigns pause <id> \xB7 unpause <id> \xB7 cancel <id> --yes
|
|
5861
|
+
satvolt usage [--month YYYY-MM] credits spent by the account this month`
|
|
5723
5862
|
);
|
|
5724
5863
|
registerSatvoltCampaignCommands(satvolt);
|
|
5725
5864
|
registerSatvoltPipelineCommands(satvolt);
|
|
5726
5865
|
registerSatvoltLeadCommands(satvolt);
|
|
5727
5866
|
registerSatvoltExportTableCommands(satvolt);
|
|
5728
5867
|
registerSatvoltTemplateCommands(satvolt);
|
|
5868
|
+
satvolt.command("usage").summary("Credits spent by the whole account in a calendar month, by campaign and by step.").description(
|
|
5869
|
+
"Credits spent by the whole account in a calendar month (UTC): total, previous\nmonth for comparison, and the breakdown by campaign and by step. It sums every\ncharge in the period, including the Google Maps search (FIND_LEADS), so it\ndoes not have to match `campaigns usage`, which reconstructs the cost of one\ncampaign and leaves the search out.\nExamples:\n suntropy satvolt usage\n suntropy satvolt usage --month 2026-08 --format human"
|
|
5870
|
+
).option("--month <YYYY-MM>", "Month to report (default: current month)").action(async (opts) => {
|
|
5871
|
+
const global = getGlobalOpts17(satvolt);
|
|
5872
|
+
try {
|
|
5873
|
+
if (opts.month !== void 0 && !/^\d{4}-\d{2}$/.test(String(opts.month))) {
|
|
5874
|
+
throw new Error("--month must be YYYY-MM, e.g. 2026-08");
|
|
5875
|
+
}
|
|
5876
|
+
const data = await call(satvoltClient(global, 12e4), "get", "/usage", { params: { month: opts.month } });
|
|
5877
|
+
if (global.format === "human" || global.format === "csv") {
|
|
5878
|
+
if (global.format === "human") {
|
|
5879
|
+
process.stderr.write(
|
|
5880
|
+
`${data.month}: ${data.credits} credits \xB7 ${data.executions} executions \xB7 previous month (${data.previous.month}): ${data.previous.credits} credits
|
|
5881
|
+
|
|
5882
|
+
`
|
|
5883
|
+
);
|
|
5884
|
+
}
|
|
5885
|
+
output(data.byCampaign, global);
|
|
5886
|
+
return;
|
|
5887
|
+
}
|
|
5888
|
+
output(data, global);
|
|
5889
|
+
} catch (err) {
|
|
5890
|
+
outputError(satvoltError(err));
|
|
5891
|
+
}
|
|
5892
|
+
});
|
|
5729
5893
|
const catalog = satvolt.command("catalog").description("Reference data for building campaigns and pipelines.");
|
|
5730
5894
|
const catalogEntries = [
|
|
5731
5895
|
["actions", "/catalog/actions", "Pipeline actions: credits per lead (fixed per action; failed or skipped runs are free), dependencies, multiple, and the JSON Schema of their config (the input). The data each step writes: satvolt export-tables fields <campaignId>."],
|
|
@@ -5818,7 +5982,10 @@ var WRITE_VERBS = /* @__PURE__ */ new Set([
|
|
|
5818
5982
|
"duplicate",
|
|
5819
5983
|
"extend",
|
|
5820
5984
|
"move",
|
|
5821
|
-
"reorder"
|
|
5985
|
+
"reorder",
|
|
5986
|
+
"pause",
|
|
5987
|
+
"unpause",
|
|
5988
|
+
"cancel"
|
|
5822
5989
|
]);
|
|
5823
5990
|
var PATH_OVERRIDES = {
|
|
5824
5991
|
"studies calculate production": "write"
|
|
@@ -5928,7 +6095,7 @@ function registerCommandProfileCommand(program2) {
|
|
|
5928
6095
|
}
|
|
5929
6096
|
|
|
5930
6097
|
// src/index.ts
|
|
5931
|
-
var CLI_VERSION = true ? "0.
|
|
6098
|
+
var CLI_VERSION = true ? "0.14.0" : "0.0.0-dev";
|
|
5932
6099
|
function createProgram() {
|
|
5933
6100
|
const program2 = new Command4();
|
|
5934
6101
|
program2.name("suntropy").description("Agent-first CLI for Suntropy solar platform. Optimized for programmatic data manipulation and progressive exploration.").version(CLI_VERSION).option("--format <format>", "Output format: json (default), human, csv", "json").option("--fields <fields>", "Comma-separated fields to include in output").option("--server <url>", "Override API server URL").option("--token <jwt>", "Override authentication token").option("--profile <name>", "Use a specific config profile").option("--verbose", "Show HTTP request/response details on stderr").option("--quiet", "Suppress non-data output").option("--save <file>", "Save output to file (also writes to stdout)");
|