@dreamboard-games/cli 0.1.30-alpha.3 → 0.1.30-alpha.31

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 (173) hide show
  1. package/README.md +27 -108
  2. package/dist/agent-verifier/agent-workspace-verifier.mjs +1988 -57
  3. package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -1
  4. package/dist/agent-verifier/{chunk-XQXDOBYB.mjs → chunk-4I2WWAPK.mjs} +27 -10
  5. package/dist/agent-verifier/chunk-4I2WWAPK.mjs.map +1 -0
  6. package/dist/agent-verifier/{chunk-O4YCPU7C.mjs → chunk-BWBN2TDJ.mjs} +539 -641
  7. package/dist/agent-verifier/chunk-BWBN2TDJ.mjs.map +1 -0
  8. package/dist/agent-verifier/{chunk-TAEQKBJB.mjs → chunk-GWRZRWCF.mjs} +1 -1
  9. package/dist/agent-verifier/chunk-GWRZRWCF.mjs.map +1 -0
  10. package/dist/agent-verifier/chunk-H6XDQJ3N.mjs +11 -0
  11. package/dist/agent-verifier/chunk-HUBV22JQ.mjs +89 -0
  12. package/dist/agent-verifier/chunk-HUBV22JQ.mjs.map +1 -0
  13. package/dist/agent-verifier/{chunk-VS573ERH.mjs → chunk-JZTH3EMV.mjs} +2 -2
  14. package/dist/agent-verifier/{chunk-XGWCY624.mjs → chunk-KDAQ4CZY.mjs} +34 -27
  15. package/dist/agent-verifier/chunk-KDAQ4CZY.mjs.map +1 -0
  16. package/dist/agent-verifier/{chunk-IAYRNVUC.mjs → chunk-LMW66VBH.mjs} +2 -13
  17. package/dist/agent-verifier/{chunk-IAYRNVUC.mjs.map → chunk-LMW66VBH.mjs.map} +1 -1
  18. package/dist/agent-verifier/{chunk-776W3UGV.mjs → chunk-M6YNQZCC.mjs} +4 -13
  19. package/dist/agent-verifier/chunk-M6YNQZCC.mjs.map +1 -0
  20. package/dist/agent-verifier/{chunk-H76MT5UR.mjs → chunk-M7UVBANQ.mjs} +2 -1
  21. package/dist/agent-verifier/chunk-M7UVBANQ.mjs.map +1 -0
  22. package/dist/agent-verifier/{chunk-SH5JKYOB.mjs → chunk-MIRGCMUC.mjs} +112 -26
  23. package/dist/agent-verifier/chunk-MIRGCMUC.mjs.map +1 -0
  24. package/dist/agent-verifier/{chunk-NAK77WXW.mjs → chunk-MYMVXTZT.mjs} +4 -5
  25. package/dist/agent-verifier/chunk-MYMVXTZT.mjs.map +1 -0
  26. package/dist/agent-verifier/{chunk-7WWGFAAU.mjs → chunk-NBRUEJUK.mjs} +215 -223
  27. package/dist/agent-verifier/chunk-NBRUEJUK.mjs.map +1 -0
  28. package/dist/agent-verifier/chunk-OJFZVGEL.mjs +492 -0
  29. package/dist/agent-verifier/chunk-OJFZVGEL.mjs.map +1 -0
  30. package/dist/agent-verifier/{chunk-LUZ7KE6H.mjs → chunk-QD4SQNUP.mjs} +4 -8
  31. package/dist/agent-verifier/{chunk-LUZ7KE6H.mjs.map → chunk-QD4SQNUP.mjs.map} +1 -1
  32. package/dist/agent-verifier/chunk-TTB7AIHZ.mjs +214 -0
  33. package/dist/agent-verifier/chunk-TTB7AIHZ.mjs.map +1 -0
  34. package/dist/agent-verifier/{chunk-F2DIOJJZ.mjs → chunk-XCQQIPCO.mjs} +5 -46
  35. package/dist/agent-verifier/chunk-XCQQIPCO.mjs.map +1 -0
  36. package/dist/agent-verifier/{global-config-Y2NTSK4R.mjs → global-config-2NUESNEQ.mjs} +6 -6
  37. package/dist/agent-verifier/{keychain-backend-SPQWGKZN.mjs → keychain-backend-FF4I6ODB.mjs} +12 -7
  38. package/dist/agent-verifier/keychain-backend-FF4I6ODB.mjs.map +1 -0
  39. package/dist/agent-verifier/{local-files-JFOQQZDL.mjs → local-files-OF4QFISU.mjs} +10 -10
  40. package/dist/agent-verifier/{chunk-UIOLGH4A.mjs → local-typecheck-DHVLM37Z.mjs} +4 -4
  41. package/dist/agent-verifier/local-typecheck-DHVLM37Z.mjs.map +1 -0
  42. package/dist/agent-verifier/{materialize-workspace-ZAVGQQSF.mjs → materialize-workspace-MAGKDMK5.mjs} +23 -22
  43. package/dist/agent-verifier/materialize-workspace-MAGKDMK5.mjs.map +1 -0
  44. package/dist/agent-verifier/{project-state-K576C2TE.mjs → project-state-XKUSCFSV.mjs} +2 -2
  45. package/dist/agent-verifier/{prompt-MJRJMOGQ.mjs → prompt-VKHMCQT6.mjs} +2 -2
  46. package/dist/agent-verifier/{chunk-A64ZZUZV.mjs → reducer-bundle-preflight-GLUJKTWU.mjs} +76 -25
  47. package/dist/agent-verifier/reducer-bundle-preflight-GLUJKTWU.mjs.map +1 -0
  48. package/dist/agent-verifier/{chunk-JGT4P4UD.mjs → reducer-contract-preflight-WVQQPW5F.mjs} +7 -6
  49. package/dist/agent-verifier/reducer-contract-preflight-WVQQPW5F.mjs.map +1 -0
  50. package/dist/agent-verifier/{chunk-E7SSWJXJ.mjs → reducer-native-test-harness-UFMSNNDY.mjs} +463 -686
  51. package/dist/agent-verifier/reducer-native-test-harness-UFMSNNDY.mjs.map +1 -0
  52. package/dist/agent-verifier/static-scaffold-CLRRWXON.mjs +24 -0
  53. package/dist/agent-verifier/workspace-codegen-SPPVHURX.mjs +10 -0
  54. package/dist/agent-verifier/{workspace-dependencies-NOOQBK6I.mjs → workspace-dependencies-5HEEKZFP.mjs} +6 -4
  55. package/dist/authoring-compatibility-internal.js +12 -0
  56. package/dist/chunk-2H7UOFLK.js +11 -0
  57. package/dist/chunk-6NYVJYN4.js +313 -0
  58. package/dist/chunk-6NYVJYN4.js.map +1 -0
  59. package/dist/chunk-EQNBQVIW.js +204 -0
  60. package/dist/chunk-EQNBQVIW.js.map +1 -0
  61. package/dist/chunk-X244CUU4.js +3815 -0
  62. package/dist/chunk-X244CUU4.js.map +1 -0
  63. package/dist/{chunk-TAQKH67O.js → chunk-YNJVKC2T.js} +2587 -7278
  64. package/dist/chunk-YNJVKC2T.js.map +1 -0
  65. package/dist/{global-config-S4ZIPECE.js → global-config-RBMW7IVA.js} +4 -3
  66. package/dist/index.js +3099 -6187
  67. package/dist/index.js.map +1 -1
  68. package/dist/internal.js +36 -10
  69. package/dist/internal.js.map +1 -1
  70. package/dist/{keychain-backend-HDF4TZDL.js → keychain-backend-FSNTNTZE.js} +12 -7
  71. package/dist/keychain-backend-FSNTNTZE.js.map +1 -0
  72. package/dist/{prompt-NDV3AE5L.js → prompt-GMZABCJC.js} +2 -2
  73. package/package.json +9 -19
  74. package/release/authoring-release-set.json +38 -0
  75. package/skills/dreamboard/SKILL.md +30 -28
  76. package/skills/dreamboard/references/building-your-first-game.md +15 -15
  77. package/skills/dreamboard/references/cli.md +46 -47
  78. package/skills/dreamboard/references/manifest-authoring.md +11 -3
  79. package/skills/dreamboard/references/quickstart.md +16 -13
  80. package/skills/dreamboard/references/testing.md +6 -13
  81. package/dist/agent-verifier/chunk-3UKQVWLV.mjs +0 -1744
  82. package/dist/agent-verifier/chunk-3UKQVWLV.mjs.map +0 -1
  83. package/dist/agent-verifier/chunk-776W3UGV.mjs.map +0 -1
  84. package/dist/agent-verifier/chunk-7WWGFAAU.mjs.map +0 -1
  85. package/dist/agent-verifier/chunk-A64ZZUZV.mjs.map +0 -1
  86. package/dist/agent-verifier/chunk-E7SSWJXJ.mjs.map +0 -1
  87. package/dist/agent-verifier/chunk-F2DIOJJZ.mjs.map +0 -1
  88. package/dist/agent-verifier/chunk-G42BGGG2.mjs +0 -70
  89. package/dist/agent-verifier/chunk-G42BGGG2.mjs.map +0 -1
  90. package/dist/agent-verifier/chunk-H76MT5UR.mjs.map +0 -1
  91. package/dist/agent-verifier/chunk-HGMUAL33.mjs +0 -39
  92. package/dist/agent-verifier/chunk-HGMUAL33.mjs.map +0 -1
  93. package/dist/agent-verifier/chunk-JGT4P4UD.mjs.map +0 -1
  94. package/dist/agent-verifier/chunk-NAK77WXW.mjs.map +0 -1
  95. package/dist/agent-verifier/chunk-O4YCPU7C.mjs.map +0 -1
  96. package/dist/agent-verifier/chunk-S34FRJHS.mjs +0 -222
  97. package/dist/agent-verifier/chunk-S34FRJHS.mjs.map +0 -1
  98. package/dist/agent-verifier/chunk-SH5JKYOB.mjs.map +0 -1
  99. package/dist/agent-verifier/chunk-SKI2ESE5.mjs +0 -44
  100. package/dist/agent-verifier/chunk-TAEQKBJB.mjs.map +0 -1
  101. package/dist/agent-verifier/chunk-UIOLGH4A.mjs.map +0 -1
  102. package/dist/agent-verifier/chunk-UIZNWRM6.mjs +0 -2432
  103. package/dist/agent-verifier/chunk-UIZNWRM6.mjs.map +0 -1
  104. package/dist/agent-verifier/chunk-W3N3QJ4V.mjs +0 -624
  105. package/dist/agent-verifier/chunk-W3N3QJ4V.mjs.map +0 -1
  106. package/dist/agent-verifier/chunk-XGWCY624.mjs.map +0 -1
  107. package/dist/agent-verifier/chunk-XQXDOBYB.mjs.map +0 -1
  108. package/dist/agent-verifier/compile-TEQVA46V.mjs +0 -312
  109. package/dist/agent-verifier/compile-TEQVA46V.mjs.map +0 -1
  110. package/dist/agent-verifier/keychain-backend-SPQWGKZN.mjs.map +0 -1
  111. package/dist/agent-verifier/local-typecheck-XVGWI75X.mjs +0 -10
  112. package/dist/agent-verifier/materialize-workspace-ZAVGQQSF.mjs.map +0 -1
  113. package/dist/agent-verifier/reducer-bundle-preflight-LXNJUBKL.mjs +0 -20
  114. package/dist/agent-verifier/reducer-contract-preflight-TUMQ43JV.mjs +0 -11
  115. package/dist/agent-verifier/reducer-native-test-harness-CHX5MBL5.mjs +0 -50
  116. package/dist/agent-verifier/static-scaffold-R7SVDRQI.mjs +0 -27
  117. package/dist/agent-verifier/sync-THAI546U.mjs +0 -588
  118. package/dist/agent-verifier/sync-THAI546U.mjs.map +0 -1
  119. package/dist/agent-verifier/test-AFAQFKOB.mjs +0 -353
  120. package/dist/agent-verifier/test-AFAQFKOB.mjs.map +0 -1
  121. package/dist/agent-verifier/workspace-codegen-2ZMQRIKJ.mjs +0 -10
  122. package/dist/agent-verifier/workspace-dependencies-NOOQBK6I.mjs.map +0 -1
  123. package/dist/chunk-N7XPNNUI.js +0 -432
  124. package/dist/chunk-N7XPNNUI.js.map +0 -1
  125. package/dist/chunk-SEGVTWSK.js +0 -44
  126. package/dist/chunk-SEGVTWSK.js.map +0 -1
  127. package/dist/chunk-TAQKH67O.js.map +0 -1
  128. package/dist/dev-host/components/drawer.tsx +0 -132
  129. package/dist/dev-host/components/input.tsx +0 -21
  130. package/dist/dev-host/dev-api-proxy-plugin.ts +0 -328
  131. package/dist/dev-host/dev-author-dom-warnings.ts +0 -100
  132. package/dist/dev-host/dev-diagnostics.ts +0 -62
  133. package/dist/dev-host/dev-fallback-stylesheet.ts +0 -53
  134. package/dist/dev-host/dev-hmr-guard-plugin.ts +0 -47
  135. package/dist/dev-host/dev-host-controller.ts +0 -674
  136. package/dist/dev-host/dev-host-player-query.ts +0 -17
  137. package/dist/dev-host/dev-host-session-transport.ts +0 -52
  138. package/dist/dev-host/dev-host-storage.ts +0 -56
  139. package/dist/dev-host/dev-log-relay-plugin.ts +0 -510
  140. package/dist/dev-host/dev-runtime-config.ts +0 -14
  141. package/dist/dev-host/dev-runtime-platform.ts +0 -335
  142. package/dist/dev-host/dev-virtual-modules-plugin.ts +0 -64
  143. package/dist/dev-host/host-main.css +0 -224
  144. package/dist/dev-host/host-main.tsx +0 -948
  145. package/dist/dev-host/index.html +0 -56
  146. package/dist/dev-host/lib/utils.ts +0 -6
  147. package/dist/dev-host/plugin-main.ts +0 -61
  148. package/dist/dev-host/plugin.html +0 -24
  149. package/dist/dev-host/shared-styles.css +0 -144
  150. package/dist/dev-host/start-dev-server.ts +0 -140
  151. package/dist/dev-host/virtual-modules.d.ts +0 -27
  152. package/dist/global-config-S4ZIPECE.js.map +0 -1
  153. package/dist/keychain-backend-HDF4TZDL.js.map +0 -1
  154. package/skills/dreamboard/scripts/events-extract.mjs +0 -218
  155. /package/dist/agent-verifier/{chunk-SKI2ESE5.mjs.map → chunk-H6XDQJ3N.mjs.map} +0 -0
  156. /package/dist/agent-verifier/{chunk-VS573ERH.mjs.map → chunk-JZTH3EMV.mjs.map} +0 -0
  157. /package/dist/agent-verifier/{global-config-Y2NTSK4R.mjs.map → global-config-2NUESNEQ.mjs.map} +0 -0
  158. /package/dist/agent-verifier/{local-files-JFOQQZDL.mjs.map → local-files-OF4QFISU.mjs.map} +0 -0
  159. /package/dist/agent-verifier/{local-typecheck-XVGWI75X.mjs.map → project-state-XKUSCFSV.mjs.map} +0 -0
  160. /package/dist/agent-verifier/{prompt-MJRJMOGQ.mjs.map → prompt-VKHMCQT6.mjs.map} +0 -0
  161. /package/dist/agent-verifier/{project-state-K576C2TE.mjs.map → static-scaffold-CLRRWXON.mjs.map} +0 -0
  162. /package/dist/agent-verifier/{reducer-bundle-preflight-LXNJUBKL.mjs.map → workspace-codegen-SPPVHURX.mjs.map} +0 -0
  163. /package/dist/agent-verifier/{reducer-contract-preflight-TUMQ43JV.mjs.map → workspace-dependencies-5HEEKZFP.mjs.map} +0 -0
  164. /package/dist/{agent-verifier/reducer-native-test-harness-CHX5MBL5.mjs.map → authoring-compatibility-internal.js.map} +0 -0
  165. /package/dist/{agent-verifier/static-scaffold-R7SVDRQI.mjs.map → chunk-2H7UOFLK.js.map} +0 -0
  166. /package/dist/{agent-verifier/workspace-codegen-2ZMQRIKJ.mjs.map → global-config-RBMW7IVA.js.map} +0 -0
  167. /package/dist/{prompt-NDV3AE5L.js.map → prompt-GMZABCJC.js.map} +0 -0
  168. /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.framework.json +0 -0
  169. /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.json +0 -0
  170. /package/{dist/scaffold → scaffold}/assets/static/ui/index.tsx +0 -0
  171. /package/{dist/scaffold → scaffold}/assets/static/ui/style.css +0 -0
  172. /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.framework.json +0 -0
  173. /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.json +0 -0
