@brass-build/cli 0.1.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 (70) hide show
  1. package/AGENTS.md +170 -0
  2. package/CHANGELOG.md +12 -0
  3. package/LICENSE +21 -0
  4. package/README.md +172 -0
  5. package/dist/api.d.ts +73 -0
  6. package/dist/api.d.ts.map +1 -0
  7. package/dist/api.js +97 -0
  8. package/dist/api.js.map +1 -0
  9. package/dist/args.d.ts +10 -0
  10. package/dist/args.d.ts.map +1 -0
  11. package/dist/args.js +94 -0
  12. package/dist/args.js.map +1 -0
  13. package/dist/auth.d.ts +5 -0
  14. package/dist/auth.d.ts.map +1 -0
  15. package/dist/auth.js +12 -0
  16. package/dist/auth.js.map +1 -0
  17. package/dist/bin/brass.d.ts +3 -0
  18. package/dist/bin/brass.d.ts.map +1 -0
  19. package/dist/bin/brass.js +11 -0
  20. package/dist/bin/brass.js.map +1 -0
  21. package/dist/cli.d.ts +4 -0
  22. package/dist/cli.d.ts.map +1 -0
  23. package/dist/cli.js +364 -0
  24. package/dist/cli.js.map +1 -0
  25. package/dist/commands.d.ts +94 -0
  26. package/dist/commands.d.ts.map +1 -0
  27. package/dist/commands.js +559 -0
  28. package/dist/commands.js.map +1 -0
  29. package/dist/config.d.ts +40 -0
  30. package/dist/config.d.ts.map +1 -0
  31. package/dist/config.js +76 -0
  32. package/dist/config.js.map +1 -0
  33. package/dist/log.d.ts +9 -0
  34. package/dist/log.d.ts.map +1 -0
  35. package/dist/log.js +32 -0
  36. package/dist/log.js.map +1 -0
  37. package/dist/login.d.ts +21 -0
  38. package/dist/login.d.ts.map +1 -0
  39. package/dist/login.js +158 -0
  40. package/dist/login.js.map +1 -0
  41. package/dist/project.d.ts +32 -0
  42. package/dist/project.d.ts.map +1 -0
  43. package/dist/project.js +129 -0
  44. package/dist/project.js.map +1 -0
  45. package/dist/session.d.ts +47 -0
  46. package/dist/session.d.ts.map +1 -0
  47. package/dist/session.js +224 -0
  48. package/dist/session.js.map +1 -0
  49. package/dist/store.d.ts +17 -0
  50. package/dist/store.d.ts.map +1 -0
  51. package/dist/store.js +90 -0
  52. package/dist/store.js.map +1 -0
  53. package/dist/version.d.ts +3 -0
  54. package/dist/version.d.ts.map +1 -0
  55. package/dist/version.js +8 -0
  56. package/dist/version.js.map +1 -0
  57. package/package.json +42 -0
  58. package/src/api.ts +195 -0
  59. package/src/args.ts +107 -0
  60. package/src/auth.ts +16 -0
  61. package/src/bin/brass.ts +11 -0
  62. package/src/cli.ts +422 -0
  63. package/src/commands.ts +864 -0
  64. package/src/config.ts +132 -0
  65. package/src/log.ts +41 -0
  66. package/src/login.ts +211 -0
  67. package/src/project.ts +176 -0
  68. package/src/session.ts +319 -0
  69. package/src/store.ts +123 -0
  70. package/src/version.ts +8 -0
