@enerlence/suntropy-cli 0.11.4 → 0.11.6
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 +106 -7
- package/dist/bin/suntropy.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/suntropy.js
CHANGED
|
@@ -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
|
|
10
|
+
import { Command as Command4 } from "commander";
|
|
11
11
|
|
|
12
12
|
// src/config.ts
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -68,14 +68,16 @@ var SERVICE_PATHS = {
|
|
|
68
68
|
solar: "/solar",
|
|
69
69
|
templates: "/templates",
|
|
70
70
|
profiles: "/profiles",
|
|
71
|
-
periods: "/periods"
|
|
71
|
+
periods: "/periods",
|
|
72
|
+
notifications: "/notifications"
|
|
72
73
|
};
|
|
73
74
|
var LOCAL_PORTS = {
|
|
74
75
|
security: 8080,
|
|
75
76
|
solar: 8086,
|
|
76
77
|
templates: 8090,
|
|
77
78
|
profiles: 8085,
|
|
78
|
-
periods: 8084
|
|
79
|
+
periods: 8084,
|
|
80
|
+
notifications: 8093
|
|
79
81
|
};
|
|
80
82
|
function getServiceUrl(baseServer, service) {
|
|
81
83
|
if (baseServer.includes("localhost") || baseServer.match(/:\d+$/)) {
|
|
@@ -139,6 +141,15 @@ function createUnauthClient(baseURL) {
|
|
|
139
141
|
headers: { "Content-Type": "application/json" }
|
|
140
142
|
});
|
|
141
143
|
}
|
|
144
|
+
var OBJECT_ID_RE = /^[a-f0-9]{24}$/i;
|
|
145
|
+
function assertStudyObjectId(id) {
|
|
146
|
+
if (OBJECT_ID_RE.test(id)) return id;
|
|
147
|
+
const looksLikeUuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/i.test(id);
|
|
148
|
+
const hint = looksLikeUuid ? `It looks like a UUID (e.g. a chat or shareable uid), not a study _id.` : `A study _id is a 24-character hex MongoDB ObjectId.`;
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Invalid study id: "${id}". ${hint} Get the correct id from \`suntropy studies list\` (the \`solarStudyId\` field).`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
142
153
|
function assertFound(data, entity, id) {
|
|
143
154
|
const isEmptyObject = data != null && typeof data === "object" && !Array.isArray(data) && Object.keys(data).length === 0;
|
|
144
155
|
if (data == null || typeof data !== "object" || Array.isArray(data) || isEmptyObject) {
|
|
@@ -2275,6 +2286,7 @@ function registerStudyBuilderCommands(studies) {
|
|
|
2275
2286
|
"Download an existing study from the backend into a local file.\nExample: suntropy studies pull abc123 --file /tmp/study.json"
|
|
2276
2287
|
).option("--file <path>", "Output file path").action(async (studyId, opts) => {
|
|
2277
2288
|
try {
|
|
2289
|
+
assertStudyObjectId(studyId);
|
|
2278
2290
|
const filePath = resolveFile(opts);
|
|
2279
2291
|
const global = getGlobalOpts7(studies);
|
|
2280
2292
|
const client = createServiceClient("solar", global);
|
|
@@ -3270,6 +3282,7 @@ Example: suntropy studies set data --file study.json --data '{"referenceId":"REF
|
|
|
3270
3282
|
'Add a comment to an existing study via API.\nExample: suntropy studies comment abc123 --content "Revisado por agente"'
|
|
3271
3283
|
).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) => {
|
|
3272
3284
|
try {
|
|
3285
|
+
assertStudyObjectId(studyId);
|
|
3273
3286
|
const global = getGlobalOpts7(studies);
|
|
3274
3287
|
const client = createServiceClient("solar", global);
|
|
3275
3288
|
const comment = createComment("commented", opts.content, {
|
|
@@ -3593,6 +3606,7 @@ function registerStudiesCommands(program2) {
|
|
|
3593
3606
|
});
|
|
3594
3607
|
studies.command("metadata <id>").description("Get solar study metadata by metadata ID (relational). Full MySQL record with state, costs, versions.").option("--by-study-id", "Interpret <id> as MongoDB solarStudyId instead of metadata ID").action(async (id, opts) => {
|
|
3595
3608
|
try {
|
|
3609
|
+
if (opts.byStudyId) assertStudyObjectId(id);
|
|
3596
3610
|
const global = getGlobalOpts8(studies);
|
|
3597
3611
|
const client = createServiceClient("solar", global);
|
|
3598
3612
|
const path = opts.byStudyId ? `/solar-study/metadata/solar-study-id/${id}` : `/solar-study/findSolarStudyMetadataById/${id}`;
|
|
@@ -3606,6 +3620,7 @@ function registerStudiesCommands(program2) {
|
|
|
3606
3620
|
"Get solar study by MongoDB ID. By default returns summary (no heavy curves).\nExpand sections: surfaces, results, economics, batteries, consumption, equipment, client, location\nExamples:\n suntropy studies get abc123\n suntropy studies get abc123 --expand surfaces,results\n suntropy studies get abc123 --expand all\n suntropy studies get abc123 --fields name,market (bypasses expand filter)"
|
|
3607
3621
|
).option("--expand <sections>", 'Comma-separated sections to expand (or "all")').action(async (studyId, opts) => {
|
|
3608
3622
|
try {
|
|
3623
|
+
assertStudyObjectId(studyId);
|
|
3609
3624
|
const global = getGlobalOpts8(studies);
|
|
3610
3625
|
const client = createServiceClient("solar", global);
|
|
3611
3626
|
const res = await client.get(`/solar-study/findById/${studyId}`);
|
|
@@ -3628,6 +3643,7 @@ function registerStudiesCommands(program2) {
|
|
|
3628
3643
|
"Extract and analyze a PowerCurve from a study.\nCurve names: consumption, production, net-consumption, excesses\nDefault: --stats. Use --raw for full hourly data (8760 values).\nUse --monthly for monthly aggregates, --daily for daily totals."
|
|
3629
3644
|
).option("--stats", "Show statistics (default if no other flag)").option("--monthly", "Monthly accumulated values").option("--daily", "Daily accumulated values").option("--raw", "Full hourly DayCurve[] data").option("--total", "Just the total accumulated value").option("--surface-index <n>", "Surface index for production curve", "0").option("--save <file>", "Save curve data to file").action(async (studyId, curveName, opts) => {
|
|
3630
3645
|
try {
|
|
3646
|
+
assertStudyObjectId(studyId);
|
|
3631
3647
|
const global = getGlobalOpts8(studies);
|
|
3632
3648
|
const client = createServiceClient("solar", global);
|
|
3633
3649
|
const res = await client.get(`/solar-study/findById/${studyId}`);
|
|
@@ -4461,6 +4477,87 @@ function registerGeocodeCommands(program2) {
|
|
|
4461
4477
|
});
|
|
4462
4478
|
}
|
|
4463
4479
|
|
|
4480
|
+
// src/commands/notifications/index.ts
|
|
4481
|
+
import { Option as Option2 } from "commander";
|
|
4482
|
+
function getGlobalOpts16(cmd) {
|
|
4483
|
+
let root = cmd;
|
|
4484
|
+
while (root.parent) root = root.parent;
|
|
4485
|
+
return root.opts();
|
|
4486
|
+
}
|
|
4487
|
+
var ALEXANDRIA_USER_UID = "alexandria";
|
|
4488
|
+
var SEVERITIES = ["info", "warning", "urgent"];
|
|
4489
|
+
function registerNotificationsCommands(program2) {
|
|
4490
|
+
const notifications = program2.command("notifications").description(
|
|
4491
|
+
"Send in-app (and email) notifications to Suntropy users.\nUseful for Alexandria to proactively tell a user a task is done, since a\ndirect/email execution leaves no study comment (and thus no auto-notification)."
|
|
4492
|
+
);
|
|
4493
|
+
notifications.command("send").description(
|
|
4494
|
+
`Send a notification to a user. It appears in the recipient's notification
|
|
4495
|
+
bell and, if enabled in their config, is also emailed. Sent FROM Alexandria
|
|
4496
|
+
by default so it is not shown as a self-notification.
|
|
4497
|
+
Examples:
|
|
4498
|
+
suntropy notifications send --to-user <userUID> --message "He terminado el estudio."
|
|
4499
|
+
suntropy notifications send --to-user <userUID> --title "Estudio listo" \\
|
|
4500
|
+
--message "Revisa los resultados." --study <solarStudyId> --mention`
|
|
4501
|
+
).requiredOption("--to-user <userUID>", "Recipient userUID (who gets notified)").requiredOption("--message <text>", "Notification body (supports Markdown)").option("--title <text>", "Notification title", "Alexandria").option("--severity <severity>", `Severity: ${SEVERITIES.join(" | ")}`, "info").option(
|
|
4502
|
+
"--mention",
|
|
4503
|
+
`Send as a mention (type new_mention) instead of an update (new_comment). Mentions are gated by the user's "mention" notification setting.`
|
|
4504
|
+
).option(
|
|
4505
|
+
"--study <solarStudyId>",
|
|
4506
|
+
"Link the notification to a solar study: makes it click-through to that study"
|
|
4507
|
+
).option("--link <url>", "Custom link for the email call-to-action").option(
|
|
4508
|
+
"--from-user <userUID>",
|
|
4509
|
+
"Sender userUID shown as the author (default: the authenticated user)"
|
|
4510
|
+
).addOption(new Option2("--as-alexandria").hideHelp()).action(async (opts) => {
|
|
4511
|
+
try {
|
|
4512
|
+
const global = getGlobalOpts16(notifications);
|
|
4513
|
+
const severity = opts.severity;
|
|
4514
|
+
if (!SEVERITIES.includes(severity)) {
|
|
4515
|
+
throw new Error(
|
|
4516
|
+
`Invalid --severity "${opts.severity}". Use one of: ${SEVERITIES.join(", ")}.`
|
|
4517
|
+
);
|
|
4518
|
+
}
|
|
4519
|
+
if (opts.study) assertStudyObjectId(opts.study);
|
|
4520
|
+
const client = createServiceClient("notifications", global);
|
|
4521
|
+
const profile = getActiveProfile(loadConfig());
|
|
4522
|
+
const fromUserUid = opts.fromUser ? opts.fromUser : opts.asAlexandria ? ALEXANDRIA_USER_UID : profile.userUID;
|
|
4523
|
+
const notification = {
|
|
4524
|
+
notificationType: opts.mention ? "new_mention" : "new_comment",
|
|
4525
|
+
severity,
|
|
4526
|
+
origin: opts.asAlexandria ? "ALEXANDRIA" : "CLI",
|
|
4527
|
+
fromUserUid,
|
|
4528
|
+
toUserUid: opts.toUser,
|
|
4529
|
+
notificationContent: {
|
|
4530
|
+
contentType: "COMMENT",
|
|
4531
|
+
name: opts.title,
|
|
4532
|
+
description: opts.message,
|
|
4533
|
+
...opts.study ? { externalId: opts.study } : {},
|
|
4534
|
+
...opts.link ? { link: opts.link } : {}
|
|
4535
|
+
}
|
|
4536
|
+
};
|
|
4537
|
+
const res = await client.post("/notifications", notification);
|
|
4538
|
+
if (typeof res.data === "string") {
|
|
4539
|
+
output(
|
|
4540
|
+
{ sent: false, reason: res.data, toUser: opts.toUser },
|
|
4541
|
+
global
|
|
4542
|
+
);
|
|
4543
|
+
return;
|
|
4544
|
+
}
|
|
4545
|
+
output(
|
|
4546
|
+
{
|
|
4547
|
+
sent: true,
|
|
4548
|
+
notificationId: res.data?.idNotification,
|
|
4549
|
+
toUser: opts.toUser,
|
|
4550
|
+
type: notification.notificationType,
|
|
4551
|
+
severity
|
|
4552
|
+
},
|
|
4553
|
+
global
|
|
4554
|
+
);
|
|
4555
|
+
} catch (err) {
|
|
4556
|
+
outputError(handleApiError(err));
|
|
4557
|
+
}
|
|
4558
|
+
});
|
|
4559
|
+
}
|
|
4560
|
+
|
|
4464
4561
|
// src/access.ts
|
|
4465
4562
|
var COMMAND_TIERS = ["read", "write", "delete"];
|
|
4466
4563
|
var TIER_VALUE = { read: 0, write: 1, delete: 2 };
|
|
@@ -4511,6 +4608,7 @@ var WRITE_VERBS = /* @__PURE__ */ new Set([
|
|
|
4511
4608
|
"init-default",
|
|
4512
4609
|
"add-comment",
|
|
4513
4610
|
"comment",
|
|
4611
|
+
"send",
|
|
4514
4612
|
"calculate-results",
|
|
4515
4613
|
"optimize-peakpower"
|
|
4516
4614
|
]);
|
|
@@ -4559,7 +4657,7 @@ function keepCommand(cmd, segs, max) {
|
|
|
4559
4657
|
}
|
|
4560
4658
|
|
|
4561
4659
|
// src/commands/command-profile.ts
|
|
4562
|
-
function
|
|
4660
|
+
function getGlobalOpts17(cmd) {
|
|
4563
4661
|
let root = cmd;
|
|
4564
4662
|
while (root.parent) root = root.parent;
|
|
4565
4663
|
return root.opts();
|
|
@@ -4567,7 +4665,7 @@ function getGlobalOpts16(cmd) {
|
|
|
4567
4665
|
function registerCommandProfileCommand(program2) {
|
|
4568
4666
|
program2.command("command-profile [tier]", { hidden: true }).description("Admin: get/set the command access profile (read | write | delete | reset)").action((tier) => {
|
|
4569
4667
|
try {
|
|
4570
|
-
const global =
|
|
4668
|
+
const global = getGlobalOpts17(program2);
|
|
4571
4669
|
if (!tier) {
|
|
4572
4670
|
const resolved = getActiveCommandProfile();
|
|
4573
4671
|
output(
|
|
@@ -4622,9 +4720,9 @@ function registerCommandProfileCommand(program2) {
|
|
|
4622
4720
|
}
|
|
4623
4721
|
|
|
4624
4722
|
// src/index.ts
|
|
4625
|
-
var CLI_VERSION = true ? "0.11.
|
|
4723
|
+
var CLI_VERSION = true ? "0.11.6" : "0.0.0-dev";
|
|
4626
4724
|
function createProgram() {
|
|
4627
|
-
const program2 = new
|
|
4725
|
+
const program2 = new Command4();
|
|
4628
4726
|
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)");
|
|
4629
4727
|
registerAuthCommands(program2);
|
|
4630
4728
|
registerConfigCommands(program2);
|
|
@@ -4637,6 +4735,7 @@ function createProgram() {
|
|
|
4637
4735
|
registerShareableCommands(program2);
|
|
4638
4736
|
registerTemplatesCommands(program2);
|
|
4639
4737
|
registerGeocodeCommands(program2);
|
|
4738
|
+
registerNotificationsCommands(program2);
|
|
4640
4739
|
registerCommandProfileCommand(program2);
|
|
4641
4740
|
applyCommandProfile(program2);
|
|
4642
4741
|
return program2;
|