@@ -0,0 +1,313 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ PROJECT_DIR_NAME,
4
+ atomicWriteFile,
5
+ ensureDir,
6
+ readJsonFile,
7
+ withFileLock
8
+ } from "./chunk-EQNBQVIW.js";
9
+
10
+ // src/config/global-config.ts
11
+ import os2 from "os";
12
+ import path2 from "path";
13
+
14
+ // src/config/credential-store.ts
15
+ import os from "os";
16
+ import path from "path";
17
+ import { promises as fs } from "fs";
18
+ var credentialDirectoryOverrideForTests = null;
19
+ function getCredentialDirectory() {
20
+ return credentialDirectoryOverrideForTests ?? path.join(os.homedir(), PROJECT_DIR_NAME);
21
+ }
22
+ function getCredentialFilePath() {
23
+ return path.join(getCredentialDirectory(), "auth.json");
24
+ }
25
+ function getCredentialAuditLogPath() {
26
+ return path.join(getCredentialDirectory(), "auth-events.log");
27
+ }
28
+ function getCredentialLockPath() {
29
+ return `${getCredentialFilePath()}.lock`;
30
+ }
31
+ async function appendCredentialAuditEvent(event) {
32
+ try {
33
+ const logPath = getCredentialAuditLogPath();
34
+ await fs.mkdir(path.dirname(logPath), { recursive: true, mode: 448 });
35
+ await fs.appendFile(
36
+ logPath,
37
+ `${JSON.stringify({
38
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
39
+ pid: process.pid,
40
+ ...event
41
+ })}
42
+ `,
43
+ { mode: 384 }
44
+ );
45
+ } catch {
46
+ }
47
+ }
48
+ async function fileRead() {
49
+ const filePath = getCredentialFilePath();
50
+ let data;
51
+ try {
52
+ data = await fs.readFile(filePath, "utf8");
53
+ } catch (err) {
54
+ if (err.code === "ENOENT") return null;
55
+ throw err;
56
+ }
57
+ if (data.trim().length === 0) {
58
+ return null;
59
+ }
60
+ let parsed;
61
+ try {
62
+ parsed = JSON.parse(data);
63
+ } catch {
64
+ return null;
65
+ }
66
+ const accessToken = parsed.clerkAccessToken ?? parsed.accessToken ?? parsed.authToken;
67
+ const refreshToken = parsed.refreshToken;
68
+ if (!accessToken && !refreshToken) return null;
69
+ return {
70
+ accessToken: accessToken || void 0,
71
+ refreshToken: refreshToken || void 0,
72
+ tokenExpiresAt: parsed.clerkAccessExpiresAt || parsed.tokenExpiresAt || void 0,
73
+ dreamboardApiToken: parsed.dreamboardApiToken || void 0,
74
+ dreamboardApiExpiresAt: parsed.dreamboardApiExpiresAt || void 0,
75
+ clerkOAuthIssuer: parsed.clerkOAuthIssuer || void 0,
76
+ clerkOAuthClientId: parsed.clerkOAuthClientId || void 0,
77
+ clerkOAuthTokenUrl: parsed.clerkOAuthTokenUrl || void 0,
78
+ environment: parsed.environment || void 0
79
+ };
80
+ }
81
+ async function writeFilePayload(payload) {
82
+ await atomicWriteFile(
83
+ getCredentialFilePath(),
84
+ `${JSON.stringify(payload, null, 2)}
85
+ `,
86
+ { mode: 384 }
87
+ );
88
+ }
89
+ async function fileWriteFull(creds) {
90
+ if (!creds.accessToken || !creds.refreshToken) {
91
+ throw new Error(
92
+ "Refusing to persist credentials with an empty accessToken or refreshToken."
93
+ );
94
+ }
95
+ await writeFilePayload({
96
+ clerkAccessToken: creds.accessToken,
97
+ refreshToken: creds.refreshToken,
98
+ clerkAccessExpiresAt: creds.tokenExpiresAt,
99
+ dreamboardApiToken: creds.dreamboardApiToken,
100
+ dreamboardApiExpiresAt: creds.dreamboardApiExpiresAt,
101
+ clerkOAuthIssuer: creds.clerkOAuthIssuer,
102
+ clerkOAuthClientId: creds.clerkOAuthClientId,
103
+ clerkOAuthTokenUrl: creds.clerkOAuthTokenUrl,
104
+ environment: creds.environment
105
+ });
106
+ }
107
+ async function fileWriteAccessOnly(accessToken) {
108
+ if (!accessToken) {
109
+ throw new Error("Refusing to persist an empty access token.");
110
+ }
111
+ await writeFilePayload({ authToken: accessToken });
112
+ }
113
+ async function fileClear(reason = "credential_store_clear") {
114
+ const filePath = getCredentialFilePath();
115
+ try {
116
+ await fs.unlink(filePath);
117
+ await appendCredentialAuditEvent({
118
+ event: "auth_file_deleted",
119
+ reason,
120
+ authPath: filePath,
121
+ backend: "file"
122
+ });
123
+ } catch (err) {
124
+ if (err.code === "ENOENT") {
125
+ await appendCredentialAuditEvent({
126
+ event: "auth_file_delete_missing",
127
+ reason,
128
+ authPath: filePath,
129
+ backend: "file"
130
+ });
131
+ return;
132
+ }
133
+ throw err;
134
+ }
135
+ }
136
+ var fileCredentialBackend = {
137
+ name: "file",
138
+ read: fileRead,
139
+ writeFull: fileWriteFull,
140
+ writeAccessOnly: fileWriteAccessOnly,
141
+ clear: fileClear
142
+ };
143
+ var CredentialStoreUnavailableError = class extends Error {
144
+ code = "CREDENTIAL_STORE_UNAVAILABLE";
145
+ constructor(reason) {
146
+ super(`Credential store unavailable: ${reason}`);
147
+ this.name = "CredentialStoreUnavailableError";
148
+ }
149
+ };
150
+ var cachedBackend = null;
151
+ var migrationCompleted = false;
152
+ var backendResolver = defaultBackendResolver;
153
+ async function defaultBackendResolver() {
154
+ const override = (process.env.DREAMBOARD_CREDENTIAL_BACKEND ?? "").trim().toLowerCase();
155
+ if (override === "file") {
156
+ return fileCredentialBackend;
157
+ }
158
+ if (override && override !== "keychain" && override !== "auto") {
159
+ throw new Error(
160
+ `Unknown DREAMBOARD_CREDENTIAL_BACKEND value "${override}" (expected "file", "keychain", or "auto").`
161
+ );
162
+ }
163
+ const useKeychain = override === "keychain" || await readCredentialBackendPreference();
164
+ if (!useKeychain) {
165
+ return fileCredentialBackend;
166
+ }
167
+ const { tryKeychainBackend } = await import("./keychain-backend-FSNTNTZE.js");
168
+ const keychain = await tryKeychainBackend();
169
+ if (keychain.available) {
170
+ return keychain.backend;
171
+ }
172
+ return fileCredentialBackend;
173
+ }
174
+ async function readCredentialBackendPreference() {
175
+ try {
176
+ const { loadGlobalConfig: loadGlobalConfig2 } = await import("./global-config-RBMW7IVA.js");
177
+ const config = await loadGlobalConfig2();
178
+ return config.credentialBackend === "keychain";
179
+ } catch {
180
+ return false;
181
+ }
182
+ }
183
+ async function getCredentialBackend() {
184
+ if (cachedBackend === null) {
185
+ cachedBackend = await backendResolver();
186
+ if (!migrationCompleted && cachedBackend.name !== "file") {
187
+ await migrateFromFileBackendIfNeeded(cachedBackend);
188
+ }
189
+ migrationCompleted = true;
190
+ }
191
+ return cachedBackend;
192
+ }
193
+ async function migrateFromFileBackendIfNeeded(target, options = {}) {
194
+ try {
195
+ const [onDisk, onTarget] = await Promise.all([
196
+ fileCredentialBackend.read(),
197
+ target.read()
198
+ ]);
199
+ if (!onDisk) return;
200
+ if (onTarget) {
201
+ return;
202
+ }
203
+ if (onDisk.accessToken && onDisk.refreshToken) {
204
+ const migrated = {
205
+ accessToken: onDisk.accessToken,
206
+ refreshToken: onDisk.refreshToken,
207
+ tokenExpiresAt: onDisk.tokenExpiresAt,
208
+ dreamboardApiToken: onDisk.dreamboardApiToken,
209
+ dreamboardApiExpiresAt: onDisk.dreamboardApiExpiresAt,
210
+ clerkOAuthIssuer: onDisk.clerkOAuthIssuer,
211
+ clerkOAuthClientId: onDisk.clerkOAuthClientId,
212
+ clerkOAuthTokenUrl: onDisk.clerkOAuthTokenUrl,
213
+ environment: onDisk.environment
214
+ };
215
+ await target.writeFull(migrated);
216
+ await verifyMigratedSession(target, migrated);
217
+ } else if (onDisk.accessToken) {
218
+ await target.writeAccessOnly(onDisk.accessToken);
219
+ const migrated = await target.read();
220
+ if (migrated?.accessToken !== onDisk.accessToken) {
221
+ throw new Error("Credential migration verification failed.");
222
+ }
223
+ } else {
224
+ return;
225
+ }
226
+ } catch (error) {
227
+ if (options.failClosed) {
228
+ throw new CredentialStoreUnavailableError(
229
+ error instanceof Error ? error.message : String(error)
230
+ );
231
+ }
232
+ }
233
+ }
234
+ async function verifyMigratedSession(target, expected) {
235
+ const migrated = await target.read();
236
+ if (migrated?.accessToken !== expected.accessToken || migrated.refreshToken !== expected.refreshToken) {
237
+ throw new Error("Credential migration verification failed.");
238
+ }
239
+ }
240
+ async function getActiveCredentialBackendName() {
241
+ const backend = await getCredentialBackend();
242
+ return backend.name;
243
+ }
244
+ async function getStoredSession() {
245
+ if (process.env.DREAMBOARD_AGENT_TOKEN?.trim()) {
246
+ return null;
247
+ }
248
+ const backend = await getCredentialBackend();
249
+ return backend.read();
250
+ }
251
+ async function withCredentialLock(fn, options) {
252
+ return withFileLock(
253
+ getCredentialLockPath(),
254
+ async () => {
255
+ const backend = await getCredentialBackend();
256
+ const ops = {
257
+ backendName: backend.name,
258
+ read: () => backend.read(),
259
+ writeFull: (creds) => backend.writeFull(creds),
260
+ writeAccessOnly: (accessToken) => backend.writeAccessOnly(accessToken),
261
+ clear: (reason) => backend.clear(reason)
262
+ };
263
+ return fn(ops);
264
+ },
265
+ options
266
+ );
267
+ }
268
+
269
+ // src/config/global-config.ts
270
+ function normalizeCredentialBackend(value) {
271
+ if (value === "file" || value === "keychain") return value;
272
+ return void 0;
273
+ }
274
+ function getGlobalConfigPath() {
275
+ return path2.join(os2.homedir(), PROJECT_DIR_NAME, "config.json");
276
+ }
277
+ function getGlobalAuthPath() {
278
+ return getCredentialFilePath();
279
+ }
280
+ async function loadGlobalConfig() {
281
+ const config = await readJsonFile(getGlobalConfigPath()).catch(
282
+ () => ({})
283
+ );
284
+ return {
285
+ environment: config.environment,
286
+ credentialBackend: normalizeCredentialBackend(config.credentialBackend)
287
+ };
288
+ }
289
+ async function saveGlobalConfig(config) {
290
+ const configDir = path2.join(os2.homedir(), PROJECT_DIR_NAME);
291
+ await ensureDir(configDir);
292
+ const normalized = {
293
+ environment: config.environment,
294
+ credentialBackend: normalizeCredentialBackend(config.credentialBackend)
295
+ };
296
+ await atomicWriteFile(
297
+ getGlobalConfigPath(),
298
+ `${JSON.stringify(normalized, null, 2)}
299
+ `,
300
+ { mode: 384 }
301
+ );
302
+ }
303
+
304
+ export {
305
+ getGlobalConfigPath,
306
+ getGlobalAuthPath,
307
+ loadGlobalConfig,
308
+ saveGlobalConfig,
309
+ getActiveCredentialBackendName,
310
+ getStoredSession,
311
+ withCredentialLock
312
+ };
313
+ //# sourceMappingURL=chunk-6NYVJYN4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config/global-config.ts","../src/config/credential-store.ts"],"sourcesContent":["import os from \"node:os\";\nimport path from \"node:path\";\nimport type { CredentialBackendPreference, GlobalConfig } from \"../types.js\";\nimport { PROJECT_DIR_NAME } from \"../constants.js\";\nimport { ensureDir, readJsonFile } from \"../utils/fs.js\";\nimport { atomicWriteFile } from \"../utils/atomic-file.js\";\nimport { getCredentialFilePath } from \"./credential-store.js\";\n\nfunction normalizeCredentialBackend(\n value: unknown,\n): CredentialBackendPreference | undefined {\n if (value === \"file\" || value === \"keychain\") return value;\n // Tolerate unknown / malformed values rather than refusing to load the\n // whole config - an unrecognised backend name should degrade to \"use\n // the default\" instead of locking the user out of their CLI.\n return undefined;\n}\n\nexport function getGlobalConfigPath(): string {\n return path.join(os.homedir(), PROJECT_DIR_NAME, \"config.json\");\n}\n\n/**\n * Path to the on-disk credential file used by the file backend of\n * `CredentialStore`. Re-exported here to avoid circular / ad-hoc imports\n * in UI surface (`auth status`, `config show`, etc).\n */\nexport function getGlobalAuthPath(): string {\n return getCredentialFilePath();\n}\n\n/**\n * Load non-credential CLI configuration.\n *\n * Note: this function used to also load `authToken` / `refreshToken`\n * from `auth.json` and flatten them onto `GlobalConfig`. That shape\n * enabled the refresh-token-wipe bug: `saveGlobalConfig({ ...config })`\n * without explicit auth fields erased the stored refresh token.\n *\n * Credentials are now owned exclusively by `CredentialStore`. Callers\n * that need them must import `getCredentials()` directly.\n */\nexport async function loadGlobalConfig(): Promise<GlobalConfig> {\n const config = await readJsonFile<GlobalConfig>(getGlobalConfigPath()).catch(\n () => ({}) as GlobalConfig,\n );\n return {\n environment: config.environment,\n credentialBackend: normalizeCredentialBackend(config.credentialBackend),\n };\n}\n\n/**\n * Persist non-credential CLI configuration.\n *\n * This function cannot write credentials, by construction: the\n * `GlobalConfig` type has no credential fields. Credentials must be\n * persisted through the user session manager.\n */\nexport async function saveGlobalConfig(config: GlobalConfig): Promise<void> {\n const configDir = path.join(os.homedir(), PROJECT_DIR_NAME);\n await ensureDir(configDir);\n const normalized: GlobalConfig = {\n environment: config.environment,\n credentialBackend: normalizeCredentialBackend(config.credentialBackend),\n };\n await atomicWriteFile(\n getGlobalConfigPath(),\n `${JSON.stringify(normalized, null, 2)}\\n`,\n { mode: 0o600 },\n );\n}\n","/**\n * Single writer for the long-lived Dreamboard session credentials.\n *\n * Design invariants (enforced at the type level and tested in\n * `credential-store.test.ts`):\n *\n * 1. This module is the ONLY place in the CLI that writes credentials to\n * disk or the OS keychain. `global-config.ts` used to own both the\n * config and the credentials via `saveGlobalConfig`, which made it\n * trivial to wipe a refresh token by accident. The `GlobalConfig` type\n * no longer carries credentials, so attempting to persist one through\n * the config path is a type error.\n *\n * 2. Product credential mutations are owned by `user-session-manager.ts`.\n * This module exposes the locked backend operations it needs, but no\n * command-level write helpers. `Credentials` requires both the Clerk\n * access and refresh tokens, while derived Dreamboard audience tokens are\n * optional caches.\n *\n * 3. Writes go through `atomicWriteFile` + `withFileLock`, so a crash or\n * interrupt during CLI writes cannot leave `auth.json`\n * truncated, and parallel CLI invocations cannot clobber each other's\n * rotated refresh tokens.\n *\n * 4. The on-disk JSON shape for the file backend is kept backward\n * compatible: we continue to read/write `authToken` + `refreshToken`\n * so existing users are not forced to log in again after this change.\n * A newer `accessToken` key is also accepted for read to ease any\n * future format bump.\n *\n * 5. All builds default to the file backend. The OS keychain is an explicit\n * opt-in through config or `DREAMBOARD_CREDENTIAL_BACKEND=keychain`.\n */\n\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { promises as fs } from \"node:fs\";\nimport { PROJECT_DIR_NAME } from \"../constants.js\";\nimport {\n atomicWriteFile,\n withFileLock,\n type FileLockOptions,\n} from \"../utils/atomic-file.js\";\n\n/**\n * Fully refreshable session. `accessToken` is the Clerk OAuth bootstrap token\n * retained for refresh/exchange compatibility; ordinary API calls use\n * `dreamboardApiToken`.\n */\nexport type Credentials = {\n readonly accessToken: string;\n readonly refreshToken: string;\n readonly tokenExpiresAt?: string;\n readonly dreamboardApiToken?: string;\n readonly dreamboardApiExpiresAt?: string;\n readonly clerkOAuthIssuer?: string;\n readonly clerkOAuthClientId?: string;\n readonly clerkOAuthTokenUrl?: string;\n readonly environment?: string;\n};\n\n/**\n * Raw on-disk snapshot. Either or both fields may be present. The refresh\n * coordinator only acts on snapshots that have both tokens populated.\n */\nexport type StoredSessionSnapshot = {\n readonly accessToken?: string;\n readonly refreshToken?: string;\n readonly tokenExpiresAt?: string;\n readonly dreamboardApiToken?: string;\n readonly dreamboardApiExpiresAt?: string;\n readonly clerkOAuthIssuer?: string;\n readonly clerkOAuthClientId?: string;\n readonly clerkOAuthTokenUrl?: string;\n readonly environment?: string;\n};\n\nexport type CredentialBackendName = \"file\" | \"keychain\";\n\nexport type CredentialBackend = {\n readonly name: CredentialBackendName;\n read(): Promise<StoredSessionSnapshot | null>;\n writeFull(creds: Credentials): Promise<void>;\n writeAccessOnly(accessToken: string): Promise<void>;\n clear(reason?: CredentialClearReason): Promise<void>;\n};\n\nexport type CredentialLockOps = {\n readonly backendName: CredentialBackendName;\n read(): Promise<StoredSessionSnapshot | null>;\n writeFull(creds: Credentials): Promise<void>;\n writeAccessOnly(accessToken: string): Promise<void>;\n clear(reason?: CredentialClearReason): Promise<void>;\n};\n\nexport type CredentialClearReason =\n | \"auth_clear_command\"\n | \"logout_command\"\n | \"credential_store_clear\";\n\ntype DiskShape = Partial<{\n clerkAccessToken: string;\n clerkAccessExpiresAt: string;\n accessToken: string;\n authToken: string;\n refreshToken: string;\n tokenExpiresAt: string;\n dreamboardApiToken: string;\n dreamboardApiExpiresAt: string;\n clerkOAuthIssuer: string;\n clerkOAuthClientId: string;\n clerkOAuthTokenUrl: string;\n environment: string;\n}>;\n\nlet credentialDirectoryOverrideForTests: string | null = null;\n\nfunction getCredentialDirectory(): string {\n return (\n credentialDirectoryOverrideForTests ??\n path.join(os.homedir(), PROJECT_DIR_NAME)\n );\n}\n\nexport function getCredentialFilePath(): string {\n return path.join(getCredentialDirectory(), \"auth.json\");\n}\n\nexport function getCredentialAuditLogPath(): string {\n return path.join(getCredentialDirectory(), \"auth-events.log\");\n}\n\nfunction getCredentialLockPath(): string {\n return `${getCredentialFilePath()}.lock`;\n}\n\nasync function appendCredentialAuditEvent(event: {\n readonly event: \"auth_file_deleted\" | \"auth_file_delete_missing\";\n readonly reason: CredentialClearReason;\n readonly authPath: string;\n readonly backend: CredentialBackendName;\n}): Promise<void> {\n try {\n const logPath = getCredentialAuditLogPath();\n await fs.mkdir(path.dirname(logPath), { recursive: true, mode: 0o700 });\n await fs.appendFile(\n logPath,\n `${JSON.stringify({\n timestamp: new Date().toISOString(),\n pid: process.pid,\n ...event,\n })}\\n`,\n { mode: 0o600 },\n );\n } catch {\n // Credential clearing must not fail because local diagnostic logging failed.\n }\n}\n\nasync function fileRead(): Promise<StoredSessionSnapshot | null> {\n const filePath = getCredentialFilePath();\n let data: string;\n try {\n data = await fs.readFile(filePath, \"utf8\");\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") return null;\n throw err;\n }\n if (data.trim().length === 0) {\n return null;\n }\n let parsed: DiskShape;\n try {\n parsed = JSON.parse(data) as DiskShape;\n } catch {\n return null;\n }\n const accessToken =\n parsed.clerkAccessToken ?? parsed.accessToken ?? parsed.authToken;\n const refreshToken = parsed.refreshToken;\n if (!accessToken && !refreshToken) return null;\n return {\n accessToken: accessToken || undefined,\n refreshToken: refreshToken || undefined,\n tokenExpiresAt:\n parsed.clerkAccessExpiresAt || parsed.tokenExpiresAt || undefined,\n dreamboardApiToken: parsed.dreamboardApiToken || undefined,\n dreamboardApiExpiresAt: parsed.dreamboardApiExpiresAt || undefined,\n clerkOAuthIssuer: parsed.clerkOAuthIssuer || undefined,\n clerkOAuthClientId: parsed.clerkOAuthClientId || undefined,\n clerkOAuthTokenUrl: parsed.clerkOAuthTokenUrl || undefined,\n environment: parsed.environment || undefined,\n };\n}\n\nasync function writeFilePayload(payload: DiskShape): Promise<void> {\n await atomicWriteFile(\n getCredentialFilePath(),\n `${JSON.stringify(payload, null, 2)}\\n`,\n { mode: 0o600 },\n );\n}\n\nasync function fileWriteFull(creds: Credentials): Promise<void> {\n if (!creds.accessToken || !creds.refreshToken) {\n throw new Error(\n \"Refusing to persist credentials with an empty accessToken or refreshToken.\",\n );\n }\n await writeFilePayload({\n clerkAccessToken: creds.accessToken,\n refreshToken: creds.refreshToken,\n clerkAccessExpiresAt: creds.tokenExpiresAt,\n dreamboardApiToken: creds.dreamboardApiToken,\n dreamboardApiExpiresAt: creds.dreamboardApiExpiresAt,\n clerkOAuthIssuer: creds.clerkOAuthIssuer,\n clerkOAuthClientId: creds.clerkOAuthClientId,\n clerkOAuthTokenUrl: creds.clerkOAuthTokenUrl,\n environment: creds.environment,\n });\n}\n\nasync function fileWriteAccessOnly(accessToken: string): Promise<void> {\n if (!accessToken) {\n throw new Error(\"Refusing to persist an empty access token.\");\n }\n await writeFilePayload({ authToken: accessToken });\n}\n\nasync function fileClear(\n reason: CredentialClearReason = \"credential_store_clear\",\n): Promise<void> {\n const filePath = getCredentialFilePath();\n try {\n await fs.unlink(filePath);\n await appendCredentialAuditEvent({\n event: \"auth_file_deleted\",\n reason,\n authPath: filePath,\n backend: \"file\",\n });\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n await appendCredentialAuditEvent({\n event: \"auth_file_delete_missing\",\n reason,\n authPath: filePath,\n backend: \"file\",\n });\n return;\n }\n throw err;\n }\n}\n\nexport const fileCredentialBackend: CredentialBackend = {\n name: \"file\",\n read: fileRead,\n writeFull: fileWriteFull,\n writeAccessOnly: fileWriteAccessOnly,\n clear: fileClear,\n};\n\nexport type BackendResolver = () =>\n | CredentialBackend\n | Promise<CredentialBackend>;\n\nexport class CredentialStoreUnavailableError extends Error {\n readonly code = \"CREDENTIAL_STORE_UNAVAILABLE\";\n\n constructor(reason: string) {\n super(`Credential store unavailable: ${reason}`);\n this.name = \"CredentialStoreUnavailableError\";\n }\n}\n\nlet cachedBackend: CredentialBackend | null = null;\nlet migrationCompleted = false;\nlet backendResolver: BackendResolver = defaultBackendResolver;\n\n/**\n * Resolver precedence for all builds:\n *\n * 1. `DREAMBOARD_CREDENTIAL_BACKEND` env var (debugging / CI override).\n * - \"file\" -> force file\n * - \"keychain\" -> force keychain (falls back to file if the native\n * module or the OS keyring is unavailable)\n * - \"auto\" -> same as unset (use config)\n * - unknown -> throw so typos fail loud\n * 2. `credentialBackend` in `~/.dreamboard/config.json`.\n * - \"keychain\" -> opt in to the OS keychain (with file fallback)\n * - \"file\" / unset / malformed -> file\n * 3. Default: file backend.\n *\n * Keychain is opt-in because on macOS the OS login-keychain prompts for\n * the user's password the first time a new binary tries to write to an\n * item, and re-prompts whenever the Node binary signature changes. We\n * would rather ship a zero-prompt default and let users who care about\n * encrypted-at-rest storage enable it.\n *\n * The resolver is async because the keychain probe requires a dynamic\n * `@napi-rs/keyring` import.\n */\nasync function defaultBackendResolver(): Promise<CredentialBackend> {\n const override = (process.env.DREAMBOARD_CREDENTIAL_BACKEND ?? \"\")\n .trim()\n .toLowerCase();\n if (override === \"file\") {\n return fileCredentialBackend;\n }\n if (override && override !== \"keychain\" && override !== \"auto\") {\n // Fail loud on typos rather than silently falling back: this env\n // var exists specifically for users who are debugging auth issues\n // and need to know their override took effect.\n throw new Error(\n `Unknown DREAMBOARD_CREDENTIAL_BACKEND value \"${override}\" (expected \"file\", \"keychain\", or \"auto\").`,\n );\n }\n\n const useKeychain =\n override === \"keychain\" || (await readCredentialBackendPreference());\n if (!useKeychain) {\n return fileCredentialBackend;\n }\n\n const { tryKeychainBackend } = await import(\"./keychain-backend.js\");\n const keychain = await tryKeychainBackend();\n if (keychain.available) {\n return keychain.backend;\n }\n // The user explicitly asked for keychain but the platform can't\n // provide one (no libsecret on Linux, missing native module, etc).\n // Silently degrade to the file backend so the CLI stays usable; the\n // active backend is still visible through `dreamboard auth status`.\n return fileCredentialBackend;\n}\n\nasync function readCredentialBackendPreference(): Promise<boolean> {\n try {\n // Dynamic import to avoid a top-level cycle with `global-config.ts`\n // (which imports `getCredentialFilePath` from this module). Using\n // the async path keeps the cycle purely lazy.\n const { loadGlobalConfig } = await import(\"./global-config.js\");\n const config = await loadGlobalConfig();\n return config.credentialBackend === \"keychain\";\n } catch {\n // If the config file is unreadable or the dynamic import fails\n // (e.g. during early bootstrap), fall back to the file-backed\n // default rather than crashing credential lookups.\n return false;\n }\n}\n\n/**\n * Override which backend is used. Tests use this to inject in-memory\n * backends; production code uses the file-default resolver.\n */\nexport function setCredentialBackendResolver(resolver: BackendResolver): void {\n backendResolver = resolver;\n cachedBackend = null;\n migrationCompleted = false;\n}\n\nexport async function getCredentialBackend(): Promise<CredentialBackend> {\n if (cachedBackend === null) {\n cachedBackend = await backendResolver();\n // One-time migration: if we resolved to a non-file backend and\n // `auth.json` still has credentials from the old layout, copy them\n // over. The file is intentionally left in place; implicit backend\n // migration must not make a working CLI session appear to vanish from\n // the default file-backed view.\n if (!migrationCompleted && cachedBackend.name !== \"file\") {\n await migrateFromFileBackendIfNeeded(cachedBackend);\n }\n migrationCompleted = true;\n }\n return cachedBackend;\n}\n\nasync function migrateFromFileBackendIfNeeded(\n target: CredentialBackend,\n options: { failClosed?: boolean } = {},\n): Promise<void> {\n try {\n const [onDisk, onTarget] = await Promise.all([\n fileCredentialBackend.read(),\n target.read(),\n ]);\n if (!onDisk) return;\n if (onTarget) {\n // Target already has a session - the user has already migrated. Leave the\n // file copy alone so a transient keychain override/probe cannot remove\n // the visible file-backed session.\n return;\n }\n if (onDisk.accessToken && onDisk.refreshToken) {\n const migrated: Credentials = {\n accessToken: onDisk.accessToken,\n refreshToken: onDisk.refreshToken,\n tokenExpiresAt: onDisk.tokenExpiresAt,\n dreamboardApiToken: onDisk.dreamboardApiToken,\n dreamboardApiExpiresAt: onDisk.dreamboardApiExpiresAt,\n clerkOAuthIssuer: onDisk.clerkOAuthIssuer,\n clerkOAuthClientId: onDisk.clerkOAuthClientId,\n clerkOAuthTokenUrl: onDisk.clerkOAuthTokenUrl,\n environment: onDisk.environment,\n };\n await target.writeFull(migrated);\n await verifyMigratedSession(target, migrated);\n } else if (onDisk.accessToken) {\n await target.writeAccessOnly(onDisk.accessToken);\n const migrated = await target.read();\n if (migrated?.accessToken !== onDisk.accessToken) {\n throw new Error(\"Credential migration verification failed.\");\n }\n } else {\n return;\n }\n } catch (error) {\n if (options.failClosed) {\n throw new CredentialStoreUnavailableError(\n error instanceof Error ? error.message : String(error),\n );\n }\n // Migration is best-effort. A failure here should not block CLI\n // operation; on next run the file backend is still consulted\n // directly because the keychain backend's `read` returns null and\n // callers fall through to \"missing session\" → login prompt.\n }\n}\n\nasync function verifyMigratedSession(\n target: CredentialBackend,\n expected: Credentials,\n): Promise<void> {\n const migrated = await target.read();\n if (\n migrated?.accessToken !== expected.accessToken ||\n migrated.refreshToken !== expected.refreshToken\n ) {\n throw new Error(\"Credential migration verification failed.\");\n }\n}\n\nexport async function getActiveCredentialBackendName(): Promise<CredentialBackendName> {\n const backend = await getCredentialBackend();\n return backend.name;\n}\n\n/** Loose read: returns whatever is on disk, including access-only sessions. */\nexport async function getStoredSession(): Promise<StoredSessionSnapshot | null> {\n if (process.env.DREAMBOARD_AGENT_TOKEN?.trim()) {\n return null;\n }\n const backend = await getCredentialBackend();\n return backend.read();\n}\n\n/** Strict read: returns a refreshable pair, or null if either token is missing. */\nexport async function getCredentials(): Promise<Credentials | null> {\n const snapshot = await getStoredSession();\n if (!snapshot) return null;\n const { accessToken, refreshToken } = snapshot;\n if (!accessToken || !refreshToken) return null;\n return {\n accessToken,\n refreshToken,\n tokenExpiresAt: snapshot.tokenExpiresAt,\n dreamboardApiToken: snapshot.dreamboardApiToken,\n dreamboardApiExpiresAt: snapshot.dreamboardApiExpiresAt,\n clerkOAuthIssuer: snapshot.clerkOAuthIssuer,\n clerkOAuthClientId: snapshot.clerkOAuthClientId,\n clerkOAuthTokenUrl: snapshot.clerkOAuthTokenUrl,\n environment: snapshot.environment,\n };\n}\n\n/**\n * Run `fn` while holding the cross-process credential lock. `fn` receives\n * an ops handle that reads/writes the active backend without re-acquiring\n * the lock (avoiding deadlock).\n *\n * This is the only correct way to perform a read-modify-write on stored\n * credentials (e.g. CLI refresh rotation) in the presence of\n * concurrent CLI invocations.\n */\nexport async function withCredentialLock<T>(\n fn: (ops: CredentialLockOps) => Promise<T>,\n options?: FileLockOptions,\n): Promise<T> {\n return withFileLock(\n getCredentialLockPath(),\n async () => {\n const backend = await getCredentialBackend();\n const ops: CredentialLockOps = {\n backendName: backend.name,\n read: () => backend.read(),\n writeFull: (creds) => backend.writeFull(creds),\n writeAccessOnly: (accessToken) => backend.writeAccessOnly(accessToken),\n clear: (reason) => backend.clear(reason),\n };\n return fn(ops);\n },\n options,\n );\n}\n\n/** Test-only reset of module state. Not exported through the barrel. */\nexport function _resetCredentialStoreForTests(): void {\n cachedBackend = null;\n migrationCompleted = false;\n backendResolver = defaultBackendResolver;\n credentialDirectoryOverrideForTests = null;\n}\n\n/** Test-only override of the credential directory. Not exported through the barrel. */\nexport function _setCredentialDirectoryForTests(\n directory: string | null,\n): void {\n credentialDirectoryOverrideForTests = directory;\n cachedBackend = null;\n migrationCompleted = false;\n}\n"],"mappings":"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","names":["os","path","loadGlobalConfig","path","os"]}
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/utils/fs.ts
4
+ import { mkdir, readFile, stat, writeFile } from "fs/promises";
5
+ import path from "path";
6
+ async function ensureDir(dirPath) {
7
+ await mkdir(dirPath, { recursive: true });
8
+ }
9
+ async function exists(filePath) {
10
+ try {
11
+ await stat(filePath);
12
+ return true;
13
+ } catch {
14
+ return false;
15
+ }
16
+ }
17
+ async function readTextFile(filePath) {
18
+ return readFile(filePath, "utf8");
19
+ }
20
+ async function readTextFileIfExists(filePath) {
21
+ try {
22
+ return await readFile(filePath, "utf8");
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
27
+ async function writeTextFile(filePath, content) {
28
+ await ensureDir(path.dirname(filePath));
29
+ await writeFile(filePath, content, "utf8");
30
+ }
31
+ async function readJsonFile(filePath) {
32
+ const data = await readTextFile(filePath);
33
+ return JSON.parse(data);
34
+ }
35
+ async function writeJsonFile(filePath, data) {
36
+ await writeTextFile(filePath, `${JSON.stringify(data, null, 2)}
37
+ `);
38
+ }
39
+
40
+ // src/constants.ts
41
+ var DEFAULT_API_BASE_URL = "https://api.dreamboard.games";
42
+ var DEFAULT_WEB_BASE_URL = "https://dreamboard.games";
43
+ var PROJECT_DIR_NAME = ".dreamboard";
44
+ var DEFAULT_CLERK_OAUTH_SCOPE = "openid profile email offline_access";
45
+ var ENVIRONMENT_CONFIGS = {
46
+ local: {
47
+ apiBaseUrl: "http://localhost:8080",
48
+ webBaseUrl: "http://localhost:5173",
49
+ clerkOAuthScope: DEFAULT_CLERK_OAUTH_SCOPE
50
+ },
51
+ staging: {
52
+ apiBaseUrl: "https://api-staging.dreamboard.games",
53
+ webBaseUrl: "https://staging.dreamboard.games",
54
+ clerkOAuthIssuer: "https://happy-caribou-19.clerk.accounts.dev",
55
+ clerkOAuthClientId: "wkjMF92OFsKbSaGI",
56
+ clerkOAuthTokenUrl: "https://happy-caribou-19.clerk.accounts.dev/oauth/token",
57
+ clerkOAuthScope: DEFAULT_CLERK_OAUTH_SCOPE
58
+ },
59
+ prod: {
60
+ apiBaseUrl: "https://api.dreamboard.games",
61
+ webBaseUrl: "https://dreamboard.games",
62
+ clerkOAuthScope: DEFAULT_CLERK_OAUTH_SCOPE
63
+ }
64
+ };
65
+ var PROJECT_CONFIG_FILE = "project.json";
66
+ var PROJECT_STATE_FILE = "state.json";
67
+ var SNAPSHOT_FILE = "snapshot.json";
68
+ var MANIFEST_FILE = "manifest.ts";
69
+ var GENERATED_DIR_NAME = "generated";
70
+ var MATERIALIZED_MANIFEST_FILE = `${PROJECT_DIR_NAME}/${GENERATED_DIR_NAME}/manifest.json`;
71
+ var MANIFEST_TYPECHECK_CONFIG_FILE = "manifest.tsconfig.json";
72
+ var RULE_FILE = "rule.md";
73
+ var DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1e3;
74
+ var LOCAL_IGNORE_DIRS = /* @__PURE__ */ new Set([
75
+ ".dreamboard",
76
+ ".git",
77
+ "node_modules",
78
+ "dist"
79
+ ]);
80
+
81
+ // src/utils/atomic-file.ts
82
+ import { constants as fsConstants, promises as fs } from "fs";
83
+ import path2 from "path";
84
+ import crypto from "crypto";
85
+ async function atomicWriteFile(targetPath, contents, options = {}) {
86
+ if (contents.length === 0) {
87
+ throw new Error(
88
+ `Refusing to atomicWriteFile an empty payload to ${targetPath}`
89
+ );
90
+ }
91
+ const mode = options.mode ?? 384;
92
+ const shouldFsync = options.fsync ?? true;
93
+ const dir = path2.dirname(targetPath);
94
+ await fs.mkdir(dir, { recursive: true });
95
+ const suffix = crypto.randomBytes(6).toString("hex");
96
+ const tmpPath = `${targetPath}.tmp-${process.pid}-${suffix}`;
97
+ const fh = await fs.open(
98
+ tmpPath,
99
+ fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
100
+ mode
101
+ );
102
+ try {
103
+ await fh.writeFile(contents, "utf8");
104
+ try {
105
+ await fh.chmod(mode);
106
+ } catch {
107
+ }
108
+ if (shouldFsync) {
109
+ try {
110
+ await fh.sync();
111
+ } catch {
112
+ }
113
+ }
114
+ } finally {
115
+ await fh.close();
116
+ }
117
+ try {
118
+ await fs.rename(tmpPath, targetPath);
119
+ } catch (err) {
120
+ await fs.unlink(tmpPath).catch(() => void 0);
121
+ throw err;
122
+ }
123
+ }
124
+ async function withFileLock(lockPath, fn, options = {}) {
125
+ const retries = options.retries ?? 100;
126
+ const minDelayMs = options.minDelayMs ?? 20;
127
+ const maxDelayMs = options.maxDelayMs ?? 200;
128
+ const staleMs = options.staleMs ?? 3e4;
129
+ await fs.mkdir(path2.dirname(lockPath), { recursive: true });
130
+ let attempt = 0;
131
+ let acquired = false;
132
+ while (!acquired) {
133
+ try {
134
+ const fh = await fs.open(
135
+ lockPath,
136
+ fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,
137
+ 384
138
+ );
139
+ await fh.writeFile(`${process.pid}
140
+ `, "utf8");
141
+ await fh.close();
142
+ acquired = true;
143
+ break;
144
+ } catch (err) {
145
+ const code = err.code;
146
+ if (code !== "EEXIST") {
147
+ throw err;
148
+ }
149
+ }
150
+ let stat2 = null;
151
+ try {
152
+ stat2 = await fs.stat(lockPath);
153
+ } catch {
154
+ continue;
155
+ }
156
+ if (stat2 !== null) {
157
+ const ageMs = Date.now() - stat2.mtimeMs;
158
+ if (ageMs > staleMs) {
159
+ await fs.unlink(lockPath).catch(() => void 0);
160
+ continue;
161
+ }
162
+ }
163
+ attempt += 1;
164
+ if (attempt >= retries) {
165
+ throw new Error(
166
+ `Timed out acquiring file lock at ${lockPath} after ${retries} attempts.`
167
+ );
168
+ }
169
+ const jitter = Math.floor(
170
+ Math.random() * Math.max(1, maxDelayMs - minDelayMs)
171
+ );
172
+ await new Promise((resolve) => setTimeout(resolve, minDelayMs + jitter));
173
+ }
174
+ try {
175
+ return await fn();
176
+ } finally {
177
+ await fs.unlink(lockPath).catch(() => void 0);
178
+ }
179
+ }
180
+
181
+ export {
182
+ ensureDir,
183
+ exists,
184
+ readTextFile,
185
+ readTextFileIfExists,
186
+ readJsonFile,
187
+ writeJsonFile,
188
+ DEFAULT_API_BASE_URL,
189
+ DEFAULT_WEB_BASE_URL,
190
+ PROJECT_DIR_NAME,
191
+ ENVIRONMENT_CONFIGS,
192
+ PROJECT_CONFIG_FILE,
193
+ PROJECT_STATE_FILE,
194
+ SNAPSHOT_FILE,
195
+ MANIFEST_FILE,
196
+ MATERIALIZED_MANIFEST_FILE,
197
+ MANIFEST_TYPECHECK_CONFIG_FILE,
198
+ RULE_FILE,
199
+ DEFAULT_LOGIN_TIMEOUT_MS,
200
+ LOCAL_IGNORE_DIRS,
201
+ atomicWriteFile,
202
+ withFileLock
203
+ };
204
+ //# sourceMappingURL=chunk-EQNBQVIW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/fs.ts","../src/constants.ts","../src/utils/atomic-file.ts"],"sourcesContent":["import { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nexport async function ensureDir(dirPath: string): Promise<void> {\n await mkdir(dirPath, { recursive: true });\n}\n\nexport async function exists(filePath: string): Promise<boolean> {\n try {\n await stat(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function readTextFile(filePath: string): Promise<string> {\n return readFile(filePath, \"utf8\");\n}\n\nexport async function readTextFileIfExists(\n filePath: string,\n): Promise<string | null> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n\nexport async function writeTextFile(\n filePath: string,\n content: string,\n): Promise<void> {\n await ensureDir(path.dirname(filePath));\n await writeFile(filePath, content, \"utf8\");\n}\n\nexport async function readJsonFile<T>(filePath: string): Promise<T> {\n const data = await readTextFile(filePath);\n return JSON.parse(data) as T;\n}\n\nexport async function writeJsonFile(\n filePath: string,\n data: unknown,\n): Promise<void> {\n await writeTextFile(filePath, `${JSON.stringify(data, null, 2)}\\n`);\n}\n","import type { EnvironmentConfig } from \"./types.js\";\n\nexport const DEFAULT_API_BASE_URL = \"https://api.dreamboard.games\";\nexport const DEFAULT_WEB_BASE_URL = \"https://dreamboard.games\";\n\nexport const PROJECT_DIR_NAME = \".dreamboard\";\nexport const DEFAULT_CLERK_OAUTH_SCOPE =\n \"openid profile email offline_access\";\n\n// Predefined environment configurations. These are intentionally static:\n// process/env overrides are applied in config resolution so the CLI does not\n// depend on a shell-sourced env file just to know first-party environments.\nexport const ENVIRONMENT_CONFIGS: Record<string, EnvironmentConfig> = {\n local: {\n apiBaseUrl: \"http://localhost:8080\",\n webBaseUrl: \"http://localhost:5173\",\n clerkOAuthScope: DEFAULT_CLERK_OAUTH_SCOPE,\n },\n staging: {\n apiBaseUrl: \"https://api-staging.dreamboard.games\",\n webBaseUrl: \"https://staging.dreamboard.games\",\n clerkOAuthIssuer: \"https://happy-caribou-19.clerk.accounts.dev\",\n clerkOAuthClientId: \"wkjMF92OFsKbSaGI\",\n clerkOAuthTokenUrl:\n \"https://happy-caribou-19.clerk.accounts.dev/oauth/token\",\n clerkOAuthScope: DEFAULT_CLERK_OAUTH_SCOPE,\n },\n prod: {\n apiBaseUrl: \"https://api.dreamboard.games\",\n webBaseUrl: \"https://dreamboard.games\",\n clerkOAuthScope: DEFAULT_CLERK_OAUTH_SCOPE,\n },\n};\nexport const PROJECT_CONFIG_FILE = \"project.json\";\nexport const PROJECT_STATE_FILE = \"state.json\";\nexport const SNAPSHOT_FILE = \"snapshot.json\";\nexport const MANIFEST_FILE = \"manifest.ts\";\nexport const GENERATED_DIR_NAME = \"generated\";\nexport const MATERIALIZED_MANIFEST_FILE = `${PROJECT_DIR_NAME}/${GENERATED_DIR_NAME}/manifest.json`;\nexport const MANIFEST_TYPECHECK_CONFIG_FILE = \"manifest.tsconfig.json\";\nexport const RULE_FILE = \"rule.md\";\nexport const DEFAULT_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;\nexport const DEFAULT_TURN_DELAY_MS = 250;\n\nexport const LOCAL_IGNORE_DIRS = new Set([\n \".dreamboard\",\n \".git\",\n \"node_modules\",\n \"dist\",\n]);\n","/**\n * Primitives for safely mutating local state files owned by the CLI.\n *\n * Two guarantees:\n * - Writes are atomic-ish: we stage the payload in a sibling temp file with\n * the target permissions, fsync the contents, then `rename` over the target.\n * On POSIX `rename` within the same directory is atomic; on Windows it is\n * atomic within the same volume which is always the case for files we write\n * inside `~/.dreamboard`.\n * - We refuse to clobber a file with an empty payload. The original bug that\n * wiped refresh tokens on a failing `sync`/`compile` hinged on `undefined`\n * JSON values being persisted and reloaded as `{}`. Forbidding empty\n * writes here removes that entire failure mode at the primitive level.\n *\n * Additionally, `withFileLock` provides a cross-process advisory lock built on\n * `O_CREAT | O_EXCL` so that parallel CLI invocations serialize around mutations\n * of the same credential state.\n */\n\nimport { constants as fsConstants, promises as fs, type Stats } from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\n\nexport type AtomicWriteOptions = {\n /** File mode applied to the written file (default: 0o600). */\n mode?: number;\n /** Call `fsync` on the temp file before renaming. Default: true. */\n fsync?: boolean;\n};\n\nexport async function atomicWriteFile(\n targetPath: string,\n contents: string,\n options: AtomicWriteOptions = {},\n): Promise<void> {\n if (contents.length === 0) {\n throw new Error(\n `Refusing to atomicWriteFile an empty payload to ${targetPath}`,\n );\n }\n const mode = options.mode ?? 0o600;\n const shouldFsync = options.fsync ?? true;\n const dir = path.dirname(targetPath);\n await fs.mkdir(dir, { recursive: true });\n\n const suffix = crypto.randomBytes(6).toString(\"hex\");\n const tmpPath = `${targetPath}.tmp-${process.pid}-${suffix}`;\n\n const fh = await fs.open(\n tmpPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n mode,\n );\n try {\n await fh.writeFile(contents, \"utf8\");\n try {\n await fh.chmod(mode);\n } catch {\n // Some filesystems (e.g. network volumes, Windows) refuse chmod.\n // Ignoring here is safe: the `open` call above already created the\n // file with the requested mode on systems that honor it.\n }\n if (shouldFsync) {\n try {\n await fh.sync();\n } catch {\n // Best-effort. Not all backends (tmpfs on some platforms) support fsync.\n }\n }\n } finally {\n await fh.close();\n }\n\n try {\n await fs.rename(tmpPath, targetPath);\n } catch (err) {\n await fs.unlink(tmpPath).catch(() => undefined);\n throw err;\n }\n}\n\nexport type FileLockOptions = {\n /** Max number of acquisition attempts before giving up. Default: 100. */\n retries?: number;\n /** Minimum backoff between retries in ms. Default: 20. */\n minDelayMs?: number;\n /** Maximum backoff between retries in ms. Default: 200. */\n maxDelayMs?: number;\n /**\n * A lockfile older than this is considered stale and forcibly removed.\n * Guards against crashed processes leaving a permanent lock. Default: 30s.\n */\n staleMs?: number;\n};\n\nexport async function withFileLock<T>(\n lockPath: string,\n fn: () => Promise<T>,\n options: FileLockOptions = {},\n): Promise<T> {\n const retries = options.retries ?? 100;\n const minDelayMs = options.minDelayMs ?? 20;\n const maxDelayMs = options.maxDelayMs ?? 200;\n const staleMs = options.staleMs ?? 30_000;\n\n await fs.mkdir(path.dirname(lockPath), { recursive: true });\n\n let attempt = 0;\n let acquired = false;\n while (!acquired) {\n try {\n const fh = await fs.open(\n lockPath,\n fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_EXCL,\n 0o600,\n );\n await fh.writeFile(`${process.pid}\\n`, \"utf8\");\n await fh.close();\n acquired = true;\n break;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code !== \"EEXIST\") {\n throw err;\n }\n }\n\n let stat: Stats | null = null;\n try {\n stat = await fs.stat(lockPath);\n } catch {\n continue;\n }\n if (stat !== null) {\n const ageMs = Date.now() - stat.mtimeMs;\n if (ageMs > staleMs) {\n await fs.unlink(lockPath).catch(() => undefined);\n continue;\n }\n }\n\n attempt += 1;\n if (attempt >= retries) {\n throw new Error(\n `Timed out acquiring file lock at ${lockPath} after ${retries} attempts.`,\n );\n }\n const jitter = Math.floor(\n Math.random() * Math.max(1, maxDelayMs - minDelayMs),\n );\n await new Promise((resolve) => setTimeout(resolve, minDelayMs + jitter));\n }\n\n try {\n return await fn();\n } finally {\n await fs.unlink(lockPath).catch(() => undefined);\n }\n}\n"],"mappings":"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","names":["path","stat"]}