@enerlence/suntropy-cli 0.6.0 → 0.8.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 CHANGED
@@ -20,6 +20,10 @@ npx @enerlence/suntropy-cli <command>
20
20
  # API key (preferred for agents)
21
21
  suntropy auth set-key --key <jwt-api-key>
22
22
 
23
+ # Target a specific server / profile (global options — see "Global Options")
24
+ suntropy --server https://api-dev.example.cloud --profile dev auth set-key --key <jwt>
25
+ suntropy --profile dev auth status
26
+
23
27
  # Email/password login
24
28
  suntropy auth login --email user@co.com --password pass
25
29
 
@@ -27,6 +31,10 @@ suntropy auth login --email user@co.com --password pass
27
31
  suntropy auth status
28
32
  ```
29
33
 
34
+ > `--server`, `--profile` and `--token` are **global** options: pass them on the
35
+ > root command (e.g. `suntropy --profile dev auth status`). They apply to every
36
+ > subcommand, including `auth`.
37
+
30
38
  ## Global Options
31
39
 
32
40
  | Option | Default | Description |
@@ -254,6 +262,30 @@ suntropy shareables create --element-id <studyId> --data '{"customLayout":true}'
254
262
  `--element-type` (default `solarStudy`): `solarStudy | colectiveSolarStudy | veChargerStudy | heatpumpStudy | billing`.
255
263
  `--shareable-type` (default `TEMPLATE`): `TEMPLATE | CONTRACT`. The response includes the public `url` and `uid`.
256
264
 
265
+ ### `suntropy templates` - Document Templates
266
+
267
+ List the client document templates (budget/document templates from the sharing
268
+ service), proxied via solar under `GET /api/templates`. Returns only `_id` and
269
+ `templateName`.
270
+
271
+ ```bash
272
+ # Solar study templates (default --type solarStudy)
273
+ suntropy templates list
274
+
275
+ # Other template types
276
+ suntropy templates list --type colectiveSolarStudy
277
+ suntropy templates list --type veChargerStudy
278
+
279
+ # Generic (untyped) templates
280
+ suntropy templates list --type generic
281
+
282
+ # Pick fields / change format
283
+ suntropy templates list --fields _id,templateName --format csv
284
+ ```
285
+
286
+ `--type` (default `solarStudy`): `solarStudy | colectiveSolarStudy | veChargerStudy | generic`.
287
+ `generic` lists the untyped templates (sends no `templateIdentifier` filter).
288
+
257
289
  ### `suntropy config` - Configuration
258
290
 
259
291
  ```bash
@@ -7,7 +7,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
7
7
  });
8
8
 
9
9
  // src/index.ts
10
- import { Command as Command2 } from "commander";
10
+ import { Command as Command3 } from "commander";
11
11
 
12
12
  // src/config.ts
13
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -275,17 +275,18 @@ function getGlobalOpts(cmd) {
275
275
  }
