@knpkv/jira-cli 0.1.1 → 0.3.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 +47 -0
- package/README.md +63 -1
- package/dist/IssueService.d.ts +2 -2
- package/dist/IssueService.d.ts.map +1 -1
- package/dist/IssueService.js +3 -3
- package/dist/IssueService.js.map +1 -1
- package/dist/JiraAuth.d.ts +14 -14
- package/dist/JiraAuth.d.ts.map +1 -1
- package/dist/JiraAuth.js +18 -10
- package/dist/JiraAuth.js.map +1 -1
- package/dist/MarkdownWriter.d.ts +4 -4
- package/dist/MarkdownWriter.d.ts.map +1 -1
- package/dist/MarkdownWriter.js +6 -6
- package/dist/MarkdownWriter.js.map +1 -1
- package/dist/VersionService.d.ts +206 -0
- package/dist/VersionService.d.ts.map +1 -0
- package/dist/VersionService.js +426 -0
- package/dist/VersionService.js.map +1 -0
- package/dist/bin.js +28 -20
- package/dist/bin.js.map +1 -1
- package/dist/commands/auth.d.ts +2 -21
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +6 -6
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/get.d.ts +3 -8
- package/dist/commands/get.d.ts.map +1 -1
- package/dist/commands/get.js +2 -2
- package/dist/commands/get.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/layers.d.ts +6 -18
- package/dist/commands/layers.d.ts.map +1 -1
- package/dist/commands/layers.js +31 -23
- package/dist/commands/layers.js.map +1 -1
- package/dist/commands/search.d.ts +3 -8
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +4 -4
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/version.d.ts +12 -0
- package/dist/commands/version.d.ts.map +1 -0
- package/dist/commands/version.js +179 -0
- package/dist/commands/version.js.map +1 -0
- package/dist/internal/oauthServer.d.ts +17 -5
- package/dist/internal/oauthServer.d.ts.map +1 -1
- package/dist/internal/oauthServer.js +23 -40
- package/dist/internal/oauthServer.js.map +1 -1
- package/dist/internal/openBrowser.d.ts +10 -0
- package/dist/internal/openBrowser.d.ts.map +1 -0
- package/dist/internal/openBrowser.js +17 -0
- package/dist/internal/openBrowser.js.map +1 -0
- package/package.json +10 -12
- package/skills/jira/SKILL.md +90 -0
- package/skills/jira/agents/openai.yaml +4 -0
- package/src/IssueService.ts +34 -28
- package/src/JiraAuth.ts +53 -39
- package/src/MarkdownWriter.ts +7 -11
- package/src/VersionService.ts +647 -0
- package/src/bin.ts +38 -26
- package/src/commands/auth.ts +6 -12
- package/src/commands/get.ts +2 -2
- package/src/commands/index.ts +1 -0
- package/src/commands/layers.ts +40 -25
- package/src/commands/search.ts +4 -4
- package/src/commands/version.ts +267 -0
- package/src/internal/oauthServer.ts +43 -70
- package/src/internal/openBrowser.ts +31 -0
- package/test/VersionService.test.ts +266 -0
- package/vitest.config.ts +5 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `jira version` command — list / view Jira project versions (releases) with
|
|
3
|
+
* Driver, Contributors and Approver fields resolved to display names, plus
|
|
4
|
+
* mutations: edit the description and manage "Related work" links (the
|
|
5
|
+
* Confluence pages surfaced on a release report).
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
import * as Console from "effect/Console";
|
|
10
|
+
import * as Effect from "effect/Effect";
|
|
11
|
+
import * as Option from "effect/Option";
|
|
12
|
+
import { Argument as Args, Command, Flag as Options } from "effect/unstable/cli";
|
|
13
|
+
import { JiraApiError } from "../JiraCliError.js";
|
|
14
|
+
import { VersionService } from "../VersionService.js";
|
|
15
|
+
/**
|
|
16
|
+
* Return a copy of `version` with every resolved {@link Person.emailAddress}
|
|
17
|
+
* (PII) set to null — covering driver, contributors, approvers[].person and
|
|
18
|
+
* tickets[].assignee. Used to keep emails out of `--json` output unless the
|
|
19
|
+
* caller opts in with `--emails`.
|
|
20
|
+
*/
|
|
21
|
+
export const stripEmails = (version) => {
|
|
22
|
+
const stripPerson = (person) => ({ ...person, emailAddress: null });
|
|
23
|
+
return {
|
|
24
|
+
...version,
|
|
25
|
+
driver: version.driver ? stripPerson(version.driver) : null,
|
|
26
|
+
contributors: version.contributors.map(stripPerson),
|
|
27
|
+
approvers: version.approvers.map((a) => ({ ...a, person: stripPerson(a.person) })),
|
|
28
|
+
tickets: version.tickets.map((t) => ({
|
|
29
|
+
...t,
|
|
30
|
+
assignee: t.assignee ? stripPerson(t.assignee) : null
|
|
31
|
+
}))
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Jira version ids are numeric (e.g. `10042`). Passing a name/key 404s with an
|
|
36
|
+
* opaque error, so validate early and emit a hint pointing at `version list`.
|
|
37
|
+
*/
|
|
38
|
+
const ensureNumericId = (id) => /^\d+$/.test(id)
|
|
39
|
+
? Effect.void
|
|
40
|
+
: Effect.fail(new JiraApiError({
|
|
41
|
+
message: `Invalid version id "${id}". The version id is numeric (e.g. 10042); ` +
|
|
42
|
+
`use 'jira version list --project <KEY>' to find it.`
|
|
43
|
+
}));
|
|
44
|
+
const projectOption = Options.string("project").pipe(Options.withAlias("p"), Options.withDescription("Jira project key (e.g. RPS)"));
|
|
45
|
+
const releasedOption = Options.boolean("released").pipe(Options.withDescription("Only list released versions"), Options.withDefault(false));
|
|
46
|
+
const unreleasedOption = Options.boolean("unreleased").pipe(Options.withDescription("Only list unreleased versions"), Options.withDefault(false));
|
|
47
|
+
const jsonOption = Options.boolean("json").pipe(Options.withDescription("Output as JSON"), Options.withDefault(false));
|
|
48
|
+
const emailsOption = Options.boolean("emails").pipe(Options.withDescription("Include resolved user email addresses in --json output"), Options.withDefault(false));
|
|
49
|
+
const customFieldOption = Options.string("custom-field").pipe(Options.withDescription("Custom field display name to include on each ticket (repeatable, e.g. " +
|
|
50
|
+
"--custom-field \"Security & Compliance Impact\"). Values are exposed in " +
|
|
51
|
+
"ticket.customFields[<name>]."), Options.atLeast(0));
|
|
52
|
+
const maxOption = Options.integer("max").pipe(Options.withAlias("m"), Options.withDescription("Maximum number of versions to fetch (default: all)"), Options.optional);
|
|
53
|
+
const idArg = Args.string("id").pipe(Args.withDescription("Version id (numeric)"));
|
|
54
|
+
const listCommand = Command.make("list", {
|
|
55
|
+
project: projectOption,
|
|
56
|
+
released: releasedOption,
|
|
57
|
+
unreleased: unreleasedOption,
|
|
58
|
+
customFields: customFieldOption,
|
|
59
|
+
max: maxOption,
|
|
60
|
+
json: jsonOption,
|
|
61
|
+
emails: emailsOption
|
|
62
|
+
}, ({ customFields, emails, json, max, project, released, unreleased }) => Effect.gen(function* () {
|
|
63
|
+
if (released && unreleased) {
|
|
64
|
+
return yield* Effect.fail(new JiraApiError({
|
|
65
|
+
message: "--released and --unreleased are mutually exclusive; pass at most one (omit both to list all)."
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
const service = yield* VersionService;
|
|
69
|
+
const versions = yield* service.listProjectVersions(project, {
|
|
70
|
+
released,
|
|
71
|
+
unreleased,
|
|
72
|
+
...(Option.isSome(max) ? { maxResults: max.value } : {}),
|
|
73
|
+
customFieldNames: customFields
|
|
74
|
+
});
|
|
75
|
+
if (json) {
|
|
76
|
+
const output = emails ? versions : versions.map(stripEmails);
|
|
77
|
+
yield* Console.log(JSON.stringify(output, null, 2));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const sep = " ";
|
|
81
|
+
yield* Console.log(["id", "name", "released", "releaseDate", "driver", "contributors", "approvers"].join(sep));
|
|
82
|
+
for (const v of versions) {
|
|
83
|
+
yield* Console.log([
|
|
84
|
+
v.id,
|
|
85
|
+
v.name,
|
|
86
|
+
String(v.released),
|
|
87
|
+
v.releaseDate ?? "-",
|
|
88
|
+
v.driver?.displayName ?? "-",
|
|
89
|
+
v.contributors.map((c) => c.displayName).join("|") || "-",
|
|
90
|
+
v.approvers.map((a) => `${a.person.displayName}:${a.status}`).join("|") || "-"
|
|
91
|
+
].join(sep));
|
|
92
|
+
}
|
|
93
|
+
})).pipe(Command.withDescription("List versions for a Jira project"));
|
|
94
|
+
/** Cap on the number of ticket keys listed in the human `view` output. */
|
|
95
|
+
const TICKET_KEYS_LIMIT = 20;
|
|
96
|
+
const viewCommand = Command.make("view", { id: idArg, json: jsonOption, emails: emailsOption }, ({ emails, id, json }) => Effect.gen(function* () {
|
|
97
|
+
yield* ensureNumericId(id);
|
|
98
|
+
const service = yield* VersionService;
|
|
99
|
+
const version = yield* service.getVersion(id);
|
|
100
|
+
if (json) {
|
|
101
|
+
const output = emails ? version : stripEmails(version);
|
|
102
|
+
yield* Console.log(JSON.stringify(output, null, 2));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
yield* Console.log(`# ${version.name} (${version.id})`);
|
|
106
|
+
yield* Console.log(`released: ${version.released}`);
|
|
107
|
+
yield* Console.log(`releaseDate: ${version.releaseDate ?? "-"}`);
|
|
108
|
+
yield* Console.log(`driver: ${version.driver?.displayName ?? "-"}`);
|
|
109
|
+
yield* Console.log(`contributors: ${version.contributors.map((c) => c.displayName).join(", ") || "-"}`);
|
|
110
|
+
yield* Console.log(`approvers: ${version.approvers.map((a) => `${a.person.displayName}:${a.status}`).join(", ") || "-"}`);
|
|
111
|
+
yield* Console.log(`tickets (${version.tickets.length}): ${formatTicketKeys(version.tickets)}`);
|
|
112
|
+
})).pipe(Command.withDescription("Show a single Jira version"));
|
|
113
|
+
/**
|
|
114
|
+
* Render a version's ticket keys for the human `view`: the first
|
|
115
|
+
* {@link TICKET_KEYS_LIMIT} keys, with a `(+M more)` suffix when truncated, or
|
|
116
|
+
* `-` when there are none.
|
|
117
|
+
*/
|
|
118
|
+
const formatTicketKeys = (tickets) => {
|
|
119
|
+
if (tickets.length === 0)
|
|
120
|
+
return "-";
|
|
121
|
+
const keys = tickets.map((t) => t.key);
|
|
122
|
+
const shown = keys.slice(0, TICKET_KEYS_LIMIT).join(", ");
|
|
123
|
+
const remaining = keys.length - TICKET_KEYS_LIMIT;
|
|
124
|
+
return remaining > 0 ? `${shown} (+${remaining} more)` : shown;
|
|
125
|
+
};
|
|
126
|
+
const descriptionOption = Options.string("description").pipe(Options.withAlias("d"), Options.withDescription("New version description"));
|
|
127
|
+
const setCommand = Command.make("set", { id: idArg, description: descriptionOption, json: jsonOption }, ({ description, id, json }) => Effect.gen(function* () {
|
|
128
|
+
yield* ensureNumericId(id);
|
|
129
|
+
const service = yield* VersionService;
|
|
130
|
+
const version = yield* service.updateVersion(id, { description });
|
|
131
|
+
if (json) {
|
|
132
|
+
yield* Console.log(JSON.stringify(version, null, 2));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
yield* Console.log(`Updated version ${version.name} (${version.id})`);
|
|
136
|
+
yield* Console.log(`description: ${version.description ?? "-"}`);
|
|
137
|
+
})).pipe(Command.withDescription("Update a version's description (requires manage:jira-project scope)"));
|
|
138
|
+
// === relatedwork ===
|
|
139
|
+
const titleOption = Options.string("title").pipe(Options.withAlias("t"), Options.withDescription("Related-work link title (e.g. \"Release notes\")"));
|
|
140
|
+
const urlOption = Options.string("url").pipe(Options.withAlias("u"), Options.withDescription("Related-work link URL (e.g. a Confluence page)"));
|
|
141
|
+
const categoryOption = Options.string("category").pipe(Options.withAlias("c"), Options.withDescription("Related-work category (Jira groups by this; e.g. Communication, Testing, Design)"), Options.withDefault("Communication"));
|
|
142
|
+
const relatedWorkListCommand = Command.make("list", { id: idArg, json: jsonOption }, ({ id, json }) => Effect.gen(function* () {
|
|
143
|
+
yield* ensureNumericId(id);
|
|
144
|
+
const service = yield* VersionService;
|
|
145
|
+
const items = yield* service.listRelatedWork(id);
|
|
146
|
+
if (json) {
|
|
147
|
+
yield* Console.log(JSON.stringify(items, null, 2));
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (items.length === 0) {
|
|
151
|
+
yield* Console.log("(no related work)");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const sep = " ";
|
|
155
|
+
yield* Console.log(["category", "title", "url"].join(sep));
|
|
156
|
+
for (const w of items) {
|
|
157
|
+
yield* Console.log([w.category || "-", w.title ?? "-", w.url ?? "-"].join(sep));
|
|
158
|
+
}
|
|
159
|
+
})).pipe(Command.withDescription("List a version's related-work links"));
|
|
160
|
+
const relatedWorkAddCommand = Command.make("add", {
|
|
161
|
+
id: idArg,
|
|
162
|
+
title: titleOption,
|
|
163
|
+
url: urlOption,
|
|
164
|
+
category: categoryOption,
|
|
165
|
+
json: jsonOption
|
|
166
|
+
}, ({ category, id, json, title, url }) => Effect.gen(function* () {
|
|
167
|
+
yield* ensureNumericId(id);
|
|
168
|
+
const service = yield* VersionService;
|
|
169
|
+
const created = yield* service.addRelatedWork(id, { title, category, url });
|
|
170
|
+
if (json) {
|
|
171
|
+
yield* Console.log(JSON.stringify(created, null, 2));
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
yield* Console.log(`Attached "${created.title ?? title}" (${created.category}) to version ${id}`);
|
|
175
|
+
yield* Console.log(`url: ${created.url ?? url}`);
|
|
176
|
+
})).pipe(Command.withDescription("Attach a related-work link (e.g. a Confluence page) to a version (requires manage:jira-project scope)"));
|
|
177
|
+
const relatedWorkCommand = Command.make("relatedwork").pipe(Command.withDescription("List or attach version related-work links (Confluence pages on the release report)"), Command.withSubcommands([relatedWorkListCommand, relatedWorkAddCommand]));
|
|
178
|
+
export const versionCommand = Command.make("version").pipe(Command.withDescription("List, view, or edit Jira project versions (releases)"), Command.withSubcommands([listCommand, viewCommand, setCommand, relatedWorkCommand]));
|
|
179
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/commands/version.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Local HTTP callback server for OAuth2 authorization code capture.
|
|
3
|
+
*
|
|
4
|
+
* **Mental model**
|
|
5
|
+
*
|
|
6
|
+
* - **Deferred-coordinated lifecycle**: {@link startCallbackServer} returns a `codePromise`
|
|
7
|
+
* (Deferred) and a `shutdown` effect. The server validates the CSRF `state` parameter
|
|
8
|
+
* and resolves the Deferred with the authorization code.
|
|
9
|
+
* - **Port auto-discovery**: Tries default port 8585, increments on conflict.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
3
13
|
import { OAuthError } from "@knpkv/atlassian-common/auth";
|
|
4
14
|
import * as Context from "effect/Context";
|
|
5
15
|
import * as Effect from "effect/Effect";
|
|
6
16
|
import * as Layer from "effect/Layer";
|
|
17
|
+
import { HttpServer } from "effect/unstable/http";
|
|
18
|
+
import type * as HttpServerError from "effect/unstable/http/HttpServerError";
|
|
7
19
|
/**
|
|
8
20
|
* Factory service for creating HTTP servers.
|
|
9
21
|
* This allows mocking the server creation in tests.
|
|
@@ -11,9 +23,9 @@ import * as Layer from "effect/Layer";
|
|
|
11
23
|
* @category Services
|
|
12
24
|
*/
|
|
13
25
|
export interface HttpServerFactory {
|
|
14
|
-
readonly createServerLayer: (port: number) => Layer.Layer<HttpServer.HttpServer, ServeError, never>;
|
|
26
|
+
readonly createServerLayer: (port: number) => Layer.Layer<HttpServer.HttpServer, HttpServerError.ServeError, never>;
|
|
15
27
|
}
|
|
16
|
-
declare const HttpServerFactoryTag_base: Context.
|
|
28
|
+
declare const HttpServerFactoryTag_base: Context.ServiceClass<HttpServerFactoryTag, "@knpkv/jira-cli/HttpServerFactory", HttpServerFactory>;
|
|
17
29
|
/**
|
|
18
30
|
* Tag for the HttpServerFactory service.
|
|
19
31
|
*
|
|
@@ -30,7 +42,7 @@ export declare class HttpServerFactoryTag extends HttpServerFactoryTag_base {
|
|
|
30
42
|
*
|
|
31
43
|
* @category Layers
|
|
32
44
|
*/
|
|
33
|
-
export declare const makeHttpServerFactory: (createLayerFn: (port: number) => Layer.Layer<HttpServer.HttpServer, ServeError, never>) => Layer.Layer<HttpServerFactoryTag>;
|
|
45
|
+
export declare const makeHttpServerFactory: (createLayerFn: (port: number) => Layer.Layer<HttpServer.HttpServer, HttpServerError.ServeError, never>) => Layer.Layer<HttpServerFactoryTag>;
|
|
34
46
|
/**
|
|
35
47
|
* Result from the OAuth callback server.
|
|
36
48
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauthServer.d.ts","sourceRoot":"","sources":["../../src/internal/oauthServer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"oauthServer.d.ts","sourceRoot":"","sources":["../../src/internal/oauthServer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAA;AAEzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAGvC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAA;AAErC,OAAO,EAAc,UAAU,EAAsB,MAAM,sBAAsB,CAAA;AACjF,OAAO,KAAK,KAAK,eAAe,MAAM,sCAAsC,CAAA;AAK5E;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,iBAAiB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC,KAAK,CACvD,UAAU,CAAC,UAAU,EACrB,eAAe,CAAC,UAAU,EAC1B,KAAK,CACN,CAAA;CACF;;AAED;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,yBAGF;CAAG;AAE3C;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,GAChC,eAAe,CAAC,IAAI,EAAE,MAAM,KAAK,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,EAAE,eAAe,CAAC,UAAU,EAAE,KAAK,CAAC,KACrG,KAAK,CAAC,KAAK,CAAC,oBAAoB,CAG/B,CAAA;AAEJ;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,wDAAwD;IACxD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IACvD,mCAAmC;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAC7C,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,mBAAmB,GAC9B,eAAe,MAAM,KACpB,MAAM,CAAC,MAAM,CAAC,oBAAoB,EAAE,UAAU,EAAE,oBAAoB,CAuFnE,CAAA"}
|
|
@@ -10,24 +10,23 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @internal
|
|
12
12
|
*/
|
|
13
|
-
import * as HttpRouter from "@effect/platform/HttpRouter";
|
|
14
|
-
import * as HttpServer from "@effect/platform/HttpServer";
|
|
15
|
-
import * as HttpServerRequest from "@effect/platform/HttpServerRequest";
|
|
16
|
-
import * as HttpServerResponse from "@effect/platform/HttpServerResponse";
|
|
17
13
|
import { OAuthError } from "@knpkv/atlassian-common/auth";
|
|
18
14
|
import * as Context from "effect/Context";
|
|
19
15
|
import * as Deferred from "effect/Deferred";
|
|
20
16
|
import * as Effect from "effect/Effect";
|
|
17
|
+
import * as Exit from "effect/Exit";
|
|
21
18
|
import * as Fiber from "effect/Fiber";
|
|
22
19
|
import * as Layer from "effect/Layer";
|
|
20
|
+
import * as Scope from "effect/Scope";
|
|
21
|
+
import { HttpRouter, HttpServer, HttpServerResponse } from "effect/unstable/http";
|
|
23
22
|
const DEFAULT_PORT = 8585;
|
|
24
|
-
const
|
|
23
|
+
const MAX_PORT = 8594;
|
|
25
24
|
/**
|
|
26
25
|
* Tag for the HttpServerFactory service.
|
|
27
26
|
*
|
|
28
27
|
* @category Services
|
|
29
28
|
*/
|
|
30
|
-
export class HttpServerFactoryTag extends Context.
|
|
29
|
+
export class HttpServerFactoryTag extends Context.Service()("@knpkv/jira-cli/HttpServerFactory") {
|
|
31
30
|
}
|
|
32
31
|
/**
|
|
33
32
|
* Create a HttpServerFactory layer from a layer factory function.
|
|
@@ -41,31 +40,6 @@ export class HttpServerFactoryTag extends Context.Tag("@knpkv/jira-cli/HttpServe
|
|
|
41
40
|
export const makeHttpServerFactory = (createLayerFn) => Layer.succeed(HttpServerFactoryTag, {
|
|
42
41
|
createServerLayer: createLayerFn
|
|
43
42
|
});
|
|
44
|
-
/**
|
|
45
|
-
* Check if a port is available by attempting to start a server.
|
|
46
|
-
*/
|
|
47
|
-
const isPortAvailable = (port) => Effect.gen(function* () {
|
|
48
|
-
const factory = yield* HttpServerFactoryTag;
|
|
49
|
-
const serverLayer = factory.createServerLayer(port);
|
|
50
|
-
const result = yield* Layer.build(serverLayer).pipe(Effect.scoped, Effect.as(true), Effect.catchAll(() => Effect.succeed(false)));
|
|
51
|
-
return result;
|
|
52
|
-
});
|
|
53
|
-
/**
|
|
54
|
-
* Find an available port starting from the default.
|
|
55
|
-
*/
|
|
56
|
-
const findAvailablePort = () => Effect.gen(function* () {
|
|
57
|
-
for (let attempt = 0; attempt < MAX_PORT_ATTEMPTS; attempt++) {
|
|
58
|
-
const port = DEFAULT_PORT + attempt;
|
|
59
|
-
const available = yield* isPortAvailable(port);
|
|
60
|
-
if (available) {
|
|
61
|
-
return port;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return yield* Effect.fail(new OAuthError({
|
|
65
|
-
step: "authorize",
|
|
66
|
-
cause: `Could not find available port (tried ${DEFAULT_PORT}-${DEFAULT_PORT + MAX_PORT_ATTEMPTS - 1}). Close other applications using these ports.`
|
|
67
|
-
}));
|
|
68
|
-
});
|
|
69
43
|
/**
|
|
70
44
|
* Start a local HTTP server to receive OAuth callback.
|
|
71
45
|
*
|
|
@@ -76,11 +50,18 @@ const findAvailablePort = () => Effect.gen(function* () {
|
|
|
76
50
|
*/
|
|
77
51
|
export const startCallbackServer = (expectedState) => Effect.gen(function* () {
|
|
78
52
|
const factory = yield* HttpServerFactoryTag;
|
|
79
|
-
const port = yield* findAvailablePort();
|
|
80
53
|
const deferred = yield* Deferred.make();
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
|
|
54
|
+
const serverScope = yield* Scope.make();
|
|
55
|
+
const buildServerContext = (port) => Layer.buildWithScope(factory.createServerLayer(port), serverScope).pipe(Effect.map((context) => ({ context, port })), Effect.catchCause((cause) => port < MAX_PORT
|
|
56
|
+
? buildServerContext(port + 1)
|
|
57
|
+
: Effect.fail(new OAuthError({ step: "authorize", cause }))));
|
|
58
|
+
const { context: serverContext } = yield* buildServerContext(DEFAULT_PORT);
|
|
59
|
+
const server = Context.get(serverContext, HttpServer.HttpServer);
|
|
60
|
+
const port = yield* (server.address._tag === "TcpAddress"
|
|
61
|
+
? Effect.succeed(server.address.port)
|
|
62
|
+
: Effect.fail(new OAuthError({ step: "authorize", cause: "OAuth callback server must listen on a TCP port" })));
|
|
63
|
+
const router = yield* HttpRouter.make;
|
|
64
|
+
yield* router.add("GET", "/callback", (req) => Effect.gen(function* () {
|
|
84
65
|
const url = new URL(req.url, `http://localhost:${port}`);
|
|
85
66
|
const code = url.searchParams.get("code");
|
|
86
67
|
const state = url.searchParams.get("state");
|
|
@@ -100,13 +81,15 @@ export const startCallbackServer = (expectedState) => Effect.gen(function* () {
|
|
|
100
81
|
}
|
|
101
82
|
yield* Deferred.succeed(deferred, code);
|
|
102
83
|
return HttpServerResponse.html("<html><body><h1>Success!</h1><p>You can close this window and return to the terminal.</p></body></html>");
|
|
103
|
-
}))
|
|
104
|
-
const
|
|
105
|
-
const serverFiber = yield* HttpServer.
|
|
106
|
-
yield* Deferred.await(readyDeferred);
|
|
84
|
+
}));
|
|
85
|
+
const app = router.asHttpEffect();
|
|
86
|
+
const serverFiber = yield* HttpServer.serveEffect(app).pipe(Effect.provide(serverContext), Effect.provideService(Scope.Scope, serverScope), Effect.forkIn(serverScope));
|
|
107
87
|
return {
|
|
108
88
|
codePromise: Deferred.await(deferred),
|
|
109
|
-
shutdown:
|
|
89
|
+
shutdown: Effect.gen(function* () {
|
|
90
|
+
yield* Fiber.interrupt(serverFiber);
|
|
91
|
+
yield* Scope.close(serverScope, Exit.void);
|
|
92
|
+
}),
|
|
110
93
|
port
|
|
111
94
|
};
|
|
112
95
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauthServer.js","sourceRoot":"","sources":["../../src/internal/oauthServer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,
|
|
1
|
+
{"version":3,"file":"oauthServer.js","sourceRoot":"","sources":["../../src/internal/oauthServer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform browser launcher backed by Effect v4 child process services.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import type * as PlatformError from "effect/PlatformError";
|
|
8
|
+
import { ChildProcessSpawner } from "effect/unstable/process";
|
|
9
|
+
export declare const openBrowser: (url: string) => Effect.Effect<void, PlatformError.PlatformError, ChildProcessSpawner.ChildProcessSpawner>;
|
|
10
|
+
//# sourceMappingURL=openBrowser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openBrowser.d.ts","sourceRoot":"","sources":["../../src/internal/openBrowser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AACvC,OAAO,KAAK,KAAK,aAAa,MAAM,sBAAsB,CAAA;AAC1D,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAiB3E,eAAO,MAAM,WAAW,GACtB,KAAK,MAAM,KACV,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,aAAa,CAAC,aAAa,EAAE,mBAAmB,CAAC,mBAAmB,CAIxF,CAAA"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform browser launcher backed by Effect v4 child process services.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
import * as Effect from "effect/Effect";
|
|
7
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
|
|
8
|
+
const run = (command, args) => Effect.gen(function* () {
|
|
9
|
+
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
|
|
10
|
+
yield* spawner.exitCode(ChildProcess.make(command, args, {
|
|
11
|
+
stdin: "ignore",
|
|
12
|
+
stdout: "ignore",
|
|
13
|
+
stderr: "ignore"
|
|
14
|
+
}));
|
|
15
|
+
});
|
|
16
|
+
export const openBrowser = (url) => run("open", [url]).pipe(Effect.catch(() => run("xdg-open", [url])), Effect.catch(() => run("rundll32.exe", ["url.dll,FileProtocolHandler", url])));
|
|
17
|
+
//# sourceMappingURL=openBrowser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openBrowser.js","sourceRoot":"","sources":["../../src/internal/openBrowser.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAA;AAEvC,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAA;AAE3E,MAAM,GAAG,GAAG,CACV,OAAe,EACf,IAA2B,EACgE,EAAE,CAC7F,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,mBAAmB,CAAC,mBAAmB,CAAA;IAC9D,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,CACrB,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE;QAC/B,KAAK,EAAE,QAAQ;QACf,MAAM,EAAE,QAAQ;QAChB,MAAM,EAAE,QAAQ;KACjB,CAAC,CACH,CAAA;AACH,CAAC,CAAC,CAAA;AAEJ,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,GAAW,EACgF,EAAE,CAC7F,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CACrB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAC1C,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,cAAc,EAAE,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC,CAAC,CAC9E,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@knpkv/jira-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI tool to fetch Jira tickets and export to markdown",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "knpkv",
|
|
@@ -46,23 +46,21 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"
|
|
50
|
-
"effect": ">=3.19.3"
|
|
49
|
+
"effect": "4.0.0-beta.87"
|
|
51
50
|
},
|
|
52
51
|
"dependencies": {
|
|
53
|
-
"@effect/
|
|
54
|
-
"@effect/platform-node": "latest",
|
|
52
|
+
"@effect/platform-node": "4.0.0-beta.87",
|
|
55
53
|
"gray-matter": "^4.0.3",
|
|
56
|
-
"@knpkv/
|
|
57
|
-
"@knpkv/
|
|
54
|
+
"@knpkv/agent-skills": "^0.2.0",
|
|
55
|
+
"@knpkv/atlassian-common": "0.3.0",
|
|
56
|
+
"@knpkv/jira-api-client": "0.3.0"
|
|
58
57
|
},
|
|
59
58
|
"devDependencies": {
|
|
60
|
-
"@effect/
|
|
61
|
-
"@effect/vitest": "latest",
|
|
59
|
+
"@effect/vitest": "4.0.0-beta.87",
|
|
62
60
|
"@types/node": "latest",
|
|
63
|
-
"effect": "
|
|
64
|
-
"typescript": "~
|
|
65
|
-
"vitest": "^4.
|
|
61
|
+
"effect": "4.0.0-beta.87",
|
|
62
|
+
"typescript": "~6.0.3",
|
|
63
|
+
"vitest": "^4.1.9"
|
|
66
64
|
},
|
|
67
65
|
"keywords": [
|
|
68
66
|
"effect",
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jira
|
|
3
|
+
description: Use the @knpkv/jira-cli command line tool to authenticate with Jira Cloud, fetch Jira issues, export issues to markdown, search by JQL or fixVersion, and inspect or update Jira project versions and related work links. Trigger when the user asks an agent to query Jira, collect tickets for release notes, write ticket markdown, inspect release metadata, or attach Confluence release-report links to a Jira version.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Jira
|
|
7
|
+
|
|
8
|
+
Use the `jira` binary for Jira Cloud issue export and release-version workflows.
|
|
9
|
+
|
|
10
|
+
## Preconditions
|
|
11
|
+
|
|
12
|
+
- Authenticate first with `jira auth status`, `jira auth create`, `jira auth configure`, and `jira auth login`.
|
|
13
|
+
- Use `--json` on version commands when the agent needs structured data.
|
|
14
|
+
- Use numeric version ids for `jira version view`, `jira version set`, and `jira version relatedwork`.
|
|
15
|
+
- Confirm before commands that mutate Jira: `jira version set` and `jira version relatedwork add`.
|
|
16
|
+
|
|
17
|
+
## Authentication
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
jira auth status
|
|
21
|
+
jira auth create
|
|
22
|
+
jira auth configure --client-id <id> --client-secret <secret>
|
|
23
|
+
jira auth login
|
|
24
|
+
jira auth login --site https://example.atlassian.net
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
OAuth scopes used by release workflows include `read:jira-work`, `write:jira-work`, `manage:jira-project`, `read:jira-user`, `read:me`, and `offline_access`.
|
|
28
|
+
|
|
29
|
+
## Issue Export
|
|
30
|
+
|
|
31
|
+
Fetch one issue as markdown:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
jira get PROJ-123 --output-dir ./jira-tickets
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Search with JQL:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
jira search 'project = PROJ AND status = Done' --output-dir ./jira-tickets
|
|
41
|
+
jira search 'fixVersion = "1.0.0"' --format single --max-results 200
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Search by fix version:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
jira search --by-version "1.0.0" --project PROJ
|
|
48
|
+
jira search --by-version "1.0.0" --project PROJ --format single
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Output formats:
|
|
52
|
+
|
|
53
|
+
- `--format multi` writes one markdown file per issue.
|
|
54
|
+
- `--format single` writes `jira-export.md`.
|
|
55
|
+
|
|
56
|
+
## Version Workflows
|
|
57
|
+
|
|
58
|
+
List versions:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
jira version list --project PROJ --json
|
|
62
|
+
jira version list --project PROJ --unreleased --max 10 --json
|
|
63
|
+
jira version list --project PROJ --custom-field "Security & Compliance Impact" --json
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
View a version:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
jira version view 10042 --json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Update a version description:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
jira version set 10042 --description "Q3 release"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Manage related work links:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
jira version relatedwork list 10042 --json
|
|
82
|
+
jira version relatedwork add 10042 --title "Release notes" --url "https://example.atlassian.net/wiki/spaces/PROJ/pages/123" --category Communication
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Agent Workflow
|
|
86
|
+
|
|
87
|
+
1. Use read-only commands first to find issue keys, version ids, and current release metadata.
|
|
88
|
+
2. Prefer `--json` for release metadata and parse the resulting JSON instead of scraping tables.
|
|
89
|
+
3. Avoid printing tokens or OAuth secrets. Let interactive commands prompt for secrets when needed.
|
|
90
|
+
4. Confirm exact version id, title, URL, category, or description before mutating Jira.
|