guesty-cli 1.0.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/auth.d.ts +2 -0
- package/dist/auth.js +156 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +46 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +109 -0
- package/dist/commands/accounting.d.ts +2 -0
- package/dist/commands/accounting.js +102 -0
- package/dist/commands/calendar.d.ts +2 -0
- package/dist/commands/calendar.js +60 -0
- package/dist/commands/conversations.d.ts +2 -0
- package/dist/commands/conversations.js +40 -0
- package/dist/commands/financials.d.ts +2 -0
- package/dist/commands/financials.js +56 -0
- package/dist/commands/guests.d.ts +2 -0
- package/dist/commands/guests.js +57 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +109 -0
- package/dist/commands/integrations.d.ts +2 -0
- package/dist/commands/integrations.js +62 -0
- package/dist/commands/listings.d.ts +2 -0
- package/dist/commands/listings.js +72 -0
- package/dist/commands/owners.d.ts +2 -0
- package/dist/commands/owners.js +76 -0
- package/dist/commands/properties.d.ts +2 -0
- package/dist/commands/properties.js +104 -0
- package/dist/commands/quotes.d.ts +2 -0
- package/dist/commands/quotes.js +31 -0
- package/dist/commands/raw.d.ts +2 -0
- package/dist/commands/raw.js +19 -0
- package/dist/commands/reservations.d.ts +2 -0
- package/dist/commands/reservations.js +126 -0
- package/dist/commands/reviews.d.ts +2 -0
- package/dist/commands/reviews.js +39 -0
- package/dist/commands/tasks.d.ts +2 -0
- package/dist/commands/tasks.js +60 -0
- package/dist/commands/users.d.ts +2 -0
- package/dist/commands/users.js +59 -0
- package/dist/commands/webhooks.d.ts +2 -0
- package/dist/commands/webhooks.js +52 -0
- package/dist/env.d.ts +1 -0
- package/dist/env.js +44 -0
- package/dist/output.d.ts +2 -0
- package/dist/output.js +22 -0
- package/dist/stdin.d.ts +1 -0
- package/dist/stdin.js +12 -0
- package/package.json +41 -0
package/dist/auth.d.ts
ADDED
package/dist/auth.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
const CONFIG_DIR = join(homedir(), ".guesty-cli");
|
|
5
|
+
const TOKEN_FILE = join(CONFIG_DIR, "token.json");
|
|
6
|
+
const MAX_TOKENS_PER_DAY = 5;
|
|
7
|
+
const REFRESH_BUFFER_MS = 5 * 60 * 1000;
|
|
8
|
+
let cached = { token: null, issued_timestamps: [] };
|
|
9
|
+
let diskLoaded = false;
|
|
10
|
+
function loadFromDisk() {
|
|
11
|
+
if (diskLoaded)
|
|
12
|
+
return;
|
|
13
|
+
try {
|
|
14
|
+
if (existsSync(TOKEN_FILE)) {
|
|
15
|
+
cached = JSON.parse(readFileSync(TOKEN_FILE, "utf8"));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
cached = { token: null, issued_timestamps: [] };
|
|
20
|
+
}
|
|
21
|
+
diskLoaded = true;
|
|
22
|
+
}
|
|
23
|
+
function saveToDisk() {
|
|
24
|
+
try {
|
|
25
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
26
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
writeFileSync(TOKEN_FILE, JSON.stringify(cached, null, 2));
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
process.stderr.write(`Warning: failed to save token cache: ${e}\n`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function pruneOldTimestamps() {
|
|
35
|
+
const cutoff = Date.now() - 24 * 60 * 60 * 1000;
|
|
36
|
+
cached.issued_timestamps = cached.issued_timestamps.filter((t) => t > cutoff);
|
|
37
|
+
}
|
|
38
|
+
function hasSupabase() {
|
|
39
|
+
return !!(process.env.SUPABASE_URL && process.env.SUPABASE_SERVICE_ROLE_KEY);
|
|
40
|
+
}
|
|
41
|
+
async function getTokenFromSupabase() {
|
|
42
|
+
const url = process.env.SUPABASE_URL;
|
|
43
|
+
const key = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
44
|
+
if (!url || !key)
|
|
45
|
+
return null;
|
|
46
|
+
try {
|
|
47
|
+
const res = await fetch(`${url}/rest/v1/guesty_tokens?select=access_token,expires_at&order=created_at.desc&limit=1`, {
|
|
48
|
+
headers: {
|
|
49
|
+
apikey: key,
|
|
50
|
+
Authorization: `Bearer ${key}`,
|
|
51
|
+
Accept: "application/json",
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok)
|
|
55
|
+
return null;
|
|
56
|
+
const rows = await res.json();
|
|
57
|
+
if (rows.length === 0)
|
|
58
|
+
return null;
|
|
59
|
+
const token = rows[0];
|
|
60
|
+
if (token.expires_at > Date.now() + REFRESH_BUFFER_MS) {
|
|
61
|
+
return token;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Fall through to direct OAuth
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
async function saveTokenToSupabase(token) {
|
|
70
|
+
const url = process.env.SUPABASE_URL;
|
|
71
|
+
const key = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
72
|
+
if (!url || !key)
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
await fetch(`${url}/rest/v1/guesty_tokens`, {
|
|
76
|
+
method: "POST",
|
|
77
|
+
headers: {
|
|
78
|
+
apikey: key,
|
|
79
|
+
Authorization: `Bearer ${key}`,
|
|
80
|
+
"Content-Type": "application/json",
|
|
81
|
+
Prefer: "resolution=merge-duplicates",
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({
|
|
84
|
+
token_type: "Bearer",
|
|
85
|
+
access_token: token.access_token,
|
|
86
|
+
expires_at: token.expires_at,
|
|
87
|
+
created_at: Date.now(),
|
|
88
|
+
}),
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
// Non-fatal
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function invalidateToken() {
|
|
96
|
+
loadFromDisk();
|
|
97
|
+
cached.token = null;
|
|
98
|
+
saveToDisk();
|
|
99
|
+
}
|
|
100
|
+
export async function getToken() {
|
|
101
|
+
loadFromDisk();
|
|
102
|
+
pruneOldTimestamps();
|
|
103
|
+
// 1. Return in-memory/disk cached token if still valid
|
|
104
|
+
if (cached.token && cached.token.expires_at > Date.now() + REFRESH_BUFFER_MS) {
|
|
105
|
+
return cached.token.access_token;
|
|
106
|
+
}
|
|
107
|
+
// 2. If Supabase is configured, read shared token (avoids burning a token request)
|
|
108
|
+
if (hasSupabase()) {
|
|
109
|
+
const sbToken = await getTokenFromSupabase();
|
|
110
|
+
if (sbToken) {
|
|
111
|
+
cached.token = sbToken;
|
|
112
|
+
saveToDisk();
|
|
113
|
+
return sbToken.access_token;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// 3. Request new token from Guesty directly
|
|
117
|
+
if (cached.issued_timestamps.length >= MAX_TOKENS_PER_DAY) {
|
|
118
|
+
const oldest = cached.issued_timestamps[0];
|
|
119
|
+
const resetIn = Math.ceil((oldest + 24 * 60 * 60 * 1000 - Date.now()) / 60_000);
|
|
120
|
+
throw new Error(`OAuth token limit reached (${MAX_TOKENS_PER_DAY}/day). Next token available in ~${resetIn} minutes. ` +
|
|
121
|
+
`Guesty enforces a strict 5-token-per-24h limit on their OAuth endpoint.`);
|
|
122
|
+
}
|
|
123
|
+
const clientId = process.env.GUESTY_CLIENT_ID;
|
|
124
|
+
const clientSecret = process.env.GUESTY_CLIENT_SECRET;
|
|
125
|
+
if (!clientId || !clientSecret) {
|
|
126
|
+
throw new Error("Missing GUESTY_CLIENT_ID and/or GUESTY_CLIENT_SECRET.\n" +
|
|
127
|
+
"Set them as environment variables or in ~/.guesty-cli/.env");
|
|
128
|
+
}
|
|
129
|
+
process.stderr.write("Requesting new Guesty OAuth token...\n");
|
|
130
|
+
const res = await fetch("https://open-api.guesty.com/oauth2/token", {
|
|
131
|
+
method: "POST",
|
|
132
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
133
|
+
body: new URLSearchParams({
|
|
134
|
+
grant_type: "client_credentials",
|
|
135
|
+
scope: "open-api",
|
|
136
|
+
client_id: clientId,
|
|
137
|
+
client_secret: clientSecret,
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
if (!res.ok) {
|
|
141
|
+
const body = await res.text().catch(() => "");
|
|
142
|
+
throw new Error(`OAuth token request failed (${res.status}): ${body}`);
|
|
143
|
+
}
|
|
144
|
+
const data = await res.json();
|
|
145
|
+
const tokenData = {
|
|
146
|
+
access_token: data.access_token,
|
|
147
|
+
expires_at: Date.now() + (data.expires_in ?? 3600) * 1000,
|
|
148
|
+
};
|
|
149
|
+
cached.token = tokenData;
|
|
150
|
+
cached.issued_timestamps.push(Date.now());
|
|
151
|
+
saveToDisk();
|
|
152
|
+
// Share with Supabase if configured
|
|
153
|
+
await saveTokenToSupabase(tokenData);
|
|
154
|
+
process.stderr.write("Token acquired and cached.\n");
|
|
155
|
+
return tokenData.access_token;
|
|
156
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { loadEnv } from "./env.js";
|
|
3
|
+
loadEnv();
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import { reservations } from "./commands/reservations.js";
|
|
6
|
+
import { listings } from "./commands/listings.js";
|
|
7
|
+
import { calendar } from "./commands/calendar.js";
|
|
8
|
+
import { guests } from "./commands/guests.js";
|
|
9
|
+
import { conversations } from "./commands/conversations.js";
|
|
10
|
+
import { tasks } from "./commands/tasks.js";
|
|
11
|
+
import { financials } from "./commands/financials.js";
|
|
12
|
+
import { reviews } from "./commands/reviews.js";
|
|
13
|
+
import { owners } from "./commands/owners.js";
|
|
14
|
+
import { accounting } from "./commands/accounting.js";
|
|
15
|
+
import { properties } from "./commands/properties.js";
|
|
16
|
+
import { quotes } from "./commands/quotes.js";
|
|
17
|
+
import { webhooks } from "./commands/webhooks.js";
|
|
18
|
+
import { users } from "./commands/users.js";
|
|
19
|
+
import { integrations } from "./commands/integrations.js";
|
|
20
|
+
import { raw } from "./commands/raw.js";
|
|
21
|
+
import { init } from "./commands/init.js";
|
|
22
|
+
const program = new Command()
|
|
23
|
+
.name("guesty")
|
|
24
|
+
.version("1.0.0")
|
|
25
|
+
.description("Guesty API CLI — authenticates via OAuth with disk-cached tokens. Use 'raw' for any endpoint not covered by named commands.");
|
|
26
|
+
program.addCommand(init);
|
|
27
|
+
program.addCommand(reservations);
|
|
28
|
+
program.addCommand(listings);
|
|
29
|
+
program.addCommand(calendar);
|
|
30
|
+
program.addCommand(guests);
|
|
31
|
+
program.addCommand(conversations);
|
|
32
|
+
program.addCommand(tasks);
|
|
33
|
+
program.addCommand(financials);
|
|
34
|
+
program.addCommand(reviews);
|
|
35
|
+
program.addCommand(owners);
|
|
36
|
+
program.addCommand(accounting);
|
|
37
|
+
program.addCommand(properties);
|
|
38
|
+
program.addCommand(quotes);
|
|
39
|
+
program.addCommand(webhooks);
|
|
40
|
+
program.addCommand(users);
|
|
41
|
+
program.addCommand(integrations);
|
|
42
|
+
program.addCommand(raw);
|
|
43
|
+
program.parseAsync().catch((err) => {
|
|
44
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
});
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface FetchOptions {
|
|
2
|
+
method?: string;
|
|
3
|
+
body?: unknown;
|
|
4
|
+
params?: Record<string, string | number | boolean | string[]>;
|
|
5
|
+
}
|
|
6
|
+
export declare function guestyFetch<T = unknown>(path: string, options?: FetchOptions): Promise<T>;
|
|
7
|
+
export declare function paginateAll<T = unknown>(path: string, params?: Record<string, string | number | boolean | string[]>, resultsKey?: string): Promise<T[]>;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { getToken, invalidateToken } from "./auth.js";
|
|
2
|
+
const BASE_URL = "https://open-api.guesty.com";
|
|
3
|
+
const MAX_RETRIES = 3;
|
|
4
|
+
const RATE_LIMIT_WINDOW = 60_000;
|
|
5
|
+
const RATE_LIMIT_MAX = 100;
|
|
6
|
+
const requestTimestamps = [];
|
|
7
|
+
async function enforceRateLimit() {
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
while (requestTimestamps.length > 0 && requestTimestamps[0] < now - RATE_LIMIT_WINDOW) {
|
|
10
|
+
requestTimestamps.shift();
|
|
11
|
+
}
|
|
12
|
+
if (requestTimestamps.length >= RATE_LIMIT_MAX) {
|
|
13
|
+
const waitMs = requestTimestamps[0] + RATE_LIMIT_WINDOW - now;
|
|
14
|
+
process.stderr.write(`Rate limit reached (${RATE_LIMIT_MAX}/min). Waiting ${Math.ceil(waitMs / 1000)}s...\n`);
|
|
15
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
16
|
+
return enforceRateLimit();
|
|
17
|
+
}
|
|
18
|
+
requestTimestamps.push(now);
|
|
19
|
+
}
|
|
20
|
+
function buildQuery(params) {
|
|
21
|
+
const parts = [];
|
|
22
|
+
for (const [key, val] of Object.entries(params)) {
|
|
23
|
+
if (val === undefined || val === null)
|
|
24
|
+
continue;
|
|
25
|
+
if (Array.isArray(val)) {
|
|
26
|
+
val.forEach((v) => parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(v)}`));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(val))}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return parts.length > 0 ? `?${parts.join("&")}` : "";
|
|
33
|
+
}
|
|
34
|
+
export async function guestyFetch(path, options = {}) {
|
|
35
|
+
const { method = "GET", body, params } = options;
|
|
36
|
+
const token = await getToken();
|
|
37
|
+
const url = `${BASE_URL}${path}${params ? buildQuery(params) : ""}`;
|
|
38
|
+
const headers = {
|
|
39
|
+
Authorization: `Bearer ${token}`,
|
|
40
|
+
Accept: "application/json",
|
|
41
|
+
};
|
|
42
|
+
if (body) {
|
|
43
|
+
headers["Content-Type"] = "application/json";
|
|
44
|
+
}
|
|
45
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
46
|
+
await enforceRateLimit();
|
|
47
|
+
const res = await fetch(url, {
|
|
48
|
+
method,
|
|
49
|
+
headers,
|
|
50
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
51
|
+
});
|
|
52
|
+
if (res.status === 204) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
if (res.status === 401 && attempt < MAX_RETRIES) {
|
|
56
|
+
process.stderr.write(`Token expired (401). Refreshing...\n`);
|
|
57
|
+
invalidateToken();
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (res.status === 429 && attempt < MAX_RETRIES) {
|
|
61
|
+
const retryAfter = res.headers.get("retry-after");
|
|
62
|
+
const waitMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : attempt * 5000;
|
|
63
|
+
process.stderr.write(`Rate limited. Retrying in ${waitMs / 1000}s...\n`);
|
|
64
|
+
await new Promise((r) => setTimeout(r, waitMs));
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
const text = await res.text().catch(() => "");
|
|
69
|
+
throw new Error(`Guesty API ${method} ${path} failed: ${res.status} ${res.statusText}\n${text}`);
|
|
70
|
+
}
|
|
71
|
+
return res.json();
|
|
72
|
+
}
|
|
73
|
+
throw new Error(`Guesty API ${method} ${path} failed after ${MAX_RETRIES} retries`);
|
|
74
|
+
}
|
|
75
|
+
export async function paginateAll(path, params = {}, resultsKey) {
|
|
76
|
+
const limit = 100;
|
|
77
|
+
let skip = 0;
|
|
78
|
+
const all = [];
|
|
79
|
+
while (true) {
|
|
80
|
+
const res = await guestyFetch(path, {
|
|
81
|
+
params: { ...params, limit, skip },
|
|
82
|
+
});
|
|
83
|
+
let items;
|
|
84
|
+
if (resultsKey && Array.isArray(res[resultsKey])) {
|
|
85
|
+
items = res[resultsKey];
|
|
86
|
+
}
|
|
87
|
+
else if (Array.isArray(res)) {
|
|
88
|
+
items = res;
|
|
89
|
+
}
|
|
90
|
+
else if ("results" in res && Array.isArray(res.results)) {
|
|
91
|
+
items = res.results;
|
|
92
|
+
}
|
|
93
|
+
else if ("data" in res && Array.isArray(res.data)) {
|
|
94
|
+
items = res.data;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
all.push(...items);
|
|
100
|
+
if (items.length < limit)
|
|
101
|
+
break;
|
|
102
|
+
if (all.length >= 10_000) {
|
|
103
|
+
process.stderr.write(`Warning: pagination capped at 10,000 results. Use filters to narrow your query.\n`);
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
skip += limit;
|
|
107
|
+
}
|
|
108
|
+
return all;
|
|
109
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { guestyFetch } from "../client.js";
|
|
3
|
+
import { print } from "../output.js";
|
|
4
|
+
import { readStdin } from "../stdin.js";
|
|
5
|
+
export const accounting = new Command("accounting")
|
|
6
|
+
.alias("acct")
|
|
7
|
+
.description("Accounting, expenses, and financial data");
|
|
8
|
+
accounting
|
|
9
|
+
.command("balance <reservationId>")
|
|
10
|
+
.description("Get folio balance for a reservation")
|
|
11
|
+
.action(async (id) => {
|
|
12
|
+
const data = await guestyFetch(`/v1/accounting-api/reservations/${id}/balance`);
|
|
13
|
+
print(data);
|
|
14
|
+
});
|
|
15
|
+
accounting
|
|
16
|
+
.command("journal-entries")
|
|
17
|
+
.description("Get recognized journal entries")
|
|
18
|
+
.option("--limit <n>", "Max results", "100")
|
|
19
|
+
.option("--skip <n>", "Offset", "0")
|
|
20
|
+
.action(async (opts) => {
|
|
21
|
+
const data = await guestyFetch("/v1/accounting-api/journal-entries", {
|
|
22
|
+
params: { limit: parseInt(opts.limit), skip: parseInt(opts.skip) },
|
|
23
|
+
});
|
|
24
|
+
print(data);
|
|
25
|
+
});
|
|
26
|
+
accounting
|
|
27
|
+
.command("journal-entries-all")
|
|
28
|
+
.description("Get all journal entries (including unrecognized)")
|
|
29
|
+
.option("--limit <n>", "Max results", "100")
|
|
30
|
+
.option("--skip <n>", "Offset", "0")
|
|
31
|
+
.action(async (opts) => {
|
|
32
|
+
const data = await guestyFetch("/v1/accounting-api/journal-entries/all", {
|
|
33
|
+
params: { limit: parseInt(opts.limit), skip: parseInt(opts.skip) },
|
|
34
|
+
});
|
|
35
|
+
print(data);
|
|
36
|
+
});
|
|
37
|
+
accounting
|
|
38
|
+
.command("categories")
|
|
39
|
+
.description("Get accounting categories")
|
|
40
|
+
.action(async () => {
|
|
41
|
+
const data = await guestyFetch("/v1/accounting-api/categories");
|
|
42
|
+
print(data);
|
|
43
|
+
});
|
|
44
|
+
accounting
|
|
45
|
+
.command("working-capital <ownerId>")
|
|
46
|
+
.description("Get owner working capital")
|
|
47
|
+
.action(async (ownerId) => {
|
|
48
|
+
const data = await guestyFetch(`/v1/accounting-api/owners/${ownerId}/working-capital`);
|
|
49
|
+
print(data);
|
|
50
|
+
});
|
|
51
|
+
accounting
|
|
52
|
+
.command("business-models")
|
|
53
|
+
.description("Get business models")
|
|
54
|
+
.action(async () => {
|
|
55
|
+
const data = await guestyFetch("/v1/business-models-api/light-business-models");
|
|
56
|
+
print(data);
|
|
57
|
+
});
|
|
58
|
+
accounting
|
|
59
|
+
.command("expenses")
|
|
60
|
+
.description("List expenses")
|
|
61
|
+
.option("--limit <n>", "Max results", "25")
|
|
62
|
+
.option("--skip <n>", "Offset", "0")
|
|
63
|
+
.action(async (opts) => {
|
|
64
|
+
const data = await guestyFetch("/v1/expenses-api/expenses", {
|
|
65
|
+
params: { limit: parseInt(opts.limit), skip: parseInt(opts.skip) },
|
|
66
|
+
});
|
|
67
|
+
print(data);
|
|
68
|
+
});
|
|
69
|
+
accounting
|
|
70
|
+
.command("expense <id>")
|
|
71
|
+
.description("Get expense by ID")
|
|
72
|
+
.action(async (id) => {
|
|
73
|
+
const data = await guestyFetch(`/v1/expenses-api/expenses/${id}`);
|
|
74
|
+
print(data);
|
|
75
|
+
});
|
|
76
|
+
accounting
|
|
77
|
+
.command("create-expense")
|
|
78
|
+
.description("Create an expense (--data or stdin)")
|
|
79
|
+
.option("--data <json>", "JSON body")
|
|
80
|
+
.action(async (opts) => {
|
|
81
|
+
const body = opts.data ? JSON.parse(opts.data) : JSON.parse(await readStdin());
|
|
82
|
+
const data = await guestyFetch("/v1/expenses-api/expenses", { method: "POST", body });
|
|
83
|
+
print(data);
|
|
84
|
+
});
|
|
85
|
+
accounting
|
|
86
|
+
.command("vendors")
|
|
87
|
+
.description("List vendors")
|
|
88
|
+
.action(async () => {
|
|
89
|
+
const data = await guestyFetch("/v1/vendors");
|
|
90
|
+
print(data);
|
|
91
|
+
});
|
|
92
|
+
accounting
|
|
93
|
+
.command("payment-transactions")
|
|
94
|
+
.description("Get payment transactions from Guesty Pay")
|
|
95
|
+
.option("--limit <n>", "Max results", "25")
|
|
96
|
+
.option("--skip <n>", "Offset", "0")
|
|
97
|
+
.action(async (opts) => {
|
|
98
|
+
const data = await guestyFetch("/v1/payment-transactions/reports", {
|
|
99
|
+
params: { limit: parseInt(opts.limit), skip: parseInt(opts.skip) },
|
|
100
|
+
});
|
|
101
|
+
print(data);
|
|
102
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { guestyFetch } from "../client.js";
|
|
3
|
+
import { print } from "../output.js";
|
|
4
|
+
import { readStdin } from "../stdin.js";
|
|
5
|
+
export const calendar = new Command("calendar")
|
|
6
|
+
.alias("cal")
|
|
7
|
+
.description("Calendar and availability");
|
|
8
|
+
calendar
|
|
9
|
+
.command("get <listingId>")
|
|
10
|
+
.description("Get calendar for a listing")
|
|
11
|
+
.requiredOption("--from <date>", "Start date (YYYY-MM-DD)")
|
|
12
|
+
.requiredOption("--to <date>", "End date (YYYY-MM-DD)")
|
|
13
|
+
.action(async (listingId, opts) => {
|
|
14
|
+
const data = await guestyFetch(`/v1/availability-pricing/api/calendar/listings/${listingId}`, { params: { from: opts.from, to: opts.to } });
|
|
15
|
+
print(data);
|
|
16
|
+
});
|
|
17
|
+
calendar
|
|
18
|
+
.command("update <listingId>")
|
|
19
|
+
.description("Update calendar (--data or stdin)")
|
|
20
|
+
.option("--data <json>", "JSON body")
|
|
21
|
+
.action(async (listingId, opts) => {
|
|
22
|
+
const body = opts.data
|
|
23
|
+
? JSON.parse(opts.data)
|
|
24
|
+
: JSON.parse(await readStdin());
|
|
25
|
+
const data = await guestyFetch(`/v1/availability-pricing/api/calendar/listings/${listingId}`, { method: "PUT", body });
|
|
26
|
+
print(data);
|
|
27
|
+
});
|
|
28
|
+
calendar
|
|
29
|
+
.command("block <listingId>")
|
|
30
|
+
.description("Block dates on a listing")
|
|
31
|
+
.requiredOption("--from <date>", "Start date (YYYY-MM-DD)")
|
|
32
|
+
.requiredOption("--to <date>", "End date (YYYY-MM-DD)")
|
|
33
|
+
.option("--note <text>", "Block note")
|
|
34
|
+
.action(async (listingId, opts) => {
|
|
35
|
+
const data = await guestyFetch(`/v1/availability-pricing/api/calendar/listings/${listingId}`, {
|
|
36
|
+
method: "PUT",
|
|
37
|
+
body: {
|
|
38
|
+
dateFrom: opts.from,
|
|
39
|
+
dateTo: opts.to,
|
|
40
|
+
status: "unavailable",
|
|
41
|
+
note: opts.note,
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
print(data);
|
|
45
|
+
});
|
|
46
|
+
calendar
|
|
47
|
+
.command("check <listingId>")
|
|
48
|
+
.description("Check availability for dates")
|
|
49
|
+
.requiredOption("--checkin <date>", "Check-in date (YYYY-MM-DD)")
|
|
50
|
+
.requiredOption("--checkout <date>", "Check-out date (YYYY-MM-DD)")
|
|
51
|
+
.action(async (listingId, opts) => {
|
|
52
|
+
const data = await guestyFetch("/v1/reservations/check-availability", {
|
|
53
|
+
params: {
|
|
54
|
+
listingId,
|
|
55
|
+
checkIn: opts.checkin,
|
|
56
|
+
checkOut: opts.checkout,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
print(data);
|
|
60
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { guestyFetch } from "../client.js";
|
|
3
|
+
import { print } from "../output.js";
|
|
4
|
+
export const conversations = new Command("conversations")
|
|
5
|
+
.alias("conv")
|
|
6
|
+
.description("Manage conversations/messaging");
|
|
7
|
+
conversations
|
|
8
|
+
.command("list")
|
|
9
|
+
.description("List conversations")
|
|
10
|
+
.option("--reservation <id>", "Filter by reservation ID")
|
|
11
|
+
.option("--limit <n>", "Max results", "25")
|
|
12
|
+
.option("--skip <n>", "Offset", "0")
|
|
13
|
+
.action(async (opts) => {
|
|
14
|
+
const params = {
|
|
15
|
+
limit: parseInt(opts.limit),
|
|
16
|
+
skip: parseInt(opts.skip),
|
|
17
|
+
};
|
|
18
|
+
if (opts.reservation)
|
|
19
|
+
params.reservationId = opts.reservation;
|
|
20
|
+
const data = await guestyFetch("/v1/communication/conversations", { params });
|
|
21
|
+
print(data);
|
|
22
|
+
});
|
|
23
|
+
conversations
|
|
24
|
+
.command("get <id>")
|
|
25
|
+
.description("Get a conversation and its posts")
|
|
26
|
+
.action(async (id) => {
|
|
27
|
+
const data = await guestyFetch(`/v1/communication/conversations/${id}/posts`);
|
|
28
|
+
print(data);
|
|
29
|
+
});
|
|
30
|
+
conversations
|
|
31
|
+
.command("send <conversationId>")
|
|
32
|
+
.description("Send a message in a conversation")
|
|
33
|
+
.requiredOption("--body <text>", "Message body")
|
|
34
|
+
.action(async (conversationId, opts) => {
|
|
35
|
+
const data = await guestyFetch(`/v1/communication/conversations/${conversationId}/send-message`, {
|
|
36
|
+
method: "POST",
|
|
37
|
+
body: { body: opts.body },
|
|
38
|
+
});
|
|
39
|
+
print(data);
|
|
40
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { guestyFetch } from "../client.js";
|
|
3
|
+
import { print } from "../output.js";
|
|
4
|
+
export const financials = new Command("financials")
|
|
5
|
+
.alias("fin")
|
|
6
|
+
.description("Financial data and accounting");
|
|
7
|
+
financials
|
|
8
|
+
.command("balance <reservationId>")
|
|
9
|
+
.description("Get folio balance for a reservation")
|
|
10
|
+
.action(async (id) => {
|
|
11
|
+
const data = await guestyFetch(`/v1/accounting-api/reservations/${id}/balance`);
|
|
12
|
+
print(data);
|
|
13
|
+
});
|
|
14
|
+
financials
|
|
15
|
+
.command("journal-entries")
|
|
16
|
+
.description("List journal entries")
|
|
17
|
+
.option("--from <date>", "From date (YYYY-MM-DD)")
|
|
18
|
+
.option("--to <date>", "To date (YYYY-MM-DD)")
|
|
19
|
+
.option("--listing <id>", "Filter by listing ID")
|
|
20
|
+
.option("--limit <n>", "Max results", "100")
|
|
21
|
+
.option("--skip <n>", "Offset", "0")
|
|
22
|
+
.action(async (opts) => {
|
|
23
|
+
const params = {
|
|
24
|
+
limit: parseInt(opts.limit),
|
|
25
|
+
skip: parseInt(opts.skip),
|
|
26
|
+
};
|
|
27
|
+
if (opts.from)
|
|
28
|
+
params.from = opts.from;
|
|
29
|
+
if (opts.to)
|
|
30
|
+
params.to = opts.to;
|
|
31
|
+
if (opts.listing)
|
|
32
|
+
params.listingId = opts.listing;
|
|
33
|
+
const data = await guestyFetch("/v1/accounting-api/journal-entries", { params });
|
|
34
|
+
print(data);
|
|
35
|
+
});
|
|
36
|
+
financials
|
|
37
|
+
.command("categories")
|
|
38
|
+
.description("List accounting categories")
|
|
39
|
+
.action(async () => {
|
|
40
|
+
const data = await guestyFetch("/v1/accounting-api/categories");
|
|
41
|
+
print(data);
|
|
42
|
+
});
|
|
43
|
+
financials
|
|
44
|
+
.command("owner-statement <listingId>")
|
|
45
|
+
.description("Get owner financial data for a listing")
|
|
46
|
+
.option("--from <date>", "From date (YYYY-MM-DD)")
|
|
47
|
+
.option("--to <date>", "To date (YYYY-MM-DD)")
|
|
48
|
+
.action(async (listingId, opts) => {
|
|
49
|
+
const params = {};
|
|
50
|
+
if (opts.from)
|
|
51
|
+
params.from = opts.from;
|
|
52
|
+
if (opts.to)
|
|
53
|
+
params.to = opts.to;
|
|
54
|
+
const data = await guestyFetch(`/v1/financials/listing/${listingId}`, { params });
|
|
55
|
+
print(data);
|
|
56
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { guestyFetch } from "../client.js";
|
|
3
|
+
import { print } from "../output.js";
|
|
4
|
+
import { readStdin } from "../stdin.js";
|
|
5
|
+
export const guests = new Command("guests")
|
|
6
|
+
.description("Manage guests");
|
|
7
|
+
guests
|
|
8
|
+
.command("list")
|
|
9
|
+
.description("List/search guests")
|
|
10
|
+
.option("--q <query>", "Search query (name, email, phone)")
|
|
11
|
+
.option("--limit <n>", "Max results", "25")
|
|
12
|
+
.option("--skip <n>", "Offset", "0")
|
|
13
|
+
.action(async (opts) => {
|
|
14
|
+
const params = {
|
|
15
|
+
limit: parseInt(opts.limit),
|
|
16
|
+
skip: parseInt(opts.skip),
|
|
17
|
+
};
|
|
18
|
+
if (opts.q)
|
|
19
|
+
params.q = opts.q;
|
|
20
|
+
const data = await guestyFetch("/v1/guests-crud", { params });
|
|
21
|
+
print(data);
|
|
22
|
+
});
|
|
23
|
+
guests
|
|
24
|
+
.command("get <id>")
|
|
25
|
+
.description("Get a single guest by ID")
|
|
26
|
+
.action(async (id) => {
|
|
27
|
+
const data = await guestyFetch(`/v1/guests-crud/${id}`);
|
|
28
|
+
print(data);
|
|
29
|
+
});
|
|
30
|
+
guests
|
|
31
|
+
.command("create")
|
|
32
|
+
.description("Create a guest (--data or stdin)")
|
|
33
|
+
.option("--data <json>", "JSON body")
|
|
34
|
+
.action(async (opts) => {
|
|
35
|
+
const body = opts.data
|
|
36
|
+
? JSON.parse(opts.data)
|
|
37
|
+
: JSON.parse(await readStdin());
|
|
38
|
+
const data = await guestyFetch("/v1/guests-crud", {
|
|
39
|
+
method: "POST",
|
|
40
|
+
body,
|
|
41
|
+
});
|
|
42
|
+
print(data);
|
|
43
|
+
});
|
|
44
|
+
guests
|
|
45
|
+
.command("update <id>")
|
|
46
|
+
.description("Update a guest (--data or stdin)")
|
|
47
|
+
.option("--data <json>", "JSON body")
|
|
48
|
+
.action(async (id, opts) => {
|
|
49
|
+
const body = opts.data
|
|
50
|
+
? JSON.parse(opts.data)
|
|
51
|
+
: JSON.parse(await readStdin());
|
|
52
|
+
const data = await guestyFetch(`/v1/guests-crud/${id}`, {
|
|
53
|
+
method: "PUT",
|
|
54
|
+
body,
|
|
55
|
+
});
|
|
56
|
+
print(data);
|
|
57
|
+
});
|