linmux 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/LICENSE +21 -0
- package/README.md +240 -0
- package/bin/run.js +4 -0
- package/dist/commands/comment/create.js +94 -0
- package/dist/commands/comment/delete.js +74 -0
- package/dist/commands/comment/list.js +84 -0
- package/dist/commands/comment/update.js +80 -0
- package/dist/commands/cycle/current.js +78 -0
- package/dist/commands/cycle/list.js +84 -0
- package/dist/commands/cycle/move.js +91 -0
- package/dist/commands/describe.js +65 -0
- package/dist/commands/graphql/index.js +92 -0
- package/dist/commands/install-skill.js +54 -0
- package/dist/commands/issue/archive.js +75 -0
- package/dist/commands/issue/create.js +115 -0
- package/dist/commands/issue/get.js +84 -0
- package/dist/commands/issue/list.js +93 -0
- package/dist/commands/issue/purge.js +81 -0
- package/dist/commands/issue/search.js +109 -0
- package/dist/commands/issue/transition.js +91 -0
- package/dist/commands/issue/trash.js +75 -0
- package/dist/commands/issue/update.js +126 -0
- package/dist/commands/label/create.js +91 -0
- package/dist/commands/label/list.js +76 -0
- package/dist/commands/list-tools.js +47 -0
- package/dist/commands/me.js +71 -0
- package/dist/commands/project/create.js +101 -0
- package/dist/commands/project/get.js +83 -0
- package/dist/commands/project/list.js +75 -0
- package/dist/commands/project/update-status.js +99 -0
- package/dist/commands/project/update.js +99 -0
- package/dist/commands/raw/batch.js +85 -0
- package/dist/commands/raw/index.js +72 -0
- package/dist/commands/schema.js +69 -0
- package/dist/commands/state/list.js +77 -0
- package/dist/commands/team/get.js +73 -0
- package/dist/commands/team/list.js +73 -0
- package/dist/commands/whoami.js +71 -0
- package/dist/commands/workspace/add.js +97 -0
- package/dist/commands/workspace/list.js +47 -0
- package/dist/commands/workspace/remove.js +63 -0
- package/dist/commands/workspace/replace-token.js +89 -0
- package/dist/commands/workspace/use.js +54 -0
- package/dist/core/client/factory.js +28 -0
- package/dist/core/client/index.js +2 -0
- package/dist/core/config/index.js +4 -0
- package/dist/core/config/paths.js +30 -0
- package/dist/core/config/schema.js +36 -0
- package/dist/core/config/store.js +149 -0
- package/dist/core/errors/error.js +142 -0
- package/dist/core/errors/exit-codes.js +70 -0
- package/dist/core/output/envelope.js +53 -0
- package/dist/core/output/format.js +42 -0
- package/dist/core/output/index.js +3 -0
- package/dist/core/pagination/flags.js +29 -0
- package/dist/core/pagination/index.js +2 -0
- package/dist/core/projection/presets.js +116 -0
- package/dist/core/projection/project.js +282 -0
- package/dist/core/redact/redact.js +45 -0
- package/dist/core/resolvers/cycle.js +60 -0
- package/dist/core/resolvers/index.js +7 -0
- package/dist/core/resolvers/label.js +54 -0
- package/dist/core/resolvers/project-status.js +42 -0
- package/dist/core/resolvers/project.js +43 -0
- package/dist/core/resolvers/state.js +46 -0
- package/dist/core/resolvers/team.js +50 -0
- package/dist/core/transport/fetch-interceptor.js +109 -0
- package/dist/core/transport/index.js +3 -0
- package/dist/core/transport/rate-limit.js +167 -0
- package/dist/core/workspace/resolver.js +70 -0
- package/dist/core/workspace/write-guard.js +43 -0
- package/dist/generated/graphql.js +89428 -0
- package/dist/generated/operations.js +3013 -0
- package/dist/lib/comment-create-runtime.js +96 -0
- package/dist/lib/comment-delete-runtime.js +46 -0
- package/dist/lib/comment-list-runtime.js +182 -0
- package/dist/lib/comment-update-runtime.js +93 -0
- package/dist/lib/cycle-current-runtime.js +90 -0
- package/dist/lib/cycle-list-runtime.js +151 -0
- package/dist/lib/cycle-move-runtime.js +142 -0
- package/dist/lib/describe-runtime.js +180 -0
- package/dist/lib/filter-heuristics.js +59 -0
- package/dist/lib/graphql-runtime.js +202 -0
- package/dist/lib/include-fragments.js +73 -0
- package/dist/lib/install-skill-runtime.js +228 -0
- package/dist/lib/introspection-registry.js +488 -0
- package/dist/lib/issue-archive-runtime.js +89 -0
- package/dist/lib/issue-create-runtime.js +175 -0
- package/dist/lib/issue-get-runtime.js +153 -0
- package/dist/lib/issue-list-runtime.js +164 -0
- package/dist/lib/issue-purge-runtime.js +89 -0
- package/dist/lib/issue-search-runtime.js +114 -0
- package/dist/lib/issue-transition-runtime.js +131 -0
- package/dist/lib/issue-trash-runtime.js +84 -0
- package/dist/lib/issue-update-runtime.js +164 -0
- package/dist/lib/label-create-runtime.js +113 -0
- package/dist/lib/label-list-runtime.js +97 -0
- package/dist/lib/levenshtein.js +42 -0
- package/dist/lib/list-tools-runtime.js +38 -0
- package/dist/lib/me-runtime.js +55 -0
- package/dist/lib/project-create-runtime.js +103 -0
- package/dist/lib/project-get-runtime.js +134 -0
- package/dist/lib/project-list-runtime.js +84 -0
- package/dist/lib/project-update-runtime.js +110 -0
- package/dist/lib/project-update-status-runtime.js +91 -0
- package/dist/lib/raw-batch-runtime.js +229 -0
- package/dist/lib/raw-runtime.js +171 -0
- package/dist/lib/schema-loader.js +41 -0
- package/dist/lib/schema-runtime.js +65 -0
- package/dist/lib/state-list-runtime.js +93 -0
- package/dist/lib/team-get-runtime.js +55 -0
- package/dist/lib/team-list-runtime.js +52 -0
- package/dist/lib/workspace-runtime.js +112 -0
- package/dist/operations/_registry.zod.js +5337 -0
- package/oclif.manifest.json +3631 -0
- package/package.json +99 -0
- package/schema.graphql +30772 -0
- package/skills/linmux/SKILL.md +186 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
|
|
2
|
+
import { withRateLimitRetry } from "../core/transport/rate-limit.js";
|
|
3
|
+
import "../core/transport/index.js";
|
|
4
|
+
import { createLinearClient } from "../core/client/factory.js";
|
|
5
|
+
import "../core/client/index.js";
|
|
6
|
+
import { loadConfig } from "../core/config/store.js";
|
|
7
|
+
import "../core/config/index.js";
|
|
8
|
+
import { parseFields, project } from "../core/projection/project.js";
|
|
9
|
+
import { resolveWorkspace } from "../core/workspace/resolver.js";
|
|
10
|
+
import { parsePagination } from "../core/pagination/flags.js";
|
|
11
|
+
import "../core/pagination/index.js";
|
|
12
|
+
import { buildIssueFilter } from "./filter-heuristics.js";
|
|
13
|
+
//#region src/lib/issue-search-runtime.ts
|
|
14
|
+
async function issueSearchRuntime(input) {
|
|
15
|
+
const config = (input.loadConfigOverride ?? loadConfig)();
|
|
16
|
+
const envForResolver = {};
|
|
17
|
+
if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
|
|
18
|
+
if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
|
|
19
|
+
const resolved = resolveWorkspace({
|
|
20
|
+
flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
|
|
21
|
+
env: envForResolver,
|
|
22
|
+
config
|
|
23
|
+
});
|
|
24
|
+
const fields = parseFields(input.flags.fields ?? "defaults", "issue");
|
|
25
|
+
const { first, after } = parsePagination({
|
|
26
|
+
...input.flags.limit !== void 0 ? { limit: input.flags.limit } : {},
|
|
27
|
+
...input.flags.cursor !== void 0 ? { cursor: input.flags.cursor } : {}
|
|
28
|
+
});
|
|
29
|
+
const filter = buildIssueFilter(input.flags);
|
|
30
|
+
const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
|
|
31
|
+
return withFetchInterception(async () => {
|
|
32
|
+
const vars = { first };
|
|
33
|
+
if (after !== void 0) vars.after = after;
|
|
34
|
+
if (filter !== void 0) vars.filter = filter;
|
|
35
|
+
if (input.flags.includeArchived === true) vars.includeArchived = true;
|
|
36
|
+
const payload = await withRateLimitRetry(() => client.searchIssues(input.args.query, vars), input.retryOptsOverride);
|
|
37
|
+
const noSnippet = input.flags.noSnippet === true;
|
|
38
|
+
const projected = await Promise.all(payload.nodes.map(async (result) => {
|
|
39
|
+
const issue = {};
|
|
40
|
+
for (const k of Object.keys(result)) {
|
|
41
|
+
if (k === "metadata") continue;
|
|
42
|
+
if (RELATION_KEYS.has(k)) {
|
|
43
|
+
const desc = Object.getOwnPropertyDescriptor(result, k);
|
|
44
|
+
if (desc) Object.defineProperty(issue, k, desc);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
issue[k] = result[k];
|
|
48
|
+
}
|
|
49
|
+
if (!noSnippet) {
|
|
50
|
+
const md = result.metadata;
|
|
51
|
+
if (md && typeof md === "object") {
|
|
52
|
+
const snippetVal = md.snippet;
|
|
53
|
+
if (typeof snippetVal === "string") issue.snippet = snippetVal;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return project(await hydrateForProjection(issue, fields), fields);
|
|
57
|
+
}));
|
|
58
|
+
const complexity = getLastComplexity();
|
|
59
|
+
return {
|
|
60
|
+
data: projected,
|
|
61
|
+
meta: {
|
|
62
|
+
workspace: resolved.name,
|
|
63
|
+
workspaceSource: resolved.source,
|
|
64
|
+
pageInfo: {
|
|
65
|
+
hasNextPage: Boolean(payload.pageInfo?.hasNextPage),
|
|
66
|
+
endCursor: payload.pageInfo?.endCursor ?? null,
|
|
67
|
+
hasPreviousPage: Boolean(payload.pageInfo?.hasPreviousPage),
|
|
68
|
+
startCursor: payload.pageInfo?.startCursor ?? null
|
|
69
|
+
},
|
|
70
|
+
totalCount: payload.totalCount,
|
|
71
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const RELATION_KEYS = new Set([
|
|
77
|
+
"state",
|
|
78
|
+
"assignee",
|
|
79
|
+
"team",
|
|
80
|
+
"project",
|
|
81
|
+
"cycle",
|
|
82
|
+
"parent"
|
|
83
|
+
]);
|
|
84
|
+
async function hydrateForProjection(issue, spec) {
|
|
85
|
+
const needs = neededRelations(spec);
|
|
86
|
+
if (needs.size === 0) {
|
|
87
|
+
const out = {};
|
|
88
|
+
for (const k of Object.keys(issue)) if (!RELATION_KEYS.has(k)) out[k] = issue[k];
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
const hydrated = {};
|
|
92
|
+
for (const k of Object.keys(issue)) if (RELATION_KEYS.has(k)) {
|
|
93
|
+
if (needs.has(k)) {
|
|
94
|
+
const value = issue[k];
|
|
95
|
+
hydrated[k] = await resolveLazy(value);
|
|
96
|
+
}
|
|
97
|
+
} else hydrated[k] = issue[k];
|
|
98
|
+
return hydrated;
|
|
99
|
+
}
|
|
100
|
+
function neededRelations(spec) {
|
|
101
|
+
if (spec === "*") return new Set(RELATION_KEYS);
|
|
102
|
+
const out = /* @__PURE__ */ new Set();
|
|
103
|
+
for (const path of spec) {
|
|
104
|
+
const head = path.split(".")[0];
|
|
105
|
+
if (head && RELATION_KEYS.has(head)) out.add(head);
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
|
109
|
+
async function resolveLazy(value) {
|
|
110
|
+
if (value && typeof value.then === "function") return await value;
|
|
111
|
+
return value;
|
|
112
|
+
}
|
|
113
|
+
//#endregion
|
|
114
|
+
export { issueSearchRuntime };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { LinearAgentError } from "../core/errors/error.js";
|
|
2
|
+
import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
|
|
3
|
+
import { withRateLimitRetry } from "../core/transport/rate-limit.js";
|
|
4
|
+
import "../core/transport/index.js";
|
|
5
|
+
import { createLinearClient } from "../core/client/factory.js";
|
|
6
|
+
import "../core/client/index.js";
|
|
7
|
+
import { loadConfig } from "../core/config/store.js";
|
|
8
|
+
import "../core/config/index.js";
|
|
9
|
+
import { parseFields, project } from "../core/projection/project.js";
|
|
10
|
+
import { resolveWorkspace } from "../core/workspace/resolver.js";
|
|
11
|
+
import { requireExplicitWorkspaceForWrite } from "../core/workspace/write-guard.js";
|
|
12
|
+
import { ISSUE_IDENTIFIER_RE } from "./filter-heuristics.js";
|
|
13
|
+
import { resolveStateNameToId } from "../core/resolvers/state.js";
|
|
14
|
+
import "../core/resolvers/index.js";
|
|
15
|
+
//#region src/lib/issue-transition-runtime.ts
|
|
16
|
+
async function issueTransitionRuntime(input) {
|
|
17
|
+
const config = (input.loadConfigOverride ?? loadConfig)();
|
|
18
|
+
const envForResolver = {};
|
|
19
|
+
if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
|
|
20
|
+
if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
|
|
21
|
+
const resolved = resolveWorkspace({
|
|
22
|
+
flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
|
|
23
|
+
env: envForResolver,
|
|
24
|
+
config
|
|
25
|
+
});
|
|
26
|
+
requireExplicitWorkspaceForWrite(resolved, input.flags.allowActiveWorkspaceWrite ?? false);
|
|
27
|
+
const fields = parseFields(input.flags.fields ?? "defaults", "issue");
|
|
28
|
+
const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
|
|
29
|
+
return withFetchInterception(async () => {
|
|
30
|
+
const ref = input.args.identifier;
|
|
31
|
+
const issue = await resolveIssue(client, ref, input.retryOptsOverride);
|
|
32
|
+
if (!issue) throw new LinearAgentError({
|
|
33
|
+
code: "ISSUE_NOT_FOUND",
|
|
34
|
+
message: `issue not found: ${ref}`,
|
|
35
|
+
details: { ref }
|
|
36
|
+
});
|
|
37
|
+
const issueIdRaw = issue.id;
|
|
38
|
+
if (typeof issueIdRaw !== "string") throw new LinearAgentError({
|
|
39
|
+
code: "ISSUE_NOT_FOUND",
|
|
40
|
+
message: `issue not found: ${ref}`,
|
|
41
|
+
details: { ref }
|
|
42
|
+
});
|
|
43
|
+
const issueId = issueIdRaw;
|
|
44
|
+
const teamRaw = await Promise.resolve(issue.team);
|
|
45
|
+
const teamId = teamRaw && typeof teamRaw === "object" && typeof teamRaw.id === "string" ? teamRaw.id : void 0;
|
|
46
|
+
if (!teamId) throw new LinearAgentError({
|
|
47
|
+
code: "ISSUE_NOT_FOUND",
|
|
48
|
+
message: `issue has no team: ${ref}`,
|
|
49
|
+
details: { ref }
|
|
50
|
+
});
|
|
51
|
+
const stateId = await resolveStateNameToId(client, resolved.name ?? "_api-key-env_", teamId, input.args.state, input.retryOptsOverride);
|
|
52
|
+
const payload = await withRateLimitRetry(() => client.updateIssue(issueId, { stateId }), input.retryOptsOverride);
|
|
53
|
+
if (!payload.success) throw LinearAgentError.linear.apiError({
|
|
54
|
+
message: "updateIssue returned success=false",
|
|
55
|
+
details: { lastSyncId: payload.lastSyncId }
|
|
56
|
+
});
|
|
57
|
+
let updated;
|
|
58
|
+
if (payload.issue !== void 0) {
|
|
59
|
+
const u = await Promise.resolve(payload.issue);
|
|
60
|
+
if (u !== void 0 && u !== null) updated = u;
|
|
61
|
+
}
|
|
62
|
+
let data;
|
|
63
|
+
if (updated) data = project(await hydrateForProjection(updated, fields), fields);
|
|
64
|
+
else data = { id: issueId };
|
|
65
|
+
const complexity = getLastComplexity();
|
|
66
|
+
const meta = {
|
|
67
|
+
workspace: resolved.name,
|
|
68
|
+
workspaceSource: resolved.source,
|
|
69
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
data,
|
|
73
|
+
meta
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
async function resolveIssue(client, ref, retryOpts) {
|
|
78
|
+
const m = ISSUE_IDENTIFIER_RE.exec(ref);
|
|
79
|
+
if (m) {
|
|
80
|
+
const teamKey = m[1].toUpperCase();
|
|
81
|
+
const number = Number(m[2]);
|
|
82
|
+
const filter = {
|
|
83
|
+
team: { key: { eq: teamKey } },
|
|
84
|
+
number: { eq: number }
|
|
85
|
+
};
|
|
86
|
+
return (await withRateLimitRetry(() => client.issues({
|
|
87
|
+
filter,
|
|
88
|
+
first: 1
|
|
89
|
+
}), retryOpts)).nodes[0];
|
|
90
|
+
}
|
|
91
|
+
return await withRateLimitRetry(() => client.issue(ref), retryOpts) ?? void 0;
|
|
92
|
+
}
|
|
93
|
+
const RELATION_KEYS = new Set([
|
|
94
|
+
"state",
|
|
95
|
+
"assignee",
|
|
96
|
+
"team",
|
|
97
|
+
"project",
|
|
98
|
+
"cycle",
|
|
99
|
+
"parent"
|
|
100
|
+
]);
|
|
101
|
+
async function hydrateForProjection(issue, spec) {
|
|
102
|
+
const needs = neededRelations(spec);
|
|
103
|
+
if (needs.size === 0) {
|
|
104
|
+
const out = {};
|
|
105
|
+
for (const k of Object.keys(issue)) if (!RELATION_KEYS.has(k)) out[k] = issue[k];
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
const hydrated = {};
|
|
109
|
+
for (const k of Object.keys(issue)) if (RELATION_KEYS.has(k)) {
|
|
110
|
+
if (needs.has(k)) {
|
|
111
|
+
const value = issue[k];
|
|
112
|
+
hydrated[k] = await resolveLazy(value);
|
|
113
|
+
}
|
|
114
|
+
} else hydrated[k] = issue[k];
|
|
115
|
+
return hydrated;
|
|
116
|
+
}
|
|
117
|
+
function neededRelations(spec) {
|
|
118
|
+
if (spec === "*") return new Set(RELATION_KEYS);
|
|
119
|
+
const out = /* @__PURE__ */ new Set();
|
|
120
|
+
for (const path of spec) {
|
|
121
|
+
const head = path.split(".")[0];
|
|
122
|
+
if (head && RELATION_KEYS.has(head)) out.add(head);
|
|
123
|
+
}
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
126
|
+
async function resolveLazy(value) {
|
|
127
|
+
if (value && typeof value.then === "function") return await value;
|
|
128
|
+
return value;
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
export { issueTransitionRuntime };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { LinearAgentError } from "../core/errors/error.js";
|
|
2
|
+
import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
|
|
3
|
+
import { withRateLimitRetry } from "../core/transport/rate-limit.js";
|
|
4
|
+
import "../core/transport/index.js";
|
|
5
|
+
import { createLinearClient } from "../core/client/factory.js";
|
|
6
|
+
import "../core/client/index.js";
|
|
7
|
+
import { loadConfig } from "../core/config/store.js";
|
|
8
|
+
import "../core/config/index.js";
|
|
9
|
+
import { resolveWorkspace } from "../core/workspace/resolver.js";
|
|
10
|
+
import { requireExplicitWorkspaceForWrite } from "../core/workspace/write-guard.js";
|
|
11
|
+
import { ISSUE_IDENTIFIER_RE, UUID_RE } from "./filter-heuristics.js";
|
|
12
|
+
//#region src/lib/issue-trash-runtime.ts
|
|
13
|
+
async function issueTrashRuntime(input) {
|
|
14
|
+
const config = (input.loadConfigOverride ?? loadConfig)();
|
|
15
|
+
const envForResolver = {};
|
|
16
|
+
if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
|
|
17
|
+
if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
|
|
18
|
+
const resolved = resolveWorkspace({
|
|
19
|
+
flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
|
|
20
|
+
env: envForResolver,
|
|
21
|
+
config
|
|
22
|
+
});
|
|
23
|
+
requireExplicitWorkspaceForWrite(resolved, input.flags.allowActiveWorkspaceWrite ?? false);
|
|
24
|
+
const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
|
|
25
|
+
return withFetchInterception(async () => {
|
|
26
|
+
const ref = input.args.identifier;
|
|
27
|
+
const { id, identifier } = await resolveIssueRef(client, ref, input.retryOptsOverride);
|
|
28
|
+
const payload = await withRateLimitRetry(() => client.deleteIssue(id), input.retryOptsOverride);
|
|
29
|
+
if (!payload.success) throw LinearAgentError.linear.apiError({
|
|
30
|
+
message: "deleteIssue returned success=false",
|
|
31
|
+
details: { lastSyncId: payload.lastSyncId }
|
|
32
|
+
});
|
|
33
|
+
const data = {
|
|
34
|
+
id,
|
|
35
|
+
trashed: true
|
|
36
|
+
};
|
|
37
|
+
if (identifier !== void 0) data.identifier = identifier;
|
|
38
|
+
const complexity = getLastComplexity();
|
|
39
|
+
return {
|
|
40
|
+
data,
|
|
41
|
+
meta: {
|
|
42
|
+
workspace: resolved.name,
|
|
43
|
+
workspaceSource: resolved.source,
|
|
44
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function resolveIssueRef(client, ref, retryOpts) {
|
|
50
|
+
if (UUID_RE.test(ref)) return { id: ref };
|
|
51
|
+
const m = ref.match(ISSUE_IDENTIFIER_RE);
|
|
52
|
+
if (!m) throw new LinearAgentError({
|
|
53
|
+
code: "ISSUE_NOT_FOUND",
|
|
54
|
+
message: `invalid issue ref: ${ref}`,
|
|
55
|
+
details: { ref }
|
|
56
|
+
});
|
|
57
|
+
const teamKey = m[1].toUpperCase();
|
|
58
|
+
const number = Number(m[2]);
|
|
59
|
+
const issue = (await withRateLimitRetry(() => client.issues({
|
|
60
|
+
filter: {
|
|
61
|
+
team: { key: { eq: teamKey } },
|
|
62
|
+
number: { eq: number }
|
|
63
|
+
},
|
|
64
|
+
first: 1,
|
|
65
|
+
includeArchived: true
|
|
66
|
+
}), retryOpts)).nodes[0];
|
|
67
|
+
if (!issue) throw new LinearAgentError({
|
|
68
|
+
code: "ISSUE_NOT_FOUND",
|
|
69
|
+
message: `issue not found: ${ref}`,
|
|
70
|
+
details: { ref }
|
|
71
|
+
});
|
|
72
|
+
const idRaw = issue.id;
|
|
73
|
+
if (typeof idRaw !== "string") throw new LinearAgentError({
|
|
74
|
+
code: "ISSUE_NOT_FOUND",
|
|
75
|
+
message: `issue not found: ${ref}`,
|
|
76
|
+
details: { ref }
|
|
77
|
+
});
|
|
78
|
+
const idRes = { id: idRaw };
|
|
79
|
+
const idn = issue.identifier;
|
|
80
|
+
if (typeof idn === "string") idRes.identifier = idn;
|
|
81
|
+
return idRes;
|
|
82
|
+
}
|
|
83
|
+
//#endregion
|
|
84
|
+
export { issueTrashRuntime };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { LinearAgentError } from "../core/errors/error.js";
|
|
2
|
+
import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
|
|
3
|
+
import { withRateLimitRetry } from "../core/transport/rate-limit.js";
|
|
4
|
+
import "../core/transport/index.js";
|
|
5
|
+
import { createLinearClient } from "../core/client/factory.js";
|
|
6
|
+
import "../core/client/index.js";
|
|
7
|
+
import { loadConfig } from "../core/config/store.js";
|
|
8
|
+
import "../core/config/index.js";
|
|
9
|
+
import { parseFields, project } from "../core/projection/project.js";
|
|
10
|
+
import { resolveWorkspace } from "../core/workspace/resolver.js";
|
|
11
|
+
import { requireExplicitWorkspaceForWrite } from "../core/workspace/write-guard.js";
|
|
12
|
+
import { ISSUE_IDENTIFIER_RE } from "./filter-heuristics.js";
|
|
13
|
+
import { resolveCycleId } from "../core/resolvers/cycle.js";
|
|
14
|
+
import { resolveLabelId, resolveLabelIds } from "../core/resolvers/label.js";
|
|
15
|
+
import { resolveProjectId } from "../core/resolvers/project.js";
|
|
16
|
+
import { resolveStateNameToId } from "../core/resolvers/state.js";
|
|
17
|
+
import "../core/resolvers/index.js";
|
|
18
|
+
import { resolveAssignee } from "./issue-create-runtime.js";
|
|
19
|
+
//#region src/lib/issue-update-runtime.ts
|
|
20
|
+
/**
|
|
21
|
+
* Returns true if any field flag is set (i.e. the update is non-empty).
|
|
22
|
+
* Empty-string `description` counts as set (intentional clear-the-description
|
|
23
|
+
* value per Test 8).
|
|
24
|
+
*/
|
|
25
|
+
function hasAnyFieldFlag(flags) {
|
|
26
|
+
return flags.title !== void 0 || flags.description !== void 0 || flags.state !== void 0 || flags.assignee !== void 0 || flags.labels !== void 0 && flags.labels !== "" || flags.addLabel !== void 0 && flags.addLabel.length > 0 || flags.removeLabel !== void 0 && flags.removeLabel.length > 0 || flags.project !== void 0 || flags.cycle !== void 0 || flags.priority !== void 0;
|
|
27
|
+
}
|
|
28
|
+
const NO_FIELDS_MESSAGE = "no fields to update -- pass at least one of --title, --description, --state, --assignee, --labels, --add-label, --remove-label, --priority, --project, --cycle";
|
|
29
|
+
async function issueUpdateRuntime(input) {
|
|
30
|
+
const config = (input.loadConfigOverride ?? loadConfig)();
|
|
31
|
+
const envForResolver = {};
|
|
32
|
+
if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
|
|
33
|
+
if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
|
|
34
|
+
const resolved = resolveWorkspace({
|
|
35
|
+
flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
|
|
36
|
+
env: envForResolver,
|
|
37
|
+
config
|
|
38
|
+
});
|
|
39
|
+
requireExplicitWorkspaceForWrite(resolved, input.flags.allowActiveWorkspaceWrite ?? false);
|
|
40
|
+
if (!hasAnyFieldFlag(input.flags)) throw new LinearAgentError({
|
|
41
|
+
code: "VALIDATION_NO_FIELDS",
|
|
42
|
+
message: NO_FIELDS_MESSAGE
|
|
43
|
+
});
|
|
44
|
+
const fields = parseFields(input.flags.fields ?? "defaults", "issue");
|
|
45
|
+
const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
|
|
46
|
+
return withFetchInterception(async () => {
|
|
47
|
+
const ref = input.args.identifier;
|
|
48
|
+
const issue = await resolveIssue(client, ref, input.retryOptsOverride);
|
|
49
|
+
if (!issue) throw new LinearAgentError({
|
|
50
|
+
code: "ISSUE_NOT_FOUND",
|
|
51
|
+
message: `issue not found: ${ref}`,
|
|
52
|
+
details: { ref }
|
|
53
|
+
});
|
|
54
|
+
const issueIdRaw = issue.id;
|
|
55
|
+
if (typeof issueIdRaw !== "string") throw new LinearAgentError({
|
|
56
|
+
code: "ISSUE_NOT_FOUND",
|
|
57
|
+
message: `issue not found: ${ref}`,
|
|
58
|
+
details: { ref }
|
|
59
|
+
});
|
|
60
|
+
const issueId = issueIdRaw;
|
|
61
|
+
const teamRaw = await Promise.resolve(issue.team);
|
|
62
|
+
const teamId = teamRaw && typeof teamRaw === "object" && typeof teamRaw.id === "string" ? teamRaw.id : void 0;
|
|
63
|
+
if (!teamId) throw new LinearAgentError({
|
|
64
|
+
code: "ISSUE_NOT_FOUND",
|
|
65
|
+
message: `issue has no team: ${ref}`,
|
|
66
|
+
details: { ref }
|
|
67
|
+
});
|
|
68
|
+
const workspaceKey = resolved.name ?? "_api-key-env_";
|
|
69
|
+
const flags = input.flags;
|
|
70
|
+
const updateInput = {};
|
|
71
|
+
if (flags.title !== void 0) updateInput.title = flags.title;
|
|
72
|
+
if (flags.description !== void 0) updateInput.description = flags.description;
|
|
73
|
+
if (flags.priority !== void 0) updateInput.priority = flags.priority;
|
|
74
|
+
if (flags.state !== void 0) updateInput.stateId = await resolveStateNameToId(client, workspaceKey, teamId, flags.state, input.retryOptsOverride);
|
|
75
|
+
if (flags.assignee !== void 0) updateInput.assigneeId = await resolveAssignee(client, flags.assignee, input.retryOptsOverride);
|
|
76
|
+
if (flags.project !== void 0) updateInput.projectId = await resolveProjectId(client, workspaceKey, flags.project, input.retryOptsOverride);
|
|
77
|
+
if (flags.cycle !== void 0) updateInput.cycleId = await resolveCycleId(client, workspaceKey, teamId, flags.cycle, input.retryOptsOverride);
|
|
78
|
+
if (flags.labels !== void 0 && flags.labels !== "") updateInput.labelIds = await resolveLabelIds(client, workspaceKey, teamId, flags.labels.split(",").map((s) => s.trim()).filter(Boolean), input.retryOptsOverride);
|
|
79
|
+
if (flags.addLabel !== void 0 && flags.addLabel.length > 0) updateInput.addedLabelIds = await Promise.all(flags.addLabel.map((l) => resolveLabelId(client, workspaceKey, teamId, l, input.retryOptsOverride)));
|
|
80
|
+
if (flags.removeLabel !== void 0 && flags.removeLabel.length > 0) updateInput.removedLabelIds = await Promise.all(flags.removeLabel.map((l) => resolveLabelId(client, workspaceKey, teamId, l, input.retryOptsOverride)));
|
|
81
|
+
if (Object.keys(updateInput).length === 0) throw new LinearAgentError({
|
|
82
|
+
code: "VALIDATION_NO_FIELDS",
|
|
83
|
+
message: NO_FIELDS_MESSAGE
|
|
84
|
+
});
|
|
85
|
+
const payload = await withRateLimitRetry(() => client.updateIssue(issueId, updateInput), input.retryOptsOverride);
|
|
86
|
+
if (!payload.success) throw LinearAgentError.linear.apiError({
|
|
87
|
+
message: "updateIssue returned success=false",
|
|
88
|
+
details: { lastSyncId: payload.lastSyncId }
|
|
89
|
+
});
|
|
90
|
+
let updated;
|
|
91
|
+
if (payload.issue !== void 0) {
|
|
92
|
+
const u = await Promise.resolve(payload.issue);
|
|
93
|
+
if (u !== void 0 && u !== null) updated = u;
|
|
94
|
+
}
|
|
95
|
+
let data;
|
|
96
|
+
if (updated) data = project(await hydrateForProjection(updated, fields), fields);
|
|
97
|
+
else data = { id: issueId };
|
|
98
|
+
const complexity = getLastComplexity();
|
|
99
|
+
const meta = {
|
|
100
|
+
workspace: resolved.name,
|
|
101
|
+
workspaceSource: resolved.source,
|
|
102
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
data,
|
|
106
|
+
meta
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
async function resolveIssue(client, ref, retryOpts) {
|
|
111
|
+
const m = ISSUE_IDENTIFIER_RE.exec(ref);
|
|
112
|
+
if (m) {
|
|
113
|
+
const teamKey = m[1].toUpperCase();
|
|
114
|
+
const number = Number(m[2]);
|
|
115
|
+
const filter = {
|
|
116
|
+
team: { key: { eq: teamKey } },
|
|
117
|
+
number: { eq: number }
|
|
118
|
+
};
|
|
119
|
+
return (await withRateLimitRetry(() => client.issues({
|
|
120
|
+
filter,
|
|
121
|
+
first: 1
|
|
122
|
+
}), retryOpts)).nodes[0];
|
|
123
|
+
}
|
|
124
|
+
return await withRateLimitRetry(() => client.issue(ref), retryOpts) ?? void 0;
|
|
125
|
+
}
|
|
126
|
+
const RELATION_KEYS = new Set([
|
|
127
|
+
"state",
|
|
128
|
+
"assignee",
|
|
129
|
+
"team",
|
|
130
|
+
"project",
|
|
131
|
+
"cycle",
|
|
132
|
+
"parent"
|
|
133
|
+
]);
|
|
134
|
+
async function hydrateForProjection(issue, spec) {
|
|
135
|
+
const needs = neededRelations(spec);
|
|
136
|
+
if (needs.size === 0) {
|
|
137
|
+
const out = {};
|
|
138
|
+
for (const k of Object.keys(issue)) if (!RELATION_KEYS.has(k)) out[k] = issue[k];
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
const hydrated = {};
|
|
142
|
+
for (const k of Object.keys(issue)) if (RELATION_KEYS.has(k)) {
|
|
143
|
+
if (needs.has(k)) {
|
|
144
|
+
const value = issue[k];
|
|
145
|
+
hydrated[k] = await resolveLazy(value);
|
|
146
|
+
}
|
|
147
|
+
} else hydrated[k] = issue[k];
|
|
148
|
+
return hydrated;
|
|
149
|
+
}
|
|
150
|
+
function neededRelations(spec) {
|
|
151
|
+
if (spec === "*") return new Set(RELATION_KEYS);
|
|
152
|
+
const out = /* @__PURE__ */ new Set();
|
|
153
|
+
for (const path of spec) {
|
|
154
|
+
const head = path.split(".")[0];
|
|
155
|
+
if (head && RELATION_KEYS.has(head)) out.add(head);
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
async function resolveLazy(value) {
|
|
160
|
+
if (value && typeof value.then === "function") return await value;
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
export { issueUpdateRuntime };
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { LinearAgentError } from "../core/errors/error.js";
|
|
2
|
+
import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
|
|
3
|
+
import { withRateLimitRetry } from "../core/transport/rate-limit.js";
|
|
4
|
+
import "../core/transport/index.js";
|
|
5
|
+
import { createLinearClient } from "../core/client/factory.js";
|
|
6
|
+
import "../core/client/index.js";
|
|
7
|
+
import { loadConfig } from "../core/config/store.js";
|
|
8
|
+
import "../core/config/index.js";
|
|
9
|
+
import { parseFields, project } from "../core/projection/project.js";
|
|
10
|
+
import { resolveWorkspace } from "../core/workspace/resolver.js";
|
|
11
|
+
import { requireExplicitWorkspaceForWrite } from "../core/workspace/write-guard.js";
|
|
12
|
+
import { TEAM_KEY_RE, UUID_RE } from "./filter-heuristics.js";
|
|
13
|
+
//#region src/lib/label-create-runtime.ts
|
|
14
|
+
async function labelCreateRuntime(input) {
|
|
15
|
+
const config = (input.loadConfigOverride ?? loadConfig)();
|
|
16
|
+
const envForResolver = {};
|
|
17
|
+
if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
|
|
18
|
+
if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
|
|
19
|
+
const resolved = resolveWorkspace({
|
|
20
|
+
flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
|
|
21
|
+
env: envForResolver,
|
|
22
|
+
config
|
|
23
|
+
});
|
|
24
|
+
requireExplicitWorkspaceForWrite(resolved, input.flags.allowActiveWorkspaceWrite ?? false);
|
|
25
|
+
if (input.flags.name === void 0 || input.flags.name === "") throw LinearAgentError.usage("--name is required");
|
|
26
|
+
if (input.flags.team === void 0 || input.flags.team === "") throw LinearAgentError.usage("--team is required");
|
|
27
|
+
const name = input.flags.name;
|
|
28
|
+
const teamRef = input.flags.team;
|
|
29
|
+
const fields = parseFields(input.flags.fields ?? "defaults", "label");
|
|
30
|
+
const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
|
|
31
|
+
return withFetchInterception(async () => {
|
|
32
|
+
let teamId;
|
|
33
|
+
if (UUID_RE.test(teamRef)) teamId = teamRef;
|
|
34
|
+
else {
|
|
35
|
+
const filter = TEAM_KEY_RE.test(teamRef) ? { key: { eq: teamRef.toUpperCase() } } : { name: { eq: teamRef } };
|
|
36
|
+
const team = (await withRateLimitRetry(() => client.teams({
|
|
37
|
+
filter,
|
|
38
|
+
first: 1
|
|
39
|
+
}), input.retryOptsOverride)).nodes[0];
|
|
40
|
+
if (!team) throw new LinearAgentError({
|
|
41
|
+
code: "TEAM_NOT_FOUND",
|
|
42
|
+
message: `team not found: ${teamRef}`,
|
|
43
|
+
details: { ref: teamRef }
|
|
44
|
+
});
|
|
45
|
+
teamId = team.id;
|
|
46
|
+
}
|
|
47
|
+
const createInput = {
|
|
48
|
+
name,
|
|
49
|
+
teamId
|
|
50
|
+
};
|
|
51
|
+
if (input.flags.color !== void 0) createInput.color = input.flags.color;
|
|
52
|
+
if (input.flags.description !== void 0) createInput.description = input.flags.description;
|
|
53
|
+
const payload = await withRateLimitRetry(() => client.createIssueLabel(createInput), input.retryOptsOverride);
|
|
54
|
+
if (!payload.success) throw LinearAgentError.linear.apiError({
|
|
55
|
+
message: "createIssueLabel returned success=false",
|
|
56
|
+
details: { lastSyncId: payload.lastSyncId }
|
|
57
|
+
});
|
|
58
|
+
let created;
|
|
59
|
+
if (payload.issueLabel !== void 0) {
|
|
60
|
+
const c = await Promise.resolve(payload.issueLabel);
|
|
61
|
+
if (c !== void 0 && c !== null) created = c;
|
|
62
|
+
}
|
|
63
|
+
let data;
|
|
64
|
+
if (created) data = project(await hydrateForProjection(created, fields), fields);
|
|
65
|
+
else data = {};
|
|
66
|
+
const complexity = getLastComplexity();
|
|
67
|
+
const meta = {
|
|
68
|
+
workspace: resolved.name,
|
|
69
|
+
workspaceSource: resolved.source,
|
|
70
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
data,
|
|
74
|
+
meta
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const RELATION_KEYS = new Set([
|
|
79
|
+
"team",
|
|
80
|
+
"parent",
|
|
81
|
+
"creator"
|
|
82
|
+
]);
|
|
83
|
+
async function hydrateForProjection(label, spec) {
|
|
84
|
+
const needs = neededRelations(spec);
|
|
85
|
+
if (needs.size === 0) {
|
|
86
|
+
const out = {};
|
|
87
|
+
for (const k of Object.keys(label)) if (!RELATION_KEYS.has(k)) out[k] = label[k];
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
const hydrated = {};
|
|
91
|
+
for (const k of Object.keys(label)) if (RELATION_KEYS.has(k)) {
|
|
92
|
+
if (needs.has(k)) {
|
|
93
|
+
const value = label[k];
|
|
94
|
+
hydrated[k] = await resolveLazy(value);
|
|
95
|
+
}
|
|
96
|
+
} else hydrated[k] = label[k];
|
|
97
|
+
return hydrated;
|
|
98
|
+
}
|
|
99
|
+
function neededRelations(spec) {
|
|
100
|
+
if (spec === "*") return new Set(RELATION_KEYS);
|
|
101
|
+
const out = /* @__PURE__ */ new Set();
|
|
102
|
+
for (const path of spec) {
|
|
103
|
+
const head = path.split(".")[0];
|
|
104
|
+
if (head && RELATION_KEYS.has(head)) out.add(head);
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
async function resolveLazy(value) {
|
|
109
|
+
if (value && typeof value.then === "function") return await value;
|
|
110
|
+
return value;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
export { labelCreateRuntime };
|