@enerlence/suntropy-cli 0.1.0 → 0.1.2
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 +251 -0
- package/dist/bin/suntropy.js +92 -2
- package/dist/bin/suntropy.js.map +1 -1
- package/package.json +7 -2
package/README.md
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# @enerlence/suntropy-cli
|
|
2
|
+
|
|
3
|
+
Agent-first CLI for the Suntropy solar platform. JSON output by default, optimized for programmatic manipulation by AI agents and automation pipelines.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @enerlence/suntropy-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run directly:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @enerlence/suntropy-cli <command>
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Authentication
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# API key (preferred for agents)
|
|
21
|
+
suntropy auth set-key --key <jwt-api-key>
|
|
22
|
+
|
|
23
|
+
# Email/password login
|
|
24
|
+
suntropy auth login --email user@co.com --password pass
|
|
25
|
+
|
|
26
|
+
# Check status
|
|
27
|
+
suntropy auth status
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Global Options
|
|
31
|
+
|
|
32
|
+
| Option | Default | Description |
|
|
33
|
+
|--------|---------|-------------|
|
|
34
|
+
| `--format json\|human\|csv` | `json` | Output format |
|
|
35
|
+
| `--fields f1,f2,...` | all | Select specific fields |
|
|
36
|
+
| `--server <url>` | config | Override API server URL |
|
|
37
|
+
| `--token <jwt>` | config | Override auth token |
|
|
38
|
+
| `--profile <name>` | default | Config profile |
|
|
39
|
+
| `--verbose` | false | Show HTTP details on stderr |
|
|
40
|
+
| `--quiet` | false | Suppress non-data output |
|
|
41
|
+
| `--save <file>` | - | Save output to file |
|
|
42
|
+
|
|
43
|
+
## Commands
|
|
44
|
+
|
|
45
|
+
### `suntropy studies` - Solar Studies
|
|
46
|
+
|
|
47
|
+
Progressive exploration and full study lifecycle management.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
# Explore existing studies
|
|
51
|
+
suntropy studies list --limit 20 --state "En curso"
|
|
52
|
+
suntropy studies metadata <id>
|
|
53
|
+
suntropy studies get <studyId>
|
|
54
|
+
suntropy studies get <studyId> --expand surfaces,results
|
|
55
|
+
suntropy studies get <studyId> --expand all
|
|
56
|
+
suntropy studies curves <studyId> consumption --stats
|
|
57
|
+
suntropy studies curves <studyId> production --monthly
|
|
58
|
+
|
|
59
|
+
# Calculate production/optimization
|
|
60
|
+
suntropy studies calculate-production --lat 37.39 --lon -5.99 --power 5000
|
|
61
|
+
suntropy studies optimize-surfaces --lat 37.39 --lon -5.99
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
#### Study Builder (create/edit studies)
|
|
65
|
+
|
|
66
|
+
Build solar studies progressively in a local JSON file, with automatic step validation replicating the frontend's 6-step system.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Initialize or pull existing study
|
|
70
|
+
suntropy studies init --file study.json --name "Residencial 5kW"
|
|
71
|
+
suntropy studies pull <studyId> --file study.json
|
|
72
|
+
|
|
73
|
+
# Configure step by step
|
|
74
|
+
suntropy studies set tariff --file study.json --tariff-id 13 --zone-id 1
|
|
75
|
+
suntropy studies set prices --file study.json --energy-p1 0.25 --energy-p2 0.17 --energy-p3 0.13
|
|
76
|
+
suntropy studies set client --file study.json --name "Juan Garcia" --email j@co.com
|
|
77
|
+
suntropy studies set consumption --file study.json --annual 4000 --pattern Domestic
|
|
78
|
+
suntropy studies set kit --file study.json --kit-id 2260
|
|
79
|
+
suntropy studies add surface --file study.json --lat 37.39 --lon -5.99 --angle 30 --azimuth 180 --power 6000
|
|
80
|
+
|
|
81
|
+
# Calculate production and results (SolarResultCalculator)
|
|
82
|
+
suntropy studies calculate production --file study.json --all-surfaces
|
|
83
|
+
suntropy studies calculate-results --file study.json
|
|
84
|
+
|
|
85
|
+
# Economics, validate, save
|
|
86
|
+
suntropy studies set economics --file study.json --margin 15 --total-cost 3990
|
|
87
|
+
suntropy studies validate --file study.json
|
|
88
|
+
suntropy studies save --file study.json
|
|
89
|
+
|
|
90
|
+
# Comments
|
|
91
|
+
suntropy studies add-comment --file study.json --content "Reviewed by agent"
|
|
92
|
+
suntropy studies comment <studyId> --content "Updated via CLI"
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Consumption modes:**
|
|
96
|
+
- `--annual 4000 --pattern Domestic` - Annual kWh + pattern (Balance, Nightly, Morning, Afternoon, Domestic, Commercial)
|
|
97
|
+
- `--by-period '{"p1":2500,"p2":1000,"p3":500}'` - Consumption per tariff period
|
|
98
|
+
- `--monthly '{"1":350,"2":320,...,"12":340}'` - Monthly consumption
|
|
99
|
+
- `--from-file /path/to/curve.json` - Raw PowerCurve JSON
|
|
100
|
+
|
|
101
|
+
**Equipment modes:**
|
|
102
|
+
- `suntropy studies set kit --kit-id <id>` - Solar kit (sets `peakPowerIntroductionMode: solarKit`)
|
|
103
|
+
- `suntropy studies set panel --panel-id <id> --panels-count 12` - Individual panel (sets `peakPowerIntroductionMode: solarPanel`)
|
|
104
|
+
- `suntropy studies set inverter --inverter-id <id>` - Inverter (panel mode)
|
|
105
|
+
|
|
106
|
+
**Auto-validation:** Every `set`/`add`/`calculate` command returns the current completion status:
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"stepsProgress": {
|
|
111
|
+
"clientDetails": true,
|
|
112
|
+
"consumption": true,
|
|
113
|
+
"surfacesSelector": true,
|
|
114
|
+
"production": true,
|
|
115
|
+
"results": true,
|
|
116
|
+
"economicBalance": false
|
|
117
|
+
},
|
|
118
|
+
"completionPercentage": 83,
|
|
119
|
+
"missing": {
|
|
120
|
+
"economicBalance": "Needs: totalCost (use: studies set economics)"
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `suntropy inventory` - Inventory Management
|
|
126
|
+
|
|
127
|
+
CRUD for all equipment types.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# List, get, create, update, delete — same pattern for all types
|
|
131
|
+
suntropy inventory panels list --active-only --fields solarPanelId,name,peakPower
|
|
132
|
+
suntropy inventory panels get <id>
|
|
133
|
+
suntropy inventory panels create --data '{"name":"JA Solar 450W","peakPower":450}'
|
|
134
|
+
suntropy inventory panels update <id> --data '{"peakPower":460}'
|
|
135
|
+
suntropy inventory panels delete <id>
|
|
136
|
+
|
|
137
|
+
# Available resource types:
|
|
138
|
+
# panels, inverters, batteries, chargers, heatpumps,
|
|
139
|
+
# custom-assets, custom-asset-types, custom-fields,
|
|
140
|
+
# kits, charger-kits, heatpump-kits, manufacturers
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Kit assembly:**
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
suntropy inventory kits panels create --data '{"name":"Panel Kit","peakPower":450}'
|
|
147
|
+
suntropy inventory kits inverters create --data '{"name":"Inversor Kit","nominalPower":5000}'
|
|
148
|
+
suntropy inventory kits assemble \
|
|
149
|
+
--name "Kit Solar 5kW" \
|
|
150
|
+
--panel <panelId> --inverter <inverterId> \
|
|
151
|
+
--panels-count 12 --peak-power 5.4 --price 3500
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### `suntropy curves` - PowerCurve Operations
|
|
155
|
+
|
|
156
|
+
Pipe-friendly operations on hourly energy curves (8760 values/year).
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# Statistics and totals
|
|
160
|
+
suntropy curves stats --input production.json
|
|
161
|
+
suntropy curves total --input consumption.json
|
|
162
|
+
|
|
163
|
+
# Arithmetic
|
|
164
|
+
suntropy curves subtract --a production.json --b consumption.json --save net.json
|
|
165
|
+
suntropy curves aggregate --a curve1.json --b curve2.json
|
|
166
|
+
suntropy curves multiply 0.85 --input production.json
|
|
167
|
+
|
|
168
|
+
# Filtering
|
|
169
|
+
suntropy curves filter-positive --input net.json # Keep positive values
|
|
170
|
+
suntropy curves filter-negative --input net.json # Keep negative values
|
|
171
|
+
suntropy curves filter-dates --start 2024-06-01 --end 2024-08-31 --input curve.json
|
|
172
|
+
|
|
173
|
+
# Period aggregation
|
|
174
|
+
suntropy curves by-period --input consumption.json --periods periods.json
|
|
175
|
+
|
|
176
|
+
# Piping
|
|
177
|
+
suntropy curves subtract --a prod.json --b cons.json \
|
|
178
|
+
| suntropy curves filter-positive --input - --save excesses.json
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### `suntropy consumption` - Consumption Curves
|
|
182
|
+
|
|
183
|
+
Generate consumption curves from patterns, profiles, or files.
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
suntropy consumption estimate --annual 5000 --pattern Domestic --save consumption.json
|
|
187
|
+
suntropy consumption ree-profiles --start 2024-01-01 --end 2024-12-31 --tariff 3.0TD
|
|
188
|
+
suntropy consumption periods --tariff-id 14 --zone-id 1 --save periods.json
|
|
189
|
+
suntropy consumption from-file --eredes-zip /path/to/file.zip
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### `suntropy solarform` - Solar Form API
|
|
193
|
+
|
|
194
|
+
Quick solar study creation via the simplified Solar Form API.
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
# Simple mode (auto-optimized)
|
|
198
|
+
suntropy solarform simple \
|
|
199
|
+
--region "Andalucia" --sub-region "Sevilla" \
|
|
200
|
+
--consumption 5000 --pattern Domestic
|
|
201
|
+
|
|
202
|
+
# Full control
|
|
203
|
+
suntropy solarform calculate --data '{"center":{"lat":37.39,"lng":-5.99},...}'
|
|
204
|
+
|
|
205
|
+
# Get form configuration
|
|
206
|
+
suntropy solarform config
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### `suntropy config` - Configuration
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
suntropy config set server https://api.enerlence.com
|
|
213
|
+
suntropy config get server
|
|
214
|
+
suntropy config list
|
|
215
|
+
suntropy config create-profile staging
|
|
216
|
+
suntropy config use staging
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Configuration
|
|
220
|
+
|
|
221
|
+
Config stored in `~/.suntropy/config.json`. Supports multiple profiles for different environments.
|
|
222
|
+
|
|
223
|
+
```json
|
|
224
|
+
{
|
|
225
|
+
"activeProfile": "default",
|
|
226
|
+
"profiles": {
|
|
227
|
+
"default": {
|
|
228
|
+
"server": "https://api.enerlence.com",
|
|
229
|
+
"token": "eyJ...",
|
|
230
|
+
"authMethod": "api-key",
|
|
231
|
+
"clientUID": "...",
|
|
232
|
+
"userUID": "..."
|
|
233
|
+
},
|
|
234
|
+
"local": {
|
|
235
|
+
"server": "http://localhost"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Local development uses port-based routing: solar=8086, security=8080, profiles=8085, periods=8084, templates=8090.
|
|
242
|
+
|
|
243
|
+
## Skills
|
|
244
|
+
|
|
245
|
+
Agent skills (prompt templates) are available in the [`suntropy-cli-skills`](https://github.com/enerlence/suntropy-cli-skills) repo. Install them in your `.claude/skills/` or `.agents/skills/` directory to give AI agents knowledge of the CLI workflows.
|
|
246
|
+
|
|
247
|
+
Available skills:
|
|
248
|
+
- **suntropy-cli** - Complete CLI reference
|
|
249
|
+
- **solar-study** - End-to-end solar study creation workflow
|
|
250
|
+
- **inventory-create** - Create inventory items (panels, inverters, batteries, etc.)
|
|
251
|
+
- **inventory-create-kit** - Create kits with components and custom assets
|
package/dist/bin/suntropy.js
CHANGED
|
@@ -632,6 +632,35 @@ function registerKitsCommands(inventory) {
|
|
|
632
632
|
batchDeletePath: "delete-batch-solar-kits",
|
|
633
633
|
getViaFilter: true
|
|
634
634
|
});
|
|
635
|
+
const kitsCreate = kits.commands.find((c) => c.name() === "create");
|
|
636
|
+
if (kitsCreate) {
|
|
637
|
+
kitsCreate.description(
|
|
638
|
+
`Create a solar kit via raw JSON. Prefer "assemble" for guided creation.
|
|
639
|
+
|
|
640
|
+
Required fields:
|
|
641
|
+
identifier (string) \u2014 Kit name (NOT "name")
|
|
642
|
+
price (number) \u2014 Kit price in EUR
|
|
643
|
+
panelNumber (number) \u2014 Number of panels
|
|
644
|
+
inverterNumber (number) \u2014 Number of inverters
|
|
645
|
+
phaseNumber (string) \u2014 "single_phase" or "three_phase"
|
|
646
|
+
|
|
647
|
+
Component relations (use kit-specific IDs, NOT inventory IDs):
|
|
648
|
+
kitSolarPanel: {"idKitSolarPanel": <id>} \u2014 from "kits panels list/create"
|
|
649
|
+
kitInverter: {"idKitInverter": <id>} \u2014 from "kits inverters list/create"
|
|
650
|
+
battery: {"batteryId": <id>} \u2014 from "inventory batteries list"
|
|
651
|
+
|
|
652
|
+
Optional fields:
|
|
653
|
+
peakPower, coplanar, defaultTaxesPercentage, active, batteriesNumber,
|
|
654
|
+
useTotalKitCostAsPrice, manufacturingWarranty, materialsWarranty,
|
|
655
|
+
imageUrl, referenceId, buyUrl
|
|
656
|
+
|
|
657
|
+
Custom assets array:
|
|
658
|
+
solarKitCustomAssets: [{"customAsset": {"idCustomAsset": <id>}, "units": <n>}]
|
|
659
|
+
|
|
660
|
+
Example:
|
|
661
|
+
suntropy inventory kits create --data '{"identifier":"Kit 5kW","kitSolarPanel":{"idKitSolarPanel":123},"kitInverter":{"idKitInverter":456},"panelNumber":12,"inverterNumber":1,"peakPower":5.4,"price":6500,"phaseNumber":"single_phase"}'`
|
|
662
|
+
);
|
|
663
|
+
}
|
|
635
664
|
kits.command("archive <kitId>").description("Archive a solar kit (soft-disable)").action(async (kitId) => {
|
|
636
665
|
try {
|
|
637
666
|
const global = getGlobalOpts3(kits);
|
|
@@ -788,10 +817,71 @@ function registerKitsCommands(inventory) {
|
|
|
788
817
|
});
|
|
789
818
|
kits.command("assemble").description(
|
|
790
819
|
'Assemble a solar kit from existing components by ID.\nReferences kit panels, inverters, batteries, and custom assets by their IDs.\n\nCustom assets format: --custom-asset <assetId>:<units> (repeatable)\n\nExamples:\n suntropy inventory kits assemble --name "Kit 5kW" --panel 123 --inverter 456 --panels-count 12 --price 6500\n suntropy inventory kits assemble --name "Kit Premium" --panel 123 --inverter 456 --battery 789 \\\n --panels-count 12 --inverters-count 1 --batteries-count 1 --peak-power 5.4 --price 8500 \\\n --custom-asset 100:12 --custom-asset 200:1 --phase single_phase'
|
|
791
|
-
).requiredOption("--name <identifier>", "Kit name/identifier").option("--panel <kitPanelId>", "Kit panel ID (idKitSolarPanel)").option("--inverter <kitInverterId>", "Kit inverter ID (idKitInverter)").option("--battery <batteryId>", "Battery ID from inventory (batteryId)").option("--panels-count <n>", "Number of panels", "12").option("--inverters-count <n>", "Number of inverters", "1").option("--batteries-count <n>", "Number of batteries", "0").option("--peak-power <kW>", "Total peak power in kW").
|
|
820
|
+
).requiredOption("--name <identifier>", "Kit name/identifier").option("--panel <kitPanelId>", "Kit panel ID (idKitSolarPanel)").option("--inverter <kitInverterId>", "Kit inverter ID (idKitInverter)").option("--battery <batteryId>", "Battery ID from inventory (batteryId)").option("--panels-count <n>", "Number of panels", "12").option("--inverters-count <n>", "Number of inverters", "1").option("--batteries-count <n>", "Number of batteries", "0").option("--peak-power <kW>", "Total peak power in kW").requiredOption("--price <eur>", "Kit price in EUR (required)").option("--phase <type>", "Phase: single_phase or three_phase", "single_phase").option("--coplanar", "Coplanar mounting").option("--taxes <pct>", "Default tax percentage", "21").option("--custom-asset <id:units>", "Custom asset as id:units (repeatable)", collectCustomAssets, []).action(async (opts) => {
|
|
792
821
|
try {
|
|
793
822
|
const global = getGlobalOpts3(kits);
|
|
794
823
|
const client = createServiceClient("solar", global);
|
|
824
|
+
const validationErrors = [];
|
|
825
|
+
if (opts.panel) {
|
|
826
|
+
try {
|
|
827
|
+
const res2 = await client.post("/solar-kits/solar-panels/filter", { idKitSolarPanel: parseInt(opts.panel) });
|
|
828
|
+
const data = Array.isArray(res2.data) ? res2.data : res2.data?.data || [];
|
|
829
|
+
if (data.length === 0) {
|
|
830
|
+
validationErrors.push(
|
|
831
|
+
`Panel ID ${opts.panel} is not a KitSolarPanel. Use "suntropy inventory kits panels list" to see available kit panels, or "suntropy inventory kits panels create --data '{...}'" to create one first.`
|
|
832
|
+
);
|
|
833
|
+
}
|
|
834
|
+
} catch {
|
|
835
|
+
validationErrors.push(
|
|
836
|
+
`Could not validate panel ID ${opts.panel}. Make sure it is a KitSolarPanel ID (not a regular inventory panel). Use "suntropy inventory kits panels list" to see available kit panels.`
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (opts.inverter) {
|
|
841
|
+
try {
|
|
842
|
+
const res2 = await client.get("/solar-kits/inverters", { params: { limit: 200 } });
|
|
843
|
+
const inverters = Array.isArray(res2.data) ? res2.data : res2.data?.data || [];
|
|
844
|
+
const found = inverters.find(
|
|
845
|
+
(inv) => String(inv.idKitInverter) === String(opts.inverter)
|
|
846
|
+
);
|
|
847
|
+
if (!found) {
|
|
848
|
+
validationErrors.push(
|
|
849
|
+
`Inverter ID ${opts.inverter} is not a KitInverter. Use "suntropy inventory kits inverters list" to see available kit inverters, or "suntropy inventory kits inverters create --data '{...}'" to create one first.`
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
} catch {
|
|
853
|
+
validationErrors.push(
|
|
854
|
+
`Could not validate inverter ID ${opts.inverter}. Make sure it is a KitInverter ID (not a regular inventory inverter). Use "suntropy inventory kits inverters list" to see available kit inverters.`
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
if (opts.battery) {
|
|
859
|
+
try {
|
|
860
|
+
const res2 = await client.get("/solar-kits/batteries", { params: { limit: 200 } });
|
|
861
|
+
const batteries = Array.isArray(res2.data) ? res2.data : res2.data?.data || [];
|
|
862
|
+
const found = batteries.find(
|
|
863
|
+
(bat) => String(bat.idKitBattery) === String(opts.battery)
|
|
864
|
+
);
|
|
865
|
+
if (!found) {
|
|
866
|
+
validationErrors.push(
|
|
867
|
+
`Battery ID ${opts.battery} is not a KitBattery. Use "suntropy inventory kits batteries list" to see available kit batteries, or "suntropy inventory kits batteries create --data '{...}'" to create one first.`
|
|
868
|
+
);
|
|
869
|
+
}
|
|
870
|
+
} catch {
|
|
871
|
+
validationErrors.push(
|
|
872
|
+
`Could not validate battery ID ${opts.battery}. Make sure it is a KitBattery ID (not a regular inventory battery). Use "suntropy inventory kits batteries list" to see available kit batteries.`
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
if (validationErrors.length > 0) {
|
|
877
|
+
outputError({
|
|
878
|
+
error: true,
|
|
879
|
+
message: "Kit assembly validation failed",
|
|
880
|
+
issues: validationErrors,
|
|
881
|
+
hint: "The assemble command requires KitSolarPanel, KitInverter, and KitBattery IDs \u2014 these are kit-specific entities, not regular inventory items."
|
|
882
|
+
});
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
795
885
|
const body = {
|
|
796
886
|
identifier: opts.name,
|
|
797
887
|
panelNumber: parseInt(opts.panelsCount),
|
|
@@ -805,7 +895,7 @@ function registerKitsCommands(inventory) {
|
|
|
805
895
|
if (opts.inverter) body.kitInverter = { idKitInverter: parseInt(opts.inverter) };
|
|
806
896
|
if (opts.battery) body.battery = { batteryId: parseInt(opts.battery) };
|
|
807
897
|
if (opts.peakPower) body.peakPower = parseFloat(opts.peakPower);
|
|
808
|
-
|
|
898
|
+
body.price = parseFloat(opts.price);
|
|
809
899
|
if (opts.coplanar) body.coplanar = true;
|
|
810
900
|
if (opts.customAsset && opts.customAsset.length > 0) {
|
|
811
901
|
body.solarKitCustomAssets = opts.customAsset.map((ca) => ({
|