@abe-ing/cli 0.2.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/README.md +125 -0
- package/dist/api.js +104 -0
- package/dist/api.js.map +1 -0
- package/dist/api.test.js +73 -0
- package/dist/api.test.js.map +1 -0
- package/dist/cli.js +7 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/auth.js +102 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/auth.test.js +129 -0
- package/dist/commands/auth.test.js.map +1 -0
- package/dist/commands/boards.js +41 -0
- package/dist/commands/boards.js.map +1 -0
- package/dist/commands/boards.test.js +32 -0
- package/dist/commands/boards.test.js.map +1 -0
- package/dist/commands/members.js +29 -0
- package/dist/commands/members.js.map +1 -0
- package/dist/commands/tasks.js +193 -0
- package/dist/commands/tasks.js.map +1 -0
- package/dist/commands/tasks.test.js +85 -0
- package/dist/commands/tasks.test.js.map +1 -0
- package/dist/config.js +56 -0
- package/dist/config.js.map +1 -0
- package/dist/config.test.js +70 -0
- package/dist/config.test.js.map +1 -0
- package/dist/context.js +10 -0
- package/dist/context.js.map +1 -0
- package/dist/enums.js +18 -0
- package/dist/enums.js.map +1 -0
- package/dist/errors.js +23 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.js +53 -0
- package/dist/index.js.map +1 -0
- package/dist/output.js +70 -0
- package/dist/output.js.map +1 -0
- package/dist/output.test.js +49 -0
- package/dist/output.test.js.map +1 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# @abe-ing/cli — the `abe` command
|
|
2
|
+
|
|
3
|
+
Drive [ABE](https://abe.ing) project boards and tasks from the command line. Built for humans **and** agents — run engineering issues inside ABE instead of GitHub, and let Claude Code (or any shell-driven agent) create boards, file issues, and move cards via `abe ... --json`.
|
|
4
|
+
|
|
5
|
+
It is a thin, dependency-light wrapper over the [ABE REST API](../../docs/agent-api/rest-api.md), authenticated with a Personal Access Token.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm i -g @abe-ing/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requires Node ≥ 18.
|
|
14
|
+
|
|
15
|
+
## Authenticate
|
|
16
|
+
|
|
17
|
+
Generate a Personal Access Token in the ABE web app (**Settings → Developer → Generate a token**), then:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
abe auth login --token abe_pat_xxxx --api-url https://api.abe.ing
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This stores the token at `~/.abe/config.json` (created `0600`). The token is bound to the workspace you generated it in.
|
|
24
|
+
|
|
25
|
+
**Headless / CI** — skip the config file and use environment variables:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
export ABE_TOKEN=abe_pat_xxxx
|
|
29
|
+
export ABE_API_URL=https://api.abe.ing
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Configuration precedence: **flag (`--token`/`--api-url`) > environment (`ABE_TOKEN`/`ABE_API_URL`) > `~/.abe/config.json` > default**. The token is never printed in full (always redacted to the last 4 chars) and `abe config set token` is intentionally refused (it would leak into shell history).
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
abe auth status # show current config + a live auth check
|
|
36
|
+
abe auth logout # remove the stored token
|
|
37
|
+
abe config path # print the config file location
|
|
38
|
+
abe config set apiUrl https://api.abe.ing
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Project boards
|
|
42
|
+
|
|
43
|
+
These are the **Project Boards** in the ABE web app (the REST resource is `task_boards`).
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
abe board create "Engineering" --columns backlog,todo,in_review,done [--description "..."]
|
|
47
|
+
abe board list
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Columns are normalized to `lowercase_with_underscores`. New boards are custom boards; app/GitHub-backed boards are managed inside ABE.
|
|
51
|
+
|
|
52
|
+
## Tasks
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Create — all properties in one shot
|
|
56
|
+
abe task create "Fix login race" \
|
|
57
|
+
--board engineering \ # slug, name, or numeric id
|
|
58
|
+
--type bug \ # task | feature | bug | suggestion | improvement | ops
|
|
59
|
+
--priority P1 \ # P0 | P1 | P2 | urgent | high | normal
|
|
60
|
+
--size M \ # XS | S | M | L | XL
|
|
61
|
+
--assignee matt@viewcy.com \ # email OR name — resolved server-side (or --assignee-id <n>)
|
|
62
|
+
--labels auth,regression \
|
|
63
|
+
--column todo \
|
|
64
|
+
--due 2026-07-01 \
|
|
65
|
+
--description "Race on concurrent login"
|
|
66
|
+
|
|
67
|
+
# Update any field (send only what changes)
|
|
68
|
+
abe task update 91 --priority P0 --labels auth,p0
|
|
69
|
+
abe task update 91 --board other-board # move to another board
|
|
70
|
+
|
|
71
|
+
abe task move 91 --column in_review
|
|
72
|
+
abe task complete 91
|
|
73
|
+
|
|
74
|
+
# Read
|
|
75
|
+
abe task list --board engineering # your OPEN tasks on that board
|
|
76
|
+
abe task list --board engineering --all --include-done # full set (admin/full token)
|
|
77
|
+
abe task board --board engineering # full board state incl. done — best for reconciliation
|
|
78
|
+
abe task show 91
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Invalid `--priority/--type/--size/--status` fail fast (exit 2) before any network call. `--column` is **not** client-validated (columns are per-board) — an invalid column returns the server's `422` listing the valid set.
|
|
82
|
+
|
|
83
|
+
## Members
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
abe members list # requires an admin/full token
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
You rarely need this — assigning by email/name on `task create`/`update` resolves members server-side.
|
|
90
|
+
|
|
91
|
+
## JSON output (for scripts & agents)
|
|
92
|
+
|
|
93
|
+
Add `--json` to any command. In `--json` mode, **stdout carries only the JSON payload**; all human/status text goes to stderr — so piping stdout to a parser never breaks.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
abe --json task list --board engineering | jq '.tasks[] | {id,title,status}'
|
|
97
|
+
BOARD_ID=$(abe --json board list | jq '.boards[] | select(.slug=="engineering") | .id')
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Exit codes
|
|
101
|
+
|
|
102
|
+
| Code | Meaning |
|
|
103
|
+
|------|---------|
|
|
104
|
+
| 0 | Success |
|
|
105
|
+
| 2 | Usage / parse error (bad flag, invalid enum) |
|
|
106
|
+
| 4 | Auth (no token, or token invalid/expired) |
|
|
107
|
+
| 5 | Workspace billing locked |
|
|
108
|
+
| 6 | Forbidden (missing scope / role) |
|
|
109
|
+
| 7 | Not found |
|
|
110
|
+
| 8 | Validation error |
|
|
111
|
+
| 9 | Server error |
|
|
112
|
+
| 10 | Network error |
|
|
113
|
+
|
|
114
|
+
## Global flags
|
|
115
|
+
|
|
116
|
+
`--json`, `--api-url <url>`, `--token <pat>`, `--no-color`. Environment: `ABE_TOKEN`, `ABE_API_URL`, `ABE_TENANT` (display only), `ABE_CONFIG_DIR` (override `~/.abe`), `ABE_NO_CONFIG` (ignore the config file).
|
|
117
|
+
|
|
118
|
+
## Develop
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npm install
|
|
122
|
+
npm run build # tsc → dist/
|
|
123
|
+
npm test # vitest
|
|
124
|
+
npm run typecheck
|
|
125
|
+
```
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { CliError, EXIT } from './errors.js';
|
|
2
|
+
export class ApiClient {
|
|
3
|
+
baseUrl;
|
|
4
|
+
token;
|
|
5
|
+
constructor(baseUrl, token) {
|
|
6
|
+
this.baseUrl = baseUrl;
|
|
7
|
+
this.token = token;
|
|
8
|
+
}
|
|
9
|
+
async request(method, p, body) {
|
|
10
|
+
let res;
|
|
11
|
+
try {
|
|
12
|
+
res = await fetch(`${this.baseUrl}/api/v1${p}`, {
|
|
13
|
+
method,
|
|
14
|
+
headers: {
|
|
15
|
+
Authorization: `Bearer ${this.token}`,
|
|
16
|
+
'Content-Type': 'application/json',
|
|
17
|
+
Accept: 'application/json',
|
|
18
|
+
},
|
|
19
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new CliError(`Could not reach ABE at ${this.baseUrl}.`, EXIT.NETWORK);
|
|
24
|
+
}
|
|
25
|
+
const text = await res.text();
|
|
26
|
+
const parsed = text ? safeJson(text) : undefined;
|
|
27
|
+
if (!res.ok)
|
|
28
|
+
throw mapError(res.status, parsed);
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
31
|
+
listBoards() {
|
|
32
|
+
return this.request('GET', '/task_boards');
|
|
33
|
+
}
|
|
34
|
+
createBoard(b) {
|
|
35
|
+
// The endpoint reads top-level params (params.permit(:name, ...)), NOT a
|
|
36
|
+
// nested { task_board: ... } wrapper — send the fields flat.
|
|
37
|
+
return this.request('POST', '/task_boards', b);
|
|
38
|
+
}
|
|
39
|
+
// Forwards every reconcile-relevant filter, including include_done + all.
|
|
40
|
+
listTasks(query) {
|
|
41
|
+
const q = new URLSearchParams();
|
|
42
|
+
for (const [k, v] of Object.entries(query))
|
|
43
|
+
if (v !== undefined)
|
|
44
|
+
q.set(k, String(v));
|
|
45
|
+
const qs = q.toString();
|
|
46
|
+
return this.request('GET', `/tasks${qs ? `?${qs}` : ''}`);
|
|
47
|
+
}
|
|
48
|
+
boardState(boardId) {
|
|
49
|
+
return this.request('GET', `/tasks/board?board_id=${boardId}`);
|
|
50
|
+
}
|
|
51
|
+
getTask(id) {
|
|
52
|
+
return this.request('GET', `/tasks/${id}`);
|
|
53
|
+
}
|
|
54
|
+
createTask(body) {
|
|
55
|
+
return this.request('POST', '/tasks', body);
|
|
56
|
+
}
|
|
57
|
+
updateTask(id, body) {
|
|
58
|
+
return this.request('PUT', `/tasks/${id}`, body);
|
|
59
|
+
}
|
|
60
|
+
moveTask(id, column) {
|
|
61
|
+
return this.request('POST', `/tasks/${id}/move`, { column });
|
|
62
|
+
}
|
|
63
|
+
completeTask(id) {
|
|
64
|
+
return this.request('POST', `/tasks/${id}/complete`);
|
|
65
|
+
}
|
|
66
|
+
listMembers() {
|
|
67
|
+
return this.request('GET', '/members');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function safeJson(t) {
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(t);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Map an HTTP status to a humanized CliError with a stable exit code. Surfaces
|
|
79
|
+
// the backend's own humanized {error} string where it is safe to do so. The
|
|
80
|
+
// 402 body carries a machine token (`billing_locked`) in `error`, so we craft
|
|
81
|
+
// our own message there and read the human `reason` instead.
|
|
82
|
+
export function mapError(status, body) {
|
|
83
|
+
const b = (body ?? {});
|
|
84
|
+
const serverMsg = b.error;
|
|
85
|
+
switch (status) {
|
|
86
|
+
case 400:
|
|
87
|
+
return new CliError(serverMsg || 'Missing or invalid parameter.', EXIT.VALIDATION);
|
|
88
|
+
case 401:
|
|
89
|
+
return new CliError('Authentication failed — your token is invalid or expired. Re-run `abe auth login`.', EXIT.AUTH);
|
|
90
|
+
case 402:
|
|
91
|
+
return new CliError(`This workspace is locked for billing${b.reason ? ` (${b.reason})` : ''}. Visit ABE to resolve it.`, EXIT.BILLING);
|
|
92
|
+
case 403:
|
|
93
|
+
return new CliError(serverMsg || 'You do not have permission to do that.', EXIT.FORBIDDEN);
|
|
94
|
+
case 404:
|
|
95
|
+
return new CliError(serverMsg || 'Not found — check the id or slug.', EXIT.NOT_FOUND);
|
|
96
|
+
case 422:
|
|
97
|
+
return new CliError(serverMsg || 'Invalid request.', EXIT.VALIDATION);
|
|
98
|
+
default:
|
|
99
|
+
return status >= 500
|
|
100
|
+
? new CliError('ABE had a server error — try again.', EXIT.SERVER)
|
|
101
|
+
: new CliError(serverMsg || `Request failed (${status}).`, EXIT.GENERIC);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
|
package/dist/api.test.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
2
|
+
import { ApiClient, mapError } from './api.js';
|
|
3
|
+
import { EXIT } from './errors.js';
|
|
4
|
+
function mockFetch(status, body) {
|
|
5
|
+
return vi.fn().mockResolvedValue({
|
|
6
|
+
ok: status >= 200 && status < 300,
|
|
7
|
+
status,
|
|
8
|
+
text: async () => (body === undefined ? '' : JSON.stringify(body)),
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
describe('ApiClient.request', () => {
|
|
12
|
+
afterEach(() => vi.unstubAllGlobals());
|
|
13
|
+
it('sends the Bearer token, JSON headers, and the /api/v1 prefix', async () => {
|
|
14
|
+
const f = mockFetch(200, { boards: [] });
|
|
15
|
+
vi.stubGlobal('fetch', f);
|
|
16
|
+
await new ApiClient('http://h', 'abe_pat_x').listBoards();
|
|
17
|
+
expect(f).toHaveBeenCalledWith('http://h/api/v1/task_boards', expect.objectContaining({
|
|
18
|
+
method: 'GET',
|
|
19
|
+
headers: expect.objectContaining({
|
|
20
|
+
Authorization: 'Bearer abe_pat_x',
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
}),
|
|
23
|
+
}));
|
|
24
|
+
});
|
|
25
|
+
it('uses correct method/path/body for task mutations', async () => {
|
|
26
|
+
const f = mockFetch(200, { task: {} });
|
|
27
|
+
vi.stubGlobal('fetch', f);
|
|
28
|
+
const c = new ApiClient('http://h', 't');
|
|
29
|
+
await c.createTask({ title: 'x', task_board_id: 5 });
|
|
30
|
+
expect(f).toHaveBeenLastCalledWith('http://h/api/v1/tasks', expect.objectContaining({ method: 'POST', body: JSON.stringify({ title: 'x', task_board_id: 5 }) }));
|
|
31
|
+
await c.updateTask(9, { priority: 'P0' });
|
|
32
|
+
expect(f).toHaveBeenLastCalledWith('http://h/api/v1/tasks/9', expect.objectContaining({ method: 'PUT', body: JSON.stringify({ priority: 'P0' }) }));
|
|
33
|
+
await c.moveTask(9, 'review');
|
|
34
|
+
expect(f).toHaveBeenLastCalledWith('http://h/api/v1/tasks/9/move', expect.objectContaining({ method: 'POST', body: JSON.stringify({ column: 'review' }) }));
|
|
35
|
+
await c.completeTask(9);
|
|
36
|
+
expect(f).toHaveBeenLastCalledWith('http://h/api/v1/tasks/9/complete', expect.objectContaining({ method: 'POST' }));
|
|
37
|
+
});
|
|
38
|
+
it('builds the list query string including include_done and all', async () => {
|
|
39
|
+
const f = mockFetch(200, { tasks: [], total: 0 });
|
|
40
|
+
vi.stubGlobal('fetch', f);
|
|
41
|
+
await new ApiClient('http://h', 't').listTasks({ board_id: 5, all: true, include_done: true, status: undefined });
|
|
42
|
+
const url = f.mock.calls[0][0];
|
|
43
|
+
expect(url).toContain('/api/v1/tasks?');
|
|
44
|
+
expect(url).toContain('board_id=5');
|
|
45
|
+
expect(url).toContain('all=true');
|
|
46
|
+
expect(url).toContain('include_done=true');
|
|
47
|
+
expect(url).not.toContain('status=');
|
|
48
|
+
});
|
|
49
|
+
it('throws a network CliError when fetch rejects', async () => {
|
|
50
|
+
vi.stubGlobal('fetch', vi.fn().mockRejectedValue(new Error('boom')));
|
|
51
|
+
await expect(new ApiClient('http://h', 't').listBoards()).rejects.toMatchObject({ exitCode: EXIT.NETWORK });
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
describe('mapError', () => {
|
|
55
|
+
it('maps statuses to stable exit codes', () => {
|
|
56
|
+
expect(mapError(400, { error: 'bad' }).exitCode).toBe(EXIT.VALIDATION);
|
|
57
|
+
expect(mapError(401, {}).exitCode).toBe(EXIT.AUTH);
|
|
58
|
+
expect(mapError(403, { error: 'nope' }).exitCode).toBe(EXIT.FORBIDDEN);
|
|
59
|
+
expect(mapError(403, { error: 'nope' }).message).toBe('nope');
|
|
60
|
+
expect(mapError(404, {}).exitCode).toBe(EXIT.NOT_FOUND);
|
|
61
|
+
expect(mapError(422, { error: 'Invalid column' }).exitCode).toBe(EXIT.VALIDATION);
|
|
62
|
+
expect(mapError(422, { error: 'Invalid column' }).message).toBe('Invalid column');
|
|
63
|
+
expect(mapError(500, {}).exitCode).toBe(EXIT.SERVER);
|
|
64
|
+
expect(mapError(418, {}).exitCode).toBe(EXIT.GENERIC);
|
|
65
|
+
});
|
|
66
|
+
it('does NOT print the machine billing_locked token; uses reason instead', () => {
|
|
67
|
+
const e = mapError(402, { error: 'billing_locked', reason: 'trial_expired' });
|
|
68
|
+
expect(e.exitCode).toBe(EXIT.BILLING);
|
|
69
|
+
expect(e.message).not.toContain('billing_locked');
|
|
70
|
+
expect(e.message).toContain('trial_expired');
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=api.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.test.js","sourceRoot":"","sources":["../src/api.test.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
ADDED
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,MAAM,YAAY,CAAA;AAEhC,GAAG,EAAE,CAAC,IAAI,CACR,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAC5B,CAAC,GAAG,EAAE,EAAE;IACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAI,GAAa,EAAE,OAAO,IAAI,eAAe,IAAI,CAAC,CAAA;IACvE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CACF,CAAA"}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ApiClient } from '../api.js';
|
|
2
|
+
import { CliError, EXIT } from '../errors.js';
|
|
3
|
+
import { configFile, readConfig, writeConfig, resolveSettings, redactToken, } from '../config.js';
|
|
4
|
+
import { status } from '../output.js';
|
|
5
|
+
// A token is valid if /task_boards returns 2xx. A 402 (billing-locked) also
|
|
6
|
+
// means the token authenticated fine — the workspace just needs billing — so
|
|
7
|
+
// we treat it as a successful login and warn.
|
|
8
|
+
async function probe(apiUrl, token) {
|
|
9
|
+
try {
|
|
10
|
+
await new ApiClient(apiUrl, token).listBoards();
|
|
11
|
+
return { ok: true, billingLocked: false };
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
if (err instanceof CliError && err.exitCode === EXIT.BILLING) {
|
|
15
|
+
return { ok: true, billingLocked: true };
|
|
16
|
+
}
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function registerAuthCommands(program) {
|
|
21
|
+
const auth = program.command('auth').description('Authenticate and manage stored credentials');
|
|
22
|
+
auth
|
|
23
|
+
.command('login')
|
|
24
|
+
.description('Store a Personal Access Token (generate one in ABE Settings → Developer)')
|
|
25
|
+
// --token and --api-url are the PROGRAM's global options (buildProgram).
|
|
26
|
+
// Re-declaring them here made the global swallow the value while the
|
|
27
|
+
// subcommand's requiredOption still saw nothing — login could never
|
|
28
|
+
// succeed. Read them via optsWithGlobals() like every other command.
|
|
29
|
+
.option('--tenant <slug>', 'workspace label (informational only)')
|
|
30
|
+
.action(async (opts, cmd) => {
|
|
31
|
+
const g = cmd.optsWithGlobals();
|
|
32
|
+
// Require the explicit flag: resolveSettings would fall back to
|
|
33
|
+
// ABE_TOKEN / an already-stored token, silently re-saving stale creds.
|
|
34
|
+
if (!g.token) {
|
|
35
|
+
throw new CliError('Missing --token. Usage: abe auth login --token <abe_pat_…> [--api-url <url>]', EXIT.USAGE);
|
|
36
|
+
}
|
|
37
|
+
const settings = resolveSettings({ apiUrl: g.apiUrl, token: g.token });
|
|
38
|
+
const result = await probe(settings.apiUrl, g.token);
|
|
39
|
+
writeConfig({ apiUrl: settings.apiUrl, token: g.token, tenant: opts.tenant ?? g.tenant });
|
|
40
|
+
status(`Logged in to ${settings.apiUrl} as ${redactToken(g.token)}`);
|
|
41
|
+
if (result.billingLocked) {
|
|
42
|
+
status('Warning: this workspace is locked for billing — API calls will return a billing error until it is resolved.');
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
auth
|
|
46
|
+
.command('status')
|
|
47
|
+
.description('Show the current configuration and a live auth check')
|
|
48
|
+
.action(async (_opts, cmd) => {
|
|
49
|
+
const g = cmd.optsWithGlobals();
|
|
50
|
+
const settings = resolveSettings({ apiUrl: g.apiUrl, token: g.token, tenant: g.tenant });
|
|
51
|
+
status(`API URL: ${settings.apiUrl}`);
|
|
52
|
+
status(`Token: ${redactToken(settings.token)}`);
|
|
53
|
+
status(`Tenant: ${settings.tenant ?? '(none)'}`);
|
|
54
|
+
if (!settings.token) {
|
|
55
|
+
throw new CliError('No token configured. Run `abe auth login --token <pat>`.', EXIT.AUTH);
|
|
56
|
+
}
|
|
57
|
+
const result = await probe(settings.apiUrl, settings.token);
|
|
58
|
+
status(result.billingLocked ? 'Auth: ok (workspace billing-locked)' : 'Auth: ok');
|
|
59
|
+
});
|
|
60
|
+
auth
|
|
61
|
+
.command('logout')
|
|
62
|
+
.description('Remove the stored token')
|
|
63
|
+
.action(() => {
|
|
64
|
+
const cfg = readConfig();
|
|
65
|
+
delete cfg.token;
|
|
66
|
+
writeConfig(cfg);
|
|
67
|
+
status('Logged out (token removed).');
|
|
68
|
+
});
|
|
69
|
+
const config = program.command('config').description('Read and write CLI configuration');
|
|
70
|
+
config
|
|
71
|
+
.command('get <key>')
|
|
72
|
+
.description('Print a config value (apiUrl | tenant)')
|
|
73
|
+
.action((key) => {
|
|
74
|
+
const cfg = readConfig();
|
|
75
|
+
if (key === 'token') {
|
|
76
|
+
status(redactToken(cfg.token));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const value = cfg[key];
|
|
80
|
+
status(value === undefined ? '(unset)' : String(value));
|
|
81
|
+
});
|
|
82
|
+
config
|
|
83
|
+
.command('set <key> <value>')
|
|
84
|
+
.description('Set apiUrl or tenant (use `auth login` for the token)')
|
|
85
|
+
.action((key, value) => {
|
|
86
|
+
if (key === 'token') {
|
|
87
|
+
throw new CliError('Refusing to set the token via `config set` (it would leak into shell history). Use `abe auth login --token <pat>`.', EXIT.USAGE);
|
|
88
|
+
}
|
|
89
|
+
if (key !== 'apiUrl' && key !== 'tenant') {
|
|
90
|
+
throw new CliError(`Unknown config key "${key}". Settable: apiUrl, tenant.`, EXIT.USAGE);
|
|
91
|
+
}
|
|
92
|
+
const cfg = readConfig();
|
|
93
|
+
cfg[key] = value;
|
|
94
|
+
writeConfig(cfg);
|
|
95
|
+
status(`Set ${key}.`);
|
|
96
|
+
});
|
|
97
|
+
config
|
|
98
|
+
.command('path')
|
|
99
|
+
.description('Print the config file path')
|
|
100
|
+
.action(() => status(configFile()));
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AACrC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EACL,UAAU,EACV,UAAU,EACV,WAAW,EACX,eAAe,EACf,WAAW,GACZ,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAErC,4EAA4E;AAC5E,6EAA6E;AAC7E,8CAA8C;AAC9C,KAAK,UAAU,KAAK,CAAC,MAAc,EAAE,KAAa;IAChD,IAAI,CAAC;QACH,MAAM,IAAI,SAAS,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,UAAU,EAAE,CAAA;QAC/C,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,EAAE,CAAA;IAC3C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,QAAQ,IAAI,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC,OAAO,EAAE,CAAC;YAC7D,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAA;QAC1C,CAAC;QACD,MAAM,GAAG,CAAA;IACX,CAAC;AACH,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,4CAA4C,CAAC,CAAA;IAE9F,IAAI;SACD,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,0EAA0E,CAAC;QACxF,yEAAyE;QACzE,qEAAqE;QACrE,oEAAoE;QACpE,qEAAqE;SACpE,MAAM,CAAC,iBAAiB,EAAE,sCAAsC,CAAC;SACjE,MAAM,CAAC,KAAK,EAAE,IAAyB,EAAE,GAAY,EAAE,EAAE;QACxD,MAAM,CAAC,GAAG,GAAG,CAAC,eAAe,EAAgB,CAAA;QAC7C,gEAAgE;QAChE,uEAAuE;QACvE,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC;YACb,MAAM,IAAI,QAAQ,CAAC,8EAA8E,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;QAChH,CAAC;QACD,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAA;QACtE,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAA;QACpD,WAAW,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAA;QACzF,MAAM,CAAC,gBAAgB,QAAQ,CAAC,MAAM,OAAO,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACpE,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YACzB,MAAM,CAAC,6GAA6G,CAAC,CAAA;QACvH,CAAC;IACH,CAAC,CAAC,CAAA;IAEJ,IAAI;SACD,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,sDAAsD,CAAC;SACnE,MAAM,CAAC,KAAK,EAAE,KAAc,EAAE,GAAY,EAAE,EAAE;QAC7C,MAAM,CAAC,GAAG,GAAG,CAAC,eAAe,EAAgB,CAAA;QAC7C,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAA;QACxF,MAAM,CAAC,YAAY,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;QACrC,MAAM,CAAC,YAAY,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA;QACjD,MAAM,CAAC,YAAY,QAAQ,CAAC,MAAM,IAAI,QAAQ,EAAE,CAAC,CAAA;QACjD,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;YACpB,MAAM,IAAI,QAAQ,CAAC,0DAA0D,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;QAC3F,CAAC;QACD,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;QAC3D,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,qCAAqC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAA;IACnF,CAAC,CAAC,CAAA;IAEJ,IAAI;SACD,OAAO,CAAC,QAAQ,CAAC;SACjB,WAAW,CAAC,yBAAyB,CAAC;SACtC,MAAM,CAAC,GAAG,EAAE;QACX,MAAM,GAAG,GAAG,UAAU,EAAE,CAAA;QACxB,OAAO,GAAG,CAAC,KAAK,CAAA;QAChB,WAAW,CAAC,GAAG,CAAC,CAAA;QAChB,MAAM,CAAC,6BAA6B,CAAC,CAAA;IACvC,CAAC,CAAC,CAAA;IAEJ,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,kCAAkC,CAAC,CAAA;IAExF,MAAM;SACH,OAAO,CAAC,WAAW,CAAC;SACpB,WAAW,CAAC,wCAAwC,CAAC;SACrD,MAAM,CAAC,CAAC,GAAW,EAAE,EAAE;QACtB,MAAM,GAAG,GAAG,UAAU,EAAE,CAAA;QACxB,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAA;YAC9B,OAAM;QACR,CAAC;QACD,MAAM,KAAK,GAAI,GAA+B,CAAC,GAAG,CAAC,CAAA;QACnD,MAAM,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;IAEJ,MAAM;SACH,OAAO,CAAC,mBAAmB,CAAC;SAC5B,WAAW,CAAC,uDAAuD,CAAC;SACpE,MAAM,CAAC,CAAC,GAAW,EAAE,KAAa,EAAE,EAAE;QACrC,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,QAAQ,CAChB,oHAAoH,EACpH,IAAI,CAAC,KAAK,CACX,CAAA;QACH,CAAC;QACD,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;YACzC,MAAM,IAAI,QAAQ,CAAC,uBAAuB,GAAG,8BAA8B,EAAE,IAAI,CAAC,KAAK,CAAC,CAAA;QAC1F,CAAC;QACD,MAAM,GAAG,GAAG,UAAU,EAAE,CACvB;QAAC,GAA+B,CAAC,GAAG,CAAC,GAAG,KAAK,CAAA;QAC9C,WAAW,CAAC,GAAG,CAAC,CAAA;QAChB,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,CAAA;IACvB,CAAC,CAAC,CAAA;IAEJ,MAAM;SACH,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,4BAA4B,CAAC;SACzC,MAAM,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAA;AACvC,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
5
|
+
import { buildProgram, run } from '../index.js';
|
|
6
|
+
import { EXIT } from '../errors.js';
|
|
7
|
+
function okFetch(body) {
|
|
8
|
+
return vi.fn().mockResolvedValue({ ok: true, status: 200, text: async () => JSON.stringify(body) });
|
|
9
|
+
}
|
|
10
|
+
function errFetch(status, body) {
|
|
11
|
+
return vi.fn().mockResolvedValue({ ok: false, status, text: async () => JSON.stringify(body) });
|
|
12
|
+
}
|
|
13
|
+
let tmpDir;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'abe-cli-auth-'));
|
|
16
|
+
process.env.ABE_CONFIG_DIR = tmpDir;
|
|
17
|
+
delete process.env.ABE_TOKEN;
|
|
18
|
+
delete process.env.ABE_API_URL;
|
|
19
|
+
});
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
vi.unstubAllGlobals();
|
|
22
|
+
vi.restoreAllMocks();
|
|
23
|
+
delete process.env.ABE_CONFIG_DIR;
|
|
24
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
25
|
+
});
|
|
26
|
+
function readWrittenConfig() {
|
|
27
|
+
return JSON.parse(fs.readFileSync(path.join(tmpDir, 'config.json'), 'utf8'));
|
|
28
|
+
}
|
|
29
|
+
describe('auth login', () => {
|
|
30
|
+
// Regression: --token/--api-url were declared BOTH as program globals and on
|
|
31
|
+
// the login subcommand; the global swallowed the value while the subcommand's
|
|
32
|
+
// requiredOption still errored — `abe auth login --token X` could NEVER
|
|
33
|
+
// succeed. The flag now flows through optsWithGlobals in either position.
|
|
34
|
+
it('logs in with --token after the subcommand (the previously-broken form)', async () => {
|
|
35
|
+
const f = okFetch({ boards: [] });
|
|
36
|
+
vi.stubGlobal('fetch', f);
|
|
37
|
+
await buildProgram().parseAsync([
|
|
38
|
+
'node', 'abe', 'auth', 'login',
|
|
39
|
+
'--token', 'abe_pat_secret1234', '--api-url', 'http://h',
|
|
40
|
+
]);
|
|
41
|
+
expect(f).toHaveBeenCalledOnce();
|
|
42
|
+
expect(String(f.mock.calls[0][0])).toBe('http://h/api/v1/task_boards');
|
|
43
|
+
expect(f.mock.calls[0][1].headers).toMatchObject({
|
|
44
|
+
Authorization: 'Bearer abe_pat_secret1234',
|
|
45
|
+
});
|
|
46
|
+
expect(readWrittenConfig()).toMatchObject({ apiUrl: 'http://h', token: 'abe_pat_secret1234' });
|
|
47
|
+
});
|
|
48
|
+
it('logs in with the global --token before the subcommand', async () => {
|
|
49
|
+
vi.stubGlobal('fetch', okFetch({ boards: [] }));
|
|
50
|
+
await buildProgram().parseAsync([
|
|
51
|
+
'node', 'abe', '--token', 'abe_pat_secret1234', '--api-url', 'http://h',
|
|
52
|
+
'auth', 'login', '--tenant', 'acme',
|
|
53
|
+
]);
|
|
54
|
+
expect(readWrittenConfig()).toMatchObject({
|
|
55
|
+
apiUrl: 'http://h',
|
|
56
|
+
token: 'abe_pat_secret1234',
|
|
57
|
+
tenant: 'acme',
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
it('exits 2 with a usage hint when --token is missing', async () => {
|
|
61
|
+
const writes = [];
|
|
62
|
+
vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
|
|
63
|
+
writes.push(String(chunk));
|
|
64
|
+
return true;
|
|
65
|
+
});
|
|
66
|
+
const fetchSpy = vi.fn();
|
|
67
|
+
vi.stubGlobal('fetch', fetchSpy);
|
|
68
|
+
const code = await run(['node', 'abe', 'auth', 'login']);
|
|
69
|
+
expect(code).toBe(EXIT.USAGE);
|
|
70
|
+
expect(fetchSpy).not.toHaveBeenCalled();
|
|
71
|
+
expect(fs.existsSync(path.join(tmpDir, 'config.json'))).toBe(false);
|
|
72
|
+
const combined = writes.join('');
|
|
73
|
+
expect(combined).toContain('Missing --token');
|
|
74
|
+
expect(combined).toContain('abe auth login --token');
|
|
75
|
+
});
|
|
76
|
+
it('treats a 402 billing-locked probe as a successful login with a warning', async () => {
|
|
77
|
+
const writes = [];
|
|
78
|
+
vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
|
|
79
|
+
writes.push(String(chunk));
|
|
80
|
+
return true;
|
|
81
|
+
});
|
|
82
|
+
vi.stubGlobal('fetch', errFetch(402, { error: 'billing_locked' }));
|
|
83
|
+
await buildProgram().parseAsync([
|
|
84
|
+
'node', 'abe', 'auth', 'login', '--token', 'abe_pat_secret1234', '--api-url', 'http://h',
|
|
85
|
+
]);
|
|
86
|
+
expect(readWrittenConfig()).toMatchObject({ token: 'abe_pat_secret1234' });
|
|
87
|
+
expect(writes.join('')).toMatch(/locked for billing/i);
|
|
88
|
+
});
|
|
89
|
+
it('does not store the token when the probe rejects it (401)', async () => {
|
|
90
|
+
vi.stubGlobal('fetch', errFetch(401, { error: 'unauthorized' }));
|
|
91
|
+
const code = await run([
|
|
92
|
+
'node', 'abe', 'auth', 'login', '--token', 'abe_pat_wrong', '--api-url', 'http://h',
|
|
93
|
+
]);
|
|
94
|
+
expect(code).toBe(EXIT.AUTH);
|
|
95
|
+
expect(fs.existsSync(path.join(tmpDir, 'config.json'))).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
it('never prints the full token', async () => {
|
|
98
|
+
const writes = [];
|
|
99
|
+
vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
|
|
100
|
+
writes.push(String(chunk));
|
|
101
|
+
return true;
|
|
102
|
+
});
|
|
103
|
+
vi.stubGlobal('fetch', okFetch({ boards: [] }));
|
|
104
|
+
await buildProgram().parseAsync([
|
|
105
|
+
'node', 'abe', 'auth', 'login', '--token', 'abe_pat_secret1234', '--api-url', 'http://h',
|
|
106
|
+
]);
|
|
107
|
+
const combined = writes.join('');
|
|
108
|
+
expect(combined).not.toContain('abe_pat_secret1234');
|
|
109
|
+
expect(combined).toContain('…1234');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
describe('usage errors', () => {
|
|
113
|
+
// Regression: Commander writes usage errors to stderr itself (exitOverride
|
|
114
|
+
// only suppresses process.exit); run() used to write the message AGAIN, so
|
|
115
|
+
// every usage error appeared twice.
|
|
116
|
+
it('prints a commander usage error exactly once', async () => {
|
|
117
|
+
const writes = [];
|
|
118
|
+
vi.spyOn(process.stderr, 'write').mockImplementation((chunk) => {
|
|
119
|
+
writes.push(String(chunk));
|
|
120
|
+
return true;
|
|
121
|
+
});
|
|
122
|
+
const code = await run(['node', 'abe', 'task', 'move', '99']);
|
|
123
|
+
expect(code).toBe(EXIT.USAGE);
|
|
124
|
+
const combined = writes.join('');
|
|
125
|
+
const occurrences = combined.match(/required option '--column <key>' not specified/g) ?? [];
|
|
126
|
+
expect(occurrences).toHaveLength(1);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
//# sourceMappingURL=auth.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.test.js","sourceRoot":"","sources":["../../src/commands/auth.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,EAAE,MAAM,SAAS,CAAA;AACxB,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AAExE,OAAO,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAC/C,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AAEnC,SAAS,OAAO,CAAC,IAAa;IAC5B,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAc,CAAC,CAAA;AACjH,CAAC;AAED,SAAS,QAAQ,CAAC,MAAc,EAAE,IAAa;IAC7C,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAc,CAAC,CAAA;AAC7G,CAAC;AAED,IAAI,MAAc,CAAA;AAElB,UAAU,CAAC,GAAG,EAAE;IACd,MAAM,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC,CAAA;IAChE,OAAO,CAAC,GAAG,CAAC,cAAc,GAAG,MAAM,CAAA;IACnC,OAAO,OAAO,CAAC,GAAG,CAAC,SAAS,CAAA;IAC5B,OAAO,OAAO,CAAC,GAAG,CAAC,WAAW,CAAA;AAChC,CAAC,CAAC,CAAA;AAEF,SAAS,CAAC,GAAG,EAAE;IACb,EAAE,CAAC,gBAAgB,EAAE,CAAA;IACrB,EAAE,CAAC,eAAe,EAAE,CAAA;IACpB,OAAO,OAAO,CAAC,GAAG,CAAC,cAAc,CAAA;IACjC,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACrD,CAAC,CAAC,CAAA;AAEF,SAAS,iBAAiB;IACxB,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,CAAC,CAAC,CAAA;AAC9E,CAAC;AAED,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,6EAA6E;IAC7E,8EAA8E;IAC9E,wEAAwE;IACxE,0EAA0E;IAC1E,EAAE,CAAC,wEAAwE,EAAE,KAAK,IAAI,EAAE;QACtF,MAAM,CAAC,GAAG,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAA;QACjC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;QACzB,MAAM,YAAY,EAAE,CAAC,UAAU,CAAC;YAC9B,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO;YAC9B,SAAS,EAAE,oBAAoB,EAAE,WAAW,EAAE,UAAU;SACzD,CAAC,CAAA;QACF,MAAM,CAAC,CAAC,CAAC,CAAC,oBAAoB,EAAE,CAAA;QAChC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAA;QACtE,MAAM,CAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAiB,CAAC,OAAO,CAAC,CAAC,aAAa,CAAC;YAChE,aAAa,EAAE,2BAA2B;SAC3C,CAAC,CAAA;QACF,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAA;IAChG,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;QACrE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;QAC/C,MAAM,YAAY,EAAE,CAAC,UAAU,CAAC;YAC9B,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,WAAW,EAAE,UAAU;YACvE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM;SACpC,CAAC,CAAA;QACF,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,aAAa,CAAC;YACxC,MAAM,EAAE,UAAU;YAClB,KAAK,EAAE,oBAAoB;YAC3B,MAAM,EAAE,MAAM;SACf,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,MAAM,GAAa,EAAE,CAAA;QAC3B,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,KAAK,EAAE,EAAE;YAC7D,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;YAC1B,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,CAAA;QACF,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAA;QACxB,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;QAChC,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;QACxD,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC7B,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAA;QACvC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACnE,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAChC,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAA;QAC7C,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAA;IACtD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,wEAAwE,EAAE,KAAK,IAAI,EAAE;QACtF,MAAM,MAAM,GAAa,EAAE,CAAA;QAC3B,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,KAAK,EAAE,EAAE;YAC7D,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;YAC1B,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,CAAA;QACF,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAA;QAClE,MAAM,YAAY,EAAE,CAAC,UAAU,CAAC;YAC9B,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,WAAW,EAAE,UAAU;SACzF,CAAC,CAAA;QACF,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,aAAa,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAA;QAC1E,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAA;IACxD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,0DAA0D,EAAE,KAAK,IAAI,EAAE;QACxE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,CAAA;QAChE,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC;YACrB,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,WAAW,EAAE,UAAU;SACpF,CAAC,CAAA;QACF,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QAC5B,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACrE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC3C,MAAM,MAAM,GAAa,EAAE,CAAA;QAC3B,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,KAAK,EAAE,EAAE;YAC7D,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;YAC1B,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,CAAA;QACF,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;QAC/C,MAAM,YAAY,EAAE,CAAC,UAAU,CAAC;YAC9B,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,WAAW,EAAE,UAAU;SACzF,CAAC,CAAA;QACF,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAChC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAA;QACpD,MAAM,CAAC,QAAQ,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA;AAEF,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;IAC5B,2EAA2E;IAC3E,2EAA2E;IAC3E,oCAAoC;IACpC,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,MAAM,GAAa,EAAE,CAAA;QAC3B,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,kBAAkB,CAAC,CAAC,KAAK,EAAE,EAAE;YAC7D,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;YAC1B,OAAO,IAAI,CAAA;QACb,CAAC,CAAC,CAAA;QACF,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAA;QAC7D,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC7B,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAChC,MAAM,WAAW,GAAG,QAAQ,CAAC,KAAK,CAAC,iDAAiD,CAAC,IAAI,EAAE,CAAA;QAC3F,MAAM,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAA;IACrC,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { buildContext } from '../context.js';
|
|
2
|
+
import { printBoards, emit, status } from '../output.js';
|
|
3
|
+
function parseColumns(csv) {
|
|
4
|
+
if (!csv)
|
|
5
|
+
return undefined;
|
|
6
|
+
return csv
|
|
7
|
+
.split(',')
|
|
8
|
+
.map((c) => c.trim().toLowerCase().replace(/[\s-]+/g, '_'))
|
|
9
|
+
.filter(Boolean);
|
|
10
|
+
}
|
|
11
|
+
export function registerBoardCommands(program) {
|
|
12
|
+
const board = program.command('board').description('Create and list project boards');
|
|
13
|
+
board
|
|
14
|
+
.command('create <name>')
|
|
15
|
+
.description('Create a custom project board (app/github boards are managed inside ABE)')
|
|
16
|
+
.option('--description <text>')
|
|
17
|
+
.option('--columns <csv>', 'comma-separated column keys (default: backlog,todo,in_progress,in_review,done)')
|
|
18
|
+
.option('--display-order <n>', 'sort order', (v) => Number(v))
|
|
19
|
+
.action(async (name, opts, cmd) => {
|
|
20
|
+
const { client, json } = buildContext(cmd.optsWithGlobals());
|
|
21
|
+
const { board: created } = await client.createBoard({
|
|
22
|
+
name,
|
|
23
|
+
description: opts.description,
|
|
24
|
+
columns: parseColumns(opts.columns),
|
|
25
|
+
display_order: opts.displayOrder,
|
|
26
|
+
});
|
|
27
|
+
if (json)
|
|
28
|
+
emit({ board: created }, { json });
|
|
29
|
+
else
|
|
30
|
+
status(`Created board "${created.name}" (slug: ${created.slug}, id: ${created.id})`);
|
|
31
|
+
});
|
|
32
|
+
board
|
|
33
|
+
.command('list')
|
|
34
|
+
.description('List project boards')
|
|
35
|
+
.action(async (_opts, cmd) => {
|
|
36
|
+
const { client, json } = buildContext(cmd.optsWithGlobals());
|
|
37
|
+
const { boards } = await client.listBoards();
|
|
38
|
+
printBoards(boards, { json });
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=boards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boards.js","sourceRoot":"","sources":["../../src/commands/boards.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAc,MAAM,eAAe,CAAA;AACxD,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAExD,SAAS,YAAY,CAAC,GAAY;IAChC,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAA;IAC1B,OAAO,GAAG;SACP,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;SAC1D,MAAM,CAAC,OAAO,CAAC,CAAA;AACpB,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,OAAgB;IACpD,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,WAAW,CAAC,gCAAgC,CAAC,CAAA;IAEpF,KAAK;SACF,OAAO,CAAC,eAAe,CAAC;SACxB,WAAW,CAAC,0EAA0E,CAAC;SACvF,MAAM,CAAC,sBAAsB,CAAC;SAC9B,MAAM,CAAC,iBAAiB,EAAE,gFAAgF,CAAC;SAC3G,MAAM,CAAC,qBAAqB,EAAE,YAAY,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;SAC7D,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,IAAuE,EAAE,GAAY,EAAE,EAAE;QACpH,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,YAAY,CAAC,GAAG,CAAC,eAAe,EAAgB,CAAC,CAAA;QAC1E,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC;YAClD,IAAI;YACJ,WAAW,EAAE,IAAI,CAAC,WAAW;YAC7B,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC;YACnC,aAAa,EAAE,IAAI,CAAC,YAAY;SACjC,CAAC,CAAA;QACF,IAAI,IAAI;YAAE,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;;YACvC,MAAM,CAAC,kBAAkB,OAAO,CAAC,IAAI,YAAY,OAAO,CAAC,IAAI,SAAS,OAAO,CAAC,EAAE,GAAG,CAAC,CAAA;IAC3F,CAAC,CAAC,CAAA;IAEJ,KAAK;SACF,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,qBAAqB,CAAC;SAClC,MAAM,CAAC,KAAK,EAAE,KAAc,EAAE,GAAY,EAAE,EAAE;QAC7C,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,YAAY,CAAC,GAAG,CAAC,eAAe,EAAgB,CAAC,CAAA;QAC1E,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAA;QAC5C,WAAW,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAA;IAC/B,CAAC,CAAC,CAAA;AACN,CAAC"}
|