bravenlab 0.1.0 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -76,7 +76,6 @@ var fail = (message, exitCode = EXIT_FAILURE) => Effect.fail(new CliFailure({ me
76
76
  var oneLine = (value3) => Str2.trim(Str2.replace(/\s+/g, " ")(String(value3))).slice(0, 300);
77
77
 
78
78
  // ../identity-store/src/identity.ts
79
- import { Entry } from "@napi-rs/keyring";
80
79
  import { Context as Context2, Effect as Effect2, Layer as Layer2, Option as Option2, pipe } from "effect";
81
80
  import * as Arr from "effect/Array";
82
81
  import * as R from "effect/Record";
@@ -86,7 +85,6 @@ import * as Predicate from "effect/Predicate";
86
85
  import * as Schema2 from "effect/Schema";
87
86
  import * as Str3 from "effect/String";
88
87
  var CONFIG_VERSION = 1;
89
- var KEYCHAIN_SERVICE = "braven";
90
88
  var CredentialKind = Schema2.Literals(["session", "apiKey"]);
91
89
  var Account = class extends Schema2.Class("Account")({
92
90
  origin: Schema2.String,
@@ -123,29 +121,56 @@ var ConfigFile = class extends Schema2.Class("ConfigFile")({
123
121
  var ConfigJson = Schema2.fromJsonString(ConfigFile, { space: 2 });
124
122
  var StoreConfig = class extends Context2.Service()("braven/StoreConfig") {
125
123
  };
126
- var Keychain = class extends Context2.Service()(
127
- "braven/Keychain"
124
+ var CREDENTIALS_FILE = "credentials.json";
125
+ var Secrets = class extends Context2.Service()(
126
+ "braven/Secrets"
128
127
  ) {
129
128
  };
130
- var entry = (account) => new Entry(KEYCHAIN_SERVICE, account);
131
- var keychainAction = (action, run2) => Effect2.try({
132
- try: run2,
133
- catch: (cause) => new CliFailure({
134
- message: `keychain ${action} failed: ${oneLine(cause)}`,
135
- exitCode: EXIT_FAILURE
136
- })
137
- });
138
- var keychainLayer = Layer2.succeed(Keychain)({
139
- get: (account) => keychainAction("read", () => entry(account).getPassword()).pipe(
140
- Effect2.map(Option2.fromNullishOr)
141
- ),
142
- set: (account, secret) => keychainAction("write", () => {
143
- entry(account).setPassword(secret);
144
- }),
145
- remove: (account) => keychainAction("delete", () => {
146
- entry(account).deletePassword();
129
+ var SecretsJson = Schema2.fromJsonString(
130
+ Schema2.Record(Schema2.String, Schema2.String),
131
+ { space: 2 }
132
+ );
133
+ var credentialsPath = (configPath3) => Str3.replace(/config\.json$/, CREDENTIALS_FILE)(configPath3);
134
+ var secretsLayer = Layer2.effect(Secrets)(
135
+ Effect2.gen(function* () {
136
+ const { path } = yield* StoreConfig;
137
+ const fs = yield* FileSystem.FileSystem;
138
+ const pathService = yield* Path.Path;
139
+ const file = credentialsPath(path);
140
+ const read2 = () => Effect2.gen(function* () {
141
+ if (!(yield* fs.exists(file).pipe(Effect2.mapError(storageFailure("read", file))))) {
142
+ return {};
143
+ }
144
+ const text = yield* fs.readFileString(file).pipe(Effect2.mapError(storageFailure("read", file)));
145
+ return yield* Schema2.decodeUnknownEffect(SecretsJson)(text).pipe(
146
+ Effect2.mapError(
147
+ (cause) => new CliFailure({
148
+ message: `${file} is not a credentials file: ${oneLine(cause.message)}`,
149
+ exitCode: EXIT_FAILURE
150
+ })
151
+ )
152
+ );
153
+ });
154
+ const write2 = (secrets) => Effect2.gen(function* () {
155
+ const text = yield* Schema2.encodeUnknownEffect(SecretsJson)(
156
+ secrets
157
+ ).pipe(Effect2.mapError(storageFailure("encode", file)));
158
+ yield* fs.makeDirectory(pathService.dirname(file), { recursive: true }).pipe(Effect2.mapError(storageFailure("create", file)));
159
+ const temporary = `${file}.tmp`;
160
+ yield* fs.writeFileString(temporary, text, { mode: 384 }).pipe(Effect2.mapError(storageFailure("write", file)));
161
+ yield* fs.chmod(temporary, 384).pipe(Effect2.mapError(storageFailure("write", file)));
162
+ yield* fs.rename(temporary, file).pipe(Effect2.mapError(storageFailure("write", file)));
163
+ });
164
+ const update = (change) => Effect2.gen(function* () {
165
+ yield* write2(change(yield* read2()));
166
+ });
167
+ return Secrets.of({
168
+ get: (account) => read2().pipe(Effect2.map((all4) => Option2.fromNullishOr(all4[account]))),
169
+ set: (account, secret) => update((all4) => R.set(all4, account, secret)),
170
+ remove: (account) => update((all4) => R.remove(all4, account))
171
+ });
147
172
  })
148
- });
173
+ );
149
174
  var accountKey = (origin, email) => `${normalizeOrigin(origin)}|${email}`;
150
175
  var normalizeOrigin = (origin) => Str3.replace(/\/+$/, "")(origin);
151
176
  var storageFailure = (action, path) => (cause) => new CliFailure({
@@ -212,8 +237,8 @@ var storeCredential = (credential2, label = Option2.none()) => Effect2.gen(funct
212
237
  }
213
238
  const origin = normalizeOrigin(credential2.origin);
214
239
  const key2 = accountKey(origin, credential2.email);
215
- const keychain = yield* Keychain;
216
- yield* keychain.set(key2, credential2.secret);
240
+ const secrets = yield* Secrets;
241
+ yield* secrets.set(key2, credential2.secret);
217
242
  const config = yield* loadConfig();
218
243
  const account = new Account({
219
244
  origin,
@@ -260,8 +285,8 @@ var removeCredential = (email) => Effect2.gen(function* () {
260
285
  if (Option2.isNone(account)) return Option2.none();
261
286
  const target = account.value;
262
287
  const key2 = accountKey(target.origin, target.email);
263
- const keychain = yield* Keychain;
264
- yield* keychain.remove(key2);
288
+ const secrets = yield* Secrets;
289
+ yield* secrets.remove(key2);
265
290
  const accounts = config.accounts.filter(
266
291
  (stored) => accountKey(stored.origin, stored.email) !== key2
267
292
  );
@@ -308,8 +333,8 @@ var storedCredential = (options) => Effect2.gen(function* () {
308
333
  const account = Option2.isSome(options.origin) || Option2.isSome(options.email) ? findAccount(config, options.origin, options.email) : currentAccount(config);
309
334
  if (Option2.isNone(account)) return Option2.none();
310
335
  const stored = account.value;
311
- const keychain = yield* Keychain;
312
- const secret = yield* keychain.get(accountKey(stored.origin, stored.email));
336
+ const secrets = yield* Secrets;
337
+ const secret = yield* secrets.get(accountKey(stored.origin, stored.email));
313
338
  if (Option2.isNone(secret)) return Option2.none();
314
339
  return Option2.some({
315
340
  origin: stored.origin,
@@ -796,18 +821,18 @@ var candidateDirs = (dir) => {
796
821
  ];
797
822
  };
798
823
  var resolveFolderSettings = (settings, folder, path) => {
799
- const entry2 = settings[folder];
800
- if (entry2 === void 0) {
824
+ const entry = settings[folder];
825
+ if (entry === void 0) {
801
826
  return { projectId: Option3.none(), experimentId: Option3.none() };
802
827
  }
803
828
  const relativePath = relative(folder, path).split(sep).join("/");
804
829
  const dir = dirname(relativePath);
805
830
  const match18 = candidateDirs(dir).find(
806
- (candidate) => entry2.experiments[candidate] !== void 0
831
+ (candidate) => entry.experiments[candidate] !== void 0
807
832
  );
808
833
  return {
809
- projectId: entry2.projectId,
810
- experimentId: match18 === void 0 ? Option3.none() : Option3.some(entry2.experiments[match18] ?? "")
834
+ projectId: entry.projectId,
835
+ experimentId: match18 === void 0 ? Option3.none() : Option3.some(entry.experiments[match18] ?? "")
811
836
  };
812
837
  };
813
838
 
@@ -1468,11 +1493,12 @@ import * as Path5 from "effect/Path";
1468
1493
  // ../../apps/agent/src/sqlite.ts
1469
1494
  var openDatabase = async (path) => {
1470
1495
  if (process.versions.bun !== void 0) {
1471
- const bunSqlite = "bun:sqlite";
1496
+ const bunSqlite = ["bun", "sqlite"].join(":");
1472
1497
  const loaded = await import(bunSqlite);
1473
1498
  return new loaded.Database(path);
1474
1499
  }
1475
- const { DatabaseSync } = await import("node:sqlite");
1500
+ const nodeSqlite = ["node", "sqlite"].join(":");
1501
+ const { DatabaseSync } = await import(nodeSqlite);
1476
1502
  return new DatabaseSync(path);
1477
1503
  };
1478
1504
 
@@ -1634,7 +1660,7 @@ var watcherKey = (origin) => `watcher_id:${origin}`;
1634
1660
  var INSTANCE_ID_KEY = "instance_id";
1635
1661
  var failed = (action) => (cause) => new AgentFailure({ message: `${action}: ${oneLine2(cause)}` });
1636
1662
  var make3 = (db, crypto2) => {
1637
- const attemptSql = (action, run2) => Effect11.try({ try: run2, catch: failed(action) });
1663
+ const attemptSql = (action, run3) => Effect11.try({ try: run3, catch: failed(action) });
1638
1664
  const query = (action, decoder, sql, ...params) => attemptSql(action, () => db.prepare(sql).all(...params)).pipe(
1639
1665
  Effect11.flatMap(
1640
1666
  (rows) => decoder(rows).pipe(
@@ -1959,7 +1985,7 @@ var walk = (root, extra = []) => Stream3.unwrap(
1959
1985
  const paths = yield* Path7.Path;
1960
1986
  const entries = yield* fs.readDirectory(root).pipe(Effect12.orElseSucceed(() => []));
1961
1987
  return Stream3.fromIterable(entries).pipe(
1962
- Stream3.map((entry2) => paths.join(root, entry2)),
1988
+ Stream3.map((entry) => paths.join(root, entry)),
1963
1989
  Stream3.filter((path) => !isIgnored(path, extra)),
1964
1990
  Stream3.flatMap(
1965
1991
  (path) => fs.stat(path).pipe(
@@ -2173,7 +2199,7 @@ var make6 = (config) => Effect14.gen(function* () {
2173
2199
  { concurrency: config.concurrency }
2174
2200
  );
2175
2201
  const missing = Arr5.map(
2176
- Arr5.filter(checked, (entry2) => entry2.state === "missing"),
2202
+ Arr5.filter(checked, (entry) => entry.state === "missing"),
2177
2203
  ({ item }) => item
2178
2204
  );
2179
2205
  yield* Effect14.forEach(
@@ -2184,21 +2210,21 @@ var make6 = (config) => Effect14.gen(function* () {
2184
2210
  const superseded = Arr5.getSomes(
2185
2211
  Arr5.map(
2186
2212
  checked,
2187
- (entry2) => entry2.state === "superseded" ? Option9.some(entry2) : Option9.none()
2213
+ (entry) => entry.state === "superseded" ? Option9.some(entry) : Option9.none()
2188
2214
  )
2189
2215
  );
2190
2216
  yield* Effect14.forEach(
2191
2217
  superseded,
2192
- (entry2) => Effect14.gen(function* () {
2218
+ (entry) => Effect14.gen(function* () {
2193
2219
  yield* queue.fail(
2194
- entry2.item.id,
2220
+ entry.item.id,
2195
2221
  "superseded by the file's current contents"
2196
2222
  );
2197
2223
  yield* queue.enqueue({
2198
- folder: entry2.item.folder,
2199
- path: entry2.item.path,
2200
- hash: entry2.hashed.hash,
2201
- size: entry2.hashed.size
2224
+ folder: entry.item.folder,
2225
+ path: entry.item.path,
2226
+ hash: entry.hashed.hash,
2227
+ size: entry.hashed.size
2202
2228
  });
2203
2229
  }),
2204
2230
  { concurrency: 1 }
@@ -2602,7 +2628,6 @@ var layer4 = (config) => Layer7.effect(Engine, make6(config));
2602
2628
  import { Effect as Effect15, Match as Match5, Option as Option10, Redacted as Redacted4 } from "effect";
2603
2629
  import "effect/FileSystem";
2604
2630
  import * as Str8 from "effect/String";
2605
- var KEYCHAIN_SERVICE2 = "braven";
2606
2631
  var environmentValue2 = (environment, name) => Option10.fromNullishOr(environment.values[name]).pipe(
2607
2632
  Option10.filter(Str8.isNonEmpty)
2608
2633
  );
@@ -2691,18 +2716,18 @@ var resolveCredential2 = (options) => Effect15.gen(function* () {
2691
2716
  );
2692
2717
  }
2693
2718
  const stored = account.value;
2694
- const keychain = yield* Keychain;
2695
- const secret = yield* keychain.get(accountKey(stored.origin, stored.email)).pipe(
2719
+ const secrets = yield* Secrets;
2720
+ const secret = yield* secrets.get(accountKey(stored.origin, stored.email)).pipe(
2696
2721
  Effect15.mapError(
2697
2722
  (cause) => new AgentFailure({
2698
- message: `cannot read the ${KEYCHAIN_SERVICE2} keychain: ${oneLine2(cause)}`
2723
+ message: `cannot read the credentials file: ${oneLine2(cause)}`
2699
2724
  })
2700
2725
  )
2701
2726
  );
2702
2727
  if (Option10.isNone(secret)) {
2703
2728
  return yield* Effect15.fail(
2704
2729
  new AgentFailure({
2705
- message: `the keychain has no secret for ${stored.email} on ${origin} \u2014 run \`braven login --origin ${origin}\``
2730
+ message: `the secrets has no secret for ${stored.email} on ${origin} \u2014 run \`braven login --origin ${origin}\``
2706
2731
  })
2707
2732
  );
2708
2733
  }
@@ -4365,7 +4390,7 @@ var make9 = (options, queue, client, gate, heartbeating, folders) => {
4365
4390
  Effect19.forever
4366
4391
  );
4367
4392
  };
4368
- const resolve2 = Effect19.gen(function* () {
4393
+ const resolve3 = Effect19.gen(function* () {
4369
4394
  if (Option13.isSome(options.instanceId)) {
4370
4395
  const provided = options.instanceId.value;
4371
4396
  yield* Effect19.logInfo("using the provided watcher instance id").pipe(
@@ -4425,7 +4450,7 @@ var make9 = (options, queue, client, gate, heartbeating, folders) => {
4425
4450
  return Option13.some(watcherId);
4426
4451
  });
4427
4452
  const start2 = Effect19.gen(function* () {
4428
- const resolved = yield* resolve2;
4453
+ const resolved = yield* resolve3;
4429
4454
  if (Option13.isNone(resolved)) return Option13.none();
4430
4455
  const watcherId = resolved.value;
4431
4456
  yield* syncFolders(watcherId).pipe(
@@ -4789,11 +4814,11 @@ import { createServer as createServer2 } from "node:http";
4789
4814
  import { randomBytes, timingSafeEqual } from "node:crypto";
4790
4815
  import { basename, relative as relative2 } from "node:path";
4791
4816
  var folderSettingsJson = (settings) => Object.fromEntries(
4792
- Object.entries(settings).map(([path, entry2]) => [
4817
+ Object.entries(settings).map(([path, entry]) => [
4793
4818
  path,
4794
4819
  {
4795
- ...Option16.isSome(entry2.projectId) ? { projectId: entry2.projectId.value } : {},
4796
- experiments: entry2.experiments
4820
+ ...Option16.isSome(entry.projectId) ? { projectId: entry.projectId.value } : {},
4821
+ experiments: entry.experiments
4797
4822
  }
4798
4823
  ])
4799
4824
  );
@@ -5477,7 +5502,7 @@ var make13 = Effect25.gen(function* () {
5477
5502
  });
5478
5503
 
5479
5504
  // ../../apps/agent/src/version.ts
5480
- var VERSION = "0.1.0";
5505
+ var VERSION = "0.1.3";
5481
5506
 
5482
5507
  // ../../apps/agent/src/run.ts
5483
5508
  var USAGE = `braven agent --folder <path> [--folder <path>] --project-id <id> [options]
@@ -5523,10 +5548,13 @@ var runAgent = (argv2) => Effect26.scoped(
5523
5548
  yield* Effect26.logInfo(USAGE);
5524
5549
  return;
5525
5550
  }
5526
- const provided = Layer13.mergeAll(
5551
+ const store2 = Layer13.mergeAll(
5527
5552
  NodeServices.layer,
5528
- Layer13.succeed(StoreConfig)({ path: configPath2(storeEnvironment) }),
5529
- keychainLayer
5553
+ Layer13.succeed(StoreConfig)({ path: configPath2(storeEnvironment) })
5554
+ );
5555
+ const provided = Layer13.mergeAll(
5556
+ store2,
5557
+ secretsLayer.pipe(Layer13.provide(store2))
5530
5558
  );
5531
5559
  const settingsPath = settingsFile(bravenDirectory(storeEnvironment));
5532
5560
  const stored = Option18.getOrNull(
@@ -5569,11 +5597,11 @@ var runAgent = (argv2) => Effect26.scoped(
5569
5597
  const config = new AgentConfig({
5570
5598
  folders: watched,
5571
5599
  folderSettings: stored === null ? {} : Object.fromEntries(
5572
- Object.entries(stored.folderSettings).map(([path, entry2]) => [
5600
+ Object.entries(stored.folderSettings).map(([path, entry]) => [
5573
5601
  path,
5574
5602
  {
5575
- projectId: entry2.projectId,
5576
- experiments: entry2.experiments
5603
+ projectId: entry.projectId,
5604
+ experiments: entry.experiments
5577
5605
  }
5578
5606
  ])
5579
5607
  ),
@@ -5750,11 +5778,11 @@ var runAgent = (argv2) => Effect26.scoped(
5750
5778
  // The settings file stores the encoded form: a bare string
5751
5779
  // for a project, and no key for "none".
5752
5780
  folderSettings: Object.fromEntries(
5753
- Object.entries(overrides).map(([path, entry2]) => [
5781
+ Object.entries(overrides).map(([path, entry]) => [
5754
5782
  path,
5755
5783
  new FolderSettings({
5756
- projectId: entry2.projectId,
5757
- experiments: entry2.experiments
5784
+ projectId: entry.projectId,
5785
+ experiments: entry.experiments
5758
5786
  })
5759
5787
  ])
5760
5788
  ),
@@ -5821,9 +5849,9 @@ var runAgent = (argv2) => Effect26.scoped(
5821
5849
  );
5822
5850
 
5823
5851
  // src/bin.ts
5824
- import { Effect as Effect59, Layer as Layer21 } from "effect";
5852
+ import { Effect as Effect60, Layer as Layer21 } from "effect";
5825
5853
  import { FetchHttpClient as FetchHttpClient3 } from "effect/http";
5826
- import { homedir as homedir2 } from "node:os";
5854
+ import { homedir as homedir3 } from "node:os";
5827
5855
 
5828
5856
  // src/auth.ts
5829
5857
  import {
@@ -6270,7 +6298,7 @@ and confirm the code ${code.user_code} in the browser.`
6270
6298
 
6271
6299
  // src/cli.ts
6272
6300
  import { Argument as Argument4, Command as Command4, Flag as Flag4 } from "effect/cli";
6273
- import { Console as Console8, Effect as Effect58, Match as Match15, Option as Option50 } from "effect";
6301
+ import { Console as Console9, Effect as Effect59, Match as Match15, Option as Option50 } from "effect";
6274
6302
 
6275
6303
  // ../agent-tools/src/catalog.ts
6276
6304
  import { Tool as Tool2 } from "effect/ai";
@@ -6876,7 +6904,7 @@ var catalog = [...tools, ...webTools].map(
6876
6904
  toEntry
6877
6905
  );
6878
6906
  var remoteTools = catalog.filter(
6879
- (entry2) => entry2.surfaces.includes("mcp") && !entry2.local
6907
+ (entry) => entry.surfaces.includes("mcp") && !entry.local
6880
6908
  );
6881
6909
 
6882
6910
  // ../agent-tools/src/server.ts
@@ -7070,7 +7098,7 @@ var layer9 = Layer16.effect(Scopes)(
7070
7098
  return Scopes.of({ scopes });
7071
7099
  })
7072
7100
  );
7073
- var permit = (access, tool, run2) => Arr10.some(scopesOf(tool), (scope) => Arr10.contains(access.scopes, scope)) ? run2 : Effect31.fail(
7101
+ var permit = (access, tool, run3) => Arr10.some(scopesOf(tool), (scope) => Arr10.contains(access.scopes, scope)) ? run3 : Effect31.fail(
7074
7102
  new AgentToolError({
7075
7103
  message: `${tool.name} needs the ${scopesOf(tool).join("/")} scope; this server allows ${access.scopes.join(", ")}. Nothing was changed. Set BRAVEN_MCP_SCOPES to include it, or run it from the CLI or dashboard.`,
7076
7104
  code: "forbidden"
@@ -7438,7 +7466,7 @@ var canonicalValue = (value3) => {
7438
7466
  Object.entries(value3),
7439
7467
  Order2.mapInput(
7440
7468
  Order2.String,
7441
- (entry2) => entry2[0]
7469
+ (entry) => entry[0]
7442
7470
  )
7443
7471
  ),
7444
7472
  ([key2, field2]) => {
@@ -8334,7 +8362,7 @@ var stableText = (value3) => Match9.value(value3).pipe(
8334
8362
  Object.entries(record2),
8335
8363
  Order3.mapInput(
8336
8364
  Order3.String,
8337
- (entry2) => entry2[0]
8365
+ (entry) => entry[0]
8338
8366
  )
8339
8367
  ),
8340
8368
  ([key2, field2]) => `${key2.length}:${key2}=${stableText(field2)}`
@@ -9070,7 +9098,7 @@ var mergeRuntime = (state, base, local, remote) => {
9070
9098
  scalarKey,
9071
9099
  scalarJson
9072
9100
  );
9073
- const entry2 = field(
9101
+ const entry = field(
9074
9102
  state,
9075
9103
  "/runtime/entry",
9076
9104
  base.entry,
@@ -9097,7 +9125,7 @@ var mergeRuntime = (state, base, local, remote) => {
9097
9125
  (value3) => stableText(Schema35.encodeSync(Schema35.Array(Schema35.String))(value3)),
9098
9126
  (value3) => asJson(Schema35.encodeSync(Schema35.Array(Schema35.String))(value3))
9099
9127
  );
9100
- return Option26.all({ python, entry: entry2, requirements, imports }).pipe(
9128
+ return Option26.all({ python, entry, requirements, imports }).pipe(
9101
9129
  Option26.map(
9102
9130
  (fields) => new IrRuntime({
9103
9131
  python: fields.python,
@@ -11179,7 +11207,7 @@ var make15 = (options) => {
11179
11207
  const valuesOf = (entries, category) => R6.fromEntries(
11180
11208
  Arr21.flatMap(
11181
11209
  entries,
11182
- (entry2) => entry2.category === category && entry2.value !== null ? [[entry2.key, entry2.value]] : []
11210
+ (entry) => entry.category === category && entry.value !== null ? [[entry.key, entry.value]] : []
11183
11211
  )
11184
11212
  );
11185
11213
  return {
@@ -12609,16 +12637,16 @@ var login = Effect53.fn("login")(function* (options) {
12609
12637
  yield* printSignedIn(account);
12610
12638
  });
12611
12639
  var watcherAccountFor = (accounts, origin) => Option46.fromNullishOr(accounts.find((account) => account.origin === origin));
12612
- var watcherAccount = (store, origin) => Option46.isSome(origin) ? watcherAccountFor(store.accounts, origin.value) : Option46.orElse(
12640
+ var watcherAccount = (store2, origin) => Option46.isSome(origin) ? watcherAccountFor(store2.accounts, origin.value) : Option46.orElse(
12613
12641
  Option46.flatMap(
12614
- store.current,
12642
+ store2.current,
12615
12643
  (current) => Option46.fromNullishOr(
12616
- store.accounts.find(
12644
+ store2.accounts.find(
12617
12645
  (account) => accountKey(account.origin, account.email) === current
12618
12646
  )
12619
12647
  )
12620
12648
  ),
12621
- () => Option46.fromNullishOr(store.accounts[0])
12649
+ () => Option46.fromNullishOr(store2.accounts[0])
12622
12650
  );
12623
12651
  var loginCommand = (options) => Effect53.gen(function* () {
12624
12652
  const environment = yield* Environment;
@@ -13736,20 +13764,40 @@ import * as P6 from "effect/Predicate";
13736
13764
  import * as R7 from "effect/Record";
13737
13765
  import * as Schema49 from "effect/Schema";
13738
13766
  import * as Str19 from "effect/String";
13739
- var CLIENTS = ["claude", "cursor", "zed", "vscode"];
13767
+ var CLIENTS = [
13768
+ "claude",
13769
+ "cursor",
13770
+ "opencode",
13771
+ "zed",
13772
+ "vscode"
13773
+ ];
13740
13774
  var serversKey = {
13741
13775
  claude: "mcpServers",
13742
13776
  cursor: "mcpServers",
13777
+ opencode: "mcp",
13743
13778
  zed: "context_servers",
13744
13779
  vscode: "servers"
13745
13780
  };
13746
13781
  var displayName = {
13747
13782
  claude: "Claude Desktop",
13748
13783
  cursor: "Cursor",
13784
+ opencode: "opencode",
13749
13785
  zed: "Zed",
13750
13786
  vscode: "VS Code"
13751
13787
  };
13752
- var serverEntry = (client) => client === "vscode" ? { type: "stdio", command: "braven", args: ["mcp"] } : { command: "braven", args: ["mcp"] };
13788
+ var serverEntry = (client) => Match14.value(client).pipe(
13789
+ Match14.when("vscode", () => ({
13790
+ type: "stdio",
13791
+ command: "braven",
13792
+ args: ["mcp"]
13793
+ })),
13794
+ Match14.when("opencode", () => ({
13795
+ type: "local",
13796
+ command: ["braven", "mcp"],
13797
+ enabled: true
13798
+ })),
13799
+ Match14.orElse(() => ({ command: "braven", args: ["mcp"] }))
13800
+ );
13753
13801
  var isRecord = (value3) => P6.isObject(value3);
13754
13802
  var asRecord = (value3) => Option49.liftPredicate(isRecord)(value3);
13755
13803
  var value2 = (shape2, name) => Option49.filter(Option49.fromNullishOr(shape2.values[name]), Str19.isNonEmpty);
@@ -13773,6 +13821,10 @@ var applications = (shape2) => Match14.value(shape2.platform).pipe(
13773
13821
  )
13774
13822
  )
13775
13823
  );
13824
+ var configHome = (shape2) => Option49.getOrElse(
13825
+ value2(shape2, "XDG_CONFIG_HOME"),
13826
+ () => join11(shape2, shape2.home, ".config")
13827
+ );
13776
13828
  var configPathFor = (client, shape2) => Match14.value(client).pipe(
13777
13829
  Match14.when(
13778
13830
  "claude",
@@ -13791,14 +13843,21 @@ var configPathFor = (client, shape2) => Match14.value(client).pipe(
13791
13843
  )
13792
13844
  )
13793
13845
  ),
13846
+ Match14.when(
13847
+ "opencode",
13848
+ () => Option49.getOrElse(
13849
+ value2(shape2, "OPENCODE_CONFIG"),
13850
+ () => join11(shape2, configHome(shape2), "opencode", "opencode.json")
13851
+ )
13852
+ ),
13794
13853
  Match14.when("vscode", () => join11(shape2, shape2.cwd, ".vscode", "mcp.json")),
13795
13854
  Match14.exhaustive
13796
13855
  );
13797
13856
  var decodeJson2 = Schema49.decodeUnknownOption(
13798
13857
  Schema49.fromJsonString(Schema49.Unknown)
13799
13858
  );
13800
- var withoutEmptyEnv = (entry2) => Option49.match(asRecord(entry2), {
13801
- onNone: () => entry2,
13859
+ var withoutEmptyEnv = (entry) => Option49.match(asRecord(entry), {
13860
+ onNone: () => entry,
13802
13861
  onSome: (record2) => Option49.match(Option49.flatMap(R7.get(record2, "env"), asRecord), {
13803
13862
  onNone: () => record2,
13804
13863
  onSome: (env) => Match14.value(R7.isEmptyRecord(env)).pipe(
@@ -13814,16 +13873,16 @@ var withServer = (config, key2, servers, client) => ({
13814
13873
  var invalid = (reason) => ({ kind: "invalid", reason });
13815
13874
  var mergeEntry = (client, config, key2, servers, force) => Option49.match(R7.get(servers, "braven"), {
13816
13875
  onNone: () => withServer(config, key2, servers, client),
13817
- onSome: (entry2) => Bool2.match(
13876
+ onSome: (entry) => Bool2.match(
13818
13877
  Equal.equals(
13819
- withoutEmptyEnv(entry2),
13878
+ withoutEmptyEnv(entry),
13820
13879
  withoutEmptyEnv(serverEntry(client))
13821
13880
  ),
13822
13881
  {
13823
13882
  onTrue: () => ({ kind: "unchanged" }),
13824
13883
  onFalse: () => Bool2.match(force, {
13825
13884
  onTrue: () => withServer(config, key2, servers, client),
13826
- onFalse: () => ({ kind: "conflict", current: entry2 })
13885
+ onFalse: () => ({ kind: "conflict", current: entry })
13827
13886
  })
13828
13887
  }
13829
13888
  )
@@ -13918,10 +13977,10 @@ var install = Effect57.fn("mcp.install")(function* (options) {
13918
13977
  { kind: "conflict" },
13919
13978
  ({ current }) => Effect57.flatMap(
13920
13979
  inline(current),
13921
- (entry2) => Console7.log(
13980
+ (entry) => Console7.log(
13922
13981
  `braven is already configured differently:
13923
13982
  file ${options.path}
13924
- current ${entry2}`
13983
+ current ${entry}`
13925
13984
  ).pipe(
13926
13985
  Effect57.andThen(
13927
13986
  fail(
@@ -13986,8 +14045,81 @@ var installCommand = Effect57.fn("mcp.installCommand")(
13986
14045
  }
13987
14046
  );
13988
14047
 
14048
+ // src/desktop.ts
14049
+ import { Console as Console8, Effect as Effect58 } from "effect";
14050
+ import { execFile } from "node:child_process";
14051
+ import { cpSync, existsSync, mkdtempSync, rmSync } from "node:fs";
14052
+ import { homedir as homedir2, tmpdir } from "node:os";
14053
+ import { join as join12, resolve as resolve2 } from "node:path";
14054
+ var destinationOf = (input) => input.destination ?? (input.system === true ? "/Applications/Braven.app" : join12(homedir2(), "Applications", "Braven.app"));
14055
+ var run2 = (bin, args) => Effect58.tryPromise({
14056
+ try: () => new Promise((resolvePromise, reject) => {
14057
+ execFile(
14058
+ bin,
14059
+ [...args],
14060
+ (error, stdout) => error === null ? resolvePromise(stdout) : reject(error)
14061
+ );
14062
+ }),
14063
+ catch: (cause) => new Error(`${bin} failed: ${String(cause)}`)
14064
+ });
14065
+ var copyApp = (source, destination) => Effect58.sync(() => {
14066
+ if (!existsSync(join12(source, "Contents", "Info.plist")))
14067
+ throw new Error(`${source} is not a Braven.app`);
14068
+ rmSync(destination, { recursive: true, force: true });
14069
+ cpSync(source, destination, { recursive: true });
14070
+ });
14071
+ var installDesktop = Effect58.fn("desktop.install")(function* (input) {
14072
+ if (process.platform === "win32") {
14073
+ if (input.exe === void 0)
14074
+ return yield* Effect58.fail(
14075
+ new Error("pass --exe <Braven-Setup.exe> on Windows")
14076
+ );
14077
+ yield* run2(
14078
+ resolve2(input.exe),
14079
+ input.silent === true ? ["/SILENT", "/SUPPRESSMSGBOXES", "/NORESTART"] : []
14080
+ );
14081
+ yield* Console8.log(
14082
+ input.silent === true ? "installer finished" : "the installer will finish in its own window"
14083
+ );
14084
+ return;
14085
+ }
14086
+ if (process.platform !== "darwin")
14087
+ return yield* Effect58.fail(
14088
+ new Error("the watcher window is available on macOS and Windows")
14089
+ );
14090
+ if (input.dmg === void 0 && input.app === void 0)
14091
+ return yield* Effect58.fail(
14092
+ new Error("pass --dmg <file.dmg> or --app <Braven.app>")
14093
+ );
14094
+ const destination = destinationOf(input);
14095
+ if (input.app !== void 0) {
14096
+ yield* copyApp(resolve2(input.app), destination);
14097
+ } else {
14098
+ const mount = mkdtempSync(join12(tmpdir(), "braven-dmg-"));
14099
+ yield* Effect58.ensuring(
14100
+ Effect58.gen(function* () {
14101
+ yield* run2("hdiutil", [
14102
+ "attach",
14103
+ "-nobrowse",
14104
+ "-readonly",
14105
+ "-mountpoint",
14106
+ mount,
14107
+ resolve2(input.dmg)
14108
+ ]);
14109
+ yield* copyApp(join12(mount, "Braven.app"), destination);
14110
+ }),
14111
+ Effect58.sync(() => {
14112
+ void run2("hdiutil", ["detach", mount]);
14113
+ rmSync(mount, { recursive: true, force: true });
14114
+ })
14115
+ );
14116
+ }
14117
+ yield* Console8.log(`installed ${destination}`);
14118
+ yield* run2("open", [destination]);
14119
+ });
14120
+
13989
14121
  // src/cli.ts
13990
- var VERSION2 = "0.0.0";
14122
+ var VERSION2 = true ? "0.1.3" : "0.0.0";
13991
14123
  var originFlag = Flag4.String("origin").pipe(
13992
14124
  Flag4.optional,
13993
14125
  Flag4.withDescription(
@@ -14038,7 +14170,7 @@ var login2 = Command4.make(
14038
14170
  ),
14039
14171
  email: emailFlag
14040
14172
  },
14041
- Effect58.fn(function* ({
14173
+ Effect59.fn(function* ({
14042
14174
  origin,
14043
14175
  webOrigin,
14044
14176
  device,
@@ -14082,7 +14214,7 @@ var keyAddCommand = Command4.make(
14082
14214
  ),
14083
14215
  origin: originFlag
14084
14216
  },
14085
- Effect58.fn(function* ({ key: key2, email, label, origin }) {
14217
+ Effect59.fn(function* ({ key: key2, email, label, origin }) {
14086
14218
  const environment = yield* Environment;
14087
14219
  yield* keyAdd({
14088
14220
  origin: resolveOrigin(origin, environment),
@@ -14125,7 +14257,7 @@ var pushCommand2 = Command4.make(
14125
14257
  )
14126
14258
  )
14127
14259
  },
14128
- Effect58.fn(function* ({ file, override, path, projectId }) {
14260
+ Effect59.fn(function* ({ file, override, path, projectId }) {
14129
14261
  yield* push2({ file, override, path, projectId });
14130
14262
  })
14131
14263
  ).pipe(
@@ -14144,14 +14276,14 @@ var configSetCommand = Command4.make(
14144
14276
  name: Argument4.Literals("name", ["company", "project"]),
14145
14277
  value: Argument4.String("value")
14146
14278
  },
14147
- Effect58.fn(function* ({ name, value: value3 }) {
14279
+ Effect59.fn(function* ({ name, value: value3 }) {
14148
14280
  yield* configSet({ name, value: value3 });
14149
14281
  })
14150
14282
  ).pipe(Command4.withDescription("set a default"));
14151
14283
  var configUseCommand = Command4.make(
14152
14284
  "use",
14153
14285
  { email: Argument4.String("email") },
14154
- Effect58.fn(function* ({ email }) {
14286
+ Effect59.fn(function* ({ email }) {
14155
14287
  yield* configUse(email);
14156
14288
  })
14157
14289
  ).pipe(Command4.withDescription("switch the current account"));
@@ -14168,7 +14300,7 @@ var configCommand = Command4.make("config", {}, () => configShow()).pipe(
14168
14300
  var logoutCommand = Command4.make(
14169
14301
  "logout",
14170
14302
  { email: emailFlag },
14171
- Effect58.fn(function* ({ email }) {
14303
+ Effect59.fn(function* ({ email }) {
14172
14304
  yield* logout(email);
14173
14305
  })
14174
14306
  ).pipe(
@@ -14193,14 +14325,57 @@ var mcpInstallCommand = Command4.make(
14193
14325
  Flag4.withDescription("write this file instead of the client's default")
14194
14326
  )
14195
14327
  },
14196
- Effect58.fn(function* ({ client, force, path }) {
14328
+ Effect59.fn(function* ({ client, force, path }) {
14197
14329
  yield* installCommand({ client, force, path });
14198
14330
  })
14199
14331
  ).pipe(
14200
14332
  Command4.withDescription(
14201
- "merge the Braven server into a client's MCP config (Claude, Cursor, Zed, VS Code)"
14333
+ "merge the Braven server into a client's MCP config (Claude, Cursor, opencode, Zed, VS Code)"
14202
14334
  )
14203
14335
  );
14336
+ var desktopInstallCommand = Command4.make(
14337
+ "install",
14338
+ {
14339
+ dmg: Flag4.String("dmg").pipe(
14340
+ Flag4.optional,
14341
+ Flag4.withDescription("a Braven DMG to install")
14342
+ ),
14343
+ app: Flag4.String("app").pipe(
14344
+ Flag4.optional,
14345
+ Flag4.withDescription("an already-built Braven.app")
14346
+ ),
14347
+ exe: Flag4.String("exe").pipe(
14348
+ Flag4.optional,
14349
+ Flag4.withDescription("a Windows Braven installer (.exe)")
14350
+ ),
14351
+ silent: Flag4.Boolean("silent").pipe(
14352
+ Flag4.withDefault(false),
14353
+ Flag4.withDescription("Windows: run the installer without its wizard")
14354
+ ),
14355
+ system: Flag4.Boolean("system").pipe(
14356
+ Flag4.withDefault(false),
14357
+ Flag4.withDescription("install into /Applications")
14358
+ ),
14359
+ destination: Flag4.String("destination").pipe(
14360
+ Flag4.optional,
14361
+ Flag4.withDescription("install here instead of ~/Applications")
14362
+ )
14363
+ },
14364
+ Effect59.fn(function* ({ dmg, app, exe, silent, system, destination }) {
14365
+ yield* installDesktop({
14366
+ dmg: Option50.getOrUndefined(dmg),
14367
+ app: Option50.getOrUndefined(app),
14368
+ exe: Option50.getOrUndefined(exe),
14369
+ silent,
14370
+ system,
14371
+ destination: Option50.getOrUndefined(destination)
14372
+ });
14373
+ })
14374
+ ).pipe(Command4.withDescription("copy the watcher window into Applications"));
14375
+ var desktopCommand = Command4.make("desktop", {}).pipe(
14376
+ Command4.withDescription("the watcher window (macOS)"),
14377
+ Command4.withSubcommands([desktopInstallCommand])
14378
+ );
14204
14379
  var mcpCommand = Command4.make("mcp", {}, () => runMcp(VERSION2)).pipe(
14205
14380
  Command4.withDescription(
14206
14381
  "run the Braven MCP server over stdio (connect Claude, Cursor, or Zed here)"
@@ -14218,6 +14393,7 @@ var braven = Command4.make("braven").pipe(
14218
14393
  pushCommand2,
14219
14394
  agentCommand,
14220
14395
  mcpCommand,
14396
+ desktopCommand,
14221
14397
  whoamiCommand,
14222
14398
  configCommand,
14223
14399
  logoutCommand
@@ -14263,15 +14439,15 @@ var describe = (error) => Match15.value(error).pipe(
14263
14439
  })),
14264
14440
  Match15.orElse((other) => ({ code: EXIT_USAGE, message: other.message }))
14265
14441
  );
14266
- var main = Effect58.fn("braven")(function* (args) {
14442
+ var main = Effect59.fn("braven")(function* (args) {
14267
14443
  return yield* runCli(args).pipe(
14268
- Effect58.as(0),
14269
- Effect58.catch((error) => {
14444
+ Effect59.as(0),
14445
+ Effect59.catch((error) => {
14270
14446
  const described = describe(error);
14271
- if (described.code === 0) return Effect58.succeed(0);
14272
- if (described.message.length === 0) return Effect58.succeed(described.code);
14273
- return Console8.error(`braven: ${oneLine(described.message)}`).pipe(
14274
- Effect58.as(described.code)
14447
+ if (described.code === 0) return Effect59.succeed(0);
14448
+ if (described.message.length === 0) return Effect59.succeed(described.code);
14449
+ return Console9.error(`braven: ${oneLine(described.message)}`).pipe(
14450
+ Effect59.as(described.code)
14275
14451
  );
14276
14452
  })
14277
14453
  );
@@ -14280,16 +14456,19 @@ var main = Effect58.fn("braven")(function* (args) {
14280
14456
  // src/bin.ts
14281
14457
  var shape = {
14282
14458
  platform: process.platform,
14283
- home: homedir2(),
14459
+ home: homedir3(),
14284
14460
  cwd: process.cwd(),
14285
14461
  values: process.env
14286
14462
  };
14287
- var AppLayer = Layer21.mergeAll(
14463
+ var store = Layer21.mergeAll(
14288
14464
  NodeServices2.layer,
14289
- FetchHttpClient3.layer,
14290
14465
  environmentLayer(shape),
14291
- Layer21.succeed(StoreConfig)({ path: configPath(shape) }),
14292
- keychainLayer,
14466
+ Layer21.succeed(StoreConfig)({ path: configPath(shape) })
14467
+ );
14468
+ var AppLayer = Layer21.mergeAll(
14469
+ store,
14470
+ FetchHttpClient3.layer,
14471
+ secretsLayer.pipe(Layer21.provide(store)),
14293
14472
  browserLayer,
14294
14473
  sessionsLayer.pipe(Layer21.provide(FetchHttpClient3.layer)),
14295
14474
  layer8.pipe(Layer21.provide(NodeServices2.layer))
@@ -14300,13 +14479,13 @@ if (argv[0] === "agent") {
14300
14479
  } else {
14301
14480
  NodeRuntime.runMain(
14302
14481
  main(argv).pipe(
14303
- Effect59.tap(
14304
- (code) => Effect59.sync(() => {
14482
+ Effect60.tap(
14483
+ (code) => Effect60.sync(() => {
14305
14484
  process.exitCode = code;
14306
14485
  })
14307
14486
  ),
14308
- Effect59.asVoid,
14309
- Effect59.provide(AppLayer)
14487
+ Effect60.asVoid,
14488
+ Effect60.provide(AppLayer)
14310
14489
  )
14311
14490
  );
14312
14491
  }