@indigoai-us/hq-cloud 5.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 (108) hide show
  1. package/dist/auth.d.ts +21 -0
  2. package/dist/auth.d.ts.map +1 -0
  3. package/dist/auth.js +116 -0
  4. package/dist/auth.js.map +1 -0
  5. package/dist/cli/accept.d.ts +29 -0
  6. package/dist/cli/accept.d.ts.map +1 -0
  7. package/dist/cli/accept.js +67 -0
  8. package/dist/cli/accept.js.map +1 -0
  9. package/dist/cli/conflict.d.ts +33 -0
  10. package/dist/cli/conflict.d.ts.map +1 -0
  11. package/dist/cli/conflict.js +91 -0
  12. package/dist/cli/conflict.js.map +1 -0
  13. package/dist/cli/index.d.ts +19 -0
  14. package/dist/cli/index.d.ts.map +1 -0
  15. package/dist/cli/index.js +14 -0
  16. package/dist/cli/index.js.map +1 -0
  17. package/dist/cli/invite.d.ts +51 -0
  18. package/dist/cli/invite.d.ts.map +1 -0
  19. package/dist/cli/invite.js +120 -0
  20. package/dist/cli/invite.js.map +1 -0
  21. package/dist/cli/invite.test.d.ts +5 -0
  22. package/dist/cli/invite.test.d.ts.map +1 -0
  23. package/dist/cli/invite.test.js +175 -0
  24. package/dist/cli/invite.test.js.map +1 -0
  25. package/dist/cli/promote.d.ts +30 -0
  26. package/dist/cli/promote.d.ts.map +1 -0
  27. package/dist/cli/promote.js +79 -0
  28. package/dist/cli/promote.js.map +1 -0
  29. package/dist/cli/share.d.ts +33 -0
  30. package/dist/cli/share.d.ts.map +1 -0
  31. package/dist/cli/share.js +153 -0
  32. package/dist/cli/share.js.map +1 -0
  33. package/dist/cli/share.test.d.ts +5 -0
  34. package/dist/cli/share.test.d.ts.map +1 -0
  35. package/dist/cli/share.test.js +121 -0
  36. package/dist/cli/share.test.js.map +1 -0
  37. package/dist/cli/sync.d.ts +30 -0
  38. package/dist/cli/sync.d.ts.map +1 -0
  39. package/dist/cli/sync.js +138 -0
  40. package/dist/cli/sync.js.map +1 -0
  41. package/dist/cli/sync.test.d.ts +5 -0
  42. package/dist/cli/sync.test.d.ts.map +1 -0
  43. package/dist/cli/sync.test.js +172 -0
  44. package/dist/cli/sync.test.js.map +1 -0
  45. package/dist/cognito-auth.d.ts +70 -0
  46. package/dist/cognito-auth.d.ts.map +1 -0
  47. package/dist/cognito-auth.js +280 -0
  48. package/dist/cognito-auth.js.map +1 -0
  49. package/dist/context.d.ts +30 -0
  50. package/dist/context.d.ts.map +1 -0
  51. package/dist/context.js +117 -0
  52. package/dist/context.js.map +1 -0
  53. package/dist/context.test.d.ts +7 -0
  54. package/dist/context.test.d.ts.map +1 -0
  55. package/dist/context.test.js +148 -0
  56. package/dist/context.test.js.map +1 -0
  57. package/dist/daemon-worker.d.ts +6 -0
  58. package/dist/daemon-worker.d.ts.map +1 -0
  59. package/dist/daemon-worker.js +26 -0
  60. package/dist/daemon-worker.js.map +1 -0
  61. package/dist/daemon.d.ts +10 -0
  62. package/dist/daemon.d.ts.map +1 -0
  63. package/dist/daemon.js +88 -0
  64. package/dist/daemon.js.map +1 -0
  65. package/dist/ignore.d.ts +10 -0
  66. package/dist/ignore.d.ts.map +1 -0
  67. package/dist/ignore.js +54 -0
  68. package/dist/ignore.js.map +1 -0
  69. package/dist/index.d.ts +33 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +138 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/journal.d.ts +12 -0
  74. package/dist/journal.d.ts.map +1 -0
  75. package/dist/journal.js +42 -0
  76. package/dist/journal.js.map +1 -0
  77. package/dist/s3.d.ts +15 -0
  78. package/dist/s3.d.ts.map +1 -0
  79. package/dist/s3.js +129 -0
  80. package/dist/s3.js.map +1 -0
  81. package/dist/types.d.ts +52 -0
  82. package/dist/types.d.ts.map +1 -0
  83. package/dist/types.js +5 -0
  84. package/dist/types.js.map +1 -0
  85. package/dist/vault-client.d.ts +164 -0
  86. package/dist/vault-client.d.ts.map +1 -0
  87. package/dist/vault-client.js +209 -0
  88. package/dist/vault-client.js.map +1 -0
  89. package/dist/vault-client.test.d.ts +7 -0
  90. package/dist/vault-client.test.d.ts.map +1 -0
  91. package/dist/vault-client.test.js +257 -0
  92. package/dist/vault-client.test.js.map +1 -0
  93. package/dist/watcher.d.ts +18 -0
  94. package/dist/watcher.d.ts.map +1 -0
  95. package/dist/watcher.js +106 -0
  96. package/dist/watcher.js.map +1 -0
  97. package/package.json +32 -0
  98. package/src/auth.ts +146 -0
  99. package/src/cognito-auth.ts +375 -0
  100. package/src/daemon-worker.ts +32 -0
  101. package/src/daemon.ts +97 -0
  102. package/src/ignore.ts +61 -0
  103. package/src/index.ts +182 -0
  104. package/src/journal.ts +63 -0
  105. package/src/s3.ts +178 -0
  106. package/src/types.ts +59 -0
  107. package/src/watcher.ts +130 -0
  108. package/tsconfig.json +8 -0
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `hq sync` command — pull everything allowed from entity vault (VLT-5 US-002).
3
+ *
4
+ * Pulls all files the caller's STS session policy permits.
5
+ * Never auto-overwrites local changes — prompts on conflict.
6
+ */
7
+ import type { VaultServiceConfig } from "../types.js";
8
+ import type { ConflictStrategy } from "./conflict.js";
9
+ export interface SyncOptions {
10
+ /** Company slug or UID (defaults to active company from config) */
11
+ company?: string;
12
+ /** Non-interactive conflict strategy */
13
+ onConflict?: ConflictStrategy;
14
+ /** Vault service config */
15
+ vaultConfig: VaultServiceConfig;
16
+ /** HQ root directory */
17
+ hqRoot: string;
18
+ }
19
+ export interface SyncResult {
20
+ filesDownloaded: number;
21
+ bytesDownloaded: number;
22
+ filesSkipped: number;
23
+ conflicts: number;
24
+ aborted: boolean;
25
+ }
26
+ /**
27
+ * Sync (pull) all allowed files from the entity vault.
28
+ */
29
+ export declare function sync(options: SyncOptions): Promise<SyncResult>;
30
+ //# sourceMappingURL=sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/cli/sync.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAMtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,MAAM,WAAW,WAAW;IAC1B,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,2BAA2B;IAC3B,WAAW,EAAE,kBAAkB,CAAC;IAChC,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAoHpE"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * `hq sync` command — pull everything allowed from entity vault (VLT-5 US-002).
3
+ *
4
+ * Pulls all files the caller's STS session policy permits.
5
+ * Never auto-overwrites local changes — prompts on conflict.
6
+ */
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import { resolveEntityContext, isExpiringSoon, refreshEntityContext } from "../context.js";
10
+ import { downloadFile, listRemoteFiles } from "../s3.js";
11
+ import { readJournal, writeJournal, hashFile, updateEntry, getEntry } from "../journal.js";
12
+ import { createIgnoreFilter } from "../ignore.js";
13
+ import { resolveConflict } from "./conflict.js";
14
+ /**
15
+ * Sync (pull) all allowed files from the entity vault.
16
+ */
17
+ export async function sync(options) {
18
+ const { company, onConflict, vaultConfig, hqRoot } = options;
19
+ // Resolve company
20
+ const companyRef = company ?? resolveActiveCompany(hqRoot);
21
+ if (!companyRef) {
22
+ throw new Error("No company specified and no active company found. " +
23
+ "Use --company <slug> or set up .hq/config.json.");
24
+ }
25
+ // Resolve entity context
26
+ let ctx = await resolveEntityContext(companyRef, vaultConfig);
27
+ const shouldSync = createIgnoreFilter(hqRoot);
28
+ const journal = readJournal(hqRoot);
29
+ let filesDownloaded = 0;
30
+ let bytesDownloaded = 0;
31
+ let filesSkipped = 0;
32
+ let conflicts = 0;
33
+ // List all remote files (IAM session policy filters at the AWS layer)
34
+ const remoteFiles = await listRemoteFiles(ctx);
35
+ for (const remoteFile of remoteFiles) {
36
+ const localPath = path.join(hqRoot, remoteFile.key);
37
+ // Apply ignore rules
38
+ if (!shouldSync(localPath)) {
39
+ filesSkipped++;
40
+ continue;
41
+ }
42
+ // Auto-refresh context if credentials expiring
43
+ if (isExpiringSoon(ctx.expiresAt)) {
44
+ ctx = await refreshEntityContext(companyRef, vaultConfig);
45
+ }
46
+ // Check for local conflict
47
+ const journalEntry = getEntry(journal, remoteFile.key);
48
+ if (fs.existsSync(localPath)) {
49
+ const localHash = hashFile(localPath);
50
+ // If local file has changed since last sync, it's a conflict
51
+ if (journalEntry && journalEntry.hash !== localHash) {
52
+ conflicts++;
53
+ const resolution = await resolveConflict({
54
+ path: remoteFile.key,
55
+ localHash,
56
+ remoteModified: remoteFile.lastModified,
57
+ localModified: fs.statSync(localPath).mtime,
58
+ direction: "pull",
59
+ }, onConflict);
60
+ if (resolution === "abort") {
61
+ writeJournal(hqRoot, journal);
62
+ return { filesDownloaded, bytesDownloaded, filesSkipped, conflicts, aborted: true };
63
+ }
64
+ if (resolution === "keep" || resolution === "skip") {
65
+ filesSkipped++;
66
+ continue;
67
+ }
68
+ // "overwrite" falls through to download
69
+ }
70
+ else if (journalEntry && journalEntry.hash === localHash) {
71
+ // Local unchanged since last sync — check if remote changed
72
+ // by comparing etag/timestamp
73
+ const lastSyncTime = new Date(journalEntry.syncedAt).getTime();
74
+ const remoteModTime = remoteFile.lastModified.getTime();
75
+ if (remoteModTime <= lastSyncTime) {
76
+ // Remote hasn't changed either — skip
77
+ filesSkipped++;
78
+ continue;
79
+ }
80
+ }
81
+ }
82
+ // Download
83
+ try {
84
+ await downloadFile(ctx, remoteFile.key, localPath);
85
+ const hash = hashFile(localPath);
86
+ const stat = fs.statSync(localPath);
87
+ updateEntry(journal, remoteFile.key, hash, stat.size, "down");
88
+ // Attach message from journal entry if present
89
+ const remoteJournalMessage = journalEntry?.message;
90
+ if (remoteJournalMessage) {
91
+ console.log(` ✓ ${remoteFile.key} — "${remoteJournalMessage}"`);
92
+ }
93
+ else {
94
+ console.log(` ✓ ${remoteFile.key}`);
95
+ }
96
+ filesDownloaded++;
97
+ bytesDownloaded += stat.size;
98
+ }
99
+ catch (err) {
100
+ // STS session policy may deny access to some paths — this is expected
101
+ // for guest members with allowedPrefixes
102
+ if (isAccessDenied(err)) {
103
+ filesSkipped++;
104
+ }
105
+ else {
106
+ console.error(` ✗ ${remoteFile.key} — ${err instanceof Error ? err.message : err}`);
107
+ }
108
+ }
109
+ }
110
+ writeJournal(hqRoot, journal);
111
+ return { filesDownloaded, bytesDownloaded, filesSkipped, conflicts, aborted: false };
112
+ }
113
+ /**
114
+ * Resolve active company from .hq/config.json.
115
+ */
116
+ function resolveActiveCompany(hqRoot) {
117
+ const configPath = path.join(hqRoot, ".hq", "config.json");
118
+ if (fs.existsSync(configPath)) {
119
+ try {
120
+ const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
121
+ return config.activeCompany ?? config.companySlug;
122
+ }
123
+ catch {
124
+ // Ignore parse errors
125
+ }
126
+ }
127
+ return undefined;
128
+ }
129
+ /**
130
+ * Check if an error is an S3 access denied (expected for filtered guests).
131
+ */
132
+ function isAccessDenied(err) {
133
+ if (err && typeof err === "object" && "name" in err) {
134
+ return err.name === "AccessDenied" || err.name === "Forbidden";
135
+ }
136
+ return false;
137
+ }
138
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/cli/sync.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unit tests for hq sync command (VLT-5 US-002).
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=sync.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.test.d.ts","sourceRoot":"","sources":["../../src/cli/sync.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Unit tests for hq sync command (VLT-5 US-002).
3
+ */
4
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import * as os from "os";
8
+ import { clearContextCache } from "../context.js";
9
+ // Mock s3 module at the top level
10
+ vi.mock("../s3.js", async () => {
11
+ const { vi: innerVi } = await import("vitest");
12
+ const innerFs = await import("fs");
13
+ const innerPath = await import("path");
14
+ const remoteFiles = [
15
+ { key: "docs/handoff.md", size: 42, lastModified: new Date(), etag: '"abc123"' },
16
+ { key: "knowledge/readme.md", size: 100, lastModified: new Date(), etag: '"def456"' },
17
+ ];
18
+ return {
19
+ uploadFile: innerVi.fn().mockResolvedValue(undefined),
20
+ downloadFile: innerVi.fn().mockImplementation(async (_ctx, _key, localPath) => {
21
+ const dir = innerPath.dirname(localPath);
22
+ if (!innerFs.existsSync(dir))
23
+ innerFs.mkdirSync(dir, { recursive: true });
24
+ innerFs.writeFileSync(localPath, "mock file content");
25
+ }),
26
+ listRemoteFiles: innerVi.fn().mockResolvedValue(remoteFiles),
27
+ deleteRemoteFile: innerVi.fn().mockResolvedValue(undefined),
28
+ headRemoteFile: innerVi.fn().mockResolvedValue(null),
29
+ };
30
+ });
31
+ import { sync } from "./sync.js";
32
+ const mockConfig = {
33
+ apiUrl: "https://vault-api.test",
34
+ authToken: "test-jwt-token",
35
+ region: "us-east-1",
36
+ };
37
+ const mockEntity = {
38
+ uid: "cmp_01ABCDEF",
39
+ slug: "acme",
40
+ bucketName: "hq-vault-acme-123",
41
+ status: "active",
42
+ };
43
+ const mockVendResponse = {
44
+ credentials: {
45
+ accessKeyId: "ASIA_TEST_KEY",
46
+ secretAccessKey: "test-secret",
47
+ sessionToken: "test-session-token",
48
+ expiration: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
49
+ },
50
+ expiresAt: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
51
+ };
52
+ function setupFetchMock() {
53
+ const fetchMock = vi.fn().mockImplementation(async (url) => {
54
+ const urlStr = String(url);
55
+ if (urlStr.includes("/entity/by-slug/")) {
56
+ return { ok: true, status: 200, json: async () => ({ entity: mockEntity }), text: async () => "" };
57
+ }
58
+ if (urlStr.includes("/sts/vend")) {
59
+ return { ok: true, status: 200, json: async () => mockVendResponse, text: async () => "" };
60
+ }
61
+ return { ok: false, status: 404, text: async () => "Not found" };
62
+ });
63
+ vi.stubGlobal("fetch", fetchMock);
64
+ return fetchMock;
65
+ }
66
+ describe("sync", () => {
67
+ let tmpDir;
68
+ beforeEach(() => {
69
+ clearContextCache();
70
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "hq-sync-test-"));
71
+ setupFetchMock();
72
+ });
73
+ afterEach(() => {
74
+ vi.restoreAllMocks();
75
+ fs.rmSync(tmpDir, { recursive: true, force: true });
76
+ });
77
+ it("downloads remote files that don't exist locally", async () => {
78
+ const result = await sync({
79
+ company: "acme",
80
+ vaultConfig: mockConfig,
81
+ hqRoot: tmpDir,
82
+ });
83
+ expect(result.filesDownloaded).toBe(2);
84
+ expect(result.aborted).toBe(false);
85
+ expect(fs.existsSync(path.join(tmpDir, "docs", "handoff.md"))).toBe(true);
86
+ expect(fs.existsSync(path.join(tmpDir, "knowledge", "readme.md"))).toBe(true);
87
+ });
88
+ it("throws when no company specified and no active company", async () => {
89
+ await expect(sync({ vaultConfig: mockConfig, hqRoot: tmpDir })).rejects.toThrow(/No company specified/);
90
+ });
91
+ it("uses active company from .hq/config.json", async () => {
92
+ fs.mkdirSync(path.join(tmpDir, ".hq"), { recursive: true });
93
+ fs.writeFileSync(path.join(tmpDir, ".hq", "config.json"), JSON.stringify({ activeCompany: "acme" }));
94
+ const result = await sync({ vaultConfig: mockConfig, hqRoot: tmpDir });
95
+ expect(result.filesDownloaded).toBe(2);
96
+ });
97
+ it("detects conflicts with local changes and keeps local on --on-conflict keep", async () => {
98
+ fs.mkdirSync(path.join(tmpDir, "docs"), { recursive: true });
99
+ fs.writeFileSync(path.join(tmpDir, "docs", "handoff.md"), "local version");
100
+ fs.writeFileSync(path.join(tmpDir, ".hq-sync-journal.json"), JSON.stringify({
101
+ version: "1",
102
+ lastSync: new Date().toISOString(),
103
+ files: {
104
+ "docs/handoff.md": {
105
+ hash: "old-hash-from-last-sync",
106
+ size: 20,
107
+ syncedAt: new Date(Date.now() - 3600000).toISOString(),
108
+ direction: "down",
109
+ },
110
+ },
111
+ }));
112
+ const result = await sync({
113
+ company: "acme",
114
+ onConflict: "keep",
115
+ vaultConfig: mockConfig,
116
+ hqRoot: tmpDir,
117
+ });
118
+ expect(result.conflicts).toBe(1);
119
+ expect(result.filesSkipped).toBeGreaterThanOrEqual(1);
120
+ expect(fs.readFileSync(path.join(tmpDir, "docs", "handoff.md"), "utf-8")).toBe("local version");
121
+ });
122
+ it("aborts on --on-conflict abort", async () => {
123
+ fs.mkdirSync(path.join(tmpDir, "docs"), { recursive: true });
124
+ fs.writeFileSync(path.join(tmpDir, "docs", "handoff.md"), "local version");
125
+ fs.writeFileSync(path.join(tmpDir, ".hq-sync-journal.json"), JSON.stringify({
126
+ version: "1",
127
+ lastSync: new Date().toISOString(),
128
+ files: {
129
+ "docs/handoff.md": {
130
+ hash: "old-hash",
131
+ size: 20,
132
+ syncedAt: new Date(Date.now() - 3600000).toISOString(),
133
+ direction: "down",
134
+ },
135
+ },
136
+ }));
137
+ const result = await sync({
138
+ company: "acme",
139
+ onConflict: "abort",
140
+ vaultConfig: mockConfig,
141
+ hqRoot: tmpDir,
142
+ });
143
+ expect(result.aborted).toBe(true);
144
+ });
145
+ it("overwrites local on --on-conflict overwrite", async () => {
146
+ fs.mkdirSync(path.join(tmpDir, "docs"), { recursive: true });
147
+ fs.writeFileSync(path.join(tmpDir, "docs", "handoff.md"), "local version");
148
+ fs.writeFileSync(path.join(tmpDir, ".hq-sync-journal.json"), JSON.stringify({
149
+ version: "1",
150
+ lastSync: new Date().toISOString(),
151
+ files: {
152
+ "docs/handoff.md": {
153
+ hash: "old-hash",
154
+ size: 20,
155
+ syncedAt: new Date(Date.now() - 3600000).toISOString(),
156
+ direction: "down",
157
+ },
158
+ },
159
+ }));
160
+ const result = await sync({
161
+ company: "acme",
162
+ onConflict: "overwrite",
163
+ vaultConfig: mockConfig,
164
+ hqRoot: tmpDir,
165
+ });
166
+ expect(result.conflicts).toBe(1);
167
+ expect(result.filesDownloaded).toBeGreaterThanOrEqual(1);
168
+ // File should be overwritten with mock content
169
+ expect(fs.readFileSync(path.join(tmpDir, "docs", "handoff.md"), "utf-8")).toBe("mock file content");
170
+ });
171
+ });
172
+ //# sourceMappingURL=sync.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sync.test.js","sourceRoot":"","sources":["../../src/cli/sync.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Cognito browser-OAuth helper (VLT-9).
3
+ *
4
+ * Drives the Cognito Hosted UI authorization-code + PKCE flow for the
5
+ * vault-service User Pool. Used by the CLI (`hq login`, `create-hq`) to
6
+ * obtain a JWT that is then passed to the vault-service API as
7
+ * `Authorization: Bearer <accessToken>`.
8
+ *
9
+ * Why PKCE: the CLI is a public client (no secret), so we use PKCE per
10
+ * RFC 7636 to prove that the same process that started the auth request
11
+ * is the one exchanging the code for tokens.
12
+ *
13
+ * Why a localhost callback: Cognito allows `http://localhost:*` as a
14
+ * redirect URI specifically for native/CLI apps (RFC 8252 §7). We spin
15
+ * up a one-shot HTTP server on the chosen port, capture exactly one
16
+ * callback, then close it.
17
+ */
18
+ export interface CognitoAuthConfig {
19
+ /** AWS region the User Pool lives in (e.g. "us-east-1"). */
20
+ region: string;
21
+ /** Cognito User Pool Domain prefix (e.g. "vault-indigo-stefanjohnson"). */
22
+ userPoolDomain: string;
23
+ /** App Client ID (e.g. "4mmujmjq3srakdueg656b9m0mp"). */
24
+ clientId: string;
25
+ /** Loopback callback port. Defaults to 3000. */
26
+ port?: number;
27
+ /** OAuth scopes. Defaults to ["openid", "email", "profile"]. */
28
+ scopes?: string[];
29
+ }
30
+ export interface CognitoTokens {
31
+ accessToken: string;
32
+ idToken: string;
33
+ refreshToken: string;
34
+ /** ISO 8601 timestamp when the access token expires. */
35
+ expiresAt: string;
36
+ tokenType: "Bearer";
37
+ }
38
+ /** Returned when an interactive login is needed but stdin/browser is unavailable. */
39
+ export declare class CognitoAuthError extends Error {
40
+ constructor(message: string);
41
+ }
42
+ export declare function loadCachedTokens(): CognitoTokens | null;
43
+ export declare function saveCachedTokens(tokens: CognitoTokens): void;
44
+ export declare function clearCachedTokens(): void;
45
+ /** True when the token expires within the given buffer (default 60s). */
46
+ export declare function isExpiring(tokens: CognitoTokens, bufferSeconds?: number): boolean;
47
+ /**
48
+ * Open the Cognito Hosted UI in the user's browser, wait for the redirect
49
+ * back to localhost, and exchange the auth code for tokens.
50
+ *
51
+ * Times out after 5 minutes if the user doesn't complete the flow.
52
+ */
53
+ export declare function browserLogin(config: CognitoAuthConfig): Promise<CognitoTokens>;
54
+ /**
55
+ * Use the refresh token to obtain a fresh access token without user interaction.
56
+ * The refresh token itself is NOT rotated by Cognito on the refresh grant, so
57
+ * we preserve the existing one in the result.
58
+ */
59
+ export declare function refreshTokens(config: CognitoAuthConfig, currentRefreshToken: string): Promise<CognitoTokens>;
60
+ /**
61
+ * High-level helper: return a non-expired access token, refreshing or
62
+ * launching browser login as needed.
63
+ *
64
+ * Pass `interactive: false` from automated contexts where you would rather
65
+ * fail fast than open a browser.
66
+ */
67
+ export declare function getValidAccessToken(config: CognitoAuthConfig, options?: {
68
+ interactive?: boolean;
69
+ }): Promise<string>;
70
+ //# sourceMappingURL=cognito-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cognito-auth.d.ts","sourceRoot":"","sources":["../src/cognito-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAaH,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,cAAc,EAAE,MAAM,CAAC;IACvB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,QAAQ,CAAC;CACrB;AAED,qFAAqF;AACrF,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AASD,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,IAAI,CAQvD;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAK5D;AAED,wBAAgB,iBAAiB,IAAI,IAAI,CAExC;AAED,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,MAAM,EAAE,aAAa,EAAE,aAAa,SAAK,GAAG,OAAO,CAG7E;AAsCD;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,aAAa,CAAC,CAmGxB;AAsDD;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,iBAAiB,EACzB,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAAC,aAAa,CAAC,CA4BxB;AAED;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,iBAAiB,EACzB,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,OAAO,CAAC,MAAM,CAAC,CAuBjB"}