gws-axi 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -0
  3. package/dist/bin/gws-axi.d.ts +2 -0
  4. package/dist/bin/gws-axi.js +4 -0
  5. package/dist/bin/gws-axi.js.map +1 -0
  6. package/dist/src/auth/gcloud.d.ts +21 -0
  7. package/dist/src/auth/gcloud.js +134 -0
  8. package/dist/src/auth/gcloud.js.map +1 -0
  9. package/dist/src/auth/loopback.d.ts +50 -0
  10. package/dist/src/auth/loopback.js +420 -0
  11. package/dist/src/auth/loopback.js.map +1 -0
  12. package/dist/src/auth/pending.d.ts +15 -0
  13. package/dist/src/auth/pending.js +35 -0
  14. package/dist/src/auth/pending.js.map +1 -0
  15. package/dist/src/auth/scopes.d.ts +13 -0
  16. package/dist/src/auth/scopes.js +29 -0
  17. package/dist/src/auth/scopes.js.map +1 -0
  18. package/dist/src/auth/setup-html.d.ts +13 -0
  19. package/dist/src/auth/setup-html.js +245 -0
  20. package/dist/src/auth/setup-html.js.map +1 -0
  21. package/dist/src/auth/state.d.ts +4 -0
  22. package/dist/src/auth/state.js +41 -0
  23. package/dist/src/auth/state.js.map +1 -0
  24. package/dist/src/auth/steps.d.ts +30 -0
  25. package/dist/src/auth/steps.js +306 -0
  26. package/dist/src/auth/steps.js.map +1 -0
  27. package/dist/src/cli.d.ts +2 -0
  28. package/dist/src/cli.js +72 -0
  29. package/dist/src/cli.js.map +1 -0
  30. package/dist/src/commands/auth.d.ts +2 -0
  31. package/dist/src/commands/auth.js +453 -0
  32. package/dist/src/commands/auth.js.map +1 -0
  33. package/dist/src/commands/calendar/calendars.d.ts +2 -0
  34. package/dist/src/commands/calendar/calendars.js +98 -0
  35. package/dist/src/commands/calendar/calendars.js.map +1 -0
  36. package/dist/src/commands/calendar/events.d.ts +2 -0
  37. package/dist/src/commands/calendar/events.js +261 -0
  38. package/dist/src/commands/calendar/events.js.map +1 -0
  39. package/dist/src/commands/calendar/get.d.ts +2 -0
  40. package/dist/src/commands/calendar/get.js +176 -0
  41. package/dist/src/commands/calendar/get.js.map +1 -0
  42. package/dist/src/commands/calendar.d.ts +2 -0
  43. package/dist/src/commands/calendar.js +76 -0
  44. package/dist/src/commands/calendar.js.map +1 -0
  45. package/dist/src/commands/docs.d.ts +2 -0
  46. package/dist/src/commands/docs.js +19 -0
  47. package/dist/src/commands/docs.js.map +1 -0
  48. package/dist/src/commands/doctor.d.ts +2 -0
  49. package/dist/src/commands/doctor.js +213 -0
  50. package/dist/src/commands/doctor.js.map +1 -0
  51. package/dist/src/commands/drive.d.ts +2 -0
  52. package/dist/src/commands/drive.js +17 -0
  53. package/dist/src/commands/drive.js.map +1 -0
  54. package/dist/src/commands/gmail.d.ts +2 -0
  55. package/dist/src/commands/gmail.js +20 -0
  56. package/dist/src/commands/gmail.js.map +1 -0
  57. package/dist/src/commands/home.d.ts +1 -0
  58. package/dist/src/commands/home.js +42 -0
  59. package/dist/src/commands/home.js.map +1 -0
  60. package/dist/src/commands/service-stub.d.ts +8 -0
  61. package/dist/src/commands/service-stub.js +67 -0
  62. package/dist/src/commands/service-stub.js.map +1 -0
  63. package/dist/src/commands/slides.d.ts +2 -0
  64. package/dist/src/commands/slides.js +11 -0
  65. package/dist/src/commands/slides.js.map +1 -0
  66. package/dist/src/config.d.ts +46 -0
  67. package/dist/src/config.js +155 -0
  68. package/dist/src/config.js.map +1 -0
  69. package/dist/src/google/account.d.ts +12 -0
  70. package/dist/src/google/account.js +65 -0
  71. package/dist/src/google/account.js.map +1 -0
  72. package/dist/src/google/client.d.ts +29 -0
  73. package/dist/src/google/client.js +106 -0
  74. package/dist/src/google/client.js.map +1 -0
  75. package/dist/src/google/probe.d.ts +15 -0
  76. package/dist/src/google/probe.js +200 -0
  77. package/dist/src/google/probe.js.map +1 -0
  78. package/dist/src/google/tokens.d.ts +22 -0
  79. package/dist/src/google/tokens.js +91 -0
  80. package/dist/src/google/tokens.js.map +1 -0
  81. package/dist/src/output/index.d.ts +2 -0
  82. package/dist/src/output/index.js +3 -0
  83. package/dist/src/output/index.js.map +1 -0
  84. package/dist/src/output/render.d.ts +40 -0
  85. package/dist/src/output/render.js +65 -0
  86. package/dist/src/output/render.js.map +1 -0
  87. package/dist/src/output/schema.d.ts +32 -0
  88. package/dist/src/output/schema.js +69 -0
  89. package/dist/src/output/schema.js.map +1 -0
  90. package/package.json +61 -0