package/AGENTS.md ADDED
@@ -0,0 +1,170 @@
1
+ # Instructions for AI coding agents
2
+
3
+ You are likely an AI coding agent publishing a Brass app you built against
4
+ `@brass-build/client`. This tool is how you ship it and how you pull a
5
+ schema to build against. The [README](./README.md) is the full reference;
6
+ this file is the short path for you.
7
+
8
+ ## Follow your organization's coding instructions first
9
+
10
+ Before you start building, fetch the organization's own agentic-coding
11
+ instructions and follow them: the conventions it wants every app built on
12
+ Brass to follow (its `AGENTS.md` / `CLAUDE.md`). Run the pull rather than
13
+ deciding up front whether the org has any; nothing loads them for you, and an
14
+ empty result (the org set none) is something you learn by running it, not by
15
+ assuming it. Treat what it returns as an instruction file committed to the
16
+ repo.
17
+
18
+ ```sh
19
+ npx @brass-build/cli agents pull --out AGENTS.md
20
+ ```
21
+
22
+ This writes the current org instructions to the file. Read it and apply it.
23
+ Keep it current one of two ways:
24
+
25
+ - **Always fetch (freshest):** run the pull at the start of each session and
26
+ re-read the file, so you are always on the latest instructions. Pull to a
27
+ gitignored path if you do not want to commit it, or `--stdout` to read the
28
+ body inline without writing a file at all (status stays on stderr, so
29
+ stdout is only the instructions).
30
+ - **Cache:** commit the pulled file and refresh it periodically. Add a
31
+ `CLAUDE.md` whose entire contents are the single line `@AGENTS.md` so
32
+ Claude Code picks it up too (Claude Code auto-loads `CLAUDE.md`, not
33
+ `AGENTS.md`); the command prints this reminder after a pull.
34
+
35
+ The pull needs a signed-in session (`brass login`); a service token is not an
36
+ organization member and cannot read the instructions. If you are not signed
37
+ in, run the sign-in yourself rather than skipping: `brass login --start`,
38
+ relay the printed URL and code to the human, then `brass login --check --wait`
39
+ until it reports approved (it is the same credential you will use to publish).
40
+ [Signing in](#signing-in) covers the whole flow, including what to do while
41
+ you wait for the approval.
42
+ `--org <organizationId>` is only needed when the human belongs to more than
43
+ one organization. An empty result means the organization has set no
44
+ instructions, and there is nothing to follow.
45
+
46
+ ## Signing in
47
+
48
+ You cannot approve the sign-in, and the human approving it is not watching
49
+ your terminal. Start it, relay the code, then wait for the approval.
50
+
51
+ ```sh
52
+ npx @brass-build/cli login --start # prints the approval URL + code
53
+ npx @brass-build/cli login --check --wait # polls until they approve
54
+ ```
55
+
56
+ `--start` prints an approval URL and a short code and exits. Give both to the
57
+ human in your next message, then run `--check --wait`. It polls for up to two
58
+ minutes (`--wait <seconds>` for a different bound) and stores the session the
59
+ moment they approve. A code that lapses is replaced and the new one printed,
60
+ so the sign-in outlives any single code and a session started early is still
61
+ good at publish time.
62
+
63
+ The session gates your organization's coding conventions, and those shape the
64
+ code you are about to write, so wait for it before you build rather than
65
+ building twice. If you do have work that does not depend on them, the wait
66
+ costs nothing to repeat: a bound reached with the code still approvable
67
+ reports `pending`, and the next `--check --wait` picks the same sign-in back
68
+ up where it left off.
69
+
70
+ Read the `state` in its `--json` result:
71
+
72
+ - `approved`: the session is stored, and every later command uses it.
73
+ - `pending`: the code you relayed is still good. Run the check again.
74
+ - `renewed`: the code changed. Relay the new one from the same output.
75
+ - `denied`: the human refused. Ask them why before starting another.
76
+
77
+ Run `brass login --start` again only when there is nothing in flight; it
78
+ resumes the sign-in already waiting rather than issuing a second code, so a
79
+ half-finished approval still completes. `brass status` reports an in-flight
80
+ sign-in and how long it has left, so you never have to guess.
81
+
82
+ ## Publishing an app you built
83
+
84
+ Start with `brass status`, and act on the `Next:` line it prints:
85
+
86
+ ```sh
87
+ npx @brass-build/cli status
88
+ ```
89
+
90
+ It reports the credential and app state for this environment and names the
91
+ one command to run next (obtain a credential, create the app on a first
92
+ publish, deploy, or open the live URL). Run it, do what it says, then run it
93
+ again. The steps below are what it walks you through:
94
+
95
+ 1. Build the app to a static bundle (its `brass-app.json` capability
96
+ manifest must be in the output).
97
+ 2. Make sure a credential is available: `BRASS_SERVICE_TOKEN` in the
98
+ environment (or `--token`) for CI, or a `brass login` session for local
99
+ development. When `brass status` reports none, run the sign-in yourself,
100
+ as [Signing in](#signing-in) describes.
101
+ Starting it, relaying the URL, and checking it through to approved is your
102
+ step, not a handoff you stop at. (Or the human mints a service token in
103
+ the dashboard: org Settings, then Service tokens.)
104
+ 3. Publish:
105
+
106
+ ```sh
107
+ npx @brass-build/cli publish ./dist
108
+ ```
109
+
110
+ The first run creates the app and writes `.brass/project.json`; commit
111
+ that so re-runs target the same app. A service token creates the app in
112
+ its own organization, so no `--org` is needed. If the pipeline does not
113
+ persist `.brass/project.json` between runs, set a stable `client_token`
114
+ (a `"client_token"` in `brass-app.json`, or `--client-token`) so the
115
+ first create is idempotent and re-runs resolve the same app.
116
+
117
+ Run with `--json` to get the result (`app_id`, `url`) as JSON on stdout for
118
+ your own parsing. A non-zero exit means the publish failed; the reason is on
119
+ stderr.
120
+
121
+ ## Pulling a schema to build against
122
+
123
+ To interoperate with documents another app or importer produces, do not
124
+ hand-write the schema. Open one of its documents and pull the real body:
125
+
126
+ ```sh
127
+ npx @brass-build/cli schema pull --doc <docId> --out brass-app.json
128
+ ```
129
+
130
+ This writes the schema verbatim into your manifest. Build your types against
131
+ that copy; do not re-approximate it from memory (a mismatched `required`
132
+ shape makes the platform read your app as a different shape). This is a
133
+ development-time step that needs a signed-in session (`brass login`), not a
134
+ service token; if you only have `BRASS_SERVICE_TOKEN`, ask the human to run
135
+ `brass login` or to copy the schema from the document's dashboard page.
136
+
137
+ ## Verifying
138
+
139
+ `npx @brass-build/cli status` is the check to run before and after a publish:
140
+ it confirms the credential authenticates and reports whether the app is
141
+ deployed, with the next step to take. `npx @brass-build/cli whoami` is the
142
+ narrower check when you only need to confirm the credential works. After a
143
+ publish, open the reported URL to confirm the app loads.
144
+
145
+ ## When a command fails
146
+
147
+ Read what the error says before drawing a conclusion about the machine you
148
+ are on. The CLI reports the state it observed, and the states have different
149
+ fixes:
150
+
151
+ - **A 401 or 403** means the credential is expired, missing, or not allowed.
152
+ A session expires, so `brass login` is the fix; a service token that is
153
+ rejected needs a valid `BRASS_SERVICE_TOKEN`. A fresh checkout or a fresh
154
+ container has no session at all, which is the ordinary starting state, not
155
+ a broken one.
156
+ - **"Network error reaching <url>"** is the only error meaning the request
157
+ never completed. It names the host it tried.
158
+ - **Any other status** is a fault on the server side. The credential is
159
+ still good and retrying is reasonable.
160
+
161
+ An HTTP status coming back at all proves the host answered you. So confirm
162
+ reachability directly before reporting that Brass is unreachable:
163
+
164
+ ```sh
165
+ curl -sS -o /dev/null -w '%{http_code}\n' https://api.brass.build/health
166
+ ```
167
+
168
+ `/health` requires a credential, so an unauthenticated `401` here is a
169
+ reachable API. Run `brass status` for the full credential and app state
170
+ along with the next command to run.
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@brass-build/cli` are recorded here. The format
4
+ follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
5
+ package follows [semantic versioning](https://semver.org/spec/v2.0.0.html).
6
+ While the version is below `1.0.0`, minor releases may introduce breaking
7
+ changes.
8
+
9
+ ## 0.1.0
10
+
11
+ Initial public release. The `brass` command-line tool publishes apps and
12
+ pulls schemas from a terminal or CI. Ships as an ESM-only package.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Brass Software LLC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,172 @@
1
+ # @brass-build/cli
2
+
3
+ From a terminal or a CI pipeline, the `brass` command publishes a Brass
4
+ app's built bundle and pulls document schemas. It wraps the same data API the
5
+ dashboard uses, authenticated by a token instead of a browser session, so
6
+ the whole publish workflow runs unattended.
7
+
8
+ This package tracks the platform's `0.1.x` line.
9
+
10
+ ## Install
11
+
12
+ No install step. Run it with `npx`:
13
+
14
+ ```sh
15
+ npx @brass-build/cli publish ./dist
16
+ ```
17
+
18
+ ## Authenticate
19
+
20
+ Two kinds of credential, for two audiences:
21
+
22
+ - A **signed-in session** from `brass login` is the developer credential.
23
+ It opens your browser, you approve once, and the CLI stores the session
24
+ under `~/.config/brass/`. It authorizes everything, including `schema pull`.
25
+ - A **service token** (mint one in the dashboard: org Settings, then Service
26
+ tokens) is the CI credential, supplied as `BRASS_SERVICE_TOKEN`. It
27
+ authorizes `publish` and `whoami`. It is an org-scoped machine identity
28
+ and, by design, cannot fetch schemas.
29
+
30
+ ```sh
31
+ npx @brass-build/cli login # developer: sign in with your browser
32
+ export BRASS_SERVICE_TOKEN=brass_sk_... # CI: a pipeline secret
33
+ ```
34
+
35
+ `login` opens your browser to an approval page showing a short code, prints
36
+ the same URL and code to the terminal, and completes on its own once you
37
+ approve. Confirm the code on the page matches the one in your terminal, then
38
+ approve. On a machine with no browser (a remote box, an SSH session), open the
39
+ printed URL from any other device.
40
+
41
+ For automation that cannot hold a command open while you approve (an AI
42
+ agent driving the sign-in), the flow splits in two: `brass login --start`
43
+ prints the approval URL and code and exits immediately, and
44
+ `brass login --check` checks that sign-in, storing the session once you have
45
+ approved. Add `--wait` to poll until you approve, for two minutes by default
46
+ or `--wait <seconds>` for a different bound; a code that lapses inside the
47
+ wait is replaced and the new one printed, so a sign-in started long before it
48
+ is needed still completes. Reaching the bound with the code still good is
49
+ reported as `pending` and exits 0, so the next check picks the same sign-in
50
+ back up. A second `--start` resumes the sign-in already waiting instead of
51
+ issuing a second code, and `--new` overrides that when you have lost the
52
+ first. Both phases honor `--json` for a machine-readable result on stdout.
53
+
54
+ The CLI picks a credential most-explicit first: `--token <token>`, then
55
+ `BRASS_SERVICE_TOKEN`, then the stored `brass login` session. `brass logout`
56
+ forgets the stored session.
57
+
58
+ ```sh
59
+ export BRASS_SERVICE_TOKEN=brass_sk_...
60
+ npx @brass-build/cli whoami
61
+ ```
62
+
63
+ ## Status
64
+
65
+ `status` reports the credential and app state for the target environment and
66
+ the one command to run next:
67
+
68
+ ```sh
69
+ npx @brass-build/cli status
70
+ ```
71
+
72
+ It confirms whether your credential authenticates, whether an app id is
73
+ resolved for this directory, and whether that app is deployed, then prints a
74
+ `Next:` line naming the exact next step (sign in, publish to create the app,
75
+ deploy, or open the live URL). When a `brass login --start` sign-in is waiting
76
+ for approval, it reports the code, the approval URL, and how long the code has
77
+ left. Run it before a publish to see what will
78
+ happen, and after one to confirm the app is live. `whoami` is the narrower
79
+ check when you only need to know the credential works. Add `--json` for the
80
+ machine-readable result on stdout.
81
+
82
+ ## Publish
83
+
84
+ `publish` deploys a built static bundle to the app's Brass hosting
85
+ (`https://<slug>.onbrass.app`):
86
+
87
+ ```sh
88
+ npx @brass-build/cli publish ./dist
89
+ ```
90
+
91
+ On the first publish with no app yet, it creates one (using `--name`, or the
92
+ `name` in `brass-app.json`) and records the new app id in `.brass/project.json`
93
+ so later publishes target the same app. Commit that file, or pin the app
94
+ explicitly with `--app <appId>` or `BRASS_APP_ID` for a stateless pipeline.
95
+
96
+ For a pipeline that stands the app up from scratch and does not persist
97
+ `.brass/project.json` between runs, set a stable `client_token` (a
98
+ `"client_token"` in `brass-app.json`, or `--client-token`). Repeated first
99
+ publishes then resolve the same app instead of creating a duplicate each run.
100
+ A service token creates the app in its own organization, so no `--org` is
101
+ needed.
102
+
103
+ The command enables hosting if needed, uploads the bundle, waits for it to go
104
+ live, and reports the URL. The platform reads your app's capabilities
105
+ (`opens` / `creates` / `schema`) from the `/.well-known/brass-app.json` you
106
+ serve, so keep that manifest in the bundle.
107
+
108
+ Flags: `--app`, `--name`, `--org` (organization to own a newly created app;
109
+ defaults to a service token's own org), `--client-token` (stable idempotency
110
+ key for a first create), `--slug` (preferred subdomain), `--manifest`
111
+ (default `brass-app.json`).
112
+
113
+ ## Pull a schema
114
+
115
+ To build against a shape another app or importer produces, open one of its
116
+ documents and copy its schema into your manifest:
117
+
118
+ ```sh
119
+ npx @brass-build/cli schema pull --doc <docId> --out brass-app.json
120
+ ```
121
+
122
+ This writes the document's schema body verbatim into the manifest's `schema`
123
+ field, preserving everything else. That is the same copy-verbatim step the
124
+ SDK's schema guide describes, done for you. It is a development-time action
125
+ that needs a signed-in session (`brass login`); a service token cannot fetch
126
+ schemas.
127
+
128
+ ## Pull your organization's agent instructions
129
+
130
+ If your organization manages a shared set of agentic-coding instructions (its
131
+ AGENTS.md / CLAUDE.md), pull the current body into your repo:
132
+
133
+ ```sh
134
+ npx @brass-build/cli agents pull --out AGENTS.md
135
+ ```
136
+
137
+ The instructions are stored once at the organization level (admins edit them
138
+ on the dashboard Settings tab) so every developer's coding agent works from
139
+ one source of truth. The pull writes the body verbatim, so it round-trips
140
+ byte-for-byte with what the dashboard stored. `--org <organizationId>` is
141
+ optional when you belong to a single organization; pass it when you belong to
142
+ more than one. This needs a signed-in session (`brass login`); a service token
143
+ is not an organization member, so it cannot read the instructions.
144
+
145
+ Pass `--stdout` instead of `--out` to print the instructions to stdout
146
+ without writing a file (status goes to stderr, so stdout carries only the
147
+ body). This is the always-fetch path: a coding agent reads the current
148
+ instructions inline each session and caches nothing.
149
+
150
+ The default `--out` is `AGENTS.md`, the cross-agent convention that Cursor,
151
+ Codex, and others read. **Claude Code reads `CLAUDE.md`, not `AGENTS.md`**, so
152
+ to use these instructions there either pull straight to it
153
+ (`agents pull --out CLAUDE.md`) or add a `CLAUDE.md` that imports the pulled
154
+ file with a single line, `@AGENTS.md`. The command prints this reminder
155
+ whenever you pull to any file other than `CLAUDE.md`.
156
+
157
+ ## CI example
158
+
159
+ ```yaml
160
+ - run: npm ci && npm run build
161
+ - run: npx @brass-build/cli publish ./dist
162
+ env:
163
+ BRASS_SERVICE_TOKEN: ${{ secrets.BRASS_SERVICE_TOKEN }}
164
+ ```
165
+
166
+ The service token creates the app in its own organization on the first run.
167
+ Give `brass-app.json` a stable `"client_token"` so that first create is
168
+ idempotent (repeated runs resolve the same app rather than duplicating it),
169
+ or commit `.brass/project.json` to pin the app id.
170
+
171
+ Add `--json` to any command to emit the machine-readable result on stdout
172
+ (human status stays on stderr).
package/dist/api.d.ts ADDED
@@ -0,0 +1,73 @@
1
+ import type { AuthProvider } from './auth.js';
2
+ export interface BrassSchemaManifest {
3
+ family: string;
4
+ streams: Record<string, Record<string, unknown>>;
5
+ }
6
+ export type AppVisibility = 'private' | 'invitee_visible' | 'public';
7
+ export interface AppDetail {
8
+ app_id: string;
9
+ name: string;
10
+ owner_organization_id?: string;
11
+ visibility?: AppVisibility;
12
+ }
13
+ export interface HostingStatus {
14
+ enabled: boolean;
15
+ slug: string | null;
16
+ url: string | null;
17
+ deployed: boolean;
18
+ active_version: string | null;
19
+ require_access?: boolean;
20
+ }
21
+ export interface HostingUploadUrl {
22
+ upload_url: string;
23
+ version_id: string;
24
+ expires_in: number;
25
+ }
26
+ export type HostingVersionStatus = 'pending' | 'ready' | 'failed';
27
+ export interface HostingVersion {
28
+ version_id: string;
29
+ status: HostingVersionStatus;
30
+ failure_reason?: string;
31
+ active: boolean;
32
+ content_hash?: string;
33
+ }
34
+ export interface DocumentStreams {
35
+ streams: {
36
+ name: string;
37
+ schema?: Record<string, unknown>;
38
+ }[];
39
+ predicted?: true;
40
+ }
41
+ export interface DocumentTypeSummary {
42
+ schema_type?: string;
43
+ }
44
+ export interface AgentInstructionsResponse {
45
+ content: string;
46
+ updated_at?: string;
47
+ updated_by?: string;
48
+ }
49
+ export interface OrganizationSummary {
50
+ organization_id: string;
51
+ name: string;
52
+ }
53
+ export interface RefreshCapabilitiesResponse {
54
+ app_id: string;
55
+ manifest_origin: string;
56
+ opens: string[];
57
+ warnings?: string[];
58
+ }
59
+ export declare class BrassApiError extends Error {
60
+ readonly status: number;
61
+ constructor(status: number, message: string);
62
+ }
63
+ export declare class BrassApi {
64
+ private readonly apiBaseUrl;
65
+ private readonly auth;
66
+ constructor(apiBaseUrl: string, auth: AuthProvider);
67
+ get<T>(path: string): Promise<T>;
68
+ post<T>(path: string, body?: unknown): Promise<T>;
69
+ patch<T>(path: string, body?: unknown): Promise<T>;
70
+ private request;
71
+ }
72
+ export declare function putPresigned(url: string, bytes: Uint8Array, contentType: string): Promise<void>;
73
+ //# sourceMappingURL=api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/api.js ADDED
@@ -0,0 +1,97 @@
1
+ // The HTTP client the commands share: a bearer-authenticated wrapper over
2
+ // the Brass data API, plus the narrow wire types the CLI reads. Wire shapes
3
+ // are declared here (not imported from `@brass-build/client`) so the CLI
4
+ // stays a standalone package with no browser-SDK dependency; the shapes it
5
+ // depends on are small and stable (the `/apps` + hosting surface).
6
+ import { CLI_CLIENT_ID } from './version.js';
7
+ // One error type for every API failure, carrying the HTTP status and the
8
+ // server-authored message so the CLI can render it verbatim (the API returns
9
+ // `{ error: string }` on the 4xx/5xx ladder).
10
+ export class BrassApiError extends Error {
11
+ status;
12
+ constructor(status, message) {
13
+ super(message);
14
+ this.name = 'BrassApiError';
15
+ this.status = status;
16
+ }
17
+ }
18
+ export class BrassApi {
19
+ apiBaseUrl;
20
+ auth;
21
+ constructor(apiBaseUrl, auth) {
22
+ this.apiBaseUrl = apiBaseUrl;
23
+ this.auth = auth;
24
+ }
25
+ get(path) {
26
+ return this.request('GET', path);
27
+ }
28
+ post(path, body) {
29
+ return this.request('POST', path, body);
30
+ }
31
+ patch(path, body) {
32
+ return this.request('PATCH', path, body);
33
+ }
34
+ async request(method, path, body) {
35
+ const headers = {
36
+ ...(await this.auth.headers()),
37
+ // Version telemetry; the api Lambda logs it so pinned CI copies
38
+ // stay visible in the deployed-version distribution.
39
+ 'x-brass-client': CLI_CLIENT_ID,
40
+ };
41
+ let init = { method, headers };
42
+ if (body !== undefined) {
43
+ headers['content-type'] = 'application/json';
44
+ init = { ...init, body: JSON.stringify(body) };
45
+ }
46
+ let response;
47
+ try {
48
+ response = await fetch(`${this.apiBaseUrl}${path}`, init);
49
+ }
50
+ catch (cause) {
51
+ throw new BrassApiError(0, `Network error reaching ${this.apiBaseUrl}: ${String(cause)}`);
52
+ }
53
+ if (!response.ok) {
54
+ throw new BrassApiError(response.status, await errorMessage(response));
55
+ }
56
+ if (response.status === 204)
57
+ return undefined;
58
+ return (await response.json());
59
+ }
60
+ }
61
+ // Upload bytes to a presigned S3 URL. Deliberately carries NO Brass headers
62
+ // and no bearer: the presigned signature pins an exact header set, and a
63
+ // stray `authorization` header would break it (the same rule the SDK's
64
+ // `api-headers` module enforces on the document path).
65
+ export async function putPresigned(url, bytes, contentType) {
66
+ let response;
67
+ try {
68
+ response = await fetch(url, {
69
+ method: 'PUT',
70
+ headers: { 'content-type': contentType },
71
+ // fflate allocates a plain ArrayBuffer, so narrowing the generic off
72
+ // `ArrayBufferLike` is sound and satisfies fetch's `BufferSource`
73
+ // (which pins `ArrayBufferView<ArrayBuffer>`) across lib configs.
74
+ body: bytes,
75
+ });
76
+ }
77
+ catch (cause) {
78
+ throw new BrassApiError(0, `Network error uploading the bundle: ${String(cause)}`);
79
+ }
80
+ if (!response.ok) {
81
+ throw new BrassApiError(response.status, `Bundle upload failed (${response.status})`);
82
+ }
83
+ }
84
+ // Pull the server's `{ error }` message off a failed response, falling back
85
+ // to the bare status when the body is not the expected shape.
86
+ async function errorMessage(response) {
87
+ try {
88
+ const body = (await response.json());
89
+ if (typeof body.error === 'string' && body.error !== '')
90
+ return body.error;
91
+ }
92
+ catch {
93
+ // Non-JSON body (an edge / gateway error); fall through to the status.
94
+ }
95
+ return `Request failed with status ${response.status}`;
96
+ }
97
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
package/dist/args.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ export interface ParsedArgs {
2
+ positionals: string[];
3
+ flags: Record<string, string | true>;
4
+ }
5
+ export declare function unknownFlags(parsed: ParsedArgs): string[];
6
+ export declare function valuelessFlags(parsed: ParsedArgs, names: readonly string[]): string[];
7
+ export declare function parseArgs(argv: readonly string[]): ParsedArgs;
8
+ export declare function stringFlag(parsed: ParsedArgs, name: string): string | undefined;
9
+ export declare function boolFlag(parsed: ParsedArgs, name: string): boolean;
10
+ //# sourceMappingURL=args.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../src/args.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;IAGtB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;CACtC;AAyCD,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EAAE,CAEzD;AAOD,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAErF;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CA6B7D;AAID,wBAAgB,UAAU,CACxB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,GACX,MAAM,GAAG,SAAS,CAGpB;AAED,wBAAgB,QAAQ,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAElE"}
package/dist/args.js ADDED
@@ -0,0 +1,94 @@
1
+ // A minimal argv parser: no dependency, and small enough to unit-test the
2
+ // exact precedence the commands rely on. Supports `--flag value`,
3
+ // `--flag=value`, and boolean `--flag`; everything else is a positional.
4
+ // Flags that never take a value, so `brass publish --yes ./dist` parses
5
+ // `./dist` as a positional rather than the value of `--yes`.
6
+ const BOOLEAN_FLAGS = new Set([
7
+ 'json',
8
+ 'yes',
9
+ 'help',
10
+ 'version',
11
+ 'stdout',
12
+ 'start',
13
+ 'check',
14
+ 'new',
15
+ ]);
16
+ // Every flag any command reads. A flag outside this set is rejected rather
17
+ // than ignored, because ignoring one silently retargets the invocation: the
18
+ // origin flags default to production, so a misspelled `--api-ur1` publishes an
19
+ // app, or mints a sign-in, against the real platform while naming another
20
+ // stack on the command line.
21
+ const KNOWN_FLAGS = new Set([
22
+ ...BOOLEAN_FLAGS,
23
+ 'api-url',
24
+ 'app',
25
+ 'auth-url',
26
+ 'client-token',
27
+ 'dashboard-url',
28
+ 'doc',
29
+ 'gate',
30
+ 'manifest',
31
+ 'name',
32
+ 'org',
33
+ 'out',
34
+ 'slug',
35
+ 'token',
36
+ 'visibility',
37
+ 'wait',
38
+ ]);
39
+ // The flag names this CLI does not know, in the order given, for an error
40
+ // naming all of them rather than one per run.
41
+ export function unknownFlags(parsed) {
42
+ return Object.keys(parsed.flags).filter((name) => !KNOWN_FLAGS.has(name));
43
+ }
44
+ // Which of `names` were given without a value (`--api-url --json`, or
45
+ // `--api-url` last on the line). The parser stores those as `true` and
46
+ // `stringFlag` reads that as absent, so an origin flag in this state resolves
47
+ // the production default while the command line names another stack. That is
48
+ // the same silent retarget `unknownFlags` catches for a misspelled name.
49
+ export function valuelessFlags(parsed, names) {
50
+ return names.filter((name) => parsed.flags[name] === true);
51
+ }
52
+ export function parseArgs(argv) {
53
+ const positionals = [];
54
+ const flags = {};
55
+ for (let i = 0; i < argv.length; i++) {
56
+ const arg = argv[i];
57
+ if (arg === undefined)
58
+ continue;
59
+ if (arg.startsWith('--')) {
60
+ const body = arg.slice(2);
61
+ const eq = body.indexOf('=');
62
+ if (eq !== -1) {
63
+ flags[body.slice(0, eq)] = body.slice(eq + 1);
64
+ continue;
65
+ }
66
+ if (BOOLEAN_FLAGS.has(body)) {
67
+ flags[body] = true;
68
+ continue;
69
+ }
70
+ const next = argv[i + 1];
71
+ if (next !== undefined && !next.startsWith('--')) {
72
+ flags[body] = next;
73
+ i++;
74
+ }
75
+ else {
76
+ flags[body] = true;
77
+ }
78
+ }
79
+ else {
80
+ positionals.push(arg);
81
+ }
82
+ }
83
+ return { positionals, flags };
84
+ }
85
+ // Read a flag expected to carry a string value; a bare boolean flag (no
86
+ // value) is treated as absent so `--doc` alone doesn't resolve to `true`.
87
+ export function stringFlag(parsed, name) {
88
+ const v = parsed.flags[name];
89
+ return typeof v === 'string' ? v : undefined;
90
+ }
91
+ export function boolFlag(parsed, name) {
92
+ return parsed.flags[name] === true || typeof parsed.flags[name] === 'string';
93
+ }
94
+ //# sourceMappingURL=args.js.map