@nutteen/ist 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/auth/pkce.d.ts +18 -0
- package/dist/src/auth/pkce.js +89 -0
- package/dist/src/auth/pkce.js.map +1 -0
- package/dist/src/client/api.d.ts +1 -0
- package/dist/src/client/api.js +29 -0
- package/dist/src/client/api.js.map +1 -0
- package/dist/src/commands/auth.d.ts +17 -0
- package/dist/src/commands/auth.js +150 -0
- package/dist/src/commands/auth.js.map +1 -0
- package/dist/src/commands/issue.d.ts +1 -0
- package/dist/src/commands/issue.js +64 -0
- package/dist/src/commands/issue.js.map +1 -0
- package/dist/src/commands/project.d.ts +1 -0
- package/dist/src/commands/project.js +25 -0
- package/dist/src/commands/project.js.map +1 -0
- package/dist/src/config.d.ts +9 -0
- package/dist/src/config.js +21 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +41 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +31 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function generateCodeVerifier(): string;
|
|
2
|
+
export declare function generateCodeChallenge(verifier: string): string;
|
|
3
|
+
export declare function generateState(): string;
|
|
4
|
+
export declare function buildAuthorizationUrl(params: {
|
|
5
|
+
tenantId: string;
|
|
6
|
+
clientId: string;
|
|
7
|
+
scope: string;
|
|
8
|
+
redirectUri: string;
|
|
9
|
+
codeChallenge: string;
|
|
10
|
+
state: string;
|
|
11
|
+
}): string;
|
|
12
|
+
export declare function openBrowser(url: string): void;
|
|
13
|
+
export declare function startCallbackServer(expectedState: string, timeoutMs?: number): Promise<{
|
|
14
|
+
port: number;
|
|
15
|
+
result: Promise<{
|
|
16
|
+
code: string;
|
|
17
|
+
}>;
|
|
18
|
+
}>;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { exec } from "node:child_process";
|
|
3
|
+
import * as http from "node:http";
|
|
4
|
+
export function generateCodeVerifier() {
|
|
5
|
+
// 32 random bytes → base64url (no padding), 43 chars — valid per RFC 7636
|
|
6
|
+
return randomBytes(32).toString("base64url");
|
|
7
|
+
}
|
|
8
|
+
export function generateCodeChallenge(verifier) {
|
|
9
|
+
// SHA256(verifier) → base64url per RFC 7636 §4.2
|
|
10
|
+
return createHash("sha256").update(verifier).digest("base64url");
|
|
11
|
+
}
|
|
12
|
+
export function generateState() {
|
|
13
|
+
return randomBytes(16).toString("hex");
|
|
14
|
+
}
|
|
15
|
+
export function buildAuthorizationUrl(params) {
|
|
16
|
+
const url = new URL(`https://login.microsoftonline.com/${params.tenantId}/oauth2/v2.0/authorize`);
|
|
17
|
+
url.searchParams.set("client_id", params.clientId);
|
|
18
|
+
url.searchParams.set("response_type", "code");
|
|
19
|
+
url.searchParams.set("redirect_uri", params.redirectUri);
|
|
20
|
+
url.searchParams.set("scope", params.scope);
|
|
21
|
+
url.searchParams.set("code_challenge", params.codeChallenge);
|
|
22
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
23
|
+
url.searchParams.set("state", params.state);
|
|
24
|
+
return url.toString();
|
|
25
|
+
}
|
|
26
|
+
export function openBrowser(url) {
|
|
27
|
+
let command;
|
|
28
|
+
const platform = process.platform;
|
|
29
|
+
if (platform === "darwin") {
|
|
30
|
+
command = `open "${url}"`;
|
|
31
|
+
}
|
|
32
|
+
else if (platform === "win32") {
|
|
33
|
+
command = `cmd /c start "" "${url}"`;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
command = `xdg-open "${url}"`;
|
|
37
|
+
}
|
|
38
|
+
exec(command, (err) => {
|
|
39
|
+
if (err) {
|
|
40
|
+
process.stderr.write(`Warning: Could not open browser: ${err.message}\n`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
export async function startCallbackServer(expectedState, timeoutMs = 120_000) {
|
|
45
|
+
return new Promise((resolveSetup) => {
|
|
46
|
+
let resolveResult;
|
|
47
|
+
let rejectResult;
|
|
48
|
+
const result = new Promise((resolve, reject) => {
|
|
49
|
+
resolveResult = resolve;
|
|
50
|
+
rejectResult = reject;
|
|
51
|
+
});
|
|
52
|
+
const server = http.createServer((req, res) => {
|
|
53
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
54
|
+
const error = url.searchParams.get("error");
|
|
55
|
+
const code = url.searchParams.get("code");
|
|
56
|
+
const state = url.searchParams.get("state");
|
|
57
|
+
const success = !error && Boolean(code) && state === expectedState;
|
|
58
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
59
|
+
res.end(`<!doctype html><html><body><p>` +
|
|
60
|
+
(success
|
|
61
|
+
? "Authentication successful. You can close this tab."
|
|
62
|
+
: "Authentication failed. You can close this tab.") +
|
|
63
|
+
`</p></body></html>`);
|
|
64
|
+
server.close();
|
|
65
|
+
if (error) {
|
|
66
|
+
rejectResult(new Error(`Authentication failed: ${error}`));
|
|
67
|
+
}
|
|
68
|
+
else if (state !== expectedState) {
|
|
69
|
+
rejectResult(new Error("State mismatch — possible CSRF attack"));
|
|
70
|
+
}
|
|
71
|
+
else if (!code) {
|
|
72
|
+
rejectResult(new Error("No authorization code received"));
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
resolveResult({ code });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
server.listen(0, () => {
|
|
79
|
+
const { port } = server.address();
|
|
80
|
+
const timeout = setTimeout(() => {
|
|
81
|
+
server.close();
|
|
82
|
+
rejectResult(new Error("Authentication timed out. Please try again."));
|
|
83
|
+
}, timeoutMs);
|
|
84
|
+
result.then(() => clearTimeout(timeout), () => clearTimeout(timeout));
|
|
85
|
+
resolveSetup({ port, result });
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=pkce.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pkce.js","sourceRoot":"","sources":["../../../src/auth/pkce.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function request<T>(path: string, init?: RequestInit): Promise<T>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isTokenExpired, loadConfig } from "../config.js";
|
|
2
|
+
export async function request(path, init = {}) {
|
|
3
|
+
let config;
|
|
4
|
+
try {
|
|
5
|
+
config = await loadConfig();
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
throw new Error("Not authenticated. Run `ist auth login` to log in.");
|
|
9
|
+
}
|
|
10
|
+
if (isTokenExpired(config)) {
|
|
11
|
+
throw new Error("Your session has expired. Run `ist auth login` to re-authenticate.");
|
|
12
|
+
}
|
|
13
|
+
const response = await fetch(`${config.apiUrl}${path}`, {
|
|
14
|
+
...init,
|
|
15
|
+
headers: {
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
Authorization: `Bearer ${config.token}`,
|
|
18
|
+
...init.headers,
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
if (!response.ok) {
|
|
22
|
+
throw new Error(`Request failed: ${response.status}`);
|
|
23
|
+
}
|
|
24
|
+
if (response.status === 204) {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return response.json();
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/client/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1D,MAAM,CAAC,KAAK,UAAU,OAAO,CAAI,IAAY,EAAE,OAAoB,EAAE;IACnE,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAC9B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;IACxE,CAAC;IACD,IAAI,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAC;IACxF,CAAC;IACD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,MAAM,GAAG,IAAI,EAAE,EAAE;QACtD,GAAG,IAAI;QACP,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,MAAM,CAAC,KAAK,EAAE;YACvC,GAAG,IAAI,CAAC,OAAO;SAChB;KACF,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;QAC5B,OAAO,SAAc,CAAC;IACxB,CAAC;IACD,OAAO,QAAQ,CAAC,IAAI,EAAgB,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare function authCommand(args: string[]): Promise<{
|
|
2
|
+
apiUrl: string;
|
|
3
|
+
authenticated: boolean;
|
|
4
|
+
expiresAt: number;
|
|
5
|
+
} | {
|
|
6
|
+
loggedOut: boolean;
|
|
7
|
+
} | {
|
|
8
|
+
authenticated: boolean;
|
|
9
|
+
apiUrl?: undefined;
|
|
10
|
+
expiresAt?: undefined;
|
|
11
|
+
expired?: undefined;
|
|
12
|
+
} | {
|
|
13
|
+
apiUrl: string;
|
|
14
|
+
authenticated: boolean;
|
|
15
|
+
expiresAt: number | undefined;
|
|
16
|
+
expired: boolean;
|
|
17
|
+
}>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { unlink } from "node:fs/promises";
|
|
2
|
+
import { buildAuthorizationUrl, generateCodeChallenge, generateCodeVerifier, generateState, openBrowser, startCallbackServer, } from "../auth/pkce.js";
|
|
3
|
+
import { configPath, isTokenExpired, loadConfig, saveConfig } from "../config.js";
|
|
4
|
+
export async function authCommand(args) {
|
|
5
|
+
const action = args[0];
|
|
6
|
+
if (action === "login")
|
|
7
|
+
return loginCommand(args.slice(1));
|
|
8
|
+
if (action === "logout")
|
|
9
|
+
return logoutCommand();
|
|
10
|
+
if (action === "status")
|
|
11
|
+
return statusCommand();
|
|
12
|
+
throw new Error(`Unknown auth command: ${action ?? ""}`);
|
|
13
|
+
}
|
|
14
|
+
async function loginCommand(args) {
|
|
15
|
+
const token = valueAfter(args, "--token");
|
|
16
|
+
// Support both --url and --api-url for backward compatibility
|
|
17
|
+
const apiUrl = valueAfter(args, "--url") ?? valueAfter(args, "--api-url");
|
|
18
|
+
// Static token path (CI / dev bypass): --token provided, exchange for internal JWT
|
|
19
|
+
if (token) {
|
|
20
|
+
if (!apiUrl)
|
|
21
|
+
throw new Error("auth login requires --url (or --api-url) when using --token");
|
|
22
|
+
const exchangeRes = await fetch(`${apiUrl}/v1/auth/exchange`, {
|
|
23
|
+
method: "POST",
|
|
24
|
+
headers: { "Content-Type": "application/json" },
|
|
25
|
+
body: JSON.stringify({ token }),
|
|
26
|
+
});
|
|
27
|
+
if (!exchangeRes.ok) {
|
|
28
|
+
const errData = (await exchangeRes.json().catch(() => ({})));
|
|
29
|
+
throw new Error(`Authentication failed: ${String(errData["detail"] ?? exchangeRes.status)}`);
|
|
30
|
+
}
|
|
31
|
+
const exchangeData = (await exchangeRes.json());
|
|
32
|
+
const expiresAt = Date.now() + exchangeData.expires_in * 1000;
|
|
33
|
+
await saveConfig({ apiUrl, token: exchangeData.access_token, expiresAt });
|
|
34
|
+
return { apiUrl, authenticated: true, expiresAt };
|
|
35
|
+
}
|
|
36
|
+
// PKCE path — resolve API URL
|
|
37
|
+
let resolvedApiUrl = apiUrl;
|
|
38
|
+
if (!resolvedApiUrl) {
|
|
39
|
+
const existing = await loadConfig().catch(() => null);
|
|
40
|
+
resolvedApiUrl = existing?.apiUrl;
|
|
41
|
+
}
|
|
42
|
+
if (!resolvedApiUrl) {
|
|
43
|
+
throw new Error("auth login requires --url <api-url>");
|
|
44
|
+
}
|
|
45
|
+
// Fetch auth config from the server (public endpoint)
|
|
46
|
+
const configRes = await fetch(`${resolvedApiUrl}/v1/auth/config`);
|
|
47
|
+
if (!configRes.ok) {
|
|
48
|
+
throw new Error(`Failed to fetch auth config: ${configRes.status}`);
|
|
49
|
+
}
|
|
50
|
+
const authConfig = (await configRes.json());
|
|
51
|
+
if (!authConfig.tenant_id || !authConfig.client_id) {
|
|
52
|
+
throw new Error("Server is not configured for Entra authentication. Use --token to provide a static token.");
|
|
53
|
+
}
|
|
54
|
+
// Generate PKCE pair and state
|
|
55
|
+
const codeVerifier = generateCodeVerifier();
|
|
56
|
+
const codeChallenge = generateCodeChallenge(codeVerifier);
|
|
57
|
+
const state = generateState();
|
|
58
|
+
// Start local callback server (binds to random port)
|
|
59
|
+
const { port, result } = await startCallbackServer(state);
|
|
60
|
+
const redirectUri = `http://localhost:${port}`;
|
|
61
|
+
// Open browser to Entra authorization URL
|
|
62
|
+
const authUrl = buildAuthorizationUrl({
|
|
63
|
+
tenantId: authConfig.tenant_id,
|
|
64
|
+
clientId: authConfig.client_id,
|
|
65
|
+
scope: authConfig.scope,
|
|
66
|
+
redirectUri,
|
|
67
|
+
codeChallenge,
|
|
68
|
+
state,
|
|
69
|
+
});
|
|
70
|
+
process.stderr.write("Opening browser for authentication...\n");
|
|
71
|
+
openBrowser(authUrl);
|
|
72
|
+
// Wait for the authorization code via local callback
|
|
73
|
+
const { code } = await result;
|
|
74
|
+
// Exchange authorization code for Entra access token directly with Entra ID
|
|
75
|
+
const entraToken = await exchangeCodeForEntraToken({
|
|
76
|
+
tenantId: authConfig.tenant_id,
|
|
77
|
+
clientId: authConfig.client_id,
|
|
78
|
+
code,
|
|
79
|
+
redirectUri,
|
|
80
|
+
codeVerifier,
|
|
81
|
+
});
|
|
82
|
+
// Exchange Entra token for an internal 8-hour JWT
|
|
83
|
+
const exchangeRes = await fetch(`${resolvedApiUrl}/v1/auth/exchange`, {
|
|
84
|
+
method: "POST",
|
|
85
|
+
headers: { "Content-Type": "application/json" },
|
|
86
|
+
body: JSON.stringify({ token: entraToken }),
|
|
87
|
+
});
|
|
88
|
+
if (!exchangeRes.ok) {
|
|
89
|
+
const errData = (await exchangeRes.json().catch(() => ({})));
|
|
90
|
+
throw new Error(`Authentication failed: ${String(errData["detail"] ?? exchangeRes.status)}`);
|
|
91
|
+
}
|
|
92
|
+
const exchangeData = (await exchangeRes.json());
|
|
93
|
+
const expiresAt = Date.now() + exchangeData.expires_in * 1000;
|
|
94
|
+
await saveConfig({ apiUrl: resolvedApiUrl, token: exchangeData.access_token, expiresAt });
|
|
95
|
+
return { apiUrl: resolvedApiUrl, authenticated: true, expiresAt };
|
|
96
|
+
}
|
|
97
|
+
async function logoutCommand() {
|
|
98
|
+
try {
|
|
99
|
+
await unlink(configPath());
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
if (err.code !== "ENOENT")
|
|
103
|
+
throw err;
|
|
104
|
+
}
|
|
105
|
+
return { loggedOut: true };
|
|
106
|
+
}
|
|
107
|
+
async function statusCommand() {
|
|
108
|
+
let config;
|
|
109
|
+
try {
|
|
110
|
+
config = await loadConfig();
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
if (err.code === "ENOENT") {
|
|
114
|
+
return { authenticated: false };
|
|
115
|
+
}
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
apiUrl: config.apiUrl,
|
|
120
|
+
authenticated: Boolean(config.token),
|
|
121
|
+
expiresAt: config.expiresAt,
|
|
122
|
+
expired: isTokenExpired(config),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
async function exchangeCodeForEntraToken(params) {
|
|
126
|
+
const tokenUrl = `https://login.microsoftonline.com/${params.tenantId}/oauth2/v2.0/token`;
|
|
127
|
+
const body = new URLSearchParams({
|
|
128
|
+
grant_type: "authorization_code",
|
|
129
|
+
client_id: params.clientId,
|
|
130
|
+
code: params.code,
|
|
131
|
+
redirect_uri: params.redirectUri,
|
|
132
|
+
code_verifier: params.codeVerifier,
|
|
133
|
+
});
|
|
134
|
+
const res = await fetch(tokenUrl, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
137
|
+
body: body.toString(),
|
|
138
|
+
});
|
|
139
|
+
if (!res.ok) {
|
|
140
|
+
const errData = (await res.json().catch(() => ({})));
|
|
141
|
+
throw new Error(`Failed to obtain Entra token: ${String(errData["error_description"] ?? res.status)}`);
|
|
142
|
+
}
|
|
143
|
+
const data = (await res.json());
|
|
144
|
+
return data.access_token;
|
|
145
|
+
}
|
|
146
|
+
function valueAfter(args, flag) {
|
|
147
|
+
const index = args.indexOf(flag);
|
|
148
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
149
|
+
}
|
|
150
|
+
//# 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 @@
|
|
|
1
|
+
export declare function issueCommand(args: string[]): Promise<unknown>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { request } from "../client/api.js";
|
|
2
|
+
export async function issueCommand(args) {
|
|
3
|
+
const action = args[0];
|
|
4
|
+
if (action === "list") {
|
|
5
|
+
const params = new URLSearchParams();
|
|
6
|
+
const assignee = valueAfter(args, "--assignee");
|
|
7
|
+
if (assignee)
|
|
8
|
+
params.set("assignee", assignee);
|
|
9
|
+
const project = valueAfter(args, "--project");
|
|
10
|
+
if (project)
|
|
11
|
+
params.set("project", project);
|
|
12
|
+
const status = valueAfter(args, "--status");
|
|
13
|
+
if (status)
|
|
14
|
+
params.set("status", status);
|
|
15
|
+
const qs = params.size ? `?${params.toString()}` : "";
|
|
16
|
+
return request(`/v1/issues${qs}`);
|
|
17
|
+
}
|
|
18
|
+
if (action === "view") {
|
|
19
|
+
const { activity: _, ...detail } = await request(`/v1/issues/${args[1]}`);
|
|
20
|
+
return detail;
|
|
21
|
+
}
|
|
22
|
+
if (action === "activity") {
|
|
23
|
+
const { activity } = await request(`/v1/issues/${args[1]}`);
|
|
24
|
+
return activity;
|
|
25
|
+
}
|
|
26
|
+
if (action === "create") {
|
|
27
|
+
return request("/v1/issues", {
|
|
28
|
+
method: "POST",
|
|
29
|
+
body: JSON.stringify({
|
|
30
|
+
project_key: valueAfter(args, "--project"),
|
|
31
|
+
title: valueAfter(args, "--title"),
|
|
32
|
+
description: valueAfter(args, "--description") ?? "",
|
|
33
|
+
issue_type: valueAfter(args, "--type") ?? "task",
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (action === "update") {
|
|
38
|
+
return request(`/v1/issues/${args[1]}`, {
|
|
39
|
+
method: "PATCH",
|
|
40
|
+
body: JSON.stringify({
|
|
41
|
+
title: valueAfter(args, "--title"),
|
|
42
|
+
status: valueAfter(args, "--status"),
|
|
43
|
+
}),
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (action === "start") {
|
|
47
|
+
return request(`/v1/issues/${args[1]}`, {
|
|
48
|
+
method: "PATCH",
|
|
49
|
+
body: JSON.stringify({ status: "in_progress" }),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
if (action === "comment") {
|
|
53
|
+
return request(`/v1/issues/${args[1]}/comments`, {
|
|
54
|
+
method: "POST",
|
|
55
|
+
body: JSON.stringify({ body: args.slice(2).join(" ") }),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`Unknown issue command: ${action ?? ""}`);
|
|
59
|
+
}
|
|
60
|
+
function valueAfter(args, flag) {
|
|
61
|
+
const index = args.indexOf(flag);
|
|
62
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=issue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issue.js","sourceRoot":"","sources":["../../../src/commands/issue.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function projectCommand(args: string[]): Promise<unknown>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { request } from "../client/api.js";
|
|
2
|
+
export async function projectCommand(args) {
|
|
3
|
+
const action = args[0];
|
|
4
|
+
if (action === "list") {
|
|
5
|
+
return request("/v1/projects");
|
|
6
|
+
}
|
|
7
|
+
if (action === "view") {
|
|
8
|
+
return request(`/v1/projects/${args[1]}`);
|
|
9
|
+
}
|
|
10
|
+
if (action === "create") {
|
|
11
|
+
return request("/v1/projects", {
|
|
12
|
+
method: "POST",
|
|
13
|
+
body: JSON.stringify({
|
|
14
|
+
key: valueAfter(args, "--key"),
|
|
15
|
+
name: valueAfter(args, "--name"),
|
|
16
|
+
}),
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
throw new Error(`Unknown project command: ${action ?? ""}`);
|
|
20
|
+
}
|
|
21
|
+
function valueAfter(args, flag) {
|
|
22
|
+
const index = args.indexOf(flag);
|
|
23
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=project.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project.js","sourceRoot":"","sources":["../../../src/commands/project.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,IAAc;IACjD,MAAM,MAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACvB,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC,cAAc,CAAC,CAAC;IACjC,CAAC;IACD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC,gBAAgB,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC5C,CAAC;IACD,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QACxB,OAAO,OAAO,CAAC,cAAc,EAAE;YAC7B,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;gBACnB,GAAG,EAAE,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC;gBAC9B,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC;aACjC,CAAC;SACH,CAAC,CAAC;IACL,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,4BAA4B,MAAM,IAAI,EAAE,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,UAAU,CAAC,IAAc,EAAE,IAAY;IAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type CliConfig = {
|
|
2
|
+
apiUrl: string;
|
|
3
|
+
token: string;
|
|
4
|
+
expiresAt?: number;
|
|
5
|
+
};
|
|
6
|
+
export declare function configPath(): string;
|
|
7
|
+
export declare function saveConfig(config: CliConfig): Promise<void>;
|
|
8
|
+
export declare function loadConfig(): Promise<CliConfig>;
|
|
9
|
+
export declare function isTokenExpired(config: CliConfig): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
export function configPath() {
|
|
5
|
+
return process.env.IST_CONFIG_PATH ?? join(homedir(), ".config", "ist", "config.json");
|
|
6
|
+
}
|
|
7
|
+
export async function saveConfig(config) {
|
|
8
|
+
const path = configPath();
|
|
9
|
+
await mkdir(dirname(path), { recursive: true });
|
|
10
|
+
await writeFile(path, JSON.stringify(config, null, 2));
|
|
11
|
+
}
|
|
12
|
+
export async function loadConfig() {
|
|
13
|
+
const raw = await readFile(configPath(), "utf-8");
|
|
14
|
+
return JSON.parse(raw);
|
|
15
|
+
}
|
|
16
|
+
export function isTokenExpired(config) {
|
|
17
|
+
if (!config.expiresAt)
|
|
18
|
+
return false; // legacy configs without expiry: never expire
|
|
19
|
+
return Date.now() >= config.expiresAt;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAQ1C,MAAM,UAAU,UAAU;IACxB,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC;AACzF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAiB;IAChD,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,UAAU,EAAE,EAAE,OAAO,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAc,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAiB;IAC9C,IAAI,CAAC,MAAM,CAAC,SAAS;QAAE,OAAO,KAAK,CAAC,CAAC,8CAA8C;IACnF,OAAO,IAAI,CAAC,GAAG,EAAE,IAAI,MAAM,CAAC,SAAS,CAAC;AACxC,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { authCommand } from "./commands/auth.js";
|
|
3
|
+
import { issueCommand } from "./commands/issue.js";
|
|
4
|
+
import { projectCommand } from "./commands/project.js";
|
|
5
|
+
export function getCliName() {
|
|
6
|
+
return "ist";
|
|
7
|
+
}
|
|
8
|
+
export async function run(argv) {
|
|
9
|
+
const args = [...argv];
|
|
10
|
+
const json = args.includes("--json");
|
|
11
|
+
const filteredArgs = args.filter((arg) => arg !== "--json");
|
|
12
|
+
const resource = filteredArgs[0];
|
|
13
|
+
const commandArgs = filteredArgs.slice(1);
|
|
14
|
+
let result;
|
|
15
|
+
if (resource === "auth") {
|
|
16
|
+
result = await authCommand(commandArgs);
|
|
17
|
+
}
|
|
18
|
+
else if (resource === "project") {
|
|
19
|
+
result = await projectCommand(commandArgs);
|
|
20
|
+
}
|
|
21
|
+
else if (resource === "issue") {
|
|
22
|
+
result = await issueCommand(commandArgs);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
throw new Error(`Unknown command: ${resource ?? ""}`);
|
|
26
|
+
}
|
|
27
|
+
return json || typeof result !== "string" ? JSON.stringify(result, null, 2) : result;
|
|
28
|
+
}
|
|
29
|
+
export async function main() {
|
|
30
|
+
try {
|
|
31
|
+
console.log(await run(process.argv.slice(2)));
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
35
|
+
process.exitCode = 1;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
39
|
+
void main();
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD,MAAM,UAAU,UAAU;IACxB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAc;IACtC,MAAM,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACrC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC;IAC5D,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;IACjC,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC1C,IAAI,MAAe,CAAC;IACpB,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;QACxB,MAAM,GAAG,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC;IAC1C,CAAC;SAAM,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAClC,MAAM,GAAG,MAAM,cAAc,CAAC,WAAW,CAAC,CAAC;IAC7C,CAAC;SAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,MAAM,GAAG,MAAM,YAAY,CAAC,WAAW,CAAC,CAAC;IAC3C,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,KAAK,CAAC,oBAAoB,QAAQ,IAAI,EAAE,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACvF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,IAAI,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACtE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,UAAU,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACpD,KAAK,IAAI,EAAE,CAAC;AACd,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nutteen/ist",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for the issue tracker — manage issues and projects from the terminal",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ist": "./dist/src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist/src"
|
|
11
|
+
],
|
|
12
|
+
"engines": {
|
|
13
|
+
"node": ">=20"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"lint": "eslint .",
|
|
18
|
+
"typecheck": "tsc --noEmit",
|
|
19
|
+
"test": "vitest run"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@eslint/js": "^9.39.1",
|
|
24
|
+
"@types/node": "^24.10.1",
|
|
25
|
+
"eslint": "^9.39.1",
|
|
26
|
+
"globals": "^16.5.0",
|
|
27
|
+
"typescript": "^5.9.3",
|
|
28
|
+
"typescript-eslint": "^8.48.0",
|
|
29
|
+
"vitest": "^4.0.13"
|
|
30
|
+
}
|
|
31
|
+
}
|