@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 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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"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"}
@@ -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
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import { run } from './index.js';
3
+ run().then((code) => process.exit(code), (err) => {
4
+ process.stderr.write(`${err?.message ?? 'Unknown error'}\n`);
5
+ process.exit(1);
6
+ });
7
+ //# sourceMappingURL=cli.js.map
@@ -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":"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"}
@@ -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":"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"}
@@ -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"}