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.
Files changed (47) hide show
  1. package/dist/auth.d.ts +2 -0
  2. package/dist/auth.js +156 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +46 -0
  5. package/dist/client.d.ts +7 -0
  6. package/dist/client.js +109 -0
  7. package/dist/commands/accounting.d.ts +2 -0
  8. package/dist/commands/accounting.js +102 -0
  9. package/dist/commands/calendar.d.ts +2 -0
  10. package/dist/commands/calendar.js +60 -0
  11. package/dist/commands/conversations.d.ts +2 -0
  12. package/dist/commands/conversations.js +40 -0
  13. package/dist/commands/financials.d.ts +2 -0
  14. package/dist/commands/financials.js +56 -0
  15. package/dist/commands/guests.d.ts +2 -0
  16. package/dist/commands/guests.js +57 -0
  17. package/dist/commands/init.d.ts +2 -0
  18. package/dist/commands/init.js +109 -0
  19. package/dist/commands/integrations.d.ts +2 -0
  20. package/dist/commands/integrations.js +62 -0
  21. package/dist/commands/listings.d.ts +2 -0
  22. package/dist/commands/listings.js +72 -0
  23. package/dist/commands/owners.d.ts +2 -0
  24. package/dist/commands/owners.js +76 -0
  25. package/dist/commands/properties.d.ts +2 -0
  26. package/dist/commands/properties.js +104 -0
  27. package/dist/commands/quotes.d.ts +2 -0
  28. package/dist/commands/quotes.js +31 -0
  29. package/dist/commands/raw.d.ts +2 -0
  30. package/dist/commands/raw.js +19 -0
  31. package/dist/commands/reservations.d.ts +2 -0
  32. package/dist/commands/reservations.js +126 -0
  33. package/dist/commands/reviews.d.ts +2 -0
  34. package/dist/commands/reviews.js +39 -0
  35. package/dist/commands/tasks.d.ts +2 -0
  36. package/dist/commands/tasks.js +60 -0
  37. package/dist/commands/users.d.ts +2 -0
  38. package/dist/commands/users.js +59 -0
  39. package/dist/commands/webhooks.d.ts +2 -0
  40. package/dist/commands/webhooks.js +52 -0
  41. package/dist/env.d.ts +1 -0
  42. package/dist/env.js +44 -0
  43. package/dist/output.d.ts +2 -0
  44. package/dist/output.js +22 -0
  45. package/dist/stdin.d.ts +1 -0
  46. package/dist/stdin.js +12 -0
  47. package/package.json +41 -0
package/dist/auth.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export declare function invalidateToken(): void;
2
+ export declare function getToken(): Promise<string>;
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
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
+ });
@@ -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,2 @@
1
+ import { Command } from "commander";
2
+ export declare const accounting: Command;
@@ -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,2 @@
1
+ import { Command } from "commander";
2
+ export declare const calendar: Command;
@@ -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,2 @@
1
+ import { Command } from "commander";
2
+ export declare const conversations: Command;
@@ -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,2 @@
1
+ import { Command } from "commander";
2
+ export declare const financials: Command;
@@ -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,2 @@
1
+ import { Command } from "commander";
2
+ export declare const guests: Command;
@@ -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
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare const init: Command;