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,89 @@
|
|
|
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-archive-runtime.ts
|
|
13
|
+
async function issueArchiveRuntime(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.archiveIssue(id), input.retryOptsOverride);
|
|
29
|
+
if (!payload.success) throw LinearAgentError.linear.apiError({
|
|
30
|
+
message: "archiveIssue returned success=false",
|
|
31
|
+
details: { lastSyncId: payload.lastSyncId }
|
|
32
|
+
});
|
|
33
|
+
const data = {
|
|
34
|
+
id,
|
|
35
|
+
archived: 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
|
+
/**
|
|
50
|
+
* Resolve a user-typed issue ref (ENG-123 or UUID) to a UUID + identifier.
|
|
51
|
+
* UUID path is a no-op (no SDK round-trip); identifier path uses the
|
|
52
|
+
* team-key + number filter. Inlined per the plan note: each runtime in this
|
|
53
|
+
* plan keeps its own resolution helper for cross-plan independence.
|
|
54
|
+
*/
|
|
55
|
+
async function resolveIssueRef(client, ref, retryOpts) {
|
|
56
|
+
if (UUID_RE.test(ref)) return { id: ref };
|
|
57
|
+
const m = ref.match(ISSUE_IDENTIFIER_RE);
|
|
58
|
+
if (!m) throw new LinearAgentError({
|
|
59
|
+
code: "ISSUE_NOT_FOUND",
|
|
60
|
+
message: `invalid issue ref: ${ref}`,
|
|
61
|
+
details: { ref }
|
|
62
|
+
});
|
|
63
|
+
const teamKey = m[1].toUpperCase();
|
|
64
|
+
const number = Number(m[2]);
|
|
65
|
+
const issue = (await withRateLimitRetry(() => client.issues({
|
|
66
|
+
filter: {
|
|
67
|
+
team: { key: { eq: teamKey } },
|
|
68
|
+
number: { eq: number }
|
|
69
|
+
},
|
|
70
|
+
first: 1
|
|
71
|
+
}), retryOpts)).nodes[0];
|
|
72
|
+
if (!issue) throw new LinearAgentError({
|
|
73
|
+
code: "ISSUE_NOT_FOUND",
|
|
74
|
+
message: `issue not found: ${ref}`,
|
|
75
|
+
details: { ref }
|
|
76
|
+
});
|
|
77
|
+
const idRaw = issue.id;
|
|
78
|
+
if (typeof idRaw !== "string") throw new LinearAgentError({
|
|
79
|
+
code: "ISSUE_NOT_FOUND",
|
|
80
|
+
message: `issue not found: ${ref}`,
|
|
81
|
+
details: { ref }
|
|
82
|
+
});
|
|
83
|
+
const idRes = { id: idRaw };
|
|
84
|
+
const idn = issue.identifier;
|
|
85
|
+
if (typeof idn === "string") idRes.identifier = idn;
|
|
86
|
+
return idRes;
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { issueArchiveRuntime };
|
|
@@ -0,0 +1,175 @@
|
|
|
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 { EMAIL_RE, ISSUE_IDENTIFIER_RE, UUID_RE } from "./filter-heuristics.js";
|
|
13
|
+
import { resolveCycleId } from "../core/resolvers/cycle.js";
|
|
14
|
+
import { resolveLabelIds } from "../core/resolvers/label.js";
|
|
15
|
+
import { resolveProjectId } from "../core/resolvers/project.js";
|
|
16
|
+
import { resolveStateNameToId } from "../core/resolvers/state.js";
|
|
17
|
+
import { resolveTeamId } from "../core/resolvers/team.js";
|
|
18
|
+
import "../core/resolvers/index.js";
|
|
19
|
+
//#region src/lib/issue-create-runtime.ts
|
|
20
|
+
async function issueCreateRuntime(input) {
|
|
21
|
+
const config = (input.loadConfigOverride ?? loadConfig)();
|
|
22
|
+
const envForResolver = {};
|
|
23
|
+
if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
|
|
24
|
+
if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
|
|
25
|
+
const resolved = resolveWorkspace({
|
|
26
|
+
flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
|
|
27
|
+
env: envForResolver,
|
|
28
|
+
config
|
|
29
|
+
});
|
|
30
|
+
requireExplicitWorkspaceForWrite(resolved, input.flags.allowActiveWorkspaceWrite ?? false);
|
|
31
|
+
if (input.flags.title === void 0 || input.flags.title === "") throw LinearAgentError.usage("--title is required");
|
|
32
|
+
if (input.flags.team === void 0 || input.flags.team === "") throw LinearAgentError.usage("--team is required");
|
|
33
|
+
const title = input.flags.title;
|
|
34
|
+
const teamRef = input.flags.team;
|
|
35
|
+
const fields = parseFields(input.flags.fields ?? "defaults", "issue");
|
|
36
|
+
const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
|
|
37
|
+
return withFetchInterception(async () => {
|
|
38
|
+
const workspaceKey = resolved.name ?? "_api-key-env_";
|
|
39
|
+
const teamId = await resolveTeamId(client, workspaceKey, teamRef, input.retryOptsOverride);
|
|
40
|
+
const flags = input.flags;
|
|
41
|
+
const [stateId, labelIds, projectId, cycleId, parentId, assigneeId] = await Promise.all([
|
|
42
|
+
flags.state !== void 0 ? resolveStateNameToId(client, workspaceKey, teamId, flags.state, input.retryOptsOverride) : Promise.resolve(void 0),
|
|
43
|
+
flags.labels !== void 0 && flags.labels !== "" ? resolveLabelIds(client, workspaceKey, teamId, flags.labels.split(",").map((s) => s.trim()).filter(Boolean), input.retryOptsOverride) : Promise.resolve(void 0),
|
|
44
|
+
flags.project !== void 0 ? resolveProjectId(client, workspaceKey, flags.project, input.retryOptsOverride) : Promise.resolve(void 0),
|
|
45
|
+
flags.cycle !== void 0 ? resolveCycleId(client, workspaceKey, teamId, flags.cycle, input.retryOptsOverride) : Promise.resolve(void 0),
|
|
46
|
+
flags.parent !== void 0 ? resolveIssueRefToUuid(client, flags.parent, input.retryOptsOverride) : Promise.resolve(void 0),
|
|
47
|
+
flags.assignee !== void 0 ? resolveAssignee(client, flags.assignee, input.retryOptsOverride) : Promise.resolve(void 0)
|
|
48
|
+
]);
|
|
49
|
+
const createInput = {
|
|
50
|
+
teamId,
|
|
51
|
+
title
|
|
52
|
+
};
|
|
53
|
+
if (flags.description !== void 0) createInput.description = flags.description;
|
|
54
|
+
if (stateId !== void 0) createInput.stateId = stateId;
|
|
55
|
+
if (assigneeId !== void 0) createInput.assigneeId = assigneeId;
|
|
56
|
+
if (labelIds !== void 0) createInput.labelIds = labelIds;
|
|
57
|
+
if (projectId !== void 0) createInput.projectId = projectId;
|
|
58
|
+
if (cycleId !== void 0) createInput.cycleId = cycleId;
|
|
59
|
+
if (flags.priority !== void 0) createInput.priority = flags.priority;
|
|
60
|
+
if (parentId !== void 0) createInput.parentId = parentId;
|
|
61
|
+
const payload = await withRateLimitRetry(() => client.createIssue(createInput), input.retryOptsOverride);
|
|
62
|
+
if (!payload.success) throw LinearAgentError.linear.apiError({
|
|
63
|
+
message: "createIssue returned success=false",
|
|
64
|
+
details: { lastSyncId: payload.lastSyncId }
|
|
65
|
+
});
|
|
66
|
+
let created;
|
|
67
|
+
if (payload.issue !== void 0) {
|
|
68
|
+
const i = await Promise.resolve(payload.issue);
|
|
69
|
+
if (i !== void 0 && i !== null) created = i;
|
|
70
|
+
}
|
|
71
|
+
let data;
|
|
72
|
+
if (created) data = project(await hydrateForProjection(created, fields), fields);
|
|
73
|
+
else data = {};
|
|
74
|
+
const complexity = getLastComplexity();
|
|
75
|
+
const meta = {
|
|
76
|
+
workspace: resolved.name,
|
|
77
|
+
workspaceSource: resolved.source,
|
|
78
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
data,
|
|
82
|
+
meta
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Resolve a parent issue reference (`ENG-42` or UUID) to an issue UUID.
|
|
88
|
+
* Mirrors the `resolveIssue` helper in `issue-get-runtime.ts` but limited to
|
|
89
|
+
* the parent-id use case (no full issue hydration needed).
|
|
90
|
+
*/
|
|
91
|
+
async function resolveIssueRefToUuid(client, ref, retryOpts) {
|
|
92
|
+
if (UUID_RE.test(ref)) return ref;
|
|
93
|
+
const m = ISSUE_IDENTIFIER_RE.exec(ref);
|
|
94
|
+
if (!m) throw new LinearAgentError({
|
|
95
|
+
code: "ISSUE_NOT_FOUND",
|
|
96
|
+
message: `parent issue not found: ${ref}`,
|
|
97
|
+
details: { ref }
|
|
98
|
+
});
|
|
99
|
+
const teamKey = m[1].toUpperCase();
|
|
100
|
+
const number = Number(m[2]);
|
|
101
|
+
const filter = {
|
|
102
|
+
team: { key: { eq: teamKey } },
|
|
103
|
+
number: { eq: number }
|
|
104
|
+
};
|
|
105
|
+
const node = (await withRateLimitRetry(() => client.issues({
|
|
106
|
+
filter,
|
|
107
|
+
first: 1
|
|
108
|
+
}), retryOpts)).nodes[0];
|
|
109
|
+
if (!node) throw new LinearAgentError({
|
|
110
|
+
code: "ISSUE_NOT_FOUND",
|
|
111
|
+
message: `parent issue not found: ${ref}`,
|
|
112
|
+
details: { ref }
|
|
113
|
+
});
|
|
114
|
+
return node.id;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Resolve a free-form assignee reference to a user UUID:
|
|
118
|
+
* - `me` → `client.viewer.id`
|
|
119
|
+
* - UUID → passthrough
|
|
120
|
+
* - email-shaped → `client.users({ filter: { email: { eq } }, first: 1 })`
|
|
121
|
+
* - other → `client.users({ filter: { name: { eq } }, first: 1 })`
|
|
122
|
+
*/
|
|
123
|
+
async function resolveAssignee(client, ref, retryOpts) {
|
|
124
|
+
if (ref === "me") return (await withRateLimitRetry(() => Promise.resolve(client.viewer), retryOpts)).id;
|
|
125
|
+
if (UUID_RE.test(ref)) return ref;
|
|
126
|
+
const filter = EMAIL_RE.test(ref) ? { email: { eq: ref } } : { name: { eq: ref } };
|
|
127
|
+
const node = (await withRateLimitRetry(() => client.users({
|
|
128
|
+
filter,
|
|
129
|
+
first: 1
|
|
130
|
+
}), retryOpts)).nodes[0];
|
|
131
|
+
if (!node) throw LinearAgentError.linear.apiError({
|
|
132
|
+
message: `assignee not found: ${ref}`,
|
|
133
|
+
details: { ref }
|
|
134
|
+
});
|
|
135
|
+
return node.id;
|
|
136
|
+
}
|
|
137
|
+
const RELATION_KEYS = new Set([
|
|
138
|
+
"state",
|
|
139
|
+
"assignee",
|
|
140
|
+
"team",
|
|
141
|
+
"project",
|
|
142
|
+
"cycle",
|
|
143
|
+
"parent"
|
|
144
|
+
]);
|
|
145
|
+
async function hydrateForProjection(issue, spec) {
|
|
146
|
+
const needs = neededRelations(spec);
|
|
147
|
+
if (needs.size === 0) {
|
|
148
|
+
const out = {};
|
|
149
|
+
for (const k of Object.keys(issue)) if (!RELATION_KEYS.has(k)) out[k] = issue[k];
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
const hydrated = {};
|
|
153
|
+
for (const k of Object.keys(issue)) if (RELATION_KEYS.has(k)) {
|
|
154
|
+
if (needs.has(k)) {
|
|
155
|
+
const value = issue[k];
|
|
156
|
+
hydrated[k] = await resolveLazy(value);
|
|
157
|
+
}
|
|
158
|
+
} else hydrated[k] = issue[k];
|
|
159
|
+
return hydrated;
|
|
160
|
+
}
|
|
161
|
+
function neededRelations(spec) {
|
|
162
|
+
if (spec === "*") return new Set(RELATION_KEYS);
|
|
163
|
+
const out = /* @__PURE__ */ new Set();
|
|
164
|
+
for (const path of spec) {
|
|
165
|
+
const head = path.split(".")[0];
|
|
166
|
+
if (head && RELATION_KEYS.has(head)) out.add(head);
|
|
167
|
+
}
|
|
168
|
+
return out;
|
|
169
|
+
}
|
|
170
|
+
async function resolveLazy(value) {
|
|
171
|
+
if (value && typeof value.then === "function") return await value;
|
|
172
|
+
return value;
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
export { issueCreateRuntime, resolveAssignee };
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { LinearAgentError } from "../core/errors/error.js";
|
|
2
|
+
import { redact } from "../core/redact/redact.js";
|
|
3
|
+
import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
|
|
4
|
+
import { withRateLimitRetry } from "../core/transport/rate-limit.js";
|
|
5
|
+
import "../core/transport/index.js";
|
|
6
|
+
import { createLinearClient } from "../core/client/factory.js";
|
|
7
|
+
import "../core/client/index.js";
|
|
8
|
+
import { loadConfig } from "../core/config/store.js";
|
|
9
|
+
import "../core/config/index.js";
|
|
10
|
+
import { parseFields, project } from "../core/projection/project.js";
|
|
11
|
+
import { resolveWorkspace } from "../core/workspace/resolver.js";
|
|
12
|
+
import { ISSUE_IDENTIFIER_RE } from "./filter-heuristics.js";
|
|
13
|
+
import { validateAndMergeIncludes } from "./include-fragments.js";
|
|
14
|
+
//#region src/lib/issue-get-runtime.ts
|
|
15
|
+
async function issueGetRuntime(input) {
|
|
16
|
+
const config = (input.loadConfigOverride ?? loadConfig)();
|
|
17
|
+
const envForResolver = {};
|
|
18
|
+
if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
|
|
19
|
+
if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
|
|
20
|
+
const resolved = resolveWorkspace({
|
|
21
|
+
flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
|
|
22
|
+
env: envForResolver,
|
|
23
|
+
config
|
|
24
|
+
});
|
|
25
|
+
const fields = parseFields(input.flags.fields ?? "defaults", "issue");
|
|
26
|
+
const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
|
|
27
|
+
const includes = input.flags.include ?? [];
|
|
28
|
+
if (includes.length === 0) return withFetchInterception(async () => {
|
|
29
|
+
const ref = input.args.identifier;
|
|
30
|
+
const issue = await resolveIssue(client, ref, input.retryOptsOverride);
|
|
31
|
+
if (!issue) throw new LinearAgentError({
|
|
32
|
+
code: "ISSUE_NOT_FOUND",
|
|
33
|
+
message: `issue not found: ${ref}`,
|
|
34
|
+
details: { ref }
|
|
35
|
+
});
|
|
36
|
+
const data = project(await hydrateForProjection(issue, fields), fields);
|
|
37
|
+
const complexity = getLastComplexity();
|
|
38
|
+
return {
|
|
39
|
+
data,
|
|
40
|
+
meta: {
|
|
41
|
+
workspace: resolved.name,
|
|
42
|
+
workspaceSource: resolved.source,
|
|
43
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
const query = composeIssueGetWithIncludes(validateAndMergeIncludes("issue get", includes));
|
|
48
|
+
return withFetchInterception(async () => {
|
|
49
|
+
const ref = input.args.identifier;
|
|
50
|
+
const response = await withRateLimitRetry(() => client.client.rawRequest(query, { id: ref }), input.retryOptsOverride);
|
|
51
|
+
if (response.error ?? !response.data) {
|
|
52
|
+
const safeMessage = redact(response.error ?? "no data returned from Linear API");
|
|
53
|
+
const safeCause = response.error !== void 0 ? redact(response.error) : void 0;
|
|
54
|
+
throw LinearAgentError.linear.apiError({
|
|
55
|
+
message: safeMessage,
|
|
56
|
+
details: {
|
|
57
|
+
command: "issue get",
|
|
58
|
+
cause: safeCause
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const issueData = response.data.issue;
|
|
63
|
+
if (!issueData) throw new LinearAgentError({
|
|
64
|
+
code: "ISSUE_NOT_FOUND",
|
|
65
|
+
message: `issue not found: ${ref}`,
|
|
66
|
+
details: { ref }
|
|
67
|
+
});
|
|
68
|
+
const data = project(issueData, fields);
|
|
69
|
+
const complexity = getLastComplexity();
|
|
70
|
+
return {
|
|
71
|
+
data,
|
|
72
|
+
meta: {
|
|
73
|
+
workspace: resolved.name,
|
|
74
|
+
workspaceSource: resolved.source,
|
|
75
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async function resolveIssue(client, ref, retryOpts) {
|
|
81
|
+
const m = ISSUE_IDENTIFIER_RE.exec(ref);
|
|
82
|
+
if (m) {
|
|
83
|
+
const teamKey = m[1].toUpperCase();
|
|
84
|
+
const number = Number(m[2]);
|
|
85
|
+
const filter = {
|
|
86
|
+
team: { key: { eq: teamKey } },
|
|
87
|
+
number: { eq: number }
|
|
88
|
+
};
|
|
89
|
+
return (await withRateLimitRetry(() => client.issues({
|
|
90
|
+
filter,
|
|
91
|
+
first: 1
|
|
92
|
+
}), retryOpts)).nodes[0];
|
|
93
|
+
}
|
|
94
|
+
return await withRateLimitRetry(() => client.issue(ref), retryOpts) ?? void 0;
|
|
95
|
+
}
|
|
96
|
+
const RELATION_KEYS = new Set([
|
|
97
|
+
"state",
|
|
98
|
+
"assignee",
|
|
99
|
+
"team",
|
|
100
|
+
"project",
|
|
101
|
+
"cycle",
|
|
102
|
+
"parent"
|
|
103
|
+
]);
|
|
104
|
+
async function hydrateForProjection(issue, spec) {
|
|
105
|
+
const needs = neededRelations(spec);
|
|
106
|
+
if (needs.size === 0) {
|
|
107
|
+
const out = {};
|
|
108
|
+
for (const k of Object.keys(issue)) if (!RELATION_KEYS.has(k)) out[k] = issue[k];
|
|
109
|
+
return out;
|
|
110
|
+
}
|
|
111
|
+
const hydrated = {};
|
|
112
|
+
for (const k of Object.keys(issue)) if (RELATION_KEYS.has(k)) {
|
|
113
|
+
if (needs.has(k)) {
|
|
114
|
+
const value = issue[k];
|
|
115
|
+
hydrated[k] = await resolveLazy(value);
|
|
116
|
+
}
|
|
117
|
+
} else hydrated[k] = issue[k];
|
|
118
|
+
return hydrated;
|
|
119
|
+
}
|
|
120
|
+
function neededRelations(spec) {
|
|
121
|
+
if (spec === "*") return new Set(RELATION_KEYS);
|
|
122
|
+
const out = /* @__PURE__ */ new Set();
|
|
123
|
+
for (const path of spec) {
|
|
124
|
+
const head = path.split(".")[0];
|
|
125
|
+
if (head && RELATION_KEYS.has(head)) out.add(head);
|
|
126
|
+
}
|
|
127
|
+
return out;
|
|
128
|
+
}
|
|
129
|
+
async function resolveLazy(value) {
|
|
130
|
+
if (value && typeof value.then === "function") return await value;
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
function composeIssueGetWithIncludes(fragmentText) {
|
|
134
|
+
return `
|
|
135
|
+
query IssueWithIncludes($id: String!) {
|
|
136
|
+
issue(id: $id) {
|
|
137
|
+
id identifier title description priority priorityLabel
|
|
138
|
+
estimate sortOrder number url
|
|
139
|
+
createdAt updatedAt archivedAt completedAt startedAt canceledAt
|
|
140
|
+
dueDate snoozedUntilAt
|
|
141
|
+
state { id name type }
|
|
142
|
+
assignee { id email name }
|
|
143
|
+
team { id key name }
|
|
144
|
+
project { id name }
|
|
145
|
+
cycle { id number }
|
|
146
|
+
parent { id identifier }
|
|
147
|
+
${fragmentText}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
`.trim();
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
export { issueGetRuntime };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { LinearAgentError } from "../core/errors/error.js";
|
|
2
|
+
import { redact } from "../core/redact/redact.js";
|
|
3
|
+
import { getLastComplexity, withFetchInterception } from "../core/transport/fetch-interceptor.js";
|
|
4
|
+
import { withRateLimitRetry } from "../core/transport/rate-limit.js";
|
|
5
|
+
import "../core/transport/index.js";
|
|
6
|
+
import { createLinearClient } from "../core/client/factory.js";
|
|
7
|
+
import "../core/client/index.js";
|
|
8
|
+
import { loadConfig } from "../core/config/store.js";
|
|
9
|
+
import "../core/config/index.js";
|
|
10
|
+
import { parseFields, project } from "../core/projection/project.js";
|
|
11
|
+
import { resolveWorkspace } from "../core/workspace/resolver.js";
|
|
12
|
+
import { parsePagination } from "../core/pagination/flags.js";
|
|
13
|
+
import "../core/pagination/index.js";
|
|
14
|
+
import { buildIssueFilter } from "./filter-heuristics.js";
|
|
15
|
+
import { validateAndMergeIncludes } from "./include-fragments.js";
|
|
16
|
+
//#region src/lib/issue-list-runtime.ts
|
|
17
|
+
async function issueListRuntime(input) {
|
|
18
|
+
const config = (input.loadConfigOverride ?? loadConfig)();
|
|
19
|
+
const envForResolver = {};
|
|
20
|
+
if (input.env.LINEAR_WORKSPACE !== void 0) envForResolver.LINEAR_WORKSPACE = input.env.LINEAR_WORKSPACE;
|
|
21
|
+
if (input.env.LINEAR_API_KEY !== void 0) envForResolver.LINEAR_API_KEY = input.env.LINEAR_API_KEY;
|
|
22
|
+
const resolved = resolveWorkspace({
|
|
23
|
+
flags: input.flags.workspace ? { workspace: input.flags.workspace } : {},
|
|
24
|
+
env: envForResolver,
|
|
25
|
+
config
|
|
26
|
+
});
|
|
27
|
+
const fields = parseFields(input.flags.fields ?? "defaults", "issue");
|
|
28
|
+
const { first, after } = parsePagination({
|
|
29
|
+
...input.flags.limit !== void 0 ? { limit: input.flags.limit } : {},
|
|
30
|
+
...input.flags.cursor !== void 0 ? { cursor: input.flags.cursor } : {}
|
|
31
|
+
});
|
|
32
|
+
const filter = buildIssueFilter(input.flags);
|
|
33
|
+
const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
|
|
34
|
+
const includes = input.flags.include ?? [];
|
|
35
|
+
if (includes.length === 0) return withFetchInterception(async () => {
|
|
36
|
+
const issuesArgs = { first };
|
|
37
|
+
if (after !== void 0) issuesArgs.after = after;
|
|
38
|
+
if (filter !== void 0) issuesArgs.filter = filter;
|
|
39
|
+
const connection = await withRateLimitRetry(() => client.issues(issuesArgs), input.retryOptsOverride);
|
|
40
|
+
const projected = await Promise.all(connection.nodes.map(async (issue) => {
|
|
41
|
+
return project(await hydrateForProjection(issue, fields), fields);
|
|
42
|
+
}));
|
|
43
|
+
const complexity = getLastComplexity();
|
|
44
|
+
return {
|
|
45
|
+
data: projected,
|
|
46
|
+
meta: {
|
|
47
|
+
workspace: resolved.name,
|
|
48
|
+
workspaceSource: resolved.source,
|
|
49
|
+
pageInfo: {
|
|
50
|
+
hasNextPage: Boolean(connection.pageInfo?.hasNextPage),
|
|
51
|
+
endCursor: connection.pageInfo?.endCursor ?? null,
|
|
52
|
+
hasPreviousPage: Boolean(connection.pageInfo?.hasPreviousPage),
|
|
53
|
+
startCursor: connection.pageInfo?.startCursor ?? null
|
|
54
|
+
},
|
|
55
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
const query = composeIssueListWithIncludes(validateAndMergeIncludes("issue list", includes));
|
|
60
|
+
return withFetchInterception(async () => {
|
|
61
|
+
const vars = { first };
|
|
62
|
+
if (after !== void 0) vars.after = after;
|
|
63
|
+
if (filter !== void 0) vars.filter = filter;
|
|
64
|
+
const response = await withRateLimitRetry(() => client.client.rawRequest(query, vars), input.retryOptsOverride);
|
|
65
|
+
if (response.error ?? !response.data) {
|
|
66
|
+
const safeMessage = redact(response.error ?? "no data returned from Linear API");
|
|
67
|
+
const safeCause = response.error !== void 0 ? redact(response.error) : void 0;
|
|
68
|
+
throw LinearAgentError.linear.apiError({
|
|
69
|
+
message: safeMessage,
|
|
70
|
+
details: {
|
|
71
|
+
command: "issue list",
|
|
72
|
+
cause: safeCause
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
const root = response.data.issues;
|
|
77
|
+
if (!root) throw LinearAgentError.linear.apiError({
|
|
78
|
+
message: "unexpected response shape: missing issues field",
|
|
79
|
+
details: { command: "issue list" }
|
|
80
|
+
});
|
|
81
|
+
const projected = root.nodes.map((issue) => project(issue, fields));
|
|
82
|
+
const complexity = getLastComplexity();
|
|
83
|
+
return {
|
|
84
|
+
data: projected,
|
|
85
|
+
meta: {
|
|
86
|
+
workspace: resolved.name,
|
|
87
|
+
workspaceSource: resolved.source,
|
|
88
|
+
pageInfo: {
|
|
89
|
+
hasNextPage: Boolean(root.pageInfo?.hasNextPage),
|
|
90
|
+
endCursor: root.pageInfo?.endCursor ?? null,
|
|
91
|
+
hasPreviousPage: Boolean(root.pageInfo?.hasPreviousPage),
|
|
92
|
+
startCursor: root.pageInfo?.startCursor ?? null
|
|
93
|
+
},
|
|
94
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The typed SDK exposes `issue.state`, `issue.assignee`, `issue.team` as
|
|
101
|
+
* lazy promise getters. To project paths like `state.name`, we must `await`
|
|
102
|
+
* each. We hydrate ONLY the related entities the projection spec actually
|
|
103
|
+
* references — narrows N+1 surface for the common `--fields=ids` case.
|
|
104
|
+
*
|
|
105
|
+
* Phase 2 will replace this with a single GraphQL fragment query that pulls
|
|
106
|
+
* the related entities in one round-trip; documented in PITFALLS as the
|
|
107
|
+
* known scaling cliff this Phase 1 implementation deliberately accepts.
|
|
108
|
+
*/
|
|
109
|
+
async function hydrateForProjection(issue, spec) {
|
|
110
|
+
const needs = neededRelations(spec);
|
|
111
|
+
const hydrated = { ...issue };
|
|
112
|
+
if (needs.has("state") && hydrated.state !== void 0) hydrated.state = await resolveLazy(hydrated.state);
|
|
113
|
+
if (needs.has("assignee") && hydrated.assignee !== void 0) hydrated.assignee = await resolveLazy(hydrated.assignee);
|
|
114
|
+
if (needs.has("team") && hydrated.team !== void 0) hydrated.team = await resolveLazy(hydrated.team);
|
|
115
|
+
if (needs.has("project") && hydrated.project !== void 0) hydrated.project = await resolveLazy(hydrated.project);
|
|
116
|
+
if (needs.has("cycle") && hydrated.cycle !== void 0) hydrated.cycle = await resolveLazy(hydrated.cycle);
|
|
117
|
+
if (needs.has("parent") && hydrated.parent !== void 0) hydrated.parent = await resolveLazy(hydrated.parent);
|
|
118
|
+
return hydrated;
|
|
119
|
+
}
|
|
120
|
+
const RELATION_KEYS = new Set([
|
|
121
|
+
"state",
|
|
122
|
+
"assignee",
|
|
123
|
+
"team",
|
|
124
|
+
"project",
|
|
125
|
+
"cycle",
|
|
126
|
+
"parent"
|
|
127
|
+
]);
|
|
128
|
+
function neededRelations(spec) {
|
|
129
|
+
if (spec === "*") return new Set(RELATION_KEYS);
|
|
130
|
+
const out = /* @__PURE__ */ new Set();
|
|
131
|
+
for (const path of spec) {
|
|
132
|
+
const head = path.split(".")[0];
|
|
133
|
+
if (head && RELATION_KEYS.has(head)) out.add(head);
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
async function resolveLazy(value) {
|
|
138
|
+
if (value && typeof value.then === "function") return await value;
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
function composeIssueListWithIncludes(fragmentText) {
|
|
142
|
+
return `
|
|
143
|
+
query IssuesWithIncludes($filter: IssueFilter, $first: Int, $after: String) {
|
|
144
|
+
issues(filter: $filter, first: $first, after: $after) {
|
|
145
|
+
nodes {
|
|
146
|
+
id identifier title description priority priorityLabel
|
|
147
|
+
estimate sortOrder number url
|
|
148
|
+
createdAt updatedAt archivedAt completedAt startedAt canceledAt
|
|
149
|
+
dueDate snoozedUntilAt
|
|
150
|
+
state { id name type }
|
|
151
|
+
assignee { id email name }
|
|
152
|
+
team { id key name }
|
|
153
|
+
project { id name }
|
|
154
|
+
cycle { id number }
|
|
155
|
+
parent { id identifier }
|
|
156
|
+
${fragmentText}
|
|
157
|
+
}
|
|
158
|
+
pageInfo { hasNextPage endCursor hasPreviousPage startCursor }
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
`.trim();
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
export { issueListRuntime };
|
|
@@ -0,0 +1,89 @@
|
|
|
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-purge-runtime.ts
|
|
13
|
+
async function issuePurgeRuntime(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
|
+
if (!input.flags.yes) throw new LinearAgentError({
|
|
25
|
+
code: "CONFIRMATION_REQUIRED",
|
|
26
|
+
message: "purge is permanent -- re-run with --yes to confirm",
|
|
27
|
+
details: { remediation: "pass --yes to confirm permanent deletion" }
|
|
28
|
+
});
|
|
29
|
+
const client = (input.clientFactoryOverride ?? createLinearClient)(resolved);
|
|
30
|
+
return withFetchInterception(async () => {
|
|
31
|
+
const ref = input.args.identifier;
|
|
32
|
+
const { id, identifier } = await resolveIssueRef(client, ref, input.retryOptsOverride);
|
|
33
|
+
const payload = await withRateLimitRetry(() => client.deleteIssue(id, { permanentlyDelete: true }), input.retryOptsOverride);
|
|
34
|
+
if (!payload.success) throw LinearAgentError.linear.apiError({
|
|
35
|
+
message: "deleteIssue returned success=false",
|
|
36
|
+
details: { lastSyncId: payload.lastSyncId }
|
|
37
|
+
});
|
|
38
|
+
const data = {
|
|
39
|
+
id,
|
|
40
|
+
permanentlyDeleted: true
|
|
41
|
+
};
|
|
42
|
+
if (identifier !== void 0) data.identifier = identifier;
|
|
43
|
+
const complexity = getLastComplexity();
|
|
44
|
+
return {
|
|
45
|
+
data,
|
|
46
|
+
meta: {
|
|
47
|
+
workspace: resolved.name,
|
|
48
|
+
workspaceSource: resolved.source,
|
|
49
|
+
...complexity !== void 0 ? { complexity } : {}
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
async function resolveIssueRef(client, ref, retryOpts) {
|
|
55
|
+
if (UUID_RE.test(ref)) return { id: ref };
|
|
56
|
+
const m = ref.match(ISSUE_IDENTIFIER_RE);
|
|
57
|
+
if (!m) throw new LinearAgentError({
|
|
58
|
+
code: "ISSUE_NOT_FOUND",
|
|
59
|
+
message: `invalid issue ref: ${ref}`,
|
|
60
|
+
details: { ref }
|
|
61
|
+
});
|
|
62
|
+
const teamKey = m[1].toUpperCase();
|
|
63
|
+
const number = Number(m[2]);
|
|
64
|
+
const issue = (await withRateLimitRetry(() => client.issues({
|
|
65
|
+
filter: {
|
|
66
|
+
team: { key: { eq: teamKey } },
|
|
67
|
+
number: { eq: number }
|
|
68
|
+
},
|
|
69
|
+
first: 1,
|
|
70
|
+
includeArchived: true
|
|
71
|
+
}), retryOpts)).nodes[0];
|
|
72
|
+
if (!issue) throw new LinearAgentError({
|
|
73
|
+
code: "ISSUE_NOT_FOUND",
|
|
74
|
+
message: `issue not found: ${ref}`,
|
|
75
|
+
details: { ref }
|
|
76
|
+
});
|
|
77
|
+
const idRaw = issue.id;
|
|
78
|
+
if (typeof idRaw !== "string") throw new LinearAgentError({
|
|
79
|
+
code: "ISSUE_NOT_FOUND",
|
|
80
|
+
message: `issue not found: ${ref}`,
|
|
81
|
+
details: { ref }
|
|
82
|
+
});
|
|
83
|
+
const idRes = { id: idRaw };
|
|
84
|
+
const idn = issue.identifier;
|
|
85
|
+
if (typeof idn === "string") idRes.identifier = idn;
|
|
86
|
+
return idRes;
|
|
87
|
+
}
|
|
88
|
+
//#endregion
|
|
89
|
+
export { issuePurgeRuntime };
|