@thinkrun/cli 0.1.27

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 (183) hide show
  1. package/README.md +349 -0
  2. package/dist/bin/thinkrun.d.ts +6 -0
  3. package/dist/bin/thinkrun.d.ts.map +1 -0
  4. package/dist/bin/thinkrun.js +124 -0
  5. package/dist/bin/thinkrun.js.map +1 -0
  6. package/dist/scripts/browse.sh +1107 -0
  7. package/dist/src/adapters/cloud.d.ts +79 -0
  8. package/dist/src/adapters/cloud.d.ts.map +1 -0
  9. package/dist/src/adapters/cloud.js +637 -0
  10. package/dist/src/adapters/cloud.js.map +1 -0
  11. package/dist/src/adapters/index.d.ts +47 -0
  12. package/dist/src/adapters/index.d.ts.map +1 -0
  13. package/dist/src/adapters/index.js +211 -0
  14. package/dist/src/adapters/index.js.map +1 -0
  15. package/dist/src/adapters/local-command-retry.d.ts +12 -0
  16. package/dist/src/adapters/local-command-retry.d.ts.map +1 -0
  17. package/dist/src/adapters/local-command-retry.js +224 -0
  18. package/dist/src/adapters/local-command-retry.js.map +1 -0
  19. package/dist/src/adapters/local.d.ts +136 -0
  20. package/dist/src/adapters/local.d.ts.map +1 -0
  21. package/dist/src/adapters/local.js +1273 -0
  22. package/dist/src/adapters/local.js.map +1 -0
  23. package/dist/src/adapters/types.d.ts +45 -0
  24. package/dist/src/adapters/types.d.ts.map +1 -0
  25. package/dist/src/adapters/types.js +6 -0
  26. package/dist/src/adapters/types.js.map +1 -0
  27. package/dist/src/commands/actions.d.ts +135 -0
  28. package/dist/src/commands/actions.d.ts.map +1 -0
  29. package/dist/src/commands/actions.js +2207 -0
  30. package/dist/src/commands/actions.js.map +1 -0
  31. package/dist/src/commands/agent-init.d.ts +16 -0
  32. package/dist/src/commands/agent-init.d.ts.map +1 -0
  33. package/dist/src/commands/agent-init.js +222 -0
  34. package/dist/src/commands/agent-init.js.map +1 -0
  35. package/dist/src/commands/analyze.d.ts +11 -0
  36. package/dist/src/commands/analyze.d.ts.map +1 -0
  37. package/dist/src/commands/analyze.js +238 -0
  38. package/dist/src/commands/analyze.js.map +1 -0
  39. package/dist/src/commands/cache.d.ts +6 -0
  40. package/dist/src/commands/cache.d.ts.map +1 -0
  41. package/dist/src/commands/cache.js +147 -0
  42. package/dist/src/commands/cache.js.map +1 -0
  43. package/dist/src/commands/cloud.d.ts +6 -0
  44. package/dist/src/commands/cloud.d.ts.map +1 -0
  45. package/dist/src/commands/cloud.js +332 -0
  46. package/dist/src/commands/cloud.js.map +1 -0
  47. package/dist/src/commands/config.d.ts +7 -0
  48. package/dist/src/commands/config.d.ts.map +1 -0
  49. package/dist/src/commands/config.js +208 -0
  50. package/dist/src/commands/config.js.map +1 -0
  51. package/dist/src/commands/doctor.d.ts +127 -0
  52. package/dist/src/commands/doctor.d.ts.map +1 -0
  53. package/dist/src/commands/doctor.js +684 -0
  54. package/dist/src/commands/doctor.js.map +1 -0
  55. package/dist/src/commands/evaluate-helpers.d.ts +6 -0
  56. package/dist/src/commands/evaluate-helpers.d.ts.map +1 -0
  57. package/dist/src/commands/evaluate-helpers.js +13 -0
  58. package/dist/src/commands/evaluate-helpers.js.map +1 -0
  59. package/dist/src/commands/install.d.ts +118 -0
  60. package/dist/src/commands/install.d.ts.map +1 -0
  61. package/dist/src/commands/install.js +975 -0
  62. package/dist/src/commands/install.js.map +1 -0
  63. package/dist/src/commands/release.d.ts +7 -0
  64. package/dist/src/commands/release.d.ts.map +1 -0
  65. package/dist/src/commands/release.js +123 -0
  66. package/dist/src/commands/release.js.map +1 -0
  67. package/dist/src/commands/reset-connection.d.ts +17 -0
  68. package/dist/src/commands/reset-connection.d.ts.map +1 -0
  69. package/dist/src/commands/reset-connection.js +141 -0
  70. package/dist/src/commands/reset-connection.js.map +1 -0
  71. package/dist/src/commands/session-debug.d.ts +23 -0
  72. package/dist/src/commands/session-debug.d.ts.map +1 -0
  73. package/dist/src/commands/session-debug.js +267 -0
  74. package/dist/src/commands/session-debug.js.map +1 -0
  75. package/dist/src/commands/setup.d.ts +53 -0
  76. package/dist/src/commands/setup.d.ts.map +1 -0
  77. package/dist/src/commands/setup.js +249 -0
  78. package/dist/src/commands/setup.js.map +1 -0
  79. package/dist/src/config/store.d.ts +39 -0
  80. package/dist/src/config/store.d.ts.map +1 -0
  81. package/dist/src/config/store.js +290 -0
  82. package/dist/src/config/store.js.map +1 -0
  83. package/dist/src/daemon/access.d.ts +53 -0
  84. package/dist/src/daemon/access.d.ts.map +1 -0
  85. package/dist/src/daemon/access.js +87 -0
  86. package/dist/src/daemon/access.js.map +1 -0
  87. package/dist/src/daemon/bridge-envelope.d.ts +96 -0
  88. package/dist/src/daemon/bridge-envelope.d.ts.map +1 -0
  89. package/dist/src/daemon/bridge-envelope.js +235 -0
  90. package/dist/src/daemon/bridge-envelope.js.map +1 -0
  91. package/dist/src/daemon/utils.d.ts +43 -0
  92. package/dist/src/daemon/utils.d.ts.map +1 -0
  93. package/dist/src/daemon/utils.js +134 -0
  94. package/dist/src/daemon/utils.js.map +1 -0
  95. package/dist/src/errors.d.ts +60 -0
  96. package/dist/src/errors.d.ts.map +1 -0
  97. package/dist/src/errors.js +87 -0
  98. package/dist/src/errors.js.map +1 -0
  99. package/dist/src/local-bridge-timing.d.ts +31 -0
  100. package/dist/src/local-bridge-timing.d.ts.map +1 -0
  101. package/dist/src/local-bridge-timing.js +41 -0
  102. package/dist/src/local-bridge-timing.js.map +1 -0
  103. package/dist/src/obstacle-recovery/classify-script.d.ts +16 -0
  104. package/dist/src/obstacle-recovery/classify-script.d.ts.map +1 -0
  105. package/dist/src/obstacle-recovery/classify-script.js +53 -0
  106. package/dist/src/obstacle-recovery/classify-script.js.map +1 -0
  107. package/dist/src/obstacle-recovery/obstacle-classifier.d.ts +21 -0
  108. package/dist/src/obstacle-recovery/obstacle-classifier.d.ts.map +1 -0
  109. package/dist/src/obstacle-recovery/obstacle-classifier.js +37 -0
  110. package/dist/src/obstacle-recovery/obstacle-classifier.js.map +1 -0
  111. package/dist/src/obstacle-recovery/state-fingerprint.d.ts +26 -0
  112. package/dist/src/obstacle-recovery/state-fingerprint.d.ts.map +1 -0
  113. package/dist/src/obstacle-recovery/state-fingerprint.js +85 -0
  114. package/dist/src/obstacle-recovery/state-fingerprint.js.map +1 -0
  115. package/dist/src/obstacle-recovery/types.d.ts +44 -0
  116. package/dist/src/obstacle-recovery/types.d.ts.map +1 -0
  117. package/dist/src/obstacle-recovery/types.js +16 -0
  118. package/dist/src/obstacle-recovery/types.js.map +1 -0
  119. package/dist/src/output/formatter.d.ts +55 -0
  120. package/dist/src/output/formatter.d.ts.map +1 -0
  121. package/dist/src/output/formatter.js +55 -0
  122. package/dist/src/output/formatter.js.map +1 -0
  123. package/dist/src/output/mode.d.ts +11 -0
  124. package/dist/src/output/mode.d.ts.map +1 -0
  125. package/dist/src/output/mode.js +16 -0
  126. package/dist/src/output/mode.js.map +1 -0
  127. package/dist/src/protected-flow/detector.d.ts +26 -0
  128. package/dist/src/protected-flow/detector.d.ts.map +1 -0
  129. package/dist/src/protected-flow/detector.js +75 -0
  130. package/dist/src/protected-flow/detector.js.map +1 -0
  131. package/dist/src/protected-flow/types.d.ts +24 -0
  132. package/dist/src/protected-flow/types.d.ts.map +1 -0
  133. package/dist/src/protected-flow/types.js +28 -0
  134. package/dist/src/protected-flow/types.js.map +1 -0
  135. package/dist/src/session/agent-identity.d.ts +65 -0
  136. package/dist/src/session/agent-identity.d.ts.map +1 -0
  137. package/dist/src/session/agent-identity.js +133 -0
  138. package/dist/src/session/agent-identity.js.map +1 -0
  139. package/dist/src/session/cli-session-sync.d.ts +72 -0
  140. package/dist/src/session/cli-session-sync.d.ts.map +1 -0
  141. package/dist/src/session/cli-session-sync.js +244 -0
  142. package/dist/src/session/cli-session-sync.js.map +1 -0
  143. package/dist/src/session/context.d.ts +24 -0
  144. package/dist/src/session/context.d.ts.map +1 -0
  145. package/dist/src/session/context.js +165 -0
  146. package/dist/src/session/context.js.map +1 -0
  147. package/dist/src/session/continuity.d.ts +33 -0
  148. package/dist/src/session/continuity.d.ts.map +1 -0
  149. package/dist/src/session/continuity.js +179 -0
  150. package/dist/src/session/continuity.js.map +1 -0
  151. package/dist/src/session/errors.d.ts +9 -0
  152. package/dist/src/session/errors.d.ts.map +1 -0
  153. package/dist/src/session/errors.js +31 -0
  154. package/dist/src/session/errors.js.map +1 -0
  155. package/dist/src/session/local-continuity.d.ts +16 -0
  156. package/dist/src/session/local-continuity.d.ts.map +1 -0
  157. package/dist/src/session/local-continuity.js +146 -0
  158. package/dist/src/session/local-continuity.js.map +1 -0
  159. package/dist/src/session/signal-handler.d.ts +24 -0
  160. package/dist/src/session/signal-handler.d.ts.map +1 -0
  161. package/dist/src/session/signal-handler.js +35 -0
  162. package/dist/src/session/signal-handler.js.map +1 -0
  163. package/dist/src/shared/local-recovery-policy.d.ts +40 -0
  164. package/dist/src/shared/local-recovery-policy.d.ts.map +1 -0
  165. package/dist/src/shared/local-recovery-policy.js +59 -0
  166. package/dist/src/shared/local-recovery-policy.js.map +1 -0
  167. package/dist/src/shared/recovery-state.d.ts +3 -0
  168. package/dist/src/shared/recovery-state.d.ts.map +1 -0
  169. package/dist/src/shared/recovery-state.js +9 -0
  170. package/dist/src/shared/recovery-state.js.map +1 -0
  171. package/dist/src/types.d.ts +131 -0
  172. package/dist/src/types.d.ts.map +1 -0
  173. package/dist/src/types.js +5 -0
  174. package/dist/src/types.js.map +1 -0
  175. package/dist/src/utils.d.ts +50 -0
  176. package/dist/src/utils.d.ts.map +1 -0
  177. package/dist/src/utils.js +147 -0
  178. package/dist/src/utils.js.map +1 -0
  179. package/dist/src/working-location.d.ts +107 -0
  180. package/dist/src/working-location.d.ts.map +1 -0
  181. package/dist/src/working-location.js +651 -0
  182. package/dist/src/working-location.js.map +1 -0
  183. package/package.json +65 -0
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Configuration store using conf package
3
+ * Stores API key, preferences, and active session
4
+ */
5
+ import type { CLIConfig } from '../types.js';
6
+ export declare class ConfigStore {
7
+ private conf;
8
+ constructor();
9
+ private buildConf;
10
+ /** Move a schema-violating apiKey value into apiKeyInvalid by editing the raw file. */
11
+ private quarantineInvalidApiKey;
12
+ get<K extends keyof CLIConfig>(key: K): CLIConfig[K];
13
+ set<K extends keyof CLIConfig>(key: K, value: CLIConfig[K]): void;
14
+ has(key: keyof CLIConfig): boolean;
15
+ delete(key: keyof CLIConfig): void;
16
+ clear(): void;
17
+ getAll(): CLIConfig;
18
+ getPath(): string;
19
+ validateApiKey(key: string): boolean;
20
+ validateExtensionId(extensionId: string): boolean;
21
+ maskApiKey(key?: string): string;
22
+ isConfigured(): boolean;
23
+ /**
24
+ * PRD 0086 B2 — distinct key states for diagnosis:
25
+ * - missing: never configured (no key, no quarantined value)
26
+ * - malformed: a stored value failed shape validation and was quarantined
27
+ * - present: a well-formed key is configured (server may still reject it)
28
+ */
29
+ getKeyHealth(): {
30
+ state: 'missing' | 'malformed' | 'present';
31
+ maskedKey?: string;
32
+ quarantinedMasked?: string;
33
+ shapeValid?: boolean;
34
+ };
35
+ clearQuarantinedApiKey(): void;
36
+ ensureConfigured(): void;
37
+ }
38
+ export declare const config: ConfigStore;
39
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../../src/config/store.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA4J7C,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAkB;;IAuB9B,OAAO,CAAC,SAAS;IAajB,uFAAuF;IACvF,OAAO,CAAC,uBAAuB;IA+B/B,GAAG,CAAC,CAAC,SAAS,MAAM,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC;IAIpD,GAAG,CAAC,CAAC,SAAS,MAAM,SAAS,EAAE,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI;IAIjE,GAAG,CAAC,GAAG,EAAE,MAAM,SAAS,GAAG,OAAO;IAIlC,MAAM,CAAC,GAAG,EAAE,MAAM,SAAS,GAAG,IAAI;IAIlC,KAAK,IAAI,IAAI;IAIb,MAAM,IAAI,SAAS;IAInB,OAAO,IAAI,MAAM;IAKjB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIpC,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO;IAIjD,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM;IAOhC,YAAY,IAAI,OAAO;IAIvB;;;;;OAKG;IACH,YAAY,IAAI;QAAE,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,SAAS,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,OAAO,CAAA;KAAE;IAUpI,sBAAsB,IAAI,IAAI;IAI9B,gBAAgB,IAAI,IAAI;CAOzB;AAGD,eAAO,MAAM,MAAM,aAAoB,CAAC"}
@@ -0,0 +1,290 @@
1
+ /**
2
+ * Configuration store using conf package
3
+ * Stores API key, preferences, and active session
4
+ */
5
+ import Conf from 'conf';
6
+ import { join } from 'path';
7
+ import { homedir, platform } from 'os';
8
+ import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
9
+ // Storage-layer sanity pattern for apiKey (see schema comment below). Shared
10
+ // by the schema and the quarantine guard so they can never drift.
11
+ const STORAGE_API_KEY_PATTERN = '^\\S{8,512}$';
12
+ const STORAGE_API_KEY_RE = new RegExp(STORAGE_API_KEY_PATTERN);
13
+ const schema = {
14
+ apiKey: {
15
+ type: 'string',
16
+ // Sanity check only (no whitespace, plausible length). The strict prefix
17
+ // check lives in validateApiKey at the UX layer — a schema this strict
18
+ // used to make Conf THROW at startup for any corrupted stored value,
19
+ // crashing every CLI command (PRD 0086 B1). Storage must tolerate what
20
+ // the UX layer (or --force) let through; doctor flags shape mismatches.
21
+ pattern: STORAGE_API_KEY_PATTERN,
22
+ },
23
+ // Quarantine slot for a stored apiKey that failed schema validation
24
+ // (e.g. overwritten by autofill with a URL). Deliberately pattern-free.
25
+ apiKeyInvalid: {
26
+ type: 'string',
27
+ },
28
+ apiUrl: {
29
+ type: 'string',
30
+ default: 'https://api.thinkbrowse.io',
31
+ },
32
+ defaultBrowser: {
33
+ type: 'string',
34
+ enum: ['chromium', 'firefox', 'webkit'],
35
+ default: 'chromium',
36
+ },
37
+ extensionId: {
38
+ type: 'string',
39
+ pattern: '^[a-p]{32}$',
40
+ },
41
+ registeredExtensionId: {
42
+ type: 'string',
43
+ pattern: '^[a-p]{32}$',
44
+ },
45
+ preferPackagedDefaultExtensionId: {
46
+ type: 'boolean',
47
+ },
48
+ nativeHostVersion: {
49
+ type: 'string',
50
+ },
51
+ nativeHostChecksum: {
52
+ type: 'string',
53
+ pattern: '^[a-f0-9]{64}$',
54
+ },
55
+ localTabId: {
56
+ type: 'string',
57
+ },
58
+ localAgentSessionId: {
59
+ type: 'string',
60
+ },
61
+ cloudSessionId: {
62
+ type: 'string',
63
+ },
64
+ activeSessionId: {
65
+ // No default — absence means no tab is currently attached.
66
+ // Used by attach command and read by any command that needs the active session.
67
+ type: 'string',
68
+ },
69
+ agentSessionId: {
70
+ // Optional agent session ID for the extension tab registry.
71
+ // Set by attach/new-window, used to pin all commands to the registered tab.
72
+ type: 'string',
73
+ },
74
+ /**
75
+ * Local transport selector (PRD 0086 Track A, task 4.0).
76
+ * 'native' (default): classic native-messaging path — zero behaviour change.
77
+ * 'daemon': route CLI/MCP local requests through the bridge daemon.
78
+ *
79
+ * Set via: thinkrun config set localTransport daemon
80
+ * Rollback: thinkrun config set localTransport native
81
+ * (safe even when daemon is unreachable — pure config write)
82
+ *
83
+ * Host-side gate: daemon only spawns when THINKRUN_DAEMON_SPIKE=1 env flag
84
+ * is present (`THINKBROWSE_DAEMON_SPIKE` is still accepted as a legacy
85
+ * fallback during the internal pass). Wiring this config value → env
86
+ * automatically is task 6.11.
87
+ */
88
+ localTransport: {
89
+ type: 'string',
90
+ enum: ['native', 'daemon'],
91
+ default: 'native',
92
+ },
93
+ };
94
+ // Use ~/.config/thinkrun/ consistently across all platforms so that the
95
+ // CLI reads the same config.json that browse.sh and users expect to find there.
96
+ // THINKRUN_CONFIG_DIR env var overrides the path — used by tests to avoid
97
+ // polluting the real user config with test data.
98
+ function getConfigDir() {
99
+ return process.env.THINKRUN_CONFIG_DIR || join(homedir(), '.config', 'thinkrun');
100
+ }
101
+ function getLegacyBrandConfigPath() {
102
+ return join(homedir(), '.config', 'thinkbrowse', 'config.json');
103
+ }
104
+ /**
105
+ * Determine the platform-default Conf path that was used before the cwd fix.
106
+ * Conf used XDG_CONFIG_HOME on Linux, ~/Library/Preferences/<name>-nodejs on macOS,
107
+ * and %APPDATA%\<name>-nodejs\Config on Windows.
108
+ * Matches the internal logic from the historical `conf` package path used by
109
+ * the pre-rename CLI (`projectName='thinkbrowse'`).
110
+ */
111
+ function getLegacyConfPath() {
112
+ const os = platform();
113
+ if (os === 'darwin') {
114
+ return join(homedir(), 'Library', 'Preferences', 'thinkbrowse-nodejs', 'config.json');
115
+ }
116
+ if (os === 'win32') {
117
+ const appData = process.env.APPDATA;
118
+ if (appData)
119
+ return join(appData, 'thinkbrowse-nodejs', 'Config', 'config.json');
120
+ return null;
121
+ }
122
+ // Linux / other: XDG_CONFIG_HOME or ~/.config
123
+ const xdg = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
124
+ return join(xdg, 'thinkbrowse-nodejs', 'config.json');
125
+ }
126
+ /**
127
+ * One-time migration: if config.json already exists at the new canonical path
128
+ * nothing is done. If the new path is empty but the legacy platform path has data,
129
+ * copy it over so the user's API key and settings are preserved after upgrading.
130
+ */
131
+ function migrateConfigIfNeeded() {
132
+ const newPath = join(getConfigDir(), 'config.json');
133
+ if (existsSync(newPath))
134
+ return; // already migrated or freshly created — nothing to do
135
+ const legacyBrandPath = getLegacyBrandConfigPath();
136
+ if (existsSync(legacyBrandPath)) {
137
+ try {
138
+ const data = readFileSync(legacyBrandPath, 'utf-8');
139
+ mkdirSync(getConfigDir(), { recursive: true });
140
+ writeFileSync(newPath, data, 'utf-8');
141
+ return;
142
+ }
143
+ catch {
144
+ // Continue to older Conf-path migration below.
145
+ }
146
+ }
147
+ const legacyPath = getLegacyConfPath();
148
+ if (!legacyPath || !existsSync(legacyPath))
149
+ return; // no legacy data
150
+ try {
151
+ const data = readFileSync(legacyPath, 'utf-8');
152
+ mkdirSync(getConfigDir(), { recursive: true });
153
+ writeFileSync(newPath, data, 'utf-8');
154
+ }
155
+ catch {
156
+ // Migration is best-effort; failures are silently ignored so startup is never blocked.
157
+ }
158
+ }
159
+ export class ConfigStore {
160
+ conf;
161
+ constructor() {
162
+ migrateConfigIfNeeded();
163
+ try {
164
+ this.conf = this.buildConf();
165
+ }
166
+ catch (error) {
167
+ // PRD 0086 B1: a schema-invalid stored apiKey used to crash every CLI
168
+ // command at startup with a raw `Config schema violation` stack trace.
169
+ // Quarantine the offending value and retry once so the CLI stays
170
+ // usable and can explain what happened (API_KEY_MALFORMED).
171
+ // Match loosely (conf/ajv wording may change across upgrades): attempt
172
+ // quarantine on ANY constructor error, then retry once. If quarantine
173
+ // changed nothing, the retry throws the original class of error anyway.
174
+ this.quarantineInvalidApiKey();
175
+ try {
176
+ this.conf = this.buildConf();
177
+ }
178
+ catch {
179
+ throw error;
180
+ }
181
+ }
182
+ }
183
+ buildConf() {
184
+ return new Conf({
185
+ projectName: 'thinkrun',
186
+ cwd: getConfigDir(),
187
+ schema: schema,
188
+ defaults: {
189
+ apiUrl: 'https://api.thinkbrowse.io',
190
+ defaultBrowser: 'chromium',
191
+ localTransport: 'native',
192
+ },
193
+ });
194
+ }
195
+ /** Move a schema-violating apiKey value into apiKeyInvalid by editing the raw file. */
196
+ quarantineInvalidApiKey() {
197
+ const configPath = join(getConfigDir(), 'config.json');
198
+ try {
199
+ const raw = JSON.parse(readFileSync(configPath, 'utf-8'));
200
+ let changed = false;
201
+ // A corrupted quarantine slot itself (non-string) would also crash Conf.
202
+ if (raw.apiKeyInvalid !== undefined && typeof raw.apiKeyInvalid !== 'string') {
203
+ raw.apiKeyInvalid = JSON.stringify(raw.apiKeyInvalid);
204
+ changed = true;
205
+ }
206
+ const bad = raw.apiKey !== undefined
207
+ && (typeof raw.apiKey !== 'string' || !STORAGE_API_KEY_RE.test(raw.apiKey));
208
+ if (bad) {
209
+ // Non-string corruption is stringified so the quarantine slot (string)
210
+ // can hold it for diagnosis.
211
+ raw.apiKeyInvalid = typeof raw.apiKey === 'string' ? raw.apiKey : JSON.stringify(raw.apiKey);
212
+ delete raw.apiKey;
213
+ changed = true;
214
+ }
215
+ if (changed) {
216
+ writeFileSync(configPath, JSON.stringify(raw, null, '\t'), 'utf-8');
217
+ process.stderr.write('[thinkrun] warn: stored API key failed validation and was quarantined. Run: thinkrun doctor\n');
218
+ }
219
+ }
220
+ catch {
221
+ // If the raw file is unreadable the retried Conf init will surface it.
222
+ }
223
+ }
224
+ get(key) {
225
+ return this.conf.get(key);
226
+ }
227
+ set(key, value) {
228
+ this.conf.set(key, value);
229
+ }
230
+ has(key) {
231
+ return this.conf.has(key);
232
+ }
233
+ delete(key) {
234
+ this.conf.delete(key);
235
+ }
236
+ clear() {
237
+ this.conf.clear();
238
+ }
239
+ getAll() {
240
+ return this.conf.store;
241
+ }
242
+ getPath() {
243
+ return this.conf.path;
244
+ }
245
+ // Validation helpers
246
+ validateApiKey(key) {
247
+ return /^(tb_|mech_browse_|ak_)[A-Za-z0-9_-]+$/.test(key);
248
+ }
249
+ validateExtensionId(extensionId) {
250
+ return /^[a-p]{32}$/.test(extensionId);
251
+ }
252
+ maskApiKey(key) {
253
+ if (!key)
254
+ return '(not set)';
255
+ if (key.length < 8)
256
+ return '***';
257
+ return `${key.slice(0, 12)}...${key.slice(-4)}`;
258
+ }
259
+ // Check if config is ready to use
260
+ isConfigured() {
261
+ return this.has('apiKey') && !!this.get('apiKey');
262
+ }
263
+ /**
264
+ * PRD 0086 B2 — distinct key states for diagnosis:
265
+ * - missing: never configured (no key, no quarantined value)
266
+ * - malformed: a stored value failed shape validation and was quarantined
267
+ * - present: a well-formed key is configured (server may still reject it)
268
+ */
269
+ getKeyHealth() {
270
+ const key = this.get('apiKey');
271
+ if (key)
272
+ return { state: 'present', maskedKey: this.maskApiKey(key), shapeValid: this.validateApiKey(key) };
273
+ const quarantined = this.conf.get('apiKeyInvalid');
274
+ if (typeof quarantined === 'string' && quarantined.length > 0) {
275
+ return { state: 'malformed', quarantinedMasked: this.maskApiKey(quarantined) };
276
+ }
277
+ return { state: 'missing' };
278
+ }
279
+ clearQuarantinedApiKey() {
280
+ this.conf.delete('apiKeyInvalid');
281
+ }
282
+ ensureConfigured() {
283
+ if (!this.isConfigured()) {
284
+ throw new Error('API key not configured. Run: thinkrun config set-key <your-api-key>');
285
+ }
286
+ }
287
+ }
288
+ // Singleton instance
289
+ export const config = new ConfigStore();
290
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../../src/config/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Shared daemon access helpers — used by CLI, MCP server, and any consumer
3
+ * that needs to connect to the bridge daemon when localTransport='daemon'.
4
+ *
5
+ * browse.sh note (when localTransport=daemon):
6
+ * #!/bin/bash
7
+ * # Read daemon access from daemon.json with jq:
8
+ * DAEMON_JSON="$HOME/.thinkrun/daemon.json"
9
+ * if [ -f "$DAEMON_JSON" ] && [ "$(jq -r '.owner' "$DAEMON_JSON" 2>/dev/null)" = "daemon" ]; then
10
+ * DAEMON_PORT=$(jq -r '.port' "$DAEMON_JSON")
11
+ * DAEMON_TOKEN=$(jq -r '.token' "$DAEMON_JSON")
12
+ * # Use port/token for HTTP requests to 127.0.0.1:$DAEMON_PORT
13
+ * fi
14
+ */
15
+ /** Resolved at call-time so tests can override HOME via process.env */
16
+ export declare function getDiscoveryDir(): string;
17
+ /** Resolved at call-time so tests can override HOME via process.env */
18
+ export declare function getDaemonJsonPath(): string;
19
+ /** Stable path reference for callers that don't change HOME (non-test) */
20
+ export declare const DISCOVERY_DIR: string;
21
+ /** Stable path reference for callers that don't change HOME (non-test) */
22
+ export declare const DAEMON_JSON: string;
23
+ /**
24
+ * Port + token pair needed to connect to the bridge daemon.
25
+ * Used by CLI HTTP client and MCP server when localTransport='daemon'.
26
+ */
27
+ export interface DaemonAccess {
28
+ port: number;
29
+ token: string;
30
+ protocolVersion?: number;
31
+ binaryVersion?: string;
32
+ }
33
+ /**
34
+ * Read daemon.json and return the port+token pair needed to connect.
35
+ *
36
+ * Does NOT check PID liveness — that is intentional. MCP/CLI callers
37
+ * should attempt a connection and retry/recover if it fails. Use
38
+ * readDaemonConfig() in daemon.ts for spawn-path calls where liveness
39
+ * matters for startup decisions.
40
+ *
41
+ * Returns null if daemon.json is absent, unreadable, or malformed.
42
+ */
43
+ export declare function readDaemonAccess(): DaemonAccess | null;
44
+ /**
45
+ * Build the HTTP base URL for the daemon given a port.
46
+ */
47
+ export declare function daemonBaseUrl(port: number): string;
48
+ /**
49
+ * Build the WS URL for the extension to connect to the daemon.
50
+ * The token is percent-encoded for safe URL embedding.
51
+ */
52
+ export declare function daemonWsUrl(port: number, token: string, clientType?: 'extension' | 'cli'): string;
53
+ //# sourceMappingURL=access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../../src/daemon/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAMH,uEAAuE;AACvE,wBAAgB,eAAe,IAAI,MAAM,CAGxC;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,IAAI,MAAM,CAO1C;AAED,0EAA0E;AAG1E,eAAO,MAAM,aAAa,QAAoB,CAAC;AAC/C,0EAA0E;AAC1E,eAAO,MAAM,WAAW,QAAsB,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,IAAI,YAAY,GAAG,IAAI,CAwBtD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,GAAE,WAAW,GAAG,KAAmB,GAAG,MAAM,CAE9G"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Shared daemon access helpers — used by CLI, MCP server, and any consumer
3
+ * that needs to connect to the bridge daemon when localTransport='daemon'.
4
+ *
5
+ * browse.sh note (when localTransport=daemon):
6
+ * #!/bin/bash
7
+ * # Read daemon access from daemon.json with jq:
8
+ * DAEMON_JSON="$HOME/.thinkrun/daemon.json"
9
+ * if [ -f "$DAEMON_JSON" ] && [ "$(jq -r '.owner' "$DAEMON_JSON" 2>/dev/null)" = "daemon" ]; then
10
+ * DAEMON_PORT=$(jq -r '.port' "$DAEMON_JSON")
11
+ * DAEMON_TOKEN=$(jq -r '.token' "$DAEMON_JSON")
12
+ * # Use port/token for HTTP requests to 127.0.0.1:$DAEMON_PORT
13
+ * fi
14
+ */
15
+ import { existsSync, readFileSync } from 'fs';
16
+ import { join } from 'path';
17
+ /** Resolved at call-time so tests can override HOME via process.env */
18
+ export function getDiscoveryDir() {
19
+ const home = process.env.HOME || process.env.USERPROFILE || '/tmp';
20
+ return join(home, '.thinkrun');
21
+ }
22
+ /** Resolved at call-time so tests can override HOME via process.env */
23
+ export function getDaemonJsonPath() {
24
+ const current = join(getDiscoveryDir(), 'daemon.json');
25
+ const home = process.env.HOME || process.env.USERPROFILE || '/tmp';
26
+ const legacy = join(home, '.thinkbrowse', 'daemon.json');
27
+ if (existsSync(current))
28
+ return current;
29
+ if (existsSync(legacy))
30
+ return legacy;
31
+ return current;
32
+ }
33
+ /** Stable path reference for callers that don't change HOME (non-test) */
34
+ // NOTE: const exports are snapshot at import time (production convenience).
35
+ // Tests that override HOME must use the get*() functions above instead.
36
+ export const DISCOVERY_DIR = getDiscoveryDir();
37
+ /** Stable path reference for callers that don't change HOME (non-test) */
38
+ export const DAEMON_JSON = getDaemonJsonPath();
39
+ /**
40
+ * Read daemon.json and return the port+token pair needed to connect.
41
+ *
42
+ * Does NOT check PID liveness — that is intentional. MCP/CLI callers
43
+ * should attempt a connection and retry/recover if it fails. Use
44
+ * readDaemonConfig() in daemon.ts for spawn-path calls where liveness
45
+ * matters for startup decisions.
46
+ *
47
+ * Returns null if daemon.json is absent, unreadable, or malformed.
48
+ */
49
+ export function readDaemonAccess() {
50
+ const daemonJson = getDaemonJsonPath();
51
+ try {
52
+ if (!existsSync(daemonJson))
53
+ return null;
54
+ const text = readFileSync(daemonJson, 'utf-8');
55
+ const parsed = JSON.parse(text);
56
+ if (parsed === null ||
57
+ typeof parsed !== 'object' ||
58
+ typeof parsed.port !== 'number' ||
59
+ typeof parsed.token !== 'string') {
60
+ return null;
61
+ }
62
+ const cfg = parsed;
63
+ return {
64
+ port: cfg.port,
65
+ token: cfg.token,
66
+ protocolVersion: cfg.protocolVersion,
67
+ binaryVersion: cfg.binaryVersion,
68
+ };
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ }
74
+ /**
75
+ * Build the HTTP base URL for the daemon given a port.
76
+ */
77
+ export function daemonBaseUrl(port) {
78
+ return `http://127.0.0.1:${port}`;
79
+ }
80
+ /**
81
+ * Build the WS URL for the extension to connect to the daemon.
82
+ * The token is percent-encoded for safe URL embedding.
83
+ */
84
+ export function daemonWsUrl(port, token, clientType = 'extension') {
85
+ return `ws://127.0.0.1:${port}/ws?type=${clientType}&token=${encodeURIComponent(token)}`;
86
+ }
87
+ //# sourceMappingURL=access.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"access.js","sourceRoot":"","sources":["../../../src/daemon/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,uEAAuE;AACvE,MAAM,UAAU,eAAe;IAC7B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,MAAM,CAAC;IACnE,OAAO,IAAI,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC;AACjC,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,iBAAiB;IAC/B,MAAM,OAAO,GAAG,IAAI,CAAC,eAAe,EAAE,EAAE,aAAa,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,MAAM,CAAC;IACnE,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,aAAa,CAAC,CAAC;IACzD,IAAI,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IACxC,IAAI,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACtC,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,0EAA0E;AAC1E,4EAA4E;AAC5E,wEAAwE;AACxE,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,EAAE,CAAC;AAC/C,0EAA0E;AAC1E,MAAM,CAAC,MAAM,WAAW,GAAG,iBAAiB,EAAE,CAAC;AAa/C;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,UAAU,GAAG,iBAAiB,EAAE,CAAC;IACvC,IAAI,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;YAAE,OAAO,IAAI,CAAC;QACzC,MAAM,IAAI,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAY,CAAC;QAC3C,IACE,MAAM,KAAK,IAAI;YACf,OAAO,MAAM,KAAK,QAAQ;YAC1B,OAAQ,MAAkC,CAAC,IAAI,KAAK,QAAQ;YAC5D,OAAQ,MAAkC,CAAC,KAAK,KAAK,QAAQ,EAC7D,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,GAAG,GAAG,MAAsB,CAAC;QACnC,OAAO;YACL,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,eAAe,EAAE,GAAG,CAAC,eAAe;YACpC,aAAa,EAAE,GAAG,CAAC,aAAa;SACjC,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACxC,OAAO,oBAAoB,IAAI,EAAE,CAAC;AACpC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,KAAa,EAAE,aAAkC,WAAW;IACpG,OAAO,kBAAkB,IAAI,YAAY,UAAU,UAAU,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC;AAC3F,CAAC"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Bridge command envelope and delivery outcome enum (PRD 0086, Task 6.2).
3
+ *
4
+ * This module is shared between the daemon (Bun runtime) and the extension.
5
+ * It MUST NOT import Bun-specific APIs.
6
+ *
7
+ * Key invariant: replay decisions key on DeliveryOutcome values, NEVER on
8
+ * exception message strings.
9
+ */
10
+ /**
11
+ * Module-private brand so a `BridgeCommandEnvelope` can ONLY be produced by
12
+ * `makeBridgeEnvelope()` — callers can't write an object literal (they can't
13
+ * supply the brand) and the fields are readonly, so `idempotency` can never
14
+ * diverge from `commandType` (PR #683 review).
15
+ */
16
+ declare const ENVELOPE_BRAND: unique symbol;
17
+ /** Caller-supplied envelope inputs — note: NO `idempotency` (it is derived). */
18
+ export interface BridgeEnvelopeInput {
19
+ /** Stable ID for exactly-once delivery — shared dedupe key between daemon and CLI paths */
20
+ commandId: string;
21
+ /** Native host session / tab identifier */
22
+ sessionId: string;
23
+ /** The command type string (matches LOCAL_COMMAND_REPLAY_POLICY keys) */
24
+ commandType: string;
25
+ /** 1-based delivery attempt counter */
26
+ attempt: number;
27
+ }
28
+ export interface BridgeCommandEnvelope extends Readonly<BridgeEnvelopeInput> {
29
+ /** DERIVED from the policy table by makeBridgeEnvelope — never caller-set. */
30
+ readonly idempotency: 'idempotent' | 'non-idempotent';
31
+ /** Brand: present only on factory output; prevents object-literal forgery. */
32
+ readonly [ENVELOPE_BRAND]: true;
33
+ }
34
+ /**
35
+ * The only way to build a BridgeCommandEnvelope. `idempotency` is DERIVED from
36
+ * the policy table (a command is idempotent iff its replay policy is `queue`),
37
+ * so it cannot disagree with `commandType`.
38
+ */
39
+ export declare function makeBridgeEnvelope(args: BridgeEnvelopeInput): BridgeCommandEnvelope;
40
+ /**
41
+ * Delivery outcome for a bridge command.
42
+ * All replay/retry logic must branch on these values — not on error strings.
43
+ */
44
+ export declare enum DeliveryOutcome {
45
+ /** Command dispatched and executed successfully */
46
+ DELIVERED_SUCCESS = "DELIVERED_SUCCESS",
47
+ /** Command was dispatched but the page returned an error (e.g. ELEMENT_NOT_FOUND, SCRIPT_ERROR) */
48
+ DELIVERED_PAGE_ERROR = "DELIVERED_PAGE_ERROR",
49
+ /** Command was never delivered — transport layer failure (socket disconnected, timeout before send) */
50
+ NOT_DELIVERED_TRANSPORT_ERROR = "NOT_DELIVERED_TRANSPORT_ERROR",
51
+ /** Outcome is ambiguous — command may or may not have been executed (timeout after send) */
52
+ UNKNOWN_DELIVERY = "UNKNOWN_DELIVERY",
53
+ /** Command rejected because the transport is currently reconnecting */
54
+ REJECTED_RECONNECTING = "REJECTED_RECONNECTING"
55
+ }
56
+ export type CommandReplayPolicy = 'queue' | 'failFast' | 'neverReplay';
57
+ export interface CommandPolicy {
58
+ /** How to handle replay when transport recovers */
59
+ replay: CommandReplayPolicy;
60
+ /** Timeout in milliseconds for this command */
61
+ timeoutMs: number;
62
+ /** Human-readable hint for retry decisions */
63
+ retryHint: string;
64
+ }
65
+ /**
66
+ * Per-command policy table.
67
+ *
68
+ * Design: policy as data — a plain const map, not a class.
69
+ * Default-deny: anything not listed falls back to neverReplay (fail safe).
70
+ *
71
+ * Idempotent reads → 'queue': safe to replay after reconnect.
72
+ * Mutations → 'failFast': outcome unknown after disconnect; caller must decide.
73
+ * evaluate → 'failFast': opaque script may mutate page state; NOT idempotent.
74
+ */
75
+ export declare const BRIDGE_COMMAND_POLICY: Record<string, CommandPolicy>;
76
+ /**
77
+ * The default policy applied when a commandType is not found in BRIDGE_COMMAND_POLICY.
78
+ * Conservative fail-safe: never replay unknown commands.
79
+ */
80
+ /** Single source for the fallback timeout so default-policy and any future use can't diverge. */
81
+ export declare const DEFAULT_COMMAND_TIMEOUT_MS = 30000;
82
+ /**
83
+ * Returns the replay/timeout policy for the given commandType.
84
+ * Falls back to the default-deny policy for unlisted commands.
85
+ */
86
+ export declare function getCommandPolicy(commandType: string): CommandPolicy;
87
+ /**
88
+ * Map an error code and send-state to the appropriate DeliveryOutcome.
89
+ *
90
+ * @param errorCode The `.code` property from the thrown Error.
91
+ * @param sentBeforeDisconnect Whether the WS send() had already been called before
92
+ * the disconnect was detected.
93
+ */
94
+ export declare function mapFailureToOutcome(errorCode: string, sentBeforeDisconnect: boolean): DeliveryOutcome;
95
+ export {};
96
+ //# sourceMappingURL=bridge-envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-envelope.d.ts","sourceRoot":"","sources":["../../../src/daemon/bridge-envelope.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;GAKG;AACH,OAAO,CAAC,MAAM,cAAc,EAAE,OAAO,MAAM,CAAC;AAE5C,gFAAgF;AAChF,MAAM,WAAW,mBAAmB;IAClC,2FAA2F;IAC3F,SAAS,EAAE,MAAM,CAAC;IAClB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,WAAW,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAsB,SAAQ,QAAQ,CAAC,mBAAmB,CAAC;IAC1E,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,YAAY,GAAG,gBAAgB,CAAC;IACtD,8EAA8E;IAC9E,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC;CACjC;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,mBAAmB,GAAG,qBAAqB,CAQnF;AAED;;;GAGG;AACH,oBAAY,eAAe;IACzB,mDAAmD;IACnD,iBAAiB,sBAAsB;IACvC,mGAAmG;IACnG,oBAAoB,yBAAyB;IAC7C,uGAAuG;IACvG,6BAA6B,kCAAkC;IAC/D,4FAA4F;IAC5F,gBAAgB,qBAAqB;IACrC,uEAAuE;IACvE,qBAAqB,0BAA0B;CAChD;AAMD,MAAM,MAAM,mBAAmB,GAAG,OAAO,GAAG,UAAU,GAAG,aAAa,CAAC;AAEvE,MAAM,WAAW,aAAa;IAC5B,mDAAmD;IACnD,MAAM,EAAE,mBAAmB,CAAC;IAC5B,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,8CAA8C;IAC9C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,qBAAqB,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAmI/D,CAAC;AAEF;;;GAGG;AACH,iGAAiG;AACjG,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAQjD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG,aAAa,CAEnE;AAMD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,MAAM,EACjB,oBAAoB,EAAE,OAAO,GAC5B,eAAe,CA8BjB"}