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/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/cli.ts
2
2
  import { Argument as Argument4, Command as Command4, Flag as Flag4 } from "effect/cli";
3
- import { Console as Console8, Effect as Effect34, Match as Match11, Option as Option34 } from "effect";
3
+ import { Console as Console9, Effect as Effect35, Match as Match11, Option as Option34 } from "effect";
4
4
 
5
5
  // ../agent-tools/src/catalog.ts
6
6
  import { Tool as Tool2 } from "effect/ai";
@@ -606,7 +606,7 @@ var catalog = [...tools, ...webTools].map(
606
606
  toEntry
607
607
  );
608
608
  var remoteTools = catalog.filter(
609
- (entry2) => entry2.surfaces.includes("mcp") && !entry2.local
609
+ (entry) => entry.surfaces.includes("mcp") && !entry.local
610
610
  );
611
611
 
612
612
  // ../agent-tools/src/server.ts
@@ -858,7 +858,7 @@ var layer2 = Layer3.effect(Scopes)(
858
858
  return Scopes.of({ scopes });
859
859
  })
860
860
  );
861
- var permit = (access, tool, run) => Arr.some(scopesOf(tool), (scope) => Arr.contains(access.scopes, scope)) ? run : Effect3.fail(
861
+ var permit = (access, tool, run2) => Arr.some(scopesOf(tool), (scope) => Arr.contains(access.scopes, scope)) ? run2 : Effect3.fail(
862
862
  new AgentToolError({
863
863
  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.`,
864
864
  code: "forbidden"
@@ -1226,7 +1226,7 @@ var canonicalValue = (value3) => {
1226
1226
  Object.entries(value3),
1227
1227
  Order.mapInput(
1228
1228
  Order.String,
1229
- (entry2) => entry2[0]
1229
+ (entry) => entry[0]
1230
1230
  )
1231
1231
  ),
1232
1232
  ([key2, field2]) => {
@@ -2122,7 +2122,7 @@ var stableText = (value3) => Match4.value(value3).pipe(
2122
2122
  Object.entries(record2),
2123
2123
  Order2.mapInput(
2124
2124
  Order2.String,
2125
- (entry2) => entry2[0]
2125
+ (entry) => entry[0]
2126
2126
  )
2127
2127
  ),
2128
2128
  ([key2, field2]) => `${key2.length}:${key2}=${stableText(field2)}`
@@ -2858,7 +2858,7 @@ var mergeRuntime = (state, base, local, remote) => {
2858
2858
  scalarKey,
2859
2859
  scalarJson
2860
2860
  );
2861
- const entry2 = field(
2861
+ const entry = field(
2862
2862
  state,
2863
2863
  "/runtime/entry",
2864
2864
  base.entry,
@@ -2885,7 +2885,7 @@ var mergeRuntime = (state, base, local, remote) => {
2885
2885
  (value3) => stableText(Schema12.encodeSync(Schema12.Array(Schema12.String))(value3)),
2886
2886
  (value3) => asJson(Schema12.encodeSync(Schema12.Array(Schema12.String))(value3))
2887
2887
  );
2888
- return Option7.all({ python, entry: entry2, requirements, imports }).pipe(
2888
+ return Option7.all({ python, entry, requirements, imports }).pipe(
2889
2889
  Option7.map(
2890
2890
  (fields) => new IrRuntime({
2891
2891
  python: fields.python,
@@ -4967,7 +4967,7 @@ var make = (options) => {
4967
4967
  const valuesOf = (entries, category) => R5.fromEntries(
4968
4968
  Arr12.flatMap(
4969
4969
  entries,
4970
- (entry2) => entry2.category === category && entry2.value !== null ? [[entry2.key, entry2.value]] : []
4970
+ (entry) => entry.category === category && entry.value !== null ? [[entry.key, entry.value]] : []
4971
4971
  )
4972
4972
  );
4973
4973
  return {
@@ -5375,7 +5375,6 @@ var fail6 = (message, exitCode = EXIT_FAILURE) => Effect15.fail(new CliFailure({
5375
5375
  var oneLine2 = (value3) => Str8.trim(Str8.replace(/\s+/g, " ")(String(value3))).slice(0, 300);
5376
5376
 
5377
5377
  // ../identity-store/src/identity.ts
5378
- import { Entry } from "@napi-rs/keyring";
5379
5378
  import { Context as Context6, Effect as Effect16, Layer as Layer5, Option as Option20, pipe } from "effect";
5380
5379
  import * as Arr13 from "effect/Array";
5381
5380
  import * as R6 from "effect/Record";
@@ -5385,7 +5384,6 @@ import * as Predicate from "effect/Predicate";
5385
5384
  import * as Schema22 from "effect/Schema";
5386
5385
  import * as Str9 from "effect/String";
5387
5386
  var CONFIG_VERSION = 1;
5388
- var KEYCHAIN_SERVICE = "braven";
5389
5387
  var CredentialKind = Schema22.Literals(["session", "apiKey"]);
5390
5388
  var Account = class extends Schema22.Class("Account")({
5391
5389
  origin: Schema22.String,
@@ -5422,29 +5420,56 @@ var ConfigFile = class extends Schema22.Class("ConfigFile")({
5422
5420
  var ConfigJson = Schema22.fromJsonString(ConfigFile, { space: 2 });
5423
5421
  var StoreConfig = class extends Context6.Service()("braven/StoreConfig") {
5424
5422
  };
5425
- var Keychain = class extends Context6.Service()(
5426
- "braven/Keychain"
5423
+ var CREDENTIALS_FILE = "credentials.json";
5424
+ var Secrets = class extends Context6.Service()(
5425
+ "braven/Secrets"
5427
5426
  ) {
5428
5427
  };
5429
- var entry = (account) => new Entry(KEYCHAIN_SERVICE, account);
5430
- var keychainAction = (action, run) => Effect16.try({
5431
- try: run,
5432
- catch: (cause) => new CliFailure({
5433
- message: `keychain ${action} failed: ${oneLine2(cause)}`,
5434
- exitCode: EXIT_FAILURE
5435
- })
5436
- });
5437
- var keychainLayer = Layer5.succeed(Keychain)({
5438
- get: (account) => keychainAction("read", () => entry(account).getPassword()).pipe(
5439
- Effect16.map(Option20.fromNullishOr)
5440
- ),
5441
- set: (account, secret) => keychainAction("write", () => {
5442
- entry(account).setPassword(secret);
5443
- }),
5444
- remove: (account) => keychainAction("delete", () => {
5445
- entry(account).deletePassword();
5428
+ var SecretsJson = Schema22.fromJsonString(
5429
+ Schema22.Record(Schema22.String, Schema22.String),
5430
+ { space: 2 }
5431
+ );
5432
+ var credentialsPath = (configPath2) => Str9.replace(/config\.json$/, CREDENTIALS_FILE)(configPath2);
5433
+ var secretsLayer = Layer5.effect(Secrets)(
5434
+ Effect16.gen(function* () {
5435
+ const { path } = yield* StoreConfig;
5436
+ const fs = yield* FileSystem11.FileSystem;
5437
+ const pathService = yield* Path11.Path;
5438
+ const file = credentialsPath(path);
5439
+ const read2 = () => Effect16.gen(function* () {
5440
+ if (!(yield* fs.exists(file).pipe(Effect16.mapError(storageFailure("read", file))))) {
5441
+ return {};
5442
+ }
5443
+ const text = yield* fs.readFileString(file).pipe(Effect16.mapError(storageFailure("read", file)));
5444
+ return yield* Schema22.decodeUnknownEffect(SecretsJson)(text).pipe(
5445
+ Effect16.mapError(
5446
+ (cause) => new CliFailure({
5447
+ message: `${file} is not a credentials file: ${oneLine2(cause.message)}`,
5448
+ exitCode: EXIT_FAILURE
5449
+ })
5450
+ )
5451
+ );
5452
+ });
5453
+ const write2 = (secrets) => Effect16.gen(function* () {
5454
+ const text = yield* Schema22.encodeUnknownEffect(SecretsJson)(
5455
+ secrets
5456
+ ).pipe(Effect16.mapError(storageFailure("encode", file)));
5457
+ yield* fs.makeDirectory(pathService.dirname(file), { recursive: true }).pipe(Effect16.mapError(storageFailure("create", file)));
5458
+ const temporary = `${file}.tmp`;
5459
+ yield* fs.writeFileString(temporary, text, { mode: 384 }).pipe(Effect16.mapError(storageFailure("write", file)));
5460
+ yield* fs.chmod(temporary, 384).pipe(Effect16.mapError(storageFailure("write", file)));
5461
+ yield* fs.rename(temporary, file).pipe(Effect16.mapError(storageFailure("write", file)));
5462
+ });
5463
+ const update = (change) => Effect16.gen(function* () {
5464
+ yield* write2(change(yield* read2()));
5465
+ });
5466
+ return Secrets.of({
5467
+ get: (account) => read2().pipe(Effect16.map((all4) => Option20.fromNullishOr(all4[account]))),
5468
+ set: (account, secret) => update((all4) => R6.set(all4, account, secret)),
5469
+ remove: (account) => update((all4) => R6.remove(all4, account))
5470
+ });
5446
5471
  })
5447
- });
5472
+ );
5448
5473
  var accountKey = (origin, email) => `${normalizeOrigin(origin)}|${email}`;
5449
5474
  var normalizeOrigin = (origin) => Str9.replace(/\/+$/, "")(origin);
5450
5475
  var storageFailure = (action, path) => (cause) => new CliFailure({
@@ -5511,8 +5536,8 @@ var storeCredential = (credential, label = Option20.none()) => Effect16.gen(func
5511
5536
  }
5512
5537
  const origin = normalizeOrigin(credential.origin);
5513
5538
  const key2 = accountKey(origin, credential.email);
5514
- const keychain = yield* Keychain;
5515
- yield* keychain.set(key2, credential.secret);
5539
+ const secrets = yield* Secrets;
5540
+ yield* secrets.set(key2, credential.secret);
5516
5541
  const config = yield* loadConfig();
5517
5542
  const account = new Account({
5518
5543
  origin,
@@ -5559,8 +5584,8 @@ var removeCredential = (email) => Effect16.gen(function* () {
5559
5584
  if (Option20.isNone(account)) return Option20.none();
5560
5585
  const target = account.value;
5561
5586
  const key2 = accountKey(target.origin, target.email);
5562
- const keychain = yield* Keychain;
5563
- yield* keychain.remove(key2);
5587
+ const secrets = yield* Secrets;
5588
+ yield* secrets.remove(key2);
5564
5589
  const accounts = config.accounts.filter(
5565
5590
  (stored) => accountKey(stored.origin, stored.email) !== key2
5566
5591
  );
@@ -5607,8 +5632,8 @@ var storedCredential = (options) => Effect16.gen(function* () {
5607
5632
  const account = Option20.isSome(options.origin) || Option20.isSome(options.email) ? findAccount(config, options.origin, options.email) : currentAccount(config);
5608
5633
  if (Option20.isNone(account)) return Option20.none();
5609
5634
  const stored = account.value;
5610
- const keychain = yield* Keychain;
5611
- const secret = yield* keychain.get(accountKey(stored.origin, stored.email));
5635
+ const secrets = yield* Secrets;
5636
+ const secret = yield* secrets.get(accountKey(stored.origin, stored.email));
5612
5637
  if (Option20.isNone(secret)) return Option20.none();
5613
5638
  return Option20.some({
5614
5639
  origin: stored.origin,
@@ -8256,20 +8281,40 @@ import * as P6 from "effect/Predicate";
8256
8281
  import * as R7 from "effect/Record";
8257
8282
  import * as Schema30 from "effect/Schema";
8258
8283
  import * as Str11 from "effect/String";
8259
- var CLIENTS = ["claude", "cursor", "zed", "vscode"];
8284
+ var CLIENTS = [
8285
+ "claude",
8286
+ "cursor",
8287
+ "opencode",
8288
+ "zed",
8289
+ "vscode"
8290
+ ];
8260
8291
  var serversKey = {
8261
8292
  claude: "mcpServers",
8262
8293
  cursor: "mcpServers",
8294
+ opencode: "mcp",
8263
8295
  zed: "context_servers",
8264
8296
  vscode: "servers"
8265
8297
  };
8266
8298
  var displayName = {
8267
8299
  claude: "Claude Desktop",
8268
8300
  cursor: "Cursor",
8301
+ opencode: "opencode",
8269
8302
  zed: "Zed",
8270
8303
  vscode: "VS Code"
8271
8304
  };
8272
- var serverEntry = (client) => client === "vscode" ? { type: "stdio", command: "braven", args: ["mcp"] } : { command: "braven", args: ["mcp"] };
8305
+ var serverEntry = (client) => Match10.value(client).pipe(
8306
+ Match10.when("vscode", () => ({
8307
+ type: "stdio",
8308
+ command: "braven",
8309
+ args: ["mcp"]
8310
+ })),
8311
+ Match10.when("opencode", () => ({
8312
+ type: "local",
8313
+ command: ["braven", "mcp"],
8314
+ enabled: true
8315
+ })),
8316
+ Match10.orElse(() => ({ command: "braven", args: ["mcp"] }))
8317
+ );
8273
8318
  var isRecord = (value3) => P6.isObject(value3);
8274
8319
  var asRecord = (value3) => Option33.liftPredicate(isRecord)(value3);
8275
8320
  var value2 = (shape, name) => Option33.filter(Option33.fromNullishOr(shape.values[name]), Str11.isNonEmpty);
@@ -8293,6 +8338,10 @@ var applications = (shape) => Match10.value(shape.platform).pipe(
8293
8338
  )
8294
8339
  )
8295
8340
  );
8341
+ var configHome = (shape) => Option33.getOrElse(
8342
+ value2(shape, "XDG_CONFIG_HOME"),
8343
+ () => join9(shape, shape.home, ".config")
8344
+ );
8296
8345
  var configPathFor = (client, shape) => Match10.value(client).pipe(
8297
8346
  Match10.when(
8298
8347
  "claude",
@@ -8311,14 +8360,21 @@ var configPathFor = (client, shape) => Match10.value(client).pipe(
8311
8360
  )
8312
8361
  )
8313
8362
  ),
8363
+ Match10.when(
8364
+ "opencode",
8365
+ () => Option33.getOrElse(
8366
+ value2(shape, "OPENCODE_CONFIG"),
8367
+ () => join9(shape, configHome(shape), "opencode", "opencode.json")
8368
+ )
8369
+ ),
8314
8370
  Match10.when("vscode", () => join9(shape, shape.cwd, ".vscode", "mcp.json")),
8315
8371
  Match10.exhaustive
8316
8372
  );
8317
8373
  var decodeJson2 = Schema30.decodeUnknownOption(
8318
8374
  Schema30.fromJsonString(Schema30.Unknown)
8319
8375
  );
8320
- var withoutEmptyEnv = (entry2) => Option33.match(asRecord(entry2), {
8321
- onNone: () => entry2,
8376
+ var withoutEmptyEnv = (entry) => Option33.match(asRecord(entry), {
8377
+ onNone: () => entry,
8322
8378
  onSome: (record2) => Option33.match(Option33.flatMap(R7.get(record2, "env"), asRecord), {
8323
8379
  onNone: () => record2,
8324
8380
  onSome: (env) => Match10.value(R7.isEmptyRecord(env)).pipe(
@@ -8334,16 +8390,16 @@ var withServer = (config, key2, servers, client) => ({
8334
8390
  var invalid = (reason) => ({ kind: "invalid", reason });
8335
8391
  var mergeEntry = (client, config, key2, servers, force) => Option33.match(R7.get(servers, "braven"), {
8336
8392
  onNone: () => withServer(config, key2, servers, client),
8337
- onSome: (entry2) => Bool2.match(
8393
+ onSome: (entry) => Bool2.match(
8338
8394
  Equal.equals(
8339
- withoutEmptyEnv(entry2),
8395
+ withoutEmptyEnv(entry),
8340
8396
  withoutEmptyEnv(serverEntry(client))
8341
8397
  ),
8342
8398
  {
8343
8399
  onTrue: () => ({ kind: "unchanged" }),
8344
8400
  onFalse: () => Bool2.match(force, {
8345
8401
  onTrue: () => withServer(config, key2, servers, client),
8346
- onFalse: () => ({ kind: "conflict", current: entry2 })
8402
+ onFalse: () => ({ kind: "conflict", current: entry })
8347
8403
  })
8348
8404
  }
8349
8405
  )
@@ -8438,10 +8494,10 @@ var install = Effect33.fn("mcp.install")(function* (options) {
8438
8494
  { kind: "conflict" },
8439
8495
  ({ current }) => Effect33.flatMap(
8440
8496
  inline(current),
8441
- (entry2) => Console7.log(
8497
+ (entry) => Console7.log(
8442
8498
  `braven is already configured differently:
8443
8499
  file ${options.path}
8444
- current ${entry2}`
8500
+ current ${entry}`
8445
8501
  ).pipe(
8446
8502
  Effect33.andThen(
8447
8503
  fail6(
@@ -8506,8 +8562,81 @@ var installCommand = Effect33.fn("mcp.installCommand")(
8506
8562
  }
8507
8563
  );
8508
8564
 
8565
+ // src/desktop.ts
8566
+ import { Console as Console8, Effect as Effect34 } from "effect";
8567
+ import { execFile } from "node:child_process";
8568
+ import { cpSync, existsSync, mkdtempSync, rmSync } from "node:fs";
8569
+ import { homedir, tmpdir } from "node:os";
8570
+ import { join as join10, resolve } from "node:path";
8571
+ var destinationOf = (input) => input.destination ?? (input.system === true ? "/Applications/Braven.app" : join10(homedir(), "Applications", "Braven.app"));
8572
+ var run = (bin, args) => Effect34.tryPromise({
8573
+ try: () => new Promise((resolvePromise, reject) => {
8574
+ execFile(
8575
+ bin,
8576
+ [...args],
8577
+ (error, stdout) => error === null ? resolvePromise(stdout) : reject(error)
8578
+ );
8579
+ }),
8580
+ catch: (cause) => new Error(`${bin} failed: ${String(cause)}`)
8581
+ });
8582
+ var copyApp = (source, destination) => Effect34.sync(() => {
8583
+ if (!existsSync(join10(source, "Contents", "Info.plist")))
8584
+ throw new Error(`${source} is not a Braven.app`);
8585
+ rmSync(destination, { recursive: true, force: true });
8586
+ cpSync(source, destination, { recursive: true });
8587
+ });
8588
+ var installDesktop = Effect34.fn("desktop.install")(function* (input) {
8589
+ if (process.platform === "win32") {
8590
+ if (input.exe === void 0)
8591
+ return yield* Effect34.fail(
8592
+ new Error("pass --exe <Braven-Setup.exe> on Windows")
8593
+ );
8594
+ yield* run(
8595
+ resolve(input.exe),
8596
+ input.silent === true ? ["/SILENT", "/SUPPRESSMSGBOXES", "/NORESTART"] : []
8597
+ );
8598
+ yield* Console8.log(
8599
+ input.silent === true ? "installer finished" : "the installer will finish in its own window"
8600
+ );
8601
+ return;
8602
+ }
8603
+ if (process.platform !== "darwin")
8604
+ return yield* Effect34.fail(
8605
+ new Error("the watcher window is available on macOS and Windows")
8606
+ );
8607
+ if (input.dmg === void 0 && input.app === void 0)
8608
+ return yield* Effect34.fail(
8609
+ new Error("pass --dmg <file.dmg> or --app <Braven.app>")
8610
+ );
8611
+ const destination = destinationOf(input);
8612
+ if (input.app !== void 0) {
8613
+ yield* copyApp(resolve(input.app), destination);
8614
+ } else {
8615
+ const mount = mkdtempSync(join10(tmpdir(), "braven-dmg-"));
8616
+ yield* Effect34.ensuring(
8617
+ Effect34.gen(function* () {
8618
+ yield* run("hdiutil", [
8619
+ "attach",
8620
+ "-nobrowse",
8621
+ "-readonly",
8622
+ "-mountpoint",
8623
+ mount,
8624
+ resolve(input.dmg)
8625
+ ]);
8626
+ yield* copyApp(join10(mount, "Braven.app"), destination);
8627
+ }),
8628
+ Effect34.sync(() => {
8629
+ void run("hdiutil", ["detach", mount]);
8630
+ rmSync(mount, { recursive: true, force: true });
8631
+ })
8632
+ );
8633
+ }
8634
+ yield* Console8.log(`installed ${destination}`);
8635
+ yield* run("open", [destination]);
8636
+ });
8637
+
8509
8638
  // src/cli.ts
8510
- var VERSION = "0.0.0";
8639
+ var VERSION = true ? "0.1.3" : "0.0.0";
8511
8640
  var originFlag = Flag4.String("origin").pipe(
8512
8641
  Flag4.optional,
8513
8642
  Flag4.withDescription(
@@ -8558,7 +8687,7 @@ var login2 = Command4.make(
8558
8687
  ),
8559
8688
  email: emailFlag
8560
8689
  },
8561
- Effect34.fn(function* ({
8690
+ Effect35.fn(function* ({
8562
8691
  origin,
8563
8692
  webOrigin,
8564
8693
  device,
@@ -8602,7 +8731,7 @@ var keyAddCommand = Command4.make(
8602
8731
  ),
8603
8732
  origin: originFlag
8604
8733
  },
8605
- Effect34.fn(function* ({ key: key2, email, label, origin }) {
8734
+ Effect35.fn(function* ({ key: key2, email, label, origin }) {
8606
8735
  const environment = yield* Environment;
8607
8736
  yield* keyAdd({
8608
8737
  origin: resolveOrigin(origin, environment),
@@ -8645,7 +8774,7 @@ var pushCommand2 = Command4.make(
8645
8774
  )
8646
8775
  )
8647
8776
  },
8648
- Effect34.fn(function* ({ file, override, path, projectId }) {
8777
+ Effect35.fn(function* ({ file, override, path, projectId }) {
8649
8778
  yield* push2({ file, override, path, projectId });
8650
8779
  })
8651
8780
  ).pipe(
@@ -8664,14 +8793,14 @@ var configSetCommand = Command4.make(
8664
8793
  name: Argument4.Literals("name", ["company", "project"]),
8665
8794
  value: Argument4.String("value")
8666
8795
  },
8667
- Effect34.fn(function* ({ name, value: value3 }) {
8796
+ Effect35.fn(function* ({ name, value: value3 }) {
8668
8797
  yield* configSet({ name, value: value3 });
8669
8798
  })
8670
8799
  ).pipe(Command4.withDescription("set a default"));
8671
8800
  var configUseCommand = Command4.make(
8672
8801
  "use",
8673
8802
  { email: Argument4.String("email") },
8674
- Effect34.fn(function* ({ email }) {
8803
+ Effect35.fn(function* ({ email }) {
8675
8804
  yield* configUse(email);
8676
8805
  })
8677
8806
  ).pipe(Command4.withDescription("switch the current account"));
@@ -8688,7 +8817,7 @@ var configCommand = Command4.make("config", {}, () => configShow()).pipe(
8688
8817
  var logoutCommand = Command4.make(
8689
8818
  "logout",
8690
8819
  { email: emailFlag },
8691
- Effect34.fn(function* ({ email }) {
8820
+ Effect35.fn(function* ({ email }) {
8692
8821
  yield* logout(email);
8693
8822
  })
8694
8823
  ).pipe(
@@ -8713,14 +8842,57 @@ var mcpInstallCommand = Command4.make(
8713
8842
  Flag4.withDescription("write this file instead of the client's default")
8714
8843
  )
8715
8844
  },
8716
- Effect34.fn(function* ({ client, force, path }) {
8845
+ Effect35.fn(function* ({ client, force, path }) {
8717
8846
  yield* installCommand({ client, force, path });
8718
8847
  })
8719
8848
  ).pipe(
8720
8849
  Command4.withDescription(
8721
- "merge the Braven server into a client's MCP config (Claude, Cursor, Zed, VS Code)"
8850
+ "merge the Braven server into a client's MCP config (Claude, Cursor, opencode, Zed, VS Code)"
8722
8851
  )
8723
8852
  );
8853
+ var desktopInstallCommand = Command4.make(
8854
+ "install",
8855
+ {
8856
+ dmg: Flag4.String("dmg").pipe(
8857
+ Flag4.optional,
8858
+ Flag4.withDescription("a Braven DMG to install")
8859
+ ),
8860
+ app: Flag4.String("app").pipe(
8861
+ Flag4.optional,
8862
+ Flag4.withDescription("an already-built Braven.app")
8863
+ ),
8864
+ exe: Flag4.String("exe").pipe(
8865
+ Flag4.optional,
8866
+ Flag4.withDescription("a Windows Braven installer (.exe)")
8867
+ ),
8868
+ silent: Flag4.Boolean("silent").pipe(
8869
+ Flag4.withDefault(false),
8870
+ Flag4.withDescription("Windows: run the installer without its wizard")
8871
+ ),
8872
+ system: Flag4.Boolean("system").pipe(
8873
+ Flag4.withDefault(false),
8874
+ Flag4.withDescription("install into /Applications")
8875
+ ),
8876
+ destination: Flag4.String("destination").pipe(
8877
+ Flag4.optional,
8878
+ Flag4.withDescription("install here instead of ~/Applications")
8879
+ )
8880
+ },
8881
+ Effect35.fn(function* ({ dmg, app, exe, silent, system, destination }) {
8882
+ yield* installDesktop({
8883
+ dmg: Option34.getOrUndefined(dmg),
8884
+ app: Option34.getOrUndefined(app),
8885
+ exe: Option34.getOrUndefined(exe),
8886
+ silent,
8887
+ system,
8888
+ destination: Option34.getOrUndefined(destination)
8889
+ });
8890
+ })
8891
+ ).pipe(Command4.withDescription("copy the watcher window into Applications"));
8892
+ var desktopCommand = Command4.make("desktop", {}).pipe(
8893
+ Command4.withDescription("the watcher window (macOS)"),
8894
+ Command4.withSubcommands([desktopInstallCommand])
8895
+ );
8724
8896
  var mcpCommand = Command4.make("mcp", {}, () => runMcp(VERSION)).pipe(
8725
8897
  Command4.withDescription(
8726
8898
  "run the Braven MCP server over stdio (connect Claude, Cursor, or Zed here)"
@@ -8738,6 +8910,7 @@ var braven = Command4.make("braven").pipe(
8738
8910
  pushCommand2,
8739
8911
  agentCommand,
8740
8912
  mcpCommand,
8913
+ desktopCommand,
8741
8914
  whoamiCommand,
8742
8915
  configCommand,
8743
8916
  logoutCommand
@@ -8783,15 +8956,15 @@ var describe = (error) => Match11.value(error).pipe(
8783
8956
  })),
8784
8957
  Match11.orElse((other) => ({ code: EXIT_USAGE, message: other.message }))
8785
8958
  );
8786
- var main = Effect34.fn("braven")(function* (args) {
8959
+ var main = Effect35.fn("braven")(function* (args) {
8787
8960
  return yield* runCli(args).pipe(
8788
- Effect34.as(0),
8789
- Effect34.catch((error) => {
8961
+ Effect35.as(0),
8962
+ Effect35.catch((error) => {
8790
8963
  const described = describe(error);
8791
- if (described.code === 0) return Effect34.succeed(0);
8792
- if (described.message.length === 0) return Effect34.succeed(described.code);
8793
- return Console8.error(`braven: ${oneLine2(described.message)}`).pipe(
8794
- Effect34.as(described.code)
8964
+ if (described.code === 0) return Effect35.succeed(0);
8965
+ if (described.message.length === 0) return Effect35.succeed(described.code);
8966
+ return Console9.error(`braven: ${oneLine2(described.message)}`).pipe(
8967
+ Effect35.as(described.code)
8795
8968
  );
8796
8969
  })
8797
8970
  );