@stephendolan/ynab-cli 1.2.4 → 1.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +471 -427
- package/dist/cli.js.map +1 -1
- package/package.json +6 -5
package/dist/cli.js
CHANGED
|
@@ -3,9 +3,6 @@
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { Command as Command11 } from "commander";
|
|
5
5
|
|
|
6
|
-
// src/lib/utils.ts
|
|
7
|
-
import { format, parseISO } from "date-fns";
|
|
8
|
-
|
|
9
6
|
// src/lib/errors.ts
|
|
10
7
|
var YnabCliError = class extends Error {
|
|
11
8
|
constructor(message, statusCode) {
|
|
@@ -162,7 +159,10 @@ function parseStatusFilter(value) {
|
|
|
162
159
|
const validStatuses = ["cleared", "uncleared", "reconciled"];
|
|
163
160
|
for (const status of statuses) {
|
|
164
161
|
if (!validStatuses.includes(status)) {
|
|
165
|
-
throw new YnabCliError(
|
|
162
|
+
throw new YnabCliError(
|
|
163
|
+
`Invalid status '${status}'. Must be one of: ${validStatuses.join(", ")}`,
|
|
164
|
+
400
|
|
165
|
+
);
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
168
|
return statuses;
|
|
@@ -196,11 +196,11 @@ function applyFieldSelection(items, fields) {
|
|
|
196
196
|
return items.map((item) => {
|
|
197
197
|
const filtered = {};
|
|
198
198
|
const itemRecord = item;
|
|
199
|
-
|
|
199
|
+
for (const field of fieldList) {
|
|
200
200
|
if (field in itemRecord) {
|
|
201
201
|
filtered[field] = itemRecord[field];
|
|
202
202
|
}
|
|
203
|
-
}
|
|
203
|
+
}
|
|
204
204
|
return filtered;
|
|
205
205
|
});
|
|
206
206
|
}
|
|
@@ -475,14 +475,6 @@ var YnabClient = class {
|
|
|
475
475
|
return response.data.account;
|
|
476
476
|
});
|
|
477
477
|
}
|
|
478
|
-
async createAccount(accountData, budgetId) {
|
|
479
|
-
return this.withErrorHandling(async () => {
|
|
480
|
-
const api = await this.getApi();
|
|
481
|
-
const id = await this.getBudgetId(budgetId);
|
|
482
|
-
const response = await api.accounts.createAccount(id, accountData);
|
|
483
|
-
return response.data.account;
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
478
|
async getCategories(budgetId, lastKnowledgeOfServer) {
|
|
487
479
|
return this.withErrorHandling(async () => {
|
|
488
480
|
const api = await this.getApi();
|
|
@@ -502,22 +494,6 @@ var YnabClient = class {
|
|
|
502
494
|
return response.data.category;
|
|
503
495
|
});
|
|
504
496
|
}
|
|
505
|
-
async updateCategory(categoryId, data, budgetId) {
|
|
506
|
-
return this.withErrorHandling(async () => {
|
|
507
|
-
const api = await this.getApi();
|
|
508
|
-
const id = await this.getBudgetId(budgetId);
|
|
509
|
-
const response = await api.categories.updateCategory(id, categoryId, data);
|
|
510
|
-
return response.data.category;
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
async getMonthCategory(month, categoryId, budgetId) {
|
|
514
|
-
return this.withErrorHandling(async () => {
|
|
515
|
-
const api = await this.getApi();
|
|
516
|
-
const id = await this.getBudgetId(budgetId);
|
|
517
|
-
const response = await api.categories.getMonthCategoryById(id, month, categoryId);
|
|
518
|
-
return response.data.category;
|
|
519
|
-
});
|
|
520
|
-
}
|
|
521
497
|
async updateMonthCategory(month, categoryId, data, budgetId) {
|
|
522
498
|
return this.withErrorHandling(async () => {
|
|
523
499
|
const api = await this.getApi();
|
|
@@ -553,22 +529,6 @@ var YnabClient = class {
|
|
|
553
529
|
return response.data.payee;
|
|
554
530
|
});
|
|
555
531
|
}
|
|
556
|
-
async getPayeeLocations(budgetId) {
|
|
557
|
-
return this.withErrorHandling(async () => {
|
|
558
|
-
const api = await this.getApi();
|
|
559
|
-
const id = await this.getBudgetId(budgetId);
|
|
560
|
-
const response = await api.payeeLocations.getPayeeLocations(id);
|
|
561
|
-
return response.data.payee_locations;
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
async getPayeeLocation(payeeLocationId, budgetId) {
|
|
565
|
-
return this.withErrorHandling(async () => {
|
|
566
|
-
const api = await this.getApi();
|
|
567
|
-
const id = await this.getBudgetId(budgetId);
|
|
568
|
-
const response = await api.payeeLocations.getPayeeLocationById(id, payeeLocationId);
|
|
569
|
-
return response.data.payee_location;
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
532
|
async getPayeeLocationsByPayee(payeeId, budgetId) {
|
|
573
533
|
return this.withErrorHandling(async () => {
|
|
574
534
|
const api = await this.getApi();
|
|
@@ -679,34 +639,14 @@ var YnabClient = class {
|
|
|
679
639
|
return response.data.transaction;
|
|
680
640
|
});
|
|
681
641
|
}
|
|
682
|
-
async createTransactions(transactionsData, budgetId) {
|
|
683
|
-
return this.withErrorHandling(async () => {
|
|
684
|
-
const api = await this.getApi();
|
|
685
|
-
const id = await this.getBudgetId(budgetId);
|
|
686
|
-
const response = await api.transactions.createTransactions(id, transactionsData);
|
|
687
|
-
return response.data;
|
|
688
|
-
});
|
|
689
|
-
}
|
|
690
642
|
async updateTransaction(transactionId, transactionData, budgetId) {
|
|
691
643
|
return this.withErrorHandling(async () => {
|
|
692
644
|
const api = await this.getApi();
|
|
693
645
|
const id = await this.getBudgetId(budgetId);
|
|
694
|
-
const response = await api.transactions.updateTransaction(
|
|
695
|
-
id,
|
|
696
|
-
transactionId,
|
|
697
|
-
transactionData
|
|
698
|
-
);
|
|
646
|
+
const response = await api.transactions.updateTransaction(id, transactionId, transactionData);
|
|
699
647
|
return response.data.transaction;
|
|
700
648
|
});
|
|
701
649
|
}
|
|
702
|
-
async updateTransactions(transactionsData, budgetId) {
|
|
703
|
-
return this.withErrorHandling(async () => {
|
|
704
|
-
const api = await this.getApi();
|
|
705
|
-
const id = await this.getBudgetId(budgetId);
|
|
706
|
-
const response = await api.transactions.updateTransactions(id, transactionsData);
|
|
707
|
-
return response.data;
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
650
|
async deleteTransaction(transactionId, budgetId) {
|
|
711
651
|
return this.withErrorHandling(async () => {
|
|
712
652
|
const api = await this.getApi();
|
|
@@ -748,26 +688,6 @@ var YnabClient = class {
|
|
|
748
688
|
return response.data.scheduled_transaction;
|
|
749
689
|
});
|
|
750
690
|
}
|
|
751
|
-
async createScheduledTransaction(data, budgetId) {
|
|
752
|
-
return this.withErrorHandling(async () => {
|
|
753
|
-
const api = await this.getApi();
|
|
754
|
-
const id = await this.getBudgetId(budgetId);
|
|
755
|
-
const response = await api.scheduledTransactions.createScheduledTransaction(id, data);
|
|
756
|
-
return response.data.scheduled_transaction;
|
|
757
|
-
});
|
|
758
|
-
}
|
|
759
|
-
async updateScheduledTransaction(scheduledTransactionId, data, budgetId) {
|
|
760
|
-
return this.withErrorHandling(async () => {
|
|
761
|
-
const api = await this.getApi();
|
|
762
|
-
const id = await this.getBudgetId(budgetId);
|
|
763
|
-
const response = await api.scheduledTransactions.updateScheduledTransaction(
|
|
764
|
-
id,
|
|
765
|
-
scheduledTransactionId,
|
|
766
|
-
data
|
|
767
|
-
);
|
|
768
|
-
return response.data.scheduled_transaction;
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
691
|
async deleteScheduledTransaction(scheduledTransactionId, budgetId) {
|
|
772
692
|
return this.withErrorHandling(async () => {
|
|
773
693
|
const api = await this.getApi();
|
|
@@ -784,8 +704,9 @@ var YnabClient = class {
|
|
|
784
704
|
await this.getApi();
|
|
785
705
|
const fullPath = path.includes("{budget_id}") ? path.replace("{budget_id}", await this.getBudgetId(budgetId)) : path;
|
|
786
706
|
const url = `https://api.ynab.com/v1${fullPath}`;
|
|
707
|
+
const accessToken = await auth.getAccessToken() || process.env.YNAB_API_KEY;
|
|
787
708
|
const headers = {
|
|
788
|
-
Authorization: `Bearer ${
|
|
709
|
+
Authorization: `Bearer ${accessToken}`,
|
|
789
710
|
"Content-Type": "application/json"
|
|
790
711
|
};
|
|
791
712
|
const httpMethod = method.toUpperCase();
|
|
@@ -845,40 +766,46 @@ function buildUpdateObject(options, mapping) {
|
|
|
845
766
|
// src/commands/auth.ts
|
|
846
767
|
function createAuthCommand() {
|
|
847
768
|
const cmd = new Command("auth").description("Authentication management");
|
|
848
|
-
cmd.command("login").description("Configure access token").action(
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
try {
|
|
853
|
-
const user = await client.getUser();
|
|
854
|
-
outputJson({
|
|
855
|
-
message: "Successfully authenticated",
|
|
856
|
-
user: { id: user?.id }
|
|
857
|
-
});
|
|
858
|
-
} catch (error) {
|
|
859
|
-
await auth.deleteAccessToken();
|
|
769
|
+
cmd.command("login").description("Configure access token").action(
|
|
770
|
+
withErrorHandling(async () => {
|
|
771
|
+
const token = await promptForAccessToken();
|
|
772
|
+
await auth.setAccessToken(token);
|
|
860
773
|
client.clearApi();
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
774
|
+
try {
|
|
775
|
+
const user = await client.getUser();
|
|
776
|
+
outputJson({
|
|
777
|
+
message: "Successfully authenticated",
|
|
778
|
+
user: { id: user?.id }
|
|
779
|
+
});
|
|
780
|
+
} catch (error) {
|
|
781
|
+
await auth.deleteAccessToken();
|
|
782
|
+
client.clearApi();
|
|
783
|
+
throw error;
|
|
784
|
+
}
|
|
785
|
+
})
|
|
786
|
+
);
|
|
787
|
+
cmd.command("status").description("Check authentication status").action(
|
|
788
|
+
withErrorHandling(async () => {
|
|
789
|
+
const isAuthenticated = await auth.isAuthenticated();
|
|
790
|
+
if (!isAuthenticated) {
|
|
791
|
+
outputJson({ authenticated: false, message: "Not authenticated" });
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
try {
|
|
795
|
+
const user = await client.getUser();
|
|
796
|
+
outputJson({ authenticated: true, user: { id: user?.id } });
|
|
797
|
+
} catch {
|
|
798
|
+
outputJson({ authenticated: false, message: "Token exists but is invalid" });
|
|
799
|
+
}
|
|
800
|
+
})
|
|
801
|
+
);
|
|
802
|
+
cmd.command("logout").description("Remove stored credentials").action(
|
|
803
|
+
withErrorHandling(async () => {
|
|
804
|
+
await auth.logout();
|
|
805
|
+
client.clearApi();
|
|
806
|
+
outputJson({ message: "Successfully logged out" });
|
|
807
|
+
})
|
|
808
|
+
);
|
|
882
809
|
return cmd;
|
|
883
810
|
}
|
|
884
811
|
|
|
@@ -886,10 +813,12 @@ function createAuthCommand() {
|
|
|
886
813
|
import { Command as Command2 } from "commander";
|
|
887
814
|
function createUserCommand() {
|
|
888
815
|
const cmd = new Command2("user").description("User information");
|
|
889
|
-
cmd.command("info").description("Get authenticated user information").action(
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
816
|
+
cmd.command("info").description("Get authenticated user information").action(
|
|
817
|
+
withErrorHandling(async () => {
|
|
818
|
+
const user = await client.getUser();
|
|
819
|
+
outputJson(user);
|
|
820
|
+
})
|
|
821
|
+
);
|
|
893
822
|
return cmd;
|
|
894
823
|
}
|
|
895
824
|
|
|
@@ -897,30 +826,38 @@ function createUserCommand() {
|
|
|
897
826
|
import { Command as Command3 } from "commander";
|
|
898
827
|
function createBudgetsCommand() {
|
|
899
828
|
const cmd = new Command3("budgets").description("Budget operations");
|
|
900
|
-
cmd.command("list").description("List all budgets").option("--include-accounts", "Include accounts in response").action(
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
cmd.command("
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
budget
|
|
922
|
-
|
|
923
|
-
|
|
829
|
+
cmd.command("list").description("List all budgets").option("--include-accounts", "Include accounts in response").action(
|
|
830
|
+
withErrorHandling(async (options) => {
|
|
831
|
+
const result = await client.getBudgets(options.includeAccounts);
|
|
832
|
+
outputJson(result?.budgets);
|
|
833
|
+
})
|
|
834
|
+
);
|
|
835
|
+
cmd.command("view").description("View budget details (uses default if no id provided)").argument("[id]", "Budget ID").action(
|
|
836
|
+
withErrorHandling(async (id) => {
|
|
837
|
+
const result = await client.getBudget(id);
|
|
838
|
+
outputJson(result?.budget);
|
|
839
|
+
})
|
|
840
|
+
);
|
|
841
|
+
cmd.command("settings").description("View budget settings").argument("[id]", "Budget ID").action(
|
|
842
|
+
withErrorHandling(async (id) => {
|
|
843
|
+
const settings = await client.getBudgetSettings(id);
|
|
844
|
+
outputJson(settings);
|
|
845
|
+
})
|
|
846
|
+
);
|
|
847
|
+
cmd.command("set-default").description("Set default budget for commands").argument("<id>", "Budget ID").action(
|
|
848
|
+
withErrorHandling(async (id) => {
|
|
849
|
+
const result = await client.getBudgets();
|
|
850
|
+
const budget = result?.budgets.find((b) => b.id === id);
|
|
851
|
+
if (!budget) {
|
|
852
|
+
throw new YnabCliError(`Budget with ID ${id} not found`, 404);
|
|
853
|
+
}
|
|
854
|
+
config.setDefaultBudget(id);
|
|
855
|
+
outputJson({
|
|
856
|
+
message: "Default budget set",
|
|
857
|
+
budget: { id: budget.id, name: budget.name }
|
|
858
|
+
});
|
|
859
|
+
})
|
|
860
|
+
);
|
|
924
861
|
return cmd;
|
|
925
862
|
}
|
|
926
863
|
|
|
@@ -928,22 +865,30 @@ function createBudgetsCommand() {
|
|
|
928
865
|
import { Command as Command4 } from "commander";
|
|
929
866
|
function createAccountsCommand() {
|
|
930
867
|
const cmd = new Command4("accounts").description("Account operations");
|
|
931
|
-
cmd.command("list").description("List all accounts").option("-b, --budget <id>", "Budget ID").action(
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
868
|
+
cmd.command("list").description("List all accounts").option("-b, --budget <id>", "Budget ID").action(
|
|
869
|
+
withErrorHandling(async (options) => {
|
|
870
|
+
const result = await client.getAccounts(options.budget);
|
|
871
|
+
outputJson(result?.accounts);
|
|
872
|
+
})
|
|
873
|
+
);
|
|
874
|
+
cmd.command("view").description("View account details").argument("<id>", "Account ID").option("-b, --budget <id>", "Budget ID").action(
|
|
875
|
+
withErrorHandling(async (id, options) => {
|
|
876
|
+
const account = await client.getAccount(id, options.budget);
|
|
877
|
+
outputJson(account);
|
|
878
|
+
})
|
|
879
|
+
);
|
|
880
|
+
cmd.command("transactions").description("List transactions for account").argument("<id>", "Account ID").option("-b, --budget <id>", "Budget ID").option("--since <date>", "Filter transactions since date (YYYY-MM-DD)").option("--type <type>", "Filter by transaction type").action(
|
|
881
|
+
withErrorHandling(
|
|
882
|
+
async (id, options) => {
|
|
883
|
+
const result = await client.getTransactionsByAccount(id, {
|
|
884
|
+
budgetId: options.budget,
|
|
885
|
+
sinceDate: options.since,
|
|
886
|
+
type: options.type
|
|
887
|
+
});
|
|
888
|
+
outputJson(result?.transactions);
|
|
889
|
+
}
|
|
890
|
+
)
|
|
891
|
+
);
|
|
947
892
|
return cmd;
|
|
948
893
|
}
|
|
949
894
|
|
|
@@ -951,36 +896,50 @@ function createAccountsCommand() {
|
|
|
951
896
|
import { Command as Command5 } from "commander";
|
|
952
897
|
function createCategoriesCommand() {
|
|
953
898
|
const cmd = new Command5("categories").description("Category operations");
|
|
954
|
-
cmd.command("list").description("List all categories").option("-b, --budget <id>", "Budget ID").option("--last-knowledge <number>", "Last knowledge of server", parseInt).action(
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
cmd.command("
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
899
|
+
cmd.command("list").description("List all categories").option("-b, --budget <id>", "Budget ID").option("--last-knowledge <number>", "Last knowledge of server", parseInt).action(
|
|
900
|
+
withErrorHandling(
|
|
901
|
+
async (options) => {
|
|
902
|
+
const result = await client.getCategories(options.budget, options.lastKnowledge);
|
|
903
|
+
outputJson(result?.category_groups);
|
|
904
|
+
}
|
|
905
|
+
)
|
|
906
|
+
);
|
|
907
|
+
cmd.command("view").description("View category details").argument("<id>", "Category ID").option("-b, --budget <id>", "Budget ID").action(
|
|
908
|
+
withErrorHandling(async (id, options) => {
|
|
909
|
+
const category = await client.getCategory(id, options.budget);
|
|
910
|
+
outputJson(category);
|
|
911
|
+
})
|
|
912
|
+
);
|
|
913
|
+
cmd.command("budget").description("Set category budgeted amount for a month (overrides existing amount)").argument("<id>", "Category ID").requiredOption("--month <month>", "Month in YYYY-MM-DD format (e.g., 2025-07-01)").requiredOption("--amount <amount>", "Total budgeted amount to set (e.g., 100.50)", parseFloat).option("-b, --budget <id>", "Budget ID").action(
|
|
914
|
+
withErrorHandling(
|
|
915
|
+
async (id, options) => {
|
|
916
|
+
if (isNaN(options.amount)) {
|
|
917
|
+
throw new YnabCliError("Amount must be a valid number", 400);
|
|
918
|
+
}
|
|
919
|
+
const milliunits = amountToMilliunits(options.amount);
|
|
920
|
+
const category = await client.updateMonthCategory(
|
|
921
|
+
options.month,
|
|
922
|
+
id,
|
|
923
|
+
{ category: { budgeted: milliunits } },
|
|
924
|
+
options.budget
|
|
925
|
+
);
|
|
926
|
+
outputJson(category);
|
|
927
|
+
}
|
|
928
|
+
)
|
|
929
|
+
);
|
|
930
|
+
cmd.command("transactions").description("List transactions for category").argument("<id>", "Category ID").option("-b, --budget <id>", "Budget ID").option("--since <date>", "Filter transactions since date (YYYY-MM-DD)").option("--type <type>", "Filter by transaction type").option("--last-knowledge <number>", "Last knowledge of server", parseInt).action(
|
|
931
|
+
withErrorHandling(
|
|
932
|
+
async (id, options) => {
|
|
933
|
+
const result = await client.getTransactionsByCategory(id, {
|
|
934
|
+
budgetId: options.budget,
|
|
935
|
+
sinceDate: options.since,
|
|
936
|
+
type: options.type,
|
|
937
|
+
lastKnowledgeOfServer: options.lastKnowledge
|
|
938
|
+
});
|
|
939
|
+
outputJson(result?.transactions);
|
|
940
|
+
}
|
|
941
|
+
)
|
|
942
|
+
);
|
|
984
943
|
return cmd;
|
|
985
944
|
}
|
|
986
945
|
|
|
@@ -1032,161 +991,207 @@ function buildTransactionData(options) {
|
|
|
1032
991
|
}
|
|
1033
992
|
function createTransactionsCommand() {
|
|
1034
993
|
const cmd = new Command6("transactions").description("Transaction operations");
|
|
1035
|
-
cmd.command("list").description("List transactions").option("-b, --budget <id>", "Budget ID").option("--account <id>", "Filter by account ID").option("--category <id>", "Filter by category ID").option("--payee <id>", "Filter by payee ID").option("--since <date>", "Filter transactions since date (YYYY-MM-DD)").option("--until <date>", "Filter transactions until date (YYYY-MM-DD)").option("--type <type>", "Filter by transaction type").option("--approved <value>", "Filter by approval status: true or false").option(
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
options.budget
|
|
1066
|
-
);
|
|
1067
|
-
outputJson(transaction);
|
|
1068
|
-
}));
|
|
1069
|
-
cmd.command("update").description("Update transaction").argument("<id>", "Transaction ID").option("-b, --budget <id>", "Budget ID").option("--account <id>", "Account ID").option("--date <date>", "Date (YYYY-MM-DD)").option("--amount <amount>", "Amount in currency units", parseFloat).option("--payee-name <name>", "Payee name").option("--payee-id <id>", "Payee ID").option("--category-id <id>", "Category ID").option("--memo <memo>", "Memo").option("--cleared <status>", "Cleared status").option("--approved", "Mark as approved").action(withErrorHandling(async (id, options) => {
|
|
1070
|
-
const transactionData = buildUpdateObject(options, {
|
|
1071
|
-
account: "account_id",
|
|
1072
|
-
date: "date",
|
|
1073
|
-
payeeName: "payee_name",
|
|
1074
|
-
payeeId: "payee_id",
|
|
1075
|
-
categoryId: "category_id",
|
|
1076
|
-
memo: "memo",
|
|
1077
|
-
cleared: "cleared",
|
|
1078
|
-
approved: "approved"
|
|
1079
|
-
});
|
|
1080
|
-
if (options.amount !== void 0) {
|
|
1081
|
-
transactionData.amount = amountToMilliunits(options.amount);
|
|
1082
|
-
}
|
|
1083
|
-
const transaction = await client.updateTransaction(
|
|
1084
|
-
id,
|
|
1085
|
-
{ transaction: transactionData },
|
|
1086
|
-
options.budget
|
|
1087
|
-
);
|
|
1088
|
-
outputJson(transaction);
|
|
1089
|
-
}));
|
|
1090
|
-
cmd.command("delete").description("Delete transaction").argument("<id>", "Transaction ID").option("-b, --budget <id>", "Budget ID").option("-y, --yes", "Skip confirmation").action(withErrorHandling(async (id, options) => {
|
|
1091
|
-
if (!await confirmDelete("transaction", options.yes)) {
|
|
1092
|
-
return;
|
|
1093
|
-
}
|
|
1094
|
-
const transaction = await client.deleteTransaction(id, options.budget);
|
|
1095
|
-
outputJson({ message: "Transaction deleted", transaction });
|
|
1096
|
-
}));
|
|
1097
|
-
cmd.command("import").description("Import transactions").option("-b, --budget <id>", "Budget ID").action(withErrorHandling(async (options) => {
|
|
1098
|
-
const transactionIds = await client.importTransactions(options.budget);
|
|
1099
|
-
outputJson({ transaction_ids: transactionIds });
|
|
1100
|
-
}));
|
|
1101
|
-
cmd.command("split").description("Split transaction into multiple categories. Amounts should be in dollars (e.g., 10.50).").argument("<id>", "Transaction ID").requiredOption("--splits <json>", 'JSON array of splits with dollar amounts: [{"amount": -21.40, "category_id": "xxx", "memo": "..."}]').option("-b, --budget <id>", "Budget ID").option("-f, --force", "Force update of already-split transactions by deleting and recreating").action(withErrorHandling(async (id, options) => {
|
|
1102
|
-
let parsedSplits;
|
|
1103
|
-
try {
|
|
1104
|
-
parsedSplits = JSON.parse(options.splits);
|
|
1105
|
-
} catch (error) {
|
|
1106
|
-
throw new YnabCliError("Invalid JSON in --splits parameter", 400);
|
|
1107
|
-
}
|
|
1108
|
-
const splits = validateJson(parsedSplits, TransactionSplitSchema, "transaction splits");
|
|
1109
|
-
const splitsInMilliunits = splits.map((split) => ({
|
|
1110
|
-
...split,
|
|
1111
|
-
amount: amountToMilliunits(split.amount)
|
|
1112
|
-
}));
|
|
1113
|
-
const existingTransaction = await client.getTransaction(id, options.budget);
|
|
1114
|
-
const isAlreadySplit = existingTransaction.subtransactions && existingTransaction.subtransactions.length > 0;
|
|
1115
|
-
if (isAlreadySplit && !options.force) {
|
|
1116
|
-
throw new YnabCliError(
|
|
1117
|
-
"Transaction is already split. YNAB API does not support updating split transactions. Use --force to delete and recreate the transaction with new splits.",
|
|
1118
|
-
400
|
|
1119
|
-
);
|
|
1120
|
-
}
|
|
1121
|
-
if (isAlreadySplit) {
|
|
1122
|
-
await client.deleteTransaction(id, options.budget);
|
|
1123
|
-
const recreatedTransaction = await client.createTransaction(
|
|
1124
|
-
{
|
|
1125
|
-
transaction: {
|
|
1126
|
-
account_id: existingTransaction.account_id,
|
|
1127
|
-
date: existingTransaction.date,
|
|
1128
|
-
amount: existingTransaction.amount,
|
|
1129
|
-
payee_id: existingTransaction.payee_id,
|
|
1130
|
-
payee_name: existingTransaction.payee_name,
|
|
1131
|
-
category_id: null,
|
|
1132
|
-
memo: existingTransaction.memo,
|
|
1133
|
-
cleared: existingTransaction.cleared,
|
|
1134
|
-
approved: existingTransaction.approved,
|
|
1135
|
-
flag_color: existingTransaction.flag_color,
|
|
1136
|
-
subtransactions: splitsInMilliunits
|
|
1137
|
-
}
|
|
1138
|
-
},
|
|
1139
|
-
options.budget
|
|
1140
|
-
);
|
|
1141
|
-
outputJson(recreatedTransaction);
|
|
1142
|
-
} else {
|
|
1143
|
-
const transaction = await client.updateTransaction(
|
|
1144
|
-
id,
|
|
1145
|
-
{
|
|
1146
|
-
transaction: {
|
|
1147
|
-
category_id: null,
|
|
1148
|
-
subtransactions: splitsInMilliunits
|
|
1149
|
-
}
|
|
1150
|
-
},
|
|
1151
|
-
options.budget
|
|
1152
|
-
);
|
|
994
|
+
cmd.command("list").description("List transactions").option("-b, --budget <id>", "Budget ID").option("--account <id>", "Filter by account ID").option("--category <id>", "Filter by category ID").option("--payee <id>", "Filter by payee ID").option("--since <date>", "Filter transactions since date (YYYY-MM-DD)").option("--until <date>", "Filter transactions until date (YYYY-MM-DD)").option("--type <type>", "Filter by transaction type").option("--approved <value>", "Filter by approval status: true or false").option(
|
|
995
|
+
"--status <statuses>",
|
|
996
|
+
"Filter by cleared status: cleared, uncleared, reconciled (comma-separated for multiple)"
|
|
997
|
+
).option("--min-amount <amount>", "Minimum amount in currency units (e.g., 10.50)", parseFloat).option("--max-amount <amount>", "Maximum amount in currency units (e.g., 100.00)", parseFloat).option(
|
|
998
|
+
"--fields <fields>",
|
|
999
|
+
"Comma-separated list of fields to include (e.g., id,date,amount,memo)"
|
|
1000
|
+
).action(
|
|
1001
|
+
withErrorHandling(
|
|
1002
|
+
async (options) => {
|
|
1003
|
+
const params = {
|
|
1004
|
+
budgetId: options.budget,
|
|
1005
|
+
sinceDate: options.since,
|
|
1006
|
+
type: options.type
|
|
1007
|
+
};
|
|
1008
|
+
const result = options.account ? await client.getTransactionsByAccount(options.account, params) : options.category ? await client.getTransactionsByCategory(options.category, params) : options.payee ? await client.getTransactionsByPayee(options.payee, params) : await client.getTransactions(params);
|
|
1009
|
+
const transactions = result?.transactions || [];
|
|
1010
|
+
const filtered = applyTransactionFilters(transactions, {
|
|
1011
|
+
until: options.until,
|
|
1012
|
+
approved: options.approved,
|
|
1013
|
+
status: options.status,
|
|
1014
|
+
minAmount: options.minAmount,
|
|
1015
|
+
maxAmount: options.maxAmount
|
|
1016
|
+
});
|
|
1017
|
+
const selected = applyFieldSelection(filtered, options.fields);
|
|
1018
|
+
outputJson(selected);
|
|
1019
|
+
}
|
|
1020
|
+
)
|
|
1021
|
+
);
|
|
1022
|
+
cmd.command("view").description("View single transaction").argument("<id>", "Transaction ID").option("-b, --budget <id>", "Budget ID").action(
|
|
1023
|
+
withErrorHandling(async (id, options) => {
|
|
1024
|
+
const transaction = await client.getTransaction(id, options.budget);
|
|
1153
1025
|
outputJson(transaction);
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
cmd.command("
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1026
|
+
})
|
|
1027
|
+
);
|
|
1028
|
+
cmd.command("create").description("Create transaction").option("-b, --budget <id>", "Budget ID").option("--account <id>", "Account ID").option("--date <date>", "Date (YYYY-MM-DD)").option("--amount <amount>", "Amount in currency units (e.g., 10.50)", parseFloat).option("--payee-name <name>", "Payee name").option("--payee-id <id>", "Payee ID").option("--category-id <id>", "Category ID").option("--memo <memo>", "Memo").option("--cleared <status>", "Cleared status (cleared, uncleared, reconciled)").option("--approved", "Mark as approved").action(
|
|
1029
|
+
withErrorHandling(
|
|
1030
|
+
async (options) => {
|
|
1031
|
+
const shouldPrompt = isInteractive() && !options.amount;
|
|
1032
|
+
if (shouldPrompt && !options.account) {
|
|
1033
|
+
throw new YnabCliError(
|
|
1034
|
+
"--account is required. Interactive mode cannot auto-select an account.",
|
|
1035
|
+
400
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
const transactionData = shouldPrompt ? { ...await promptForTransaction(), account_id: options.account } : buildTransactionData(options);
|
|
1039
|
+
const transaction = await client.createTransaction(
|
|
1040
|
+
{ transaction: transactionData },
|
|
1041
|
+
options.budget
|
|
1042
|
+
);
|
|
1043
|
+
outputJson(transaction);
|
|
1044
|
+
}
|
|
1045
|
+
)
|
|
1046
|
+
);
|
|
1047
|
+
cmd.command("update").description("Update transaction").argument("<id>", "Transaction ID").option("-b, --budget <id>", "Budget ID").option("--account <id>", "Account ID").option("--date <date>", "Date (YYYY-MM-DD)").option("--amount <amount>", "Amount in currency units", parseFloat).option("--payee-name <name>", "Payee name").option("--payee-id <id>", "Payee ID").option("--category-id <id>", "Category ID").option("--memo <memo>", "Memo").option("--cleared <status>", "Cleared status").option("--approved", "Mark as approved").action(
|
|
1048
|
+
withErrorHandling(
|
|
1049
|
+
async (id, options) => {
|
|
1050
|
+
const transactionData = buildUpdateObject(options, {
|
|
1051
|
+
account: "account_id",
|
|
1052
|
+
date: "date",
|
|
1053
|
+
payeeName: "payee_name",
|
|
1054
|
+
payeeId: "payee_id",
|
|
1055
|
+
categoryId: "category_id",
|
|
1056
|
+
memo: "memo",
|
|
1057
|
+
cleared: "cleared",
|
|
1058
|
+
approved: "approved"
|
|
1059
|
+
});
|
|
1060
|
+
if (options.amount !== void 0) {
|
|
1061
|
+
transactionData.amount = amountToMilliunits(options.amount);
|
|
1062
|
+
}
|
|
1063
|
+
const transaction = await client.updateTransaction(
|
|
1064
|
+
id,
|
|
1065
|
+
{ transaction: transactionData },
|
|
1066
|
+
options.budget
|
|
1067
|
+
);
|
|
1068
|
+
outputJson(transaction);
|
|
1069
|
+
}
|
|
1070
|
+
)
|
|
1071
|
+
);
|
|
1072
|
+
cmd.command("delete").description("Delete transaction").argument("<id>", "Transaction ID").option("-b, --budget <id>", "Budget ID").option("-y, --yes", "Skip confirmation").action(
|
|
1073
|
+
withErrorHandling(
|
|
1074
|
+
async (id, options) => {
|
|
1075
|
+
if (!await confirmDelete("transaction", options.yes)) {
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
const transaction = await client.deleteTransaction(id, options.budget);
|
|
1079
|
+
outputJson({ message: "Transaction deleted", transaction });
|
|
1080
|
+
}
|
|
1081
|
+
)
|
|
1082
|
+
);
|
|
1083
|
+
cmd.command("import").description("Import transactions").option("-b, --budget <id>", "Budget ID").action(
|
|
1084
|
+
withErrorHandling(async (options) => {
|
|
1085
|
+
const transactionIds = await client.importTransactions(options.budget);
|
|
1086
|
+
outputJson({ transaction_ids: transactionIds });
|
|
1087
|
+
})
|
|
1088
|
+
);
|
|
1089
|
+
cmd.command("split").description(
|
|
1090
|
+
"Split transaction into multiple categories. Amounts should be in dollars (e.g., 10.50)."
|
|
1091
|
+
).argument("<id>", "Transaction ID").requiredOption(
|
|
1092
|
+
"--splits <json>",
|
|
1093
|
+
'JSON array of splits with dollar amounts: [{"amount": -21.40, "category_id": "xxx", "memo": "..."}]'
|
|
1094
|
+
).option("-b, --budget <id>", "Budget ID").option("-f, --force", "Force update of already-split transactions by deleting and recreating").action(
|
|
1095
|
+
withErrorHandling(
|
|
1096
|
+
async (id, options) => {
|
|
1097
|
+
let parsedSplits;
|
|
1098
|
+
try {
|
|
1099
|
+
parsedSplits = JSON.parse(options.splits);
|
|
1100
|
+
} catch {
|
|
1101
|
+
throw new YnabCliError("Invalid JSON in --splits parameter", 400);
|
|
1102
|
+
}
|
|
1103
|
+
const splits = validateJson(parsedSplits, TransactionSplitSchema, "transaction splits");
|
|
1104
|
+
const splitsInMilliunits = splits.map((split) => ({
|
|
1105
|
+
...split,
|
|
1106
|
+
amount: amountToMilliunits(split.amount)
|
|
1107
|
+
}));
|
|
1108
|
+
const existingTransaction = await client.getTransaction(id, options.budget);
|
|
1109
|
+
const isAlreadySplit = existingTransaction.subtransactions && existingTransaction.subtransactions.length > 0;
|
|
1110
|
+
if (isAlreadySplit && !options.force) {
|
|
1111
|
+
throw new YnabCliError(
|
|
1112
|
+
"Transaction is already split. YNAB API does not support updating split transactions. Use --force to delete and recreate the transaction with new splits.",
|
|
1113
|
+
400
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
if (isAlreadySplit) {
|
|
1117
|
+
await client.deleteTransaction(id, options.budget);
|
|
1118
|
+
const recreatedTransaction = await client.createTransaction(
|
|
1119
|
+
{
|
|
1120
|
+
transaction: {
|
|
1121
|
+
account_id: existingTransaction.account_id,
|
|
1122
|
+
date: existingTransaction.date,
|
|
1123
|
+
amount: existingTransaction.amount,
|
|
1124
|
+
payee_id: existingTransaction.payee_id,
|
|
1125
|
+
payee_name: existingTransaction.payee_name,
|
|
1126
|
+
category_id: null,
|
|
1127
|
+
memo: existingTransaction.memo,
|
|
1128
|
+
cleared: existingTransaction.cleared,
|
|
1129
|
+
approved: existingTransaction.approved,
|
|
1130
|
+
flag_color: existingTransaction.flag_color,
|
|
1131
|
+
subtransactions: splitsInMilliunits
|
|
1132
|
+
}
|
|
1133
|
+
},
|
|
1134
|
+
options.budget
|
|
1135
|
+
);
|
|
1136
|
+
outputJson(recreatedTransaction);
|
|
1137
|
+
} else {
|
|
1138
|
+
const transaction = await client.updateTransaction(
|
|
1139
|
+
id,
|
|
1140
|
+
{
|
|
1141
|
+
transaction: {
|
|
1142
|
+
category_id: null,
|
|
1143
|
+
subtransactions: splitsInMilliunits
|
|
1144
|
+
}
|
|
1145
|
+
},
|
|
1146
|
+
options.budget
|
|
1147
|
+
);
|
|
1148
|
+
outputJson(transaction);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
)
|
|
1152
|
+
);
|
|
1153
|
+
cmd.command("search").description("Search transactions").option("-b, --budget <id>", "Budget ID").option("--memo <text>", "Search in memo field").option("--payee-name <name>", "Search in payee name").option("--amount <amount>", "Search for exact amount in currency units", parseFloat).option("--since <date>", "Search transactions since date (YYYY-MM-DD)").option("--until <date>", "Search transactions until date (YYYY-MM-DD)").option("--approved <value>", "Filter by approval status: true or false").option(
|
|
1154
|
+
"--status <statuses>",
|
|
1155
|
+
"Filter by cleared status: cleared, uncleared, reconciled (comma-separated)"
|
|
1156
|
+
).option("--fields <fields>", "Comma-separated list of fields to include").action(
|
|
1157
|
+
withErrorHandling(
|
|
1158
|
+
async (options) => {
|
|
1159
|
+
if (!options.memo && !options.payeeName && options.amount === void 0) {
|
|
1160
|
+
throw new YnabCliError(
|
|
1161
|
+
"At least one search criteria required: --memo, --payee-name, or --amount",
|
|
1162
|
+
400
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
const params = {
|
|
1166
|
+
budgetId: options.budget,
|
|
1167
|
+
sinceDate: options.since
|
|
1168
|
+
};
|
|
1169
|
+
const result = await client.getTransactions(params);
|
|
1170
|
+
let transactions = result?.transactions || [];
|
|
1171
|
+
if (options.memo) {
|
|
1172
|
+
const searchTerm = options.memo.toLowerCase();
|
|
1173
|
+
transactions = transactions.filter((t) => t.memo?.toLowerCase().includes(searchTerm));
|
|
1174
|
+
}
|
|
1175
|
+
if (options.payeeName) {
|
|
1176
|
+
const searchTerm = options.payeeName.toLowerCase();
|
|
1177
|
+
transactions = transactions.filter(
|
|
1178
|
+
(t) => t.payee_name?.toLowerCase().includes(searchTerm)
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
if (options.amount !== void 0) {
|
|
1182
|
+
const amountMilliunits = amountToMilliunits(options.amount);
|
|
1183
|
+
transactions = transactions.filter((t) => t.amount === amountMilliunits);
|
|
1184
|
+
}
|
|
1185
|
+
transactions = applyTransactionFilters(transactions, {
|
|
1186
|
+
until: options.until,
|
|
1187
|
+
approved: options.approved,
|
|
1188
|
+
status: options.status
|
|
1189
|
+
});
|
|
1190
|
+
const filteredTransactions = applyFieldSelection(transactions, options.fields);
|
|
1191
|
+
outputJson(filteredTransactions);
|
|
1192
|
+
}
|
|
1193
|
+
)
|
|
1194
|
+
);
|
|
1190
1195
|
return cmd;
|
|
1191
1196
|
}
|
|
1192
1197
|
|
|
@@ -1194,24 +1199,37 @@ function createTransactionsCommand() {
|
|
|
1194
1199
|
import { Command as Command7 } from "commander";
|
|
1195
1200
|
function createScheduledCommand() {
|
|
1196
1201
|
const cmd = new Command7("scheduled").description("Scheduled transaction operations");
|
|
1197
|
-
cmd.command("list").description("List all scheduled transactions").option("-b, --budget <id>", "Budget ID").option("--last-knowledge <number>", "Last knowledge of server", parseInt).action(
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1202
|
+
cmd.command("list").description("List all scheduled transactions").option("-b, --budget <id>", "Budget ID").option("--last-knowledge <number>", "Last knowledge of server", parseInt).action(
|
|
1203
|
+
withErrorHandling(
|
|
1204
|
+
async (options) => {
|
|
1205
|
+
const result = await client.getScheduledTransactions(
|
|
1206
|
+
options.budget,
|
|
1207
|
+
options.lastKnowledge
|
|
1208
|
+
);
|
|
1209
|
+
outputJson(result?.scheduled_transactions);
|
|
1210
|
+
}
|
|
1211
|
+
)
|
|
1212
|
+
);
|
|
1213
|
+
cmd.command("view").description("View scheduled transaction").argument("<id>", "Scheduled transaction ID").option("-b, --budget <id>", "Budget ID").action(
|
|
1214
|
+
withErrorHandling(async (id, options) => {
|
|
1215
|
+
const scheduledTransaction = await client.getScheduledTransaction(id, options.budget);
|
|
1216
|
+
outputJson(scheduledTransaction);
|
|
1217
|
+
})
|
|
1218
|
+
);
|
|
1219
|
+
cmd.command("delete").description("Delete scheduled transaction").argument("<id>", "Scheduled transaction ID").option("-b, --budget <id>", "Budget ID").option("-y, --yes", "Skip confirmation").action(
|
|
1220
|
+
withErrorHandling(
|
|
1221
|
+
async (id, options) => {
|
|
1222
|
+
if (!await confirmDelete("scheduled transaction", options.yes)) {
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
const scheduledTransaction = await client.deleteScheduledTransaction(id, options.budget);
|
|
1226
|
+
outputJson({
|
|
1227
|
+
message: "Scheduled transaction deleted",
|
|
1228
|
+
scheduled_transaction: scheduledTransaction
|
|
1229
|
+
});
|
|
1230
|
+
}
|
|
1231
|
+
)
|
|
1232
|
+
);
|
|
1215
1233
|
return cmd;
|
|
1216
1234
|
}
|
|
1217
1235
|
|
|
@@ -1219,38 +1237,54 @@ function createScheduledCommand() {
|
|
|
1219
1237
|
import { Command as Command8 } from "commander";
|
|
1220
1238
|
function createPayeesCommand() {
|
|
1221
1239
|
const cmd = new Command8("payees").description("Payee operations");
|
|
1222
|
-
cmd.command("list").description("List all payees").option("-b, --budget <id>", "Budget ID").option("--last-knowledge <number>", "Last knowledge of server", parseInt).action(
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
cmd.command("
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1240
|
+
cmd.command("list").description("List all payees").option("-b, --budget <id>", "Budget ID").option("--last-knowledge <number>", "Last knowledge of server", parseInt).action(
|
|
1241
|
+
withErrorHandling(
|
|
1242
|
+
async (options) => {
|
|
1243
|
+
const result = await client.getPayees(options.budget, options.lastKnowledge);
|
|
1244
|
+
outputJson(result?.payees);
|
|
1245
|
+
}
|
|
1246
|
+
)
|
|
1247
|
+
);
|
|
1248
|
+
cmd.command("view").description("View payee details").argument("<id>", "Payee ID").option("-b, --budget <id>", "Budget ID").action(
|
|
1249
|
+
withErrorHandling(async (id, options) => {
|
|
1250
|
+
const payee = await client.getPayee(id, options.budget);
|
|
1251
|
+
outputJson(payee);
|
|
1252
|
+
})
|
|
1253
|
+
);
|
|
1254
|
+
cmd.command("update").description("Rename payee").argument("<id>", "Payee ID").requiredOption("--name <name>", "New payee name").option("-b, --budget <id>", "Budget ID").action(
|
|
1255
|
+
withErrorHandling(
|
|
1256
|
+
async (id, options) => {
|
|
1257
|
+
if (!options.name?.trim()) {
|
|
1258
|
+
throw new YnabCliError("Name cannot be empty", 400);
|
|
1259
|
+
}
|
|
1260
|
+
const payee = await client.updatePayee(
|
|
1261
|
+
id,
|
|
1262
|
+
{ payee: { name: options.name } },
|
|
1263
|
+
options.budget
|
|
1264
|
+
);
|
|
1265
|
+
outputJson(payee);
|
|
1266
|
+
}
|
|
1267
|
+
)
|
|
1268
|
+
);
|
|
1269
|
+
cmd.command("locations").description("List locations for payee").argument("<id>", "Payee ID").option("-b, --budget <id>", "Budget ID").action(
|
|
1270
|
+
withErrorHandling(async (id, options) => {
|
|
1271
|
+
const locations = await client.getPayeeLocationsByPayee(id, options.budget);
|
|
1272
|
+
outputJson(locations);
|
|
1273
|
+
})
|
|
1274
|
+
);
|
|
1275
|
+
cmd.command("transactions").description("List transactions for payee").argument("<id>", "Payee ID").option("-b, --budget <id>", "Budget ID").option("--since <date>", "Filter transactions since date (YYYY-MM-DD)").option("--type <type>", "Filter by transaction type").option("--last-knowledge <number>", "Last knowledge of server", parseInt).action(
|
|
1276
|
+
withErrorHandling(
|
|
1277
|
+
async (id, options) => {
|
|
1278
|
+
const result = await client.getTransactionsByPayee(id, {
|
|
1279
|
+
budgetId: options.budget,
|
|
1280
|
+
sinceDate: options.since,
|
|
1281
|
+
type: options.type,
|
|
1282
|
+
lastKnowledgeOfServer: options.lastKnowledge
|
|
1283
|
+
});
|
|
1284
|
+
outputJson(result?.transactions);
|
|
1285
|
+
}
|
|
1286
|
+
)
|
|
1287
|
+
);
|
|
1254
1288
|
return cmd;
|
|
1255
1289
|
}
|
|
1256
1290
|
|
|
@@ -1258,14 +1292,20 @@ function createPayeesCommand() {
|
|
|
1258
1292
|
import { Command as Command9 } from "commander";
|
|
1259
1293
|
function createMonthsCommand() {
|
|
1260
1294
|
const cmd = new Command9("months").description("Monthly budget operations");
|
|
1261
|
-
cmd.command("list").description("List all budget months").option("-b, --budget <id>", "Budget ID").option("--last-knowledge <number>", "Last knowledge of server", parseInt).action(
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1295
|
+
cmd.command("list").description("List all budget months").option("-b, --budget <id>", "Budget ID").option("--last-knowledge <number>", "Last knowledge of server", parseInt).action(
|
|
1296
|
+
withErrorHandling(
|
|
1297
|
+
async (options) => {
|
|
1298
|
+
const result = await client.getBudgetMonths(options.budget, options.lastKnowledge);
|
|
1299
|
+
outputJson(result?.months);
|
|
1300
|
+
}
|
|
1301
|
+
)
|
|
1302
|
+
);
|
|
1303
|
+
cmd.command("view").description("View specific month details").argument("<month>", "Month in YYYY-MM-DD format (e.g., 2025-07-01)").option("-b, --budget <id>", "Budget ID").action(
|
|
1304
|
+
withErrorHandling(async (month, options) => {
|
|
1305
|
+
const monthData = await client.getBudgetMonth(month, options.budget);
|
|
1306
|
+
outputJson(monthData);
|
|
1307
|
+
})
|
|
1308
|
+
);
|
|
1269
1309
|
return cmd;
|
|
1270
1310
|
}
|
|
1271
1311
|
|
|
@@ -1274,32 +1314,36 @@ import { Command as Command10 } from "commander";
|
|
|
1274
1314
|
var VALID_HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
1275
1315
|
function createApiCommand() {
|
|
1276
1316
|
const cmd = new Command10("api").description("Raw API access");
|
|
1277
|
-
cmd.argument("<method>", "HTTP method (GET, POST, PUT, PATCH, DELETE)").argument("<path>", "API path (e.g., /budgets or /budgets/{budget_id}/transactions)").option("-b, --budget <id>", "Budget ID (used to replace {budget_id} in path)").option("--data <json>", "JSON data for POST/PUT/PATCH requests").description("Make raw API calls to YNAB").action(
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1317
|
+
cmd.argument("<method>", "HTTP method (GET, POST, PUT, PATCH, DELETE)").argument("<path>", "API path (e.g., /budgets or /budgets/{budget_id}/transactions)").option("-b, --budget <id>", "Budget ID (used to replace {budget_id} in path)").option("--data <json>", "JSON data for POST/PUT/PATCH requests").description("Make raw API calls to YNAB").action(
|
|
1318
|
+
withErrorHandling(
|
|
1319
|
+
async (method, path, options) => {
|
|
1320
|
+
const upperMethod = method.toUpperCase();
|
|
1321
|
+
if (!VALID_HTTP_METHODS.includes(upperMethod)) {
|
|
1322
|
+
throw new YnabCliError(
|
|
1323
|
+
`Invalid HTTP method: ${method}. Must be one of: ${VALID_HTTP_METHODS.join(", ")}`,
|
|
1324
|
+
400
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
let data;
|
|
1328
|
+
if (options.data) {
|
|
1329
|
+
try {
|
|
1330
|
+
const parsedData = JSON.parse(options.data);
|
|
1331
|
+
data = validateJson(parsedData, ApiDataSchema, "API data");
|
|
1332
|
+
} catch {
|
|
1333
|
+
throw new YnabCliError("Invalid JSON in --data parameter", 400);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
const result = await client.rawApiCall(upperMethod, path, data, options.budget);
|
|
1337
|
+
outputJson(result);
|
|
1292
1338
|
}
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
outputJson(result);
|
|
1296
|
-
}));
|
|
1339
|
+
)
|
|
1340
|
+
);
|
|
1297
1341
|
return cmd;
|
|
1298
1342
|
}
|
|
1299
1343
|
|
|
1300
1344
|
// src/cli.ts
|
|
1301
1345
|
var program = new Command11();
|
|
1302
|
-
program.name("ynab").description("A command-line interface for You Need a Budget (YNAB)").version("1.2.
|
|
1346
|
+
program.name("ynab").description("A command-line interface for You Need a Budget (YNAB)").version("1.2.6").option("-c, --compact", "Minified JSON output (single line)").hook("preAction", (thisCommand) => {
|
|
1303
1347
|
const options = thisCommand.opts();
|
|
1304
1348
|
setOutputOptions({
|
|
1305
1349
|
compact: options.compact
|