@knpkv/jira-cli 1.4.1 → 1.5.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/CHANGELOG.md +18 -0
- package/README.md +4 -1
- package/dist/JiraAuth.d.ts +3 -0
- package/dist/JiraAuth.d.ts.map +1 -1
- package/dist/JiraAuth.js +7 -3
- package/dist/JiraAuth.js.map +1 -1
- package/dist/commands/auth.d.ts +1 -2
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +4 -128
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/layers.d.ts +1 -1
- package/dist/commands/layers.d.ts.map +1 -1
- package/package.json +3 -3
- package/skills/jira/SKILL.md +1 -0
- package/src/JiraAuth.ts +9 -4
- package/src/commands/auth.ts +4 -167
- package/test/authCommand.test.ts +54 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @knpkv/jira-cli
|
|
2
2
|
|
|
3
|
+
## 1.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#472](https://github.com/knpkv/npm/pull/472) [`d12c336`](https://github.com/knpkv/npm/commit/d12c3364e56abb7cfd32aa693c5406788a67e3f5) Thanks [@konopkov](https://github.com/konopkov)! - One shared `auth` command group for jira and confluence: `makeAuthCommand(service, descriptor, options)` in `@knpkv/atlassian-common/cli-auth`. Each CLI exports one descriptor (`jiraCliDescriptor`, `confluenceCliDescriptor`: command name, product name, login scopes) that feeds both its auth and its `auth` commands.
|
|
8
|
+
|
|
9
|
+
- `jira auth create` lists every scope `jira auth login` requests. It previously listed three of five (missing `write:jira-work` and `manage:jira-project`), so an app set up from its instructions could not log in.
|
|
10
|
+
- `jira auth manage` is new: it opens the developer console with the scope and callback checklist.
|
|
11
|
+
- `confluence auth profiles`, `auth use <profile>` and `auth remove <profile>` are new; the docs already described them.
|
|
12
|
+
- `confluence auth status` prints the active profile, account, site and profile ID.
|
|
13
|
+
- `auth create` and `auth manage` print the URL first and exit non-zero when no browser opens (jira's `create` used to ignore that).
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Updated dependencies [[`d12c336`](https://github.com/knpkv/npm/commit/d12c3364e56abb7cfd32aa693c5406788a67e3f5)]:
|
|
18
|
+
- @knpkv/atlassian-common@1.7.0
|
|
19
|
+
- @knpkv/agent-skills@0.3.3
|
|
20
|
+
|
|
3
21
|
## 1.4.1
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -22,7 +22,9 @@ cd packages/jira-cli && pnpm link --global
|
|
|
22
22
|
jira auth create
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
Prints the URL and opens the Atlassian Developer Console (the command fails if no browser
|
|
26
|
+
opens; finish from the printed URL). Create a new OAuth 2.0 (3LO) app with the scopes it
|
|
27
|
+
prints, which are exactly the ones `jira auth login` requests:
|
|
26
28
|
|
|
27
29
|
**Permissions:**
|
|
28
30
|
|
|
@@ -252,6 +254,7 @@ set, including surplus copies of a URL that _is_ desired. Other categories are n
|
|
|
252
254
|
|
|
253
255
|
```bash
|
|
254
256
|
jira auth create # Open Atlassian console to create OAuth app
|
|
257
|
+
jira auth manage # Open the console to edit the app's scopes
|
|
255
258
|
jira auth configure # Set client ID and secret
|
|
256
259
|
jira auth login # Authenticate via OAuth
|
|
257
260
|
jira auth profiles # List stored accounts/sites
|
package/dist/JiraAuth.d.ts
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* @module
|
|
18
18
|
*/
|
|
19
19
|
import type { OAuthError } from "@knpkv/atlassian-common/auth";
|
|
20
|
+
import { type AtlassianCliDescriptor } from "@knpkv/atlassian-common/cli-auth";
|
|
20
21
|
import type { AuthProfile, FileSystemError, HomeDirectoryError, OAuthConfig, OAuthUser } from "@knpkv/atlassian-common/config";
|
|
21
22
|
import * as Context from "effect/Context";
|
|
22
23
|
import type * as Effect from "effect/Effect";
|
|
@@ -98,6 +99,8 @@ declare const JiraAuth_base: Context.ServiceClass<JiraAuth, "@knpkv/jira-cli/Jir
|
|
|
98
99
|
*/
|
|
99
100
|
export declare class JiraAuth extends JiraAuth_base {
|
|
100
101
|
}
|
|
102
|
+
/** Who the Jira CLI is: shared by its auth and its `auth` commands, so both name the same scopes. */
|
|
103
|
+
export declare const jiraCliDescriptor: AtlassianCliDescriptor;
|
|
101
104
|
/**
|
|
102
105
|
* Layer for JiraAuth service.
|
|
103
106
|
*
|
package/dist/JiraAuth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JiraAuth.d.ts","sourceRoot":"","sources":["../src/JiraAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;
|
|
1
|
+
{"version":3,"file":"JiraAuth.d.ts","sourceRoot":"","sources":["../src/JiraAuth.ts"],"names":[],"mappings":"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"}
|
package/dist/JiraAuth.js
CHANGED
|
@@ -40,16 +40,20 @@ const JIRA_CLI_SCOPES = [
|
|
|
40
40
|
*/
|
|
41
41
|
export class JiraAuth extends Context.Service()("@knpkv/jira-cli/JiraAuth") {
|
|
42
42
|
}
|
|
43
|
+
/** Who the Jira CLI is: shared by its auth and its `auth` commands, so both name the same scopes. */
|
|
44
|
+
export const jiraCliDescriptor = {
|
|
45
|
+
commandName: "jira",
|
|
46
|
+
productName: "Jira",
|
|
47
|
+
scopes: JIRA_CLI_SCOPES
|
|
48
|
+
};
|
|
43
49
|
/**
|
|
44
50
|
* Layer for JiraAuth service.
|
|
45
51
|
*
|
|
46
52
|
* @category Layers
|
|
47
53
|
*/
|
|
48
54
|
export const layer = Layer.effect(JiraAuth, makeAtlassianCliAuth({
|
|
55
|
+
...jiraCliDescriptor,
|
|
49
56
|
toolName: "jira-cli",
|
|
50
|
-
commandName: "jira",
|
|
51
|
-
productName: "Jira",
|
|
52
|
-
scopes: JIRA_CLI_SCOPES,
|
|
53
57
|
authMissing
|
|
54
58
|
})).pipe(Layer.provide(NodeCliAuthLive));
|
|
55
59
|
//# sourceMappingURL=JiraAuth.js.map
|
package/dist/JiraAuth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"JiraAuth.js","sourceRoot":"","sources":["../src/JiraAuth.ts"],"names":[],"mappings":"AAmBA,OAAO,
|
|
1
|
+
{"version":3,"file":"JiraAuth.js","sourceRoot":"","sources":["../src/JiraAuth.ts"],"names":[],"mappings":"AAmBA,OAAO,EAA+B,oBAAoB,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;AAQrH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAEzC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAIrC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAE/C,+GAA+G;AAC/G,MAAM,eAAe,GAAG;IACtB,0CAA0C;IAC1C,gBAAgB;IAChB,kCAAkC;IAClC,iBAAiB;IACjB,oEAAoE;IACpE,sEAAsE;IACtE,iEAAiE;IACjE,qBAAqB;IACrB,0EAA0E;IAC1E,gBAAgB;IAChB,oEAAoE;IACpE,SAAS;IACT,gEAAgE;IAChE,gBAAgB;CACjB,CAAA;AA4FD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,QAAS,SAAQ,OAAO,CAAC,OAAO,EAG1C,CAAC,0BAA0B,CAAC;CAAG;AAElC,qGAAqG;AACrG,MAAM,CAAC,MAAM,iBAAiB,GAA2B;IACvD,WAAW,EAAE,MAAM;IACnB,WAAW,EAAE,MAAM;IACnB,MAAM,EAAE,eAAe;CACxB,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAC/B,QAAQ,EACR,oBAAoB,CAAC;IACnB,GAAG,iBAAiB;IACpB,QAAQ,EAAE,UAAU;IACpB,WAAW;CACZ,CAAC,CACH,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAA"}
|
package/dist/commands/auth.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { Command, Prompt } from "effect/cli";
|
|
2
1
|
import { JiraAuth } from "../JiraAuth.js";
|
|
3
|
-
export declare const authCommand: Command.Command<"auth", {}, {}, import("@knpkv/atlassian-common/
|
|
2
|
+
export declare const authCommand: import("effect/cli/Command").Command<"auth", {}, {}, import("@knpkv/atlassian-common/cli-auth").BrowserOpenError | import("@knpkv/atlassian-common").FileSystemError | import("@knpkv/atlassian-common").HomeDirectoryError | import("@knpkv/atlassian-common").OAuthError | import("effect/PlatformError").PlatformError | import("effect/Terminal").QuitError, import("effect/process/ChildProcessSpawner").ChildProcessSpawner | JiraAuth | import("effect/cli/Prompt").Environment>;
|
|
4
3
|
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,QAAQ,EAAqB,MAAM,gBAAgB,CAAA;AAE5D,eAAO,MAAM,WAAW,ydAA2E,CAAA"}
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,133 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* **Mental model**
|
|
5
|
-
*
|
|
6
|
-
* - Each command is a standalone `Command.make` that yields into `JiraAuth` service methods.
|
|
7
|
-
* - `create` opens the Atlassian Developer Console; `configure` prompts for client ID/secret.
|
|
2
|
+
* `jira auth`: the shared Atlassian auth commands over {@link JiraAuth}.
|
|
8
3
|
*
|
|
9
4
|
* @internal
|
|
10
5
|
*/
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
import * as Effect from "effect/Effect";
|
|
15
|
-
import * as Option from "effect/Option";
|
|
16
|
-
import { JiraAuth } from "../JiraAuth.js";
|
|
17
|
-
// === Auth create command ===
|
|
18
|
-
const createCommand = Command.make("create", {}, () => Effect.gen(function* () {
|
|
19
|
-
yield* Console.log(`
|
|
20
|
-
Creating OAuth app in Atlassian Developer Console...
|
|
21
|
-
|
|
22
|
-
1. Browser will open to create a new OAuth 2.0 (3LO) app
|
|
23
|
-
2. Enter app name (e.g., "Jira CLI")
|
|
24
|
-
3. After creation, go to "Permissions" and add:
|
|
25
|
-
|
|
26
|
-
Jira API:
|
|
27
|
-
- read:jira-work Search and read issues, comments, attachments
|
|
28
|
-
- read:jira-user Read user info for assignee/reporter fields
|
|
29
|
-
|
|
30
|
-
User Identity API:
|
|
31
|
-
- read:me Get your account ID and email for auth
|
|
32
|
-
|
|
33
|
-
4. Go to "Authorization" and set callback URL:
|
|
34
|
-
http://localhost:8585/callback
|
|
35
|
-
5. Go to "Settings" and copy Client ID and Secret
|
|
36
|
-
6. Run: jira auth configure --client-id <ID> --client-secret <SECRET>
|
|
37
|
-
`);
|
|
38
|
-
const url = "https://developer.atlassian.com/console/myapps/create-3lo-app/";
|
|
39
|
-
yield* openBrowser(url).pipe(Effect.catch(() => Effect.void));
|
|
40
|
-
})).pipe(Command.withDescription("Create OAuth app in Atlassian Developer Console"));
|
|
41
|
-
// === Auth configure command ===
|
|
42
|
-
const clientIdOption = Options.String("client-id").pipe(Options.withDescription("OAuth client ID from Atlassian Developer Console"), Options.optional);
|
|
43
|
-
const clientSecretOption = Options.String("client-secret").pipe(Options.withDescription("OAuth client secret"), Options.optional);
|
|
44
|
-
const configureCommand = Command.make("configure", { clientId: clientIdOption, clientSecret: clientSecretOption }, ({ clientId, clientSecret }) => Effect.gen(function* () {
|
|
45
|
-
const auth = yield* JiraAuth;
|
|
46
|
-
const rawClientId = Option.isSome(clientId)
|
|
47
|
-
? clientId.value
|
|
48
|
-
: yield* Prompt.String({ message: "Enter OAuth client ID:" });
|
|
49
|
-
const rawClientSecret = Option.isSome(clientSecret)
|
|
50
|
-
? clientSecret.value
|
|
51
|
-
: yield* Prompt.String({ message: "Enter OAuth client secret:" });
|
|
52
|
-
yield* auth.configure({ clientId: rawClientId, clientSecret: rawClientSecret });
|
|
53
|
-
yield* Console.log("OAuth configured. Run 'jira auth login' to authenticate.");
|
|
54
|
-
})).pipe(Command.withDescription("Configure OAuth client credentials"));
|
|
55
|
-
// === Auth login command ===
|
|
56
|
-
const siteOption = Options.String("site").pipe(Options.withDescription("Jira site URL to use (for accounts with multiple sites)"), Options.optional);
|
|
57
|
-
const loginCommand = Command.make("login", { site: siteOption }, ({ site }) => Effect.gen(function* () {
|
|
58
|
-
const auth = yield* JiraAuth;
|
|
59
|
-
const result = yield* auth.login(Option.isSome(site) ? { siteUrl: site.value } : undefined);
|
|
60
|
-
if (Array.isArray(result) && result.length > 0) {
|
|
61
|
-
yield* Console.log("\nRe-run with --site to select a specific site.");
|
|
62
|
-
}
|
|
63
|
-
})).pipe(Command.withDescription("Authenticate with Atlassian via OAuth"));
|
|
64
|
-
// === Auth logout command ===
|
|
65
|
-
const logoutCommand = Command.make("logout", {}, () => Effect.gen(function* () {
|
|
66
|
-
const auth = yield* JiraAuth;
|
|
67
|
-
yield* auth.logout();
|
|
68
|
-
yield* Console.log("Logged out");
|
|
69
|
-
})).pipe(Command.withDescription("Remove stored authentication"));
|
|
70
|
-
// === Auth status command ===
|
|
71
|
-
const statusCommand = Command.make("status", {}, () => Effect.gen(function* () {
|
|
72
|
-
const auth = yield* JiraAuth;
|
|
73
|
-
const profile = yield* auth.getActiveProfile();
|
|
74
|
-
if (profile) {
|
|
75
|
-
const user = profile.token.user;
|
|
76
|
-
const account = user ? `${user.name} (${user.email})` : "unknown user";
|
|
77
|
-
yield* Console.log(`Active profile: ${profile.name}`);
|
|
78
|
-
yield* Console.log(`Account: ${account}`);
|
|
79
|
-
yield* Console.log(`Site: ${profile.token.site_url}`);
|
|
80
|
-
yield* Console.log(`Profile ID: ${profile.id}`);
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
yield* Console.log("Not logged in. Use 'jira auth login' to authenticate.");
|
|
84
|
-
}
|
|
85
|
-
})).pipe(Command.withDescription("Show authentication status"));
|
|
86
|
-
// === Auth profiles command ===
|
|
87
|
-
const profilesCommand = Command.make("profiles", {}, () => Effect.gen(function* () {
|
|
88
|
-
const auth = yield* JiraAuth;
|
|
89
|
-
const [profiles, active] = yield* Effect.all([auth.listProfiles(), auth.getActiveProfile()]);
|
|
90
|
-
if (profiles.length === 0) {
|
|
91
|
-
yield* Console.log("No auth profiles. Use 'jira auth login' to authenticate.");
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
for (const profile of profiles) {
|
|
95
|
-
const marker = active?.id === profile.id ? "*" : " ";
|
|
96
|
-
yield* Console.log(`${marker} ${profile.id}`);
|
|
97
|
-
yield* Console.log(` ${profile.name}`);
|
|
98
|
-
yield* Console.log(` ${profile.token.site_url}`);
|
|
99
|
-
}
|
|
100
|
-
})).pipe(Command.withDescription("List stored auth profiles"));
|
|
101
|
-
const profileArg = Args.String("profile").pipe(Args.withDescription("Profile ID, name, site URL, cloud ID, or account ID"));
|
|
102
|
-
// === Auth use command ===
|
|
103
|
-
const useCommand = Command.make("use", { profile: profileArg }, ({ profile }) => Effect.gen(function* () {
|
|
104
|
-
const auth = yield* JiraAuth;
|
|
105
|
-
const selected = yield* auth.switchProfile(profile);
|
|
106
|
-
if (!selected) {
|
|
107
|
-
yield* Console.log(`Profile not found: ${profile}`);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
yield* Console.log(`Active profile: ${selected.name}`);
|
|
111
|
-
})).pipe(Command.withDescription("Switch active auth profile"));
|
|
112
|
-
// === Auth remove command ===
|
|
113
|
-
const removeCommand = Command.make("remove", { profile: profileArg }, ({ profile }) => Effect.gen(function* () {
|
|
114
|
-
const auth = yield* JiraAuth;
|
|
115
|
-
const removed = yield* auth.removeProfile(profile);
|
|
116
|
-
if (!removed) {
|
|
117
|
-
yield* Console.log(`Profile not found: ${profile}`);
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
yield* Console.log(`Removed profile: ${removed.name}`);
|
|
121
|
-
})).pipe(Command.withDescription("Remove stored auth profile"));
|
|
122
|
-
// === Auth command group ===
|
|
123
|
-
export const authCommand = Command.make("auth").pipe(Command.withDescription("Manage OAuth authentication"), Command.withSubcommands([
|
|
124
|
-
createCommand,
|
|
125
|
-
configureCommand,
|
|
126
|
-
loginCommand,
|
|
127
|
-
logoutCommand,
|
|
128
|
-
statusCommand,
|
|
129
|
-
profilesCommand,
|
|
130
|
-
useCommand,
|
|
131
|
-
removeCommand
|
|
132
|
-
]));
|
|
6
|
+
import { makeAuthCommand } from "@knpkv/atlassian-common/cli-auth";
|
|
7
|
+
import { JiraAuth, jiraCliDescriptor } from "../JiraAuth.js";
|
|
8
|
+
export const authCommand = makeAuthCommand(JiraAuth, jiraCliDescriptor, { apiSection: "Jira API" });
|
|
133
9
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAA;AAClE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAE5D,MAAM,CAAC,MAAM,WAAW,GAAG,eAAe,CAAC,QAAQ,EAAE,iBAAiB,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAA"}
|
|
@@ -11,7 +11,7 @@ import { VersionService } from "../VersionService.js";
|
|
|
11
11
|
*
|
|
12
12
|
* @category Layers
|
|
13
13
|
*/
|
|
14
|
-
export declare const AppLayer: Layer.Layer<AttachmentService | import("effect/http/HttpClient").HttpClient | IssueService | JiraApiClient | JiraAuth | MarkdownWriter | SiteUrl | VersionService | NodeServices.NodeServices, import("../JiraCliError.js").AuthMissingError | import("@knpkv/atlassian-common
|
|
14
|
+
export declare const AppLayer: Layer.Layer<AttachmentService | import("effect/http/HttpClient").HttpClient | IssueService | JiraApiClient | JiraAuth | MarkdownWriter | SiteUrl | VersionService | NodeServices.NodeServices, import("../JiraCliError.js").AuthMissingError | import("@knpkv/atlassian-common").FileSystemError | import("@knpkv/atlassian-common").HomeDirectoryError | import("@knpkv/atlassian-common").OAuthError | import("effect/PlatformError").PlatformError, never>;
|
|
15
15
|
/**
|
|
16
16
|
* Auth-only layer for auth commands.
|
|
17
17
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/commands/layers.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,YAAY,MAAM,oCAAoC,CAAA;AAClE,OAAO,EAAE,aAAa,EAAiB,MAAM,wBAAwB,CAAA;AAErE,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,iBAAiB,EAAmC,MAAM,yBAAyB,CAAA;AAC5F,OAAO,EAAE,YAAY,EAA8B,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACtF,OAAO,EAAE,QAAQ,EAA0B,MAAM,gBAAgB,CAAA;AACjE,OAAO,EAAgC,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACnF,OAAO,EAAgC,cAAc,EAAE,MAAM,sBAAsB,CAAA;AA+FnF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"layers.d.ts","sourceRoot":"","sources":["../../src/commands/layers.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,YAAY,MAAM,oCAAoC,CAAA;AAClE,OAAO,EAAE,aAAa,EAAiB,MAAM,wBAAwB,CAAA;AAErE,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,iBAAiB,EAAmC,MAAM,yBAAyB,CAAA;AAC5F,OAAO,EAAE,YAAY,EAA8B,OAAO,EAAE,MAAM,oBAAoB,CAAA;AACtF,OAAO,EAAE,QAAQ,EAA0B,MAAM,gBAAgB,CAAA;AACjE,OAAO,EAAgC,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACnF,OAAO,EAAgC,cAAc,EAAE,MAAM,sBAAsB,CAAA;AA+FnF;;;;GAIG;AACH,eAAO,MAAM,QAAQ,+bASpB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,oLAOzB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,sIAMxB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY,SAAU,aAAa,CAAC,MAAM,CAAC,KAAG,MAAM,GAAG,MAAM,GAAG,SAkB5E,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knpkv/jira-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "CLI tool to fetch Jira tickets and export to markdown",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "knpkv",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"@effect/platform-node": "4.0.0",
|
|
61
61
|
"gray-matter": "^4.0.3",
|
|
62
62
|
"js-yaml": "5.4.2",
|
|
63
|
-
"@knpkv/agent-skills": "^0.3.
|
|
64
|
-
"@knpkv/atlassian-common": "1.
|
|
63
|
+
"@knpkv/agent-skills": "^0.3.3",
|
|
64
|
+
"@knpkv/atlassian-common": "1.7.0",
|
|
65
65
|
"@knpkv/jira-api-client": "2.0.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
package/skills/jira/SKILL.md
CHANGED
|
@@ -21,6 +21,7 @@ Use the `jira` binary for Jira Cloud issue export and release-version workflows.
|
|
|
21
21
|
```bash
|
|
22
22
|
jira auth status
|
|
23
23
|
jira auth create
|
|
24
|
+
jira auth manage
|
|
24
25
|
jira auth configure --client-id <id> --client-secret <secret>
|
|
25
26
|
jira auth login
|
|
26
27
|
jira auth login --site https://example.atlassian.net
|
package/src/JiraAuth.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* @module
|
|
18
18
|
*/
|
|
19
19
|
import type { OAuthError } from "@knpkv/atlassian-common/auth"
|
|
20
|
-
import { makeAtlassianCliAuth, NodeCliAuthLive } from "@knpkv/atlassian-common/cli-auth"
|
|
20
|
+
import { type AtlassianCliDescriptor, makeAtlassianCliAuth, NodeCliAuthLive } from "@knpkv/atlassian-common/cli-auth"
|
|
21
21
|
import type {
|
|
22
22
|
AuthProfile,
|
|
23
23
|
FileSystemError,
|
|
@@ -165,6 +165,13 @@ export class JiraAuth extends Context.Service<
|
|
|
165
165
|
JiraAuthService
|
|
166
166
|
>()("@knpkv/jira-cli/JiraAuth") {}
|
|
167
167
|
|
|
168
|
+
/** Who the Jira CLI is: shared by its auth and its `auth` commands, so both name the same scopes. */
|
|
169
|
+
export const jiraCliDescriptor: AtlassianCliDescriptor = {
|
|
170
|
+
commandName: "jira",
|
|
171
|
+
productName: "Jira",
|
|
172
|
+
scopes: JIRA_CLI_SCOPES
|
|
173
|
+
}
|
|
174
|
+
|
|
168
175
|
/**
|
|
169
176
|
* Layer for JiraAuth service.
|
|
170
177
|
*
|
|
@@ -173,10 +180,8 @@ export class JiraAuth extends Context.Service<
|
|
|
173
180
|
export const layer = Layer.effect(
|
|
174
181
|
JiraAuth,
|
|
175
182
|
makeAtlassianCliAuth({
|
|
183
|
+
...jiraCliDescriptor,
|
|
176
184
|
toolName: "jira-cli",
|
|
177
|
-
commandName: "jira",
|
|
178
|
-
productName: "Jira",
|
|
179
|
-
scopes: JIRA_CLI_SCOPES,
|
|
180
185
|
authMissing
|
|
181
186
|
})
|
|
182
187
|
).pipe(Layer.provide(NodeCliAuthLive))
|
package/src/commands/auth.ts
CHANGED
|
@@ -1,172 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* **Mental model**
|
|
5
|
-
*
|
|
6
|
-
* - Each command is a standalone `Command.make` that yields into `JiraAuth` service methods.
|
|
7
|
-
* - `create` opens the Atlassian Developer Console; `configure` prompts for client ID/secret.
|
|
2
|
+
* `jira auth`: the shared Atlassian auth commands over {@link JiraAuth}.
|
|
8
3
|
*
|
|
9
4
|
* @internal
|
|
10
5
|
*/
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import * as Console from "effect/Console"
|
|
14
|
-
import * as Effect from "effect/Effect"
|
|
15
|
-
import * as Option from "effect/Option"
|
|
16
|
-
import { JiraAuth } from "../JiraAuth.js"
|
|
17
|
-
|
|
18
|
-
// === Auth create command ===
|
|
19
|
-
const createCommand = Command.make("create", {}, () =>
|
|
20
|
-
Effect.gen(function*() {
|
|
21
|
-
yield* Console.log(`
|
|
22
|
-
Creating OAuth app in Atlassian Developer Console...
|
|
23
|
-
|
|
24
|
-
1. Browser will open to create a new OAuth 2.0 (3LO) app
|
|
25
|
-
2. Enter app name (e.g., "Jira CLI")
|
|
26
|
-
3. After creation, go to "Permissions" and add:
|
|
27
|
-
|
|
28
|
-
Jira API:
|
|
29
|
-
- read:jira-work Search and read issues, comments, attachments
|
|
30
|
-
- read:jira-user Read user info for assignee/reporter fields
|
|
31
|
-
|
|
32
|
-
User Identity API:
|
|
33
|
-
- read:me Get your account ID and email for auth
|
|
34
|
-
|
|
35
|
-
4. Go to "Authorization" and set callback URL:
|
|
36
|
-
http://localhost:8585/callback
|
|
37
|
-
5. Go to "Settings" and copy Client ID and Secret
|
|
38
|
-
6. Run: jira auth configure --client-id <ID> --client-secret <SECRET>
|
|
39
|
-
`)
|
|
40
|
-
const url = "https://developer.atlassian.com/console/myapps/create-3lo-app/"
|
|
41
|
-
yield* openBrowser(url).pipe(Effect.catch(() => Effect.void))
|
|
42
|
-
})).pipe(Command.withDescription("Create OAuth app in Atlassian Developer Console"))
|
|
43
|
-
|
|
44
|
-
// === Auth configure command ===
|
|
45
|
-
const clientIdOption = Options.String("client-id").pipe(
|
|
46
|
-
Options.withDescription("OAuth client ID from Atlassian Developer Console"),
|
|
47
|
-
Options.optional
|
|
48
|
-
)
|
|
49
|
-
const clientSecretOption = Options.String("client-secret").pipe(
|
|
50
|
-
Options.withDescription("OAuth client secret"),
|
|
51
|
-
Options.optional
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
const configureCommand = Command.make(
|
|
55
|
-
"configure",
|
|
56
|
-
{ clientId: clientIdOption, clientSecret: clientSecretOption },
|
|
57
|
-
({ clientId, clientSecret }) =>
|
|
58
|
-
Effect.gen(function*() {
|
|
59
|
-
const auth = yield* JiraAuth
|
|
60
|
-
|
|
61
|
-
const rawClientId = Option.isSome(clientId)
|
|
62
|
-
? clientId.value
|
|
63
|
-
: yield* Prompt.String({ message: "Enter OAuth client ID:" })
|
|
64
|
-
const rawClientSecret = Option.isSome(clientSecret)
|
|
65
|
-
? clientSecret.value
|
|
66
|
-
: yield* Prompt.String({ message: "Enter OAuth client secret:" })
|
|
67
|
-
|
|
68
|
-
yield* auth.configure({ clientId: rawClientId, clientSecret: rawClientSecret })
|
|
69
|
-
yield* Console.log("OAuth configured. Run 'jira auth login' to authenticate.")
|
|
70
|
-
})
|
|
71
|
-
).pipe(Command.withDescription("Configure OAuth client credentials"))
|
|
72
|
-
|
|
73
|
-
// === Auth login command ===
|
|
74
|
-
const siteOption = Options.String("site").pipe(
|
|
75
|
-
Options.withDescription("Jira site URL to use (for accounts with multiple sites)"),
|
|
76
|
-
Options.optional
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
const loginCommand = Command.make("login", { site: siteOption }, ({ site }) =>
|
|
80
|
-
Effect.gen(function*() {
|
|
81
|
-
const auth = yield* JiraAuth
|
|
82
|
-
const result = yield* auth.login(Option.isSome(site) ? { siteUrl: site.value } : undefined)
|
|
83
|
-
if (Array.isArray(result) && result.length > 0) {
|
|
84
|
-
yield* Console.log("\nRe-run with --site to select a specific site.")
|
|
85
|
-
}
|
|
86
|
-
})).pipe(Command.withDescription("Authenticate with Atlassian via OAuth"))
|
|
87
|
-
|
|
88
|
-
// === Auth logout command ===
|
|
89
|
-
const logoutCommand = Command.make("logout", {}, () =>
|
|
90
|
-
Effect.gen(function*() {
|
|
91
|
-
const auth = yield* JiraAuth
|
|
92
|
-
yield* auth.logout()
|
|
93
|
-
yield* Console.log("Logged out")
|
|
94
|
-
})).pipe(Command.withDescription("Remove stored authentication"))
|
|
95
|
-
|
|
96
|
-
// === Auth status command ===
|
|
97
|
-
const statusCommand = Command.make("status", {}, () =>
|
|
98
|
-
Effect.gen(function*() {
|
|
99
|
-
const auth = yield* JiraAuth
|
|
100
|
-
const profile = yield* auth.getActiveProfile()
|
|
101
|
-
if (profile) {
|
|
102
|
-
const user = profile.token.user
|
|
103
|
-
const account = user ? `${user.name} (${user.email})` : "unknown user"
|
|
104
|
-
yield* Console.log(`Active profile: ${profile.name}`)
|
|
105
|
-
yield* Console.log(`Account: ${account}`)
|
|
106
|
-
yield* Console.log(`Site: ${profile.token.site_url}`)
|
|
107
|
-
yield* Console.log(`Profile ID: ${profile.id}`)
|
|
108
|
-
} else {
|
|
109
|
-
yield* Console.log("Not logged in. Use 'jira auth login' to authenticate.")
|
|
110
|
-
}
|
|
111
|
-
})).pipe(Command.withDescription("Show authentication status"))
|
|
112
|
-
|
|
113
|
-
// === Auth profiles command ===
|
|
114
|
-
const profilesCommand = Command.make("profiles", {}, () =>
|
|
115
|
-
Effect.gen(function*() {
|
|
116
|
-
const auth = yield* JiraAuth
|
|
117
|
-
const [profiles, active] = yield* Effect.all([auth.listProfiles(), auth.getActiveProfile()])
|
|
118
|
-
if (profiles.length === 0) {
|
|
119
|
-
yield* Console.log("No auth profiles. Use 'jira auth login' to authenticate.")
|
|
120
|
-
return
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
for (const profile of profiles) {
|
|
124
|
-
const marker = active?.id === profile.id ? "*" : " "
|
|
125
|
-
yield* Console.log(`${marker} ${profile.id}`)
|
|
126
|
-
yield* Console.log(` ${profile.name}`)
|
|
127
|
-
yield* Console.log(` ${profile.token.site_url}`)
|
|
128
|
-
}
|
|
129
|
-
})).pipe(Command.withDescription("List stored auth profiles"))
|
|
130
|
-
|
|
131
|
-
const profileArg = Args.String("profile").pipe(
|
|
132
|
-
Args.withDescription("Profile ID, name, site URL, cloud ID, or account ID")
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
// === Auth use command ===
|
|
136
|
-
const useCommand = Command.make("use", { profile: profileArg }, ({ profile }) =>
|
|
137
|
-
Effect.gen(function*() {
|
|
138
|
-
const auth = yield* JiraAuth
|
|
139
|
-
const selected = yield* auth.switchProfile(profile)
|
|
140
|
-
if (!selected) {
|
|
141
|
-
yield* Console.log(`Profile not found: ${profile}`)
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
yield* Console.log(`Active profile: ${selected.name}`)
|
|
145
|
-
})).pipe(Command.withDescription("Switch active auth profile"))
|
|
146
|
-
|
|
147
|
-
// === Auth remove command ===
|
|
148
|
-
const removeCommand = Command.make("remove", { profile: profileArg }, ({ profile }) =>
|
|
149
|
-
Effect.gen(function*() {
|
|
150
|
-
const auth = yield* JiraAuth
|
|
151
|
-
const removed = yield* auth.removeProfile(profile)
|
|
152
|
-
if (!removed) {
|
|
153
|
-
yield* Console.log(`Profile not found: ${profile}`)
|
|
154
|
-
return
|
|
155
|
-
}
|
|
156
|
-
yield* Console.log(`Removed profile: ${removed.name}`)
|
|
157
|
-
})).pipe(Command.withDescription("Remove stored auth profile"))
|
|
6
|
+
import { makeAuthCommand } from "@knpkv/atlassian-common/cli-auth"
|
|
7
|
+
import { JiraAuth, jiraCliDescriptor } from "../JiraAuth.js"
|
|
158
8
|
|
|
159
|
-
|
|
160
|
-
export const authCommand = Command.make("auth").pipe(
|
|
161
|
-
Command.withDescription("Manage OAuth authentication"),
|
|
162
|
-
Command.withSubcommands([
|
|
163
|
-
createCommand,
|
|
164
|
-
configureCommand,
|
|
165
|
-
loginCommand,
|
|
166
|
-
logoutCommand,
|
|
167
|
-
statusCommand,
|
|
168
|
-
profilesCommand,
|
|
169
|
-
useCommand,
|
|
170
|
-
removeCommand
|
|
171
|
-
])
|
|
172
|
-
)
|
|
9
|
+
export const authCommand = makeAuthCommand(JiraAuth, jiraCliDescriptor, { apiSection: "Jira API" })
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/** @effect-diagnostics strictEffectProvide:skip-file */
|
|
2
|
+
/**
|
|
3
|
+
* `jira auth create` must list every scope `jira auth login` requests: Atlassian rejects an
|
|
4
|
+
* authorization request naming a scope the app lacks, so a shorter list is a setup that cannot log in.
|
|
5
|
+
*/
|
|
6
|
+
import { NodeServices } from "@effect/platform-node"
|
|
7
|
+
import { expect, it } from "@effect/vitest"
|
|
8
|
+
import { Command } from "effect/cli"
|
|
9
|
+
import * as Console from "effect/Console"
|
|
10
|
+
import * as Effect from "effect/Effect"
|
|
11
|
+
import { FetchHttpClient } from "effect/http"
|
|
12
|
+
import * as Layer from "effect/Layer"
|
|
13
|
+
import { ChildProcessSpawner } from "effect/process"
|
|
14
|
+
import * as Sink from "effect/Sink"
|
|
15
|
+
import * as Stream from "effect/Stream"
|
|
16
|
+
import { authCommand } from "../src/commands/auth.js"
|
|
17
|
+
import { jiraCliDescriptor, layer } from "../src/JiraAuth.js"
|
|
18
|
+
|
|
19
|
+
const browserOpens = ChildProcessSpawner.make(() =>
|
|
20
|
+
Effect.succeed(ChildProcessSpawner.makeHandle({
|
|
21
|
+
all: Stream.empty,
|
|
22
|
+
exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)),
|
|
23
|
+
getInputFd: () => Sink.drain,
|
|
24
|
+
getOutputFd: () => Stream.empty,
|
|
25
|
+
isRunning: Effect.succeed(false),
|
|
26
|
+
kill: () => Effect.void,
|
|
27
|
+
pid: ChildProcessSpawner.ProcessId(1),
|
|
28
|
+
reref: Effect.void,
|
|
29
|
+
stderr: Stream.empty,
|
|
30
|
+
stdin: Sink.drain,
|
|
31
|
+
stdout: Stream.empty,
|
|
32
|
+
unref: Effect.succeed(Effect.void)
|
|
33
|
+
}))
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
it.effect("auth create lists every scope JiraAuth logs in with", () =>
|
|
37
|
+
Effect.gen(function*() {
|
|
38
|
+
const lines: Array<string> = []
|
|
39
|
+
const capture: Console.Console = Object.assign(Object.create(console), {
|
|
40
|
+
log: (...parts: ReadonlyArray<unknown>) => lines.push(parts.join(" "))
|
|
41
|
+
})
|
|
42
|
+
const cli = Command.runWith(Command.make("jira").pipe(Command.withSubcommands([authCommand])), { version: "0" })
|
|
43
|
+
const exit = yield* cli(["auth", "create"]).pipe(Effect.provideService(Console.Console, capture), Effect.exit)
|
|
44
|
+
expect(exit._tag).toBe("Success")
|
|
45
|
+
const output = lines.join("\n")
|
|
46
|
+
const permissions = jiraCliDescriptor.scopes.filter((scope) => scope !== "offline_access")
|
|
47
|
+
expect(permissions.length).toBeGreaterThan(2)
|
|
48
|
+
for (const scope of permissions) expect(output).toContain(scope)
|
|
49
|
+
expect(output).toContain("jira auth configure")
|
|
50
|
+
}).pipe(
|
|
51
|
+
// The command's browser launch sees the fake; the auth layer is built over real platform services.
|
|
52
|
+
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, browserOpens),
|
|
53
|
+
Effect.provide(layer.pipe(Layer.provideMerge(Layer.mergeAll(FetchHttpClient.layer, NodeServices.layer))))
|
|
54
|
+
))
|