braith 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +5 -3
  2. package/dist/cli.js +194 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,7 +1,9 @@
1
1
  # braith
2
2
 
3
- Run `npx braith init` in your application repository, then follow `.braith/SETUP.md` with your coding agent. The CLI validates and publishes reviewed intent manifests; it does not upload your repository.
3
+ Run `npx braith@0.3.0 connect` in your application repository, then follow `.braith/SETUP.md` with your coding agent. The CLI validates and publishes reviewed intent manifests; it does not upload your repository.
4
4
 
5
- Commands: `init`, `enroll`, `validate`, `push`, `release`, `test`.
5
+ Commands: `connect`, `init`, `enroll`, `validate`, `push`, `release`, `test`.
6
6
 
7
- Use a short-lived enrollment prompt from the Braith dashboard to get scoped setup credentials. Keep `.braith/credentials.json` private. A pushed manifest is not active until you bind it to a release or explicitly select `release default`.
7
+ Version 0.3.0 includes optional discovery context alongside configured outcomes. The coding tool installs supported capture across the app and creates a static catalogue, with findings visible in review mode. Discovery does not require a declared outcome for every flow. Capture/source changes enter the local review map; source files are not uploaded. The `test` command remains an isolated configured-outcome fixture, not a discovery accuracy test. Exercise actual hooks separately with normal, failed and recovered interactions.
8
+
9
+ The setup prompt is public and identical for everyone. The CLI displays an expiring authorization link and code; approve the matching code in your signed-in Braith account. Only the waiting CLI receives scoped setup credentials. Run connect again to renew expired setup access. Keep `.braith/credentials.json` private. A pushed manifest is not active until you bind it to a release or explicitly select `release default`.
package/dist/cli.js CHANGED
@@ -19860,6 +19860,99 @@ var DecisionRecordSchema = external_exports.object({
19860
19860
  }
19861
19861
  });
19862
19862
 
19863
+ // ../contracts/src/discovery.ts
19864
+ var DISCOVERY_EVENT = "braith_observation";
19865
+ var catalogue = (limit) => external_exports.record(IdentifierSchema, external_exports.string().trim().min(3).max(160)).refine(
19866
+ (value) => Object.keys(value).length <= limit,
19867
+ "Context catalogue too large"
19868
+ );
19869
+ var DiscoveryConfigSchema = external_exports.object({
19870
+ enabled: external_exports.literal(true),
19871
+ mode: external_exports.enum(["shadow", "review"]).default("review"),
19872
+ application: external_exports.string().trim().min(10).max(600),
19873
+ surfaces: catalogue(64).default({}),
19874
+ actions: catalogue(128).default({}),
19875
+ feedback: catalogue(64).default({})
19876
+ }).strict();
19877
+ var DiscoveryObservationSchema = external_exports.object({
19878
+ kind: external_exports.enum([
19879
+ "action",
19880
+ "navigation",
19881
+ "feedback",
19882
+ "request",
19883
+ "state",
19884
+ "error"
19885
+ ]),
19886
+ surface: IdentifierSchema,
19887
+ action: IdentifierSchema.optional(),
19888
+ feedback: IdentifierSchema.optional(),
19889
+ result: external_exports.enum([
19890
+ "started",
19891
+ "success",
19892
+ "failure",
19893
+ "pending",
19894
+ "changed",
19895
+ "unchanged",
19896
+ "denied",
19897
+ "cancelled",
19898
+ "unknown"
19899
+ ]),
19900
+ httpStatus: external_exports.number().int().min(100).max(599).optional(),
19901
+ contentKind: external_exports.enum(["html", "json", "file", "empty", "other"]).optional(),
19902
+ position: external_exports.number().int().min(0).max(31),
19903
+ end: external_exports.boolean()
19904
+ }).strict();
19905
+ var DiscoveryEvidenceSchema = DiscoveryObservationSchema.extend({
19906
+ eventId: external_exports.string().min(1).max(256),
19907
+ occurredAt: IsoDateTimeSchema,
19908
+ sourceTrust: SourceTrustSchema
19909
+ }).strict();
19910
+ var DiscoveryStateSchema = external_exports.object({
19911
+ schemaVersion: external_exports.literal(1),
19912
+ context: DiscoveryConfigSchema,
19913
+ observations: external_exports.array(DiscoveryEvidenceSchema).min(2).max(32)
19914
+ }).strict().superRefine((value, ctx) => {
19915
+ const ids = /* @__PURE__ */ new Set();
19916
+ value.observations.forEach((item, index) => {
19917
+ if (ids.has(item.eventId) || item.position !== index || item.end !== (index === value.observations.length - 1))
19918
+ ctx.addIssue({
19919
+ code: "custom",
19920
+ message: "Discovery requires a complete ordered window"
19921
+ });
19922
+ ids.add(item.eventId);
19923
+ });
19924
+ });
19925
+ var DiscoveryDecisionSchema = external_exports.object({
19926
+ assessment: external_exports.enum(["ordinary", "possible_problem", "insufficient"]),
19927
+ confidence: external_exports.number().min(0).max(1),
19928
+ problemProbability: external_exports.number().min(0).max(1),
19929
+ category: external_exports.enum([
19930
+ "contradiction",
19931
+ "friction",
19932
+ "access",
19933
+ "data_loss",
19934
+ "delivery",
19935
+ "other",
19936
+ "none"
19937
+ ]),
19938
+ severity: external_exports.enum(["low", "medium", "high", "critical"]),
19939
+ evidenceStart: external_exports.string().min(1).max(256).nullable(),
19940
+ evidenceEnd: external_exports.string().min(1).max(256).nullable()
19941
+ }).strict();
19942
+ var DiscoveryResultSchema = external_exports.discriminatedUnion("status", [
19943
+ external_exports.object({
19944
+ kind: external_exports.literal("discovery"),
19945
+ status: external_exports.literal("evaluated"),
19946
+ decision: DiscoveryDecisionSchema,
19947
+ usage: ModelUsageSchema
19948
+ }).strict(),
19949
+ external_exports.object({
19950
+ kind: external_exports.literal("discovery"),
19951
+ status: external_exports.literal("unavailable"),
19952
+ reason: external_exports.string().min(1).max(200)
19953
+ }).strict()
19954
+ ]);
19955
+
19863
19956
  // ../contracts/src/events.ts
