@banatie/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.
- package/LICENSE +21 -0
- package/README.md +54 -0
- package/dist/apiClient.d.ts +102 -0
- package/dist/apiClient.d.ts.map +1 -0
- package/dist/apiClient.js +83 -0
- package/dist/baseUrl.d.ts +2 -0
- package/dist/baseUrl.d.ts.map +1 -0
- package/dist/baseUrl.js +7 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +15 -0
- package/dist/commands/connectDirect.d.ts +3 -0
- package/dist/commands/connectDirect.d.ts.map +1 -0
- package/dist/commands/connectDirect.js +34 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +40 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +21 -0
- package/dist/commands/projectConnect.d.ts +3 -0
- package/dist/commands/projectConnect.d.ts.map +1 -0
- package/dist/commands/projectConnect.js +66 -0
- package/dist/commands/projectDelete.d.ts +7 -0
- package/dist/commands/projectDelete.d.ts.map +1 -0
- package/dist/commands/projectDelete.js +99 -0
- package/dist/commands/projectList.d.ts +3 -0
- package/dist/commands/projectList.d.ts.map +1 -0
- package/dist/commands/projectList.js +54 -0
- package/dist/commands/projectNew.d.ts +3 -0
- package/dist/commands/projectNew.d.ts.map +1 -0
- package/dist/commands/projectNew.js +61 -0
- package/dist/commands/revoke.d.ts +3 -0
- package/dist/commands/revoke.d.ts.map +1 -0
- package/dist/commands/revoke.js +41 -0
- package/dist/commands/rotate.d.ts +3 -0
- package/dist/commands/rotate.d.ts.map +1 -0
- package/dist/commands/rotate.js +36 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +30 -0
- package/dist/commands/types.d.ts +5 -0
- package/dist/commands/types.d.ts.map +1 -0
- package/dist/commands/types.js +2 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +109 -0
- package/dist/confirmPrompt.d.ts +2 -0
- package/dist/confirmPrompt.d.ts.map +1 -0
- package/dist/confirmPrompt.js +20 -0
- package/dist/dispatch.d.ts +4 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +101 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/preview.d.ts +2 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +9 -0
- package/dist/projectConfig.d.ts +12 -0
- package/dist/projectConfig.d.ts.map +1 -0
- package/dist/projectConfig.js +40 -0
- package/package.json +35 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Banatie
|
|
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,54 @@
|
|
|
1
|
+
# @banatie/cli
|
|
2
|
+
|
|
3
|
+
Headless CLI for [Banatie](https://banatie.app) organization authentication and project management. Log in to your organization, create and manage projects, and connect a local repository to a project — all from the terminal, designed to be driven by humans or coding agents.
|
|
4
|
+
|
|
5
|
+
Requires **Node >= 18** (uses the built-in global `fetch`).
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
No install needed — run it with `npx`:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @banatie/cli <command>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Backend selector
|
|
16
|
+
|
|
17
|
+
Commands talk to `https://api.banatie.app` by default. Point at another backend with `BANATIE_API_BASE_URL`:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
BANATIE_API_BASE_URL=http://localhost:3000 npx @banatie/cli org acme auth status
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
banatie org <org-slug> login <bnt_boot-token>
|
|
27
|
+
banatie org <org-slug> auth status
|
|
28
|
+
banatie org <org-slug> auth logout
|
|
29
|
+
banatie org <org-slug> auth revoke
|
|
30
|
+
banatie org <org-slug> auth rotate
|
|
31
|
+
banatie org <org-slug> project new <project-slug>
|
|
32
|
+
banatie org <org-slug> project list
|
|
33
|
+
banatie org <org-slug> project connect <project-slug> --project-root <path>
|
|
34
|
+
banatie org <org-slug> project delete <project-slug> [<one-time-token>]
|
|
35
|
+
banatie <project-api-key> connect [--project-root <path>]
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- **login** — exchange the one-time `bnt_boot_` token for a permanent organization token and store it locally. The permanent token is never printed.
|
|
39
|
+
- **auth status / logout / revoke / rotate** — inspect local login, remove it locally, revoke it server-side, or rotate the current organization token. Tokens are shown as previews only.
|
|
40
|
+
- **project new / list** — create a project (returns a key preview and a connect command — never a raw key) and list the organization's projects (safe metadata only).
|
|
41
|
+
- **project connect** — materialize the project credential inside the CLI and write it to `.banatie/project.json` under `--project-root`. The raw key is written to the file but never printed. Direct form: `banatie <project-api-key> connect` when you already hold a raw project key.
|
|
42
|
+
- **project delete** — a guarded, two-step destructive flow: without a token it emails a one-time confirmation command (nothing is deleted); with the token it requires an interactive confirmation before deleting. Non-interactive deletion is refused.
|
|
43
|
+
|
|
44
|
+
There is no `org bootstrap` command — a login link is requested from the Banatie web app / API and the one-time `login` command arrives by email.
|
|
45
|
+
|
|
46
|
+
## Configuration & security
|
|
47
|
+
|
|
48
|
+
- Login profiles are stored in `~/.banatie/config.json` (override the directory with `BANATIE_HOME`), scoped by backend URL + organization slug.
|
|
49
|
+
- Permanent organization tokens and raw project keys are stored locally and **never printed** — commands show non-secret previews (`bnt_org_...abcd`) only.
|
|
50
|
+
- `project connect` writes `.banatie/project.json` (holding the project key) and prints a recommendation to add `.banatie/` to your `.gitignore` so the key is never committed. The CLI does not edit `.gitignore` itself.
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
MIT
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export declare class ApiError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
constructor(message: string, status: number);
|
|
4
|
+
}
|
|
5
|
+
export interface LoginResult {
|
|
6
|
+
organization: {
|
|
7
|
+
id: string;
|
|
8
|
+
slug: string;
|
|
9
|
+
name?: string;
|
|
10
|
+
email: string;
|
|
11
|
+
};
|
|
12
|
+
token: string;
|
|
13
|
+
tokenPreview?: string;
|
|
14
|
+
configPath?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface RotateResult {
|
|
17
|
+
token: string;
|
|
18
|
+
tokenPreview?: string;
|
|
19
|
+
previousTokenPreview?: string;
|
|
20
|
+
}
|
|
21
|
+
export interface RevokeResult {
|
|
22
|
+
status: string;
|
|
23
|
+
tokenPreview?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface CreateProjectResult {
|
|
26
|
+
project: {
|
|
27
|
+
id: string;
|
|
28
|
+
slug: string;
|
|
29
|
+
name: string;
|
|
30
|
+
};
|
|
31
|
+
apiKey: {
|
|
32
|
+
id: string;
|
|
33
|
+
keyPreview: string;
|
|
34
|
+
};
|
|
35
|
+
commands: {
|
|
36
|
+
connect: string;
|
|
37
|
+
};
|
|
38
|
+
nextActions?: Array<{
|
|
39
|
+
type: string;
|
|
40
|
+
command?: string;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
export interface CreateProjectApiKeyResult {
|
|
44
|
+
apiKey: {
|
|
45
|
+
id: string;
|
|
46
|
+
key: string;
|
|
47
|
+
keyPreview: string;
|
|
48
|
+
expiresAt: string;
|
|
49
|
+
};
|
|
50
|
+
nextActions?: Array<{
|
|
51
|
+
type: string;
|
|
52
|
+
command?: string;
|
|
53
|
+
}>;
|
|
54
|
+
}
|
|
55
|
+
export interface ListProjectsResult {
|
|
56
|
+
projects: Array<{
|
|
57
|
+
id: string;
|
|
58
|
+
slug: string;
|
|
59
|
+
name: string;
|
|
60
|
+
createdAt: string;
|
|
61
|
+
}>;
|
|
62
|
+
nextActions?: Array<{
|
|
63
|
+
type: string;
|
|
64
|
+
command?: string;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
export interface ProjectDeleteRequestResult {
|
|
68
|
+
status: string;
|
|
69
|
+
message?: string;
|
|
70
|
+
nextActions?: Array<{
|
|
71
|
+
type: string;
|
|
72
|
+
command?: string;
|
|
73
|
+
}>;
|
|
74
|
+
}
|
|
75
|
+
export interface ProjectDeleteResult {
|
|
76
|
+
status: string;
|
|
77
|
+
projectSlug?: string;
|
|
78
|
+
nextActions?: Array<{
|
|
79
|
+
type: string;
|
|
80
|
+
command?: string;
|
|
81
|
+
}>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Thin HTTP client for the Banatie organization API (Node 18+ global fetch).
|
|
85
|
+
* On failure it throws an ApiError carrying the HTTP status, with a safe,
|
|
86
|
+
* non-secret message — it never echoes the provider response body or any token.
|
|
87
|
+
*/
|
|
88
|
+
export declare class BanatieApiClient {
|
|
89
|
+
private readonly baseApiUrl;
|
|
90
|
+
constructor(baseApiUrl: string);
|
|
91
|
+
private post;
|
|
92
|
+
private get;
|
|
93
|
+
login(organizationSlug: string, bootToken: string): Promise<LoginResult>;
|
|
94
|
+
rotateCurrentToken(token: string): Promise<RotateResult>;
|
|
95
|
+
revokeCurrentToken(token: string): Promise<RevokeResult>;
|
|
96
|
+
createProject(token: string, projectSlug: string): Promise<CreateProjectResult>;
|
|
97
|
+
listProjects(token: string): Promise<ListProjectsResult>;
|
|
98
|
+
requestProjectDelete(token: string, projectSlug: string): Promise<ProjectDeleteRequestResult>;
|
|
99
|
+
confirmProjectDelete(token: string, projectSlug: string, deleteToken: string): Promise<ProjectDeleteResult>;
|
|
100
|
+
createProjectApiKey(token: string, projectSlug: string): Promise<CreateProjectApiKeyResult>;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=apiClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiClient.d.ts","sourceRoot":"","sources":["../src/apiClient.ts"],"names":[],"mappings":"AAAA,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAK5C;AAED,MAAM,WAAW,WAAW;IAC1B,YAAY,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACzE,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACpD,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9B,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3E,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/E,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzD;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACzD;AAED;;;;GAIG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,UAAU,EAAE,MAAM;YAIhB,IAAI;YAuBJ,GAAG;IAmBX,KAAK,CAAC,gBAAgB,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAQxE,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAQxD,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAWxD,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAO/E,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAKxD,oBAAoB,CACxB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,0BAA0B,CAAC;IAShC,oBAAoB,CACxB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,mBAAmB,CAAC;IASzB,mBAAmB,CACvB,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,yBAAyB,CAAC;CAOtC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BanatieApiClient = exports.ApiError = void 0;
|
|
4
|
+
class ApiError extends Error {
|
|
5
|
+
status;
|
|
6
|
+
constructor(message, status) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'ApiError';
|
|
9
|
+
this.status = status;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.ApiError = ApiError;
|
|
13
|
+
/**
|
|
14
|
+
* Thin HTTP client for the Banatie organization API (Node 18+ global fetch).
|
|
15
|
+
* On failure it throws an ApiError carrying the HTTP status, with a safe,
|
|
16
|
+
* non-secret message — it never echoes the provider response body or any token.
|
|
17
|
+
*/
|
|
18
|
+
class BanatieApiClient {
|
|
19
|
+
baseApiUrl;
|
|
20
|
+
constructor(baseApiUrl) {
|
|
21
|
+
this.baseApiUrl = baseApiUrl;
|
|
22
|
+
}
|
|
23
|
+
async post(path, safeErrorLabel, options = {}) {
|
|
24
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
25
|
+
if (options.token) {
|
|
26
|
+
headers['Authorization'] = `Bearer ${options.token}`;
|
|
27
|
+
}
|
|
28
|
+
const response = await fetch(`${this.baseApiUrl}${path}`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers,
|
|
31
|
+
body: JSON.stringify(options.body ?? {}),
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
throw new ApiError(`${safeErrorLabel} (HTTP ${response.status}).`, response.status);
|
|
35
|
+
}
|
|
36
|
+
return (await response.json());
|
|
37
|
+
}
|
|
38
|
+
async get(path, safeErrorLabel, options = {}) {
|
|
39
|
+
const headers = {};
|
|
40
|
+
if (options.token) {
|
|
41
|
+
headers['Authorization'] = `Bearer ${options.token}`;
|
|
42
|
+
}
|
|
43
|
+
const response = await fetch(`${this.baseApiUrl}${path}`, { method: 'GET', headers });
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
throw new ApiError(`${safeErrorLabel} (HTTP ${response.status}).`, response.status);
|
|
46
|
+
}
|
|
47
|
+
return (await response.json());
|
|
48
|
+
}
|
|
49
|
+
async login(organizationSlug, bootToken) {
|
|
50
|
+
return this.post('/api/v1/org/login', 'Login failed — the login link may be invalid or expired', { body: { organizationSlug, token: bootToken } });
|
|
51
|
+
}
|
|
52
|
+
async rotateCurrentToken(token) {
|
|
53
|
+
return this.post('/api/v1/org/auth/tokens/current/rotate', 'Token rotation failed', { token, body: {} });
|
|
54
|
+
}
|
|
55
|
+
async revokeCurrentToken(token) {
|
|
56
|
+
return this.post('/api/v1/org/auth/tokens/current/revoke', 'Token revoke failed', {
|
|
57
|
+
token,
|
|
58
|
+
body: {},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async createProject(token, projectSlug) {
|
|
62
|
+
return this.post('/api/v1/org/projects', 'Project creation failed', {
|
|
63
|
+
token,
|
|
64
|
+
body: { projectSlug },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async listProjects(token) {
|
|
68
|
+
return this.get('/api/v1/org/projects', 'Project list failed', { token });
|
|
69
|
+
}
|
|
70
|
+
// Step 1: request deletion — the server emails a one-time delete token, deletes nothing.
|
|
71
|
+
async requestProjectDelete(token, projectSlug) {
|
|
72
|
+
return this.post(`/api/v1/org/projects/${encodeURIComponent(projectSlug)}/delete-request`, 'Project delete request failed', { token, body: {} });
|
|
73
|
+
}
|
|
74
|
+
// Step 2: confirm deletion with the one-time delete token (distinct from the org Bearer token).
|
|
75
|
+
async confirmProjectDelete(token, projectSlug, deleteToken) {
|
|
76
|
+
return this.post(`/api/v1/org/projects/${encodeURIComponent(projectSlug)}/delete`, 'Project delete failed', { token, body: { token: deleteToken } });
|
|
77
|
+
}
|
|
78
|
+
// Materialize a raw project key for connect. Returns the plaintext key exactly once.
|
|
79
|
+
async createProjectApiKey(token, projectSlug) {
|
|
80
|
+
return this.post(`/api/v1/org/projects/${encodeURIComponent(projectSlug)}/api-keys`, 'Project connect failed', { token, body: {} });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
exports.BanatieApiClient = BanatieApiClient;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseUrl.d.ts","sourceRoot":"","sources":["../src/baseUrl.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,QAAO,MAC6C,CAAC"}
|
package/dist/baseUrl.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveBaseUrl = void 0;
|
|
4
|
+
const DEFAULT_BASE_URL = 'https://api.banatie.app';
|
|
5
|
+
// The backend is selected by BANATIE_API_BASE_URL; defaults to production.
|
|
6
|
+
const resolveBaseUrl = () => (process.env['BANATIE_API_BASE_URL'] || DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
7
|
+
exports.resolveBaseUrl = resolveBaseUrl;
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const dispatch_1 = require("./dispatch");
|
|
5
|
+
(0, dispatch_1.run)(process.argv.slice(2))
|
|
6
|
+
.then((result) => {
|
|
7
|
+
for (const line of result.lines) {
|
|
8
|
+
console.log(line);
|
|
9
|
+
}
|
|
10
|
+
process.exit(result.exitCode);
|
|
11
|
+
})
|
|
12
|
+
.catch((error) => {
|
|
13
|
+
console.error(error instanceof Error ? error.message : 'Command failed.');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connectDirect.d.ts","sourceRoot":"","sources":["../../src/commands/connectDirect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAQ7C,eAAO,MAAM,oBAAoB,GAAI,eAAe,MAAM,EAAE,aAAa,MAAM,KAAG,aAyBjF,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.connectDirectCommand = void 0;
|
|
4
|
+
const baseUrl_1 = require("../baseUrl");
|
|
5
|
+
const projectConfig_1 = require("../projectConfig");
|
|
6
|
+
// A raw project key looks like `bnt_...` and is neither an org nor a bootstrap token.
|
|
7
|
+
const isProjectKey = (key) => key.startsWith('bnt_') && !key.startsWith('bnt_org_') && !key.startsWith('bnt_boot_');
|
|
8
|
+
// `<project-api-key> connect [--project-root <path>]`: write .banatie/project.json
|
|
9
|
+
// from a raw key the caller already holds. Never re-prints the key.
|
|
10
|
+
const connectDirectCommand = (projectApiKey, projectRoot) => {
|
|
11
|
+
if (!isProjectKey(projectApiKey)) {
|
|
12
|
+
return {
|
|
13
|
+
exitCode: 1,
|
|
14
|
+
lines: ['Invalid project API key. Expected a project key starting with "bnt_".'],
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const baseApiUrl = (0, baseUrl_1.resolveBaseUrl)();
|
|
18
|
+
const configPath = (0, projectConfig_1.writeProjectConfig)(projectRoot, {
|
|
19
|
+
version: 1,
|
|
20
|
+
baseApiUrl,
|
|
21
|
+
apiKey: projectApiKey,
|
|
22
|
+
connectedAt: new Date().toISOString(),
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
exitCode: 0,
|
|
26
|
+
lines: [
|
|
27
|
+
'Connected this repo to a Banatie project.',
|
|
28
|
+
`Wrote: ${configPath}`,
|
|
29
|
+
`Backend: ${baseApiUrl}`,
|
|
30
|
+
projectConfig_1.GITIGNORE_HINT,
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
exports.connectDirectCommand = connectDirectCommand;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAI7C,eAAO,MAAM,YAAY,GAAU,SAAS,MAAM,EAAE,WAAW,MAAM,KAAG,OAAO,CAAC,aAAa,CA8B5F,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loginCommand = void 0;
|
|
4
|
+
const apiClient_1 = require("../apiClient");
|
|
5
|
+
const baseUrl_1 = require("../baseUrl");
|
|
6
|
+
const preview_1 = require("../preview");
|
|
7
|
+
const config_1 = require("../config");
|
|
8
|
+
// `org <slug> login <bnt_boot-token>`: exchange the one-time token for a permanent
|
|
9
|
+
// org token and store it locally. The raw token is never printed — only a preview.
|
|
10
|
+
const loginCommand = async (orgSlug, bootToken) => {
|
|
11
|
+
const baseApiUrl = (0, baseUrl_1.resolveBaseUrl)();
|
|
12
|
+
const client = new apiClient_1.BanatieApiClient(baseApiUrl);
|
|
13
|
+
try {
|
|
14
|
+
const result = await client.login(orgSlug, bootToken);
|
|
15
|
+
(0, config_1.upsertProfile)({
|
|
16
|
+
baseApiUrl,
|
|
17
|
+
orgSlug,
|
|
18
|
+
token: result.token,
|
|
19
|
+
...(result.organization.id ? { orgId: result.organization.id } : {}),
|
|
20
|
+
...(result.organization.email ? { email: result.organization.email } : {}),
|
|
21
|
+
});
|
|
22
|
+
const preview = result.tokenPreview ?? (0, preview_1.previewToken)(result.token);
|
|
23
|
+
return {
|
|
24
|
+
exitCode: 0,
|
|
25
|
+
lines: [
|
|
26
|
+
`Logged in to organization "${orgSlug}" (${result.organization.email ?? 'unknown email'}).`,
|
|
27
|
+
`Backend: ${baseApiUrl}`,
|
|
28
|
+
`Token: ${preview}`,
|
|
29
|
+
`Config: ${(0, config_1.getConfigPath)()}`,
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return {
|
|
35
|
+
exitCode: 1,
|
|
36
|
+
lines: [error instanceof Error ? error.message : 'Login failed.'],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
exports.loginCommand = loginCommand;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAG7C,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,KAAG,aAe/C,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logoutCommand = void 0;
|
|
4
|
+
const baseUrl_1 = require("../baseUrl");
|
|
5
|
+
const config_1 = require("../config");
|
|
6
|
+
// `org <slug> auth logout`: remove only the local profile; the server token stays valid.
|
|
7
|
+
const logoutCommand = (orgSlug) => {
|
|
8
|
+
const baseApiUrl = (0, baseUrl_1.resolveBaseUrl)();
|
|
9
|
+
if (!(0, config_1.getProfile)(baseApiUrl, orgSlug)) {
|
|
10
|
+
return { exitCode: 0, lines: [`Not logged in to "${orgSlug}" on ${baseApiUrl}.`] };
|
|
11
|
+
}
|
|
12
|
+
(0, config_1.removeProfile)(baseApiUrl, orgSlug);
|
|
13
|
+
return {
|
|
14
|
+
exitCode: 0,
|
|
15
|
+
lines: [
|
|
16
|
+
`Logged out of "${orgSlug}" locally.`,
|
|
17
|
+
'Note: the organization token is still valid on the server. Run "auth revoke" to invalidate it.',
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
exports.logoutCommand = logoutCommand;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectConnect.d.ts","sourceRoot":"","sources":["../../src/commands/projectConnect.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAK7C,eAAO,MAAM,qBAAqB,GAChC,SAAS,MAAM,EACf,aAAa,MAAM,EACnB,aAAa,MAAM,KAClB,OAAO,CAAC,aAAa,CAuDvB,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.projectConnectCommand = void 0;
|
|
4
|
+
const apiClient_1 = require("../apiClient");
|
|
5
|
+
const baseUrl_1 = require("../baseUrl");
|
|
6
|
+
const config_1 = require("../config");
|
|
7
|
+
const projectConfig_1 = require("../projectConfig");
|
|
8
|
+
// `org <slug> project connect <project-slug> --project-root <path>`: use the saved
|
|
9
|
+
// org token to materialize a project key inside the process, write it to
|
|
10
|
+
// .banatie/project.json, and print only a preview — never the raw key or org token.
|
|
11
|
+
const projectConnectCommand = async (orgSlug, projectSlug, projectRoot) => {
|
|
12
|
+
const baseApiUrl = (0, baseUrl_1.resolveBaseUrl)();
|
|
13
|
+
const profile = (0, config_1.getProfile)(baseApiUrl, orgSlug);
|
|
14
|
+
if (!profile) {
|
|
15
|
+
return {
|
|
16
|
+
exitCode: 1,
|
|
17
|
+
lines: [
|
|
18
|
+
`Not logged in to "${orgSlug}" on ${baseApiUrl}.`,
|
|
19
|
+
`Run: banatie org ${orgSlug} login <bnt_boot-token>`,
|
|
20
|
+
],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const client = new apiClient_1.BanatieApiClient(baseApiUrl);
|
|
24
|
+
try {
|
|
25
|
+
const result = await client.createProjectApiKey(profile.token, projectSlug);
|
|
26
|
+
const configPath = (0, projectConfig_1.writeProjectConfig)(projectRoot, {
|
|
27
|
+
version: 1,
|
|
28
|
+
baseApiUrl,
|
|
29
|
+
apiKey: result.apiKey.key,
|
|
30
|
+
orgSlug,
|
|
31
|
+
projectSlug,
|
|
32
|
+
connectedAt: new Date().toISOString(),
|
|
33
|
+
});
|
|
34
|
+
return {
|
|
35
|
+
exitCode: 0,
|
|
36
|
+
lines: [
|
|
37
|
+
`Connected project "${projectSlug}" in "${orgSlug}" to Banatie.`,
|
|
38
|
+
`Wrote: ${configPath}`,
|
|
39
|
+
`Backend: ${baseApiUrl}`,
|
|
40
|
+
`Key: ${result.apiKey.keyPreview}`,
|
|
41
|
+
projectConfig_1.GITIGNORE_HINT,
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error instanceof apiClient_1.ApiError) {
|
|
47
|
+
if (error.status === 404) {
|
|
48
|
+
return {
|
|
49
|
+
exitCode: 1,
|
|
50
|
+
lines: [`Project "${projectSlug}" not found in "${orgSlug}".`],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (error.status === 401) {
|
|
54
|
+
return {
|
|
55
|
+
exitCode: 1,
|
|
56
|
+
lines: [`Your login has expired. Run: banatie org ${orgSlug} login <bnt_boot-token>`],
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
exitCode: 1,
|
|
62
|
+
lines: [error instanceof Error ? error.message : 'Project connect failed.'],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
exports.projectConnectCommand = projectConnectCommand;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { CommandResult } from './types';
|
|
2
|
+
export interface ProjectDeleteDeps {
|
|
3
|
+
isTTY?: boolean;
|
|
4
|
+
confirm?: (projectSlug: string) => Promise<boolean>;
|
|
5
|
+
}
|
|
6
|
+
export declare const projectDeleteCommand: (orgSlug: string, projectSlug: string, deleteToken?: string, deps?: ProjectDeleteDeps) => Promise<CommandResult>;
|
|
7
|
+
//# sourceMappingURL=projectDelete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectDelete.d.ts","sourceRoot":"","sources":["../../src/commands/projectDelete.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CACrD;AAMD,eAAO,MAAM,oBAAoB,GAC/B,SAAS,MAAM,EACf,aAAa,MAAM,EACnB,cAAc,MAAM,EACpB,OAAM,iBAAsB,KAC3B,OAAO,CAAC,aAAa,CAyFvB,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.projectDeleteCommand = void 0;
|
|
4
|
+
const apiClient_1 = require("../apiClient");
|
|
5
|
+
const baseUrl_1 = require("../baseUrl");
|
|
6
|
+
const config_1 = require("../config");
|
|
7
|
+
const confirmPrompt_1 = require("../confirmPrompt");
|
|
8
|
+
// `org <slug> project delete <project-slug> [<one-time-token>]`: two-step destructive delete.
|
|
9
|
+
// Without a token, triggers the confirmation email. With a token, requires an explicit
|
|
10
|
+
// interactive confirmation before deleting; non-interactive invocation is refused.
|
|
11
|
+
// Neither the org token nor the raw delete token is ever echoed back.
|
|
12
|
+
const projectDeleteCommand = async (orgSlug, projectSlug, deleteToken, deps = {}) => {
|
|
13
|
+
const baseApiUrl = (0, baseUrl_1.resolveBaseUrl)();
|
|
14
|
+
const profile = (0, config_1.getProfile)(baseApiUrl, orgSlug);
|
|
15
|
+
if (!profile) {
|
|
16
|
+
return {
|
|
17
|
+
exitCode: 1,
|
|
18
|
+
lines: [
|
|
19
|
+
`Not logged in to "${orgSlug}" on ${baseApiUrl}.`,
|
|
20
|
+
`Run: banatie org ${orgSlug} login <bnt_boot-token>`,
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const client = new apiClient_1.BanatieApiClient(baseApiUrl);
|
|
25
|
+
// Step 1: no token -> trigger the confirmation email, delete nothing.
|
|
26
|
+
if (!deleteToken) {
|
|
27
|
+
try {
|
|
28
|
+
await client.requestProjectDelete(profile.token, projectSlug);
|
|
29
|
+
return {
|
|
30
|
+
exitCode: 0,
|
|
31
|
+
lines: [
|
|
32
|
+
`A one-time delete confirmation for "${projectSlug}" was emailed to the organization owner.`,
|
|
33
|
+
'Nothing has been deleted yet. Run the one-time command from that email to confirm.',
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
if (error instanceof apiClient_1.ApiError) {
|
|
39
|
+
if (error.status === 404) {
|
|
40
|
+
return { exitCode: 1, lines: [`Project "${projectSlug}" not found in "${orgSlug}".`] };
|
|
41
|
+
}
|
|
42
|
+
if (error.status === 401) {
|
|
43
|
+
return {
|
|
44
|
+
exitCode: 1,
|
|
45
|
+
lines: [`Your login has expired. Run: banatie org ${orgSlug} login <bnt_boot-token>`],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
exitCode: 1,
|
|
51
|
+
lines: [error instanceof Error ? error.message : 'Project delete request failed.'],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Step 2: token present -> require explicit interactive confirmation before deleting.
|
|
56
|
+
const isTTY = deps.isTTY ?? Boolean(process.stdin.isTTY);
|
|
57
|
+
if (!isTTY) {
|
|
58
|
+
return {
|
|
59
|
+
exitCode: 1,
|
|
60
|
+
lines: [
|
|
61
|
+
'Refusing to delete non-interactively.',
|
|
62
|
+
`Run this in a terminal to confirm: banatie org ${orgSlug} project delete ${projectSlug} <one-time-token>`,
|
|
63
|
+
],
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const confirm = deps.confirm ?? confirmPrompt_1.promptDeleteConfirmation;
|
|
67
|
+
const confirmed = await confirm(projectSlug);
|
|
68
|
+
if (!confirmed) {
|
|
69
|
+
return { exitCode: 1, lines: ['Aborted. Nothing was deleted.'] };
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
await client.confirmProjectDelete(profile.token, projectSlug, deleteToken);
|
|
73
|
+
return {
|
|
74
|
+
exitCode: 0,
|
|
75
|
+
lines: [`Deleted project "${projectSlug}" in "${orgSlug}".`],
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error instanceof apiClient_1.ApiError) {
|
|
80
|
+
if (error.status === 401) {
|
|
81
|
+
return {
|
|
82
|
+
exitCode: 1,
|
|
83
|
+
lines: [
|
|
84
|
+
'The delete token is invalid or has expired.',
|
|
85
|
+
`Request a new one: banatie org ${orgSlug} project delete ${projectSlug}`,
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (error.status === 404) {
|
|
90
|
+
return { exitCode: 1, lines: [`Project "${projectSlug}" not found in "${orgSlug}".`] };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
exitCode: 1,
|
|
95
|
+
lines: [error instanceof Error ? error.message : 'Project delete failed.'],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
exports.projectDeleteCommand = projectDeleteCommand;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectList.d.ts","sourceRoot":"","sources":["../../src/commands/projectList.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAI7C,eAAO,MAAM,kBAAkB,GAAU,SAAS,MAAM,KAAG,OAAO,CAAC,aAAa,CA4C/E,CAAC"}
|