276
276
  function registerAuthCommands(program2) {
277
277
  const auth = program2.command("auth").description("Authentication management");
278
- auth.command("set-key").description("Set an API key (JWT) for authentication. Preferred method for agents.").requiredOption("--key <jwt>", "JWT API key").option("--server <url>", "API server URL").option("--profile <name>", "Profile name").action(async (opts) => {
278
+ auth.command("set-key").description("Set an API key (JWT) for authentication. Preferred method for agents.").requiredOption("--key <jwt>", "JWT API key").action(async (opts) => {
279
279
  try {
280
+ const global = getGlobalOpts(auth);
280
281
  const config = loadConfig();
281
- const profileName = opts.profile || config.activeProfile;
282
+ const profileName = global.profile || config.activeProfile;
282
283
  if (!config.profiles[profileName]) {
283
284
  config.profiles[profileName] = { server: "https://api.enerlence.com" };
284
285
  }
285
286
  const profile = config.profiles[profileName];
286
287
  profile.token = opts.key;
287
288
  profile.authMethod = "api-key";
288
- if (opts.server) profile.server = opts.server;
289
+ if (global.server) profile.server = global.server;
289
290
  try {
290
291
  const payload = JSON.parse(Buffer.from(opts.key.split(".")[1], "base64").toString());
291
292
  profile.clientUID = payload.clientUID;
@@ -298,15 +299,16 @@ function registerAuthCommands(program2) {
298
299
  outputError(err);
299
300
  }
300
301
  });
301
- auth.command("login").description("Login with email and password").requiredOption("--email <email>", "User email").requiredOption("--password <password>", "User password").option("--server <url>", "API server URL").option("--profile <name>", "Profile name").action(async (opts) => {
302
+ auth.command("login").description("Login with email and password").requiredOption("--email <email>", "User email").requiredOption("--password <password>", "User password").action(async (opts) => {
302
303
  try {
304
+ const global = getGlobalOpts(auth);
303
305
  const config = loadConfig();
304
- const profileName = opts.profile || config.activeProfile;
306
+ const profileName = global.profile || config.activeProfile;
305
307
  if (!config.profiles[profileName]) {
306
308
  config.profiles[profileName] = { server: "https://api.enerlence.com" };
307
309
  }
308
310
  const profile = config.profiles[profileName];
309
- if (opts.server) profile.server = opts.server;
311
+ if (global.server) profile.server = global.server;
310
312
  const securityUrl = getServiceUrl(profile.server, "security");
311
313
  const client = createUnauthClient(securityUrl);
312
314
  const res = await client.post("/auth/login", { email: opts.email, password: opts.password });
@@ -334,10 +336,11 @@ function registerAuthCommands(program2) {
334
336
  outputError(handleApiError(err));
335
337
  }
336
338
  });
337
- auth.command("status").description("Show current authentication status").option("--profile <name>", "Profile name").action(async (opts) => {
339
+ auth.command("status").description("Show current authentication status").action(async () => {
338
340
  try {
341
+ const global = getGlobalOpts(auth);
339
342
  const config = loadConfig();
340
- const profile = getActiveProfile(config, opts.profile);
343
+ const profile = getActiveProfile(config, global.profile);
341
344
  if (!profile.token) {
342
345
  output({ authenticated: false, message: "No token configured" }, getGlobalOpts(auth));
343
346
  return;
@@ -367,11 +370,11 @@ function registerAuthCommands(program2) {
367
370
  outputError(err);
368
371
  }
369
372
  });
370
- auth.command("refresh").description("Refresh the current JWT token").option("--profile <name>", "Profile name").action(async (opts) => {
373
+ auth.command("refresh").description("Refresh the current JWT token").action(async () => {
371
374
  try {
375
+ const globalOpts = getGlobalOpts(auth);
372
376
  const config = loadConfig();
373
- const profileName = opts.profile || config.activeProfile;
374
- const globalOpts = program2.opts();
377
+ const profileName = globalOpts.profile || config.activeProfile;
375
378
  const client = createServiceClient("security", { ...globalOpts, profile: profileName });
376
379
  const res = await client.get("/auth/jwt/refreshToken");
377
380
  if (res.data?.access_token || res.data?.token?.access_token) {
@@ -1794,6 +1797,7 @@ function registerInventoryCommands(program2) {
1794
1797
  }
1795
1798
 
1796
1799
  // src/commands/studies/builder.ts
1800
+ import { Option } from "commander";
1797
1801
  import { readFileSync as readFileSync3, writeFileSync as writeFileSync4, existsSync as existsSync2 } from "fs";
1798
1802
 
1799
1803
  // node_modules/uuid/dist/esm-node/rng.js
@@ -3141,11 +3145,15 @@ Example: suntropy studies set data --file study.json --data '{"referenceId":"REF
3141
3145
  });
3142
3146
  studies.command("comment <studyId>").description(
3143
3147
  'Add a comment to an existing study via API.\nExample: suntropy studies comment abc123 --content "Revisado por agente"'
3144
- ).requiredOption("--content <text>", "Comment text").action(async (studyId, opts) => {
3148
+ ).requiredOption("--content <text>", "Comment text").addOption(new Option("--as-alexandria").hideHelp()).addOption(new Option("--reply-to-user <userUID>").hideHelp()).addOption(new Option("--reply-to-name <name>").hideHelp()).action(async (studyId, opts) => {
3145
3149
  try {
3146
3150
  const global = getGlobalOpts7(studies);
3147
3151
  const client = createServiceClient("solar", global);
3148
- const comment = createComment("commented", opts.content);
3152
+ const comment = createComment("commented", opts.content, {
3153
+ asAlexandria: Boolean(opts.asAlexandria),
3154
+ replyToUser: opts.replyToUser,
3155
+ replyToName: opts.replyToName
3156
+ });
3149
3157
  const res = await client.post(`/solar-study/addSolarStudyComment/${studyId}`, comment);
3150
3158
  output(res.data, global);
3151
3159
  } catch (err) {
@@ -3268,19 +3276,28 @@ async function fetchPeriodDistribution(study, global) {
3268
3276
  return null;
3269
3277
  }
3270
3278
  }
3271
- function createComment(type, content) {
3279
+ function createComment(type, content, extra) {
3272
3280
  const config = loadConfig();
3273
3281
  const profile = getActiveProfile(config);
3274
3282
  const autoContent = {
3275
3283
  created: "Estudio creado via CLI",
3276
3284
  modified: "Estudio actualizado via CLI"
3277
3285
  };
3278
- return {
3279
- content: content || autoContent[type] || "",
3286
+ let body = content || autoContent[type] || "";
3287
+ if (extra?.replyToUser) {
3288
+ const name = extra.replyToName || extra.replyToUser;
3289
+ body = `@[${name}](${extra.replyToUser}) ${body}`.trim();
3290
+ }
3291
+ const comment = {
3292
+ content: body,
3280
3293
  type,
3281
3294
  creationTimestamp: (/* @__PURE__ */ new Date()).toISOString(),
3282
- creationUserUID: profile.userUID || "cli-agent"
3295
+ creationUserUID: extra?.asAlexandria ? "alexandria" : profile.userUID || "cli-agent"
3283
3296
  };
3297
+ if (extra?.asAlexandria) {
3298
+ comment.aiGenerated = true;
3299
+ }
3300
+ return comment;
3284
3301
  }
3285
3302
 
3286
3303
  // src/commands/studies/index.ts
@@ -4206,12 +4223,44 @@ function registerShareableCommands(program2) {
4206
4223
  });
4207
4224
  }
4208
4225
 
4209
- // src/commands/geocode/index.ts
4226
+ // src/commands/templates/index.ts
4210
4227
  function getGlobalOpts14(cmd) {
4211
4228
  let root = cmd;
4212
4229
  while (root.parent) root = root.parent;
4213
4230
  return root.opts();
4214
4231
  }
4232
+ function registerTemplatesCommands(program2) {
4233
+ const templates = program2.command("templates").description(
4234
+ "Read client document templates (id + name), proxied via solar from the sharing service."
4235
+ );
4236
+ templates.command("list").description(
4237
+ "List templates returning only _id and templateName (GET /api/templates).\nDefaults to the solar study templates (--type solarStudy).\n\nTypes: solarStudy (default) | colectiveSolarStudy | veChargerStudy.\nUse --type generic for the generic (untyped) templates.\n\nExamples:\n suntropy templates list\n suntropy templates list --type colectiveSolarStudy\n suntropy templates list --type generic --fields _id,templateName --format csv"
4238
+ ).option(
4239
+ "--type <identifier>",
4240
+ "solarStudy (default) | colectiveSolarStudy | veChargerStudy | generic",
4241
+ "solarStudy"
4242
+ ).action(async (opts) => {
4243
+ try {
4244
+ const global = getGlobalOpts14(templates);
4245
+ const client = createServiceClient("solar", global);
4246
+ const params = {};
4247
+ if (opts.type && opts.type !== "generic") {
4248
+ params.templateIdentifier = opts.type;
4249
+ }
4250
+ const res = await client.get("/api/templates", { params });
4251
+ output(res.data, global);
4252
+ } catch (err) {
4253
+ outputError(handleApiError(err));
4254
+ }
4255
+ });
4256
+ }
4257
+
4258
+ // src/commands/geocode/index.ts
4259
+ function getGlobalOpts15(cmd) {
4260
+ let root = cmd;
4261
+ while (root.parent) root = root.parent;
4262
+ return root.opts();
4263
+ }
4215
4264
  function emit(envelope, all, query, global) {
4216
4265
  if (envelope.error) {
4217
4266
  outputError({
@@ -4245,7 +4294,7 @@ function registerGeocodeCommands(program2) {
4245
4294
  'Resolve an address into coordinates (lat/lng + formatted address).\nExamples:\n suntropy geocode resolve --address "Calle Mayor 1, Madrid"\n suntropy geocode resolve --address "Gran V\xEDa, Madrid" --country es --all'
4246
4295
  ).requiredOption("--address <string>", "Address to geocode (quote it)").option("--country <code>", "Bias results to a country (ISO code, e.g. es, pt, it)").option("--all", "Return all candidate matches instead of only the best one").action(async (opts) => {
4247
4296
  try {
4248
- const global = getGlobalOpts14(geocode);
4297
+ const global = getGlobalOpts15(geocode);
4249
4298
  const client = createServiceClient("solar", global);
4250
4299
  const res = await client.get(
4251
4300
  "/api/geocode",
@@ -4260,7 +4309,7 @@ function registerGeocodeCommands(program2) {
4260
4309
  "Resolve coordinates into an address (reverse geocoding).\nExample:\n suntropy geocode reverse --lat 40.4168 --lng -3.7038"
4261
4310
  ).requiredOption("--lat <number>", "Latitude").requiredOption("--lng <number>", "Longitude").option("--all", "Return all candidate matches instead of only the best one").action(async (opts) => {
4262
4311
  try {
4263
- const global = getGlobalOpts14(geocode);
4312
+ const global = getGlobalOpts15(geocode);
4264
4313
  const client = createServiceClient("solar", global);
4265
4314
  const res = await client.get(
4266
4315
  "/api/reverse-geocode",
@@ -4274,9 +4323,9 @@ function registerGeocodeCommands(program2) {
4274
4323
  }
4275
4324
 
4276
4325
  // src/index.ts
4277
- var CLI_VERSION = true ? "0.6.0" : "0.0.0-dev";
4326
+ var CLI_VERSION = true ? "0.8.0" : "0.0.0-dev";
4278
4327
  function createProgram() {
4279
- const program2 = new Command2();
4328
+ const program2 = new Command3();
4280
4329
  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)");
4281
4330
  registerAuthCommands(program2);
4282
4331
  registerConfigCommands(program2);
@@ -4287,6 +4336,7 @@ function createProgram() {
4287
4336
  registerSolarformCommands(program2);
4288
4337
  registerPPACommands(program2);
4289
4338
  registerShareableCommands(program2);
4339
+ registerTemplatesCommands(program2);
4290
4340
  registerGeocodeCommands(program2);
4291
4341
  return program2;
4292
4342
  }