19864
19957
  var EventNameSchema = external_exports.string().trim().min(1).max(128).regex(/^[a-z][a-z0-9_]*$/, "Event names must use lower_snake_case");
19865
19958
  var EventBaseSchema = external_exports.object({
@@ -19883,7 +19976,16 @@ function requireSubject(value, ctx) {
19883
19976
  });
19884
19977
  }
19885
19978
  }
19886
- var EventInputSchema = EventBaseSchema.superRefine(requireSubject);
19979
+ var EventInputSchema = EventBaseSchema.superRefine((event, ctx) => {
19980
+ requireSubject(event, ctx);
19981
+ if (Object.keys(event.properties).length > 24 || new TextEncoder().encode(JSON.stringify(event.properties)).byteLength > 1024 || new TextEncoder().encode(JSON.stringify(event)).byteLength > 4096) {
19982
+ ctx.addIssue({
19983
+ code: "custom",
19984
+ path: ["properties"],
19985
+ message: "Events allow at most 24 properties, 1 KiB of properties and 4 KiB total"
19986
+ });
19987
+ }
19988
+ });
19887
19989
  var AcceptedEventSchema = EventBaseSchema.extend({
19888
19990
  projectId: OpaqueIdSchema,
19889
19991
  environment: EnvironmentSchema,
@@ -20088,6 +20190,9 @@ var EventDefinitionSchema = external_exports.object({
20088
20190
  allowedProperties: external_exports.record(IdentifierSchema, PropertyTypeSchema),
20089
20191
  requiredProperties: external_exports.array(IdentifierSchema).max(24)
20090
20192
  }).superRefine((definition, ctx) => {
20193
+ if (Object.keys(definition.allowedProperties).length > 24) {
20194
+ ctx.addIssue({ code: "custom", path: ["allowedProperties"], message: "At most 24 properties are supported" });
20195
+ }
20091
20196
  for (const property of definition.requiredProperties) {
20092
20197
  if (!(property in definition.allowedProperties)) {
20093
20198
  ctx.addIssue({
@@ -20145,9 +20250,19 @@ var ProjectManifestSchema = external_exports.object({
20145
20250
  schemaVersion: external_exports.literal(1),
20146
20251
  manifestVersion: external_exports.string().trim().min(1).max(128),
20147
20252
  projectKey: external_exports.string().trim().min(1).max(256),
20148
- eventDefinitions: external_exports.array(EventDefinitionSchema).min(1).max(250),
20149
- intents: external_exports.array(IntentDefinitionSchema).min(1).max(15)
20253
+ eventDefinitions: external_exports.array(EventDefinitionSchema).max(250),
20254
+ intents: external_exports.array(IntentDefinitionSchema).max(15),
20255
+ discovery: DiscoveryConfigSchema.optional()
20150
20256
  }).superRefine((manifest, ctx) => {
20257
+ if (!manifest.discovery && (!manifest.intents.length || !manifest.eventDefinitions.length)) {
20258
+ ctx.addIssue({ code: "custom", message: "A manifest needs configured outcomes or enabled discovery" });
20259
+ }
20260
+ if (manifest.eventDefinitions.some((event) => event.name === DISCOVERY_EVENT)) {
20261
+ ctx.addIssue({ code: "custom", message: "braith_observation is reserved for validated discovery capture" });
20262
+ }
20263
+ if (new TextEncoder().encode(JSON.stringify(manifest)).byteLength > 65536) {
20264
+ ctx.addIssue({ code: "custom", message: "Manifest exceeds the 64 KiB delivery limit" });
20265
+ }
20151
20266
  const eventMap = new Map(manifest.eventDefinitions.map((event) => [event.name, event]));
20152
20267
  if (eventMap.size !== manifest.eventDefinitions.length) {
20153
20268
  ctx.addIssue({
@@ -20558,17 +20673,17 @@ function localCredentials(root) {
20558
20673
  }
20559
20674
  var setupInstructions = `# Set up Braith
20560
20675
 
20561
- Instrument the application's important user outcomes using the Braith SDK.
20676
+ Install continual customer-problem discovery and stronger checks for the application's important outcomes. Inspect the repository locally; the developer should not have to define each flow.
20562
20677
 
20563
- 1. Inspect the repository and identify 3\u20135 critical user-facing outcomes. Prefer outcomes tied to activation, payment, retained access, data creation, export, invitation, or integration setup.
20564
- 2. For each outcome, describe the observable success boundary. A UI success message is not enough when the outcome depends on committed server state.
20565
- 3. Identify meaningful entry, progress, success, contradiction, recovery, and explicit cancellation signals. For each outcome, include at least one repeatable post-success contradiction when one exists (for example, an upgrade prompt after payment, a created project returning Not Found, or an export opening empty). Jev can interpret these ambiguous signals, but the incident policy still needs them declared. Do not instrument every interaction.
20678
+ 1. Inspect the repository's routes, shared UI components, feedback and request/state boundaries. Create a static discovery catalogue in braith.config.json: enabled true, mode review, application purpose, surfaces, actions and feedback. These are descriptions of what the app does, not rules listing possible failures. Configure the SDK's discovery catalogue, route templates and stable action/feedback selectors. Cover supported app surfaces, including flows without configured outcomes. Never derive catalogue labels from customer content, URL parameters or form values.
20679
+ 2. Alongside broad discovery, choose a few critical outcomes for stronger explicit checks. Describe their observable success boundary. A UI success message is not enough when the outcome depends on committed server state.
20680
+ 3. Add finite observations at shared server/job/access/state boundaries where browser status alone cannot reveal the result. Use client.observeWindow on the server for a coherent same-subject sequence, or Braith.observe in the browser for a safe semantic summary. Use only catalogue identifiers and fixed kind/result/status/contentKind fields; never send values or request/response bodies. A local before/after comparison may report changed or unchanged without sending the underlying data. Automatic browser capture handles supported clicks, submits, navigation, same-origin fetch summaries and declared feedback selectors. It does not read page text, inspect network bodies, or prove that every feature is covered.
20566
20681
  4. Add allowlisted Braith events at those code locations. In browser code, use the named Braith export from @braith/sdk and call Braith.init({ projectKey, endpoint }) once, then Braith.identify and Braith.track. On the server, use createServerClient({ projectKey, endpoint }), pass userId and attemptId in track context, and flush before a short-lived process exits. Use server events for authoritative facts and propagate an attemptId across the journey.
20567
- 5. Replace the example in braith.config.json with the resulting event definitions and intents. Keep the set to 3\u20135 outcomes initially.
20568
- 6. Run the short-lived Braith enrollment command from the dashboard prompt. It stores keys only in .braith/credentials.json, ignored by .braith/.gitignore. For a later setup prompt, run braith enroll <token> --replace; the old local file is retained if renewal fails. Set the browser and server project keys in the corresponding application deployment environment. Never commit the server key. The temporary management key expires after 30 minutes.
20682
+ 5. Additionally identify 3\u20135 critical user-facing outcomes, or fewer when appropriate, for stronger explicit checks. Define entry, authoritative success, progress, contradiction, recovery and cancellation signals. A UI success message alone cannot establish committed server state. Replace the example event definitions and intents with these real outcomes. Their absence must not exclude other activity from discovery; discovery-only apps may have empty arrays. Broad capture is opt-in, defaults to shadow review, and uses the existing event/analysis allowance: 20% of analysis capacity for discovery and 80% for core checks. Start at 10% ordinary-window sampling and at most 10 windows/hour per browser; observed failures are prioritised within that same cap. State coverage gaps and privacy choices honestly.
20683
+ 6. Run braith connect and show the owner its authorization link and matching code. The owner signs in to Braith and approves that connection. Keys are stored only in .braith/credentials.json, ignored by .braith/.gitignore. Run connect again to renew setup access; existing credentials are preserved on failure or a different-project selection. Set browser and server keys in the corresponding deployment environment. Never commit the server key. The temporary management key expires after 30 minutes.
20569
20684
  7. Run npx braith validate, resolve errors, and review every warning or unverifiable source check.
20570
20685
  8. Present the instrumentation and configuration diff to the developer for review. Do not upload the repository or deploy automatically.
20571
- 9. After review, run npx braith push, then npx braith test. Push records .braith/source-map.json, a local review map of intent IDs to source files and hashes; it uploads only the manifest. Commit and review that map with the instrumentation. Use npx braith test --semantic to check a configured browser-contradiction journey with Jev. Test incidents are isolated from production usage and alerts.
20686
+ 9. After review, run npx braith push. If core outcomes are configured, run npx braith test (and --semantic where supported). These isolated fixtures check configured-outcome plumbing, not discovery accuracy. Exercise real discovery hooks locally with normal, failed and recovered interactions; inspect sanitised payloads without exposing secrets. Push records .braith/source-map.json, a local review map of outcome and discovery source files and hashes; it uploads only the manifest. Commit and review that map with the instrumentation. Do not present synthetic test incidents as proof of real coverage or model accuracy.
20572
20687
  10. Before deployment, run npx braith release <your-build-id> to bind this reviewed manifest to the exact release used in Braith.init({ release }). For applications that cannot supply release metadata, explicitly run npx braith release default after review. New pushes never silently activate. A rollback binds a new build ID to the previous manifest version; do not rewrite an existing release binding.
20573
20688
  11. When a monitored workflow changes in a later release, validate again. A changed mapped source file or config requires a new manifestVersion, review of the affected code-to-outcome mapping, and another push before release binding. This hash check is only a reminder: code outside mapped files may affect outcomes, so inspect the whole workflow. Keep the previous manifest available for attempts already in progress. Never redefine success to make a regression look healthy.
20574
20689
  `;
@@ -20576,6 +20691,14 @@ var exampleConfig = {
20576
20691
  schemaVersion: 1,
20577
20692
  manifestVersion: "local-v1",
20578
20693
  projectKey: "env:BRAITH_PROJECT_KEY",
20694
+ discovery: {
20695
+ enabled: true,
20696
+ mode: "review",
20697
+ application: "A subscription app where customers pay for access to reporting tools.",
20698
+ surfaces: { billing: "Choose a plan and pay for access", reports: "Use reports included in the selected plan" },
20699
+ actions: { upgrade: "Upgrade the account", open_report: "Open a report" },
20700
+ feedback: { upgraded: "The app reports paid access is active", upgrade_required: "The app asks the customer to upgrade for access" }
20701
+ },
20579
20702
  eventDefinitions: [
20580
20703
  {
20581
20704
  name: "checkout_started",
@@ -20639,6 +20762,10 @@ function writeNew(path, contents) {
20639
20762
  writeFileSync(path, contents, "utf8");
20640
20763
  return true;
20641
20764
  }
20765
+ function ensureCredentialIgnore(directory) {
20766
+ const path = join(directory, ".gitignore"), existing = existsSync(path) ? readFileSync(path, "utf8") : "";
20767
+ if (!existing.split(/\r?\n/).some((line) => line.trim() === CREDENTIALS_NAME)) writeFileSync(path, existing + (existing && !existing.endsWith("\n") ? "\n" : "") + CREDENTIALS_NAME + "\n", "utf8");
20768
+ }
20642
20769
  function initProject(root = process.cwd()) {
20643
20770
  const directory = join(root, ".braith");
20644
20771
  mkdirSync(directory, { recursive: true });
@@ -20680,6 +20807,10 @@ function buildSourceMap(root, manifest) {
20680
20807
  }));
20681
20808
  const intents = {};
20682
20809
  const files = {};
20810
+ const discovery = manifest.discovery ? sources.filter(
20811
+ (source) => /\b(discovery|observeWindow|observe|catalogue)\b/.test(source.contents) && /braith/i.test(source.contents)
20812
+ ).map((source) => source.path) : void 0;
20813
+ for (const path of discovery ?? []) files[path] = sha256(sources.find((source) => source.path === path).contents);
20683
20814
  for (const intent of manifest.intents) {
20684
20815
  const names = new Set([
20685
20816
  ...intent.entrySignals,
@@ -20700,6 +20831,7 @@ function buildSourceMap(root, manifest) {
20700
20831
  manifestVersion: manifest.manifestVersion,
20701
20832
  configHash: sha256(readFileSync(join(root, CONFIG_NAME), "utf8")),
20702
20833
  intents,
20834
+ ...discovery ? { discovery } : {},
20703
20835
  files
20704
20836
  };
20705
20837
  }
@@ -20770,6 +20902,7 @@ function validateProject(root = process.cwd()) {
20770
20902
  if (!/(createBrowserClient|createServerClient|Braith\.init)\s*\(/.test(source)) {
20771
20903
  messages.push({ level: "warning", message: "Braith SDK initialization was not found in source" });
20772
20904
  }
20905
+ if (manifest.discovery && !/\b(discovery|observeWindow)\b/.test(source)) messages.push({ level: "unable", message: "Discovery is enabled but no capture configuration or server observation reference was found. This scan does not prove coverage." });
20773
20906
  for (const event of manifest.eventDefinitions) {
20774
20907
  if (!source.includes(event.name)) {
20775
20908
  messages.push({ level: "unable", message: `${event.name}: no source reference was found; wrappers or generated code may hide it` });
@@ -20782,8 +20915,7 @@ async function enrollProject(token, root = process.cwd(), replace = false) {
20782
20915
  if (!/^br_enroll_[A-Za-z0-9_-]{32}$/.test(token)) throw new Error("Invalid enrollment token");
20783
20916
  const directory = join(root, ".braith");
20784
20917
  mkdirSync(directory, { recursive: true });
20785
- writeNew(join(directory, ".gitignore"), `${CREDENTIALS_NAME}
20786
- `);
20918
+ ensureCredentialIgnore(directory);
20787
20919
  const path = join(directory, CREDENTIALS_NAME);
20788
20920
  if (existsSync(path) && !replace) {
20789
20921
  throw new Error("Existing enrollment found. Use braith enroll <token> --replace to renew setup access without deleting credentials by hand");
@@ -20817,6 +20949,43 @@ async function enrollProject(token, root = process.cwd(), replace = false) {
20817
20949
  }
20818
20950
  return { projectId: credentials.projectId, expiresAt: credentials.expiresAt };
20819
20951
  }
20952
+ async function connectProject(root = process.cwd()) {
20953
+ const previous = localCredentials(root), apiUrl = (process.env.BRAITH_API_URL?.trim() || "https://app.braith.dev").replace(/\/$/, "");
20954
+ const origin = new URL(apiUrl);
20955
+ if (origin.protocol !== "https:" && !["127.0.0.1", "localhost"].includes(origin.hostname)) throw new Error("Braith connection requires HTTPS");
20956
+ const call = (body) => fetch(apiUrl + "/api/v1/setup", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body), signal: AbortSignal.timeout(15e3), redirect: "error" });
20957
+ const start = await call({ action: "start" });
20958
+ if (!start.ok) throw new Error("Braith connection is unavailable. Try again shortly.");
20959
+ const pending = await start.json();
20960
+ if (!/^br_device_[A-Za-z0-9_-]{32}$/.test(pending.deviceToken) || !Number.isFinite(pending.expiresIn) || pending.expiresIn <= 0 || pending.expiresIn > 900 || new URL(pending.verificationUrl).origin !== origin.origin || !Number.isFinite(pending.interval) || pending.interval < 5 || pending.interval > 30) throw new Error("Invalid Braith connection response");
20961
+ console.log("Approve this connection in Braith: " + pending.verificationUrl + "\nCheck that the code matches: " + pending.userCode + "\nWaiting for your approval\u2026");
20962
+ const deadline = Date.now() + pending.expiresIn * 1e3;
20963
+ while (Date.now() < deadline) {
20964
+ await new Promise((resolve2) => setTimeout(resolve2, pending.interval * 1e3));
20965
+ if (Date.now() >= deadline) break;
20966
+ const response = await call({ action: "poll", deviceToken: pending.deviceToken });
20967
+ if (response.status === 202) continue;
20968
+ if (response.status === 429) {
20969
+ await new Promise((resolve2) => setTimeout(resolve2, 3e4));
20970
+ continue;
20971
+ }
20972
+ if (!response.ok) throw new Error("Connection cancelled or expired. Run braith connect again.");
20973
+ const value = await response.json();
20974
+ if (![value.projectId, value.browserKey, value.serverKey, value.managementKey, value.expiresAt].every((item) => typeof item === "string" && item.length > 0) || !Number.isFinite(Date.parse(value.expiresAt)) || Date.parse(value.expiresAt) <= Date.now()) throw new Error("Connection returned incomplete credentials");
20975
+ if (previous && previous.projectId !== value.projectId) throw new Error("This connection is for a different app. Existing credentials were preserved.");
20976
+ initProject(root);
20977
+ ensureCredentialIgnore(join(root, ".braith"));
20978
+ const path = join(root, ".braith", CREDENTIALS_NAME), temporary = path + "." + randomUUID() + ".tmp";
20979
+ try {
20980
+ writeFileSync(temporary, JSON.stringify({ ...value, apiUrl }, null, 2) + "\n", { encoding: "utf8", mode: 384, flag: "wx" });
20981
+ renameSync(temporary, path);
20982
+ } finally {
20983
+ if (existsSync(temporary)) rmSync(temporary);
20984
+ }
20985
+ return { projectId: value.projectId, expiresAt: value.expiresAt };
20986
+ }
20987
+ throw new Error("Connection expired. Run braith connect again.");
20988
+ }
20820
20989
  function printValidation(result) {
20821
20990
  for (const message of result.messages) console.log(`${message.level.toUpperCase()}: ${message.message}`);
20822
20991
  console.log(result.valid ? "Braith configuration is valid." : "Braith configuration is invalid.");
@@ -20928,6 +21097,17 @@ async function testProject(root = process.cwd(), mode) {
20928
21097
  }
20929
21098
  async function run(argv = process.argv.slice(2), root = process.cwd()) {
20930
21099
  const command = argv[0];
21100
+ if (command === "connect") {
21101
+ try {
21102
+ if (argv.length !== 1) throw new Error("Usage: braith connect");
21103
+ await connectProject(root);
21104
+ console.log("Connected. Follow .braith/SETUP.md to integrate this app.");
21105
+ return 0;
21106
+ } catch (error62) {
21107
+ console.error(error62 instanceof Error ? error62.message : "Connection failed");
21108
+ return 1;
21109
+ }
21110
+ }
20931
21111
  if (command === "init") {
20932
21112
  const result = initProject(root);
20933
21113
  for (const path of result.created) console.log(`Created ${path}`);
@@ -20985,13 +21165,14 @@ async function run(argv = process.argv.slice(2), root = process.cwd()) {
20985
21165
  return 1;
20986
21166
  }
20987
21167
  }
20988
- console.error("Usage: braith <init|enroll|validate|push|release|test>");
21168
+ console.error("Usage: braith <connect|init|enroll|validate|push|release|test>");
20989
21169
  return 1;
20990
21170
  }
20991
21171
  var invokedPath = process.argv[1] ? pathToFileURL(resolve(process.argv[1])).href : "";
20992
21172
  if (invokedPath === import.meta.url) process.exitCode = await run();
20993
21173
  export {
20994
21174
  bindRelease,
21175
+ connectProject,
20995
21176
  enrollProject,
20996
21177
  initProject,
20997
21178
  pushProject,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braith",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Braith coding-agent setup and outcome manifest CLI",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": { "access": "public" },