@@ -0,0 +1,155 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync, rmSync, statSync, } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ export const SETUP_VERSION = 1;
5
+ export const CONFIG_VERSION = 1;
6
+ const SETUP_STEP_ORDER = [
7
+ "gcp_project",
8
+ "apis_enabled",
9
+ "oauth_client",
10
+ "credentials_saved",
11
+ "consent_screen",
12
+ "test_user_added",
13
+ "tokens_obtained",
14
+ ];
15
+ // Paths ────────────────────────────────────────────────────────────
16
+ export function configDir() {
17
+ const xdg = process.env.XDG_CONFIG_HOME;
18
+ return xdg ? join(xdg, "gws-axi") : join(homedir(), ".config", "gws-axi");
19
+ }
20
+ export function setupStatePath() {
21
+ return join(configDir(), "setup.json");
22
+ }
23
+ export function credentialsPath() {
24
+ return join(configDir(), "credentials.json");
25
+ }
26
+ export function userConfigPath() {
27
+ return join(configDir(), "config.json");
28
+ }
29
+ export function accountsDir() {
30
+ return join(configDir(), "accounts");
31
+ }
32
+ export function accountDir(email) {
33
+ return join(accountsDir(), normalizeEmail(email));
34
+ }
35
+ export function tokensPathForAccount(email) {
36
+ return join(accountDir(email), "tokens.json");
37
+ }
38
+ export function profilePathForAccount(email) {
39
+ return join(accountDir(email), "profile.json");
40
+ }
41
+ export function normalizeEmail(email) {
42
+ return email.trim().toLowerCase();
43
+ }
44
+ // Setup state ──────────────────────────────────────────────────────
45
+ export function defaultSetupState() {
46
+ return {
47
+ version: SETUP_VERSION,
48
+ auth_mode: "byo",
49
+ steps: Object.fromEntries(SETUP_STEP_ORDER.map((key) => [key, { done: false }])),
50
+ };
51
+ }
52
+ export function readSetupState() {
53
+ const path = setupStatePath();
54
+ if (!existsSync(path)) {
55
+ return defaultSetupState();
56
+ }
57
+ try {
58
+ return JSON.parse(readFileSync(path, "utf-8"));
59
+ }
60
+ catch {
61
+ return defaultSetupState();
62
+ }
63
+ }
64
+ export function writeSetupState(state) {
65
+ const dir = configDir();
66
+ if (!existsSync(dir)) {
67
+ mkdirSync(dir, { recursive: true });
68
+ }
69
+ writeFileSync(setupStatePath(), `${JSON.stringify(state, null, 2)}\n`);
70
+ }
71
+ export function setupProgress(state) {
72
+ let done = 0;
73
+ let nextStep = null;
74
+ for (const key of SETUP_STEP_ORDER) {
75
+ if (state.steps[key].done) {
76
+ done++;
77
+ }
78
+ else if (nextStep === null) {
79
+ nextStep = key;
80
+ }
81
+ }
82
+ return { done, total: SETUP_STEP_ORDER.length, nextStep };
83
+ }
84
+ // User config (default_account, etc.) ──────────────────────────────
85
+ export function defaultUserConfig() {
86
+ return { version: CONFIG_VERSION };
87
+ }
88
+ export function readUserConfig() {
89
+ const path = userConfigPath();
90
+ if (!existsSync(path))
91
+ return defaultUserConfig();
92
+ try {
93
+ return JSON.parse(readFileSync(path, "utf-8"));
94
+ }
95
+ catch {
96
+ return defaultUserConfig();
97
+ }
98
+ }
99
+ export function writeUserConfig(cfg) {
100
+ const dir = configDir();
101
+ if (!existsSync(dir)) {
102
+ mkdirSync(dir, { recursive: true });
103
+ }
104
+ writeFileSync(userConfigPath(), `${JSON.stringify(cfg, null, 2)}\n`);
105
+ }
106
+ // Accounts ─────────────────────────────────────────────────────────
107
+ export function listAccounts() {
108
+ const dir = accountsDir();
109
+ if (!existsSync(dir))
110
+ return [];
111
+ try {
112
+ return readdirSync(dir)
113
+ .filter((name) => {
114
+ const p = join(dir, name);
115
+ return (statSync(p).isDirectory() && existsSync(join(p, "tokens.json")));
116
+ })
117
+ .map((name) => name.toLowerCase())
118
+ .sort();
119
+ }
120
+ catch {
121
+ return [];
122
+ }
123
+ }
124
+ export function hasAccount(email) {
125
+ return existsSync(tokensPathForAccount(email));
126
+ }
127
+ export function removeAccount(email) {
128
+ const dir = accountDir(email);
129
+ if (existsSync(dir)) {
130
+ rmSync(dir, { recursive: true, force: true });
131
+ }
132
+ const cfg = readUserConfig();
133
+ if (cfg.default_account === normalizeEmail(email)) {
134
+ const remaining = listAccounts();
135
+ cfg.default_account = remaining[0];
136
+ writeUserConfig(cfg);
137
+ }
138
+ }
139
+ export function getDefaultAccount() {
140
+ const cfg = readUserConfig();
141
+ if (cfg.default_account && hasAccount(cfg.default_account)) {
142
+ return cfg.default_account;
143
+ }
144
+ const accounts = listAccounts();
145
+ if (accounts.length === 1)
146
+ return accounts[0];
147
+ return undefined;
148
+ }
149
+ export function setDefaultAccount(email) {
150
+ const cfg = readUserConfig();
151
+ cfg.default_account = normalizeEmail(email);
152
+ writeUserConfig(cfg);
153
+ }
154
+ export { SETUP_STEP_ORDER };
155
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,SAAS,EACT,YAAY,EACZ,WAAW,EACX,aAAa,EACb,MAAM,EACN,QAAQ,GACT,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,CAAC;AAC/B,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AA8BhC,MAAM,gBAAgB,GAAmB;IACvC,aAAa;IACb,cAAc;IACd,cAAc;IACd,mBAAmB;IACnB,gBAAgB;IAChB,iBAAiB;IACjB,iBAAiB;CAClB,CAAC;AAEF,qEAAqE;AACrE,MAAM,UAAU,SAAS;IACvB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;IACxC,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,SAAS,CAAC,CAAC;AAC5E,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,YAAY,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,kBAAkB,CAAC,CAAC;AAC/C,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,aAAa,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,IAAI,CAAC,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,IAAI,CAAC,WAAW,EAAE,EAAE,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;AACpD,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,OAAO,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AACpC,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,OAAO,EAAE,aAAa;QACtB,SAAS,EAAE,KAAK;QAChB,KAAK,EAAE,MAAM,CAAC,WAAW,CACvB,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CACnB;KACrC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,iBAAiB,EAAE,CAAC;IAC7B,CAAC;IAED,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAe,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,iBAAiB,EAAE,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAiB;IAC/C,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;IACD,aAAa,CAAC,cAAc,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAiB;IAK7C,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,QAAQ,GAAwB,IAAI,CAAC;IACzC,KAAK,MAAM,GAAG,IAAI,gBAAgB,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;YAC1B,IAAI,EAAE,CAAC;QACT,CAAC;aAAM,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YAC7B,QAAQ,GAAG,GAAG,CAAC;QACjB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,gBAAgB,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC;AAC5D,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,iBAAiB;IAC/B,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,cAAc;IAC5B,MAAM,IAAI,GAAG,cAAc,EAAE,CAAC;IAC9B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,iBAAiB,EAAE,CAAC;IAClD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAe,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,iBAAiB,EAAE,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,GAAe;IAC7C,MAAM,GAAG,GAAG,SAAS,EAAE,CAAC;IACxB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;IACD,aAAa,CAAC,cAAc,EAAE,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AACvE,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,YAAY;IAC1B,MAAM,GAAG,GAAG,WAAW,EAAE,CAAC;IAC1B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,GAAG,CAAC;aACpB,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;YACf,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;YAC1B,OAAO,CACL,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,aAAa,CAAC,CAAC,CAChE,CAAC;QACJ,CAAC,CAAC;aACD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;aACjC,IAAI,EAAE,CAAC;IACZ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,UAAU,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,CAAC;IACD,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,IAAI,GAAG,CAAC,eAAe,KAAK,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;QAClD,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;QACjC,GAAG,CAAC,eAAe,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC;QACnC,eAAe,CAAC,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,IAAI,GAAG,CAAC,eAAe,IAAI,UAAU,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC;QAC3D,OAAO,GAAG,CAAC,eAAe,CAAC;IAC7B,CAAC;IACD,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC9C,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,MAAM,GAAG,GAAG,cAAc,EAAE,CAAC;IAC7B,GAAG,CAAC,eAAe,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IAC5C,eAAe,CAAC,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,12 @@
1
+ export interface ResolveAccountOptions {
2
+ mutation: boolean;
3
+ commandName: string;
4
+ }
5
+ export interface AccountResolution {
6
+ account: string;
7
+ totalAccounts: number;
8
+ defaultAccount?: string;
9
+ explicit: boolean;
10
+ }
11
+ export declare function resolveAccount(requestedAccount: string | undefined, options: ResolveAccountOptions): AccountResolution;
12
+ export declare function accountHeaderFields(resolution: AccountResolution): Record<string, unknown>;
@@ -0,0 +1,65 @@
1
+ import { AxiError } from "axi-sdk-js";
2
+ import { getDefaultAccount, hasAccount, listAccounts, normalizeEmail, } from "../config.js";
3
+ export function resolveAccount(requestedAccount, options) {
4
+ const accounts = listAccounts();
5
+ if (accounts.length === 0) {
6
+ throw new AxiError("No accounts authenticated", "NO_ACCOUNTS", [
7
+ "Run `gws-axi auth setup` to configure OAuth and authenticate an account",
8
+ "Run `gws-axi auth login` if setup is already complete",
9
+ ]);
10
+ }
11
+ const defaultAccount = getDefaultAccount();
12
+ if (requestedAccount) {
13
+ const normalized = normalizeEmail(requestedAccount);
14
+ if (!hasAccount(normalized)) {
15
+ throw new AxiError(`Account ${normalized} is not authenticated`, "ACCOUNT_NOT_FOUND", [
16
+ `Authenticated accounts: ${accounts.join(", ")}`,
17
+ `Run \`gws-axi auth login --account ${normalized}\` to add it`,
18
+ "Run `gws-axi auth accounts` to see all authenticated accounts",
19
+ ]);
20
+ }
21
+ return {
22
+ account: normalized,
23
+ totalAccounts: accounts.length,
24
+ defaultAccount,
25
+ explicit: true,
26
+ };
27
+ }
28
+ if (accounts.length === 1) {
29
+ return {
30
+ account: accounts[0],
31
+ totalAccounts: 1,
32
+ defaultAccount,
33
+ explicit: false,
34
+ };
35
+ }
36
+ if (options.mutation) {
37
+ throw new AxiError(`\`${options.commandName}\` mutates state; --account is required when multiple accounts are authenticated`, "ACCOUNT_REQUIRED", [
38
+ `Authenticated accounts: ${accounts.join(", ")}`,
39
+ `Default account: ${defaultAccount ?? "(none set)"}`,
40
+ `Run with --account ${defaultAccount ?? accounts[0]} to use ${defaultAccount ? "the default" : "an account"}`,
41
+ "Multi-account write protection prevents silent wrong-account mutations",
42
+ ]);
43
+ }
44
+ if (!defaultAccount) {
45
+ throw new AxiError("No default account set", "NO_DEFAULT_ACCOUNT", [
46
+ `Authenticated accounts: ${accounts.join(", ")}`,
47
+ `Run \`gws-axi auth use <email>\` to set a default`,
48
+ "Or pass --account <email> to this command",
49
+ ]);
50
+ }
51
+ return {
52
+ account: defaultAccount,
53
+ totalAccounts: accounts.length,
54
+ defaultAccount,
55
+ explicit: false,
56
+ };
57
+ }
58
+ export function accountHeaderFields(resolution) {
59
+ const fields = { account: resolution.account };
60
+ if (resolution.totalAccounts > 1 && !resolution.explicit) {
61
+ fields.account_source = "default";
62
+ }
63
+ return fields;
64
+ }
65
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../../../src/google/account.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EACL,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,cAAc,GACf,MAAM,cAAc,CAAC;AActB,MAAM,UAAU,cAAc,CAC5B,gBAAoC,EACpC,OAA8B;IAE9B,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAEhC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,QAAQ,CAChB,2BAA2B,EAC3B,aAAa,EACb;YACE,yEAAyE;YACzE,uDAAuD;SACxD,CACF,CAAC;IACJ,CAAC;IAED,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAC;IAE3C,IAAI,gBAAgB,EAAE,CAAC;QACrB,MAAM,UAAU,GAAG,cAAc,CAAC,gBAAgB,CAAC,CAAC;QACpD,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,QAAQ,CAChB,WAAW,UAAU,uBAAuB,EAC5C,mBAAmB,EACnB;gBACE,2BAA2B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;gBAChD,sCAAsC,UAAU,cAAc;gBAC9D,+DAA+D;aAChE,CACF,CAAC;QACJ,CAAC;QACD,OAAO;YACL,OAAO,EAAE,UAAU;YACnB,aAAa,EAAE,QAAQ,CAAC,MAAM;YAC9B,cAAc;YACd,QAAQ,EAAE,IAAI;SACf,CAAC;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO;YACL,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC;YACpB,aAAa,EAAE,CAAC;YAChB,cAAc;YACd,QAAQ,EAAE,KAAK;SAChB,CAAC;IACJ,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,MAAM,IAAI,QAAQ,CAChB,KAAK,OAAO,CAAC,WAAW,kFAAkF,EAC1G,kBAAkB,EAClB;YACE,2BAA2B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAChD,oBAAoB,cAAc,IAAI,YAAY,EAAE;YACpD,sBAAsB,cAAc,IAAI,QAAQ,CAAC,CAAC,CAAC,WAAW,cAAc,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,YAAY,EAAE;YAC7G,wEAAwE;SACzE,CACF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,QAAQ,CAChB,wBAAwB,EACxB,oBAAoB,EACpB;YACE,2BAA2B,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;YAChD,mDAAmD;YACnD,2CAA2C;SAC5C,CACF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,cAAc;QACvB,aAAa,EAAE,QAAQ,CAAC,MAAM;QAC9B,cAAc;QACd,QAAQ,EAAE,KAAK;KAChB,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,UAA6B;IAE7B,MAAM,MAAM,GAA4B,EAAE,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,CAAC;IACxE,IAAI,UAAU,CAAC,aAAa,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;QACzD,MAAM,CAAC,cAAc,GAAG,SAAS,CAAC;IACpC,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { AxiError } from "axi-sdk-js";
2
+ import { type calendar_v3, type docs_v1, type drive_v3, type gmail_v1, type slides_v1 } from "googleapis";
3
+ import { OAuth2Client } from "google-auth-library";
4
+ /**
5
+ * Build an OAuth2Client seeded with the stored tokens for `email`. Uses
6
+ * google-auth-library's refresh mechanism via the refresh_token so the
7
+ * client auto-refreshes mid-request if the access token hits 401. We also
8
+ * set expiry_date so the library proactively refreshes before 401s.
9
+ */
10
+ export declare function oauthClientForAccount(email: string): Promise<OAuth2Client>;
11
+ export declare function calendarClient(email: string): Promise<calendar_v3.Calendar>;
12
+ export declare function gmailClient(email: string): Promise<gmail_v1.Gmail>;
13
+ export declare function docsClient(email: string): Promise<docs_v1.Docs>;
14
+ export declare function driveClient(email: string): Promise<drive_v3.Drive>;
15
+ export declare function slidesClient(email: string): Promise<slides_v1.Slides>;
16
+ /**
17
+ * Translate a Google API error into an AxiError with actionable suggestions.
18
+ * Caller context (account, operation name) improves the suggestions.
19
+ */
20
+ export declare function translateGoogleError(err: unknown, context: {
21
+ account: string;
22
+ operation: string;
23
+ }): AxiError;
24
+ /**
25
+ * Convenience wrapper: run a Google API call for `email` and translate any
26
+ * thrown error. Callers pass the operation name (e.g. "calendar.events.list")
27
+ * so error messages are specific.
28
+ */
29
+ export declare function runGoogleApi<T>(email: string, operation: string, fn: (auth: OAuth2Client) => Promise<T>): Promise<T>;
@@ -0,0 +1,106 @@
1
+ import { AxiError } from "axi-sdk-js";
2
+ import { google } from "googleapis";
3
+ import { OAuth2Client } from "google-auth-library";
4
+ import { getValidAccessToken } from "./tokens.js";
5
+ /**
6
+ * Build an OAuth2Client seeded with the stored tokens for `email`. Uses
7
+ * google-auth-library's refresh mechanism via the refresh_token so the
8
+ * client auto-refreshes mid-request if the access token hits 401. We also
9
+ * set expiry_date so the library proactively refreshes before 401s.
10
+ */
11
+ export async function oauthClientForAccount(email) {
12
+ const tokens = await getValidAccessToken(email);
13
+ const auth = new OAuth2Client();
14
+ auth.setCredentials({
15
+ access_token: tokens.access_token,
16
+ refresh_token: tokens.refresh_token,
17
+ expiry_date: tokens.expiry_date,
18
+ scope: tokens.scope,
19
+ token_type: tokens.token_type,
20
+ });
21
+ return auth;
22
+ }
23
+ export async function calendarClient(email) {
24
+ const auth = await oauthClientForAccount(email);
25
+ return google.calendar({ version: "v3", auth });
26
+ }
27
+ export async function gmailClient(email) {
28
+ const auth = await oauthClientForAccount(email);
29
+ return google.gmail({ version: "v1", auth });
30
+ }
31
+ export async function docsClient(email) {
32
+ const auth = await oauthClientForAccount(email);
33
+ return google.docs({ version: "v1", auth });
34
+ }
35
+ export async function driveClient(email) {
36
+ const auth = await oauthClientForAccount(email);
37
+ return google.drive({ version: "v3", auth });
38
+ }
39
+ export async function slidesClient(email) {
40
+ const auth = await oauthClientForAccount(email);
41
+ return google.slides({ version: "v1", auth });
42
+ }
43
+ function extractError(err) {
44
+ const e = err;
45
+ const apiError = e.response?.data?.error;
46
+ const code = apiError?.code ?? e.code ?? (e.response?.status ?? 0);
47
+ const message = apiError?.message ?? e.message ?? (err instanceof Error ? err.message : String(err));
48
+ const reason = apiError?.errors?.[0]?.reason ?? e.errors?.[0]?.reason;
49
+ const status = apiError?.status;
50
+ return { code, message, reason, status };
51
+ }
52
+ /**
53
+ * Translate a Google API error into an AxiError with actionable suggestions.
54
+ * Caller context (account, operation name) improves the suggestions.
55
+ */
56
+ export function translateGoogleError(err, context) {
57
+ const { code, message, reason, status } = extractError(err);
58
+ const { account, operation } = context;
59
+ if (code === 401 || status === "UNAUTHENTICATED") {
60
+ return new AxiError(`Authentication failed for ${account} — token revoked or invalid`, "TOKEN_INVALID", [
61
+ `Run \`gws-axi auth login --account ${account}\` to re-authenticate`,
62
+ "Then run `gws-axi auth login --wait` to complete the OAuth flow",
63
+ ]);
64
+ }
65
+ if (code === 403 || status === "PERMISSION_DENIED") {
66
+ if (reason === "insufficientPermissions" ||
67
+ reason === "ACCESS_TOKEN_SCOPE_INSUFFICIENT" ||
68
+ /insufficient scope|scope/i.test(message)) {
69
+ return new AxiError(`Insufficient scope for ${operation} — this account needs to re-consent`, "SCOPE_MISSING", [
70
+ `Run \`gws-axi auth login --account ${account}\` to re-consent to the full scope set`,
71
+ ]);
72
+ }
73
+ if (reason === "accessNotConfigured" ||
74
+ /has not been used|not enabled|API is disabled/i.test(message)) {
75
+ return new AxiError(`API not enabled in the GCP project backing this OAuth client`, "API_NOT_ENABLED", [
76
+ "Re-run `gws-axi auth setup` — the step 2 API-enable flow will fix this",
77
+ ]);
78
+ }
79
+ return new AxiError(`Forbidden: ${message}`, "FORBIDDEN", reason ? [`Reason: ${reason}`] : []);
80
+ }
81
+ if (code === 404 || status === "NOT_FOUND") {
82
+ return new AxiError(message || "not found", "NOT_FOUND", []);
83
+ }
84
+ if (code === 429 || status === "RESOURCE_EXHAUSTED") {
85
+ return new AxiError(`Rate limit exceeded for ${operation}`, "RATE_LIMITED", ["Retry after a short wait"]);
86
+ }
87
+ if (code >= 500) {
88
+ return new AxiError(`Google server error (${code}) on ${operation}: ${message}`, "SERVER_ERROR", ["Retry after a moment"]);
89
+ }
90
+ return new AxiError(message || `Unknown error on ${operation}`, code ? `GOOGLE_API_ERROR_${code}` : "UNKNOWN_ERROR", reason ? [`Reason: ${reason}`] : []);
91
+ }
92
+ /**
93
+ * Convenience wrapper: run a Google API call for `email` and translate any
94
+ * thrown error. Callers pass the operation name (e.g. "calendar.events.list")
95
+ * so error messages are specific.
96
+ */
97
+ export async function runGoogleApi(email, operation, fn) {
98
+ const auth = await oauthClientForAccount(email);
99
+ try {
100
+ return await fn(auth);
101
+ }
102
+ catch (err) {
103
+ throw translateGoogleError(err, { account: email, operation });
104
+ }
105
+ }
106
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/google/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { type ServiceName } from "../auth/scopes.js";
2
+ export interface ProbeResult {
3
+ service: ServiceName;
4
+ status: "ok" | "warn" | "fail";
5
+ detail: string;
6
+ }
7
+ /**
8
+ * Run all service probes for a single account in parallel (where they're
9
+ * independent) and return results keyed by service name.
10
+ */
11
+ export declare function probeAccount(email: string): Promise<{
12
+ service: ServiceName;
13
+ status: "ok" | "warn" | "fail";
14
+ detail: string;
15
+ }[]>;
@@ -0,0 +1,200 @@
1
+ import { SERVICE_SCOPES } from "../auth/scopes.js";
2
+ import { getValidAccessToken } from "./tokens.js";
3
+ function hasScope(tokens, scope) {
4
+ const granted = tokens.scope.split(" ").filter(Boolean);
5
+ return granted.includes(scope);
6
+ }
7
+ async function gfetch(url, accessToken) {
8
+ const res = await fetch(url, {
9
+ headers: { authorization: `Bearer ${accessToken}` },
10
+ });
11
+ if (res.status === 204)
12
+ return { status: 204, body: null };
13
+ const text = await res.text();
14
+ let body = text;
15
+ try {
16
+ body = JSON.parse(text);
17
+ }
18
+ catch {
19
+ // leave as text
20
+ }
21
+ return { status: res.status, body, text };
22
+ }
23
+ function classifyError(service, status, body) {
24
+ const err = body
25
+ ?.error;
26
+ const message = err?.message ?? "";
27
+ if (status === 401) {
28
+ return {
29
+ service,
30
+ status: "fail",
31
+ detail: `401 unauthorized — token revoked or invalid (${message})`,
32
+ };
33
+ }
34
+ if (status === 403) {
35
+ if (/scope|insufficient/i.test(message)) {
36
+ return {
37
+ service,
38
+ status: "fail",
39
+ detail: `403 insufficient_scope — ${service} scope not granted`,
40
+ };
41
+ }
42
+ if (/disabled|not enabled/i.test(message)) {
43
+ return {
44
+ service,
45
+ status: "fail",
46
+ detail: `403 api_not_enabled — enable the ${service} API in the Console`,
47
+ };
48
+ }
49
+ return { service, status: "fail", detail: `403 forbidden — ${message}` };
50
+ }
51
+ if (status === 429) {
52
+ return { service, status: "warn", detail: `429 rate_limited — try again shortly` };
53
+ }
54
+ if (status >= 500) {
55
+ return { service, status: "warn", detail: `${status} server_error — Google side` };
56
+ }
57
+ return {
58
+ service,
59
+ status: "fail",
60
+ detail: `${status} ${message || "unexpected error"}`,
61
+ };
62
+ }
63
+ async function probeGmail(ctx) {
64
+ const service = "gmail";
65
+ if (!hasScope(ctx.tokens, SERVICE_SCOPES.gmail)) {
66
+ return { service, status: "fail", detail: "scope not granted" };
67
+ }
68
+ const { status, body } = await gfetch("https://gmail.googleapis.com/gmail/v1/users/me/profile", ctx.accessToken);
69
+ if (status === 200) {
70
+ const profile = body;
71
+ return {
72
+ service,
73
+ status: "ok",
74
+ detail: `${profile.emailAddress ?? ctx.email}${profile.messagesTotal !== undefined ? ` · ${profile.messagesTotal} messages` : ""}`,
75
+ };
76
+ }
77
+ return classifyError(service, status, body);
78
+ }
79
+ async function probeCalendar(ctx) {
80
+ const service = "calendar";
81
+ if (!hasScope(ctx.tokens, SERVICE_SCOPES.calendar)) {
82
+ return { service, status: "fail", detail: "scope not granted" };
83
+ }
84
+ const { status, body } = await gfetch("https://www.googleapis.com/calendar/v3/users/me/calendarList?maxResults=1&fields=items(id),nextPageToken", ctx.accessToken);
85
+ if (status === 200) {
86
+ // API doesn't directly give a total count; do a second tiny call to count
87
+ // all accessible calendars. Skip if the first call already showed none.
88
+ const first = body;
89
+ if (!first.items?.length) {
90
+ return { service, status: "ok", detail: "0 calendars" };
91
+ }
92
+ // Second call to get full count cheaply — fields filter keeps payload tiny
93
+ const { status: s2, body: b2 } = await gfetch("https://www.googleapis.com/calendar/v3/users/me/calendarList?fields=items(id)", ctx.accessToken);
94
+ if (s2 === 200) {
95
+ const count = (b2.items ?? []).length;
96
+ return { service, status: "ok", detail: `${count} calendars accessible` };
97
+ }
98
+ return { service, status: "ok", detail: "≥1 calendar (partial count)" };
99
+ }
100
+ return classifyError(service, status, body);
101
+ }
102
+ async function probeDrive(ctx) {
103
+ const service = "drive";
104
+ if (!hasScope(ctx.tokens, SERVICE_SCOPES.drive)) {
105
+ return { service, status: "fail", detail: "scope not granted" };
106
+ }
107
+ const { status, body } = await gfetch("https://www.googleapis.com/drive/v3/about?fields=user(emailAddress),storageQuota(usage,limit)", ctx.accessToken);
108
+ if (status === 200) {
109
+ const about = body;
110
+ const email = about.user?.emailAddress ?? ctx.email;
111
+ const usage = about.storageQuota?.usage
112
+ ? `${Math.round(Number(about.storageQuota.usage) / 1_000_000)}MB used`
113
+ : "";
114
+ return {
115
+ service,
116
+ status: "ok",
117
+ detail: usage ? `${email} · ${usage}` : email,
118
+ };
119
+ }
120
+ return classifyError(service, status, body);
121
+ }
122
+ /**
123
+ * Docs has no equivalent of drive.about — querying requires a known
124
+ * document ID. Rely on scope presence + whether drive.about.get worked.
125
+ * Caller should run probeDrive first and pass its status.
126
+ */
127
+ function probeDocs(ctx, driveOk) {
128
+ const service = "docs";
129
+ if (!hasScope(ctx.tokens, SERVICE_SCOPES.docs)) {
130
+ return { service, status: "fail", detail: "scope not granted" };
131
+ }
132
+ if (!driveOk) {
133
+ return {
134
+ service,
135
+ status: "warn",
136
+ detail: "scope granted; drive probe failed so auth state unclear",
137
+ };
138
+ }
139
+ return { service, status: "ok", detail: "scope granted (no cheap direct probe)" };
140
+ }
141
+ /**
142
+ * Slides: same situation as docs — no cheap generic endpoint. Check scope
143
+ * presence and rely on the drive probe as a proxy for auth health.
144
+ */
145
+ function probeSlides(ctx, driveOk) {
146
+ const service = "slides";
147
+ if (!hasScope(ctx.tokens, SERVICE_SCOPES.slides)) {
148
+ return { service, status: "fail", detail: "scope not granted" };
149
+ }
150
+ if (!driveOk) {
151
+ return {
152
+ service,
153
+ status: "warn",
154
+ detail: "scope granted; drive probe failed so auth state unclear",
155
+ };
156
+ }
157
+ return { service, status: "ok", detail: "scope granted (no cheap direct probe)" };
158
+ }
159
+ /**
160
+ * Run all service probes for a single account in parallel (where they're
161
+ * independent) and return results keyed by service name.
162
+ */
163
+ export async function probeAccount(email) {
164
+ let tokens;
165
+ try {
166
+ tokens = await getValidAccessToken(email);
167
+ }
168
+ catch (err) {
169
+ const message = err instanceof Error ? err.message : String(err);
170
+ // token refresh failed — every service fails for this account
171
+ return ["gmail", "calendar", "docs", "drive", "slides"].map((service) => ({
172
+ service,
173
+ status: "fail",
174
+ detail: message,
175
+ }));
176
+ }
177
+ const ctx = {
178
+ email,
179
+ tokens,
180
+ accessToken: tokens.access_token,
181
+ };
182
+ // Gmail, Calendar, Drive are independent — run in parallel.
183
+ const [gmail, calendar, drive] = await Promise.all([
184
+ probeGmail(ctx).catch((e) => errorResult("gmail", e)),
185
+ probeCalendar(ctx).catch((e) => errorResult("calendar", e)),
186
+ probeDrive(ctx).catch((e) => errorResult("drive", e)),
187
+ ]);
188
+ const driveOk = drive.status === "ok";
189
+ const docs = probeDocs(ctx, driveOk);
190
+ const slides = probeSlides(ctx, driveOk);
191
+ return [gmail, calendar, docs, drive, slides];
192
+ }
193
+ function errorResult(service, err) {
194
+ return {
195
+ service,
196
+ status: "fail",
197
+ detail: `probe error: ${err instanceof Error ? err.message : String(err)}`,
198
+ };
199
+ }
200
+ //# sourceMappingURL=probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../../src/google/probe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ export interface StoredTokens {
2
+ client_id: string;
3
+ access_token: string;
4
+ refresh_token?: string;
5
+ expiry_date: number;
6
+ scope: string;
7
+ token_type: string;
8
+ obtained_at: string;
9
+ }
10
+ export declare function readTokens(email: string): StoredTokens | null;
11
+ /**
12
+ * Force-refresh the access token using the stored refresh_token. Persists
13
+ * the new access_token + expiry_date back to tokens.json (keeps the
14
+ * existing refresh_token since Google doesn't rotate it on every refresh).
15
+ */
16
+ export declare function refreshAccessToken(email: string): Promise<StoredTokens>;
17
+ /**
18
+ * Return a valid access token for the account — auto-refreshing if the
19
+ * stored one is expired or within EXPIRY_BUFFER_MS of expiry. Throws
20
+ * AxiError if the account isn't authenticated or refresh fails.
21
+ */
22
+ export declare function getValidAccessToken(email: string): Promise<StoredTokens>;