@dodo-planet/cli 0.1.4

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/index.js ADDED
@@ -0,0 +1,2598 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/lib/config.ts
12
+ import { mkdir, readFile, writeFile } from "fs/promises";
13
+ import { existsSync } from "fs";
14
+ import { homedir } from "os";
15
+ import { dirname, join } from "path";
16
+ function getConfigDir() {
17
+ const override = process.env.DODO_CONFIG_HOME;
18
+ if (override) return override;
19
+ const xdg = process.env.XDG_CONFIG_HOME;
20
+ if (xdg) return join(xdg, "dodo");
21
+ return join(homedir(), ".config", "dodo");
22
+ }
23
+ function getConfigPath() {
24
+ return join(getConfigDir(), "config.json");
25
+ }
26
+ async function ensureDir(path2) {
27
+ if (!existsSync(path2)) {
28
+ await mkdir(path2, { recursive: true, mode: 448 });
29
+ }
30
+ }
31
+ async function readConfig() {
32
+ const envApiUrl = process.env.DODO_API_URL?.trim();
33
+ const envLocale = process.env.DODO_LOCALE?.trim();
34
+ const envOutput = process.env.DODO_OUTPUT?.trim();
35
+ const path2 = getConfigPath();
36
+ let fileCfg = {};
37
+ if (existsSync(path2)) {
38
+ try {
39
+ fileCfg = JSON.parse(await readFile(path2, "utf8"));
40
+ } catch {
41
+ }
42
+ }
43
+ return {
44
+ apiUrl: envApiUrl || fileCfg.apiUrl || DEFAULT_API_URL,
45
+ locale: envLocale || fileCfg.locale,
46
+ output: (envOutput === "json" || envOutput === "pretty" ? envOutput : void 0) || fileCfg.output
47
+ };
48
+ }
49
+ async function writeConfig(config) {
50
+ await ensureDir(getConfigDir());
51
+ await writeFile(getConfigPath(), JSON.stringify(config, null, 2), {
52
+ mode: 384
53
+ });
54
+ }
55
+ async function setConfigValue(key, value) {
56
+ const cfg = await readConfig();
57
+ cfg[key] = value;
58
+ await writeConfig(cfg);
59
+ }
60
+ function detectLocale() {
61
+ const lang = process.env.LANG || process.env.LC_ALL || process.env.LC_MESSAGES;
62
+ if (!lang) return "en";
63
+ const code = lang.split(/[._]/)[0]?.toLowerCase();
64
+ if (code && ["ko", "en", "es", "fr"].includes(code)) return code;
65
+ return "en";
66
+ }
67
+ async function resolveLocale(flagLocale) {
68
+ if (flagLocale) return flagLocale;
69
+ const cfg = await readConfig();
70
+ if (cfg.locale) return cfg.locale;
71
+ return detectLocale();
72
+ }
73
+ var DEFAULT_API_URL;
74
+ var init_config = __esm({
75
+ "src/lib/config.ts"() {
76
+ "use strict";
77
+ DEFAULT_API_URL = "https://www.dodoplanet.space";
78
+ }
79
+ });
80
+
81
+ // src/lib/credentials.ts
82
+ import { readFile as readFile2, writeFile as writeFile2, unlink, chmod } from "fs/promises";
83
+ import { existsSync as existsSync2, statSync } from "fs";
84
+ import { join as join2 } from "path";
85
+ import { mkdir as mkdir2 } from "fs/promises";
86
+ function getCredentialsPath() {
87
+ return join2(getConfigDir(), "credentials.json");
88
+ }
89
+ async function getToken() {
90
+ const fromEnv = process.env.DODO_TOKEN;
91
+ if (fromEnv && fromEnv.trim()) return fromEnv.trim();
92
+ const path2 = getCredentialsPath();
93
+ if (!existsSync2(path2)) return null;
94
+ try {
95
+ const stat = statSync(path2);
96
+ const mode = stat.mode & 511;
97
+ if (mode !== 384 && process.env.DODO_STRICT_PERMS === "1") {
98
+ throw new Error(
99
+ `credentials.json \uAD8C\uD55C\uC774 \uC548\uC804\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (\uD604\uC7AC ${mode.toString(8)}, \uAD8C\uC7A5 600). chmod 600 \uC73C\uB85C \uC218\uC815\uD558\uC138\uC694.`
100
+ );
101
+ }
102
+ } catch {
103
+ }
104
+ try {
105
+ const raw = await readFile2(path2, "utf8");
106
+ const parsed = JSON.parse(raw);
107
+ return parsed.token || null;
108
+ } catch {
109
+ return null;
110
+ }
111
+ }
112
+ async function saveToken(token, travelerId) {
113
+ const dir = getConfigDir();
114
+ if (!existsSync2(dir)) {
115
+ await mkdir2(dir, { recursive: true, mode: 448 });
116
+ }
117
+ const payload = {
118
+ token,
119
+ travelerId,
120
+ savedAt: (/* @__PURE__ */ new Date()).toISOString()
121
+ };
122
+ const path2 = getCredentialsPath();
123
+ await writeFile2(path2, JSON.stringify(payload, null, 2), { mode: 384 });
124
+ await chmod(path2, 384);
125
+ }
126
+ async function clearToken() {
127
+ const path2 = getCredentialsPath();
128
+ if (existsSync2(path2)) {
129
+ await unlink(path2);
130
+ }
131
+ }
132
+ function isTokenFromEnv() {
133
+ return !!process.env.DODO_TOKEN?.trim();
134
+ }
135
+ var init_credentials = __esm({
136
+ "src/lib/credentials.ts"() {
137
+ "use strict";
138
+ init_config();
139
+ }
140
+ });
141
+
142
+ // src/lib/exit-codes.ts
143
+ function exitCodeForStatus(status2) {
144
+ if (status2 === 401) return ExitCode.Unauthorized;
145
+ if (status2 === 403) return ExitCode.Forbidden;
146
+ if (status2 === 412) return ExitCode.ConfirmRequired;
147
+ if (status2 === 400) return ExitCode.InvalidArgs;
148
+ if (status2 >= 500) return ExitCode.Network;
149
+ return ExitCode.GeneralError;
150
+ }
151
+ var ExitCode;
152
+ var init_exit_codes = __esm({
153
+ "src/lib/exit-codes.ts"() {
154
+ "use strict";
155
+ ExitCode = {
156
+ Ok: 0,
157
+ GeneralError: 1,
158
+ InvalidArgs: 2,
159
+ Network: 3,
160
+ Unauthorized: 4,
161
+ Forbidden: 5,
162
+ ConfirmRequired: 6
163
+ };
164
+ }
165
+ });
166
+
167
+ // src/lib/api-client.ts
168
+ function buildUrl(base, path2, query) {
169
+ const url = new URL(path2, base);
170
+ if (query) {
171
+ for (const [k, v] of Object.entries(query)) {
172
+ if (v !== void 0 && v !== "") url.searchParams.set(k, v);
173
+ }
174
+ }
175
+ return url.toString();
176
+ }
177
+ async function apiRequest(path2, opts = {}) {
178
+ const cfg = await readConfig();
179
+ const url = buildUrl(cfg.apiUrl, path2, opts.query);
180
+ const headers = {
181
+ accept: "application/json",
182
+ ...opts.headers
183
+ };
184
+ if (!opts.unauthenticated) {
185
+ const token = await getToken();
186
+ if (!token) {
187
+ throw new ApiError(
188
+ "\uC778\uC99D\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. `dodo auth login` \uC73C\uB85C \uD1A0\uD070\uC744 \uB4F1\uB85D\uD558\uC138\uC694.",
189
+ 401,
190
+ ExitCode.Unauthorized
191
+ );
192
+ }
193
+ headers.authorization = `Bearer ${token}`;
194
+ }
195
+ if (opts.body !== void 0) {
196
+ headers["content-type"] = "application/json";
197
+ }
198
+ let response;
199
+ try {
200
+ response = await fetch(url, {
201
+ method: opts.method ?? "GET",
202
+ headers,
203
+ body: opts.body !== void 0 ? JSON.stringify(opts.body) : void 0
204
+ });
205
+ } catch (err) {
206
+ const msg = err instanceof Error ? err.message : "network error";
207
+ throw new ApiError(`\uC11C\uBC84\uC5D0 \uC5F0\uACB0\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${msg}`, 0, ExitCode.Network);
208
+ }
209
+ const text3 = await response.text();
210
+ let parsed = text3;
211
+ try {
212
+ parsed = text3 ? JSON.parse(text3) : null;
213
+ } catch {
214
+ }
215
+ if (!response.ok) {
216
+ const errorMsg = (parsed && typeof parsed === "object" && "error" in parsed && typeof parsed.error === "string" ? parsed.error : null) || `HTTP ${response.status}`;
217
+ throw new ApiError(errorMsg, response.status, exitCodeForStatus(response.status), parsed);
218
+ }
219
+ return parsed;
220
+ }
221
+ async function executeFunction(name, args = {}, options = {}) {
222
+ return apiRequest("/api/cli/execute", {
223
+ method: "POST",
224
+ body: {
225
+ name,
226
+ args,
227
+ tripId: options.tripId,
228
+ locale: options.locale
229
+ },
230
+ headers: options.confirmDestructive ? { "x-dodo-confirm": "yes" } : void 0
231
+ });
232
+ }
233
+ var ApiError;
234
+ var init_api_client = __esm({
235
+ "src/lib/api-client.ts"() {
236
+ "use strict";
237
+ init_config();
238
+ init_credentials();
239
+ init_exit_codes();
240
+ ApiError = class extends Error {
241
+ constructor(message, status2, exitCode, body) {
242
+ super(message);
243
+ this.status = status2;
244
+ this.exitCode = exitCode;
245
+ this.body = body;
246
+ this.name = "ApiError";
247
+ }
248
+ status;
249
+ exitCode;
250
+ body;
251
+ };
252
+ }
253
+ });
254
+
255
+ // src/lib/output.ts
256
+ import pc from "picocolors";
257
+ import Table from "cli-table3";
258
+ function resolveOutputMode(flag, configured) {
259
+ if (flag === "json") return "json";
260
+ if (flag === "pretty") return "pretty";
261
+ if (configured) return configured;
262
+ return process.stdout.isTTY ? "pretty" : "json";
263
+ }
264
+ function shouldUseColor(mode) {
265
+ if (mode === "json") return false;
266
+ if (process.env.NO_COLOR) return false;
267
+ if (process.env.CI) return false;
268
+ return !!process.stdout.isTTY;
269
+ }
270
+ function getOutputContext(flag, configured) {
271
+ const mode = resolveOutputMode(flag, configured);
272
+ return { mode, color: shouldUseColor(mode) };
273
+ }
274
+ async function resolveOutputContext(flag) {
275
+ const cfg = await readConfig();
276
+ return getOutputContext(flag, cfg.output);
277
+ }
278
+ function emitJsonOk(data, meta) {
279
+ const payload = { ok: true, data };
280
+ if (meta) payload.meta = meta;
281
+ process.stdout.write(JSON.stringify(payload) + "\n");
282
+ }
283
+ function emitJsonError(code, message, hint) {
284
+ const payload = { ok: false, error: { code, message, ...hint ? { hint } : {} } };
285
+ process.stderr.write(JSON.stringify(payload) + "\n");
286
+ }
287
+ function colorize(ctx) {
288
+ if (ctx.color) {
289
+ return {
290
+ bold: pc.bold,
291
+ dim: pc.dim,
292
+ green: pc.green,
293
+ red: pc.red,
294
+ yellow: pc.yellow,
295
+ cyan: pc.cyan,
296
+ orange: (s) => pc.yellow(s)
297
+ // picocolors는 orange가 없어 yellow로 대체
298
+ };
299
+ }
300
+ const noop = (s) => s;
301
+ return {
302
+ bold: noop,
303
+ dim: noop,
304
+ green: noop,
305
+ red: noop,
306
+ yellow: noop,
307
+ cyan: noop,
308
+ orange: noop
309
+ };
310
+ }
311
+ function printTable(headers, rows, ctx) {
312
+ const useColor = ctx?.color ?? shouldUseColor("pretty");
313
+ const tbl = new Table({
314
+ head: useColor ? headers.map((h) => pc.bold(h)) : headers,
315
+ style: { head: [], border: [] },
316
+ wordWrap: true
317
+ });
318
+ for (const row of rows) tbl.push(row.map((c) => String(c)));
319
+ process.stdout.write(tbl.toString() + "\n");
320
+ }
321
+ function printLine(text3, mode = "pretty") {
322
+ if (mode === "pretty") process.stdout.write(text3 + "\n");
323
+ }
324
+ function printError(text3, ctx) {
325
+ const useColor = ctx?.color ?? shouldUseColor("pretty");
326
+ const prefix = useColor ? pc.red("Error:") : "Error:";
327
+ process.stderr.write(`${prefix} ${text3}
328
+ `);
329
+ }
330
+ var init_output = __esm({
331
+ "src/lib/output.ts"() {
332
+ "use strict";
333
+ init_config();
334
+ }
335
+ });
336
+
337
+ // src/lib/prompt.ts
338
+ import * as p from "@clack/prompts";
339
+ function isInteractive() {
340
+ return !!process.stdout.isTTY && !!process.stdin.isTTY;
341
+ }
342
+ async function confirm2(options) {
343
+ if (options.yes) return true;
344
+ if (!isInteractive()) return false;
345
+ const result = await p.confirm({
346
+ message: options.message,
347
+ initialValue: options.initialValue ?? false
348
+ });
349
+ if (p.isCancel(result)) return false;
350
+ return result;
351
+ }
352
+ async function text2(message, options = {}) {
353
+ if (!isInteractive()) return null;
354
+ const validate = options.validate;
355
+ const result = await p.text({
356
+ message,
357
+ placeholder: options.placeholder,
358
+ initialValue: options.initialValue,
359
+ validate: validate ? (v) => validate(v ?? "") : void 0
360
+ });
361
+ if (p.isCancel(result)) return null;
362
+ return result;
363
+ }
364
+ async function password2(message) {
365
+ if (!isInteractive()) return null;
366
+ const result = await p.password({ message });
367
+ if (p.isCancel(result)) return null;
368
+ return result;
369
+ }
370
+ var init_prompt = __esm({
371
+ "src/lib/prompt.ts"() {
372
+ "use strict";
373
+ }
374
+ });
375
+
376
+ // src/lib/state.ts
377
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
378
+ import { existsSync as existsSync3 } from "fs";
379
+ import { join as join3 } from "path";
380
+ function getStatePath() {
381
+ return join3(getConfigDir(), "state.json");
382
+ }
383
+ async function readState() {
384
+ const path2 = getStatePath();
385
+ if (!existsSync3(path2)) return {};
386
+ try {
387
+ const raw = await readFile3(path2, "utf8");
388
+ return JSON.parse(raw);
389
+ } catch {
390
+ return {};
391
+ }
392
+ }
393
+ async function setActiveTrip(trip) {
394
+ const dir = getConfigDir();
395
+ if (!existsSync3(dir)) await mkdir3(dir, { recursive: true, mode: 448 });
396
+ const state = await readState();
397
+ state.activeTrip = trip;
398
+ await writeFile3(getStatePath(), JSON.stringify(state, null, 2), { mode: 384 });
399
+ }
400
+ async function getActiveTripId(flagTrip) {
401
+ if (flagTrip) {
402
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(flagTrip)) {
403
+ return flagTrip;
404
+ }
405
+ return void 0;
406
+ }
407
+ const state = await readState();
408
+ return state.activeTrip?.id;
409
+ }
410
+ var init_state = __esm({
411
+ "src/lib/state.ts"() {
412
+ "use strict";
413
+ init_config();
414
+ }
415
+ });
416
+
417
+ // src/lib/function-catalog-shared.ts
418
+ function getFunctionMeta(name) {
419
+ return CATALOG_BY_NAME.get(name);
420
+ }
421
+ var FUNCTION_CATALOG, CATALOG_BY_NAME;
422
+ var init_function_catalog_shared = __esm({
423
+ "src/lib/function-catalog-shared.ts"() {
424
+ "use strict";
425
+ FUNCTION_CATALOG = [
426
+ // ── 외부 검색 / 정보 조회 (read-only, 11개)
427
+ { name: "get_weather", category: "weather", verb: "show", requiresTrip: false, isWrite: false, isDestructive: false },
428
+ { name: "get_flight_status", category: "flight", verb: "status", requiresTrip: false, isWrite: false, isDestructive: false },
429
+ { name: "search_nearby_places", category: "place", verb: "search", requiresTrip: false, isWrite: false, isDestructive: false },
430
+ { name: "search_places_text", category: "place", verb: "search-text", requiresTrip: false, isWrite: false, isDestructive: false },
431
+ { name: "get_place_details", category: "place", verb: "details", requiresTrip: false, isWrite: false, isDestructive: false },
432
+ { name: "get_directions", category: "directions", verb: "show", requiresTrip: false, isWrite: false, isDestructive: false },
433
+ { name: "search_flights", category: "flight", verb: "search", requiresTrip: false, isWrite: false, isDestructive: false },
434
+ { name: "search_hotels", category: "hotel", verb: "search", requiresTrip: true, isWrite: false, isDestructive: false },
435
+ { name: "search_transfers", category: "transfer", verb: "search", requiresTrip: true, isWrite: false, isDestructive: false },
436
+ { name: "search_activities", category: "activity", verb: "search", requiresTrip: true, isWrite: false, isDestructive: false },
437
+ { name: "search_web_perplexity", category: "web", verb: "search", requiresTrip: false, isWrite: false, isDestructive: false },
438
+ // ── Trip 관리 (4개)
439
+ { name: "list_trips", category: "trip", verb: "list", requiresTrip: false, isWrite: false, isDestructive: false },
440
+ { name: "get_current_trip", category: "trip", verb: "current", requiresTrip: false, isWrite: false, isDestructive: false },
441
+ { name: "switch_trip", category: "trip", verb: "switch", requiresTrip: false, isWrite: true, isDestructive: false },
442
+ { name: "create_trip", category: "trip", verb: "create", requiresTrip: false, isWrite: true, isDestructive: false },
443
+ // ── 경비 (4개)
444
+ { name: "get_expenses", category: "expense", verb: "list", requiresTrip: true, isWrite: false, isDestructive: false },
445
+ { name: "add_expense", category: "expense", verb: "add", requiresTrip: true, isWrite: true, isDestructive: false },
446
+ { name: "update_expense", category: "expense", verb: "update", requiresTrip: true, isWrite: true, isDestructive: false },
447
+ { name: "delete_expense", category: "expense", verb: "delete", requiresTrip: true, isWrite: true, isDestructive: true },
448
+ // ── 예약 (4개)
449
+ { name: "get_bookings", category: "booking", verb: "list", requiresTrip: true, isWrite: false, isDestructive: false },
450
+ { name: "add_booking", category: "booking", verb: "add", requiresTrip: true, isWrite: true, isDestructive: false },
451
+ { name: "update_booking", category: "booking", verb: "update", requiresTrip: true, isWrite: true, isDestructive: false },
452
+ { name: "delete_booking", category: "booking", verb: "delete", requiresTrip: true, isWrite: true, isDestructive: true },
453
+ // ── 일정 (4개)
454
+ { name: "get_itinerary", category: "itinerary", verb: "show", requiresTrip: true, isWrite: false, isDestructive: false },
455
+ { name: "add_place_to_itinerary", category: "itinerary", verb: "add", requiresTrip: true, isWrite: true, isDestructive: false },
456
+ { name: "remove_place_from_itinerary", category: "itinerary", verb: "remove", requiresTrip: true, isWrite: true, isDestructive: true },
457
+ { name: "reorder_itinerary", category: "itinerary", verb: "reorder", requiresTrip: true, isWrite: true, isDestructive: false },
458
+ // ── 피드 (3개)
459
+ { name: "get_feed", category: "feed", verb: "list", requiresTrip: true, isWrite: false, isDestructive: false },
460
+ { name: "add_feed_post", category: "feed", verb: "post", requiresTrip: true, isWrite: true, isDestructive: false },
461
+ { name: "delete_feed_post", category: "feed", verb: "delete", requiresTrip: true, isWrite: true, isDestructive: true },
462
+ // ── 아이 정보 (7개)
463
+ { name: "list_babies", category: "baby", verb: "list", requiresTrip: false, isWrite: false, isDestructive: false },
464
+ { name: "get_baby_info", category: "baby", verb: "info", requiresTrip: false, isWrite: false, isDestructive: false },
465
+ { name: "add_baby", category: "baby", verb: "add", requiresTrip: false, isWrite: true, isDestructive: false },
466
+ { name: "update_baby_info", category: "baby", verb: "update", requiresTrip: false, isWrite: true, isDestructive: false },
467
+ { name: "update_baby_status", category: "baby", verb: "status", requiresTrip: true, isWrite: true, isDestructive: false },
468
+ { name: "add_baby_travel_note", category: "baby", verb: "note", requiresTrip: true, isWrite: true, isDestructive: false },
469
+ { name: "delete_baby", category: "baby", verb: "delete", requiresTrip: false, isWrite: true, isDestructive: true },
470
+ // ── 가족 / 친구 / 초대 (6개)
471
+ { name: "get_family_members", category: "family", verb: "list", requiresTrip: false, isWrite: false, isDestructive: false },
472
+ { name: "add_family_to_trip", category: "family", verb: "add-to-trip", requiresTrip: true, isWrite: true, isDestructive: false },
473
+ { name: "get_friends", category: "friend", verb: "list", requiresTrip: false, isWrite: false, isDestructive: false },
474
+ { name: "add_friend", category: "friend", verb: "add", requiresTrip: false, isWrite: true, isDestructive: false },
475
+ { name: "get_invitations", category: "invite", verb: "list", requiresTrip: true, isWrite: false, isDestructive: false },
476
+ { name: "invite_to_trip", category: "invite", verb: "create", requiresTrip: true, isWrite: true, isDestructive: false }
477
+ ];
478
+ CATALOG_BY_NAME = new Map(
479
+ FUNCTION_CATALOG.map((meta) => [meta.name, meta])
480
+ );
481
+ }
482
+ });
483
+
484
+ // src/lib/command-helpers.ts
485
+ var command_helpers_exports = {};
486
+ __export(command_helpers_exports, {
487
+ ApiError: () => ApiError,
488
+ ExitCode: () => ExitCode,
489
+ colorize: () => colorize,
490
+ printLine: () => printLine,
491
+ runFunctionCommand: () => runFunctionCommand,
492
+ unwrap: () => unwrap,
493
+ unwrapMessage: () => unwrapMessage
494
+ });
495
+ async function runFunctionCommand(opts) {
496
+ const ctx = await resolveOutputContext(opts.outputFlag);
497
+ const locale = await resolveLocale(opts.localeFlag);
498
+ const meta = getFunctionMeta(opts.function);
499
+ if (!meta) {
500
+ const msg = `\uD568\uC218 \uCE74\uD0C8\uB85C\uADF8\uC5D0 ${opts.function}\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 (CLI \uBC84\uADF8).`;
501
+ if (ctx.mode === "json") emitJsonError("catalog_missing", msg);
502
+ else printError(msg, ctx);
503
+ process.exit(ExitCode.GeneralError);
504
+ }
505
+ let tripId;
506
+ if (!opts.noTripContext) {
507
+ tripId = await getActiveTripId(opts.tripFlag);
508
+ if (!tripId && !opts.tripFlag) {
509
+ const state = await readState();
510
+ tripId = state.activeTrip?.id;
511
+ }
512
+ if (meta.requiresTrip && !tripId) {
513
+ const msg = `${opts.function}\uB294 \uD65C\uC131 trip\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.`;
514
+ if (ctx.mode === "json") emitJsonError("no_active_trip", msg, "dodo trip switch <name-or-slug>");
515
+ else printError(msg, ctx);
516
+ process.exit(ExitCode.InvalidArgs);
517
+ }
518
+ }
519
+ if (meta.isDestructive && !opts.yesFlag) {
520
+ const ok = await confirm2({
521
+ message: `\uC815\uB9D0 ${opts.function}\uB97C \uC2E4\uD589\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`,
522
+ yes: false
523
+ });
524
+ if (!ok) {
525
+ const msg = "\uC0AC\uC6A9\uC790\uAC00 confirm\uC744 \uAC70\uBD80\uD588\uAC70\uB098 non-TTY \uD658\uACBD\uC785\uB2C8\uB2E4. --yes \uD50C\uB798\uADF8\uB97C \uC0AC\uC6A9\uD558\uC138\uC694.";
526
+ if (ctx.mode === "json") emitJsonError("confirm_required", msg);
527
+ else printError(msg, ctx);
528
+ process.exit(ExitCode.ConfirmRequired);
529
+ }
530
+ }
531
+ try {
532
+ const result = await executeFunction(opts.function, opts.args, {
533
+ tripId,
534
+ locale,
535
+ confirmDestructive: meta.isDestructive
536
+ });
537
+ if (ctx.mode === "json") {
538
+ emitJsonOk(result);
539
+ } else {
540
+ await opts.formatResponse(result, ctx);
541
+ }
542
+ } catch (err) {
543
+ if (err instanceof ApiError) {
544
+ if (ctx.mode === "json") emitJsonError(`${opts.function}_failed`, err.message);
545
+ else printError(err.message, ctx);
546
+ process.exit(err.exitCode);
547
+ }
548
+ throw err;
549
+ }
550
+ }
551
+ function unwrap(result, key) {
552
+ if (typeof result !== "object" || result === null) return void 0;
553
+ const r = result;
554
+ if (key in r) return r[key];
555
+ if ("data" in r && typeof r.data === "object" && r.data !== null && key in r.data) {
556
+ return r.data[key];
557
+ }
558
+ return void 0;
559
+ }
560
+ function unwrapMessage(result) {
561
+ if (typeof result !== "object" || result === null) return void 0;
562
+ const r = result;
563
+ return r.message;
564
+ }
565
+ var init_command_helpers = __esm({
566
+ "src/lib/command-helpers.ts"() {
567
+ "use strict";
568
+ init_api_client();
569
+ init_exit_codes();
570
+ init_output();
571
+ init_config();
572
+ init_state();
573
+ init_function_catalog_shared();
574
+ init_prompt();
575
+ }
576
+ });
577
+
578
+ // src/index.ts
579
+ import { defineCommand as defineCommand16, runMain } from "citty";
580
+
581
+ // src/commands/auth.ts
582
+ init_credentials();
583
+ init_api_client();
584
+ init_exit_codes();
585
+ init_output();
586
+ init_prompt();
587
+ import { defineCommand } from "citty";
588
+ var login = defineCommand({
589
+ meta: { name: "login", description: "PAT\uC744 \uB4F1\uB85D\uD558\uC5EC \uB85C\uADF8\uC778\uD569\uB2C8\uB2E4." },
590
+ args: {
591
+ token: {
592
+ type: "string",
593
+ description: "PAT \uD3C9\uBB38. \uBBF8\uC9C0\uC815 \uC2DC stdin\uC5D0\uC11C prompt (TTY \uC804\uC6A9)."
594
+ },
595
+ output: { type: "string", description: "pretty | json" }
596
+ },
597
+ async run({ args }) {
598
+ const ctx = await resolveOutputContext(args.output);
599
+ let token = args.token;
600
+ if (!token) {
601
+ if (!isInteractive()) {
602
+ printError("--token \uD50C\uB798\uADF8\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4 (non-TTY \uD658\uACBD).", ctx);
603
+ process.exit(ExitCode.InvalidArgs);
604
+ }
605
+ const result = await password2("PAT \uD3C9\uBB38\uC744 \uC785\uB825\uD558\uC138\uC694 (dodo_pat_...)");
606
+ if (!result) process.exit(ExitCode.GeneralError);
607
+ token = result;
608
+ }
609
+ if (!token.startsWith("dodo_pat_")) {
610
+ const msg = "\uD1A0\uD070 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. dodo_pat_ \uC73C\uB85C \uC2DC\uC791\uD574\uC57C \uD569\uB2C8\uB2E4.";
611
+ if (ctx.mode === "json") emitJsonError("invalid_token_format", msg);
612
+ else printError(msg, ctx);
613
+ process.exit(ExitCode.InvalidArgs);
614
+ }
615
+ try {
616
+ process.env.DODO_TOKEN = token;
617
+ const info2 = await apiRequest("/api/cli/auth/exchange", { method: "POST", body: {} });
618
+ delete process.env.DODO_TOKEN;
619
+ await saveToken(token, info2.traveler.id);
620
+ if (ctx.mode === "json") {
621
+ emitJsonOk({ traveler: info2.traveler });
622
+ } else {
623
+ const c = colorize(ctx);
624
+ printLine(c.green(`\u2713 \uB85C\uADF8\uC778 \uC131\uACF5`));
625
+ printLine(` ${c.dim("\uC774\uB984:")} ${info2.traveler.name}`);
626
+ if (info2.traveler.email) printLine(` ${c.dim("\uC774\uBA54\uC77C:")} ${info2.traveler.email}`);
627
+ }
628
+ } catch (err) {
629
+ delete process.env.DODO_TOKEN;
630
+ if (err instanceof ApiError) {
631
+ if (ctx.mode === "json") emitJsonError("auth_failed", err.message);
632
+ else printError(`\uD1A0\uD070 \uAC80\uC99D \uC2E4\uD328: ${err.message}`, ctx);
633
+ process.exit(err.exitCode);
634
+ }
635
+ throw err;
636
+ }
637
+ }
638
+ });
639
+ var logout = defineCommand({
640
+ meta: { name: "logout", description: "\uC800\uC7A5\uB41C PAT\uC744 \uC0AD\uC81C\uD569\uB2C8\uB2E4." },
641
+ args: { output: { type: "string", description: "pretty | json" } },
642
+ async run({ args }) {
643
+ const ctx = await resolveOutputContext(args.output);
644
+ if (isTokenFromEnv()) {
645
+ const msg = "DODO_TOKEN \uD658\uACBD\uBCC0\uC218\uB85C \uC778\uC99D \uC911\uC785\uB2C8\uB2E4. \uC178\uC5D0\uC11C \uD574\uB2F9 \uBCC0\uC218\uB97C unset \uD558\uC138\uC694.";
646
+ if (ctx.mode === "json") emitJsonError("env_token_active", msg);
647
+ else printError(msg, ctx);
648
+ process.exit(ExitCode.InvalidArgs);
649
+ }
650
+ await clearToken();
651
+ if (ctx.mode === "json") emitJsonOk({ logged_out: true });
652
+ else printLine(colorize(ctx).green("\u2713 \uB85C\uADF8\uC544\uC6C3\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
653
+ }
654
+ });
655
+ var whoami = defineCommand({
656
+ meta: { name: "whoami", description: "\uD604\uC7AC \uB85C\uADF8\uC778\uB41C traveler \uC815\uBCF4\uB97C \uD45C\uC2DC\uD569\uB2C8\uB2E4." },
657
+ args: { output: { type: "string", description: "pretty | json" } },
658
+ async run({ args }) {
659
+ const ctx = await resolveOutputContext(args.output);
660
+ const token = await getToken();
661
+ if (!token) {
662
+ const msg = "\uB85C\uADF8\uC778\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.";
663
+ if (ctx.mode === "json") emitJsonError("not_authenticated", msg, "dodo auth login \uC744 \uC2E4\uD589\uD558\uC138\uC694.");
664
+ else printError(msg, ctx);
665
+ process.exit(ExitCode.Unauthorized);
666
+ }
667
+ try {
668
+ const info2 = await apiRequest("/api/cli/auth/exchange", { method: "POST", body: {} });
669
+ if (ctx.mode === "json") {
670
+ emitJsonOk({ traveler: info2.traveler, via: info2.via });
671
+ } else {
672
+ const c = colorize(ctx);
673
+ printLine(c.bold(info2.traveler.name));
674
+ if (info2.traveler.email) printLine(c.dim(info2.traveler.email));
675
+ printLine(c.dim(`\uC778\uC99D: ${info2.via === "pat" ? "PAT" : "\uCFE0\uD0A4 \uC138\uC158"}`));
676
+ }
677
+ } catch (err) {
678
+ if (err instanceof ApiError) {
679
+ if (ctx.mode === "json") emitJsonError("auth_check_failed", err.message);
680
+ else printError(err.message, ctx);
681
+ process.exit(err.exitCode);
682
+ }
683
+ throw err;
684
+ }
685
+ }
686
+ });
687
+ var tokensList = defineCommand({
688
+ meta: { name: "list", description: "\uBCF8\uC778 PAT \uBAA9\uB85D\uC744 \uD45C\uC2DC\uD569\uB2C8\uB2E4." },
689
+ args: { output: { type: "string", description: "pretty | json" } },
690
+ async run({ args }) {
691
+ const ctx = await resolveOutputContext(args.output);
692
+ try {
693
+ const data = await apiRequest("/api/account/tokens");
694
+ if (ctx.mode === "json") {
695
+ emitJsonOk(data);
696
+ return;
697
+ }
698
+ const c = colorize(ctx);
699
+ if (!data.tokens.length) {
700
+ printLine(c.dim("\uBC1C\uAE09\uB41C \uD1A0\uD070\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
701
+ return;
702
+ }
703
+ printTable(
704
+ ["ID", "\uC774\uB984", "Prefix", "\uB9C8\uC9C0\uB9C9 \uC0AC\uC6A9", "\uB9CC\uB8CC"],
705
+ data.tokens.map((t) => [
706
+ t.id.slice(0, 8),
707
+ t.name,
708
+ `\u2022\u2022\u2022\u2022${t.token_prefix}`,
709
+ t.last_used_at ? new Date(t.last_used_at).toLocaleString() : c.dim("\uBBF8\uC0AC\uC6A9"),
710
+ t.expires_at ? new Date(t.expires_at).toLocaleDateString() : c.dim("\uBB34\uAE30\uD55C")
711
+ ]),
712
+ ctx
713
+ );
714
+ } catch (err) {
715
+ if (err instanceof ApiError) {
716
+ if (ctx.mode === "json") emitJsonError("list_failed", err.message);
717
+ else printError(err.message, ctx);
718
+ process.exit(err.exitCode);
719
+ }
720
+ throw err;
721
+ }
722
+ }
723
+ });
724
+ var tokensRevoke = defineCommand({
725
+ meta: { name: "revoke", description: "\uBCF8\uC778 PAT\uC744 \uCDE8\uC18C\uD569\uB2C8\uB2E4." },
726
+ args: {
727
+ id: { type: "positional", description: "\uD1A0\uD070 ID (\uD639\uC740 ID prefix 8\uC790)", required: true },
728
+ output: { type: "string", description: "pretty | json" }
729
+ },
730
+ async run({ args }) {
731
+ const ctx = await resolveOutputContext(args.output);
732
+ try {
733
+ let fullId = args.id;
734
+ if (fullId.length < 36) {
735
+ const list6 = await apiRequest("/api/account/tokens");
736
+ const match = list6.tokens.find((t) => t.id.startsWith(fullId));
737
+ if (!match) {
738
+ const msg = `\uD1A0\uD070\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${args.id}`;
739
+ if (ctx.mode === "json") emitJsonError("not_found", msg);
740
+ else printError(msg, ctx);
741
+ process.exit(ExitCode.GeneralError);
742
+ }
743
+ fullId = match.id;
744
+ }
745
+ await apiRequest(`/api/account/tokens/${fullId}`, { method: "DELETE" });
746
+ if (ctx.mode === "json") emitJsonOk({ revoked: fullId });
747
+ else printLine(colorize(ctx).green(`\u2713 \uD1A0\uD070\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`));
748
+ } catch (err) {
749
+ if (err instanceof ApiError) {
750
+ if (ctx.mode === "json") emitJsonError("revoke_failed", err.message);
751
+ else printError(err.message, ctx);
752
+ process.exit(err.exitCode);
753
+ }
754
+ throw err;
755
+ }
756
+ }
757
+ });
758
+ var tokens = defineCommand({
759
+ meta: { name: "tokens", description: "PAT \uD1A0\uD070 \uAD00\uB9AC." },
760
+ subCommands: { list: tokensList, revoke: tokensRevoke }
761
+ });
762
+ var authCommand = defineCommand({
763
+ meta: { name: "auth", description: "\uC778\uC99D \uAD00\uB9AC (login/logout/whoami/tokens)." },
764
+ subCommands: { login, logout, whoami, tokens }
765
+ });
766
+
767
+ // src/commands/config.ts
768
+ init_config();
769
+ init_exit_codes();
770
+ init_output();
771
+ import { defineCommand as defineCommand2 } from "citty";
772
+ var ALLOWED_KEYS = ["apiUrl", "locale", "output"];
773
+ function isAllowedKey(key) {
774
+ return ALLOWED_KEYS.includes(key);
775
+ }
776
+ var get = defineCommand2({
777
+ meta: { name: "get", description: "\uC124\uC815 \uAC12\uC744 \uC870\uD68C\uD569\uB2C8\uB2E4. key \uBBF8\uC9C0\uC815 \uC2DC \uC804\uCCB4 \uCD9C\uB825." },
778
+ args: {
779
+ key: { type: "positional", description: "apiUrl | locale | output", required: false },
780
+ output: { type: "string", description: "pretty | json" }
781
+ },
782
+ async run({ args }) {
783
+ const ctx = await resolveOutputContext(args.output);
784
+ const cfg = await readConfig();
785
+ if (!args.key) {
786
+ if (ctx.mode === "json") emitJsonOk(cfg);
787
+ else for (const [k, v] of Object.entries(cfg)) printLine(`${colorize(ctx).dim(k)} = ${v ?? ""}`);
788
+ return;
789
+ }
790
+ if (!isAllowedKey(args.key)) {
791
+ const msg = `\uD5C8\uC6A9\uB41C key\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (\uC0AC\uC6A9 \uAC00\uB2A5: ${ALLOWED_KEYS.join(", ")})`;
792
+ if (ctx.mode === "json") emitJsonError("invalid_key", msg);
793
+ else printError(msg, ctx);
794
+ process.exit(ExitCode.InvalidArgs);
795
+ }
796
+ const value = cfg[args.key];
797
+ if (ctx.mode === "json") emitJsonOk({ [args.key]: value ?? null });
798
+ else printLine(value !== void 0 ? String(value) : "");
799
+ }
800
+ });
801
+ var set = defineCommand2({
802
+ meta: { name: "set", description: "\uC124\uC815 \uAC12\uC744 \uAC31\uC2E0\uD569\uB2C8\uB2E4." },
803
+ args: {
804
+ key: { type: "positional", description: "apiUrl | locale | output", required: true },
805
+ value: { type: "positional", description: "\uAC12", required: true },
806
+ output: { type: "string", description: "pretty | json" }
807
+ },
808
+ async run({ args }) {
809
+ const ctx = await resolveOutputContext(args.output);
810
+ if (!isAllowedKey(args.key)) {
811
+ const msg = `\uD5C8\uC6A9\uB41C key\uAC00 \uC544\uB2D9\uB2C8\uB2E4. (\uC0AC\uC6A9 \uAC00\uB2A5: ${ALLOWED_KEYS.join(", ")})`;
812
+ if (ctx.mode === "json") emitJsonError("invalid_key", msg);
813
+ else printError(msg, ctx);
814
+ process.exit(ExitCode.InvalidArgs);
815
+ }
816
+ if (args.key === "output" && args.value !== "pretty" && args.value !== "json") {
817
+ const msg = "output\uC740 pretty \uB610\uB294 json\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.";
818
+ if (ctx.mode === "json") emitJsonError("invalid_value", msg);
819
+ else printError(msg, ctx);
820
+ process.exit(ExitCode.InvalidArgs);
821
+ }
822
+ if (args.key === "locale" && !["ko", "en", "es", "fr"].includes(args.value)) {
823
+ const msg = "locale\uC740 ko/en/es/fr \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.";
824
+ if (ctx.mode === "json") emitJsonError("invalid_value", msg);
825
+ else printError(msg, ctx);
826
+ process.exit(ExitCode.InvalidArgs);
827
+ }
828
+ await setConfigValue(args.key, args.value);
829
+ if (ctx.mode === "json") emitJsonOk({ [args.key]: args.value });
830
+ else printLine(colorize(ctx).green(`\u2713 ${args.key} = ${args.value}`));
831
+ }
832
+ });
833
+ var path = defineCommand2({
834
+ meta: { name: "path", description: "\uC124\uC815 \uB514\uB809\uD1A0\uB9AC \uACBD\uB85C \uCD9C\uB825." },
835
+ args: { output: { type: "string", description: "pretty | json" } },
836
+ async run({ args }) {
837
+ const ctx = await resolveOutputContext(args.output);
838
+ if (ctx.mode === "json") {
839
+ emitJsonOk({ dir: getConfigDir(), file: getConfigPath() });
840
+ } else {
841
+ printLine(getConfigDir());
842
+ }
843
+ }
844
+ });
845
+ var configCommand = defineCommand2({
846
+ meta: { name: "config", description: "CLI \uC124\uC815 \uAD00\uB9AC (apiUrl, locale, output)." },
847
+ subCommands: { get, set, path }
848
+ });
849
+
850
+ // src/commands/trip.ts
851
+ init_api_client();
852
+ init_exit_codes();
853
+ init_output();
854
+ init_config();
855
+ init_state();
856
+ import { defineCommand as defineCommand3 } from "citty";
857
+ var list = defineCommand3({
858
+ meta: { name: "list", description: "\uB0B4 \uC5EC\uD589 \uBAA9\uB85D\uC744 \uD45C\uC2DC\uD569\uB2C8\uB2E4." },
859
+ args: {
860
+ locale: { type: "string", description: "ko | en | es | fr" },
861
+ output: { type: "string", description: "pretty | json" }
862
+ },
863
+ async run({ args }) {
864
+ const ctx = await resolveOutputContext(args.output);
865
+ const locale = await resolveLocale(args.locale);
866
+ try {
867
+ const result = await executeFunction("list_trips", {}, { locale });
868
+ const trips = result.trips ?? result.data?.trips ?? [];
869
+ if (ctx.mode === "json") {
870
+ emitJsonOk({ trips });
871
+ return;
872
+ }
873
+ const c = colorize(ctx);
874
+ if (!trips.length) {
875
+ printLine(c.dim("\uC5EC\uD589\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
876
+ return;
877
+ }
878
+ const state = await readState();
879
+ printTable(
880
+ ["", "\uC774\uB984", "\uAD6D\uAC00", "\uAE30\uAC04", "\uB3C4\uC2DC"],
881
+ trips.map((t) => [
882
+ state.activeTrip?.id === t.id ? c.cyan("\u2192") : "",
883
+ t.name ?? t.slug ?? t.id.slice(0, 8),
884
+ t.country ?? "",
885
+ t.start_date && t.end_date ? `${t.start_date} ~ ${t.end_date}` : "",
886
+ (t.cities ?? []).map((city) => city.nameLocal || city.name).join(", ")
887
+ ]),
888
+ ctx
889
+ );
890
+ } catch (err) {
891
+ if (err instanceof ApiError) {
892
+ if (ctx.mode === "json") emitJsonError("list_failed", err.message);
893
+ else printError(err.message, ctx);
894
+ process.exit(err.exitCode);
895
+ }
896
+ throw err;
897
+ }
898
+ }
899
+ });
900
+ var current = defineCommand3({
901
+ meta: { name: "current", description: "\uD604\uC7AC \uD65C\uC131 trip\uC744 \uD45C\uC2DC\uD569\uB2C8\uB2E4." },
902
+ args: { output: { type: "string", description: "pretty | json" } },
903
+ async run({ args }) {
904
+ const ctx = await resolveOutputContext(args.output);
905
+ const state = await readState();
906
+ if (!state.activeTrip) {
907
+ const msg = "\uD65C\uC131 trip\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
908
+ if (ctx.mode === "json") emitJsonError("no_active_trip", msg, "dodo trip switch <name-or-slug>");
909
+ else printError(msg, ctx);
910
+ process.exit(ExitCode.GeneralError);
911
+ }
912
+ if (ctx.mode === "json") emitJsonOk(state.activeTrip);
913
+ else {
914
+ const c = colorize(ctx);
915
+ const t = state.activeTrip;
916
+ printLine(c.bold(t.name ?? t.slug));
917
+ if (t.country) printLine(c.dim(t.country));
918
+ if (t.cities?.length) printLine(c.dim(t.cities.map((city) => city.nameLocal || city.name).join(" \u2192 ")));
919
+ }
920
+ }
921
+ });
922
+ var switchCmd = defineCommand3({
923
+ meta: { name: "switch", description: "\uD65C\uC131 trip\uC744 \uC804\uD658\uD569\uB2C8\uB2E4 (\uC774\uB984/slug/ID \uAC00\uB2A5)." },
924
+ args: {
925
+ target: { type: "positional", description: "trip \uC774\uB984, slug, \uB610\uB294 UUID", required: true },
926
+ locale: { type: "string", description: "ko | en | es | fr" },
927
+ output: { type: "string", description: "pretty | json" }
928
+ },
929
+ async run({ args }) {
930
+ const ctx = await resolveOutputContext(args.output);
931
+ const locale = await resolveLocale(args.locale);
932
+ try {
933
+ const list6 = await executeFunction("list_trips", {}, { locale });
934
+ const trips = list6.trips ?? list6.data?.trips ?? [];
935
+ const q = args.target.toLowerCase();
936
+ const match = trips.find(
937
+ (t) => t.id === args.target || t.slug?.toLowerCase() === q || t.name?.toLowerCase().includes(q)
938
+ );
939
+ if (!match) {
940
+ const msg = `\uB9E4\uCE6D\uB418\uB294 trip\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: ${args.target}`;
941
+ if (ctx.mode === "json") emitJsonError("no_match", msg);
942
+ else printError(msg, ctx);
943
+ process.exit(ExitCode.GeneralError);
944
+ }
945
+ await setActiveTrip({
946
+ id: match.id,
947
+ slug: match.slug ?? match.id,
948
+ name: match.name,
949
+ country: match.country,
950
+ cities: match.cities
951
+ });
952
+ if (ctx.mode === "json") emitJsonOk({ activeTrip: match });
953
+ else {
954
+ const c = colorize(ctx);
955
+ printLine(c.green(`\u2713 ${match.name ?? match.slug} \uB85C \uC804\uD658\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`));
956
+ }
957
+ } catch (err) {
958
+ if (err instanceof ApiError) {
959
+ if (ctx.mode === "json") emitJsonError("switch_failed", err.message);
960
+ else printError(err.message, ctx);
961
+ process.exit(err.exitCode);
962
+ }
963
+ throw err;
964
+ }
965
+ }
966
+ });
967
+ var create = defineCommand3({
968
+ meta: { name: "create", description: "\uC0C8 \uC5EC\uD589 \uC0DD\uC131." },
969
+ args: {
970
+ name: { type: "string", required: true, description: "\uC5EC\uD589 \uC774\uB984" },
971
+ "start-date": { type: "string", required: true, description: "\uC2DC\uC791\uC77C YYYY-MM-DD" },
972
+ "end-date": { type: "string", required: true, description: "\uC885\uB8CC\uC77C YYYY-MM-DD" },
973
+ country: { type: "string", required: true, description: "\uAD6D\uAC00 \uCF54\uB4DC (KR, ES, JP \uB4F1)" },
974
+ cities: { type: "string", description: `\uB3C4\uC2DC JSON \uBC30\uC5F4 (\uC608: '[{"name":"Madrid","nameLocal":"Madrid"}]')` },
975
+ extra: { type: "string", description: "\uCD94\uAC00 \uD544\uB4DC JSON" },
976
+ locale: { type: "string", description: "ko|en|es|fr" },
977
+ output: { type: "string", description: "pretty | json" }
978
+ },
979
+ async run({ args }) {
980
+ const ctx = await resolveOutputContext(args.output);
981
+ const locale = await resolveLocale(args.locale);
982
+ let cities = void 0;
983
+ if (args.cities) {
984
+ try {
985
+ cities = JSON.parse(args.cities);
986
+ } catch {
987
+ if (ctx.mode === "json") emitJsonError("invalid_cities", "--cities\uAC00 \uC720\uD6A8\uD55C JSON \uBC30\uC5F4\uC774 \uC544\uB2D9\uB2C8\uB2E4.");
988
+ else printError("--cities\uAC00 \uC720\uD6A8\uD55C JSON \uBC30\uC5F4\uC774 \uC544\uB2D9\uB2C8\uB2E4.", ctx);
989
+ process.exit(ExitCode.InvalidArgs);
990
+ }
991
+ }
992
+ let extraArgs = {};
993
+ if (args.extra) {
994
+ try {
995
+ extraArgs = JSON.parse(args.extra);
996
+ } catch {
997
+ if (ctx.mode === "json") emitJsonError("invalid_extra", "--extra JSON parse \uC2E4\uD328.");
998
+ else printError("--extra JSON parse \uC2E4\uD328.", ctx);
999
+ process.exit(ExitCode.InvalidArgs);
1000
+ }
1001
+ }
1002
+ try {
1003
+ const result = await executeFunction(
1004
+ "create_trip",
1005
+ {
1006
+ name: args.name,
1007
+ start_date: args["start-date"],
1008
+ end_date: args["end-date"],
1009
+ country: args.country,
1010
+ cities: cities ? JSON.stringify(cities) : void 0,
1011
+ ...extraArgs
1012
+ },
1013
+ { locale }
1014
+ );
1015
+ if (ctx.mode === "json") emitJsonOk(result);
1016
+ else printLine(colorize(ctx).green("\u2713 \uC5EC\uD589\uC774 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
1017
+ } catch (err) {
1018
+ if (err instanceof ApiError) {
1019
+ if (ctx.mode === "json") emitJsonError("create_failed", err.message);
1020
+ else printError(err.message, ctx);
1021
+ process.exit(err.exitCode);
1022
+ }
1023
+ throw err;
1024
+ }
1025
+ }
1026
+ });
1027
+ var tripCommand = defineCommand3({
1028
+ meta: { name: "trip", description: "\uC5EC\uD589 \uAD00\uB9AC (list/current/switch/create)." },
1029
+ subCommands: { list, current, switch: switchCmd, create }
1030
+ });
1031
+
1032
+ // src/commands/expense.ts
1033
+ init_api_client();
1034
+ init_exit_codes();
1035
+ init_output();
1036
+ init_config();
1037
+ init_state();
1038
+ import { defineCommand as defineCommand4 } from "citty";
1039
+ async function resolveTripId(flagTrip, ctx) {
1040
+ const direct = await getActiveTripId(flagTrip);
1041
+ if (direct) return direct;
1042
+ if (flagTrip) {
1043
+ const msg = `slug \uAE30\uBC18 trip \uC9C0\uC815\uC740 'dodo trip switch <slug>' \uD6C4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.`;
1044
+ if (ctx.mode === "json") emitJsonError("trip_slug_unsupported", msg);
1045
+ else printError(msg, ctx);
1046
+ process.exit(ExitCode.InvalidArgs);
1047
+ }
1048
+ const state = await readState();
1049
+ if (!state.activeTrip) {
1050
+ const msg = "\uD65C\uC131 trip\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.";
1051
+ if (ctx.mode === "json") emitJsonError("no_active_trip", msg, "dodo trip switch <name-or-slug>");
1052
+ else printError(msg, ctx);
1053
+ process.exit(ExitCode.InvalidArgs);
1054
+ }
1055
+ return state.activeTrip.id;
1056
+ }
1057
+ var list2 = defineCommand4({
1058
+ meta: { name: "list", description: "\uD65C\uC131 trip\uC758 \uACBD\uBE44 \uBAA9\uB85D\uC744 \uD45C\uC2DC\uD569\uB2C8\uB2E4." },
1059
+ args: {
1060
+ limit: { type: "string", description: "\uCD5C\uB300 \uD45C\uC2DC \uAC74\uC218 (\uAE30\uBCF8 20)" },
1061
+ trip: { type: "string", description: "Trip UUID (\uBBF8\uC9C0\uC815 \uC2DC \uD65C\uC131 trip)" },
1062
+ locale: { type: "string", description: "ko | en | es | fr" },
1063
+ output: { type: "string", description: "pretty | json" }
1064
+ },
1065
+ async run({ args }) {
1066
+ const ctx = await resolveOutputContext(args.output);
1067
+ const tripId = await resolveTripId(args.trip, ctx);
1068
+ const locale = await resolveLocale(args.locale);
1069
+ const limit = args.limit ? Number.parseInt(args.limit, 10) : 20;
1070
+ try {
1071
+ const result = await executeFunction(
1072
+ "get_expenses",
1073
+ { limit: String(limit) },
1074
+ { tripId, locale }
1075
+ );
1076
+ const expenses = result.expenses ?? result.data?.expenses ?? [];
1077
+ if (ctx.mode === "json") {
1078
+ emitJsonOk({ expenses });
1079
+ return;
1080
+ }
1081
+ const c = colorize(ctx);
1082
+ if (!expenses.length) {
1083
+ printLine(c.dim("\uACBD\uBE44 \uB0B4\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
1084
+ return;
1085
+ }
1086
+ printTable(
1087
+ ["\uB0A0\uC9DC", "\uAE08\uC561", "\uD1B5\uD654", "\uCE74\uD14C\uACE0\uB9AC", "\uC124\uBA85", "\uACB0\uC81C\uC790"],
1088
+ expenses.map((e) => [
1089
+ e.date ?? "",
1090
+ e.amount,
1091
+ e.currency,
1092
+ e.category ?? "",
1093
+ e.description ?? "",
1094
+ e.paid_by_name ?? ""
1095
+ ]),
1096
+ ctx
1097
+ );
1098
+ } catch (err) {
1099
+ if (err instanceof ApiError) {
1100
+ if (ctx.mode === "json") emitJsonError("list_failed", err.message);
1101
+ else printError(err.message, ctx);
1102
+ process.exit(err.exitCode);
1103
+ }
1104
+ throw err;
1105
+ }
1106
+ }
1107
+ });
1108
+ var add = defineCommand4({
1109
+ meta: { name: "add", description: "\uACBD\uBE44\uB97C \uCD94\uAC00\uD569\uB2C8\uB2E4." },
1110
+ args: {
1111
+ amount: { type: "string", description: "\uAE08\uC561 (\uC22B\uC790)", required: true },
1112
+ currency: { type: "string", description: "\uD1B5\uD654 (KRW, EUR, USD \uB4F1)", required: true },
1113
+ description: { type: "string", description: "\uC124\uBA85", required: true },
1114
+ type: { type: "string", description: "shared | family (\uAE30\uBCF8 shared)" },
1115
+ category: { type: "string", description: "\uCE74\uD14C\uACE0\uB9AC (food/transport/lodging \uB4F1)" },
1116
+ date: { type: "string", description: "YYYY-MM-DD (\uAE30\uBCF8 \uC624\uB298)" },
1117
+ trip: { type: "string", description: "Trip UUID (\uBBF8\uC9C0\uC815 \uC2DC \uD65C\uC131 trip)" },
1118
+ locale: { type: "string", description: "ko | en | es | fr" },
1119
+ output: { type: "string", description: "pretty | json" }
1120
+ },
1121
+ async run({ args }) {
1122
+ const ctx = await resolveOutputContext(args.output);
1123
+ const tripId = await resolveTripId(args.trip, ctx);
1124
+ const locale = await resolveLocale(args.locale);
1125
+ const amount = Number.parseFloat(args.amount);
1126
+ if (Number.isNaN(amount) || amount <= 0) {
1127
+ const msg = "amount\uB294 \uC591\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.";
1128
+ if (ctx.mode === "json") emitJsonError("invalid_amount", msg);
1129
+ else printError(msg, ctx);
1130
+ process.exit(ExitCode.InvalidArgs);
1131
+ }
1132
+ const type = args.type ?? "shared";
1133
+ if (type !== "shared" && type !== "family") {
1134
+ const msg = "type\uC740 shared \uB610\uB294 family\uB9CC \uD5C8\uC6A9\uB429\uB2C8\uB2E4.";
1135
+ if (ctx.mode === "json") emitJsonError("invalid_type", msg);
1136
+ else printError(msg, ctx);
1137
+ process.exit(ExitCode.InvalidArgs);
1138
+ }
1139
+ try {
1140
+ const result = await executeFunction(
1141
+ "add_expense",
1142
+ {
1143
+ amount: String(amount),
1144
+ currency: args.currency,
1145
+ description: args.description,
1146
+ type,
1147
+ category: args.category,
1148
+ date: args.date
1149
+ },
1150
+ { tripId, locale }
1151
+ );
1152
+ if (ctx.mode === "json") emitJsonOk(result);
1153
+ else printLine(colorize(ctx).green(`\u2713 \uACBD\uBE44\uAC00 \uCD94\uAC00\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`));
1154
+ } catch (err) {
1155
+ if (err instanceof ApiError) {
1156
+ if (ctx.mode === "json") emitJsonError("add_failed", err.message);
1157
+ else printError(err.message, ctx);
1158
+ process.exit(err.exitCode);
1159
+ }
1160
+ throw err;
1161
+ }
1162
+ }
1163
+ });
1164
+ var update = defineCommand4({
1165
+ meta: { name: "update", description: "\uACBD\uBE44\uB97C \uC218\uC815\uD569\uB2C8\uB2E4." },
1166
+ args: {
1167
+ id: { type: "positional", description: "\uACBD\uBE44 ID", required: true },
1168
+ amount: { type: "string", description: "\uAE08\uC561" },
1169
+ currency: { type: "string", description: "\uD1B5\uD654" },
1170
+ description: { type: "string", description: "\uC124\uBA85" },
1171
+ type: { type: "string", description: "shared | family" },
1172
+ category: { type: "string", description: "\uCE74\uD14C\uACE0\uB9AC" },
1173
+ date: { type: "string", description: "YYYY-MM-DD" },
1174
+ trip: { type: "string", description: "Trip UUID" },
1175
+ locale: { type: "string", description: "ko | en | es | fr" },
1176
+ output: { type: "string", description: "pretty | json" }
1177
+ },
1178
+ async run({ args }) {
1179
+ const ctx = await resolveOutputContext(args.output);
1180
+ const tripId = await resolveTripId(args.trip, ctx);
1181
+ const locale = await resolveLocale(args.locale);
1182
+ try {
1183
+ const result = await executeFunction(
1184
+ "update_expense",
1185
+ {
1186
+ id: args.id,
1187
+ amount: args.amount,
1188
+ currency: args.currency,
1189
+ description: args.description,
1190
+ type: args.type,
1191
+ category: args.category,
1192
+ date: args.date
1193
+ },
1194
+ { tripId, locale }
1195
+ );
1196
+ if (ctx.mode === "json") emitJsonOk(result);
1197
+ else printLine(colorize(ctx).green(`\u2713 \uACBD\uBE44\uAC00 \uC218\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`));
1198
+ } catch (err) {
1199
+ if (err instanceof ApiError) {
1200
+ if (ctx.mode === "json") emitJsonError("update_failed", err.message);
1201
+ else printError(err.message, ctx);
1202
+ process.exit(err.exitCode);
1203
+ }
1204
+ throw err;
1205
+ }
1206
+ }
1207
+ });
1208
+ var del = defineCommand4({
1209
+ meta: { name: "delete", description: "\uACBD\uBE44\uB97C \uC0AD\uC81C\uD569\uB2C8\uB2E4 (--yes \uC5C6\uC73C\uBA74 confirm)." },
1210
+ args: {
1211
+ id: { type: "positional", description: "\uACBD\uBE44 ID", required: true },
1212
+ yes: { type: "boolean", description: "confirm prompt \uC6B0\uD68C", default: false },
1213
+ trip: { type: "string", description: "Trip UUID" },
1214
+ locale: { type: "string", description: "ko | en | es | fr" },
1215
+ output: { type: "string", description: "pretty | json" }
1216
+ },
1217
+ async run({ args }) {
1218
+ const { runFunctionCommand: runFunctionCommand2 } = await Promise.resolve().then(() => (init_command_helpers(), command_helpers_exports));
1219
+ await runFunctionCommand2({
1220
+ function: "delete_expense",
1221
+ args: { id: args.id },
1222
+ outputFlag: args.output,
1223
+ localeFlag: args.locale,
1224
+ tripFlag: args.trip,
1225
+ yesFlag: args.yes,
1226
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uACBD\uBE44\uAC00 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
1227
+ });
1228
+ }
1229
+ });
1230
+ var expenseCommand = defineCommand4({
1231
+ meta: { name: "expense", description: "\uACBD\uBE44 \uAD00\uB9AC (list/add/update/delete)." },
1232
+ subCommands: { list: list2, add, update, delete: del }
1233
+ });
1234
+
1235
+ // src/commands/booking.ts
1236
+ init_command_helpers();
1237
+ init_output();
1238
+ import { defineCommand as defineCommand5 } from "citty";
1239
+ var list3 = defineCommand5({
1240
+ meta: { name: "list", description: "\uD65C\uC131 trip\uC758 \uC608\uC57D \uBAA9\uB85D." },
1241
+ args: {
1242
+ type: { type: "string", description: "flight|train|accommodation|activity|car_rental|transfer" },
1243
+ scope: { type: "string", description: "shared | family" },
1244
+ trip: { type: "string", description: "Trip UUID" },
1245
+ locale: { type: "string", description: "ko|en|es|fr" },
1246
+ output: { type: "string", description: "pretty | json" }
1247
+ },
1248
+ async run({ args }) {
1249
+ await runFunctionCommand({
1250
+ function: "get_bookings",
1251
+ args: { type: args.type, scope: args.scope },
1252
+ outputFlag: args.output,
1253
+ localeFlag: args.locale,
1254
+ tripFlag: args.trip,
1255
+ formatResponse: (data, ctx) => {
1256
+ const bookings = unwrap(data, "bookings") ?? [];
1257
+ const c = colorize(ctx);
1258
+ if (!bookings.length) return printLine(c.dim("\uC608\uC57D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
1259
+ printTable(
1260
+ ["ID", "\uC720\uD615", "\uBC94\uC704", "\uC81C\uBAA9", "\uAE30\uAC04", "\uBCA4\uB354", "\uAE08\uC561"],
1261
+ bookings.map((b) => [
1262
+ b.id.slice(0, 8),
1263
+ b.type ?? "",
1264
+ b.scope ?? "",
1265
+ b.title ?? "",
1266
+ (b.start_date ?? "") + (b.end_date ? ` ~ ${b.end_date}` : ""),
1267
+ b.vendor ?? "",
1268
+ b.amount ? `${b.amount} ${b.currency ?? ""}` : ""
1269
+ ]),
1270
+ ctx
1271
+ );
1272
+ }
1273
+ });
1274
+ }
1275
+ });
1276
+ var add2 = defineCommand5({
1277
+ meta: { name: "add", description: "\uC608\uC57D \uCD94\uAC00." },
1278
+ args: {
1279
+ type: { type: "string", required: true, description: "flight|train|accommodation|activity|car_rental|transfer" },
1280
+ title: { type: "string", required: true, description: "\uC608\uC57D \uC81C\uBAA9" },
1281
+ scope: { type: "string", description: "shared | family (\uAE30\uBCF8 shared)" },
1282
+ "start-date": { type: "string", description: "\uC2DC\uC791 (YYYY-MM-DD \uB610\uB294 ISO)" },
1283
+ "end-date": { type: "string", description: "\uC885\uB8CC" },
1284
+ vendor: { type: "string", description: "\uC608\uC57D \uBCA4\uB354/\uC5C5\uCCB4" },
1285
+ amount: { type: "string", description: "\uAE08\uC561" },
1286
+ currency: { type: "string", description: "\uD1B5\uD654" },
1287
+ extra: { type: "string", description: "\uCD94\uAC00 \uD544\uB4DC JSON (flight_number, hotel_address, etc.)" },
1288
+ trip: { type: "string", description: "Trip UUID" },
1289
+ locale: { type: "string", description: "ko|en|es|fr" },
1290
+ output: { type: "string", description: "pretty | json" }
1291
+ },
1292
+ async run({ args }) {
1293
+ let extraArgs = {};
1294
+ if (args.extra) {
1295
+ try {
1296
+ extraArgs = JSON.parse(args.extra);
1297
+ } catch {
1298
+ process.stderr.write("Error: --extra\uAC00 \uC720\uD6A8\uD55C JSON\uC774 \uC544\uB2D9\uB2C8\uB2E4.\n");
1299
+ process.exit(2);
1300
+ }
1301
+ }
1302
+ await runFunctionCommand({
1303
+ function: "add_booking",
1304
+ args: {
1305
+ type: args.type,
1306
+ title: args.title,
1307
+ scope: args.scope ?? "shared",
1308
+ start_date: args["start-date"],
1309
+ end_date: args["end-date"],
1310
+ vendor: args.vendor,
1311
+ amount: args.amount,
1312
+ currency: args.currency,
1313
+ ...extraArgs
1314
+ },
1315
+ outputFlag: args.output,
1316
+ localeFlag: args.locale,
1317
+ tripFlag: args.trip,
1318
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uC608\uC57D\uC774 \uCD94\uAC00\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
1319
+ });
1320
+ }
1321
+ });
1322
+ var update2 = defineCommand5({
1323
+ meta: { name: "update", description: "\uC608\uC57D \uC218\uC815." },
1324
+ args: {
1325
+ id: { type: "positional", required: true, description: "\uC608\uC57D ID" },
1326
+ title: { type: "string", description: "\uC81C\uBAA9" },
1327
+ "start-date": { type: "string", description: "\uC2DC\uC791" },
1328
+ "end-date": { type: "string", description: "\uC885\uB8CC" },
1329
+ vendor: { type: "string", description: "\uBCA4\uB354" },
1330
+ amount: { type: "string", description: "\uAE08\uC561" },
1331
+ currency: { type: "string", description: "\uD1B5\uD654" },
1332
+ extra: { type: "string", description: "\uCD94\uAC00 \uD544\uB4DC JSON" },
1333
+ trip: { type: "string", description: "Trip UUID" },
1334
+ locale: { type: "string", description: "ko|en|es|fr" },
1335
+ output: { type: "string", description: "pretty | json" }
1336
+ },
1337
+ async run({ args }) {
1338
+ let extraArgs = {};
1339
+ if (args.extra) {
1340
+ try {
1341
+ extraArgs = JSON.parse(args.extra);
1342
+ } catch {
1343
+ process.stderr.write("Error: --extra JSON parse \uC2E4\uD328\n");
1344
+ process.exit(2);
1345
+ }
1346
+ }
1347
+ await runFunctionCommand({
1348
+ function: "update_booking",
1349
+ args: {
1350
+ id: args.id,
1351
+ title: args.title,
1352
+ start_date: args["start-date"],
1353
+ end_date: args["end-date"],
1354
+ vendor: args.vendor,
1355
+ amount: args.amount,
1356
+ currency: args.currency,
1357
+ ...extraArgs
1358
+ },
1359
+ outputFlag: args.output,
1360
+ localeFlag: args.locale,
1361
+ tripFlag: args.trip,
1362
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uC608\uC57D\uC774 \uC218\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
1363
+ });
1364
+ }
1365
+ });
1366
+ var del2 = defineCommand5({
1367
+ meta: { name: "delete", description: "\uC608\uC57D \uC0AD\uC81C." },
1368
+ args: {
1369
+ id: { type: "positional", required: true, description: "\uC608\uC57D ID" },
1370
+ yes: { type: "boolean", default: false, description: "confirm \uC6B0\uD68C" },
1371
+ trip: { type: "string", description: "Trip UUID" },
1372
+ locale: { type: "string", description: "ko|en|es|fr" },
1373
+ output: { type: "string", description: "pretty | json" }
1374
+ },
1375
+ async run({ args }) {
1376
+ await runFunctionCommand({
1377
+ function: "delete_booking",
1378
+ args: { id: args.id },
1379
+ outputFlag: args.output,
1380
+ localeFlag: args.locale,
1381
+ tripFlag: args.trip,
1382
+ yesFlag: args.yes,
1383
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uC608\uC57D\uC774 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
1384
+ });
1385
+ }
1386
+ });
1387
+ var bookingCommand = defineCommand5({
1388
+ meta: { name: "booking", description: "\uC608\uC57D \uAD00\uB9AC." },
1389
+ subCommands: { list: list3, add: add2, update: update2, delete: del2 }
1390
+ });
1391
+
1392
+ // src/commands/itinerary.ts
1393
+ init_command_helpers();
1394
+ init_output();
1395
+ import { defineCommand as defineCommand6 } from "citty";
1396
+ var show = defineCommand6({
1397
+ meta: { name: "show", description: "\uC77C\uC815 \uD45C\uC2DC." },
1398
+ args: {
1399
+ date: { type: "string", description: "YYYY-MM-DD (\uD2B9\uC815 \uB0A0\uB9CC)" },
1400
+ all: { type: "boolean", default: false, description: "\uC5EC\uD589 \uC804\uCCB4" },
1401
+ trip: { type: "string", description: "Trip UUID" },
1402
+ locale: { type: "string", description: "ko|en|es|fr" },
1403
+ output: { type: "string", description: "pretty | json" }
1404
+ },
1405
+ async run({ args }) {
1406
+ await runFunctionCommand({
1407
+ function: "get_itinerary",
1408
+ args: { date: args.date, all: args.all ? "true" : void 0 },
1409
+ outputFlag: args.output,
1410
+ localeFlag: args.locale,
1411
+ tripFlag: args.trip,
1412
+ formatResponse: (data, ctx) => {
1413
+ const items = unwrap(data, "itinerary") ?? unwrap(data, "places") ?? [];
1414
+ const c = colorize(ctx);
1415
+ if (!items.length) return printLine(c.dim("\uC77C\uC815\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
1416
+ printTable(
1417
+ ["\uB0A0\uC9DC", "\uC21C\uC11C", "\uC7A5\uC18C", "\uB3C4\uC2DC", "\uBA54\uBAA8"],
1418
+ items.map((i) => [i.date ?? "", i.position ?? "", i.place_name ?? "", i.city ?? "", i.notes ?? ""]),
1419
+ ctx
1420
+ );
1421
+ }
1422
+ });
1423
+ }
1424
+ });
1425
+ var add3 = defineCommand6({
1426
+ meta: { name: "add", description: "\uC77C\uC815\uC5D0 \uC7A5\uC18C \uCD94\uAC00." },
1427
+ args: {
1428
+ date: { type: "string", required: true, description: "YYYY-MM-DD" },
1429
+ place: { type: "string", required: true, description: "\uC7A5\uC18C \uC774\uB984 \uB610\uB294 place_id" },
1430
+ city: { type: "string", description: "\uB3C4\uC2DC" },
1431
+ notes: { type: "string", description: "\uBA54\uBAA8" },
1432
+ trip: { type: "string", description: "Trip UUID" },
1433
+ locale: { type: "string", description: "ko|en|es|fr" },
1434
+ output: { type: "string", description: "pretty | json" }
1435
+ },
1436
+ async run({ args }) {
1437
+ await runFunctionCommand({
1438
+ function: "add_place_to_itinerary",
1439
+ args: { date: args.date, place_name: args.place, city: args.city, notes: args.notes },
1440
+ outputFlag: args.output,
1441
+ localeFlag: args.locale,
1442
+ tripFlag: args.trip,
1443
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uC77C\uC815\uC5D0 \uCD94\uAC00\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
1444
+ });
1445
+ }
1446
+ });
1447
+ var remove = defineCommand6({
1448
+ meta: { name: "remove", description: "\uC77C\uC815\uC5D0\uC11C \uC7A5\uC18C \uC0AD\uC81C." },
1449
+ args: {
1450
+ date: { type: "string", required: true, description: "YYYY-MM-DD" },
1451
+ place: { type: "string", required: true, description: "\uC7A5\uC18C \uC774\uB984" },
1452
+ yes: { type: "boolean", default: false, description: "confirm \uC6B0\uD68C" },
1453
+ trip: { type: "string", description: "Trip UUID" },
1454
+ locale: { type: "string", description: "ko|en|es|fr" },
1455
+ output: { type: "string", description: "pretty | json" }
1456
+ },
1457
+ async run({ args }) {
1458
+ await runFunctionCommand({
1459
+ function: "remove_place_from_itinerary",
1460
+ args: { date: args.date, place_name: args.place },
1461
+ outputFlag: args.output,
1462
+ localeFlag: args.locale,
1463
+ tripFlag: args.trip,
1464
+ yesFlag: args.yes,
1465
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uC77C\uC815\uC5D0\uC11C \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
1466
+ });
1467
+ }
1468
+ });
1469
+ var reorder = defineCommand6({
1470
+ meta: { name: "reorder", description: "\uC77C\uC815 \uC21C\uC11C \uBCC0\uACBD." },
1471
+ args: {
1472
+ date: { type: "string", required: true, description: "YYYY-MM-DD" },
1473
+ place: { type: "string", required: true, description: "\uC7A5\uC18C \uC774\uB984" },
1474
+ position: { type: "string", required: true, description: "first|last|up|down|<\uC22B\uC790>" },
1475
+ trip: { type: "string", description: "Trip UUID" },
1476
+ locale: { type: "string", description: "ko|en|es|fr" },
1477
+ output: { type: "string", description: "pretty | json" }
1478
+ },
1479
+ async run({ args }) {
1480
+ await runFunctionCommand({
1481
+ function: "reorder_itinerary",
1482
+ args: { date: args.date, place_name: args.place, position: args.position },
1483
+ outputFlag: args.output,
1484
+ localeFlag: args.locale,
1485
+ tripFlag: args.trip,
1486
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uC77C\uC815 \uC21C\uC11C\uAC00 \uBCC0\uACBD\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
1487
+ });
1488
+ }
1489
+ });
1490
+ var itineraryCommand = defineCommand6({
1491
+ meta: { name: "itinerary", description: "\uC77C\uC815 \uAD00\uB9AC." },
1492
+ subCommands: { show, add: add3, remove, reorder }
1493
+ });
1494
+
1495
+ // src/commands/search.ts
1496
+ init_command_helpers();
1497
+ init_output();
1498
+ import { defineCommand as defineCommand7 } from "citty";
1499
+ var flightSearch = defineCommand7({
1500
+ meta: { name: "search", description: "\uD56D\uACF5\uD3B8 \uAC80\uC0C9 (Amadeus)." },
1501
+ args: {
1502
+ from: { type: "string", required: true, description: "\uCD9C\uBC1C IATA (\uC608: ICN)" },
1503
+ to: { type: "string", required: true, description: "\uB3C4\uCC29 IATA (\uC608: BCN)" },
1504
+ date: { type: "string", required: true, description: "\uCD9C\uBC1C\uC77C YYYY-MM-DD" },
1505
+ return: { type: "string", description: "\uBCF5\uADC0\uC77C YYYY-MM-DD (\uC655\uBCF5)" },
1506
+ adults: { type: "string", description: "\uC131\uC778 \uC778\uC6D0 (\uAE30\uBCF8 1)" },
1507
+ class: { type: "string", description: "ECONOMY|PREMIUM_ECONOMY|BUSINESS|FIRST" },
1508
+ locale: { type: "string", description: "ko|en|es|fr" },
1509
+ output: { type: "string", description: "pretty | json" }
1510
+ },
1511
+ async run({ args }) {
1512
+ await runFunctionCommand({
1513
+ function: "search_flights",
1514
+ args: {
1515
+ origin: args.from,
1516
+ destination: args.to,
1517
+ departure_date: args.date,
1518
+ return_date: args.return,
1519
+ adults: args.adults ?? "1",
1520
+ travel_class: args.class
1521
+ },
1522
+ outputFlag: args.output,
1523
+ localeFlag: args.locale,
1524
+ noTripContext: true,
1525
+ formatResponse: (data, ctx) => {
1526
+ const offers = unwrap(data, "offers") ?? [];
1527
+ const c = colorize(ctx);
1528
+ if (!offers.length) return printLine(c.dim("\uACB0\uACFC \uC5C6\uC74C."));
1529
+ printTable(
1530
+ ["\uAC00\uACA9", "\uD56D\uACF5\uC0AC", "\uC18C\uC694", "\uACBD\uC720", "\uCD9C\uBC1C", "\uB3C4\uCC29"],
1531
+ offers.slice(0, 10).map((o) => [o.price ?? "", o.carrier ?? "", o.duration ?? "", o.stops ?? 0, o.departure ?? "", o.arrival ?? ""]),
1532
+ ctx
1533
+ );
1534
+ }
1535
+ });
1536
+ }
1537
+ });
1538
+ var hotelSearch = defineCommand7({
1539
+ meta: { name: "search", description: "\uD638\uD154 \uAC80\uC0C9 (Amadeus)." },
1540
+ args: {
1541
+ city: { type: "string", required: true, description: "\uB3C4\uC2DC IATA \uB610\uB294 \uC774\uB984" },
1542
+ "check-in": { type: "string", required: true, description: "\uCCB4\uD06C\uC778 YYYY-MM-DD" },
1543
+ "check-out": { type: "string", required: true, description: "\uCCB4\uD06C\uC544\uC6C3 YYYY-MM-DD" },
1544
+ adults: { type: "string", description: "\uC131\uC778 \uC778\uC6D0 (\uAE30\uBCF8 2)" },
1545
+ rooms: { type: "string", description: "\uBC29 \uC218 (\uAE30\uBCF8 1)" },
1546
+ trip: { type: "string", description: "Trip UUID" },
1547
+ locale: { type: "string", description: "ko|en|es|fr" },
1548
+ output: { type: "string", description: "pretty | json" }
1549
+ },
1550
+ async run({ args }) {
1551
+ await runFunctionCommand({
1552
+ function: "search_hotels",
1553
+ args: {
1554
+ city: args.city,
1555
+ check_in_date: args["check-in"],
1556
+ check_out_date: args["check-out"],
1557
+ adults: args.adults ?? "2",
1558
+ rooms: args.rooms ?? "1"
1559
+ },
1560
+ outputFlag: args.output,
1561
+ localeFlag: args.locale,
1562
+ tripFlag: args.trip,
1563
+ formatResponse: (data, ctx) => {
1564
+ const offers = unwrap(data, "offers") ?? [];
1565
+ const c = colorize(ctx);
1566
+ if (!offers.length) return printLine(c.dim("\uACB0\uACFC \uC5C6\uC74C."));
1567
+ printTable(["\uD638\uD154", "\uAC00\uACA9", "\uD3C9\uC810"], offers.slice(0, 10).map((o) => [o.name ?? "", o.price ?? "", o.rating ?? ""]), ctx);
1568
+ }
1569
+ });
1570
+ }
1571
+ });
1572
+ var activitySearch = defineCommand7({
1573
+ meta: { name: "search", description: "\uC561\uD2F0\uBE44\uD2F0 \uAC80\uC0C9 (Amadeus)." },
1574
+ args: {
1575
+ city: { type: "string", required: true, description: "\uB3C4\uC2DC \uC774\uB984" },
1576
+ radius: { type: "string", description: "\uAC80\uC0C9 \uBC18\uACBD km (\uAE30\uBCF8 2)" },
1577
+ trip: { type: "string", description: "Trip UUID" },
1578
+ locale: { type: "string", description: "ko|en|es|fr" },
1579
+ output: { type: "string", description: "pretty | json" }
1580
+ },
1581
+ async run({ args }) {
1582
+ await runFunctionCommand({
1583
+ function: "search_activities",
1584
+ args: { city: args.city, radius: args.radius ?? "2" },
1585
+ outputFlag: args.output,
1586
+ localeFlag: args.locale,
1587
+ tripFlag: args.trip,
1588
+ formatResponse: (data, ctx) => {
1589
+ const items = unwrap(data, "activities") ?? [];
1590
+ const c = colorize(ctx);
1591
+ if (!items.length) return printLine(c.dim("\uACB0\uACFC \uC5C6\uC74C."));
1592
+ printTable(["\uC774\uB984", "\uAC00\uACA9", "\uD3C9\uC810", "\uC18C\uC694"], items.slice(0, 10).map((i) => [i.name ?? "", i.price ?? "", i.rating ?? "", i.duration ?? ""]), ctx);
1593
+ }
1594
+ });
1595
+ }
1596
+ });
1597
+ var transferSearch = defineCommand7({
1598
+ meta: { name: "search", description: "\uAD50\uD1B5\uD3B8 \uAC80\uC0C9 (Amadeus, \uACF5\uD56D-\uC8FC\uC18C \uD53D\uC5C5 \uB4F1)." },
1599
+ args: {
1600
+ "start-time": { type: "string", required: true, description: "\uCD9C\uBC1C \uC2DC\uAC01 ISO" },
1601
+ "from-iata": { type: "string", description: "\uACF5\uD56D IATA" },
1602
+ "from-address": { type: "string", description: "\uCD9C\uBC1C \uC8FC\uC18C" },
1603
+ "to-iata": { type: "string", description: "\uACF5\uD56D IATA" },
1604
+ "to-address": { type: "string", description: "\uB3C4\uCC29 \uC8FC\uC18C" },
1605
+ passengers: { type: "string", description: "\uC2B9\uAC1D \uC218 (\uAE30\uBCF8 1)" },
1606
+ extra: { type: "string", description: "\uCD94\uAC00 \uD544\uB4DC JSON" },
1607
+ trip: { type: "string", description: "Trip UUID" },
1608
+ locale: { type: "string", description: "ko|en|es|fr" },
1609
+ output: { type: "string", description: "pretty | json" }
1610
+ },
1611
+ async run({ args }) {
1612
+ let extraArgs = {};
1613
+ if (args.extra) {
1614
+ try {
1615
+ extraArgs = JSON.parse(args.extra);
1616
+ } catch {
1617
+ process.stderr.write("Error: --extra JSON parse \uC2E4\uD328\n");
1618
+ process.exit(2);
1619
+ }
1620
+ }
1621
+ await runFunctionCommand({
1622
+ function: "search_transfers",
1623
+ args: {
1624
+ start_time: args["start-time"],
1625
+ from_iata: args["from-iata"],
1626
+ from_address: args["from-address"],
1627
+ to_iata: args["to-iata"],
1628
+ to_address: args["to-address"],
1629
+ passengers: args.passengers ?? "1",
1630
+ ...extraArgs
1631
+ },
1632
+ outputFlag: args.output,
1633
+ localeFlag: args.locale,
1634
+ tripFlag: args.trip,
1635
+ formatResponse: (data, ctx) => {
1636
+ const offers = unwrap(data, "offers") ?? [];
1637
+ const c = colorize(ctx);
1638
+ if (!offers.length) return printLine(c.dim("\uACB0\uACFC \uC5C6\uC74C."));
1639
+ printTable(["\uCC28\uB7C9", "\uAC00\uACA9", "\uC81C\uACF5\uC790"], offers.slice(0, 10).map((o) => [o.vehicle ?? "", o.price ?? "", o.provider ?? ""]), ctx);
1640
+ }
1641
+ });
1642
+ }
1643
+ });
1644
+ var flightCommand = defineCommand7({
1645
+ meta: { name: "flight", description: "\uD56D\uACF5\uD3B8 \uAC80\uC0C9 / \uC0C1\uD0DC \uC870\uD68C." },
1646
+ subCommands: { search: flightSearch }
1647
+ });
1648
+ var hotelCommand = defineCommand7({
1649
+ meta: { name: "hotel", description: "\uD638\uD154 \uAC80\uC0C9." },
1650
+ subCommands: { search: hotelSearch }
1651
+ });
1652
+ var activityCommand = defineCommand7({
1653
+ meta: { name: "activity", description: "\uC561\uD2F0\uBE44\uD2F0 \uAC80\uC0C9." },
1654
+ subCommands: { search: activitySearch }
1655
+ });
1656
+ var transferCommand = defineCommand7({
1657
+ meta: { name: "transfer", description: "\uAD50\uD1B5\uD3B8 \uAC80\uC0C9." },
1658
+ subCommands: { search: transferSearch }
1659
+ });
1660
+
1661
+ // src/commands/travel.ts
1662
+ init_command_helpers();
1663
+ init_output();
1664
+ import { defineCommand as defineCommand8 } from "citty";
1665
+ var placeSearch = defineCommand8({
1666
+ meta: { name: "search", description: "\uC7A5\uC18C \uAC80\uC0C9 (Google Places, \uB3C4\uC2DC \uB610\uB294 \uD604\uC7AC \uC704\uCE58 \uAE30\uC900)." },
1667
+ args: {
1668
+ query: { type: "positional", required: true, description: "\uAC80\uC0C9\uC5B4 \uB610\uB294 \uC7A5\uC18C \uC720\uD615" },
1669
+ city: { type: "string", description: "\uAC80\uC0C9 \uB3C4\uC2DC" },
1670
+ type: { type: "string", description: "place type (pharmacy, restaurant \uB4F1)" },
1671
+ nearby: { type: "boolean", default: false, description: "\uD604\uC7AC \uC704\uCE58 \uADFC\uCC98" },
1672
+ locale: { type: "string", description: "ko|en|es|fr" },
1673
+ output: { type: "string", description: "pretty | json" }
1674
+ },
1675
+ async run({ args }) {
1676
+ await runFunctionCommand({
1677
+ function: "search_nearby_places",
1678
+ args: { query: args.query, city: args.city, placeType: args.type, useCurrentLocation: args.nearby ? "true" : void 0 },
1679
+ outputFlag: args.output,
1680
+ localeFlag: args.locale,
1681
+ noTripContext: true,
1682
+ formatResponse: (data, ctx) => {
1683
+ const places = unwrap(data, "places") ?? [];
1684
+ const c = colorize(ctx);
1685
+ if (!places.length) return printLine(c.dim("\uACB0\uACFC \uC5C6\uC74C."));
1686
+ printTable(["\uC774\uB984", "\uC8FC\uC18C", "\uD3C9\uC810", "place_id"], places.slice(0, 10).map((p2) => [p2.name ?? "", p2.address ?? "", p2.rating ?? "", p2.place_id ?? ""]), ctx);
1687
+ }
1688
+ });
1689
+ }
1690
+ });
1691
+ var placeSearchText = defineCommand8({
1692
+ meta: { name: "search-text", description: "\uD14D\uC2A4\uD2B8 \uAC80\uC0C9 (\uB3C4\uC2DC \uBC94\uC704)." },
1693
+ args: {
1694
+ query: { type: "positional", required: true, description: "\uAC80\uC0C9\uC5B4" },
1695
+ city: { type: "string", description: "\uB3C4\uC2DC (\uD544\uC218)" },
1696
+ locale: { type: "string", description: "ko|en|es|fr" },
1697
+ output: { type: "string", description: "pretty | json" }
1698
+ },
1699
+ async run({ args }) {
1700
+ await runFunctionCommand({
1701
+ function: "search_places_text",
1702
+ args: { query: args.query, city: args.city },
1703
+ outputFlag: args.output,
1704
+ localeFlag: args.locale,
1705
+ noTripContext: true,
1706
+ formatResponse: (data, ctx) => {
1707
+ const places = unwrap(data, "places") ?? [];
1708
+ const c = colorize(ctx);
1709
+ if (!places.length) return printLine(c.dim("\uACB0\uACFC \uC5C6\uC74C."));
1710
+ printTable(["\uC774\uB984", "\uC8FC\uC18C", "place_id"], places.slice(0, 10).map((p2) => [p2.name ?? "", p2.address ?? "", p2.place_id ?? ""]), ctx);
1711
+ }
1712
+ });
1713
+ }
1714
+ });
1715
+ var placeDetails = defineCommand8({
1716
+ meta: { name: "details", description: "\uC7A5\uC18C \uC0C1\uC138 \uC815\uBCF4 (place_id\uB85C \uC870\uD68C)." },
1717
+ args: {
1718
+ placeId: { type: "positional", required: true, description: "Google place_id" },
1719
+ locale: { type: "string", description: "ko|en|es|fr" },
1720
+ output: { type: "string", description: "pretty | json" }
1721
+ },
1722
+ async run({ args }) {
1723
+ await runFunctionCommand({
1724
+ function: "get_place_details",
1725
+ args: { place_id: args.placeId },
1726
+ outputFlag: args.output,
1727
+ localeFlag: args.locale,
1728
+ noTripContext: true,
1729
+ formatResponse: (data, ctx) => {
1730
+ const c = colorize(ctx);
1731
+ const d = data.data ?? data;
1732
+ if (!d) return printLine(c.dim("\uACB0\uACFC \uC5C6\uC74C."));
1733
+ printLine(c.bold(String(d.name ?? d.place_id ?? "")));
1734
+ if (d.formatted_address) printLine(c.dim(String(d.formatted_address)));
1735
+ if (d.rating) printLine(`\uD3C9\uC810: ${d.rating}`);
1736
+ if (d.opening_hours) printLine(`\uC601\uC5C5: ${JSON.stringify(d.opening_hours)}`);
1737
+ }
1738
+ });
1739
+ }
1740
+ });
1741
+ var placeCommand = defineCommand8({
1742
+ meta: { name: "place", description: "\uC7A5\uC18C \uAC80\uC0C9/\uC0C1\uC138." },
1743
+ subCommands: { search: placeSearch, "search-text": placeSearchText, details: placeDetails }
1744
+ });
1745
+ var directionsCommand = defineCommand8({
1746
+ meta: { name: "directions", description: "\uACBD\uB85C \uC548\uB0B4." },
1747
+ args: {
1748
+ from: { type: "string", required: true, description: "\uCD9C\uBC1C\uC9C0" },
1749
+ to: { type: "string", required: true, description: "\uB3C4\uCC29\uC9C0" },
1750
+ mode: { type: "string", description: "transit|driving|walking|bicycling" },
1751
+ avoid: { type: "string", description: "tolls,highways (\uCF64\uB9C8)" },
1752
+ locale: { type: "string", description: "ko|en|es|fr" },
1753
+ output: { type: "string", description: "pretty | json" }
1754
+ },
1755
+ async run({ args }) {
1756
+ await runFunctionCommand({
1757
+ function: "get_directions",
1758
+ args: { origin: args.from, destination: args.to, mode: args.mode, avoid: args.avoid },
1759
+ outputFlag: args.output,
1760
+ localeFlag: args.locale,
1761
+ noTripContext: true,
1762
+ formatResponse: (data, ctx) => {
1763
+ const c = colorize(ctx);
1764
+ const d = data.data ?? data;
1765
+ if (d?.duration) printLine(`\uC18C\uC694: ${d.duration}`);
1766
+ if (d?.distance) printLine(`\uAC70\uB9AC: ${d.distance}`);
1767
+ if (Array.isArray(d?.steps)) for (const s of d.steps) printLine(` ${typeof s === "string" ? s : JSON.stringify(s)}`);
1768
+ else printLine(c.dim(JSON.stringify(d ?? data)));
1769
+ }
1770
+ });
1771
+ }
1772
+ });
1773
+ var weatherCommand = defineCommand8({
1774
+ meta: { name: "weather", description: "\uB0A0\uC528 \uC870\uD68C." },
1775
+ args: {
1776
+ city: { type: "positional", required: true, description: "\uB3C4\uC2DC \uC774\uB984" },
1777
+ locale: { type: "string", description: "ko|en|es|fr" },
1778
+ output: { type: "string", description: "pretty | json" }
1779
+ },
1780
+ async run({ args }) {
1781
+ await runFunctionCommand({
1782
+ function: "get_weather",
1783
+ args: { city: args.city },
1784
+ outputFlag: args.output,
1785
+ localeFlag: args.locale,
1786
+ noTripContext: true,
1787
+ formatResponse: (data, ctx) => {
1788
+ const c = colorize(ctx);
1789
+ const d = data.data ?? data;
1790
+ printLine(c.bold(String(d?.city ?? args.city)));
1791
+ if (d?.temperature) printLine(`\uAE30\uC628: ${d.temperature}`);
1792
+ if (d?.condition) printLine(`\uB0A0\uC528: ${d.condition}`);
1793
+ if (d?.humidity) printLine(`\uC2B5\uB3C4: ${d.humidity}`);
1794
+ }
1795
+ });
1796
+ }
1797
+ });
1798
+ var flightStatusCommand = defineCommand8({
1799
+ meta: { name: "flight-status", description: "\uD56D\uACF5\uD3B8 \uC0C1\uD0DC \uC870\uD68C." },
1800
+ args: {
1801
+ flightNumber: { type: "positional", required: true, description: "\uD56D\uACF5\uD3B8 \uBC88\uD638 (KE901)" },
1802
+ date: { type: "string", description: "YYYY-MM-DD (\uAE30\uBCF8 \uC624\uB298)" },
1803
+ locale: { type: "string", description: "ko|en|es|fr" },
1804
+ output: { type: "string", description: "pretty | json" }
1805
+ },
1806
+ async run({ args }) {
1807
+ await runFunctionCommand({
1808
+ function: "get_flight_status",
1809
+ args: { flightNumber: args.flightNumber, date: args.date },
1810
+ outputFlag: args.output,
1811
+ localeFlag: args.locale,
1812
+ noTripContext: true,
1813
+ formatResponse: (data, ctx) => {
1814
+ const c = colorize(ctx);
1815
+ const d = data.data ?? data;
1816
+ printLine(c.bold(String(d?.flight_number ?? args.flightNumber)));
1817
+ if (d?.status) printLine(`\uC0C1\uD0DC: ${d.status}`);
1818
+ if (d?.departure) printLine(`\uCD9C\uBC1C: ${JSON.stringify(d.departure)}`);
1819
+ if (d?.arrival) printLine(`\uB3C4\uCC29: ${JSON.stringify(d.arrival)}`);
1820
+ }
1821
+ });
1822
+ }
1823
+ });
1824
+
1825
+ // src/commands/feed.ts
1826
+ init_command_helpers();
1827
+ init_output();
1828
+ import { defineCommand as defineCommand9 } from "citty";
1829
+ var list4 = defineCommand9({
1830
+ meta: { name: "list", description: "\uD53C\uB4DC \uBAA9\uB85D." },
1831
+ args: {
1832
+ limit: { type: "string", description: "\uCD5C\uB300 \uD45C\uC2DC (\uAE30\uBCF8 20)" },
1833
+ trip: { type: "string", description: "Trip UUID" },
1834
+ locale: { type: "string", description: "ko|en|es|fr" },
1835
+ output: { type: "string", description: "pretty | json" }
1836
+ },
1837
+ async run({ args }) {
1838
+ await runFunctionCommand({
1839
+ function: "get_feed",
1840
+ args: { limit: args.limit ?? "20" },
1841
+ outputFlag: args.output,
1842
+ localeFlag: args.locale,
1843
+ tripFlag: args.trip,
1844
+ formatResponse: (data, ctx) => {
1845
+ const posts = unwrap(data, "posts") ?? unwrap(data, "feed") ?? [];
1846
+ const c = colorize(ctx);
1847
+ if (!posts.length) return printLine(c.dim("\uD53C\uB4DC\uAC00 \uBE44\uC5B4\uC788\uC2B5\uB2C8\uB2E4."));
1848
+ printTable(
1849
+ ["ID", "\uC791\uC131\uC790", "\uACF5\uAC1C \uBC94\uC704", "\uC0DD\uC131", "\uB0B4\uC6A9"],
1850
+ posts.map((p2) => [p2.id.slice(0, 8), p2.author_name ?? "", p2.visibility ?? "", p2.created_at ?? "", (p2.content ?? "").slice(0, 80)]),
1851
+ ctx
1852
+ );
1853
+ }
1854
+ });
1855
+ }
1856
+ });
1857
+ var post = defineCommand9({
1858
+ meta: { name: "post", description: "\uD53C\uB4DC \uAE00 \uC791\uC131." },
1859
+ args: {
1860
+ content: { type: "positional", required: true, description: "\uAE00 \uB0B4\uC6A9" },
1861
+ visibility: { type: "string", description: "private|family|shared|friends (\uAE30\uBCF8 shared)" },
1862
+ trip: { type: "string", description: "Trip UUID" },
1863
+ locale: { type: "string", description: "ko|en|es|fr" },
1864
+ output: { type: "string", description: "pretty | json" }
1865
+ },
1866
+ async run({ args }) {
1867
+ await runFunctionCommand({
1868
+ function: "add_feed_post",
1869
+ args: { content: args.content, visibility: args.visibility ?? "shared" },
1870
+ outputFlag: args.output,
1871
+ localeFlag: args.locale,
1872
+ tripFlag: args.trip,
1873
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uAE00\uC774 \uAC8C\uC2DC\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
1874
+ });
1875
+ }
1876
+ });
1877
+ var del3 = defineCommand9({
1878
+ meta: { name: "delete", description: "\uD53C\uB4DC \uAE00 \uC0AD\uC81C." },
1879
+ args: {
1880
+ id: { type: "positional", required: true, description: "\uD3EC\uC2A4\uD2B8 ID" },
1881
+ yes: { type: "boolean", default: false, description: "confirm \uC6B0\uD68C" },
1882
+ trip: { type: "string", description: "Trip UUID" },
1883
+ locale: { type: "string", description: "ko|en|es|fr" },
1884
+ output: { type: "string", description: "pretty | json" }
1885
+ },
1886
+ async run({ args }) {
1887
+ await runFunctionCommand({
1888
+ function: "delete_feed_post",
1889
+ args: { id: args.id },
1890
+ outputFlag: args.output,
1891
+ localeFlag: args.locale,
1892
+ tripFlag: args.trip,
1893
+ yesFlag: args.yes,
1894
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
1895
+ });
1896
+ }
1897
+ });
1898
+ var feedCommand = defineCommand9({
1899
+ meta: { name: "feed", description: "\uD53C\uB4DC \uAD00\uB9AC." },
1900
+ subCommands: { list: list4, post, delete: del3 }
1901
+ });
1902
+
1903
+ // src/commands/baby.ts
1904
+ init_command_helpers();
1905
+ init_output();
1906
+ import { defineCommand as defineCommand10 } from "citty";
1907
+ var list5 = defineCommand10({
1908
+ meta: { name: "list", description: "\uB4F1\uB85D\uB41C \uC544\uC774 \uBAA9\uB85D." },
1909
+ args: {
1910
+ locale: { type: "string", description: "ko|en|es|fr" },
1911
+ output: { type: "string", description: "pretty | json" }
1912
+ },
1913
+ async run({ args }) {
1914
+ await runFunctionCommand({
1915
+ function: "list_babies",
1916
+ args: {},
1917
+ outputFlag: args.output,
1918
+ localeFlag: args.locale,
1919
+ noTripContext: true,
1920
+ formatResponse: (data, ctx) => {
1921
+ const babies = unwrap(data, "babies") ?? [];
1922
+ const c = colorize(ctx);
1923
+ if (!babies.length) return printLine(c.dim("\uB4F1\uB85D\uB41C \uC544\uC774\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
1924
+ printTable(["\uC774\uB984", "\uC0DD\uB144\uC6D4\uC77C", "\uC131\uBCC4", "\uB098\uC774"], babies.map((b) => [b.name ?? "", b.birthdate ?? "", b.gender ?? "", b.age ?? ""]), ctx);
1925
+ }
1926
+ });
1927
+ }
1928
+ });
1929
+ var info = defineCommand10({
1930
+ meta: { name: "info", description: "\uC544\uC774 \uC815\uBCF4 \uC870\uD68C (\uC774\uB984 \uB610\uB294 \uBAA8\uB4E0 \uC544\uC774)." },
1931
+ args: {
1932
+ name: { type: "string", description: "\uC544\uC774 \uC774\uB984 (\uBBF8\uC9C0\uC815 \uC2DC \uBAA8\uB4E0 \uC544\uC774)" },
1933
+ field: { type: "string", description: "health|diet|sleep|status|all (\uAE30\uBCF8 all)" },
1934
+ locale: { type: "string", description: "ko|en|es|fr" },
1935
+ output: { type: "string", description: "pretty | json" }
1936
+ },
1937
+ async run({ args }) {
1938
+ await runFunctionCommand({
1939
+ function: "get_baby_info",
1940
+ args: { name: args.name, field: args.field ?? "all" },
1941
+ outputFlag: args.output,
1942
+ localeFlag: args.locale,
1943
+ noTripContext: true,
1944
+ formatResponse: (data, ctx) => {
1945
+ printLine(colorize(ctx).dim(JSON.stringify(data, null, 2)));
1946
+ }
1947
+ });
1948
+ }
1949
+ });
1950
+ var add4 = defineCommand10({
1951
+ meta: { name: "add", description: "\uC544\uC774 \uCD94\uAC00." },
1952
+ args: {
1953
+ name: { type: "string", required: true, description: "\uC774\uB984" },
1954
+ birthdate: { type: "string", description: "YYYY-MM-DD" },
1955
+ gender: { type: "string", description: "male|female|other" },
1956
+ extra: { type: "string", description: "\uCD94\uAC00 \uD544\uB4DC JSON" },
1957
+ locale: { type: "string", description: "ko|en|es|fr" },
1958
+ output: { type: "string", description: "pretty | json" }
1959
+ },
1960
+ async run({ args }) {
1961
+ let extraArgs = {};
1962
+ if (args.extra) {
1963
+ try {
1964
+ extraArgs = JSON.parse(args.extra);
1965
+ } catch {
1966
+ process.stderr.write("Error: --extra JSON parse \uC2E4\uD328\n");
1967
+ process.exit(2);
1968
+ }
1969
+ }
1970
+ await runFunctionCommand({
1971
+ function: "add_baby",
1972
+ args: { name: args.name, birthdate: args.birthdate, gender: args.gender, ...extraArgs },
1973
+ outputFlag: args.output,
1974
+ localeFlag: args.locale,
1975
+ noTripContext: true,
1976
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uC544\uC774\uAC00 \uCD94\uAC00\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
1977
+ });
1978
+ }
1979
+ });
1980
+ var update3 = defineCommand10({
1981
+ meta: { name: "update", description: "\uC544\uC774 \uC815\uBCF4 \uC218\uC815." },
1982
+ args: {
1983
+ name: { type: "string", required: true, description: "\uC544\uC774 \uC774\uB984" },
1984
+ extra: { type: "string", required: true, description: "\uC218\uC815 \uD544\uB4DC JSON (allergies, bedtime \uB4F1)" },
1985
+ locale: { type: "string", description: "ko|en|es|fr" },
1986
+ output: { type: "string", description: "pretty | json" }
1987
+ },
1988
+ async run({ args }) {
1989
+ let extraArgs = {};
1990
+ try {
1991
+ extraArgs = JSON.parse(args.extra);
1992
+ } catch {
1993
+ process.stderr.write("Error: --extra JSON parse \uC2E4\uD328\n");
1994
+ process.exit(2);
1995
+ }
1996
+ await runFunctionCommand({
1997
+ function: "update_baby_info",
1998
+ args: { name: args.name, ...extraArgs },
1999
+ outputFlag: args.output,
2000
+ localeFlag: args.locale,
2001
+ noTripContext: true,
2002
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uC544\uC774 \uC815\uBCF4\uAC00 \uC218\uC815\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
2003
+ });
2004
+ }
2005
+ });
2006
+ var status = defineCommand10({
2007
+ meta: { name: "status", description: "\uC5EC\uD589 \uC911 \uC544\uC774 \uC0C1\uD0DC \uC5C5\uB370\uC774\uD2B8." },
2008
+ args: {
2009
+ name: { type: "string", required: true, description: "\uC544\uC774 \uC774\uB984" },
2010
+ text: { type: "positional", required: true, description: "\uC0C1\uD0DC \uD14D\uC2A4\uD2B8" },
2011
+ trip: { type: "string", description: "Trip UUID" },
2012
+ locale: { type: "string", description: "ko|en|es|fr" },
2013
+ output: { type: "string", description: "pretty | json" }
2014
+ },
2015
+ async run({ args }) {
2016
+ await runFunctionCommand({
2017
+ function: "update_baby_status",
2018
+ args: { name: args.name, status: args.text },
2019
+ outputFlag: args.output,
2020
+ localeFlag: args.locale,
2021
+ tripFlag: args.trip,
2022
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uC0C1\uD0DC\uAC00 \uC5C5\uB370\uC774\uD2B8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
2023
+ });
2024
+ }
2025
+ });
2026
+ var note2 = defineCommand10({
2027
+ meta: { name: "note", description: "\uC5EC\uD589 \uBA54\uBAA8 \uCD94\uAC00." },
2028
+ args: {
2029
+ name: { type: "string", required: true, description: "\uC544\uC774 \uC774\uB984" },
2030
+ text: { type: "positional", required: true, description: "\uBA54\uBAA8 \uB0B4\uC6A9" },
2031
+ trip: { type: "string", description: "Trip UUID" },
2032
+ locale: { type: "string", description: "ko|en|es|fr" },
2033
+ output: { type: "string", description: "pretty | json" }
2034
+ },
2035
+ async run({ args }) {
2036
+ await runFunctionCommand({
2037
+ function: "add_baby_travel_note",
2038
+ args: { name: args.name, note: args.text },
2039
+ outputFlag: args.output,
2040
+ localeFlag: args.locale,
2041
+ tripFlag: args.trip,
2042
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uBA54\uBAA8\uAC00 \uCD94\uAC00\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
2043
+ });
2044
+ }
2045
+ });
2046
+ var del4 = defineCommand10({
2047
+ meta: { name: "delete", description: "\uC544\uC774 \uC815\uBCF4 \uC0AD\uC81C." },
2048
+ args: {
2049
+ name: { type: "positional", required: true, description: "\uC544\uC774 \uC774\uB984" },
2050
+ yes: { type: "boolean", default: false, description: "confirm \uC6B0\uD68C" },
2051
+ locale: { type: "string", description: "ko|en|es|fr" },
2052
+ output: { type: "string", description: "pretty | json" }
2053
+ },
2054
+ async run({ args }) {
2055
+ await runFunctionCommand({
2056
+ function: "delete_baby",
2057
+ args: { name: args.name },
2058
+ outputFlag: args.output,
2059
+ localeFlag: args.locale,
2060
+ noTripContext: true,
2061
+ yesFlag: args.yes,
2062
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uC0AD\uC81C\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
2063
+ });
2064
+ }
2065
+ });
2066
+ var babyCommand = defineCommand10({
2067
+ meta: { name: "baby", description: "\uC544\uC774 \uC815\uBCF4 \uAD00\uB9AC." },
2068
+ subCommands: { list: list5, info, add: add4, update: update3, status, note: note2, delete: del4 }
2069
+ });
2070
+
2071
+ // src/commands/social.ts
2072
+ init_command_helpers();
2073
+ init_output();
2074
+ import { defineCommand as defineCommand11 } from "citty";
2075
+ var familyList = defineCommand11({
2076
+ meta: { name: "list", description: "\uAC00\uC871 \uAD6C\uC131\uC6D0 \uBAA9\uB85D." },
2077
+ args: {
2078
+ locale: { type: "string", description: "ko|en|es|fr" },
2079
+ output: { type: "string", description: "pretty | json" }
2080
+ },
2081
+ async run({ args }) {
2082
+ await runFunctionCommand({
2083
+ function: "get_family_members",
2084
+ args: {},
2085
+ outputFlag: args.output,
2086
+ localeFlag: args.locale,
2087
+ noTripContext: true,
2088
+ formatResponse: (data, ctx) => {
2089
+ const items = unwrap(data, "members") ?? unwrap(data, "family_members") ?? [];
2090
+ const c = colorize(ctx);
2091
+ if (!items.length) return printLine(c.dim("\uB4F1\uB85D\uB41C \uAC00\uC871\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."));
2092
+ printTable(["\uC774\uB984", "\uAD00\uACC4"], items.map((m) => [m.name ?? "", m.relationship ?? ""]), ctx);
2093
+ }
2094
+ });
2095
+ }
2096
+ });
2097
+ var familyAddToTrip = defineCommand11({
2098
+ meta: { name: "add-to-trip", description: "\uAC00\uC871 \uAD6C\uC131\uC6D0\uC744 \uD65C\uC131 trip\uC5D0 \uCD94\uAC00." },
2099
+ args: {
2100
+ names: { type: "string", description: "\uC27C\uD45C \uAD6C\uBD84 \uC774\uB984 \uBAA9\uB85D (\uBBF8\uC9C0\uC815 \uC2DC \uBAA8\uB4E0 \uAC00\uC871)" },
2101
+ trip: { type: "string", description: "Trip UUID" },
2102
+ locale: { type: "string", description: "ko|en|es|fr" },
2103
+ output: { type: "string", description: "pretty | json" }
2104
+ },
2105
+ async run({ args }) {
2106
+ await runFunctionCommand({
2107
+ function: "add_family_to_trip",
2108
+ args: { names: args.names },
2109
+ outputFlag: args.output,
2110
+ localeFlag: args.locale,
2111
+ tripFlag: args.trip,
2112
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uAC00\uC871 \uAD6C\uC131\uC6D0\uC774 trip\uC5D0 \uCD94\uAC00\uB418\uC5C8\uC2B5\uB2C8\uB2E4."))
2113
+ });
2114
+ }
2115
+ });
2116
+ var familyCommand = defineCommand11({
2117
+ meta: { name: "family", description: "\uAC00\uC871 \uAD00\uB9AC." },
2118
+ subCommands: { list: familyList, "add-to-trip": familyAddToTrip }
2119
+ });
2120
+ var friendList = defineCommand11({
2121
+ meta: { name: "list", description: "\uCE5C\uAD6C \uBAA9\uB85D." },
2122
+ args: {
2123
+ locale: { type: "string", description: "ko|en|es|fr" },
2124
+ output: { type: "string", description: "pretty | json" }
2125
+ },
2126
+ async run({ args }) {
2127
+ await runFunctionCommand({
2128
+ function: "get_friends",
2129
+ args: {},
2130
+ outputFlag: args.output,
2131
+ localeFlag: args.locale,
2132
+ noTripContext: true,
2133
+ formatResponse: (data, ctx) => {
2134
+ const items = unwrap(data, "friends") ?? [];
2135
+ const c = colorize(ctx);
2136
+ if (!items.length) return printLine(c.dim("\uCE5C\uAD6C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
2137
+ printTable(["\uC774\uB984", "\uC774\uBA54\uC77C", "\uC0C1\uD0DC"], items.map((f) => [f.name ?? "", f.email ?? "", f.status ?? ""]), ctx);
2138
+ }
2139
+ });
2140
+ }
2141
+ });
2142
+ var friendAdd = defineCommand11({
2143
+ meta: { name: "add", description: "\uCE5C\uAD6C \uCD94\uAC00 (\uC774\uBA54\uC77C)." },
2144
+ args: {
2145
+ email: { type: "positional", required: true, description: "\uC774\uBA54\uC77C \uC8FC\uC18C" },
2146
+ locale: { type: "string", description: "ko|en|es|fr" },
2147
+ output: { type: "string", description: "pretty | json" }
2148
+ },
2149
+ async run({ args }) {
2150
+ await runFunctionCommand({
2151
+ function: "add_friend",
2152
+ args: { email: args.email },
2153
+ outputFlag: args.output,
2154
+ localeFlag: args.locale,
2155
+ noTripContext: true,
2156
+ formatResponse: (_, ctx) => printLine(colorize(ctx).green("\u2713 \uCE5C\uAD6C \uC694\uCCAD\uC744 \uBCF4\uB0C8\uC2B5\uB2C8\uB2E4."))
2157
+ });
2158
+ }
2159
+ });
2160
+ var friendCommand = defineCommand11({
2161
+ meta: { name: "friend", description: "\uCE5C\uAD6C \uAD00\uB9AC." },
2162
+ subCommands: { list: friendList, add: friendAdd }
2163
+ });
2164
+ var inviteList = defineCommand11({
2165
+ meta: { name: "list", description: "\uD65C\uC131 trip\uC758 \uCD08\uB300 \uBAA9\uB85D." },
2166
+ args: {
2167
+ trip: { type: "string", description: "Trip UUID" },
2168
+ locale: { type: "string", description: "ko|en|es|fr" },
2169
+ output: { type: "string", description: "pretty | json" }
2170
+ },
2171
+ async run({ args }) {
2172
+ await runFunctionCommand({
2173
+ function: "get_invitations",
2174
+ args: {},
2175
+ outputFlag: args.output,
2176
+ localeFlag: args.locale,
2177
+ tripFlag: args.trip,
2178
+ formatResponse: (data, ctx) => {
2179
+ const items = unwrap(data, "invitations") ?? [];
2180
+ const c = colorize(ctx);
2181
+ if (!items.length) return printLine(c.dim("\uCD08\uB300\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4."));
2182
+ printTable(["\uCD08\uB300\uB300\uC0C1", "\uC0C1\uD0DC", "\uC0DD\uC131\uC77C"], items.map((i) => [i.invitee_email ?? "", i.status ?? "", i.created_at ?? ""]), ctx);
2183
+ }
2184
+ });
2185
+ }
2186
+ });
2187
+ var inviteCreate = defineCommand11({
2188
+ meta: { name: "create", description: "trip \uCD08\uB300 \uC0DD\uC131." },
2189
+ args: {
2190
+ email: { type: "string", description: "\uCD08\uB300\uD560 \uC774\uBA54\uC77C (\uC0DD\uB7B5 \uC2DC \uACF5\uAC1C \uCF54\uB4DC \uBC1C\uAE09)" },
2191
+ role: { type: "string", description: "member|admin (\uAE30\uBCF8 member)" },
2192
+ "expires-days": { type: "string", description: "\uB9CC\uB8CC \uC77C\uC218 (\uAE30\uBCF8 7)" },
2193
+ trip: { type: "string", description: "Trip UUID" },
2194
+ locale: { type: "string", description: "ko|en|es|fr" },
2195
+ output: { type: "string", description: "pretty | json" }
2196
+ },
2197
+ async run({ args }) {
2198
+ await runFunctionCommand({
2199
+ function: "invite_to_trip",
2200
+ args: { email: args.email, role: args.role ?? "member", expires_days: args["expires-days"] ?? "7" },
2201
+ outputFlag: args.output,
2202
+ localeFlag: args.locale,
2203
+ tripFlag: args.trip,
2204
+ formatResponse: (data, ctx) => {
2205
+ const c = colorize(ctx);
2206
+ printLine(c.green("\u2713 \uCD08\uB300\uAC00 \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4."));
2207
+ const code = data.code ?? data.data?.code;
2208
+ if (code) printLine(`\uCF54\uB4DC: ${c.bold(code)}`);
2209
+ }
2210
+ });
2211
+ }
2212
+ });
2213
+ var inviteCommand = defineCommand11({
2214
+ meta: { name: "invite", description: "\uCD08\uB300 \uAD00\uB9AC." },
2215
+ subCommands: { list: inviteList, create: inviteCreate }
2216
+ });
2217
+
2218
+ // src/commands/web.ts
2219
+ init_command_helpers();
2220
+ import { defineCommand as defineCommand12 } from "citty";
2221
+ var webCommand = defineCommand12({
2222
+ meta: { name: "web", description: "\uC6F9 \uAC80\uC0C9 (Perplexity)." },
2223
+ args: {
2224
+ query: { type: "positional", required: true, description: "\uAC80\uC0C9\uC5B4" },
2225
+ recency: { type: "string", description: "hour|day|week|month|year" },
2226
+ locale: { type: "string", description: "ko|en|es|fr" },
2227
+ output: { type: "string", description: "pretty | json" }
2228
+ },
2229
+ async run({ args }) {
2230
+ await runFunctionCommand({
2231
+ function: "search_web_perplexity",
2232
+ args: { query: args.query, recency: args.recency },
2233
+ outputFlag: args.output,
2234
+ localeFlag: args.locale,
2235
+ noTripContext: true,
2236
+ formatResponse: (data, ctx) => {
2237
+ const c = colorize(ctx);
2238
+ const results = unwrap(data, "results") ?? [];
2239
+ if (!results.length) return printLine(c.dim("\uACB0\uACFC \uC5C6\uC74C."));
2240
+ for (const [idx, r] of results.entries()) {
2241
+ printLine(`${c.bold(`[${idx + 1}]`)} ${c.bold(r.title ?? "")}`);
2242
+ if (r.url) printLine(` ${c.dim(r.url)}`);
2243
+ if (r.snippet) printLine(` ${r.snippet}`);
2244
+ if (r.date) printLine(` ${c.dim(r.date)}`);
2245
+ printLine("");
2246
+ }
2247
+ }
2248
+ });
2249
+ }
2250
+ });
2251
+
2252
+ // src/commands/raw.ts
2253
+ init_command_helpers();
2254
+ import { defineCommand as defineCommand13 } from "citty";
2255
+ var rawCommand = defineCommand13({
2256
+ meta: {
2257
+ name: "raw",
2258
+ description: "\uD568\uC218\uB97C \uC774\uB984\uC73C\uB85C \uC9C1\uC811 \uD638\uCD9C (escape hatch)."
2259
+ },
2260
+ args: {
2261
+ function: { type: "positional", required: true, description: "\uD568\uC218 \uC774\uB984 (\uC608: get_weather)" },
2262
+ data: { type: "string", description: `JSON args (\uC608: '{"city":"Seoul"}')` },
2263
+ trip: { type: "string", description: "Trip UUID (\uD544\uC694 \uC2DC)" },
2264
+ yes: { type: "boolean", default: false, description: "destructive confirm \uC6B0\uD68C" },
2265
+ locale: { type: "string", description: "ko|en|es|fr" },
2266
+ output: { type: "string", description: "pretty | json" }
2267
+ },
2268
+ async run({ args }) {
2269
+ let parsedArgs = {};
2270
+ if (args.data) {
2271
+ try {
2272
+ parsedArgs = JSON.parse(args.data);
2273
+ } catch {
2274
+ process.stderr.write("Error: --data\uAC00 \uC720\uD6A8\uD55C JSON\uC774 \uC544\uB2D9\uB2C8\uB2E4.\n");
2275
+ process.exit(2);
2276
+ }
2277
+ }
2278
+ await runFunctionCommand({
2279
+ function: args.function,
2280
+ args: parsedArgs,
2281
+ outputFlag: args.output,
2282
+ localeFlag: args.locale,
2283
+ tripFlag: args.trip,
2284
+ yesFlag: args.yes,
2285
+ formatResponse: (data, ctx) => {
2286
+ printLine(colorize(ctx).dim(JSON.stringify(data, null, 2)));
2287
+ }
2288
+ });
2289
+ }
2290
+ });
2291
+
2292
+ // src/commands/chat.ts
2293
+ init_config();
2294
+ init_credentials();
2295
+ init_state();
2296
+ init_config();
2297
+ init_output();
2298
+ init_exit_codes();
2299
+ init_prompt();
2300
+ import { defineCommand as defineCommand14 } from "citty";
2301
+ async function* parseSseStream(response) {
2302
+ if (!response.body) return;
2303
+ const reader = response.body.getReader();
2304
+ const decoder = new TextDecoder();
2305
+ let buffer = "";
2306
+ while (true) {
2307
+ const { value, done } = await reader.read();
2308
+ if (done) break;
2309
+ buffer += decoder.decode(value, { stream: true });
2310
+ let separator;
2311
+ while ((separator = buffer.indexOf("\n\n")) !== -1) {
2312
+ const block = buffer.slice(0, separator);
2313
+ buffer = buffer.slice(separator + 2);
2314
+ let event = "message";
2315
+ let data = "";
2316
+ for (const line of block.split("\n")) {
2317
+ if (line.startsWith("event:")) event = line.slice(6).trim();
2318
+ else if (line.startsWith("data:")) data += line.slice(5).trim();
2319
+ }
2320
+ if (data) {
2321
+ try {
2322
+ yield { event, data: JSON.parse(data) };
2323
+ } catch {
2324
+ }
2325
+ }
2326
+ }
2327
+ }
2328
+ }
2329
+ async function streamChatTurn(history, options) {
2330
+ const cfg = await readConfig();
2331
+ const token = await getToken();
2332
+ if (!token) throw Object.assign(new Error("not authenticated"), { exitCode: ExitCode.Unauthorized });
2333
+ const url = new URL("/api/cli/chat", cfg.apiUrl).toString();
2334
+ const response = await fetch(url, {
2335
+ method: "POST",
2336
+ headers: {
2337
+ authorization: `Bearer ${token}`,
2338
+ "content-type": "application/json",
2339
+ accept: "text/event-stream"
2340
+ },
2341
+ body: JSON.stringify({
2342
+ messages: history.map((m) => ({ role: m.role, text: m.text })),
2343
+ tripId: options.tripId,
2344
+ locale: options.locale
2345
+ })
2346
+ });
2347
+ if (!response.ok) {
2348
+ const body = await response.text().catch(() => "");
2349
+ let parsed = {};
2350
+ try {
2351
+ parsed = JSON.parse(body);
2352
+ } catch {
2353
+ }
2354
+ const exitCode = response.status === 401 ? ExitCode.Unauthorized : response.status === 403 ? ExitCode.Forbidden : response.status === 400 ? ExitCode.InvalidArgs : ExitCode.GeneralError;
2355
+ throw Object.assign(new Error(parsed.error || `HTTP ${response.status}`), { exitCode });
2356
+ }
2357
+ let reply = "";
2358
+ const toolCalls = [];
2359
+ const c = colorize({ mode: options.mode, color: options.color });
2360
+ for await (const ev of parseSseStream(response)) {
2361
+ if (ev.event === "text") {
2362
+ const chunk = String(ev.data.text ?? "");
2363
+ reply += chunk;
2364
+ if (options.mode === "pretty") process.stdout.write(chunk);
2365
+ } else if (ev.event === "tool") {
2366
+ toolCalls.push(ev.data);
2367
+ if (options.mode === "pretty") {
2368
+ process.stdout.write("\n" + c.dim(`[\uB3C4\uAD6C: ${ev.data.name}]`) + "\n");
2369
+ }
2370
+ } else if (ev.event === "error") {
2371
+ throw Object.assign(new Error(String(ev.data.message ?? "unknown")), { exitCode: ExitCode.GeneralError });
2372
+ } else if (ev.event === "done") {
2373
+ reply = String(ev.data.reply ?? reply);
2374
+ }
2375
+ }
2376
+ if (options.mode === "pretty") process.stdout.write("\n");
2377
+ return { reply, toolCalls };
2378
+ }
2379
+ var chatCommand = defineCommand14({
2380
+ meta: { name: "chat", description: "AI \uCC57 (\uC790\uC5F0\uC5B4 \u2192 \uD568\uC218 \uD638\uCD9C). \uC778\uC790 \uC788\uC73C\uBA74 single-shot." },
2381
+ args: {
2382
+ prompt: { type: "positional", required: false, description: "\uC9C8\uBB38 (\uC0DD\uB7B5 \uC2DC \uBA40\uD2F0\uD134 REPL)" },
2383
+ trip: { type: "string", description: "Trip UUID (\uD65C\uC131 trip \uBB34\uC2DC)" },
2384
+ locale: { type: "string", description: "ko|en|es|fr" },
2385
+ output: { type: "string", description: "pretty | json" }
2386
+ },
2387
+ async run({ args }) {
2388
+ const ctx = await resolveOutputContext(args.output);
2389
+ const locale = await resolveLocale(args.locale);
2390
+ const state = await readState();
2391
+ const tripId = args.trip ?? state.activeTrip?.id;
2392
+ const history = [];
2393
+ const runOnce = async (userInput) => {
2394
+ history.push({ role: "user", text: userInput });
2395
+ try {
2396
+ const { reply, toolCalls } = await streamChatTurn(history, { tripId, locale, mode: ctx.mode, color: ctx.color });
2397
+ history.push({ role: "model", text: reply });
2398
+ if (ctx.mode === "json") emitJsonOk({ reply, tool_calls: toolCalls });
2399
+ } catch (err) {
2400
+ const e = err;
2401
+ if (ctx.mode === "json") emitJsonError("chat_failed", e.message ?? "chat error");
2402
+ else printError(e.message ?? "chat error", ctx);
2403
+ process.exit(e.exitCode ?? ExitCode.GeneralError);
2404
+ }
2405
+ };
2406
+ if (args.prompt) {
2407
+ await runOnce(args.prompt);
2408
+ return;
2409
+ }
2410
+ if (!isInteractive()) {
2411
+ const msg = 'REPL\uC740 TTY\uC5D0\uC11C\uB9CC \uB3D9\uC791\uD569\uB2C8\uB2E4. dodo chat "\uC9C8\uBB38" \uC73C\uB85C single-shot \uD638\uCD9C\uD558\uC138\uC694.';
2412
+ if (ctx.mode === "json") emitJsonError("non_tty_repl", msg);
2413
+ else printError(msg, ctx);
2414
+ process.exit(ExitCode.InvalidArgs);
2415
+ }
2416
+ const c = colorize(ctx);
2417
+ printLine(c.dim("dodo chat \u2014 \uC885\uB8CC\uB294 Ctrl+C \uB610\uB294 'exit'."));
2418
+ while (true) {
2419
+ const userInput = await text2("> ", { placeholder: "\uC9C8\uBB38\uC744 \uC785\uB825\uD558\uC138\uC694" });
2420
+ if (userInput === null) break;
2421
+ const trimmed = userInput.trim();
2422
+ if (!trimmed) continue;
2423
+ if (trimmed === "exit" || trimmed === "quit") break;
2424
+ await runOnce(trimmed);
2425
+ }
2426
+ }
2427
+ });
2428
+
2429
+ // src/commands/completion.ts
2430
+ import { defineCommand as defineCommand15 } from "citty";
2431
+ var COMMAND_TREE = {
2432
+ auth: ["login", "logout", "whoami", "tokens"],
2433
+ config: ["get", "set", "path"],
2434
+ trip: ["list", "current", "switch", "create"],
2435
+ expense: ["list", "add", "update", "delete"],
2436
+ booking: ["list", "add", "update", "delete"],
2437
+ itinerary: ["show", "add", "remove", "reorder"],
2438
+ feed: ["list", "post", "delete"],
2439
+ baby: ["list", "info", "add", "update", "status", "note", "delete"],
2440
+ flight: ["search"],
2441
+ hotel: ["search"],
2442
+ activity: ["search"],
2443
+ transfer: ["search"],
2444
+ place: ["search", "search-text", "details"],
2445
+ family: ["list", "add-to-trip"],
2446
+ friend: ["list", "add"],
2447
+ invite: ["list", "create"]
2448
+ };
2449
+ var TOP_LEVEL = [
2450
+ ...Object.keys(COMMAND_TREE),
2451
+ "directions",
2452
+ "weather",
2453
+ "flight-status",
2454
+ "web",
2455
+ "chat",
2456
+ "raw",
2457
+ "completion"
2458
+ ];
2459
+ function bashCompletion() {
2460
+ return `# dodo CLI bash completion. Add to ~/.bashrc:
2461
+ # eval "$(dodo completion bash)"
2462
+ _dodo_complete() {
2463
+ local cur prev words cword
2464
+ _init_completion -n : || return
2465
+
2466
+ local cmd_index=1
2467
+ local cmd=""
2468
+ if [[ $cword -ge 1 ]]; then
2469
+ cmd="\${COMP_WORDS[1]}"
2470
+ fi
2471
+
2472
+ if [[ $cword -eq 1 ]]; then
2473
+ COMPREPLY=( $(compgen -W "${TOP_LEVEL.join(" ")}" -- "$cur") )
2474
+ return
2475
+ fi
2476
+
2477
+ case "$cmd" in
2478
+ ${Object.entries(COMMAND_TREE).map(([k, v]) => ` ${k})
2479
+ if [[ $cword -eq 2 ]]; then
2480
+ COMPREPLY=( $(compgen -W "${v.join(" ")}" -- "$cur") )
2481
+ fi
2482
+ ;;`).join("\n")}
2483
+ esac
2484
+ }
2485
+ complete -F _dodo_complete dodo
2486
+ `;
2487
+ }
2488
+ function zshCompletion() {
2489
+ return `#compdef dodo
2490
+ # dodo CLI zsh completion. Add to ~/.zshrc:
2491
+ # eval "$(dodo completion zsh)"
2492
+ # \uB610\uB294 fpath\uC5D0 _dodo \uD30C\uC77C\uB85C \uC800\uC7A5.
2493
+ _dodo() {
2494
+ local -a top_level
2495
+ top_level=(
2496
+ ${TOP_LEVEL.map((c) => `'${c}'`).join(" ")}
2497
+ )
2498
+
2499
+ if (( CURRENT == 2 )); then
2500
+ _describe 'command' top_level
2501
+ return
2502
+ fi
2503
+
2504
+ local cmd="\${words[2]}"
2505
+ case "$cmd" in
2506
+ ${Object.entries(COMMAND_TREE).map(([k, v]) => ` ${k})
2507
+ if (( CURRENT == 3 )); then
2508
+ _values 'subcommand' ${v.map((s) => `'${s}'`).join(" ")}
2509
+ fi
2510
+ ;;`).join("\n")}
2511
+ esac
2512
+ }
2513
+ compdef _dodo dodo
2514
+ `;
2515
+ }
2516
+ function fishCompletion() {
2517
+ const lines = [
2518
+ "# dodo CLI fish completion. Save to ~/.config/fish/completions/dodo.fish:",
2519
+ "# dodo completion fish > ~/.config/fish/completions/dodo.fish",
2520
+ "",
2521
+ `complete -c dodo -f -n '__fish_use_subcommand' -a '${TOP_LEVEL.join(" ")}'`
2522
+ ];
2523
+ for (const [cmd, subs] of Object.entries(COMMAND_TREE)) {
2524
+ lines.push(`complete -c dodo -f -n "__fish_seen_subcommand_from ${cmd}" -a "${subs.join(" ")}"`);
2525
+ }
2526
+ return lines.join("\n") + "\n";
2527
+ }
2528
+ var completionCommand = defineCommand15({
2529
+ meta: { name: "completion", description: "\uC178 \uC790\uB3D9\uC644\uC131 \uC2A4\uD06C\uB9BD\uD2B8 \uCD9C\uB825 (bash|zsh|fish)." },
2530
+ args: {
2531
+ shell: { type: "positional", required: true, description: "bash | zsh | fish" }
2532
+ },
2533
+ async run({ args }) {
2534
+ let script;
2535
+ switch (args.shell) {
2536
+ case "bash":
2537
+ script = bashCompletion();
2538
+ break;
2539
+ case "zsh":
2540
+ script = zshCompletion();
2541
+ break;
2542
+ case "fish":
2543
+ script = fishCompletion();
2544
+ break;
2545
+ default:
2546
+ process.stderr.write(`Error: \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC178: ${args.shell} (bash|zsh|fish)
2547
+ `);
2548
+ process.exit(2);
2549
+ }
2550
+ process.stdout.write(script);
2551
+ }
2552
+ });
2553
+
2554
+ // src/index.ts
2555
+ var main = defineCommand16({
2556
+ meta: {
2557
+ name: "dodo",
2558
+ version: "0.1.0",
2559
+ description: "Dodo Planet CLI \u2014 terminal access to family trip data."
2560
+ },
2561
+ subCommands: {
2562
+ // 인증/설정
2563
+ auth: authCommand,
2564
+ config: configCommand,
2565
+ // 핵심 데이터
2566
+ trip: tripCommand,
2567
+ expense: expenseCommand,
2568
+ booking: bookingCommand,
2569
+ itinerary: itineraryCommand,
2570
+ feed: feedCommand,
2571
+ baby: babyCommand,
2572
+ // 외부 검색
2573
+ flight: flightCommand,
2574
+ hotel: hotelCommand,
2575
+ activity: activityCommand,
2576
+ transfer: transferCommand,
2577
+ place: placeCommand,
2578
+ directions: directionsCommand,
2579
+ weather: weatherCommand,
2580
+ "flight-status": flightStatusCommand,
2581
+ web: webCommand,
2582
+ // 사회
2583
+ family: familyCommand,
2584
+ friend: friendCommand,
2585
+ invite: inviteCommand,
2586
+ // AI 챗 + escape hatch
2587
+ chat: chatCommand,
2588
+ raw: rawCommand,
2589
+ // DX
2590
+ completion: completionCommand
2591
+ }
2592
+ });
2593
+ function run() {
2594
+ return runMain(main);
2595
+ }
2596
+ export {
2597
+ run
2598
+ };