@skillrecordings/cli 0.12.0 → 0.13.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/README.md +124 -9
- package/dist/index.js +2440 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -58568,9 +58568,9 @@ var require_addComment = __commonJS({
|
|
|
58568
58568
|
Object.defineProperty(exports, "__esModule", {
|
|
58569
58569
|
value: true
|
|
58570
58570
|
});
|
|
58571
|
-
exports.default =
|
|
58571
|
+
exports.default = addComment2;
|
|
58572
58572
|
var _addComments = require_addComments();
|
|
58573
|
-
function
|
|
58573
|
+
function addComment2(node, type, content, line) {
|
|
58574
58574
|
return (0, _addComments.default)(node, type, [{
|
|
58575
58575
|
type: line ? "CommentLine" : "CommentBlock",
|
|
58576
58576
|
value: content
|
|
@@ -73654,7 +73654,7 @@ var require_comments = __commonJS({
|
|
|
73654
73654
|
Object.defineProperty(exports, "__esModule", {
|
|
73655
73655
|
value: true
|
|
73656
73656
|
});
|
|
73657
|
-
exports.addComment =
|
|
73657
|
+
exports.addComment = addComment2;
|
|
73658
73658
|
exports.addComments = addComments;
|
|
73659
73659
|
exports.shareCommentsWithSiblings = shareCommentsWithSiblings;
|
|
73660
73660
|
var _t = require_lib6();
|
|
@@ -73693,7 +73693,7 @@ var require_comments = __commonJS({
|
|
|
73693
73693
|
return !set.has(el);
|
|
73694
73694
|
});
|
|
73695
73695
|
}
|
|
73696
|
-
function
|
|
73696
|
+
function addComment2(type, content, line) {
|
|
73697
73697
|
_addComment(this.node, type, content, line);
|
|
73698
73698
|
}
|
|
73699
73699
|
function addComments(type, comments) {
|
|
@@ -74833,7 +74833,7 @@ var SECRET_REFS = {
|
|
|
74833
74833
|
QDRANT_COLLECTION: "op://Support/skill-cli/QDRANT_COLLECTION",
|
|
74834
74834
|
OLLAMA_BASE_URL: "op://Support/skill-cli/OLLAMA_BASE_URL",
|
|
74835
74835
|
EMBEDDING_MODEL: "op://Support/skill-cli/EMBEDDING_MODEL",
|
|
74836
|
-
AGE_SECRET_KEY: "op://Support/skill-cli-age-key/
|
|
74836
|
+
AGE_SECRET_KEY: "op://Support/skill-cli-age-key/private_key"
|
|
74837
74837
|
};
|
|
74838
74838
|
|
|
74839
74839
|
// src/core/secrets.ts
|
|
@@ -113466,6 +113466,2440 @@ function registerKbCommands(program3) {
|
|
|
113466
113466
|
});
|
|
113467
113467
|
}
|
|
113468
113468
|
|
|
113469
|
+
// src/commands/linear/index.ts
|
|
113470
|
+
init_esm_shims();
|
|
113471
|
+
|
|
113472
|
+
// src/commands/linear/assign.ts
|
|
113473
|
+
init_esm_shims();
|
|
113474
|
+
|
|
113475
|
+
// src/commands/linear/client.ts
|
|
113476
|
+
init_esm_shims();
|
|
113477
|
+
import { LinearClient } from "@linear/sdk";
|
|
113478
|
+
function getLinearClient() {
|
|
113479
|
+
const apiKey = process.env.LINEAR_API_KEY;
|
|
113480
|
+
if (!apiKey) {
|
|
113481
|
+
throw new CLIError({
|
|
113482
|
+
userMessage: "LINEAR_API_KEY environment variable is not set.",
|
|
113483
|
+
suggestion: "Set LINEAR_API_KEY to your Linear API token. Get it from https://linear.app/settings/api",
|
|
113484
|
+
exitCode: 1
|
|
113485
|
+
});
|
|
113486
|
+
}
|
|
113487
|
+
return new LinearClient({ apiKey });
|
|
113488
|
+
}
|
|
113489
|
+
|
|
113490
|
+
// src/commands/linear/hateoas.ts
|
|
113491
|
+
init_esm_shims();
|
|
113492
|
+
function hateoasWrap2(opts) {
|
|
113493
|
+
return {
|
|
113494
|
+
_type: opts.type,
|
|
113495
|
+
_command: opts.command,
|
|
113496
|
+
data: opts.data,
|
|
113497
|
+
_links: opts.links ?? [],
|
|
113498
|
+
_actions: opts.actions ?? []
|
|
113499
|
+
};
|
|
113500
|
+
}
|
|
113501
|
+
function issueLinks(identifier, teamKey) {
|
|
113502
|
+
const links = [
|
|
113503
|
+
{
|
|
113504
|
+
rel: "self",
|
|
113505
|
+
command: `skill linear issue ${identifier} --json`,
|
|
113506
|
+
description: "This issue"
|
|
113507
|
+
},
|
|
113508
|
+
{
|
|
113509
|
+
rel: "comments",
|
|
113510
|
+
command: `skill linear comments ${identifier} --json`,
|
|
113511
|
+
description: "Comments on this issue"
|
|
113512
|
+
}
|
|
113513
|
+
];
|
|
113514
|
+
if (teamKey) {
|
|
113515
|
+
links.push({
|
|
113516
|
+
rel: "team-issues",
|
|
113517
|
+
command: `skill linear issues --team ${teamKey} --json`,
|
|
113518
|
+
description: "All issues in this team"
|
|
113519
|
+
});
|
|
113520
|
+
}
|
|
113521
|
+
return links;
|
|
113522
|
+
}
|
|
113523
|
+
function issueActions(identifier) {
|
|
113524
|
+
return [
|
|
113525
|
+
{
|
|
113526
|
+
action: "comment",
|
|
113527
|
+
command: `skill linear comment ${identifier} --body "<text>"`,
|
|
113528
|
+
description: "Add a comment"
|
|
113529
|
+
},
|
|
113530
|
+
{
|
|
113531
|
+
action: "assign",
|
|
113532
|
+
command: `skill linear assign ${identifier} --to <user-email>`,
|
|
113533
|
+
description: "Assign this issue"
|
|
113534
|
+
},
|
|
113535
|
+
{
|
|
113536
|
+
action: "unassign",
|
|
113537
|
+
command: `skill linear assign ${identifier} --unassign`,
|
|
113538
|
+
description: "Unassign this issue"
|
|
113539
|
+
},
|
|
113540
|
+
{
|
|
113541
|
+
action: "update-state",
|
|
113542
|
+
command: `skill linear state ${identifier} --state "<state-name>"`,
|
|
113543
|
+
description: "Change workflow state"
|
|
113544
|
+
},
|
|
113545
|
+
{
|
|
113546
|
+
action: "update-priority",
|
|
113547
|
+
command: `skill linear update ${identifier} --priority <0-4>`,
|
|
113548
|
+
description: "Change priority (0=urgent, 4=none)"
|
|
113549
|
+
},
|
|
113550
|
+
{
|
|
113551
|
+
action: "add-label",
|
|
113552
|
+
command: `skill linear label ${identifier} --add "<label-name>"`,
|
|
113553
|
+
description: "Add a label"
|
|
113554
|
+
},
|
|
113555
|
+
{
|
|
113556
|
+
action: "close",
|
|
113557
|
+
command: `skill linear close ${identifier}`,
|
|
113558
|
+
description: "Close this issue",
|
|
113559
|
+
destructive: true
|
|
113560
|
+
},
|
|
113561
|
+
{
|
|
113562
|
+
action: "link",
|
|
113563
|
+
command: `skill linear link ${identifier} --blocks <other-id>`,
|
|
113564
|
+
description: "Link to another issue"
|
|
113565
|
+
}
|
|
113566
|
+
];
|
|
113567
|
+
}
|
|
113568
|
+
function issueListLinks(issues, teamKey) {
|
|
113569
|
+
const links = issues.slice(0, 10).map((issue) => ({
|
|
113570
|
+
rel: "issue",
|
|
113571
|
+
command: `skill linear issue ${issue.identifier} --json`,
|
|
113572
|
+
description: issue.title
|
|
113573
|
+
}));
|
|
113574
|
+
if (teamKey) {
|
|
113575
|
+
links.push({
|
|
113576
|
+
rel: "team",
|
|
113577
|
+
command: `skill linear team ${teamKey} --json`,
|
|
113578
|
+
description: "Parent team"
|
|
113579
|
+
});
|
|
113580
|
+
}
|
|
113581
|
+
return links;
|
|
113582
|
+
}
|
|
113583
|
+
function issueListActions(teamKey) {
|
|
113584
|
+
const actions = [
|
|
113585
|
+
{
|
|
113586
|
+
action: "create",
|
|
113587
|
+
command: `skill linear create "<title>"${teamKey ? ` --team ${teamKey}` : ""}`,
|
|
113588
|
+
description: "Create a new issue"
|
|
113589
|
+
},
|
|
113590
|
+
{
|
|
113591
|
+
action: "search",
|
|
113592
|
+
command: `skill linear search "<query>"`,
|
|
113593
|
+
description: "Search issues"
|
|
113594
|
+
},
|
|
113595
|
+
{
|
|
113596
|
+
action: "my-issues",
|
|
113597
|
+
command: `skill linear my --json`,
|
|
113598
|
+
description: "View my assigned issues"
|
|
113599
|
+
}
|
|
113600
|
+
];
|
|
113601
|
+
if (teamKey) {
|
|
113602
|
+
actions.push({
|
|
113603
|
+
action: "team-states",
|
|
113604
|
+
command: `skill linear states ${teamKey} --json`,
|
|
113605
|
+
description: "View workflow states"
|
|
113606
|
+
});
|
|
113607
|
+
actions.push({
|
|
113608
|
+
action: "team-labels",
|
|
113609
|
+
command: `skill linear labels ${teamKey} --json`,
|
|
113610
|
+
description: "View available labels"
|
|
113611
|
+
});
|
|
113612
|
+
}
|
|
113613
|
+
return actions;
|
|
113614
|
+
}
|
|
113615
|
+
function teamLinks(teamKey) {
|
|
113616
|
+
return [
|
|
113617
|
+
{
|
|
113618
|
+
rel: "self",
|
|
113619
|
+
command: `skill linear team ${teamKey} --json`,
|
|
113620
|
+
description: "This team"
|
|
113621
|
+
},
|
|
113622
|
+
{
|
|
113623
|
+
rel: "issues",
|
|
113624
|
+
command: `skill linear issues --team ${teamKey} --json`,
|
|
113625
|
+
description: "Issues in this team"
|
|
113626
|
+
},
|
|
113627
|
+
{
|
|
113628
|
+
rel: "states",
|
|
113629
|
+
command: `skill linear states ${teamKey} --json`,
|
|
113630
|
+
description: "Workflow states"
|
|
113631
|
+
},
|
|
113632
|
+
{
|
|
113633
|
+
rel: "labels",
|
|
113634
|
+
command: `skill linear labels ${teamKey} --json`,
|
|
113635
|
+
description: "Labels"
|
|
113636
|
+
}
|
|
113637
|
+
];
|
|
113638
|
+
}
|
|
113639
|
+
function commentLinks(commentId, issueIdentifier) {
|
|
113640
|
+
return [
|
|
113641
|
+
{
|
|
113642
|
+
rel: "issue",
|
|
113643
|
+
command: `skill linear issue ${issueIdentifier} --json`,
|
|
113644
|
+
description: "Parent issue"
|
|
113645
|
+
},
|
|
113646
|
+
{
|
|
113647
|
+
rel: "all-comments",
|
|
113648
|
+
command: `skill linear comments ${issueIdentifier} --json`,
|
|
113649
|
+
description: "All comments"
|
|
113650
|
+
}
|
|
113651
|
+
];
|
|
113652
|
+
}
|
|
113653
|
+
function userListLinks(users) {
|
|
113654
|
+
return users.map((u) => ({
|
|
113655
|
+
rel: "user-issues",
|
|
113656
|
+
command: `skill linear issues --assignee ${u.email} --json`,
|
|
113657
|
+
description: `${u.name} (${u.email})`
|
|
113658
|
+
}));
|
|
113659
|
+
}
|
|
113660
|
+
|
|
113661
|
+
// src/commands/linear/assign.ts
|
|
113662
|
+
async function assignIssue(ctx, issueId, options) {
|
|
113663
|
+
if (options.to && options.unassign) {
|
|
113664
|
+
throw new CLIError({
|
|
113665
|
+
userMessage: "Cannot use both --to and --unassign.",
|
|
113666
|
+
suggestion: "Choose one: --to <email> OR --unassign",
|
|
113667
|
+
exitCode: 1
|
|
113668
|
+
});
|
|
113669
|
+
}
|
|
113670
|
+
if (!options.to && !options.unassign) {
|
|
113671
|
+
throw new CLIError({
|
|
113672
|
+
userMessage: "Missing assignment option.",
|
|
113673
|
+
suggestion: "Use --to <email> to assign or --unassign to remove assignee.",
|
|
113674
|
+
exitCode: 1
|
|
113675
|
+
});
|
|
113676
|
+
}
|
|
113677
|
+
try {
|
|
113678
|
+
const client = getLinearClient();
|
|
113679
|
+
const issue = await client.issue(issueId);
|
|
113680
|
+
if (!issue) {
|
|
113681
|
+
throw new CLIError({
|
|
113682
|
+
userMessage: `Issue not found: ${issueId}`,
|
|
113683
|
+
suggestion: "Use `skill linear issues --json` to list available issues.",
|
|
113684
|
+
exitCode: 1
|
|
113685
|
+
});
|
|
113686
|
+
}
|
|
113687
|
+
let assigneeId = null;
|
|
113688
|
+
let assigneeName = null;
|
|
113689
|
+
if (options.to) {
|
|
113690
|
+
if (options.to.toLowerCase() === "me") {
|
|
113691
|
+
const viewer = await client.viewer;
|
|
113692
|
+
assigneeId = viewer.id;
|
|
113693
|
+
assigneeName = viewer.name;
|
|
113694
|
+
} else {
|
|
113695
|
+
const users = await client.users();
|
|
113696
|
+
const user = users.nodes.find(
|
|
113697
|
+
(u) => u.email?.toLowerCase() === options.to.toLowerCase() || u.name?.toLowerCase() === options.to.toLowerCase()
|
|
113698
|
+
);
|
|
113699
|
+
if (!user) {
|
|
113700
|
+
throw new CLIError({
|
|
113701
|
+
userMessage: `User not found: ${options.to}`,
|
|
113702
|
+
suggestion: "Use `skill linear users --json` to list available users.",
|
|
113703
|
+
exitCode: 1
|
|
113704
|
+
});
|
|
113705
|
+
}
|
|
113706
|
+
assigneeId = user.id;
|
|
113707
|
+
assigneeName = user.name;
|
|
113708
|
+
}
|
|
113709
|
+
}
|
|
113710
|
+
await client.updateIssue(issue.id, {
|
|
113711
|
+
assigneeId: assigneeId || void 0
|
|
113712
|
+
});
|
|
113713
|
+
const team = await issue.team;
|
|
113714
|
+
const resultData = {
|
|
113715
|
+
issueId: issue.id,
|
|
113716
|
+
issueIdentifier: issue.identifier,
|
|
113717
|
+
action: options.unassign ? "unassigned" : "assigned",
|
|
113718
|
+
assignee: assigneeName ? { id: assigneeId, name: assigneeName } : null,
|
|
113719
|
+
success: true
|
|
113720
|
+
};
|
|
113721
|
+
if (ctx.format === "json") {
|
|
113722
|
+
ctx.output.data(
|
|
113723
|
+
JSON.stringify(
|
|
113724
|
+
hateoasWrap2({
|
|
113725
|
+
type: "assign-result",
|
|
113726
|
+
command: `skill linear issue ${issue.identifier} --json`,
|
|
113727
|
+
data: resultData,
|
|
113728
|
+
links: issueLinks(issue.identifier, team?.key),
|
|
113729
|
+
actions: issueActions(issue.identifier)
|
|
113730
|
+
}),
|
|
113731
|
+
null,
|
|
113732
|
+
2
|
|
113733
|
+
)
|
|
113734
|
+
);
|
|
113735
|
+
return;
|
|
113736
|
+
}
|
|
113737
|
+
ctx.output.data("");
|
|
113738
|
+
if (options.unassign) {
|
|
113739
|
+
ctx.output.data(`\u2705 Unassigned ${issue.identifier}`);
|
|
113740
|
+
} else {
|
|
113741
|
+
ctx.output.data(`\u2705 Assigned ${issue.identifier} to ${assigneeName}`);
|
|
113742
|
+
}
|
|
113743
|
+
ctx.output.data("");
|
|
113744
|
+
ctx.output.data(` View: skill linear issue ${issue.identifier}`);
|
|
113745
|
+
ctx.output.data("");
|
|
113746
|
+
} catch (error) {
|
|
113747
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
113748
|
+
userMessage: "Failed to assign issue.",
|
|
113749
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
113750
|
+
cause: error
|
|
113751
|
+
});
|
|
113752
|
+
ctx.output.error(formatError(cliError));
|
|
113753
|
+
process.exitCode = cliError.exitCode;
|
|
113754
|
+
}
|
|
113755
|
+
}
|
|
113756
|
+
|
|
113757
|
+
// src/commands/linear/close.ts
|
|
113758
|
+
init_esm_shims();
|
|
113759
|
+
async function closeIssue(ctx, issueId, options = {}) {
|
|
113760
|
+
try {
|
|
113761
|
+
const client = getLinearClient();
|
|
113762
|
+
const issue = await client.issue(issueId);
|
|
113763
|
+
if (!issue) {
|
|
113764
|
+
throw new CLIError({
|
|
113765
|
+
userMessage: `Issue not found: ${issueId}`,
|
|
113766
|
+
suggestion: "Use `skill linear issues --json` to list available issues.",
|
|
113767
|
+
exitCode: 1
|
|
113768
|
+
});
|
|
113769
|
+
}
|
|
113770
|
+
const team = await issue.team;
|
|
113771
|
+
if (!team) {
|
|
113772
|
+
throw new CLIError({
|
|
113773
|
+
userMessage: "Could not determine team for issue.",
|
|
113774
|
+
exitCode: 1
|
|
113775
|
+
});
|
|
113776
|
+
}
|
|
113777
|
+
const states = await team.states();
|
|
113778
|
+
const targetType = options.canceled ? "canceled" : "completed";
|
|
113779
|
+
const closeState = states.nodes.find((s) => s.type === targetType);
|
|
113780
|
+
if (!closeState) {
|
|
113781
|
+
throw new CLIError({
|
|
113782
|
+
userMessage: `No ${targetType} state found for team ${team.key}.`,
|
|
113783
|
+
suggestion: `Use \`skill linear states ${team.key}\` to see available states.`,
|
|
113784
|
+
exitCode: 1
|
|
113785
|
+
});
|
|
113786
|
+
}
|
|
113787
|
+
const currentState = await issue.state;
|
|
113788
|
+
await client.updateIssue(issue.id, {
|
|
113789
|
+
stateId: closeState.id
|
|
113790
|
+
});
|
|
113791
|
+
const resultData = {
|
|
113792
|
+
issueId: issue.id,
|
|
113793
|
+
issueIdentifier: issue.identifier,
|
|
113794
|
+
previousState: currentState?.name || null,
|
|
113795
|
+
newState: closeState.name,
|
|
113796
|
+
closeType: targetType,
|
|
113797
|
+
success: true
|
|
113798
|
+
};
|
|
113799
|
+
if (ctx.format === "json") {
|
|
113800
|
+
ctx.output.data(
|
|
113801
|
+
JSON.stringify(
|
|
113802
|
+
hateoasWrap2({
|
|
113803
|
+
type: "close-result",
|
|
113804
|
+
command: `skill linear issue ${issue.identifier} --json`,
|
|
113805
|
+
data: resultData,
|
|
113806
|
+
links: [
|
|
113807
|
+
...issueLinks(issue.identifier, team.key),
|
|
113808
|
+
...teamLinks(team.key)
|
|
113809
|
+
]
|
|
113810
|
+
}),
|
|
113811
|
+
null,
|
|
113812
|
+
2
|
|
113813
|
+
)
|
|
113814
|
+
);
|
|
113815
|
+
return;
|
|
113816
|
+
}
|
|
113817
|
+
const emoji = options.canceled ? "\u274C" : "\u2705";
|
|
113818
|
+
const verb = options.canceled ? "Canceled" : "Closed";
|
|
113819
|
+
ctx.output.data("");
|
|
113820
|
+
ctx.output.data(`${emoji} ${verb} ${issue.identifier}`);
|
|
113821
|
+
ctx.output.data("\u2500".repeat(50));
|
|
113822
|
+
ctx.output.data(` Title: ${issue.title}`);
|
|
113823
|
+
ctx.output.data(` From: ${currentState?.name || "Unknown"}`);
|
|
113824
|
+
ctx.output.data(` To: ${closeState.name}`);
|
|
113825
|
+
ctx.output.data("");
|
|
113826
|
+
} catch (error) {
|
|
113827
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
113828
|
+
userMessage: "Failed to close issue.",
|
|
113829
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
113830
|
+
cause: error
|
|
113831
|
+
});
|
|
113832
|
+
ctx.output.error(formatError(cliError));
|
|
113833
|
+
process.exitCode = cliError.exitCode;
|
|
113834
|
+
}
|
|
113835
|
+
}
|
|
113836
|
+
|
|
113837
|
+
// src/commands/linear/comment.ts
|
|
113838
|
+
init_esm_shims();
|
|
113839
|
+
async function addComment(ctx, issueId, options) {
|
|
113840
|
+
if (!options.body || options.body.trim().length === 0) {
|
|
113841
|
+
throw new CLIError({
|
|
113842
|
+
userMessage: "Comment body is required.",
|
|
113843
|
+
suggestion: 'Usage: skill linear comment ENG-123 --body "Your comment"',
|
|
113844
|
+
exitCode: 1
|
|
113845
|
+
});
|
|
113846
|
+
}
|
|
113847
|
+
try {
|
|
113848
|
+
const client = getLinearClient();
|
|
113849
|
+
const issue = await client.issue(issueId);
|
|
113850
|
+
if (!issue) {
|
|
113851
|
+
throw new CLIError({
|
|
113852
|
+
userMessage: `Issue not found: ${issueId}`,
|
|
113853
|
+
suggestion: "Use `skill linear issues --json` to list available issues.",
|
|
113854
|
+
exitCode: 1
|
|
113855
|
+
});
|
|
113856
|
+
}
|
|
113857
|
+
const payload = await client.createComment({
|
|
113858
|
+
issueId: issue.id,
|
|
113859
|
+
body: options.body.trim()
|
|
113860
|
+
});
|
|
113861
|
+
const comment = await payload.comment;
|
|
113862
|
+
if (!comment) {
|
|
113863
|
+
throw new CLIError({
|
|
113864
|
+
userMessage: "Failed to create comment - no comment returned.",
|
|
113865
|
+
exitCode: 1
|
|
113866
|
+
});
|
|
113867
|
+
}
|
|
113868
|
+
const user = await comment.user;
|
|
113869
|
+
const commentData = {
|
|
113870
|
+
id: comment.id,
|
|
113871
|
+
body: comment.body,
|
|
113872
|
+
issueId: issue.id,
|
|
113873
|
+
issueIdentifier: issue.identifier,
|
|
113874
|
+
author: user ? { id: user.id, name: user.name, email: user.email } : null,
|
|
113875
|
+
createdAt: comment.createdAt,
|
|
113876
|
+
url: comment.url
|
|
113877
|
+
};
|
|
113878
|
+
if (ctx.format === "json") {
|
|
113879
|
+
ctx.output.data(
|
|
113880
|
+
JSON.stringify(
|
|
113881
|
+
hateoasWrap2({
|
|
113882
|
+
type: "comment-created",
|
|
113883
|
+
command: `skill linear comments ${issue.identifier} --json`,
|
|
113884
|
+
data: commentData,
|
|
113885
|
+
links: [
|
|
113886
|
+
...commentLinks(comment.id, issue.identifier),
|
|
113887
|
+
...issueLinks(issue.identifier)
|
|
113888
|
+
],
|
|
113889
|
+
actions: issueActions(issue.identifier)
|
|
113890
|
+
}),
|
|
113891
|
+
null,
|
|
113892
|
+
2
|
|
113893
|
+
)
|
|
113894
|
+
);
|
|
113895
|
+
return;
|
|
113896
|
+
}
|
|
113897
|
+
ctx.output.data("");
|
|
113898
|
+
ctx.output.data(`\u2705 Comment added to ${issue.identifier}`);
|
|
113899
|
+
ctx.output.data("\u2500".repeat(50));
|
|
113900
|
+
ctx.output.data(` Author: ${user?.name || "Unknown"}`);
|
|
113901
|
+
ctx.output.data(` URL: ${comment.url}`);
|
|
113902
|
+
ctx.output.data("");
|
|
113903
|
+
ctx.output.data(" Comment:");
|
|
113904
|
+
ctx.output.data(" " + "-".repeat(40));
|
|
113905
|
+
const bodyLines = options.body.split("\n");
|
|
113906
|
+
for (const line of bodyLines.slice(0, 10)) {
|
|
113907
|
+
ctx.output.data(` ${line}`);
|
|
113908
|
+
}
|
|
113909
|
+
if (bodyLines.length > 10) {
|
|
113910
|
+
ctx.output.data(` ... (${bodyLines.length - 10} more lines)`);
|
|
113911
|
+
}
|
|
113912
|
+
ctx.output.data("");
|
|
113913
|
+
} catch (error) {
|
|
113914
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
113915
|
+
userMessage: "Failed to add comment.",
|
|
113916
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
113917
|
+
cause: error
|
|
113918
|
+
});
|
|
113919
|
+
ctx.output.error(formatError(cliError));
|
|
113920
|
+
process.exitCode = cliError.exitCode;
|
|
113921
|
+
}
|
|
113922
|
+
}
|
|
113923
|
+
|
|
113924
|
+
// src/commands/linear/comments.ts
|
|
113925
|
+
init_esm_shims();
|
|
113926
|
+
async function listComments(ctx, issueId, options = {}) {
|
|
113927
|
+
const limit2 = options.limit || 50;
|
|
113928
|
+
try {
|
|
113929
|
+
const client = getLinearClient();
|
|
113930
|
+
const issue = await client.issue(issueId);
|
|
113931
|
+
if (!issue) {
|
|
113932
|
+
throw new CLIError({
|
|
113933
|
+
userMessage: `Issue not found: ${issueId}`,
|
|
113934
|
+
suggestion: "Use `skill linear issues --json` to list available issues.",
|
|
113935
|
+
exitCode: 1
|
|
113936
|
+
});
|
|
113937
|
+
}
|
|
113938
|
+
const commentsConnection = await issue.comments({ first: limit2 });
|
|
113939
|
+
const comments = commentsConnection.nodes || [];
|
|
113940
|
+
const commentsWithUsers = await Promise.all(
|
|
113941
|
+
comments.map(async (comment) => ({
|
|
113942
|
+
comment,
|
|
113943
|
+
user: await comment.user
|
|
113944
|
+
}))
|
|
113945
|
+
);
|
|
113946
|
+
if (ctx.format === "json") {
|
|
113947
|
+
const commentData = commentsWithUsers.map(({ comment, user }) => ({
|
|
113948
|
+
id: comment.id,
|
|
113949
|
+
body: comment.body,
|
|
113950
|
+
author: user ? { id: user.id, name: user.name, email: user.email } : null,
|
|
113951
|
+
createdAt: comment.createdAt,
|
|
113952
|
+
updatedAt: comment.updatedAt,
|
|
113953
|
+
url: comment.url
|
|
113954
|
+
}));
|
|
113955
|
+
ctx.output.data(
|
|
113956
|
+
JSON.stringify(
|
|
113957
|
+
hateoasWrap2({
|
|
113958
|
+
type: "comment-list",
|
|
113959
|
+
command: `skill linear comments ${issue.identifier} --json`,
|
|
113960
|
+
data: {
|
|
113961
|
+
issueId: issue.id,
|
|
113962
|
+
issueIdentifier: issue.identifier,
|
|
113963
|
+
issueTitle: issue.title,
|
|
113964
|
+
count: comments.length,
|
|
113965
|
+
comments: commentData
|
|
113966
|
+
},
|
|
113967
|
+
links: issueLinks(issue.identifier),
|
|
113968
|
+
actions: [
|
|
113969
|
+
{
|
|
113970
|
+
action: "add-comment",
|
|
113971
|
+
command: `skill linear comment ${issue.identifier} --body "<text>"`,
|
|
113972
|
+
description: "Add a new comment"
|
|
113973
|
+
},
|
|
113974
|
+
...issueActions(issue.identifier)
|
|
113975
|
+
]
|
|
113976
|
+
}),
|
|
113977
|
+
null,
|
|
113978
|
+
2
|
|
113979
|
+
)
|
|
113980
|
+
);
|
|
113981
|
+
return;
|
|
113982
|
+
}
|
|
113983
|
+
ctx.output.data("");
|
|
113984
|
+
ctx.output.data(`\u{1F4AC} Comments on ${issue.identifier}: ${issue.title}`);
|
|
113985
|
+
ctx.output.data("\u2500".repeat(80));
|
|
113986
|
+
if (comments.length === 0) {
|
|
113987
|
+
ctx.output.data("");
|
|
113988
|
+
ctx.output.data(" No comments yet.");
|
|
113989
|
+
ctx.output.data("");
|
|
113990
|
+
ctx.output.data(
|
|
113991
|
+
` Add one: skill linear comment ${issue.identifier} --body "Your comment"`
|
|
113992
|
+
);
|
|
113993
|
+
ctx.output.data("");
|
|
113994
|
+
return;
|
|
113995
|
+
}
|
|
113996
|
+
for (const { comment, user } of commentsWithUsers) {
|
|
113997
|
+
const date = new Date(comment.createdAt).toLocaleDateString();
|
|
113998
|
+
const authorName = user?.name || "Unknown";
|
|
113999
|
+
ctx.output.data("");
|
|
114000
|
+
ctx.output.data(` \u{1F4DD} ${authorName} \u2022 ${date}`);
|
|
114001
|
+
ctx.output.data(" " + "-".repeat(40));
|
|
114002
|
+
const bodyLines = comment.body.split("\n");
|
|
114003
|
+
for (const line of bodyLines.slice(0, 5)) {
|
|
114004
|
+
ctx.output.data(` ${line}`);
|
|
114005
|
+
}
|
|
114006
|
+
if (bodyLines.length > 5) {
|
|
114007
|
+
ctx.output.data(` ... (${bodyLines.length - 5} more lines)`);
|
|
114008
|
+
}
|
|
114009
|
+
}
|
|
114010
|
+
ctx.output.data("");
|
|
114011
|
+
ctx.output.data(
|
|
114012
|
+
` Add comment: skill linear comment ${issue.identifier} --body "text"`
|
|
114013
|
+
);
|
|
114014
|
+
ctx.output.data("");
|
|
114015
|
+
} catch (error) {
|
|
114016
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
114017
|
+
userMessage: "Failed to list comments.",
|
|
114018
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
114019
|
+
cause: error
|
|
114020
|
+
});
|
|
114021
|
+
ctx.output.error(formatError(cliError));
|
|
114022
|
+
process.exitCode = cliError.exitCode;
|
|
114023
|
+
}
|
|
114024
|
+
}
|
|
114025
|
+
|
|
114026
|
+
// src/commands/linear/create.ts
|
|
114027
|
+
init_esm_shims();
|
|
114028
|
+
async function createIssue(ctx, title, options = {}) {
|
|
114029
|
+
if (!title || title.trim().length === 0) {
|
|
114030
|
+
throw new CLIError({
|
|
114031
|
+
userMessage: "Issue title is required.",
|
|
114032
|
+
suggestion: 'Usage: skill linear create "Issue title"',
|
|
114033
|
+
exitCode: 1
|
|
114034
|
+
});
|
|
114035
|
+
}
|
|
114036
|
+
try {
|
|
114037
|
+
const client = getLinearClient();
|
|
114038
|
+
let teamId;
|
|
114039
|
+
let teamKey;
|
|
114040
|
+
const teams = await client.teams();
|
|
114041
|
+
if (options.team) {
|
|
114042
|
+
const team = teams.nodes.find(
|
|
114043
|
+
(t2) => t2.key.toLowerCase() === options.team.toLowerCase() || t2.name.toLowerCase() === options.team.toLowerCase()
|
|
114044
|
+
);
|
|
114045
|
+
if (!team) {
|
|
114046
|
+
throw new CLIError({
|
|
114047
|
+
userMessage: `Team not found: ${options.team}`,
|
|
114048
|
+
suggestion: "Use `skill linear teams --json` to list available teams."
|
|
114049
|
+
});
|
|
114050
|
+
}
|
|
114051
|
+
teamId = team.id;
|
|
114052
|
+
teamKey = team.key;
|
|
114053
|
+
} else {
|
|
114054
|
+
const team = teams.nodes[0];
|
|
114055
|
+
if (!team) {
|
|
114056
|
+
throw new CLIError({
|
|
114057
|
+
userMessage: "No teams found in Linear workspace.",
|
|
114058
|
+
exitCode: 1
|
|
114059
|
+
});
|
|
114060
|
+
}
|
|
114061
|
+
teamId = team.id;
|
|
114062
|
+
teamKey = team.key;
|
|
114063
|
+
}
|
|
114064
|
+
let assigneeId;
|
|
114065
|
+
if (options.assignee) {
|
|
114066
|
+
if (options.assignee.toLowerCase() === "me") {
|
|
114067
|
+
const viewer = await client.viewer;
|
|
114068
|
+
assigneeId = viewer.id;
|
|
114069
|
+
} else {
|
|
114070
|
+
const users = await client.users();
|
|
114071
|
+
const user = users.nodes.find(
|
|
114072
|
+
(u) => u.email?.toLowerCase() === options.assignee.toLowerCase() || u.name?.toLowerCase() === options.assignee.toLowerCase()
|
|
114073
|
+
);
|
|
114074
|
+
if (!user) {
|
|
114075
|
+
throw new CLIError({
|
|
114076
|
+
userMessage: `User not found: ${options.assignee}`,
|
|
114077
|
+
suggestion: "Use `skill linear users --json` to list available users."
|
|
114078
|
+
});
|
|
114079
|
+
}
|
|
114080
|
+
assigneeId = user.id;
|
|
114081
|
+
}
|
|
114082
|
+
}
|
|
114083
|
+
let labelIds;
|
|
114084
|
+
if (options.label && options.label.length > 0) {
|
|
114085
|
+
const teamLabels = await client.issueLabels({
|
|
114086
|
+
filter: { team: { id: { eq: teamId } } }
|
|
114087
|
+
});
|
|
114088
|
+
const workspaceLabels = await client.issueLabels({
|
|
114089
|
+
filter: { team: { null: true } }
|
|
114090
|
+
});
|
|
114091
|
+
const allLabels = [...teamLabels.nodes, ...workspaceLabels.nodes];
|
|
114092
|
+
labelIds = [];
|
|
114093
|
+
for (const labelName of options.label) {
|
|
114094
|
+
const label = allLabels.find(
|
|
114095
|
+
(l) => l.name.toLowerCase() === labelName.toLowerCase()
|
|
114096
|
+
);
|
|
114097
|
+
if (!label) {
|
|
114098
|
+
throw new CLIError({
|
|
114099
|
+
userMessage: `Label not found: ${labelName}`,
|
|
114100
|
+
suggestion: `Use \`skill linear labels ${teamKey} --json\` to list available labels.`
|
|
114101
|
+
});
|
|
114102
|
+
}
|
|
114103
|
+
labelIds.push(label.id);
|
|
114104
|
+
}
|
|
114105
|
+
}
|
|
114106
|
+
let projectId;
|
|
114107
|
+
if (options.project) {
|
|
114108
|
+
const projects = await client.projects();
|
|
114109
|
+
const project = projects.nodes.find(
|
|
114110
|
+
(p) => p.id === options.project || p.name.toLowerCase() === options.project.toLowerCase()
|
|
114111
|
+
);
|
|
114112
|
+
if (!project) {
|
|
114113
|
+
throw new CLIError({
|
|
114114
|
+
userMessage: `Project not found: ${options.project}`,
|
|
114115
|
+
suggestion: "Use `skill linear projects --json` to list available projects."
|
|
114116
|
+
});
|
|
114117
|
+
}
|
|
114118
|
+
projectId = project.id;
|
|
114119
|
+
}
|
|
114120
|
+
const payload = await client.createIssue({
|
|
114121
|
+
teamId,
|
|
114122
|
+
title: title.trim(),
|
|
114123
|
+
description: options.description,
|
|
114124
|
+
priority: options.priority ?? 2,
|
|
114125
|
+
assigneeId,
|
|
114126
|
+
labelIds,
|
|
114127
|
+
projectId,
|
|
114128
|
+
estimate: options.estimate,
|
|
114129
|
+
dueDate: options.dueDate
|
|
114130
|
+
});
|
|
114131
|
+
const issue = await payload.issue;
|
|
114132
|
+
if (!issue) {
|
|
114133
|
+
throw new CLIError({
|
|
114134
|
+
userMessage: "Failed to create issue - no issue returned.",
|
|
114135
|
+
exitCode: 1
|
|
114136
|
+
});
|
|
114137
|
+
}
|
|
114138
|
+
const state = await issue.state;
|
|
114139
|
+
const assignee = await issue.assignee;
|
|
114140
|
+
const issueData = {
|
|
114141
|
+
id: issue.id,
|
|
114142
|
+
identifier: issue.identifier,
|
|
114143
|
+
title: issue.title,
|
|
114144
|
+
description: issue.description || null,
|
|
114145
|
+
state: state?.name || null,
|
|
114146
|
+
priority: issue.priority,
|
|
114147
|
+
assignee: assignee ? { id: assignee.id, name: assignee.name, email: assignee.email } : null,
|
|
114148
|
+
team: { key: teamKey },
|
|
114149
|
+
url: issue.url,
|
|
114150
|
+
createdAt: issue.createdAt
|
|
114151
|
+
};
|
|
114152
|
+
if (ctx.format === "json") {
|
|
114153
|
+
ctx.output.data(
|
|
114154
|
+
JSON.stringify(
|
|
114155
|
+
hateoasWrap2({
|
|
114156
|
+
type: "issue-created",
|
|
114157
|
+
command: `skill linear issue ${issue.identifier} --json`,
|
|
114158
|
+
data: issueData,
|
|
114159
|
+
links: issueLinks(issue.identifier, teamKey),
|
|
114160
|
+
actions: issueActions(issue.identifier)
|
|
114161
|
+
}),
|
|
114162
|
+
null,
|
|
114163
|
+
2
|
|
114164
|
+
)
|
|
114165
|
+
);
|
|
114166
|
+
return;
|
|
114167
|
+
}
|
|
114168
|
+
ctx.output.data("");
|
|
114169
|
+
ctx.output.data(`\u2705 Issue created: ${issue.identifier}`);
|
|
114170
|
+
ctx.output.data("\u2500".repeat(50));
|
|
114171
|
+
ctx.output.data(` Title: ${issue.title}`);
|
|
114172
|
+
ctx.output.data(` Team: ${teamKey}`);
|
|
114173
|
+
ctx.output.data(` State: ${state?.name || "Backlog"}`);
|
|
114174
|
+
if (assignee) {
|
|
114175
|
+
ctx.output.data(` Assignee: ${assignee.name}`);
|
|
114176
|
+
}
|
|
114177
|
+
ctx.output.data(` URL: ${issue.url}`);
|
|
114178
|
+
ctx.output.data("");
|
|
114179
|
+
ctx.output.data(" Next steps:");
|
|
114180
|
+
ctx.output.data(` \u2022 View: skill linear issue ${issue.identifier}`);
|
|
114181
|
+
ctx.output.data(
|
|
114182
|
+
` \u2022 Assign: skill linear assign ${issue.identifier} --to <email>`
|
|
114183
|
+
);
|
|
114184
|
+
ctx.output.data(
|
|
114185
|
+
` \u2022 Comment: skill linear comment ${issue.identifier} --body "text"`
|
|
114186
|
+
);
|
|
114187
|
+
ctx.output.data("");
|
|
114188
|
+
} catch (error) {
|
|
114189
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
114190
|
+
userMessage: "Failed to create Linear issue.",
|
|
114191
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
114192
|
+
cause: error
|
|
114193
|
+
});
|
|
114194
|
+
ctx.output.error(formatError(cliError));
|
|
114195
|
+
process.exitCode = cliError.exitCode;
|
|
114196
|
+
}
|
|
114197
|
+
}
|
|
114198
|
+
|
|
114199
|
+
// src/commands/linear/get.ts
|
|
114200
|
+
init_esm_shims();
|
|
114201
|
+
var PRIORITY_LABELS = {
|
|
114202
|
+
0: "Urgent",
|
|
114203
|
+
1: "High",
|
|
114204
|
+
2: "Medium",
|
|
114205
|
+
3: "Low",
|
|
114206
|
+
4: "None"
|
|
114207
|
+
};
|
|
114208
|
+
var PRIORITY_EMOJI = {
|
|
114209
|
+
0: "\u{1F534}",
|
|
114210
|
+
1: "\u{1F7E0}",
|
|
114211
|
+
2: "\u{1F7E1}",
|
|
114212
|
+
3: "\u{1F7E2}",
|
|
114213
|
+
4: "\u26AA"
|
|
114214
|
+
};
|
|
114215
|
+
async function getIssue(ctx, id) {
|
|
114216
|
+
try {
|
|
114217
|
+
const client = getLinearClient();
|
|
114218
|
+
const issue = await client.issue(id);
|
|
114219
|
+
if (!issue) {
|
|
114220
|
+
throw new CLIError({
|
|
114221
|
+
userMessage: `Issue ${id} not found.`,
|
|
114222
|
+
suggestion: "Use `skill linear issues --json` to list available issues.",
|
|
114223
|
+
exitCode: 1
|
|
114224
|
+
});
|
|
114225
|
+
}
|
|
114226
|
+
const [state, assignee, team, labels, project, parent2, cycle] = await Promise.all([
|
|
114227
|
+
issue.state,
|
|
114228
|
+
issue.assignee,
|
|
114229
|
+
issue.team,
|
|
114230
|
+
issue.labels(),
|
|
114231
|
+
issue.project,
|
|
114232
|
+
issue.parent,
|
|
114233
|
+
issue.cycle
|
|
114234
|
+
]);
|
|
114235
|
+
const teamKey = team?.key;
|
|
114236
|
+
const issueData = {
|
|
114237
|
+
id: issue.id,
|
|
114238
|
+
identifier: issue.identifier,
|
|
114239
|
+
title: issue.title,
|
|
114240
|
+
description: issue.description || null,
|
|
114241
|
+
state: state?.name || null,
|
|
114242
|
+
stateType: state?.type || null,
|
|
114243
|
+
priority: issue.priority,
|
|
114244
|
+
priorityLabel: PRIORITY_LABELS[issue.priority] || "Unknown",
|
|
114245
|
+
estimate: issue.estimate || null,
|
|
114246
|
+
assignee: assignee ? { id: assignee.id, name: assignee.name, email: assignee.email } : null,
|
|
114247
|
+
team: team ? { id: team.id, key: team.key, name: team.name } : null,
|
|
114248
|
+
project: project ? { id: project.id, name: project.name, url: project.url } : null,
|
|
114249
|
+
parent: parent2 ? { id: parent2.id, identifier: parent2.identifier, title: parent2.title } : null,
|
|
114250
|
+
cycle: cycle ? { id: cycle.id, name: cycle.name, number: cycle.number } : null,
|
|
114251
|
+
labels: labels.nodes.map((l) => ({ id: l.id, name: l.name })),
|
|
114252
|
+
url: issue.url,
|
|
114253
|
+
createdAt: issue.createdAt,
|
|
114254
|
+
updatedAt: issue.updatedAt,
|
|
114255
|
+
completedAt: issue.completedAt || null,
|
|
114256
|
+
dueDate: issue.dueDate || null
|
|
114257
|
+
};
|
|
114258
|
+
if (ctx.format === "json") {
|
|
114259
|
+
ctx.output.data(
|
|
114260
|
+
JSON.stringify(
|
|
114261
|
+
hateoasWrap2({
|
|
114262
|
+
type: "issue",
|
|
114263
|
+
command: `skill linear issue ${issue.identifier} --json`,
|
|
114264
|
+
data: issueData,
|
|
114265
|
+
links: issueLinks(issue.identifier, teamKey),
|
|
114266
|
+
actions: issueActions(issue.identifier)
|
|
114267
|
+
}),
|
|
114268
|
+
null,
|
|
114269
|
+
2
|
|
114270
|
+
)
|
|
114271
|
+
);
|
|
114272
|
+
return;
|
|
114273
|
+
}
|
|
114274
|
+
ctx.output.data("");
|
|
114275
|
+
ctx.output.data(
|
|
114276
|
+
`${PRIORITY_EMOJI[issue.priority] || "\u26AA"} [${issue.identifier}] ${issue.title}`
|
|
114277
|
+
);
|
|
114278
|
+
ctx.output.data("\u2500".repeat(80));
|
|
114279
|
+
ctx.output.data("");
|
|
114280
|
+
ctx.output.data(` Status: ${state?.name || "Unknown"}`);
|
|
114281
|
+
ctx.output.data(
|
|
114282
|
+
` Priority: ${PRIORITY_LABELS[issue.priority] || "Unknown"} (${issue.priority})`
|
|
114283
|
+
);
|
|
114284
|
+
if (assignee) {
|
|
114285
|
+
ctx.output.data(` Assignee: ${assignee.name} <${assignee.email}>`);
|
|
114286
|
+
} else {
|
|
114287
|
+
ctx.output.data(` Assignee: Unassigned`);
|
|
114288
|
+
}
|
|
114289
|
+
if (team) {
|
|
114290
|
+
ctx.output.data(` Team: ${team.name} (${team.key})`);
|
|
114291
|
+
}
|
|
114292
|
+
if (project) {
|
|
114293
|
+
ctx.output.data(` Project: ${project.name}`);
|
|
114294
|
+
}
|
|
114295
|
+
if (cycle) {
|
|
114296
|
+
ctx.output.data(` Cycle: ${cycle.name}`);
|
|
114297
|
+
}
|
|
114298
|
+
if (parent2) {
|
|
114299
|
+
ctx.output.data(` Parent: ${parent2.identifier} - ${parent2.title}`);
|
|
114300
|
+
}
|
|
114301
|
+
if (labels.nodes.length > 0) {
|
|
114302
|
+
ctx.output.data(
|
|
114303
|
+
` Labels: ${labels.nodes.map((l) => l.name).join(", ")}`
|
|
114304
|
+
);
|
|
114305
|
+
}
|
|
114306
|
+
if (issue.estimate) {
|
|
114307
|
+
ctx.output.data(` Estimate: ${issue.estimate} points`);
|
|
114308
|
+
}
|
|
114309
|
+
if (issue.dueDate) {
|
|
114310
|
+
ctx.output.data(` Due: ${issue.dueDate}`);
|
|
114311
|
+
}
|
|
114312
|
+
ctx.output.data("");
|
|
114313
|
+
ctx.output.data(` URL: ${issue.url}`);
|
|
114314
|
+
ctx.output.data(
|
|
114315
|
+
` Created: ${new Date(issue.createdAt).toLocaleDateString()}`
|
|
114316
|
+
);
|
|
114317
|
+
ctx.output.data(
|
|
114318
|
+
` Updated: ${new Date(issue.updatedAt).toLocaleDateString()}`
|
|
114319
|
+
);
|
|
114320
|
+
if (issue.description) {
|
|
114321
|
+
ctx.output.data("");
|
|
114322
|
+
ctx.output.data(" Description:");
|
|
114323
|
+
ctx.output.data(" " + "-".repeat(40));
|
|
114324
|
+
const descLines = issue.description.split("\n");
|
|
114325
|
+
for (const line of descLines.slice(0, 20)) {
|
|
114326
|
+
ctx.output.data(` ${line}`);
|
|
114327
|
+
}
|
|
114328
|
+
if (descLines.length > 20) {
|
|
114329
|
+
ctx.output.data(` ... (${descLines.length - 20} more lines)`);
|
|
114330
|
+
}
|
|
114331
|
+
}
|
|
114332
|
+
ctx.output.data("");
|
|
114333
|
+
ctx.output.data(" Actions:");
|
|
114334
|
+
ctx.output.data(
|
|
114335
|
+
` \u2022 Comment: skill linear comment ${issue.identifier} --body "text"`
|
|
114336
|
+
);
|
|
114337
|
+
ctx.output.data(
|
|
114338
|
+
` \u2022 Assign: skill linear assign ${issue.identifier} --to <email>`
|
|
114339
|
+
);
|
|
114340
|
+
ctx.output.data(
|
|
114341
|
+
` \u2022 State: skill linear state ${issue.identifier} --state "Done"`
|
|
114342
|
+
);
|
|
114343
|
+
ctx.output.data(` \u2022 Close: skill linear close ${issue.identifier}`);
|
|
114344
|
+
ctx.output.data("");
|
|
114345
|
+
} catch (error) {
|
|
114346
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
114347
|
+
userMessage: "Failed to fetch Linear issue.",
|
|
114348
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
114349
|
+
cause: error
|
|
114350
|
+
});
|
|
114351
|
+
ctx.output.error(formatError(cliError));
|
|
114352
|
+
process.exitCode = cliError.exitCode;
|
|
114353
|
+
}
|
|
114354
|
+
}
|
|
114355
|
+
|
|
114356
|
+
// src/commands/linear/label.ts
|
|
114357
|
+
init_esm_shims();
|
|
114358
|
+
async function modifyLabels(ctx, issueId, options) {
|
|
114359
|
+
const hasAdd = options.add && options.add.length > 0;
|
|
114360
|
+
const hasRemove = options.remove && options.remove.length > 0;
|
|
114361
|
+
if (!hasAdd && !hasRemove) {
|
|
114362
|
+
throw new CLIError({
|
|
114363
|
+
userMessage: "No label changes specified.",
|
|
114364
|
+
suggestion: 'Use --add "Label" or --remove "Label".',
|
|
114365
|
+
exitCode: 1
|
|
114366
|
+
});
|
|
114367
|
+
}
|
|
114368
|
+
try {
|
|
114369
|
+
const client = getLinearClient();
|
|
114370
|
+
const issue = await client.issue(issueId);
|
|
114371
|
+
if (!issue) {
|
|
114372
|
+
throw new CLIError({
|
|
114373
|
+
userMessage: `Issue not found: ${issueId}`,
|
|
114374
|
+
suggestion: "Use `skill linear issues --json` to list available issues.",
|
|
114375
|
+
exitCode: 1
|
|
114376
|
+
});
|
|
114377
|
+
}
|
|
114378
|
+
const team = await issue.team;
|
|
114379
|
+
if (!team) {
|
|
114380
|
+
throw new CLIError({
|
|
114381
|
+
userMessage: "Could not determine team for issue.",
|
|
114382
|
+
exitCode: 1
|
|
114383
|
+
});
|
|
114384
|
+
}
|
|
114385
|
+
const [teamLabels, workspaceLabels] = await Promise.all([
|
|
114386
|
+
client.issueLabels({ filter: { team: { id: { eq: team.id } } } }),
|
|
114387
|
+
client.issueLabels({ filter: { team: { null: true } } })
|
|
114388
|
+
]);
|
|
114389
|
+
const allLabels = [...teamLabels.nodes, ...workspaceLabels.nodes];
|
|
114390
|
+
const currentLabelsConnection = await issue.labels();
|
|
114391
|
+
const currentLabelIds = new Set(
|
|
114392
|
+
currentLabelsConnection.nodes.map((l) => l.id)
|
|
114393
|
+
);
|
|
114394
|
+
const labelsToAdd = [];
|
|
114395
|
+
if (options.add) {
|
|
114396
|
+
for (const labelName of options.add) {
|
|
114397
|
+
const label = allLabels.find(
|
|
114398
|
+
(l) => l.name.toLowerCase() === labelName.toLowerCase()
|
|
114399
|
+
);
|
|
114400
|
+
if (!label) {
|
|
114401
|
+
throw new CLIError({
|
|
114402
|
+
userMessage: `Label not found: ${labelName}`,
|
|
114403
|
+
suggestion: `Use \`skill linear labels ${team.key} --json\` to list available labels.`,
|
|
114404
|
+
exitCode: 1
|
|
114405
|
+
});
|
|
114406
|
+
}
|
|
114407
|
+
if (!currentLabelIds.has(label.id)) {
|
|
114408
|
+
labelsToAdd.push(label.id);
|
|
114409
|
+
currentLabelIds.add(label.id);
|
|
114410
|
+
}
|
|
114411
|
+
}
|
|
114412
|
+
}
|
|
114413
|
+
const labelsToRemove = [];
|
|
114414
|
+
if (options.remove) {
|
|
114415
|
+
for (const labelName of options.remove) {
|
|
114416
|
+
const label = currentLabelsConnection.nodes.find(
|
|
114417
|
+
(l) => l.name.toLowerCase() === labelName.toLowerCase()
|
|
114418
|
+
);
|
|
114419
|
+
if (!label) {
|
|
114420
|
+
continue;
|
|
114421
|
+
}
|
|
114422
|
+
labelsToRemove.push(label.id);
|
|
114423
|
+
currentLabelIds.delete(label.id);
|
|
114424
|
+
}
|
|
114425
|
+
}
|
|
114426
|
+
if (labelsToAdd.length > 0 || labelsToRemove.length > 0) {
|
|
114427
|
+
await client.updateIssue(issue.id, {
|
|
114428
|
+
labelIds: Array.from(currentLabelIds)
|
|
114429
|
+
});
|
|
114430
|
+
}
|
|
114431
|
+
const updatedIssue = await client.issue(issueId);
|
|
114432
|
+
const updatedLabelsConnection = await updatedIssue?.labels();
|
|
114433
|
+
const updatedLabels = updatedLabelsConnection?.nodes || [];
|
|
114434
|
+
const resultData = {
|
|
114435
|
+
issueId: issue.id,
|
|
114436
|
+
issueIdentifier: issue.identifier,
|
|
114437
|
+
added: options.add || [],
|
|
114438
|
+
removed: options.remove || [],
|
|
114439
|
+
currentLabels: updatedLabels.map((l) => ({ id: l.id, name: l.name })),
|
|
114440
|
+
success: true
|
|
114441
|
+
};
|
|
114442
|
+
if (ctx.format === "json") {
|
|
114443
|
+
ctx.output.data(
|
|
114444
|
+
JSON.stringify(
|
|
114445
|
+
hateoasWrap2({
|
|
114446
|
+
type: "label-result",
|
|
114447
|
+
command: `skill linear issue ${issue.identifier} --json`,
|
|
114448
|
+
data: resultData,
|
|
114449
|
+
links: issueLinks(issue.identifier, team.key),
|
|
114450
|
+
actions: issueActions(issue.identifier)
|
|
114451
|
+
}),
|
|
114452
|
+
null,
|
|
114453
|
+
2
|
|
114454
|
+
)
|
|
114455
|
+
);
|
|
114456
|
+
return;
|
|
114457
|
+
}
|
|
114458
|
+
ctx.output.data("");
|
|
114459
|
+
ctx.output.data(`\u2705 Labels updated on ${issue.identifier}`);
|
|
114460
|
+
ctx.output.data("\u2500".repeat(50));
|
|
114461
|
+
if (options.add && options.add.length > 0) {
|
|
114462
|
+
ctx.output.data(` Added: ${options.add.join(", ")}`);
|
|
114463
|
+
}
|
|
114464
|
+
if (options.remove && options.remove.length > 0) {
|
|
114465
|
+
ctx.output.data(` Removed: ${options.remove.join(", ")}`);
|
|
114466
|
+
}
|
|
114467
|
+
ctx.output.data("");
|
|
114468
|
+
ctx.output.data(
|
|
114469
|
+
` Current labels: ${updatedLabels.length > 0 ? updatedLabels.map((l) => l.name).join(", ") : "(none)"}`
|
|
114470
|
+
);
|
|
114471
|
+
ctx.output.data("");
|
|
114472
|
+
} catch (error) {
|
|
114473
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
114474
|
+
userMessage: "Failed to modify labels.",
|
|
114475
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
114476
|
+
cause: error
|
|
114477
|
+
});
|
|
114478
|
+
ctx.output.error(formatError(cliError));
|
|
114479
|
+
process.exitCode = cliError.exitCode;
|
|
114480
|
+
}
|
|
114481
|
+
}
|
|
114482
|
+
|
|
114483
|
+
// src/commands/linear/labels.ts
|
|
114484
|
+
init_esm_shims();
|
|
114485
|
+
async function listLabels(ctx, teamKey) {
|
|
114486
|
+
if (!teamKey || teamKey.trim().length === 0) {
|
|
114487
|
+
throw new CLIError({
|
|
114488
|
+
userMessage: "Team key is required.",
|
|
114489
|
+
suggestion: "Usage: skill linear labels ENG\nUse `skill linear teams` to list teams.",
|
|
114490
|
+
exitCode: 1
|
|
114491
|
+
});
|
|
114492
|
+
}
|
|
114493
|
+
try {
|
|
114494
|
+
const client = getLinearClient();
|
|
114495
|
+
const teams = await client.teams();
|
|
114496
|
+
const team = teams.nodes.find(
|
|
114497
|
+
(t2) => t2.key.toLowerCase() === teamKey.toLowerCase() || t2.name.toLowerCase() === teamKey.toLowerCase()
|
|
114498
|
+
);
|
|
114499
|
+
if (!team) {
|
|
114500
|
+
throw new CLIError({
|
|
114501
|
+
userMessage: `Team not found: ${teamKey}`,
|
|
114502
|
+
suggestion: "Use `skill linear teams --json` to list available teams.",
|
|
114503
|
+
exitCode: 1
|
|
114504
|
+
});
|
|
114505
|
+
}
|
|
114506
|
+
const [teamLabels, workspaceLabels] = await Promise.all([
|
|
114507
|
+
client.issueLabels({ filter: { team: { id: { eq: team.id } } } }),
|
|
114508
|
+
client.issueLabels({ filter: { team: { null: true } } })
|
|
114509
|
+
]);
|
|
114510
|
+
const teamLabelNodes = teamLabels.nodes || [];
|
|
114511
|
+
const workspaceLabelNodes = workspaceLabels.nodes || [];
|
|
114512
|
+
if (ctx.format === "json") {
|
|
114513
|
+
const labelData = {
|
|
114514
|
+
team: { id: team.id, key: team.key, name: team.name },
|
|
114515
|
+
teamLabels: teamLabelNodes.map((l) => ({
|
|
114516
|
+
id: l.id,
|
|
114517
|
+
name: l.name,
|
|
114518
|
+
color: l.color,
|
|
114519
|
+
description: l.description || null
|
|
114520
|
+
})),
|
|
114521
|
+
workspaceLabels: workspaceLabelNodes.map((l) => ({
|
|
114522
|
+
id: l.id,
|
|
114523
|
+
name: l.name,
|
|
114524
|
+
color: l.color,
|
|
114525
|
+
description: l.description || null
|
|
114526
|
+
})),
|
|
114527
|
+
totalCount: teamLabelNodes.length + workspaceLabelNodes.length
|
|
114528
|
+
};
|
|
114529
|
+
ctx.output.data(
|
|
114530
|
+
JSON.stringify(
|
|
114531
|
+
hateoasWrap2({
|
|
114532
|
+
type: "label-list",
|
|
114533
|
+
command: `skill linear labels ${team.key} --json`,
|
|
114534
|
+
data: labelData,
|
|
114535
|
+
links: teamLinks(team.key),
|
|
114536
|
+
actions: [
|
|
114537
|
+
{
|
|
114538
|
+
action: "add-label",
|
|
114539
|
+
command: `skill linear label <issue-id> --add "<label-name>"`,
|
|
114540
|
+
description: "Add a label to an issue"
|
|
114541
|
+
},
|
|
114542
|
+
{
|
|
114543
|
+
action: "remove-label",
|
|
114544
|
+
command: `skill linear label <issue-id> --remove "<label-name>"`,
|
|
114545
|
+
description: "Remove a label from an issue"
|
|
114546
|
+
}
|
|
114547
|
+
]
|
|
114548
|
+
}),
|
|
114549
|
+
null,
|
|
114550
|
+
2
|
|
114551
|
+
)
|
|
114552
|
+
);
|
|
114553
|
+
return;
|
|
114554
|
+
}
|
|
114555
|
+
ctx.output.data("");
|
|
114556
|
+
ctx.output.data(`\u{1F3F7}\uFE0F Labels for ${team.name} (${team.key})`);
|
|
114557
|
+
ctx.output.data("\u2500".repeat(60));
|
|
114558
|
+
if (teamLabelNodes.length > 0) {
|
|
114559
|
+
ctx.output.data("");
|
|
114560
|
+
ctx.output.data(" Team Labels:");
|
|
114561
|
+
for (const label of teamLabelNodes) {
|
|
114562
|
+
ctx.output.data(` \u2022 ${label.name}`);
|
|
114563
|
+
}
|
|
114564
|
+
}
|
|
114565
|
+
if (workspaceLabelNodes.length > 0) {
|
|
114566
|
+
ctx.output.data("");
|
|
114567
|
+
ctx.output.data(" Workspace Labels:");
|
|
114568
|
+
for (const label of workspaceLabelNodes) {
|
|
114569
|
+
ctx.output.data(` \u2022 ${label.name}`);
|
|
114570
|
+
}
|
|
114571
|
+
}
|
|
114572
|
+
if (teamLabelNodes.length === 0 && workspaceLabelNodes.length === 0) {
|
|
114573
|
+
ctx.output.data("");
|
|
114574
|
+
ctx.output.data(" No labels defined.");
|
|
114575
|
+
}
|
|
114576
|
+
ctx.output.data("");
|
|
114577
|
+
ctx.output.data(' Usage: skill linear label ENG-123 --add "Bug"');
|
|
114578
|
+
ctx.output.data("");
|
|
114579
|
+
} catch (error) {
|
|
114580
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
114581
|
+
userMessage: "Failed to list labels.",
|
|
114582
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
114583
|
+
cause: error
|
|
114584
|
+
});
|
|
114585
|
+
ctx.output.error(formatError(cliError));
|
|
114586
|
+
process.exitCode = cliError.exitCode;
|
|
114587
|
+
}
|
|
114588
|
+
}
|
|
114589
|
+
|
|
114590
|
+
// src/commands/linear/link.ts
|
|
114591
|
+
init_esm_shims();
|
|
114592
|
+
async function linkIssues(ctx, issueId, options) {
|
|
114593
|
+
const relationships = [
|
|
114594
|
+
{ type: "blocks", value: options.blocks },
|
|
114595
|
+
{ type: "blocked_by", value: options.blockedBy },
|
|
114596
|
+
{ type: "related", value: options.related },
|
|
114597
|
+
{ type: "duplicate", value: options.duplicate }
|
|
114598
|
+
].filter((r) => r.value);
|
|
114599
|
+
if (relationships.length === 0) {
|
|
114600
|
+
throw new CLIError({
|
|
114601
|
+
userMessage: "No relationship specified.",
|
|
114602
|
+
suggestion: "Use --blocks, --blocked-by, --related, or --duplicate with a target issue ID.",
|
|
114603
|
+
exitCode: 1
|
|
114604
|
+
});
|
|
114605
|
+
}
|
|
114606
|
+
if (relationships.length > 1) {
|
|
114607
|
+
throw new CLIError({
|
|
114608
|
+
userMessage: "Only one relationship type can be specified at a time.",
|
|
114609
|
+
suggestion: "Choose one: --blocks, --blocked-by, --related, or --duplicate.",
|
|
114610
|
+
exitCode: 1
|
|
114611
|
+
});
|
|
114612
|
+
}
|
|
114613
|
+
const relationship = relationships[0];
|
|
114614
|
+
const targetValue = relationship.value;
|
|
114615
|
+
try {
|
|
114616
|
+
const client = getLinearClient();
|
|
114617
|
+
const [issue, targetIssue] = await Promise.all([
|
|
114618
|
+
client.issue(issueId),
|
|
114619
|
+
client.issue(targetValue)
|
|
114620
|
+
]);
|
|
114621
|
+
if (!issue) {
|
|
114622
|
+
throw new CLIError({
|
|
114623
|
+
userMessage: `Issue not found: ${issueId}`,
|
|
114624
|
+
suggestion: "Use `skill linear issues --json` to list available issues.",
|
|
114625
|
+
exitCode: 1
|
|
114626
|
+
});
|
|
114627
|
+
}
|
|
114628
|
+
if (!targetIssue) {
|
|
114629
|
+
throw new CLIError({
|
|
114630
|
+
userMessage: `Target issue not found: ${targetValue}`,
|
|
114631
|
+
suggestion: "Use `skill linear issues --json` to list available issues.",
|
|
114632
|
+
exitCode: 1
|
|
114633
|
+
});
|
|
114634
|
+
}
|
|
114635
|
+
await client.createIssueRelation({
|
|
114636
|
+
issueId: issue.id,
|
|
114637
|
+
relatedIssueId: targetIssue.id,
|
|
114638
|
+
type: relationship.type
|
|
114639
|
+
});
|
|
114640
|
+
const team = await issue.team;
|
|
114641
|
+
const resultData = {
|
|
114642
|
+
issueId: issue.id,
|
|
114643
|
+
issueIdentifier: issue.identifier,
|
|
114644
|
+
targetIssueId: targetIssue.id,
|
|
114645
|
+
targetIdentifier: targetIssue.identifier,
|
|
114646
|
+
relationshipType: relationship.type,
|
|
114647
|
+
success: true
|
|
114648
|
+
};
|
|
114649
|
+
if (ctx.format === "json") {
|
|
114650
|
+
ctx.output.data(
|
|
114651
|
+
JSON.stringify(
|
|
114652
|
+
hateoasWrap2({
|
|
114653
|
+
type: "link-result",
|
|
114654
|
+
command: `skill linear issue ${issue.identifier} --json`,
|
|
114655
|
+
data: resultData,
|
|
114656
|
+
links: [
|
|
114657
|
+
...issueLinks(issue.identifier, team?.key),
|
|
114658
|
+
{
|
|
114659
|
+
rel: "linked-issue",
|
|
114660
|
+
command: `skill linear issue ${targetIssue.identifier} --json`,
|
|
114661
|
+
description: targetIssue.title
|
|
114662
|
+
}
|
|
114663
|
+
],
|
|
114664
|
+
actions: issueActions(issue.identifier)
|
|
114665
|
+
}),
|
|
114666
|
+
null,
|
|
114667
|
+
2
|
|
114668
|
+
)
|
|
114669
|
+
);
|
|
114670
|
+
return;
|
|
114671
|
+
}
|
|
114672
|
+
const relationDesc = {
|
|
114673
|
+
blocks: "blocks",
|
|
114674
|
+
blocked_by: "is blocked by",
|
|
114675
|
+
related: "is related to",
|
|
114676
|
+
duplicate: "is a duplicate of"
|
|
114677
|
+
}[relationship.type];
|
|
114678
|
+
ctx.output.data("");
|
|
114679
|
+
ctx.output.data(`\u{1F517} Link created`);
|
|
114680
|
+
ctx.output.data("\u2500".repeat(50));
|
|
114681
|
+
ctx.output.data(
|
|
114682
|
+
` ${issue.identifier} ${relationDesc} ${targetIssue.identifier}`
|
|
114683
|
+
);
|
|
114684
|
+
ctx.output.data("");
|
|
114685
|
+
ctx.output.data(
|
|
114686
|
+
` View ${issue.identifier}: skill linear issue ${issue.identifier}`
|
|
114687
|
+
);
|
|
114688
|
+
ctx.output.data(
|
|
114689
|
+
` View ${targetIssue.identifier}: skill linear issue ${targetIssue.identifier}`
|
|
114690
|
+
);
|
|
114691
|
+
ctx.output.data("");
|
|
114692
|
+
} catch (error) {
|
|
114693
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
114694
|
+
userMessage: "Failed to link issues.",
|
|
114695
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
114696
|
+
cause: error
|
|
114697
|
+
});
|
|
114698
|
+
ctx.output.error(formatError(cliError));
|
|
114699
|
+
process.exitCode = cliError.exitCode;
|
|
114700
|
+
}
|
|
114701
|
+
}
|
|
114702
|
+
|
|
114703
|
+
// src/commands/linear/list.ts
|
|
114704
|
+
init_esm_shims();
|
|
114705
|
+
var PRIORITY_EMOJI2 = {
|
|
114706
|
+
0: "\u{1F534}",
|
|
114707
|
+
1: "\u{1F7E0}",
|
|
114708
|
+
2: "\u{1F7E1}",
|
|
114709
|
+
3: "\u{1F7E2}",
|
|
114710
|
+
4: "\u26AA"
|
|
114711
|
+
};
|
|
114712
|
+
async function listIssues(ctx, options = {}) {
|
|
114713
|
+
const limit2 = options.limit || 20;
|
|
114714
|
+
try {
|
|
114715
|
+
const client = getLinearClient();
|
|
114716
|
+
const filter4 = {
|
|
114717
|
+
state: {
|
|
114718
|
+
type: {
|
|
114719
|
+
neq: "canceled"
|
|
114720
|
+
}
|
|
114721
|
+
}
|
|
114722
|
+
};
|
|
114723
|
+
let teamKey;
|
|
114724
|
+
if (options.team) {
|
|
114725
|
+
const teams = await client.teams();
|
|
114726
|
+
const team = teams.nodes.find(
|
|
114727
|
+
(t2) => t2.key.toLowerCase() === options.team.toLowerCase() || t2.name.toLowerCase() === options.team.toLowerCase()
|
|
114728
|
+
);
|
|
114729
|
+
if (!team) {
|
|
114730
|
+
throw new CLIError({
|
|
114731
|
+
userMessage: `Team not found: ${options.team}`,
|
|
114732
|
+
suggestion: "Use `skill linear teams --json` to list available teams."
|
|
114733
|
+
});
|
|
114734
|
+
}
|
|
114735
|
+
filter4.team = { id: { eq: team.id } };
|
|
114736
|
+
teamKey = team.key;
|
|
114737
|
+
}
|
|
114738
|
+
if (options.state) {
|
|
114739
|
+
filter4.state = {
|
|
114740
|
+
...filter4.state || {},
|
|
114741
|
+
name: { eqIgnoreCase: options.state }
|
|
114742
|
+
};
|
|
114743
|
+
}
|
|
114744
|
+
if (options.assignee) {
|
|
114745
|
+
if (options.assignee.toLowerCase() === "me") {
|
|
114746
|
+
const viewer = await client.viewer;
|
|
114747
|
+
filter4.assignee = { id: { eq: viewer.id } };
|
|
114748
|
+
} else {
|
|
114749
|
+
const users = await client.users();
|
|
114750
|
+
const user = users.nodes.find(
|
|
114751
|
+
(u) => u.email?.toLowerCase() === options.assignee.toLowerCase() || u.name?.toLowerCase() === options.assignee.toLowerCase()
|
|
114752
|
+
);
|
|
114753
|
+
if (!user) {
|
|
114754
|
+
throw new CLIError({
|
|
114755
|
+
userMessage: `User not found: ${options.assignee}`,
|
|
114756
|
+
suggestion: "Use `skill linear users --json` to list available users."
|
|
114757
|
+
});
|
|
114758
|
+
}
|
|
114759
|
+
filter4.assignee = { id: { eq: user.id } };
|
|
114760
|
+
}
|
|
114761
|
+
}
|
|
114762
|
+
if (options.project) {
|
|
114763
|
+
const projects = await client.projects();
|
|
114764
|
+
const project = projects.nodes.find(
|
|
114765
|
+
(p) => p.id === options.project || p.name.toLowerCase() === options.project.toLowerCase()
|
|
114766
|
+
);
|
|
114767
|
+
if (!project) {
|
|
114768
|
+
throw new CLIError({
|
|
114769
|
+
userMessage: `Project not found: ${options.project}`,
|
|
114770
|
+
suggestion: "Use `skill linear projects --json` to list available projects."
|
|
114771
|
+
});
|
|
114772
|
+
}
|
|
114773
|
+
filter4.project = { id: { eq: project.id } };
|
|
114774
|
+
}
|
|
114775
|
+
if (options.priority !== void 0) {
|
|
114776
|
+
filter4.priority = { eq: options.priority };
|
|
114777
|
+
}
|
|
114778
|
+
const response = await client.issues({
|
|
114779
|
+
first: limit2,
|
|
114780
|
+
filter: filter4
|
|
114781
|
+
});
|
|
114782
|
+
const issues = response.nodes || [];
|
|
114783
|
+
const issuesWithDetails = await Promise.all(
|
|
114784
|
+
issues.map(async (issue) => ({
|
|
114785
|
+
issue,
|
|
114786
|
+
state: await issue.state,
|
|
114787
|
+
assignee: await issue.assignee,
|
|
114788
|
+
team: await issue.team
|
|
114789
|
+
}))
|
|
114790
|
+
);
|
|
114791
|
+
if (ctx.format === "json") {
|
|
114792
|
+
const issueData = issuesWithDetails.map(
|
|
114793
|
+
({ issue, state, assignee, team }) => ({
|
|
114794
|
+
id: issue.id,
|
|
114795
|
+
identifier: issue.identifier,
|
|
114796
|
+
title: issue.title,
|
|
114797
|
+
state: state?.name || null,
|
|
114798
|
+
stateType: state?.type || null,
|
|
114799
|
+
priority: issue.priority,
|
|
114800
|
+
assignee: assignee ? { id: assignee.id, name: assignee.name, email: assignee.email } : null,
|
|
114801
|
+
team: team ? { key: team.key, name: team.name } : null,
|
|
114802
|
+
url: issue.url,
|
|
114803
|
+
createdAt: issue.createdAt,
|
|
114804
|
+
updatedAt: issue.updatedAt
|
|
114805
|
+
})
|
|
114806
|
+
);
|
|
114807
|
+
ctx.output.data(
|
|
114808
|
+
JSON.stringify(
|
|
114809
|
+
hateoasWrap2({
|
|
114810
|
+
type: "issue-list",
|
|
114811
|
+
command: `skill linear issues${teamKey ? ` --team ${teamKey}` : ""} --json`,
|
|
114812
|
+
data: {
|
|
114813
|
+
count: issues.length,
|
|
114814
|
+
issues: issueData
|
|
114815
|
+
},
|
|
114816
|
+
links: issueListLinks(
|
|
114817
|
+
issuesWithDetails.map(({ issue }) => ({
|
|
114818
|
+
identifier: issue.identifier,
|
|
114819
|
+
title: issue.title
|
|
114820
|
+
})),
|
|
114821
|
+
teamKey
|
|
114822
|
+
),
|
|
114823
|
+
actions: issueListActions(teamKey)
|
|
114824
|
+
}),
|
|
114825
|
+
null,
|
|
114826
|
+
2
|
|
114827
|
+
)
|
|
114828
|
+
);
|
|
114829
|
+
return;
|
|
114830
|
+
}
|
|
114831
|
+
const filterDesc = [];
|
|
114832
|
+
if (options.team) filterDesc.push(`team:${options.team}`);
|
|
114833
|
+
if (options.state) filterDesc.push(`state:"${options.state}"`);
|
|
114834
|
+
if (options.assignee) filterDesc.push(`assignee:${options.assignee}`);
|
|
114835
|
+
if (options.project) filterDesc.push(`project:"${options.project}"`);
|
|
114836
|
+
if (options.priority !== void 0)
|
|
114837
|
+
filterDesc.push(`priority:${options.priority}`);
|
|
114838
|
+
ctx.output.data("");
|
|
114839
|
+
ctx.output.data(
|
|
114840
|
+
`\u{1F4CB} Linear Issues (${issues.length})${filterDesc.length > 0 ? ` [${filterDesc.join(", ")}]` : ""}`
|
|
114841
|
+
);
|
|
114842
|
+
ctx.output.data("\u2500".repeat(80));
|
|
114843
|
+
if (issues.length === 0) {
|
|
114844
|
+
ctx.output.data(" No issues found matching filters.");
|
|
114845
|
+
ctx.output.data("");
|
|
114846
|
+
ctx.output.data(" Suggestions:");
|
|
114847
|
+
ctx.output.data(" \u2022 List all issues: skill linear issues");
|
|
114848
|
+
ctx.output.data(' \u2022 Create an issue: skill linear create "Title"');
|
|
114849
|
+
ctx.output.data("");
|
|
114850
|
+
return;
|
|
114851
|
+
}
|
|
114852
|
+
for (const { issue, state, assignee, team } of issuesWithDetails) {
|
|
114853
|
+
const emoji = PRIORITY_EMOJI2[issue.priority] || "\u26AA";
|
|
114854
|
+
const assigneeName = assignee ? `@${assignee.name}` : "";
|
|
114855
|
+
const teamBadge = team ? `[${team.key}]` : "";
|
|
114856
|
+
ctx.output.data("");
|
|
114857
|
+
ctx.output.data(
|
|
114858
|
+
` ${emoji} ${teamBadge} ${issue.identifier}: ${issue.title}`
|
|
114859
|
+
);
|
|
114860
|
+
ctx.output.data(
|
|
114861
|
+
` Status: ${state?.name || "unknown"}${assigneeName ? ` | Assignee: ${assigneeName}` : ""}`
|
|
114862
|
+
);
|
|
114863
|
+
}
|
|
114864
|
+
ctx.output.data("");
|
|
114865
|
+
ctx.output.data(" Use `skill linear issue <ID> --json` for full details.");
|
|
114866
|
+
ctx.output.data("");
|
|
114867
|
+
} catch (error) {
|
|
114868
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
114869
|
+
userMessage: "Failed to list Linear issues.",
|
|
114870
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
114871
|
+
cause: error
|
|
114872
|
+
});
|
|
114873
|
+
ctx.output.error(formatError(cliError));
|
|
114874
|
+
process.exitCode = cliError.exitCode;
|
|
114875
|
+
}
|
|
114876
|
+
}
|
|
114877
|
+
|
|
114878
|
+
// src/commands/linear/my.ts
|
|
114879
|
+
init_esm_shims();
|
|
114880
|
+
var PRIORITY_EMOJI3 = {
|
|
114881
|
+
0: "\u{1F534}",
|
|
114882
|
+
1: "\u{1F7E0}",
|
|
114883
|
+
2: "\u{1F7E1}",
|
|
114884
|
+
3: "\u{1F7E2}",
|
|
114885
|
+
4: "\u26AA"
|
|
114886
|
+
};
|
|
114887
|
+
async function listMyIssues(ctx, options = {}) {
|
|
114888
|
+
const limit2 = options.limit || 20;
|
|
114889
|
+
try {
|
|
114890
|
+
const client = getLinearClient();
|
|
114891
|
+
const viewer = await client.viewer;
|
|
114892
|
+
const filter4 = {
|
|
114893
|
+
assignee: { id: { eq: viewer.id } },
|
|
114894
|
+
state: {
|
|
114895
|
+
type: {
|
|
114896
|
+
nin: ["canceled", "completed"]
|
|
114897
|
+
}
|
|
114898
|
+
}
|
|
114899
|
+
};
|
|
114900
|
+
if (options.state) {
|
|
114901
|
+
filter4.state = {
|
|
114902
|
+
...filter4.state || {},
|
|
114903
|
+
name: { eqIgnoreCase: options.state }
|
|
114904
|
+
};
|
|
114905
|
+
}
|
|
114906
|
+
const response = await client.issues({
|
|
114907
|
+
first: limit2,
|
|
114908
|
+
filter: filter4
|
|
114909
|
+
});
|
|
114910
|
+
const issues = response.nodes || [];
|
|
114911
|
+
const issuesWithDetails = await Promise.all(
|
|
114912
|
+
issues.map(async (issue) => ({
|
|
114913
|
+
issue,
|
|
114914
|
+
state: await issue.state,
|
|
114915
|
+
team: await issue.team
|
|
114916
|
+
}))
|
|
114917
|
+
);
|
|
114918
|
+
if (ctx.format === "json") {
|
|
114919
|
+
const issueData = issuesWithDetails.map(({ issue, state, team }) => ({
|
|
114920
|
+
id: issue.id,
|
|
114921
|
+
identifier: issue.identifier,
|
|
114922
|
+
title: issue.title,
|
|
114923
|
+
state: state?.name || null,
|
|
114924
|
+
stateType: state?.type || null,
|
|
114925
|
+
priority: issue.priority,
|
|
114926
|
+
team: team ? { key: team.key, name: team.name } : null,
|
|
114927
|
+
url: issue.url,
|
|
114928
|
+
createdAt: issue.createdAt,
|
|
114929
|
+
updatedAt: issue.updatedAt,
|
|
114930
|
+
dueDate: issue.dueDate || null
|
|
114931
|
+
}));
|
|
114932
|
+
ctx.output.data(
|
|
114933
|
+
JSON.stringify(
|
|
114934
|
+
hateoasWrap2({
|
|
114935
|
+
type: "my-issues",
|
|
114936
|
+
command: `skill linear my --json`,
|
|
114937
|
+
data: {
|
|
114938
|
+
user: {
|
|
114939
|
+
id: viewer.id,
|
|
114940
|
+
name: viewer.name,
|
|
114941
|
+
email: viewer.email
|
|
114942
|
+
},
|
|
114943
|
+
count: issues.length,
|
|
114944
|
+
issues: issueData
|
|
114945
|
+
},
|
|
114946
|
+
links: issueListLinks(
|
|
114947
|
+
issuesWithDetails.map(({ issue }) => ({
|
|
114948
|
+
identifier: issue.identifier,
|
|
114949
|
+
title: issue.title
|
|
114950
|
+
}))
|
|
114951
|
+
),
|
|
114952
|
+
actions: issueListActions()
|
|
114953
|
+
}),
|
|
114954
|
+
null,
|
|
114955
|
+
2
|
|
114956
|
+
)
|
|
114957
|
+
);
|
|
114958
|
+
return;
|
|
114959
|
+
}
|
|
114960
|
+
ctx.output.data("");
|
|
114961
|
+
ctx.output.data(`\u{1F464} My Issues (${issues.length}) - ${viewer.name}`);
|
|
114962
|
+
ctx.output.data("\u2500".repeat(80));
|
|
114963
|
+
if (issues.length === 0) {
|
|
114964
|
+
ctx.output.data("");
|
|
114965
|
+
ctx.output.data(" No issues assigned to you.");
|
|
114966
|
+
ctx.output.data("");
|
|
114967
|
+
ctx.output.data(
|
|
114968
|
+
' Create one: skill linear create "Title" --assignee me'
|
|
114969
|
+
);
|
|
114970
|
+
ctx.output.data("");
|
|
114971
|
+
return;
|
|
114972
|
+
}
|
|
114973
|
+
const byStateType = /* @__PURE__ */ new Map();
|
|
114974
|
+
for (const item of issuesWithDetails) {
|
|
114975
|
+
const stateType = item.state?.type || "unknown";
|
|
114976
|
+
const existing = byStateType.get(stateType) || [];
|
|
114977
|
+
existing.push(item);
|
|
114978
|
+
byStateType.set(stateType, existing);
|
|
114979
|
+
}
|
|
114980
|
+
const stateOrder = ["started", "unstarted", "backlog", "triage"];
|
|
114981
|
+
for (const stateType of stateOrder) {
|
|
114982
|
+
const items = byStateType.get(stateType);
|
|
114983
|
+
if (!items || items.length === 0) continue;
|
|
114984
|
+
ctx.output.data("");
|
|
114985
|
+
ctx.output.data(` ${stateType.toUpperCase()}:`);
|
|
114986
|
+
for (const { issue, state, team } of items) {
|
|
114987
|
+
const emoji = PRIORITY_EMOJI3[issue.priority] || "\u26AA";
|
|
114988
|
+
const teamBadge = team ? `[${team.key}]` : "";
|
|
114989
|
+
const dueInfo = issue.dueDate ? ` | Due: ${issue.dueDate}` : "";
|
|
114990
|
+
ctx.output.data(
|
|
114991
|
+
` ${emoji} ${teamBadge} ${issue.identifier}: ${issue.title}`
|
|
114992
|
+
);
|
|
114993
|
+
ctx.output.data(` ${state?.name || "unknown"}${dueInfo}`);
|
|
114994
|
+
}
|
|
114995
|
+
}
|
|
114996
|
+
ctx.output.data("");
|
|
114997
|
+
ctx.output.data(" Use `skill linear issue <ID>` for full details.");
|
|
114998
|
+
ctx.output.data("");
|
|
114999
|
+
} catch (error) {
|
|
115000
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
115001
|
+
userMessage: "Failed to list your issues.",
|
|
115002
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
115003
|
+
cause: error
|
|
115004
|
+
});
|
|
115005
|
+
ctx.output.error(formatError(cliError));
|
|
115006
|
+
process.exitCode = cliError.exitCode;
|
|
115007
|
+
}
|
|
115008
|
+
}
|
|
115009
|
+
|
|
115010
|
+
// src/commands/linear/projects.ts
|
|
115011
|
+
init_esm_shims();
|
|
115012
|
+
async function listProjects(ctx, options = {}) {
|
|
115013
|
+
const limit2 = options.limit || 50;
|
|
115014
|
+
try {
|
|
115015
|
+
const client = getLinearClient();
|
|
115016
|
+
const response = await client.projects({
|
|
115017
|
+
first: limit2
|
|
115018
|
+
});
|
|
115019
|
+
const projects = response.nodes || [];
|
|
115020
|
+
if (ctx.format === "json") {
|
|
115021
|
+
ctx.output.data(
|
|
115022
|
+
JSON.stringify(
|
|
115023
|
+
{
|
|
115024
|
+
success: true,
|
|
115025
|
+
count: projects.length,
|
|
115026
|
+
projects: projects.map((project) => ({
|
|
115027
|
+
id: project.id,
|
|
115028
|
+
name: project.name,
|
|
115029
|
+
description: project.description,
|
|
115030
|
+
state: project.state,
|
|
115031
|
+
url: project.url
|
|
115032
|
+
}))
|
|
115033
|
+
},
|
|
115034
|
+
null,
|
|
115035
|
+
2
|
|
115036
|
+
)
|
|
115037
|
+
);
|
|
115038
|
+
return;
|
|
115039
|
+
}
|
|
115040
|
+
ctx.output.data(`
|
|
115041
|
+
\u{1F4C1} Linear Projects (${projects.length}):`);
|
|
115042
|
+
ctx.output.data("-".repeat(80));
|
|
115043
|
+
if (projects.length === 0) {
|
|
115044
|
+
ctx.output.data(" No projects found.");
|
|
115045
|
+
} else {
|
|
115046
|
+
for (const project of projects) {
|
|
115047
|
+
const stateIcon = project.state === "completed" ? "\u2713" : "\u25CF";
|
|
115048
|
+
ctx.output.data(` ${stateIcon} ${project.name}`);
|
|
115049
|
+
ctx.output.data(` ID: ${project.id}`);
|
|
115050
|
+
ctx.output.data(` State: ${project.state}`);
|
|
115051
|
+
if (project.description) {
|
|
115052
|
+
const truncatedDesc = project.description.length > 60 ? project.description.slice(0, 60) + "..." : project.description;
|
|
115053
|
+
ctx.output.data(` Desc: ${truncatedDesc}`);
|
|
115054
|
+
}
|
|
115055
|
+
ctx.output.data("");
|
|
115056
|
+
}
|
|
115057
|
+
}
|
|
115058
|
+
} catch (error) {
|
|
115059
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
115060
|
+
userMessage: "Failed to list Linear projects.",
|
|
115061
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
115062
|
+
cause: error
|
|
115063
|
+
});
|
|
115064
|
+
ctx.output.error(formatError(cliError));
|
|
115065
|
+
process.exitCode = cliError.exitCode;
|
|
115066
|
+
}
|
|
115067
|
+
}
|
|
115068
|
+
|
|
115069
|
+
// src/commands/linear/search.ts
|
|
115070
|
+
init_esm_shims();
|
|
115071
|
+
var PRIORITY_EMOJI4 = {
|
|
115072
|
+
0: "\u{1F534}",
|
|
115073
|
+
1: "\u{1F7E0}",
|
|
115074
|
+
2: "\u{1F7E1}",
|
|
115075
|
+
3: "\u{1F7E2}",
|
|
115076
|
+
4: "\u26AA"
|
|
115077
|
+
};
|
|
115078
|
+
async function searchIssues(ctx, query, options = {}) {
|
|
115079
|
+
if (!query || query.trim().length === 0) {
|
|
115080
|
+
throw new CLIError({
|
|
115081
|
+
userMessage: "Search query is required.",
|
|
115082
|
+
suggestion: 'Usage: skill linear search "your query"',
|
|
115083
|
+
exitCode: 1
|
|
115084
|
+
});
|
|
115085
|
+
}
|
|
115086
|
+
const limit2 = options.limit || 20;
|
|
115087
|
+
try {
|
|
115088
|
+
const client = getLinearClient();
|
|
115089
|
+
const response = await client.searchIssues(query.trim(), {
|
|
115090
|
+
first: limit2
|
|
115091
|
+
});
|
|
115092
|
+
const issues = response.nodes || [];
|
|
115093
|
+
const issuesWithDetails = await Promise.all(
|
|
115094
|
+
issues.map(async (issue) => ({
|
|
115095
|
+
issue,
|
|
115096
|
+
state: await issue.state,
|
|
115097
|
+
assignee: await issue.assignee,
|
|
115098
|
+
team: await issue.team
|
|
115099
|
+
}))
|
|
115100
|
+
);
|
|
115101
|
+
if (ctx.format === "json") {
|
|
115102
|
+
const issueData = issuesWithDetails.map(
|
|
115103
|
+
({ issue, state, assignee, team }) => ({
|
|
115104
|
+
id: issue.id,
|
|
115105
|
+
identifier: issue.identifier,
|
|
115106
|
+
title: issue.title,
|
|
115107
|
+
state: state?.name || null,
|
|
115108
|
+
priority: issue.priority,
|
|
115109
|
+
assignee: assignee ? { id: assignee.id, name: assignee.name, email: assignee.email } : null,
|
|
115110
|
+
team: team ? { key: team.key, name: team.name } : null,
|
|
115111
|
+
url: issue.url
|
|
115112
|
+
})
|
|
115113
|
+
);
|
|
115114
|
+
ctx.output.data(
|
|
115115
|
+
JSON.stringify(
|
|
115116
|
+
hateoasWrap2({
|
|
115117
|
+
type: "search-results",
|
|
115118
|
+
command: `skill linear search "${query}" --json`,
|
|
115119
|
+
data: {
|
|
115120
|
+
query,
|
|
115121
|
+
count: issues.length,
|
|
115122
|
+
issues: issueData
|
|
115123
|
+
},
|
|
115124
|
+
links: issueListLinks(
|
|
115125
|
+
issuesWithDetails.map(({ issue }) => ({
|
|
115126
|
+
identifier: issue.identifier,
|
|
115127
|
+
title: issue.title
|
|
115128
|
+
}))
|
|
115129
|
+
),
|
|
115130
|
+
actions: issueListActions()
|
|
115131
|
+
}),
|
|
115132
|
+
null,
|
|
115133
|
+
2
|
|
115134
|
+
)
|
|
115135
|
+
);
|
|
115136
|
+
return;
|
|
115137
|
+
}
|
|
115138
|
+
ctx.output.data("");
|
|
115139
|
+
ctx.output.data(`\u{1F50D} Search: "${query}" (${issues.length} results)`);
|
|
115140
|
+
ctx.output.data("\u2500".repeat(80));
|
|
115141
|
+
if (issues.length === 0) {
|
|
115142
|
+
ctx.output.data("");
|
|
115143
|
+
ctx.output.data(" No issues found matching your search.");
|
|
115144
|
+
ctx.output.data("");
|
|
115145
|
+
ctx.output.data(" Try:");
|
|
115146
|
+
ctx.output.data(" \u2022 Different keywords");
|
|
115147
|
+
ctx.output.data(" \u2022 skill linear issues (list all)");
|
|
115148
|
+
ctx.output.data(" \u2022 skill linear my (your assigned issues)");
|
|
115149
|
+
ctx.output.data("");
|
|
115150
|
+
return;
|
|
115151
|
+
}
|
|
115152
|
+
for (const { issue, state, assignee, team } of issuesWithDetails) {
|
|
115153
|
+
const emoji = PRIORITY_EMOJI4[issue.priority] || "\u26AA";
|
|
115154
|
+
const teamBadge = team ? `[${team.key}]` : "";
|
|
115155
|
+
ctx.output.data("");
|
|
115156
|
+
ctx.output.data(
|
|
115157
|
+
` ${emoji} ${teamBadge} ${issue.identifier}: ${issue.title}`
|
|
115158
|
+
);
|
|
115159
|
+
ctx.output.data(
|
|
115160
|
+
` Status: ${state?.name || "unknown"}${assignee ? ` | @${assignee.name}` : ""}`
|
|
115161
|
+
);
|
|
115162
|
+
}
|
|
115163
|
+
ctx.output.data("");
|
|
115164
|
+
ctx.output.data(" Use `skill linear issue <ID>` for full details.");
|
|
115165
|
+
ctx.output.data("");
|
|
115166
|
+
} catch (error) {
|
|
115167
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
115168
|
+
userMessage: "Failed to search issues.",
|
|
115169
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
115170
|
+
cause: error
|
|
115171
|
+
});
|
|
115172
|
+
ctx.output.error(formatError(cliError));
|
|
115173
|
+
process.exitCode = cliError.exitCode;
|
|
115174
|
+
}
|
|
115175
|
+
}
|
|
115176
|
+
|
|
115177
|
+
// src/commands/linear/state.ts
|
|
115178
|
+
init_esm_shims();
|
|
115179
|
+
async function changeState(ctx, issueId, options) {
|
|
115180
|
+
if (!options.state || options.state.trim().length === 0) {
|
|
115181
|
+
throw new CLIError({
|
|
115182
|
+
userMessage: "State name is required.",
|
|
115183
|
+
suggestion: 'Usage: skill linear state ENG-123 --state "In Progress"',
|
|
115184
|
+
exitCode: 1
|
|
115185
|
+
});
|
|
115186
|
+
}
|
|
115187
|
+
try {
|
|
115188
|
+
const client = getLinearClient();
|
|
115189
|
+
const issue = await client.issue(issueId);
|
|
115190
|
+
if (!issue) {
|
|
115191
|
+
throw new CLIError({
|
|
115192
|
+
userMessage: `Issue not found: ${issueId}`,
|
|
115193
|
+
suggestion: "Use `skill linear issues --json` to list available issues.",
|
|
115194
|
+
exitCode: 1
|
|
115195
|
+
});
|
|
115196
|
+
}
|
|
115197
|
+
const team = await issue.team;
|
|
115198
|
+
if (!team) {
|
|
115199
|
+
throw new CLIError({
|
|
115200
|
+
userMessage: "Could not determine team for issue.",
|
|
115201
|
+
exitCode: 1
|
|
115202
|
+
});
|
|
115203
|
+
}
|
|
115204
|
+
const states = await team.states();
|
|
115205
|
+
const targetState = states.nodes.find(
|
|
115206
|
+
(s) => s.name.toLowerCase() === options.state.toLowerCase()
|
|
115207
|
+
);
|
|
115208
|
+
if (!targetState) {
|
|
115209
|
+
const availableStates = states.nodes.map((s) => s.name).join(", ");
|
|
115210
|
+
throw new CLIError({
|
|
115211
|
+
userMessage: `State not found: ${options.state}`,
|
|
115212
|
+
suggestion: `Available states for ${team.key}: ${availableStates}`,
|
|
115213
|
+
exitCode: 1
|
|
115214
|
+
});
|
|
115215
|
+
}
|
|
115216
|
+
const currentState = await issue.state;
|
|
115217
|
+
await client.updateIssue(issue.id, {
|
|
115218
|
+
stateId: targetState.id
|
|
115219
|
+
});
|
|
115220
|
+
const resultData = {
|
|
115221
|
+
issueId: issue.id,
|
|
115222
|
+
issueIdentifier: issue.identifier,
|
|
115223
|
+
previousState: currentState?.name || null,
|
|
115224
|
+
newState: targetState.name,
|
|
115225
|
+
stateType: targetState.type,
|
|
115226
|
+
success: true
|
|
115227
|
+
};
|
|
115228
|
+
if (ctx.format === "json") {
|
|
115229
|
+
ctx.output.data(
|
|
115230
|
+
JSON.stringify(
|
|
115231
|
+
hateoasWrap2({
|
|
115232
|
+
type: "state-change-result",
|
|
115233
|
+
command: `skill linear issue ${issue.identifier} --json`,
|
|
115234
|
+
data: resultData,
|
|
115235
|
+
links: issueLinks(issue.identifier, team.key),
|
|
115236
|
+
actions: issueActions(issue.identifier)
|
|
115237
|
+
}),
|
|
115238
|
+
null,
|
|
115239
|
+
2
|
|
115240
|
+
)
|
|
115241
|
+
);
|
|
115242
|
+
return;
|
|
115243
|
+
}
|
|
115244
|
+
ctx.output.data("");
|
|
115245
|
+
ctx.output.data(`\u2705 ${issue.identifier} state changed`);
|
|
115246
|
+
ctx.output.data("\u2500".repeat(50));
|
|
115247
|
+
ctx.output.data(` From: ${currentState?.name || "Unknown"}`);
|
|
115248
|
+
ctx.output.data(` To: ${targetState.name}`);
|
|
115249
|
+
ctx.output.data("");
|
|
115250
|
+
ctx.output.data(` View: skill linear issue ${issue.identifier}`);
|
|
115251
|
+
ctx.output.data("");
|
|
115252
|
+
} catch (error) {
|
|
115253
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
115254
|
+
userMessage: "Failed to change state.",
|
|
115255
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
115256
|
+
cause: error
|
|
115257
|
+
});
|
|
115258
|
+
ctx.output.error(formatError(cliError));
|
|
115259
|
+
process.exitCode = cliError.exitCode;
|
|
115260
|
+
}
|
|
115261
|
+
}
|
|
115262
|
+
|
|
115263
|
+
// src/commands/linear/states.ts
|
|
115264
|
+
init_esm_shims();
|
|
115265
|
+
var STATE_TYPE_EMOJI = {
|
|
115266
|
+
backlog: "\u{1F4CB}",
|
|
115267
|
+
unstarted: "\u26AA",
|
|
115268
|
+
started: "\u{1F535}",
|
|
115269
|
+
completed: "\u2705",
|
|
115270
|
+
canceled: "\u274C",
|
|
115271
|
+
triage: "\u{1F4E5}"
|
|
115272
|
+
};
|
|
115273
|
+
async function listStates(ctx, teamKey) {
|
|
115274
|
+
if (!teamKey || teamKey.trim().length === 0) {
|
|
115275
|
+
throw new CLIError({
|
|
115276
|
+
userMessage: "Team key is required.",
|
|
115277
|
+
suggestion: "Usage: skill linear states ENG\nUse `skill linear teams` to list teams.",
|
|
115278
|
+
exitCode: 1
|
|
115279
|
+
});
|
|
115280
|
+
}
|
|
115281
|
+
try {
|
|
115282
|
+
const client = getLinearClient();
|
|
115283
|
+
const teams = await client.teams();
|
|
115284
|
+
const team = teams.nodes.find(
|
|
115285
|
+
(t2) => t2.key.toLowerCase() === teamKey.toLowerCase() || t2.name.toLowerCase() === teamKey.toLowerCase()
|
|
115286
|
+
);
|
|
115287
|
+
if (!team) {
|
|
115288
|
+
throw new CLIError({
|
|
115289
|
+
userMessage: `Team not found: ${teamKey}`,
|
|
115290
|
+
suggestion: "Use `skill linear teams --json` to list available teams.",
|
|
115291
|
+
exitCode: 1
|
|
115292
|
+
});
|
|
115293
|
+
}
|
|
115294
|
+
const statesConnection = await team.states();
|
|
115295
|
+
const states = statesConnection.nodes || [];
|
|
115296
|
+
const sortedStates = [...states].sort((a, b) => a.position - b.position);
|
|
115297
|
+
if (ctx.format === "json") {
|
|
115298
|
+
const stateData = sortedStates.map((state) => ({
|
|
115299
|
+
id: state.id,
|
|
115300
|
+
name: state.name,
|
|
115301
|
+
type: state.type,
|
|
115302
|
+
color: state.color,
|
|
115303
|
+
position: state.position,
|
|
115304
|
+
description: state.description || null
|
|
115305
|
+
}));
|
|
115306
|
+
ctx.output.data(
|
|
115307
|
+
JSON.stringify(
|
|
115308
|
+
hateoasWrap2({
|
|
115309
|
+
type: "state-list",
|
|
115310
|
+
command: `skill linear states ${team.key} --json`,
|
|
115311
|
+
data: {
|
|
115312
|
+
team: { id: team.id, key: team.key, name: team.name },
|
|
115313
|
+
count: states.length,
|
|
115314
|
+
states: stateData
|
|
115315
|
+
},
|
|
115316
|
+
links: teamLinks(team.key),
|
|
115317
|
+
actions: [
|
|
115318
|
+
{
|
|
115319
|
+
action: "change-issue-state",
|
|
115320
|
+
command: `skill linear state <issue-id> --state "<state-name>"`,
|
|
115321
|
+
description: "Change an issue to this state"
|
|
115322
|
+
}
|
|
115323
|
+
]
|
|
115324
|
+
}),
|
|
115325
|
+
null,
|
|
115326
|
+
2
|
|
115327
|
+
)
|
|
115328
|
+
);
|
|
115329
|
+
return;
|
|
115330
|
+
}
|
|
115331
|
+
ctx.output.data("");
|
|
115332
|
+
ctx.output.data(`\u{1F4CA} Workflow States for ${team.name} (${team.key})`);
|
|
115333
|
+
ctx.output.data("\u2500".repeat(60));
|
|
115334
|
+
const typeOrder = [
|
|
115335
|
+
"triage",
|
|
115336
|
+
"backlog",
|
|
115337
|
+
"unstarted",
|
|
115338
|
+
"started",
|
|
115339
|
+
"completed",
|
|
115340
|
+
"canceled"
|
|
115341
|
+
];
|
|
115342
|
+
const statesByType = /* @__PURE__ */ new Map();
|
|
115343
|
+
for (const state of sortedStates) {
|
|
115344
|
+
const existing = statesByType.get(state.type) || [];
|
|
115345
|
+
existing.push(state);
|
|
115346
|
+
statesByType.set(state.type, existing);
|
|
115347
|
+
}
|
|
115348
|
+
for (const type of typeOrder) {
|
|
115349
|
+
const typeStates = statesByType.get(type);
|
|
115350
|
+
if (!typeStates || typeStates.length === 0) continue;
|
|
115351
|
+
const emoji = STATE_TYPE_EMOJI[type] || "\u{1F4CC}";
|
|
115352
|
+
ctx.output.data("");
|
|
115353
|
+
ctx.output.data(` ${emoji} ${type.toUpperCase()}`);
|
|
115354
|
+
for (const state of typeStates) {
|
|
115355
|
+
ctx.output.data(` \u2022 ${state.name}`);
|
|
115356
|
+
}
|
|
115357
|
+
}
|
|
115358
|
+
ctx.output.data("");
|
|
115359
|
+
ctx.output.data(
|
|
115360
|
+
' Usage: skill linear state ENG-123 --state "In Progress"'
|
|
115361
|
+
);
|
|
115362
|
+
ctx.output.data("");
|
|
115363
|
+
} catch (error) {
|
|
115364
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
115365
|
+
userMessage: "Failed to list states.",
|
|
115366
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
115367
|
+
cause: error
|
|
115368
|
+
});
|
|
115369
|
+
ctx.output.error(formatError(cliError));
|
|
115370
|
+
process.exitCode = cliError.exitCode;
|
|
115371
|
+
}
|
|
115372
|
+
}
|
|
115373
|
+
|
|
115374
|
+
// src/commands/linear/teams.ts
|
|
115375
|
+
init_esm_shims();
|
|
115376
|
+
async function listTeams(ctx, options = {}) {
|
|
115377
|
+
try {
|
|
115378
|
+
const client = getLinearClient();
|
|
115379
|
+
const response = await client.teams();
|
|
115380
|
+
const teams = response.nodes || [];
|
|
115381
|
+
if (ctx.format === "json") {
|
|
115382
|
+
ctx.output.data(
|
|
115383
|
+
JSON.stringify(
|
|
115384
|
+
{
|
|
115385
|
+
success: true,
|
|
115386
|
+
count: teams.length,
|
|
115387
|
+
teams: teams.map((team) => ({
|
|
115388
|
+
id: team.id,
|
|
115389
|
+
key: team.key,
|
|
115390
|
+
name: team.name,
|
|
115391
|
+
description: team.description
|
|
115392
|
+
}))
|
|
115393
|
+
},
|
|
115394
|
+
null,
|
|
115395
|
+
2
|
|
115396
|
+
)
|
|
115397
|
+
);
|
|
115398
|
+
return;
|
|
115399
|
+
}
|
|
115400
|
+
ctx.output.data(`
|
|
115401
|
+
\u{1F465} Linear Teams (${teams.length}):`);
|
|
115402
|
+
ctx.output.data("-".repeat(80));
|
|
115403
|
+
if (teams.length === 0) {
|
|
115404
|
+
ctx.output.data(" No teams found.");
|
|
115405
|
+
} else {
|
|
115406
|
+
for (const team of teams) {
|
|
115407
|
+
ctx.output.data(` ${team.key} - ${team.name}`);
|
|
115408
|
+
ctx.output.data(` ID: ${team.id}`);
|
|
115409
|
+
if (team.description) {
|
|
115410
|
+
ctx.output.data(` Desc: ${team.description}`);
|
|
115411
|
+
}
|
|
115412
|
+
ctx.output.data("");
|
|
115413
|
+
}
|
|
115414
|
+
}
|
|
115415
|
+
} catch (error) {
|
|
115416
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
115417
|
+
userMessage: "Failed to list Linear teams.",
|
|
115418
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
115419
|
+
cause: error
|
|
115420
|
+
});
|
|
115421
|
+
ctx.output.error(formatError(cliError));
|
|
115422
|
+
process.exitCode = cliError.exitCode;
|
|
115423
|
+
}
|
|
115424
|
+
}
|
|
115425
|
+
|
|
115426
|
+
// src/commands/linear/update.ts
|
|
115427
|
+
init_esm_shims();
|
|
115428
|
+
async function updateIssue(ctx, issueId, options) {
|
|
115429
|
+
const hasUpdate = Object.values(options).some((v) => v !== void 0);
|
|
115430
|
+
if (!hasUpdate) {
|
|
115431
|
+
throw new CLIError({
|
|
115432
|
+
userMessage: "No updates specified.",
|
|
115433
|
+
suggestion: "Use --title, --description, --priority, --estimate, --due-date, or --project.",
|
|
115434
|
+
exitCode: 1
|
|
115435
|
+
});
|
|
115436
|
+
}
|
|
115437
|
+
try {
|
|
115438
|
+
const client = getLinearClient();
|
|
115439
|
+
const issue = await client.issue(issueId);
|
|
115440
|
+
if (!issue) {
|
|
115441
|
+
throw new CLIError({
|
|
115442
|
+
userMessage: `Issue not found: ${issueId}`,
|
|
115443
|
+
suggestion: "Use `skill linear issues --json` to list available issues.",
|
|
115444
|
+
exitCode: 1
|
|
115445
|
+
});
|
|
115446
|
+
}
|
|
115447
|
+
const updatePayload = {};
|
|
115448
|
+
if (options.title) {
|
|
115449
|
+
updatePayload.title = options.title;
|
|
115450
|
+
}
|
|
115451
|
+
if (options.description !== void 0) {
|
|
115452
|
+
updatePayload.description = options.description;
|
|
115453
|
+
}
|
|
115454
|
+
if (options.priority !== void 0) {
|
|
115455
|
+
if (options.priority < 0 || options.priority > 4) {
|
|
115456
|
+
throw new CLIError({
|
|
115457
|
+
userMessage: "Invalid priority value.",
|
|
115458
|
+
suggestion: "Priority must be 0-4: 0=Urgent, 1=High, 2=Medium, 3=Low, 4=None",
|
|
115459
|
+
exitCode: 1
|
|
115460
|
+
});
|
|
115461
|
+
}
|
|
115462
|
+
updatePayload.priority = options.priority;
|
|
115463
|
+
}
|
|
115464
|
+
if (options.estimate !== void 0) {
|
|
115465
|
+
updatePayload.estimate = options.estimate;
|
|
115466
|
+
}
|
|
115467
|
+
if (options.dueDate !== void 0) {
|
|
115468
|
+
updatePayload.dueDate = options.dueDate;
|
|
115469
|
+
}
|
|
115470
|
+
if (options.project) {
|
|
115471
|
+
const projects = await client.projects();
|
|
115472
|
+
const project = projects.nodes.find(
|
|
115473
|
+
(p) => p.id === options.project || p.name.toLowerCase() === options.project.toLowerCase()
|
|
115474
|
+
);
|
|
115475
|
+
if (!project) {
|
|
115476
|
+
throw new CLIError({
|
|
115477
|
+
userMessage: `Project not found: ${options.project}`,
|
|
115478
|
+
suggestion: "Use `skill linear projects --json` to list available projects.",
|
|
115479
|
+
exitCode: 1
|
|
115480
|
+
});
|
|
115481
|
+
}
|
|
115482
|
+
updatePayload.projectId = project.id;
|
|
115483
|
+
}
|
|
115484
|
+
await client.updateIssue(issue.id, updatePayload);
|
|
115485
|
+
const updatedIssue = await client.issue(issueId);
|
|
115486
|
+
const [state, assignee, team] = await Promise.all([
|
|
115487
|
+
updatedIssue?.state,
|
|
115488
|
+
updatedIssue?.assignee,
|
|
115489
|
+
updatedIssue?.team
|
|
115490
|
+
]);
|
|
115491
|
+
const changes = Object.keys(options).filter(
|
|
115492
|
+
(k) => options[k] !== void 0
|
|
115493
|
+
);
|
|
115494
|
+
const resultData = {
|
|
115495
|
+
issueId: issue.id,
|
|
115496
|
+
issueIdentifier: issue.identifier,
|
|
115497
|
+
changes,
|
|
115498
|
+
updated: {
|
|
115499
|
+
title: updatedIssue?.title,
|
|
115500
|
+
description: updatedIssue?.description || null,
|
|
115501
|
+
priority: updatedIssue?.priority,
|
|
115502
|
+
estimate: updatedIssue?.estimate || null,
|
|
115503
|
+
dueDate: updatedIssue?.dueDate || null,
|
|
115504
|
+
state: state?.name || null,
|
|
115505
|
+
assignee: assignee ? { id: assignee.id, name: assignee.name } : null
|
|
115506
|
+
},
|
|
115507
|
+
success: true
|
|
115508
|
+
};
|
|
115509
|
+
if (ctx.format === "json") {
|
|
115510
|
+
ctx.output.data(
|
|
115511
|
+
JSON.stringify(
|
|
115512
|
+
hateoasWrap2({
|
|
115513
|
+
type: "update-result",
|
|
115514
|
+
command: `skill linear issue ${issue.identifier} --json`,
|
|
115515
|
+
data: resultData,
|
|
115516
|
+
links: issueLinks(issue.identifier, team?.key),
|
|
115517
|
+
actions: issueActions(issue.identifier)
|
|
115518
|
+
}),
|
|
115519
|
+
null,
|
|
115520
|
+
2
|
|
115521
|
+
)
|
|
115522
|
+
);
|
|
115523
|
+
return;
|
|
115524
|
+
}
|
|
115525
|
+
ctx.output.data("");
|
|
115526
|
+
ctx.output.data(`\u2705 Updated ${issue.identifier}`);
|
|
115527
|
+
ctx.output.data("\u2500".repeat(50));
|
|
115528
|
+
for (const change of changes) {
|
|
115529
|
+
const value = options[change];
|
|
115530
|
+
ctx.output.data(` ${change}: ${value}`);
|
|
115531
|
+
}
|
|
115532
|
+
ctx.output.data("");
|
|
115533
|
+
ctx.output.data(` View: skill linear issue ${issue.identifier}`);
|
|
115534
|
+
ctx.output.data("");
|
|
115535
|
+
} catch (error) {
|
|
115536
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
115537
|
+
userMessage: "Failed to update issue.",
|
|
115538
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
115539
|
+
cause: error
|
|
115540
|
+
});
|
|
115541
|
+
ctx.output.error(formatError(cliError));
|
|
115542
|
+
process.exitCode = cliError.exitCode;
|
|
115543
|
+
}
|
|
115544
|
+
}
|
|
115545
|
+
|
|
115546
|
+
// src/commands/linear/users.ts
|
|
115547
|
+
init_esm_shims();
|
|
115548
|
+
async function listUsers(ctx) {
|
|
115549
|
+
try {
|
|
115550
|
+
const client = getLinearClient();
|
|
115551
|
+
const usersConnection = await client.users();
|
|
115552
|
+
const users = usersConnection.nodes || [];
|
|
115553
|
+
const activeUsers = users.filter((u) => u.active);
|
|
115554
|
+
if (ctx.format === "json") {
|
|
115555
|
+
const userData = activeUsers.map((user) => ({
|
|
115556
|
+
id: user.id,
|
|
115557
|
+
name: user.name,
|
|
115558
|
+
displayName: user.displayName,
|
|
115559
|
+
email: user.email,
|
|
115560
|
+
admin: user.admin,
|
|
115561
|
+
active: user.active,
|
|
115562
|
+
avatarUrl: user.avatarUrl || null
|
|
115563
|
+
}));
|
|
115564
|
+
ctx.output.data(
|
|
115565
|
+
JSON.stringify(
|
|
115566
|
+
hateoasWrap2({
|
|
115567
|
+
type: "user-list",
|
|
115568
|
+
command: `skill linear users --json`,
|
|
115569
|
+
data: {
|
|
115570
|
+
count: activeUsers.length,
|
|
115571
|
+
users: userData
|
|
115572
|
+
},
|
|
115573
|
+
links: userListLinks(
|
|
115574
|
+
activeUsers.map((u) => ({
|
|
115575
|
+
id: u.id,
|
|
115576
|
+
email: u.email || "",
|
|
115577
|
+
name: u.name
|
|
115578
|
+
}))
|
|
115579
|
+
),
|
|
115580
|
+
actions: [
|
|
115581
|
+
{
|
|
115582
|
+
action: "assign-to-user",
|
|
115583
|
+
command: `skill linear assign <issue-id> --to "<email>"`,
|
|
115584
|
+
description: "Assign an issue to this user"
|
|
115585
|
+
},
|
|
115586
|
+
{
|
|
115587
|
+
action: "filter-by-user",
|
|
115588
|
+
command: `skill linear issues --assignee "<email>" --json`,
|
|
115589
|
+
description: "View user's assigned issues"
|
|
115590
|
+
}
|
|
115591
|
+
]
|
|
115592
|
+
}),
|
|
115593
|
+
null,
|
|
115594
|
+
2
|
|
115595
|
+
)
|
|
115596
|
+
);
|
|
115597
|
+
return;
|
|
115598
|
+
}
|
|
115599
|
+
ctx.output.data("");
|
|
115600
|
+
ctx.output.data(`\u{1F465} Workspace Users (${activeUsers.length})`);
|
|
115601
|
+
ctx.output.data("\u2500".repeat(60));
|
|
115602
|
+
for (const user of activeUsers) {
|
|
115603
|
+
const adminBadge = user.admin ? " [Admin]" : "";
|
|
115604
|
+
ctx.output.data("");
|
|
115605
|
+
ctx.output.data(` ${user.name}${adminBadge}`);
|
|
115606
|
+
ctx.output.data(` Email: ${user.email || "(no email)"}`);
|
|
115607
|
+
}
|
|
115608
|
+
ctx.output.data("");
|
|
115609
|
+
ctx.output.data(
|
|
115610
|
+
' Assign: skill linear assign ENG-123 --to "email@example.com"'
|
|
115611
|
+
);
|
|
115612
|
+
ctx.output.data("");
|
|
115613
|
+
} catch (error) {
|
|
115614
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
115615
|
+
userMessage: "Failed to list users.",
|
|
115616
|
+
suggestion: "Verify LINEAR_API_KEY is set correctly.",
|
|
115617
|
+
cause: error
|
|
115618
|
+
});
|
|
115619
|
+
ctx.output.error(formatError(cliError));
|
|
115620
|
+
process.exitCode = cliError.exitCode;
|
|
115621
|
+
}
|
|
115622
|
+
}
|
|
115623
|
+
|
|
115624
|
+
// src/commands/linear/index.ts
|
|
115625
|
+
async function contextFromCommand2(command, options) {
|
|
115626
|
+
const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
|
|
115627
|
+
...command.parent?.opts(),
|
|
115628
|
+
...command.opts()
|
|
115629
|
+
};
|
|
115630
|
+
return createContext({
|
|
115631
|
+
format: options.json ? "json" : opts.format,
|
|
115632
|
+
verbose: opts.verbose,
|
|
115633
|
+
quiet: opts.quiet
|
|
115634
|
+
});
|
|
115635
|
+
}
|
|
115636
|
+
function registerLinearCommands(program3) {
|
|
115637
|
+
const linear = program3.command("linear").description(
|
|
115638
|
+
`Linear issue tracking commands
|
|
115639
|
+
|
|
115640
|
+
Quick start:
|
|
115641
|
+
skill linear my Your assigned issues
|
|
115642
|
+
skill linear issues --team ENG Team's issues
|
|
115643
|
+
skill linear create "Title" Create issue
|
|
115644
|
+
skill linear search "query" Search issues
|
|
115645
|
+
|
|
115646
|
+
All commands support --json for machine-readable output.`
|
|
115647
|
+
);
|
|
115648
|
+
linear.command("issues").description(
|
|
115649
|
+
`List issues with optional filters
|
|
115650
|
+
|
|
115651
|
+
Examples:
|
|
115652
|
+
skill linear issues All recent issues
|
|
115653
|
+
skill linear issues --team ENG Filter by team
|
|
115654
|
+
skill linear issues --state "In Progress" Filter by state
|
|
115655
|
+
skill linear issues --assignee me Your issues
|
|
115656
|
+
skill linear issues --priority 0 Urgent only`
|
|
115657
|
+
).option("--limit <number>", "Maximum results (default: 20)", "20").option("--team <key>", "Filter by team key (e.g., ENG)").option("--state <name>", "Filter by state name").option("--assignee <email>", 'Filter by assignee (or "me")').option("--project <name>", "Filter by project name").option("--priority <0-4>", "Filter by priority (0=urgent)").option("--json", "Output as JSON with HATEOAS links").action(async (options, command) => {
|
|
115658
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115659
|
+
await listIssues(ctx, {
|
|
115660
|
+
limit: parseInt(options.limit || "20", 10),
|
|
115661
|
+
team: options.team,
|
|
115662
|
+
state: options.state,
|
|
115663
|
+
assignee: options.assignee,
|
|
115664
|
+
project: options.project,
|
|
115665
|
+
priority: options.priority !== void 0 ? parseInt(options.priority, 10) : void 0
|
|
115666
|
+
});
|
|
115667
|
+
});
|
|
115668
|
+
linear.command("my").description(
|
|
115669
|
+
`List your assigned issues (excludes completed/canceled)
|
|
115670
|
+
|
|
115671
|
+
Examples:
|
|
115672
|
+
skill linear my All your open issues
|
|
115673
|
+
skill linear my --state "In Progress" Only in-progress
|
|
115674
|
+
skill linear my --limit 5 Just top 5`
|
|
115675
|
+
).option("--limit <number>", "Maximum results (default: 20)", "20").option("--state <name>", "Filter by state name").option("--json", "Output as JSON with HATEOAS links").action(async (options, command) => {
|
|
115676
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115677
|
+
await listMyIssues(ctx, {
|
|
115678
|
+
limit: parseInt(options.limit || "20", 10),
|
|
115679
|
+
state: options.state
|
|
115680
|
+
});
|
|
115681
|
+
});
|
|
115682
|
+
linear.command("search").description(
|
|
115683
|
+
`Search issues by text
|
|
115684
|
+
|
|
115685
|
+
Examples:
|
|
115686
|
+
skill linear search "authentication bug"
|
|
115687
|
+
skill linear search "login" --limit 10
|
|
115688
|
+
skill linear search "error" --json`
|
|
115689
|
+
).argument("<query>", "Search query").option("--limit <number>", "Maximum results (default: 20)", "20").option("--json", "Output as JSON with HATEOAS links").action(async (query, options, command) => {
|
|
115690
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115691
|
+
await searchIssues(ctx, query, {
|
|
115692
|
+
limit: parseInt(options.limit || "20", 10)
|
|
115693
|
+
});
|
|
115694
|
+
});
|
|
115695
|
+
linear.command("issue").description(
|
|
115696
|
+
`Get detailed info about an issue
|
|
115697
|
+
|
|
115698
|
+
Examples:
|
|
115699
|
+
skill linear issue ENG-123
|
|
115700
|
+
skill linear issue ENG-123 --json`
|
|
115701
|
+
).argument("<id>", "Issue identifier (e.g., ENG-123)").option("--json", "Output as JSON with HATEOAS links").action(async (id, options, command) => {
|
|
115702
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115703
|
+
await getIssue(ctx, id);
|
|
115704
|
+
});
|
|
115705
|
+
linear.command("create").description(
|
|
115706
|
+
`Create a new issue
|
|
115707
|
+
|
|
115708
|
+
Examples:
|
|
115709
|
+
skill linear create "Fix login bug"
|
|
115710
|
+
skill linear create "Title" --team ENG --priority 1
|
|
115711
|
+
skill linear create "Task" --assignee me --label "Frontend"
|
|
115712
|
+
|
|
115713
|
+
Priority: 0=Urgent, 1=High, 2=Medium, 3=Low, 4=None`
|
|
115714
|
+
).argument("<title>", "Issue title").option("--description <text>", "Issue description (markdown)").option("--team <key>", "Team key (defaults to first team)").option("--priority <0-4>", "Priority level", "2").option("--assignee <email>", 'Assignee email (or "me")').option(
|
|
115715
|
+
"--label <name>",
|
|
115716
|
+
"Add label (repeatable)",
|
|
115717
|
+
(v, p) => [...p, v],
|
|
115718
|
+
[]
|
|
115719
|
+
).option("--project <name>", "Project name").option("--estimate <points>", "Estimate in points").option("--due-date <YYYY-MM-DD>", "Due date").option("--json", "Output as JSON with HATEOAS links").action(async (title, options, command) => {
|
|
115720
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115721
|
+
await createIssue(ctx, title, {
|
|
115722
|
+
description: options.description,
|
|
115723
|
+
priority: parseInt(options.priority || "2", 10),
|
|
115724
|
+
team: options.team,
|
|
115725
|
+
label: options.label,
|
|
115726
|
+
assignee: options.assignee,
|
|
115727
|
+
project: options.project,
|
|
115728
|
+
estimate: options.estimate ? parseInt(options.estimate, 10) : void 0,
|
|
115729
|
+
dueDate: options.dueDate
|
|
115730
|
+
});
|
|
115731
|
+
});
|
|
115732
|
+
linear.command("update").description(
|
|
115733
|
+
`Update issue properties
|
|
115734
|
+
|
|
115735
|
+
Examples:
|
|
115736
|
+
skill linear update ENG-123 --title "New title"
|
|
115737
|
+
skill linear update ENG-123 --priority 1 --estimate 3
|
|
115738
|
+
skill linear update ENG-123 --due-date 2024-03-15`
|
|
115739
|
+
).argument("<id>", "Issue identifier").option("--title <text>", "New title").option("--description <text>", "New description").option("--priority <0-4>", "New priority").option("--estimate <points>", "New estimate").option("--due-date <YYYY-MM-DD>", "New due date").option("--project <name>", "Move to project").option("--json", "Output as JSON with HATEOAS links").action(async (id, options, command) => {
|
|
115740
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115741
|
+
await updateIssue(ctx, id, {
|
|
115742
|
+
title: options.title,
|
|
115743
|
+
description: options.description,
|
|
115744
|
+
priority: options.priority !== void 0 ? parseInt(options.priority, 10) : void 0,
|
|
115745
|
+
estimate: options.estimate !== void 0 ? parseInt(options.estimate, 10) : void 0,
|
|
115746
|
+
dueDate: options.dueDate,
|
|
115747
|
+
project: options.project
|
|
115748
|
+
});
|
|
115749
|
+
});
|
|
115750
|
+
linear.command("assign").description(
|
|
115751
|
+
`Assign or unassign an issue
|
|
115752
|
+
|
|
115753
|
+
Examples:
|
|
115754
|
+
skill linear assign ENG-123 --to user@example.com
|
|
115755
|
+
skill linear assign ENG-123 --to me
|
|
115756
|
+
skill linear assign ENG-123 --unassign`
|
|
115757
|
+
).argument("<id>", "Issue identifier").option("--to <email>", 'Assign to user email (or "me")').option("--unassign", "Remove assignee").option("--json", "Output as JSON with HATEOAS links").action(async (id, options, command) => {
|
|
115758
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115759
|
+
await assignIssue(ctx, id, {
|
|
115760
|
+
to: options.to,
|
|
115761
|
+
unassign: options.unassign
|
|
115762
|
+
});
|
|
115763
|
+
});
|
|
115764
|
+
linear.command("state").description(
|
|
115765
|
+
`Change issue workflow state
|
|
115766
|
+
|
|
115767
|
+
Examples:
|
|
115768
|
+
skill linear state ENG-123 --state "In Progress"
|
|
115769
|
+
skill linear state ENG-123 --state "Done"
|
|
115770
|
+
|
|
115771
|
+
Use 'skill linear states <team>' to see available states.`
|
|
115772
|
+
).argument("<id>", "Issue identifier").requiredOption("--state <name>", "Target state name").option("--json", "Output as JSON with HATEOAS links").action(async (id, options, command) => {
|
|
115773
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115774
|
+
await changeState(ctx, id, { state: options.state });
|
|
115775
|
+
});
|
|
115776
|
+
linear.command("close").description(
|
|
115777
|
+
`Close an issue (mark as done or canceled)
|
|
115778
|
+
|
|
115779
|
+
Examples:
|
|
115780
|
+
skill linear close ENG-123 Close as done
|
|
115781
|
+
skill linear close ENG-123 --canceled Cancel the issue`
|
|
115782
|
+
).argument("<id>", "Issue identifier").option("--canceled", "Close as canceled instead of done").option("--json", "Output as JSON with HATEOAS links").action(async (id, options, command) => {
|
|
115783
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115784
|
+
await closeIssue(ctx, id, { canceled: options.canceled });
|
|
115785
|
+
});
|
|
115786
|
+
linear.command("label").description(
|
|
115787
|
+
`Add or remove labels from an issue
|
|
115788
|
+
|
|
115789
|
+
Examples:
|
|
115790
|
+
skill linear label ENG-123 --add "Bug"
|
|
115791
|
+
skill linear label ENG-123 --add "Bug" --add "Frontend"
|
|
115792
|
+
skill linear label ENG-123 --remove "WIP"
|
|
115793
|
+
|
|
115794
|
+
Use 'skill linear labels <team>' to see available labels.`
|
|
115795
|
+
).argument("<id>", "Issue identifier").option(
|
|
115796
|
+
"--add <name>",
|
|
115797
|
+
"Add label (repeatable)",
|
|
115798
|
+
(v, p) => [...p, v],
|
|
115799
|
+
[]
|
|
115800
|
+
).option(
|
|
115801
|
+
"--remove <name>",
|
|
115802
|
+
"Remove label (repeatable)",
|
|
115803
|
+
(v, p) => [...p, v],
|
|
115804
|
+
[]
|
|
115805
|
+
).option("--json", "Output as JSON with HATEOAS links").action(async (id, options, command) => {
|
|
115806
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115807
|
+
await modifyLabels(ctx, id, {
|
|
115808
|
+
add: options.add,
|
|
115809
|
+
remove: options.remove
|
|
115810
|
+
});
|
|
115811
|
+
});
|
|
115812
|
+
linear.command("link").description(
|
|
115813
|
+
`Link issues together (dependencies, relations)
|
|
115814
|
+
|
|
115815
|
+
Examples:
|
|
115816
|
+
skill linear link ENG-123 --blocks ENG-456
|
|
115817
|
+
skill linear link ENG-123 --blocked-by ENG-456
|
|
115818
|
+
skill linear link ENG-123 --related ENG-456
|
|
115819
|
+
skill linear link ENG-123 --duplicate ENG-456`
|
|
115820
|
+
).argument("<id>", "Source issue identifier").option("--blocks <id>", "This issue blocks <id>").option("--blocked-by <id>", "This issue is blocked by <id>").option("--related <id>", "Related to <id>").option("--duplicate <id>", "Duplicate of <id>").option("--json", "Output as JSON with HATEOAS links").action(async (id, options, command) => {
|
|
115821
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115822
|
+
await linkIssues(ctx, id, {
|
|
115823
|
+
blocks: options.blocks,
|
|
115824
|
+
blockedBy: options.blockedBy,
|
|
115825
|
+
related: options.related,
|
|
115826
|
+
duplicate: options.duplicate
|
|
115827
|
+
});
|
|
115828
|
+
});
|
|
115829
|
+
linear.command("comment").description(
|
|
115830
|
+
`Add a comment to an issue
|
|
115831
|
+
|
|
115832
|
+
Examples:
|
|
115833
|
+
skill linear comment ENG-123 --body "Great work!"
|
|
115834
|
+
skill linear comment ENG-123 --body "## Update\\n- Item 1\\n- Item 2"`
|
|
115835
|
+
).argument("<id>", "Issue identifier").requiredOption("--body <text>", "Comment text (supports markdown)").option("--json", "Output as JSON with HATEOAS links").action(async (id, options, command) => {
|
|
115836
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115837
|
+
await addComment(ctx, id, { body: options.body });
|
|
115838
|
+
});
|
|
115839
|
+
linear.command("comments").description(
|
|
115840
|
+
`List comments on an issue
|
|
115841
|
+
|
|
115842
|
+
Examples:
|
|
115843
|
+
skill linear comments ENG-123
|
|
115844
|
+
skill linear comments ENG-123 --limit 10 --json`
|
|
115845
|
+
).argument("<id>", "Issue identifier").option("--limit <number>", "Maximum results (default: 50)", "50").option("--json", "Output as JSON with HATEOAS links").action(async (id, options, command) => {
|
|
115846
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115847
|
+
await listComments(ctx, id, {
|
|
115848
|
+
limit: parseInt(options.limit || "50", 10)
|
|
115849
|
+
});
|
|
115850
|
+
});
|
|
115851
|
+
linear.command("teams").description(
|
|
115852
|
+
`List all teams in your workspace
|
|
115853
|
+
|
|
115854
|
+
Examples:
|
|
115855
|
+
skill linear teams
|
|
115856
|
+
skill linear teams --json`
|
|
115857
|
+
).option("--json", "Output as JSON with HATEOAS links").action(async (options, command) => {
|
|
115858
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115859
|
+
await listTeams(ctx);
|
|
115860
|
+
});
|
|
115861
|
+
linear.command("states").description(
|
|
115862
|
+
`List workflow states for a team
|
|
115863
|
+
|
|
115864
|
+
Examples:
|
|
115865
|
+
skill linear states ENG
|
|
115866
|
+
skill linear states "Product" --json`
|
|
115867
|
+
).argument("<team>", "Team key or name").option("--json", "Output as JSON with HATEOAS links").action(async (team, options, command) => {
|
|
115868
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115869
|
+
await listStates(ctx, team);
|
|
115870
|
+
});
|
|
115871
|
+
linear.command("labels").description(
|
|
115872
|
+
`List labels for a team
|
|
115873
|
+
|
|
115874
|
+
Examples:
|
|
115875
|
+
skill linear labels ENG
|
|
115876
|
+
skill linear labels ENG --json`
|
|
115877
|
+
).argument("<team>", "Team key or name").option("--json", "Output as JSON with HATEOAS links").action(async (team, options, command) => {
|
|
115878
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115879
|
+
await listLabels(ctx, team);
|
|
115880
|
+
});
|
|
115881
|
+
linear.command("users").description(
|
|
115882
|
+
`List workspace users
|
|
115883
|
+
|
|
115884
|
+
Examples:
|
|
115885
|
+
skill linear users
|
|
115886
|
+
skill linear users --json`
|
|
115887
|
+
).option("--json", "Output as JSON with HATEOAS links").action(async (options, command) => {
|
|
115888
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115889
|
+
await listUsers(ctx);
|
|
115890
|
+
});
|
|
115891
|
+
linear.command("projects").description(
|
|
115892
|
+
`List all projects
|
|
115893
|
+
|
|
115894
|
+
Examples:
|
|
115895
|
+
skill linear projects
|
|
115896
|
+
skill linear projects --limit 100 --json`
|
|
115897
|
+
).option("--limit <number>", "Maximum results (default: 50)", "50").option("--json", "Output as JSON with HATEOAS links").action(async (options, command) => {
|
|
115898
|
+
const ctx = await contextFromCommand2(command, options);
|
|
115899
|
+
await listProjects(ctx, { limit: parseInt(options.limit || "50", 10) });
|
|
115900
|
+
});
|
|
115901
|
+
}
|
|
115902
|
+
|
|
113469
115903
|
// src/commands/memory/index.ts
|
|
113470
115904
|
init_esm_shims();
|
|
113471
115905
|
|
|
@@ -117139,6 +119573,7 @@ registerDatasetCommands(program2);
|
|
|
117139
119573
|
registerResponseCommands(program2);
|
|
117140
119574
|
registerToolsCommands(program2);
|
|
117141
119575
|
registerMemoryCommands(program2);
|
|
119576
|
+
registerLinearCommands(program2);
|
|
117142
119577
|
registerFaqCommands(program2);
|
|
117143
119578
|
registerDeployCommands(program2);
|
|
117144
119579
|
registerKbCommands(program2);
|