@sascha384/tic 2.1.0 → 3.0.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/.claude-plugin/plugin.json +1 -1
- package/README.md +32 -17
- package/dist/app.js +7 -2
- package/dist/app.js.map +1 -1
- package/dist/auth/ado.d.ts +31 -0
- package/dist/auth/ado.js +136 -0
- package/dist/auth/ado.js.map +1 -0
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/backends/ado/api.d.ts +19 -0
- package/dist/backends/ado/api.js +110 -0
- package/dist/backends/ado/api.js.map +1 -0
- package/dist/backends/ado/index.d.ts +6 -12
- package/dist/backends/ado/index.js +201 -349
- package/dist/backends/ado/index.js.map +1 -1
- package/dist/backends/factory.js +1 -1
- package/dist/backends/factory.js.map +1 -1
- package/dist/cli/commands/auth.d.ts +5 -3
- package/dist/cli/commands/auth.js +39 -4
- package/dist/cli/commands/auth.js.map +1 -1
- package/dist/cli/commands/mcp.js +17 -17
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/index.js +5 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/components/AuthPrompt.d.ts +1 -0
- package/dist/components/AuthPrompt.js +37 -0
- package/dist/components/AuthPrompt.js.map +1 -0
- package/dist/components/Header.js +10 -2
- package/dist/components/Header.js.map +1 -1
- package/dist/components/StatusScreen.js +2 -1
- package/dist/components/StatusScreen.js.map +1 -1
- package/dist/stores/backendDataStore.d.ts +15 -0
- package/dist/stores/backendDataStore.js +81 -5
- package/dist/stores/backendDataStore.js.map +1 -1
- package/package.json +1 -1
- package/dist/backends/ado/az.d.ts +0 -28
- package/dist/backends/ado/az.js +0 -162
- package/dist/backends/ado/az.js.map +0 -1
package/README.md
CHANGED
|
@@ -7,7 +7,8 @@ Built with TypeScript and [Ink](https://github.com/vadimdemedes/ink).
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **Keyboard-driven TUI** — browse, create, edit, and manage work items without leaving the terminal
|
|
10
|
-
- **Multiple backends** — GitHub (
|
|
10
|
+
- **Multiple backends** — GitHub (REST/GraphQL API), GitLab (via `glab`), Azure DevOps (REST API), Jira (REST API)
|
|
11
|
+
- **Built-in authentication** — OAuth device flow for GitHub and Azure DevOps, with PAT fallback
|
|
11
12
|
- **Automatic backend detection** — selects backend based on git remote, or configure manually
|
|
12
13
|
- **SQLite storage** — all data stored locally in `.tic/tic.db` with optional sync to remote backends
|
|
13
14
|
- **CLI commands** — scriptable commands for all operations (`tic item list`, `tic item create`, etc.)
|
|
@@ -186,40 +187,54 @@ tic iteration list # List iterations
|
|
|
186
187
|
tic iteration set sprint-2 # Set current iteration
|
|
187
188
|
tic config get backend # Get config value
|
|
188
189
|
tic config set backend github # Set config value
|
|
190
|
+
tic auth login github # Authenticate with GitHub (OAuth device flow)
|
|
191
|
+
tic auth login azure # Authenticate with Azure DevOps (Entra ID)
|
|
192
|
+
tic auth login azure --pat # Authenticate with a Personal Access Token
|
|
193
|
+
tic auth status # Show authentication status
|
|
194
|
+
tic auth logout github # Remove stored credentials
|
|
189
195
|
```
|
|
190
196
|
|
|
191
197
|
Add `--json` to any command for machine-readable output, or `--quiet` to suppress non-essential output.
|
|
192
198
|
|
|
193
199
|
## Backends
|
|
194
200
|
|
|
195
|
-
| Backend |
|
|
196
|
-
|
|
201
|
+
| Backend | Auth Method | Detection |
|
|
202
|
+
|---------|-------------|-----------|
|
|
197
203
|
| Local only (SQLite) | — | Default fallback |
|
|
198
|
-
| GitHub Issues |
|
|
199
|
-
| GitLab Issues | [`glab`](https://gitlab.com/gitlab-org/cli) | `gitlab.com` in git remote |
|
|
200
|
-
| Azure DevOps Work Items |
|
|
201
|
-
| Jira | REST API | Configured via settings |
|
|
204
|
+
| GitHub Issues | `tic auth login github` (OAuth) or existing `gh` token | `github.com` in git remote |
|
|
205
|
+
| GitLab Issues | [`glab`](https://gitlab.com/gitlab-org/cli) CLI | `gitlab.com` in git remote |
|
|
206
|
+
| Azure DevOps Work Items | `tic auth login azure` (Entra ID) or `--pat` | `dev.azure.com` or `visualstudio.com` in git remote |
|
|
207
|
+
| Jira | REST API (configured in settings) | Configured via settings |
|
|
202
208
|
|
|
203
209
|
Each backend supports a different set of capabilities (types, statuses, iterations, relationships, etc.). The TUI and CLI automatically adapt to show only what the active backend supports.
|
|
204
210
|
|
|
205
211
|
You can switch backends from within the TUI by pressing `,` to open settings. For Jira, you'll need to configure your site URL, project key, and optionally a board ID.
|
|
206
212
|
|
|
207
|
-
###
|
|
213
|
+
### Authentication
|
|
208
214
|
|
|
209
|
-
Azure DevOps
|
|
215
|
+
GitHub and Azure DevOps use tic's built-in authentication with credentials stored securely in your OS keychain. If not already authenticated, the TUI will prompt you to log in when a remote backend is detected.
|
|
210
216
|
|
|
211
|
-
|
|
212
|
-
|--------|---------|-----------------|
|
|
213
|
-
| PAT (Personal Access Token) | `az devops login` | Work items, queries, iterations, relations |
|
|
214
|
-
| Azure AD / Entra ID | `az login` | Comments (read and write) |
|
|
217
|
+
**GitHub:**
|
|
215
218
|
|
|
216
|
-
|
|
219
|
+
```bash
|
|
220
|
+
tic auth login github # Opens browser for OAuth device flow
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Falls back to an existing `gh` CLI token if available.
|
|
224
|
+
|
|
225
|
+
**Azure DevOps:**
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
tic auth login azure # Entra ID device code flow (recommended)
|
|
229
|
+
tic auth login azure --pat # Personal Access Token
|
|
230
|
+
```
|
|
217
231
|
|
|
218
|
-
|
|
232
|
+
**Managing credentials:**
|
|
219
233
|
|
|
220
234
|
```bash
|
|
221
|
-
|
|
222
|
-
|
|
235
|
+
tic auth status # Show auth status for all providers
|
|
236
|
+
tic auth logout github # Remove stored credentials
|
|
237
|
+
tic auth logout azure
|
|
223
238
|
```
|
|
224
239
|
|
|
225
240
|
## Contributing
|
package/dist/app.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { lazy, Suspense, useEffect } from 'react';
|
|
3
3
|
import { Box } from 'ink';
|
|
4
4
|
import { WorkItemList } from './components/WorkItemList.js';
|
|
5
5
|
import { Header } from './components/Header.js';
|
|
6
6
|
import { useConfigStore } from './stores/configStore.js';
|
|
7
|
+
import { useBackendDataStore } from './stores/backendDataStore.js';
|
|
7
8
|
import { navigationStore, useNavigationStore, } from './stores/navigationStore.js';
|
|
8
9
|
// Lazy — loaded on demand when screen changes
|
|
9
10
|
const WorkItemForm = lazy(() => import('./components/WorkItemForm.js').then((m) => ({
|
|
@@ -19,10 +20,14 @@ const StatusScreen = lazy(() => import('./components/StatusScreen.js').then((m)
|
|
|
19
20
|
const HelpScreen = lazy(() => import('./components/HelpScreen.js').then((m) => ({
|
|
20
21
|
default: m.HelpScreen,
|
|
21
22
|
})));
|
|
23
|
+
const AuthPrompt = lazy(() => import('./components/AuthPrompt.js').then((m) => ({
|
|
24
|
+
default: m.AuthPrompt,
|
|
25
|
+
})));
|
|
22
26
|
export function App() {
|
|
23
27
|
const screen = useNavigationStore((s) => s.screen);
|
|
24
28
|
const previousScreen = useNavigationStore((s) => s.previousScreen);
|
|
25
29
|
const autoUpdate = useConfigStore((s) => s.config.autoUpdate);
|
|
30
|
+
const authPrompt = useBackendDataStore((s) => s.authPrompt);
|
|
26
31
|
// Update check on mount
|
|
27
32
|
useEffect(() => {
|
|
28
33
|
if (autoUpdate !== false) {
|
|
@@ -32,6 +37,6 @@ export function App() {
|
|
|
32
37
|
}));
|
|
33
38
|
}
|
|
34
39
|
}, [autoUpdate]);
|
|
35
|
-
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, {}), screen === 'list' && _jsx(WorkItemList, {}), _jsxs(Suspense, { fallback: null, children: [screen === 'form' && _jsx(WorkItemForm, {}), screen === 'iteration-picker' && _jsx(IterationPicker, {}), screen === 'settings' && _jsx(Settings, {}), screen === 'status' && _jsx(StatusScreen, {}), screen === 'help' && _jsx(HelpScreen, { sourceScreen: previousScreen })] })] }));
|
|
40
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, {}), authPrompt ? (_jsx(Suspense, { fallback: null, children: _jsx(AuthPrompt, {}) })) : (_jsxs(_Fragment, { children: [screen === 'list' && _jsx(WorkItemList, {}), _jsxs(Suspense, { fallback: null, children: [screen === 'form' && _jsx(WorkItemForm, {}), screen === 'iteration-picker' && _jsx(IterationPicker, {}), screen === 'settings' && _jsx(Settings, {}), screen === 'status' && _jsx(StatusScreen, {}), screen === 'help' && _jsx(HelpScreen, { sourceScreen: previousScreen })] })] }))] }));
|
|
36
41
|
}
|
|
37
42
|
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClD,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAC5D,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EACL,eAAe,EACf,kBAAkB,GACnB,MAAM,6BAA6B,CAAC;AAKrC,8CAA8C;AAC9C,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAC7B,MAAM,CAAC,8BAA8B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,EAAE,CAAC,CAAC,YAAY;CACxB,CAAC,CAAC,CACJ,CAAC;AACF,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,EAAE,CAChC,MAAM,CAAC,iCAAiC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrD,OAAO,EAAE,CAAC,CAAC,eAAe;CAC3B,CAAC,CAAC,CACJ,CAAC;AACF,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CACzB,MAAM,CAAC,0BAA0B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAC1E,CAAC;AACF,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG,EAAE,CAC7B,MAAM,CAAC,8BAA8B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAClD,OAAO,EAAE,CAAC,CAAC,YAAY;CACxB,CAAC,CAAC,CACJ,CAAC;AACF,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,CAC3B,MAAM,CAAC,4BAA4B,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChD,OAAO,EAAE,CAAC,CAAC,UAAU;CACtB,CAAC,CAAC,CACJ,CAAC;AAEF,MAAM,UAAU,GAAG;IACjB,MAAM,MAAM,GAAG,kBAAkB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IACnD,MAAM,cAAc,GAAG,kBAAkB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IACnE,MAAM,UAAU,GAAG,cAAc,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const ADO_ACCOUNT = "dev.azure.com";
|
|
2
|
+
export declare const ADO_REFRESH_ACCOUNT = "dev.azure.com:refresh";
|
|
3
|
+
export declare const ADO_PAT_ACCOUNT = "dev.azure.com:pat";
|
|
4
|
+
export declare const AZURE_CLI_CLIENT_ID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46";
|
|
5
|
+
export declare function getAdoToken(): string | null;
|
|
6
|
+
export declare function getAdoRefreshToken(): string | null;
|
|
7
|
+
export declare function getAdoPat(): string | null;
|
|
8
|
+
export declare function setAdoPat(pat: string): void;
|
|
9
|
+
export declare function clearAdoTokens(): void;
|
|
10
|
+
export interface AuthenticateAdoOptions {
|
|
11
|
+
onCode?: (userCode: string, verificationUri: string) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Refresh an expired access token using the stored refresh token.
|
|
15
|
+
* Returns the new access token, or null if refresh fails.
|
|
16
|
+
*/
|
|
17
|
+
export declare function refreshAdoToken(refreshToken: string): Promise<string | null>;
|
|
18
|
+
/**
|
|
19
|
+
* Run the Entra ID device code flow to authenticate with Azure DevOps.
|
|
20
|
+
*
|
|
21
|
+
* 1. Requests a device code from Entra ID
|
|
22
|
+
* 2. Calls onCode callback so the caller can display the code/URL
|
|
23
|
+
* 3. Polls for the access token at the specified interval
|
|
24
|
+
* 4. Stores access + refresh tokens in keychain on success
|
|
25
|
+
*
|
|
26
|
+
* Note: Entra ID returns HTTP 400 (not 200) for pending/slow_down/denied
|
|
27
|
+
* errors during polling, unlike GitHub which returns 200 with error in body.
|
|
28
|
+
*
|
|
29
|
+
* @returns The access token
|
|
30
|
+
*/
|
|
31
|
+
export declare function authenticateAdo(options?: AuthenticateAdoOptions): Promise<string>;
|
package/dist/auth/ado.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { getToken, setToken, deleteToken } from './keychain.js';
|
|
2
|
+
export const ADO_ACCOUNT = 'dev.azure.com';
|
|
3
|
+
export const ADO_REFRESH_ACCOUNT = 'dev.azure.com:refresh';
|
|
4
|
+
export const ADO_PAT_ACCOUNT = 'dev.azure.com:pat';
|
|
5
|
+
// Azure CLI's well-known public client ID — no app registration needed
|
|
6
|
+
export const AZURE_CLI_CLIENT_ID = '04b07795-8ddb-461a-bbee-02f9e1bf7b46';
|
|
7
|
+
const AUTHORITY = 'https://login.microsoftonline.com/organizations';
|
|
8
|
+
const ADO_SCOPE = '499b84ac-1321-427f-aa17-267ca6975798/.default offline_access';
|
|
9
|
+
export function getAdoToken() {
|
|
10
|
+
return getToken(ADO_ACCOUNT);
|
|
11
|
+
}
|
|
12
|
+
export function getAdoRefreshToken() {
|
|
13
|
+
return getToken(ADO_REFRESH_ACCOUNT);
|
|
14
|
+
}
|
|
15
|
+
export function getAdoPat() {
|
|
16
|
+
return getToken(ADO_PAT_ACCOUNT);
|
|
17
|
+
}
|
|
18
|
+
export function setAdoPat(pat) {
|
|
19
|
+
setToken(ADO_PAT_ACCOUNT, pat);
|
|
20
|
+
}
|
|
21
|
+
export function clearAdoTokens() {
|
|
22
|
+
deleteToken(ADO_ACCOUNT);
|
|
23
|
+
deleteToken(ADO_REFRESH_ACCOUNT);
|
|
24
|
+
deleteToken(ADO_PAT_ACCOUNT);
|
|
25
|
+
}
|
|
26
|
+
function isTokenError(response) {
|
|
27
|
+
return 'error' in response;
|
|
28
|
+
}
|
|
29
|
+
function sleep(ms) {
|
|
30
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
31
|
+
}
|
|
32
|
+
function urlEncode(params) {
|
|
33
|
+
return Object.entries(params)
|
|
34
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
|
|
35
|
+
.join('&');
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Refresh an expired access token using the stored refresh token.
|
|
39
|
+
* Returns the new access token, or null if refresh fails.
|
|
40
|
+
*/
|
|
41
|
+
export async function refreshAdoToken(refreshToken) {
|
|
42
|
+
try {
|
|
43
|
+
const response = await fetch(`${AUTHORITY}/oauth2/v2.0/token`, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
46
|
+
body: urlEncode({
|
|
47
|
+
client_id: AZURE_CLI_CLIENT_ID,
|
|
48
|
+
grant_type: 'refresh_token',
|
|
49
|
+
refresh_token: refreshToken,
|
|
50
|
+
scope: ADO_SCOPE,
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
if (!response.ok)
|
|
54
|
+
return null;
|
|
55
|
+
const data = (await response.json());
|
|
56
|
+
if (isTokenError(data))
|
|
57
|
+
return null;
|
|
58
|
+
setToken(ADO_ACCOUNT, data.access_token);
|
|
59
|
+
if (data.refresh_token) {
|
|
60
|
+
setToken(ADO_REFRESH_ACCOUNT, data.refresh_token);
|
|
61
|
+
}
|
|
62
|
+
return data.access_token;
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Run the Entra ID device code flow to authenticate with Azure DevOps.
|
|
70
|
+
*
|
|
71
|
+
* 1. Requests a device code from Entra ID
|
|
72
|
+
* 2. Calls onCode callback so the caller can display the code/URL
|
|
73
|
+
* 3. Polls for the access token at the specified interval
|
|
74
|
+
* 4. Stores access + refresh tokens in keychain on success
|
|
75
|
+
*
|
|
76
|
+
* Note: Entra ID returns HTTP 400 (not 200) for pending/slow_down/denied
|
|
77
|
+
* errors during polling, unlike GitHub which returns 200 with error in body.
|
|
78
|
+
*
|
|
79
|
+
* @returns The access token
|
|
80
|
+
*/
|
|
81
|
+
export async function authenticateAdo(options) {
|
|
82
|
+
// Step 1: Request device code
|
|
83
|
+
const codeResponse = await fetch(`${AUTHORITY}/oauth2/v2.0/devicecode`, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
86
|
+
body: urlEncode({
|
|
87
|
+
client_id: AZURE_CLI_CLIENT_ID,
|
|
88
|
+
scope: ADO_SCOPE,
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
if (!codeResponse.ok) {
|
|
92
|
+
throw new Error(`Failed to request device code: ${codeResponse.status} ${codeResponse.statusText}`);
|
|
93
|
+
}
|
|
94
|
+
const deviceCode = (await codeResponse.json());
|
|
95
|
+
// Step 2: Notify caller with user code and verification URL
|
|
96
|
+
options?.onCode?.(deviceCode.user_code, deviceCode.verification_uri);
|
|
97
|
+
// Step 3: Poll for access token
|
|
98
|
+
let interval = deviceCode.interval * 1000;
|
|
99
|
+
while (true) {
|
|
100
|
+
await sleep(interval);
|
|
101
|
+
const tokenResponse = await fetch(`${AUTHORITY}/oauth2/v2.0/token`, {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
104
|
+
body: urlEncode({
|
|
105
|
+
client_id: AZURE_CLI_CLIENT_ID,
|
|
106
|
+
grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
|
|
107
|
+
device_code: deviceCode.device_code,
|
|
108
|
+
}),
|
|
109
|
+
});
|
|
110
|
+
// Entra ID returns 400 for pending/slow_down/declined/expired errors
|
|
111
|
+
if (!tokenResponse.ok) {
|
|
112
|
+
const data = (await tokenResponse.json());
|
|
113
|
+
switch (data.error) {
|
|
114
|
+
case 'authorization_pending':
|
|
115
|
+
continue;
|
|
116
|
+
case 'slow_down':
|
|
117
|
+
interval += 5000;
|
|
118
|
+
continue;
|
|
119
|
+
case 'authorization_declined':
|
|
120
|
+
throw new Error('Authorization was denied by the user');
|
|
121
|
+
case 'expired_token':
|
|
122
|
+
throw new Error('Device code has expired. Please restart the authentication flow.');
|
|
123
|
+
default:
|
|
124
|
+
throw new Error(`Authentication failed: ${data.error}${data.error_description ? ` - ${data.error_description}` : ''}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const data = (await tokenResponse.json());
|
|
128
|
+
// Success — store both tokens and return
|
|
129
|
+
setToken(ADO_ACCOUNT, data.access_token);
|
|
130
|
+
if (data.refresh_token) {
|
|
131
|
+
setToken(ADO_REFRESH_ACCOUNT, data.refresh_token);
|
|
132
|
+
}
|
|
133
|
+
return data.access_token;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=ado.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ado.js","sourceRoot":"","sources":["../../src/auth/ado.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { getToken, setToken, deleteToken } from './keychain.js';
|
|
2
2
|
export { GITHUB_ACCOUNT, DEFAULT_CLIENT_ID, getGitHubToken, clearGitHubToken, authenticateGitHub, } from './github.js';
|
|
3
3
|
export type { AuthenticateGitHubOptions } from './github.js';
|
|
4
|
+
export { ADO_ACCOUNT, ADO_REFRESH_ACCOUNT, ADO_PAT_ACCOUNT, AZURE_CLI_CLIENT_ID, getAdoToken, getAdoRefreshToken, getAdoPat, setAdoPat, clearAdoTokens, refreshAdoToken, authenticateAdo, } from './ado.js';
|
|
5
|
+
export type { AuthenticateAdoOptions } from './ado.js';
|
package/dist/auth/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { getToken, setToken, deleteToken } from './keychain.js';
|
|
2
2
|
export { GITHUB_ACCOUNT, DEFAULT_CLIENT_ID, getGitHubToken, clearGitHubToken, authenticateGitHub, } from './github.js';
|
|
3
|
+
export { ADO_ACCOUNT, ADO_REFRESH_ACCOUNT, ADO_PAT_ACCOUNT, AZURE_CLI_CLIENT_ID, getAdoToken, getAdoRefreshToken, getAdoPat, setAdoPat, clearAdoTokens, refreshAdoToken, authenticateAdo, } from './ado.js';
|
|
3
4
|
//# sourceMappingURL=index.js.map
|
package/dist/auth/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAChE,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,kBAAkB,EAClB,SAAS,EACT,SAAS,EACT,cAAc,EACd,eAAe,EACf,eAAe,GAChB,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BaseApiClient } from '../shared/api-client.js';
|
|
2
|
+
export type AdoAuth = {
|
|
3
|
+
type: 'bearer';
|
|
4
|
+
token: string;
|
|
5
|
+
} | {
|
|
6
|
+
type: 'basic';
|
|
7
|
+
pat: string;
|
|
8
|
+
};
|
|
9
|
+
export declare class AdoApiClient extends BaseApiClient {
|
|
10
|
+
private auth;
|
|
11
|
+
constructor(auth: AdoAuth, org: string);
|
|
12
|
+
private getAuthHeader;
|
|
13
|
+
private appendApiVersion;
|
|
14
|
+
protected fetch<T>(method: string, path: string, body?: unknown, contentType?: string): Promise<T>;
|
|
15
|
+
rest<T>(method: string, path: string, body?: unknown, contentType?: string): Promise<T>;
|
|
16
|
+
wiql<T>(project: string, query: string): Promise<T>;
|
|
17
|
+
batchGetWorkItems<T>(ids: number[]): Promise<T>;
|
|
18
|
+
paginate<T>(path: string): AsyncGenerator<T[]>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { BaseApiClient, AuthError } from '../shared/api-client.js';
|
|
2
|
+
import { getAdoRefreshToken, refreshAdoToken } from '../../auth/ado.js';
|
|
3
|
+
const ADO_API_VERSION = '7.1';
|
|
4
|
+
export class AdoApiClient extends BaseApiClient {
|
|
5
|
+
auth;
|
|
6
|
+
constructor(auth, org) {
|
|
7
|
+
const token = auth.type === 'bearer' ? auth.token : auth.pat;
|
|
8
|
+
super(token, `https://dev.azure.com/${org}`);
|
|
9
|
+
this.auth = auth;
|
|
10
|
+
}
|
|
11
|
+
getAuthHeader() {
|
|
12
|
+
if (this.auth.type === 'bearer') {
|
|
13
|
+
return `Bearer ${this.auth.token}`;
|
|
14
|
+
}
|
|
15
|
+
return `Basic ${Buffer.from(`:${this.auth.pat}`).toString('base64')}`;
|
|
16
|
+
}
|
|
17
|
+
appendApiVersion(path) {
|
|
18
|
+
const separator = path.includes('?') ? '&' : '?';
|
|
19
|
+
return `${path}${separator}api-version=${ADO_API_VERSION}`;
|
|
20
|
+
}
|
|
21
|
+
async fetch(method, path, body, contentType) {
|
|
22
|
+
const url = this.baseUrl + this.appendApiVersion(path);
|
|
23
|
+
const headers = {
|
|
24
|
+
Authorization: this.getAuthHeader(),
|
|
25
|
+
Accept: 'application/json',
|
|
26
|
+
};
|
|
27
|
+
const init = { method, headers };
|
|
28
|
+
if (body !== undefined && ['POST', 'PATCH', 'PUT'].includes(method)) {
|
|
29
|
+
headers['Content-Type'] = contentType ?? 'application/json';
|
|
30
|
+
init.body = JSON.stringify(body);
|
|
31
|
+
}
|
|
32
|
+
let response = await globalThis.fetch(url, init);
|
|
33
|
+
this.checkRateLimit(response.headers);
|
|
34
|
+
// Try token refresh on 401 for OAuth auth
|
|
35
|
+
if (response.status === 401 && this.auth.type === 'bearer') {
|
|
36
|
+
const refreshToken = getAdoRefreshToken();
|
|
37
|
+
if (refreshToken) {
|
|
38
|
+
const newToken = await refreshAdoToken(refreshToken);
|
|
39
|
+
if (newToken) {
|
|
40
|
+
this.auth = { type: 'bearer', token: newToken };
|
|
41
|
+
this.token = newToken;
|
|
42
|
+
headers['Authorization'] = `Bearer ${newToken}`;
|
|
43
|
+
response = await globalThis.fetch(url, {
|
|
44
|
+
method,
|
|
45
|
+
headers,
|
|
46
|
+
body: init.body,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (response.status === 401) {
|
|
52
|
+
throw new AuthError();
|
|
53
|
+
}
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const text = await response.text();
|
|
56
|
+
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
57
|
+
}
|
|
58
|
+
return (await response.json());
|
|
59
|
+
}
|
|
60
|
+
async rest(method, path, body, contentType) {
|
|
61
|
+
return this.retry(() => this.fetch(method, path, body, contentType));
|
|
62
|
+
}
|
|
63
|
+
async wiql(project, query) {
|
|
64
|
+
return this.rest('POST', `/${project}/_apis/wit/wiql`, { query });
|
|
65
|
+
}
|
|
66
|
+
async batchGetWorkItems(ids) {
|
|
67
|
+
const CHUNK_SIZE = 200;
|
|
68
|
+
const allValues = [];
|
|
69
|
+
for (let i = 0; i < ids.length; i += CHUNK_SIZE) {
|
|
70
|
+
const chunk = ids.slice(i, i + CHUNK_SIZE);
|
|
71
|
+
const result = await this.rest('POST', '/_apis/wit/workitemsbatch', { ids: chunk, $expand: 4 });
|
|
72
|
+
allValues.push(...result.value);
|
|
73
|
+
}
|
|
74
|
+
return { value: allValues };
|
|
75
|
+
}
|
|
76
|
+
async *paginate(path) {
|
|
77
|
+
let url = this.baseUrl + this.appendApiVersion(path);
|
|
78
|
+
while (url) {
|
|
79
|
+
const headers = {
|
|
80
|
+
Authorization: this.getAuthHeader(),
|
|
81
|
+
Accept: 'application/json',
|
|
82
|
+
};
|
|
83
|
+
const response = await globalThis.fetch(url, {
|
|
84
|
+
method: 'GET',
|
|
85
|
+
headers,
|
|
86
|
+
});
|
|
87
|
+
this.checkRateLimit(response.headers);
|
|
88
|
+
if (response.status === 401) {
|
|
89
|
+
throw new AuthError();
|
|
90
|
+
}
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const text = await response.text();
|
|
93
|
+
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
94
|
+
}
|
|
95
|
+
const json = (await response.json());
|
|
96
|
+
yield json.value;
|
|
97
|
+
const continuationToken = response.headers.get('x-ms-continuationtoken');
|
|
98
|
+
if (continuationToken) {
|
|
99
|
+
const separator = path.includes('?') ? '&' : '?';
|
|
100
|
+
url =
|
|
101
|
+
this.baseUrl +
|
|
102
|
+
this.appendApiVersion(`${path}${separator}continuationToken=${continuationToken}`);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
url = null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/backends/ado/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { BaseBackend } from '../types.js';
|
|
2
2
|
import type { BackendCapabilities } from '../types.js';
|
|
3
3
|
import type { WorkItem, NewWorkItem, NewComment, Comment, Template } from '../../types.js';
|
|
4
|
+
export interface AzureDevOpsBackendOptions {
|
|
5
|
+
skipAuth?: boolean;
|
|
6
|
+
}
|
|
4
7
|
export declare class AzureDevOpsBackend extends BaseBackend {
|
|
5
|
-
private
|
|
8
|
+
private api;
|
|
6
9
|
private org;
|
|
7
10
|
private project;
|
|
8
11
|
private types;
|
|
9
|
-
constructor(
|
|
12
|
+
private constructor();
|
|
13
|
+
static create(cwd: string, options?: AzureDevOpsBackendOptions): Promise<AzureDevOpsBackend>;
|
|
10
14
|
getCapabilities(): BackendCapabilities;
|
|
11
15
|
getStatuses(): Promise<string[]>;
|
|
12
16
|
getWorkItemTypes(): Promise<string[]>;
|
|
@@ -22,16 +26,6 @@ export declare class AzureDevOpsBackend extends BaseBackend {
|
|
|
22
26
|
createWorkItem(data: NewWorkItem): Promise<WorkItem>;
|
|
23
27
|
updateWorkItem(id: string, data: Partial<WorkItem>): Promise<WorkItem>;
|
|
24
28
|
deleteWorkItem(id: string): Promise<void>;
|
|
25
|
-
/**
|
|
26
|
-
* Add a comment to a work item.
|
|
27
|
-
*
|
|
28
|
-
* The Work Item Comments API is preview-only (7.1-preview.4) and cannot be
|
|
29
|
-
* called via `az devops invoke` (it fails to parse preview version strings),
|
|
30
|
-
* so we use `az rest` instead. Unlike `az devops` commands which honor PAT
|
|
31
|
-
* auth from `az devops login`, `az rest --resource` requires an Azure AD
|
|
32
|
-
* token obtained via `az login`. If only PAT auth is available, this method
|
|
33
|
-
* throws a descriptive error directing the user to run `az login`.
|
|
34
|
-
*/
|
|
35
29
|
addComment(workItemId: string, comment: NewComment): Promise<Comment>;
|
|
36
30
|
getChildren(id: string): Promise<WorkItem[]>;
|
|
37
31
|
getDependents(id: string): Promise<WorkItem[]>;
|