@opally/cli 0.1.2 → 0.2.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/opally.js +3 -1
- package/dist/commands/analytics.js +4 -2
- package/dist/commands/chats.js +7 -4
- package/dist/commands/config.js +18 -1
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +107 -0
- package/dist/commands/emails.js +5 -3
- package/dist/commands/leads.js +4 -3
- package/dist/commands/voice.js +5 -3
- package/dist/output.d.ts +7 -0
- package/dist/output.js +10 -0
- package/package.json +1 -1
package/dist/bin/opally.js
CHANGED
|
@@ -8,15 +8,17 @@ const emails_js_1 = require("../commands/emails.js");
|
|
|
8
8
|
const chats_js_1 = require("../commands/chats.js");
|
|
9
9
|
const voice_js_1 = require("../commands/voice.js");
|
|
10
10
|
const analytics_js_1 = require("../commands/analytics.js");
|
|
11
|
+
const doctor_js_1 = require("../commands/doctor.js");
|
|
11
12
|
const program = new commander_1.Command();
|
|
12
13
|
program
|
|
13
14
|
.name("opally")
|
|
14
15
|
.description("CLI for the Opally API")
|
|
15
|
-
.version("0.
|
|
16
|
+
.version("0.2.0");
|
|
16
17
|
program.addCommand(config_js_1.configCommand);
|
|
17
18
|
program.addCommand(leads_js_1.leadsCommand);
|
|
18
19
|
program.addCommand(emails_js_1.emailsCommand);
|
|
19
20
|
program.addCommand(chats_js_1.chatsCommand);
|
|
20
21
|
program.addCommand(voice_js_1.voiceCommand);
|
|
21
22
|
program.addCommand(analytics_js_1.analyticsCommand);
|
|
23
|
+
program.addCommand(doctor_js_1.doctorCommand);
|
|
22
24
|
program.parse();
|
|
@@ -13,11 +13,12 @@ exports.analyticsCommand
|
|
|
13
13
|
.option("--to <date>", "End date (ISO 8601)")
|
|
14
14
|
.option("--json", "Output as JSON")
|
|
15
15
|
.action(async (opts) => {
|
|
16
|
+
const fmt = (0, output_js_1.getFormat)(opts);
|
|
16
17
|
const res = await (0, client_js_1.api)("/analytics/overview", {
|
|
17
18
|
from: opts.from,
|
|
18
19
|
to: opts.to,
|
|
19
20
|
});
|
|
20
|
-
if (
|
|
21
|
+
if (fmt === "json") {
|
|
21
22
|
(0, output_js_1.output)(res, "json");
|
|
22
23
|
return;
|
|
23
24
|
}
|
|
@@ -58,6 +59,7 @@ function addTimeSeriesCommand(name, path, description, extraFilters) {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
cmd.action(async (opts) => {
|
|
62
|
+
const fmt = (0, output_js_1.getFormat)(opts);
|
|
61
63
|
const params = {
|
|
62
64
|
from: opts.from,
|
|
63
65
|
to: opts.to,
|
|
@@ -70,7 +72,7 @@ function addTimeSeriesCommand(name, path, description, extraFilters) {
|
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
const res = await (0, client_js_1.api)(path, params);
|
|
73
|
-
if (
|
|
75
|
+
if (fmt === "json") {
|
|
74
76
|
(0, output_js_1.output)(res, "json");
|
|
75
77
|
return;
|
|
76
78
|
}
|
package/dist/commands/chats.js
CHANGED
|
@@ -16,6 +16,7 @@ exports.chatsCommand
|
|
|
16
16
|
.option("--cursor <cursor>", "Pagination cursor")
|
|
17
17
|
.option("--json", "Output as JSON")
|
|
18
18
|
.action(async (opts) => {
|
|
19
|
+
const fmt = (0, output_js_1.getFormat)(opts);
|
|
19
20
|
const res = await (0, client_js_1.api)("/conversations/chats", {
|
|
20
21
|
from: opts.from,
|
|
21
22
|
to: opts.to,
|
|
@@ -23,14 +24,14 @@ exports.chatsCommand
|
|
|
23
24
|
limit: opts.limit,
|
|
24
25
|
cursor: opts.cursor,
|
|
25
26
|
});
|
|
26
|
-
(0, output_js_1.output)(res.data,
|
|
27
|
+
(0, output_js_1.output)(res.data, fmt, [
|
|
27
28
|
"id",
|
|
28
29
|
"title",
|
|
29
30
|
"platform",
|
|
30
31
|
"language",
|
|
31
32
|
"created_at",
|
|
32
33
|
]);
|
|
33
|
-
if (res.pagination.has_more) {
|
|
34
|
+
if (fmt === "table" && res.pagination.has_more) {
|
|
34
35
|
console.log(`\nMore results available. Use --cursor ${res.pagination.next_cursor}`);
|
|
35
36
|
}
|
|
36
37
|
});
|
|
@@ -39,9 +40,10 @@ exports.chatsCommand
|
|
|
39
40
|
.description("Get chat conversation details")
|
|
40
41
|
.option("--json", "Output as JSON")
|
|
41
42
|
.action(async (id, opts) => {
|
|
43
|
+
const fmt = (0, output_js_1.getFormat)(opts);
|
|
42
44
|
const res = await (0, client_js_1.api)(`/conversations/chats/${(0, client_js_1.validateId)(id)}`);
|
|
43
45
|
const data = res.data;
|
|
44
|
-
if (
|
|
46
|
+
if (fmt === "json") {
|
|
45
47
|
(0, output_js_1.output)(data, "json");
|
|
46
48
|
return;
|
|
47
49
|
}
|
|
@@ -57,9 +59,10 @@ exports.chatsCommand
|
|
|
57
59
|
.description("Get messages in a chat conversation")
|
|
58
60
|
.option("--json", "Output as JSON")
|
|
59
61
|
.action(async (id, opts) => {
|
|
62
|
+
const fmt = (0, output_js_1.getFormat)(opts);
|
|
60
63
|
const res = await (0, client_js_1.api)(`/conversations/chats/${(0, client_js_1.validateId)(id)}/messages`);
|
|
61
64
|
const messages = res.data.messages;
|
|
62
|
-
if (
|
|
65
|
+
if (fmt === "json") {
|
|
63
66
|
(0, output_js_1.output)(messages, "json");
|
|
64
67
|
return;
|
|
65
68
|
}
|
package/dist/commands/config.js
CHANGED
|
@@ -8,7 +8,7 @@ exports.configCommand = new commander_1.Command("config")
|
|
|
8
8
|
exports.configCommand
|
|
9
9
|
.command("set-key <key>")
|
|
10
10
|
.description("Save your Opally API key")
|
|
11
|
-
.action((key) => {
|
|
11
|
+
.action(async (key) => {
|
|
12
12
|
if (!key.startsWith("op_live_") && !key.startsWith("op_test_")) {
|
|
13
13
|
console.error("Invalid key format. Expected op_live_* or op_test_*");
|
|
14
14
|
process.exit(1);
|
|
@@ -17,6 +17,23 @@ exports.configCommand
|
|
|
17
17
|
console.error("Invalid key format. Key is too short.");
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
|
+
// Validate key against the API
|
|
21
|
+
const baseUrl = (0, config_js_1.getBaseUrl)();
|
|
22
|
+
try {
|
|
23
|
+
const res = await fetch(new URL("/v1/analytics/overview", baseUrl).toString(), {
|
|
24
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
25
|
+
});
|
|
26
|
+
if (res.status === 401 || res.status === 403) {
|
|
27
|
+
console.error("Invalid API key. The key was rejected by the Opally API.");
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
if (!res.ok) {
|
|
31
|
+
console.error(`Warning: Could not validate key (HTTP ${res.status}). Saving anyway.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
console.error("Warning: Could not reach the Opally API to validate key. Saving anyway.");
|
|
36
|
+
}
|
|
20
37
|
const config = (0, config_js_1.loadConfig)();
|
|
21
38
|
config.api_key = key;
|
|
22
39
|
(0, config_js_1.saveConfig)(config);
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.doctorCommand = void 0;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const config_js_1 = require("../config.js");
|
|
6
|
+
const output_js_1 = require("../output.js");
|
|
7
|
+
const VERSION = "0.2.0";
|
|
8
|
+
exports.doctorCommand = new commander_1.Command("doctor")
|
|
9
|
+
.description("Run environment diagnostics")
|
|
10
|
+
.option("--json", "Output as JSON")
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const checks = [];
|
|
13
|
+
const isJson = opts.json || !process.stdout.isTTY;
|
|
14
|
+
// 1. CLI Version
|
|
15
|
+
checks.push({
|
|
16
|
+
name: "CLI Version",
|
|
17
|
+
status: "pass",
|
|
18
|
+
message: `v${VERSION}`,
|
|
19
|
+
});
|
|
20
|
+
// 2. API Key
|
|
21
|
+
const envKey = process.env.OPALLY_API_KEY;
|
|
22
|
+
const config = (0, config_js_1.loadConfig)();
|
|
23
|
+
const key = envKey || config.api_key;
|
|
24
|
+
if (key) {
|
|
25
|
+
const source = envKey ? "env" : "config";
|
|
26
|
+
const masked = key.slice(0, 12) + "..." + key.slice(-4);
|
|
27
|
+
checks.push({
|
|
28
|
+
name: "API Key",
|
|
29
|
+
status: "pass",
|
|
30
|
+
message: `${masked} (source: ${source})`,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
checks.push({
|
|
35
|
+
name: "API Key",
|
|
36
|
+
status: "fail",
|
|
37
|
+
message: "No API key found. Run: opally config set-key <key>",
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// 3. Base URL
|
|
41
|
+
const baseUrl = (0, config_js_1.getBaseUrl)();
|
|
42
|
+
checks.push({
|
|
43
|
+
name: "Base URL",
|
|
44
|
+
status: "pass",
|
|
45
|
+
message: baseUrl,
|
|
46
|
+
});
|
|
47
|
+
// 4. API Connection
|
|
48
|
+
if (key) {
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(new URL("/v1/analytics/overview", baseUrl).toString(), {
|
|
51
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
52
|
+
});
|
|
53
|
+
if (res.ok) {
|
|
54
|
+
checks.push({
|
|
55
|
+
name: "API Connection",
|
|
56
|
+
status: "pass",
|
|
57
|
+
message: "Connected",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else if (res.status === 401 || res.status === 403) {
|
|
61
|
+
checks.push({
|
|
62
|
+
name: "API Connection",
|
|
63
|
+
status: "fail",
|
|
64
|
+
message: "API key rejected (401/403)",
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
checks.push({
|
|
69
|
+
name: "API Connection",
|
|
70
|
+
status: "warn",
|
|
71
|
+
message: `Unexpected response (HTTP ${res.status})`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
checks.push({
|
|
77
|
+
name: "API Connection",
|
|
78
|
+
status: "fail",
|
|
79
|
+
message: `Could not reach ${baseUrl}`,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
checks.push({
|
|
85
|
+
name: "API Connection",
|
|
86
|
+
status: "fail",
|
|
87
|
+
message: "Skipped (no API key)",
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// Output
|
|
91
|
+
const ok = checks.every((c) => c.status !== "fail");
|
|
92
|
+
if (isJson) {
|
|
93
|
+
(0, output_js_1.json)({ ok, checks });
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.log("\n Opally Doctor\n");
|
|
97
|
+
for (const check of checks) {
|
|
98
|
+
const icon = check.status === "pass" ? "\x1b[32m✔\x1b[0m"
|
|
99
|
+
: check.status === "warn" ? "\x1b[33m!\x1b[0m"
|
|
100
|
+
: "\x1b[31m✖\x1b[0m";
|
|
101
|
+
console.log(` ${icon} ${check.name}: ${check.message}`);
|
|
102
|
+
}
|
|
103
|
+
console.log();
|
|
104
|
+
}
|
|
105
|
+
if (!ok)
|
|
106
|
+
process.exit(1);
|
|
107
|
+
});
|
package/dist/commands/emails.js
CHANGED
|
@@ -16,6 +16,7 @@ exports.emailsCommand
|
|
|
16
16
|
.option("--cursor <cursor>", "Pagination cursor")
|
|
17
17
|
.option("--json", "Output as JSON")
|
|
18
18
|
.action(async (opts) => {
|
|
19
|
+
const fmt = (0, output_js_1.getFormat)(opts);
|
|
19
20
|
const res = await (0, client_js_1.api)("/conversations/emails", {
|
|
20
21
|
from: opts.from,
|
|
21
22
|
to: opts.to,
|
|
@@ -23,14 +24,14 @@ exports.emailsCommand
|
|
|
23
24
|
limit: opts.limit,
|
|
24
25
|
cursor: opts.cursor,
|
|
25
26
|
});
|
|
26
|
-
(0, output_js_1.output)(res.data,
|
|
27
|
+
(0, output_js_1.output)(res.data, fmt, [
|
|
27
28
|
"id",
|
|
28
29
|
"subject",
|
|
29
30
|
"sender",
|
|
30
31
|
"status",
|
|
31
32
|
"timestamp",
|
|
32
33
|
]);
|
|
33
|
-
if (res.pagination.has_more) {
|
|
34
|
+
if (fmt === "table" && res.pagination.has_more) {
|
|
34
35
|
console.log(`\nMore results available. Use --cursor ${res.pagination.next_cursor}`);
|
|
35
36
|
}
|
|
36
37
|
});
|
|
@@ -39,9 +40,10 @@ exports.emailsCommand
|
|
|
39
40
|
.description("Get email details with draft")
|
|
40
41
|
.option("--json", "Output as JSON")
|
|
41
42
|
.action(async (id, opts) => {
|
|
43
|
+
const fmt = (0, output_js_1.getFormat)(opts);
|
|
42
44
|
const res = await (0, client_js_1.api)(`/conversations/emails/${(0, client_js_1.validateId)(id)}`);
|
|
43
45
|
const data = res.data;
|
|
44
|
-
if (
|
|
46
|
+
if (fmt === "json") {
|
|
45
47
|
(0, output_js_1.output)(data, "json");
|
|
46
48
|
return;
|
|
47
49
|
}
|
package/dist/commands/leads.js
CHANGED
|
@@ -16,6 +16,7 @@ exports.leadsCommand
|
|
|
16
16
|
.option("--cursor <cursor>", "Pagination cursor")
|
|
17
17
|
.option("--json", "Output as JSON")
|
|
18
18
|
.action(async (opts) => {
|
|
19
|
+
const fmt = (0, output_js_1.getFormat)(opts);
|
|
19
20
|
const res = await (0, client_js_1.api)("/leads", {
|
|
20
21
|
from: opts.from,
|
|
21
22
|
to: opts.to,
|
|
@@ -23,14 +24,14 @@ exports.leadsCommand
|
|
|
23
24
|
limit: opts.limit,
|
|
24
25
|
cursor: opts.cursor,
|
|
25
26
|
});
|
|
26
|
-
(0, output_js_1.output)(res.data,
|
|
27
|
+
(0, output_js_1.output)(res.data, fmt, [
|
|
27
28
|
"id",
|
|
28
29
|
"name",
|
|
29
30
|
"email",
|
|
30
31
|
"source",
|
|
31
32
|
"created_at",
|
|
32
33
|
]);
|
|
33
|
-
if (res.pagination.has_more) {
|
|
34
|
+
if (fmt === "table" && res.pagination.has_more) {
|
|
34
35
|
console.log(`\nMore results available. Use --cursor ${res.pagination.next_cursor}`);
|
|
35
36
|
}
|
|
36
37
|
});
|
|
@@ -40,5 +41,5 @@ exports.leadsCommand
|
|
|
40
41
|
.option("--json", "Output as JSON")
|
|
41
42
|
.action(async (id, opts) => {
|
|
42
43
|
const res = await (0, client_js_1.api)(`/leads/${(0, client_js_1.validateId)(id)}`);
|
|
43
|
-
(0, output_js_1.output)(res.data,
|
|
44
|
+
(0, output_js_1.output)(res.data, (0, output_js_1.getFormat)(opts));
|
|
44
45
|
});
|
package/dist/commands/voice.js
CHANGED
|
@@ -15,13 +15,14 @@ exports.voiceCommand
|
|
|
15
15
|
.option("--cursor <cursor>", "Pagination cursor")
|
|
16
16
|
.option("--json", "Output as JSON")
|
|
17
17
|
.action(async (opts) => {
|
|
18
|
+
const fmt = (0, output_js_1.getFormat)(opts);
|
|
18
19
|
const res = await (0, client_js_1.api)("/conversations/voice", {
|
|
19
20
|
from: opts.from,
|
|
20
21
|
to: opts.to,
|
|
21
22
|
limit: opts.limit,
|
|
22
23
|
cursor: opts.cursor,
|
|
23
24
|
});
|
|
24
|
-
(0, output_js_1.output)(res.data,
|
|
25
|
+
(0, output_js_1.output)(res.data, fmt, [
|
|
25
26
|
"id",
|
|
26
27
|
"from_number",
|
|
27
28
|
"to_number",
|
|
@@ -29,7 +30,7 @@ exports.voiceCommand
|
|
|
29
30
|
"status",
|
|
30
31
|
"started_at",
|
|
31
32
|
]);
|
|
32
|
-
if (res.pagination.has_more) {
|
|
33
|
+
if (fmt === "table" && res.pagination.has_more) {
|
|
33
34
|
console.log(`\nMore results available. Use --cursor ${res.pagination.next_cursor}`);
|
|
34
35
|
}
|
|
35
36
|
});
|
|
@@ -38,9 +39,10 @@ exports.voiceCommand
|
|
|
38
39
|
.description("Get voice call details")
|
|
39
40
|
.option("--json", "Output as JSON")
|
|
40
41
|
.action(async (id, opts) => {
|
|
42
|
+
const fmt = (0, output_js_1.getFormat)(opts);
|
|
41
43
|
const res = await (0, client_js_1.api)(`/conversations/voice/${(0, client_js_1.validateId)(id)}`);
|
|
42
44
|
const data = res.data;
|
|
43
|
-
if (
|
|
45
|
+
if (fmt === "json") {
|
|
44
46
|
(0, output_js_1.output)(data, "json");
|
|
45
47
|
return;
|
|
46
48
|
}
|
package/dist/output.d.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
export declare function table(rows: Record<string, unknown>[], columns?: string[]): void;
|
|
2
2
|
export declare function json(data: unknown): void;
|
|
3
|
+
/**
|
|
4
|
+
* Resolve output format: --json flag wins, otherwise auto-detect
|
|
5
|
+
* based on whether stdout is a TTY (terminal = table, piped = json).
|
|
6
|
+
*/
|
|
7
|
+
export declare function getFormat(opts: {
|
|
8
|
+
json?: boolean;
|
|
9
|
+
}): "table" | "json";
|
|
3
10
|
export declare function output(data: unknown, format: "table" | "json", columns?: string[]): void;
|
package/dist/output.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.table = table;
|
|
4
4
|
exports.json = json;
|
|
5
|
+
exports.getFormat = getFormat;
|
|
5
6
|
exports.output = output;
|
|
6
7
|
function table(rows, columns) {
|
|
7
8
|
if (rows.length === 0) {
|
|
@@ -27,6 +28,15 @@ function table(rows, columns) {
|
|
|
27
28
|
function json(data) {
|
|
28
29
|
console.log(JSON.stringify(data, null, 2));
|
|
29
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve output format: --json flag wins, otherwise auto-detect
|
|
33
|
+
* based on whether stdout is a TTY (terminal = table, piped = json).
|
|
34
|
+
*/
|
|
35
|
+
function getFormat(opts) {
|
|
36
|
+
if (opts.json)
|
|
37
|
+
return "json";
|
|
38
|
+
return process.stdout.isTTY ? "table" : "json";
|
|
39
|
+
}
|
|
30
40
|
function output(data, format, columns) {
|
|
31
41
|
if (format === "json") {
|
|
32
42
|
json(data);
|