@skillrecordings/cli 0.11.2 → 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 +134 -9
- package/dist/index.js +3392 -237
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -19837,8 +19837,8 @@ var require_snapshot_recorder = __commonJS({
|
|
|
19837
19837
|
"../../node_modules/.bun/undici@7.20.0/node_modules/undici/lib/mock/snapshot-recorder.js"(exports, module) {
|
|
19838
19838
|
"use strict";
|
|
19839
19839
|
init_esm_shims();
|
|
19840
|
-
var { writeFile:
|
|
19841
|
-
var { dirname:
|
|
19840
|
+
var { writeFile: writeFile9, readFile: readFile10, mkdir: mkdir2 } = __require("fs/promises");
|
|
19841
|
+
var { dirname: dirname6, resolve: resolve7 } = __require("path");
|
|
19842
19842
|
var { setTimeout: setTimeout2, clearTimeout: clearTimeout2 } = __require("timers");
|
|
19843
19843
|
var { InvalidArgumentError, UndiciError } = require_errors();
|
|
19844
19844
|
var { hashId, isUrlExcludedFactory, normalizeHeaders, createHeaderFilters } = require_snapshot_utils();
|
|
@@ -20069,12 +20069,12 @@ var require_snapshot_recorder = __commonJS({
|
|
|
20069
20069
|
throw new InvalidArgumentError("Snapshot path is required");
|
|
20070
20070
|
}
|
|
20071
20071
|
const resolvedPath = resolve7(path);
|
|
20072
|
-
await mkdir2(
|
|
20072
|
+
await mkdir2(dirname6(resolvedPath), { recursive: true });
|
|
20073
20073
|
const data2 = Array.from(this.#snapshots.entries()).map(([hash, snapshot]) => ({
|
|
20074
20074
|
hash,
|
|
20075
20075
|
snapshot
|
|
20076
20076
|
}));
|
|
20077
|
-
await
|
|
20077
|
+
await writeFile9(resolvedPath, JSON.stringify(data2, null, 2), { flush: true });
|
|
20078
20078
|
}
|
|
20079
20079
|
/**
|
|
20080
20080
|
* Clears all recorded snapshots
|
|
@@ -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) {
|
|
@@ -74544,6 +74544,63 @@ import { Command as Command4 } from "commander";
|
|
|
74544
74544
|
// src/commands/auth/index.ts
|
|
74545
74545
|
init_esm_shims();
|
|
74546
74546
|
|
|
74547
|
+
// src/core/adaptive-help.ts
|
|
74548
|
+
init_esm_shims();
|
|
74549
|
+
var ABBREVIATED_THRESHOLD = 2;
|
|
74550
|
+
var MINIMAL_THRESHOLD = 5;
|
|
74551
|
+
var ROOT_DESCRIPTIONS = {
|
|
74552
|
+
full: "Skill Recordings support agent CLI \u2014 triage, investigate, and manage customer conversations.\n\n Getting Started:\n 1. skill auth setup Configure 1Password secrets (Front API token, DB, etc.)\n 2. skill auth status Verify your credentials are working\n 3. skill front inbox See what needs attention right now\n\n Common Workflows:\n Triage inbox skill front inbox \u2192 skill front triage\n Investigate ticket skill front conversation <id> --messages\n Bulk cleanup skill front bulk-archive --older-than 30d\n Generate report skill front report --inbox support\n Check deploys skill deploys\n\n For AI Agents (Claude Code, MCP):\n skill mcp Start JSON-RPC server with 9 Front tools\n skill plugin sync Install the Claude Code plugin\n All commands support --json for structured, HATEOAS-enriched output",
|
|
74553
|
+
abbreviated: "Skill Recordings support agent CLI \u2014 triage and investigate support conversations.\n\n Start here:\n skill auth setup Configure 1Password + secrets\n skill front inbox See what needs attention\n skill front triage Auto-categorize conversations\n\n Common:\n skill front conversation <id> --messages\n skill front reply <id>\n skill deploys\n skill mcp\n",
|
|
74554
|
+
minimal: "Skill Recordings support agent CLI. Try: skill front inbox, skill front triage, skill auth status. Use --help for details."
|
|
74555
|
+
};
|
|
74556
|
+
var FRONT_DESCRIPTIONS = {
|
|
74557
|
+
full: "Front conversations, inboxes, tags, archival, and reporting.\n\n Prerequisites:\n FRONT_API_TOKEN must be set. Run: skill auth setup\n\n Start here:\n skill front inbox See unassigned conversations\n skill front inbox support List conversations in a specific inbox\n skill front triage AI-powered categorization of inbox items\n\n Investigate a conversation:\n skill front conversation <id> -m Full conversation with messages\n skill front message <id> Single message details + body\n\n Take action:\n skill front assign <id> Assign to a teammate\n skill front reply <id> Draft a reply (HITL, never auto-sends)\n skill front tag <id> Add a tag\n skill front archive <id> Archive a resolved conversation\n\n Bulk operations:\n skill front bulk-archive Archive old/spam conversations\n skill front report Volume + tag + sender forensics\n\n All commands accept --json for HATEOAS-enriched output with _links and _actions.",
|
|
74558
|
+
abbreviated: "Front API workflows for inbox triage and conversation actions.\n\n Common:\n skill front inbox\n skill front triage\n skill front conversation <id> -m\n skill front reply <id>\n skill front archive <id>\n\n Requires FRONT_API_TOKEN (skill auth setup).",
|
|
74559
|
+
minimal: "Front API commands (inbox, triage, assign, reply, archive). Requires FRONT_API_TOKEN."
|
|
74560
|
+
};
|
|
74561
|
+
var AUTH_DESCRIPTIONS = {
|
|
74562
|
+
full: "Manage CLI authentication and secrets.\n\n First time? Start here:\n skill auth setup Interactive wizard \u2014 connects to 1Password vault,\n configures FRONT_API_TOKEN, DATABASE_URL, and more.\n Requires: `op` CLI installed (brew install 1password-cli)\n\n Check your setup:\n skill auth status Shows which secrets are configured and which are missing\n skill auth whoami Verify your 1Password service account identity\n\n All Skill Recordings employees have access to the Support vault in 1Password.\n The setup wizard handles everything \u2014 no manual token pasting needed.",
|
|
74563
|
+
abbreviated: "Manage CLI authentication and 1Password secrets.\n\n Start:\n skill auth setup\n skill auth status\n skill auth whoami\n\n Requires the 1Password CLI (`op`).",
|
|
74564
|
+
minimal: "Auth and 1Password setup commands."
|
|
74565
|
+
};
|
|
74566
|
+
var INNGEST_DESCRIPTIONS = {
|
|
74567
|
+
full: "Inngest event and workflow commands.\n\n Debug pipeline runs:\n skill inngest runs --status failed --after 1h Recent failures\n skill inngest events --after 12h Recent events\n skill inngest investigate <run-id> Deep-dive a specific run\n\n Requires: INNGEST_SIGNING_KEY, INNGEST_EVENT_KEY in env",
|
|
74568
|
+
abbreviated: "Inngest events and workflow runs.\n\n Common:\n skill inngest runs --status failed --after 1h\n skill inngest events --after 12h\n skill inngest investigate <run-id>\n\n Requires INNGEST_SIGNING_KEY, INNGEST_EVENT_KEY.",
|
|
74569
|
+
minimal: "Inngest events and runs debugging. Requires INNGEST_SIGNING_KEY and INNGEST_EVENT_KEY."
|
|
74570
|
+
};
|
|
74571
|
+
var GROUP_DESCRIPTIONS = {
|
|
74572
|
+
root: ROOT_DESCRIPTIONS,
|
|
74573
|
+
front: FRONT_DESCRIPTIONS,
|
|
74574
|
+
auth: AUTH_DESCRIPTIONS,
|
|
74575
|
+
inngest: INNGEST_DESCRIPTIONS
|
|
74576
|
+
};
|
|
74577
|
+
var getGroupCommandCount = (state, group) => {
|
|
74578
|
+
if (group === "root") return state.totalRuns;
|
|
74579
|
+
const prefix = `${group}.`;
|
|
74580
|
+
let total = 0;
|
|
74581
|
+
for (const [name, entry] of Object.entries(state.commands)) {
|
|
74582
|
+
if (name === group || name.startsWith(prefix)) {
|
|
74583
|
+
total += entry.count;
|
|
74584
|
+
}
|
|
74585
|
+
}
|
|
74586
|
+
return total;
|
|
74587
|
+
};
|
|
74588
|
+
var resolveProficiencyLevel = (state, group) => {
|
|
74589
|
+
if (!state) return "full";
|
|
74590
|
+
const count = getGroupCommandCount(state, group);
|
|
74591
|
+
if (count >= MINIMAL_THRESHOLD) return "minimal";
|
|
74592
|
+
if (count >= ABBREVIATED_THRESHOLD) return "abbreviated";
|
|
74593
|
+
return "full";
|
|
74594
|
+
};
|
|
74595
|
+
var getAdaptiveDescription = (group, state) => {
|
|
74596
|
+
const level = resolveProficiencyLevel(state, group);
|
|
74597
|
+
return GROUP_DESCRIPTIONS[group][level];
|
|
74598
|
+
};
|
|
74599
|
+
var getRootAdaptiveDescription = (state) => getAdaptiveDescription("root", state);
|
|
74600
|
+
var getFrontAdaptiveDescription = (state) => getAdaptiveDescription("front", state);
|
|
74601
|
+
var getAuthAdaptiveDescription = (state) => getAdaptiveDescription("auth", state);
|
|
74602
|
+
var getInngestAdaptiveDescription = (state) => getAdaptiveDescription("inngest", state);
|
|
74603
|
+
|
|
74547
74604
|
// src/core/context.ts
|
|
74548
74605
|
init_esm_shims();
|
|
74549
74606
|
|
|
@@ -74776,7 +74833,7 @@ var SECRET_REFS = {
|
|
|
74776
74833
|
QDRANT_COLLECTION: "op://Support/skill-cli/QDRANT_COLLECTION",
|
|
74777
74834
|
OLLAMA_BASE_URL: "op://Support/skill-cli/OLLAMA_BASE_URL",
|
|
74778
74835
|
EMBEDDING_MODEL: "op://Support/skill-cli/EMBEDDING_MODEL",
|
|
74779
|
-
AGE_SECRET_KEY: "op://Support/skill-cli-age-key/
|
|
74836
|
+
AGE_SECRET_KEY: "op://Support/skill-cli-age-key/private_key"
|
|
74780
74837
|
};
|
|
74781
74838
|
|
|
74782
74839
|
// src/core/secrets.ts
|
|
@@ -75582,8 +75639,8 @@ var buildContext = async (command, json) => {
|
|
|
75582
75639
|
quiet: opts.quiet
|
|
75583
75640
|
});
|
|
75584
75641
|
};
|
|
75585
|
-
function registerAuthCommands(program3) {
|
|
75586
|
-
const auth = program3.command("auth").description(
|
|
75642
|
+
function registerAuthCommands(program3, usageState2) {
|
|
75643
|
+
const auth = program3.command("auth").description(getAuthAdaptiveDescription(usageState2));
|
|
75587
75644
|
auth.command("status").description("Show active auth provider and secret availability").option("--json", "Output as JSON").action(async (options, command) => {
|
|
75588
75645
|
const ctx = await buildContext(command, options.json);
|
|
75589
75646
|
await statusAction(ctx, options);
|
|
@@ -96087,8 +96144,8 @@ async function getTeammate(ctx, id, options) {
|
|
|
96087
96144
|
process.exitCode = cliError.exitCode;
|
|
96088
96145
|
}
|
|
96089
96146
|
}
|
|
96090
|
-
function registerFrontCommands(program3) {
|
|
96091
|
-
const front = program3.command("front").description(
|
|
96147
|
+
function registerFrontCommands(program3, usageState2) {
|
|
96148
|
+
const front = program3.command("front").description(getFrontAdaptiveDescription(usageState2));
|
|
96092
96149
|
front.command("message").description("Get a message by ID (body, author, recipients, attachments)").argument("<id>", "Message ID (e.g., msg_xxx)").option("--json", "Output as JSON").action(
|
|
96093
96150
|
async (id, options, command) => {
|
|
96094
96151
|
const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
|
|
@@ -97377,8 +97434,8 @@ function registerSignalCommand(inngest) {
|
|
|
97377
97434
|
}
|
|
97378
97435
|
|
|
97379
97436
|
// src/commands/inngest/index.ts
|
|
97380
|
-
function registerInngestCommands(program3) {
|
|
97381
|
-
const inngest = program3.command("inngest").description(
|
|
97437
|
+
function registerInngestCommands(program3, usageState2) {
|
|
97438
|
+
const inngest = program3.command("inngest").description(getInngestAdaptiveDescription(usageState2));
|
|
97382
97439
|
registerEventsCommands(inngest);
|
|
97383
97440
|
registerRunsCommands(inngest);
|
|
97384
97441
|
registerSignalCommand(inngest);
|
|
@@ -113409,297 +113466,2731 @@ function registerKbCommands(program3) {
|
|
|
113409
113466
|
});
|
|
113410
113467
|
}
|
|
113411
113468
|
|
|
113412
|
-
// src/commands/
|
|
113469
|
+
// src/commands/linear/index.ts
|
|
113413
113470
|
init_esm_shims();
|
|
113414
113471
|
|
|
113415
|
-
// src/commands/
|
|
113472
|
+
// src/commands/linear/assign.ts
|
|
113416
113473
|
init_esm_shims();
|
|
113417
|
-
|
|
113418
|
-
|
|
113419
|
-
|
|
113420
|
-
|
|
113421
|
-
|
|
113422
|
-
|
|
113423
|
-
|
|
113424
|
-
|
|
113425
|
-
|
|
113426
|
-
|
|
113427
|
-
|
|
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 });
|
|
113428
113488
|
}
|
|
113429
|
-
|
|
113430
|
-
|
|
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
|
+
};
|
|
113431
113500
|
}
|
|
113432
|
-
|
|
113433
|
-
const
|
|
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
|
+
}
|
|
113434
113677
|
try {
|
|
113435
|
-
const
|
|
113436
|
-
const
|
|
113437
|
-
if (
|
|
113678
|
+
const client = getLinearClient();
|
|
113679
|
+
const issue = await client.issue(issueId);
|
|
113680
|
+
if (!issue) {
|
|
113438
113681
|
throw new CLIError({
|
|
113439
|
-
userMessage:
|
|
113440
|
-
suggestion: "
|
|
113682
|
+
userMessage: `Issue not found: ${issueId}`,
|
|
113683
|
+
suggestion: "Use `skill linear issues --json` to list available issues.",
|
|
113684
|
+
exitCode: 1
|
|
113441
113685
|
});
|
|
113442
113686
|
}
|
|
113443
|
-
|
|
113444
|
-
|
|
113445
|
-
|
|
113446
|
-
|
|
113447
|
-
|
|
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
|
+
}
|
|
113448
113709
|
}
|
|
113449
|
-
|
|
113450
|
-
|
|
113451
|
-
limit: limit2,
|
|
113452
|
-
threshold,
|
|
113453
|
-
app_slug: options.app
|
|
113710
|
+
await client.updateIssue(issue.id, {
|
|
113711
|
+
assigneeId: assigneeId || void 0
|
|
113454
113712
|
});
|
|
113455
|
-
|
|
113456
|
-
|
|
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
|
+
);
|
|
113457
113735
|
return;
|
|
113458
113736
|
}
|
|
113459
|
-
|
|
113460
|
-
|
|
113461
|
-
|
|
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}`);
|
|
113462
113742
|
}
|
|
113463
|
-
ctx.output.data(
|
|
113464
|
-
|
|
113465
|
-
|
|
113466
|
-
|
|
113467
|
-
|
|
113468
|
-
|
|
113469
|
-
|
|
113470
|
-
|
|
113471
|
-
|
|
113472
|
-
|
|
113473
|
-
|
|
113474
|
-
|
|
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") {
|
|
113475
113800
|
ctx.output.data(
|
|
113476
|
-
|
|
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
|
+
)
|
|
113477
113814
|
);
|
|
113478
|
-
|
|
113479
|
-
ctx.output.data(
|
|
113480
|
-
pad2("", 36) + " " + pad2("", 8) + " " + pad2("", 6) + " " + pad2("", 8) + ` Tags: ${result.memory.metadata.tags.join(", ")}`
|
|
113481
|
-
);
|
|
113482
|
-
}
|
|
113815
|
+
return;
|
|
113483
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}`);
|
|
113484
113825
|
ctx.output.data("");
|
|
113485
113826
|
} catch (error) {
|
|
113486
|
-
|
|
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;
|
|
113487
113834
|
}
|
|
113488
113835
|
}
|
|
113489
113836
|
|
|
113490
|
-
// src/commands/
|
|
113837
|
+
// src/commands/linear/comment.ts
|
|
113491
113838
|
init_esm_shims();
|
|
113492
|
-
|
|
113493
|
-
|
|
113494
|
-
|
|
113495
|
-
|
|
113496
|
-
|
|
113497
|
-
|
|
113498
|
-
|
|
113499
|
-
|
|
113500
|
-
};
|
|
113501
|
-
async function get2(ctx, id, options) {
|
|
113502
|
-
const outputJson = options.json === true || ctx.format === "json";
|
|
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
|
+
}
|
|
113503
113847
|
try {
|
|
113504
|
-
const
|
|
113505
|
-
|
|
113506
|
-
|
|
113507
|
-
);
|
|
113508
|
-
if (!memory) {
|
|
113848
|
+
const client = getLinearClient();
|
|
113849
|
+
const issue = await client.issue(issueId);
|
|
113850
|
+
if (!issue) {
|
|
113509
113851
|
throw new CLIError({
|
|
113510
|
-
userMessage:
|
|
113511
|
-
suggestion: "
|
|
113852
|
+
userMessage: `Issue not found: ${issueId}`,
|
|
113853
|
+
suggestion: "Use `skill linear issues --json` to list available issues.",
|
|
113854
|
+
exitCode: 1
|
|
113512
113855
|
});
|
|
113513
113856
|
}
|
|
113514
|
-
|
|
113515
|
-
|
|
113516
|
-
|
|
113517
|
-
}
|
|
113518
|
-
const
|
|
113519
|
-
|
|
113520
|
-
|
|
113521
|
-
|
|
113522
|
-
|
|
113523
|
-
|
|
113524
|
-
ctx.output.data(` Source: ${memory.metadata.source}`);
|
|
113525
|
-
ctx.output.data(` Confidence: ${(confidence * 100).toFixed(0)}%`);
|
|
113526
|
-
ctx.output.data(` Created: ${createdAt.toLocaleString()}`);
|
|
113527
|
-
if (lastValidated) {
|
|
113528
|
-
ctx.output.data(` Validated: ${lastValidated.toLocaleString()}`);
|
|
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
|
+
});
|
|
113529
113867
|
}
|
|
113530
|
-
|
|
113531
|
-
|
|
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;
|
|
113532
113896
|
}
|
|
113533
|
-
|
|
113534
|
-
|
|
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}`);
|
|
113535
113908
|
}
|
|
113536
|
-
|
|
113537
|
-
|
|
113538
|
-
`);
|
|
113539
|
-
if (memory.metadata.votes) {
|
|
113540
|
-
const { upvotes, downvotes, citations, success_rate } = memory.metadata.votes;
|
|
113541
|
-
if (upvotes > 0 || downvotes > 0 || citations > 0) {
|
|
113542
|
-
ctx.output.data("\u{1F4CA} Votes:");
|
|
113543
|
-
ctx.output.data(` Upvotes: ${upvotes}`);
|
|
113544
|
-
ctx.output.data(` Downvotes: ${downvotes}`);
|
|
113545
|
-
ctx.output.data(` Citations: ${citations}`);
|
|
113546
|
-
ctx.output.data(
|
|
113547
|
-
` Success Rate: ${(success_rate * 100).toFixed(0)}%
|
|
113548
|
-
`
|
|
113549
|
-
);
|
|
113550
|
-
}
|
|
113909
|
+
if (bodyLines.length > 10) {
|
|
113910
|
+
ctx.output.data(` ... (${bodyLines.length - 10} more lines)`);
|
|
113551
113911
|
}
|
|
113912
|
+
ctx.output.data("");
|
|
113552
113913
|
} catch (error) {
|
|
113553
|
-
|
|
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;
|
|
113554
113921
|
}
|
|
113555
113922
|
}
|
|
113556
113923
|
|
|
113557
|
-
// src/commands/
|
|
113924
|
+
// src/commands/linear/comments.ts
|
|
113558
113925
|
init_esm_shims();
|
|
113559
|
-
|
|
113560
|
-
const
|
|
113561
|
-
userMessage: message,
|
|
113562
|
-
suggestion,
|
|
113563
|
-
cause: error
|
|
113564
|
-
});
|
|
113565
|
-
ctx.output.error(formatError(cliError));
|
|
113566
|
-
process.exitCode = cliError.exitCode;
|
|
113567
|
-
};
|
|
113568
|
-
async function stats3(ctx, options) {
|
|
113569
|
-
const outputJson = options.json === true || ctx.format === "json";
|
|
113926
|
+
async function listComments(ctx, issueId, options = {}) {
|
|
113927
|
+
const limit2 = options.limit || 50;
|
|
113570
113928
|
try {
|
|
113571
|
-
const
|
|
113572
|
-
|
|
113573
|
-
|
|
113574
|
-
|
|
113575
|
-
|
|
113576
|
-
|
|
113577
|
-
|
|
113578
|
-
|
|
113579
|
-
return;
|
|
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
|
+
});
|
|
113580
113937
|
}
|
|
113581
|
-
|
|
113582
|
-
|
|
113583
|
-
|
|
113584
|
-
|
|
113585
|
-
|
|
113586
|
-
|
|
113587
|
-
|
|
113588
|
-
|
|
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
|
+
}));
|
|
113589
113955
|
ctx.output.data(
|
|
113590
|
-
|
|
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
|
+
)
|
|
113591
113980
|
);
|
|
113592
|
-
|
|
113593
|
-
|
|
113594
|
-
|
|
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("");
|
|
113595
113990
|
ctx.output.data(
|
|
113596
|
-
`
|
|
113991
|
+
` Add one: skill linear comment ${issue.identifier} --body "Your comment"`
|
|
113597
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
|
+
}
|
|
113598
114009
|
}
|
|
113599
114010
|
ctx.output.data("");
|
|
114011
|
+
ctx.output.data(
|
|
114012
|
+
` Add comment: skill linear comment ${issue.identifier} --body "text"`
|
|
114013
|
+
);
|
|
114014
|
+
ctx.output.data("");
|
|
113600
114015
|
} catch (error) {
|
|
113601
|
-
|
|
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;
|
|
113602
114023
|
}
|
|
113603
114024
|
}
|
|
113604
|
-
|
|
113605
|
-
|
|
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
|
+
}
|
|
113606
114036
|
try {
|
|
113607
|
-
const
|
|
113608
|
-
|
|
113609
|
-
|
|
113610
|
-
|
|
113611
|
-
|
|
113612
|
-
|
|
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;
|
|
113613
114063
|
}
|
|
113614
|
-
|
|
113615
|
-
|
|
113616
|
-
|
|
113617
|
-
|
|
113618
|
-
|
|
113619
|
-
|
|
113620
|
-
|
|
113621
|
-
|
|
113622
|
-
|
|
113623
|
-
|
|
113624
|
-
|
|
113625
|
-
|
|
113626
|
-
|
|
113627
|
-
|
|
113628
|
-
collection,
|
|
113629
|
-
confidence,
|
|
113630
|
-
age_days: ageDays,
|
|
113631
|
-
content_preview: contentPreview
|
|
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."
|
|
113632
114078
|
});
|
|
113633
114079
|
}
|
|
114080
|
+
assigneeId = user.id;
|
|
113634
114081
|
}
|
|
113635
114082
|
}
|
|
113636
|
-
|
|
113637
|
-
|
|
113638
|
-
|
|
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
|
+
}
|
|
113639
114105
|
}
|
|
113640
|
-
|
|
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") {
|
|
113641
114153
|
ctx.output.data(
|
|
113642
|
-
|
|
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
|
+
)
|
|
113643
114165
|
);
|
|
113644
114166
|
return;
|
|
113645
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}`);
|
|
113646
114181
|
ctx.output.data(
|
|
113647
|
-
`
|
|
113648
|
-
|
|
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"`
|
|
113649
114186
|
);
|
|
113650
|
-
ctx.output.data("\u2500".repeat(80));
|
|
113651
|
-
for (const mem of staleMemories) {
|
|
113652
|
-
ctx.output.data(`
|
|
113653
|
-
${mem.id} [${mem.collection}]`);
|
|
113654
|
-
ctx.output.data(` Confidence: ${(mem.confidence * 100).toFixed(1)}%`);
|
|
113655
|
-
ctx.output.data(` Age: ${mem.age_days.toFixed(1)} days`);
|
|
113656
|
-
ctx.output.data(` Preview: ${mem.content_preview}`);
|
|
113657
|
-
}
|
|
113658
114187
|
ctx.output.data("");
|
|
113659
114188
|
} catch (error) {
|
|
113660
|
-
|
|
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;
|
|
113661
114196
|
}
|
|
113662
114197
|
}
|
|
113663
114198
|
|
|
113664
|
-
// src/commands/
|
|
114199
|
+
// src/commands/linear/get.ts
|
|
113665
114200
|
init_esm_shims();
|
|
113666
|
-
var
|
|
113667
|
-
|
|
113668
|
-
|
|
113669
|
-
|
|
113670
|
-
|
|
113671
|
-
|
|
113672
|
-
ctx.output.error(formatError(cliError));
|
|
113673
|
-
process.exitCode = cliError.exitCode;
|
|
114201
|
+
var PRIORITY_LABELS = {
|
|
114202
|
+
0: "Urgent",
|
|
114203
|
+
1: "High",
|
|
114204
|
+
2: "Medium",
|
|
114205
|
+
3: "Low",
|
|
114206
|
+
4: "None"
|
|
113674
114207
|
};
|
|
113675
|
-
|
|
113676
|
-
|
|
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) {
|
|
113677
114216
|
try {
|
|
113678
|
-
const
|
|
113679
|
-
|
|
113680
|
-
|
|
113681
|
-
|
|
113682
|
-
|
|
113683
|
-
|
|
113684
|
-
|
|
113685
|
-
|
|
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
|
+
);
|
|
113686
114272
|
return;
|
|
113687
114273
|
}
|
|
113688
|
-
ctx.output.data(
|
|
113689
|
-
|
|
113690
|
-
|
|
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`);
|
|
113691
114288
|
}
|
|
113692
|
-
if (
|
|
113693
|
-
ctx.output.data(`
|
|
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}`);
|
|
113694
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("");
|
|
113695
114345
|
} catch (error) {
|
|
113696
|
-
|
|
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;
|
|
113697
114353
|
}
|
|
113698
114354
|
}
|
|
113699
114355
|
|
|
113700
|
-
// src/commands/
|
|
114356
|
+
// src/commands/linear/label.ts
|
|
113701
114357
|
init_esm_shims();
|
|
113702
|
-
|
|
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
|
+
|
|
115903
|
+
// src/commands/memory/index.ts
|
|
115904
|
+
init_esm_shims();
|
|
115905
|
+
|
|
115906
|
+
// src/commands/memory/find.ts
|
|
115907
|
+
init_esm_shims();
|
|
115908
|
+
var handleMemoryError = (ctx, error, message, suggestion = "Verify memory service configuration and try again.") => {
|
|
115909
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
115910
|
+
userMessage: message,
|
|
115911
|
+
suggestion,
|
|
115912
|
+
cause: error
|
|
115913
|
+
});
|
|
115914
|
+
ctx.output.error(formatError(cliError));
|
|
115915
|
+
process.exitCode = cliError.exitCode;
|
|
115916
|
+
};
|
|
115917
|
+
function pad2(str2, width) {
|
|
115918
|
+
return str2.padEnd(width).slice(0, width);
|
|
115919
|
+
}
|
|
115920
|
+
function formatConfidence(confidence) {
|
|
115921
|
+
return `${(confidence * 100).toFixed(0)}%`;
|
|
115922
|
+
}
|
|
115923
|
+
async function find5(ctx, query, options) {
|
|
115924
|
+
const outputJson = options.json === true || ctx.format === "json";
|
|
115925
|
+
try {
|
|
115926
|
+
const limit2 = options.limit ? parseInt(options.limit, 10) : 10;
|
|
115927
|
+
const threshold = options.minConfidence ? parseFloat(options.minConfidence) : 0.5;
|
|
115928
|
+
if (limit2 < 1 || limit2 > 100 || Number.isNaN(limit2)) {
|
|
115929
|
+
throw new CLIError({
|
|
115930
|
+
userMessage: "--limit must be between 1 and 100.",
|
|
115931
|
+
suggestion: "Choose a value between 1 and 100 (default: 10)."
|
|
115932
|
+
});
|
|
115933
|
+
}
|
|
115934
|
+
if (threshold < 0 || threshold > 1 || Number.isNaN(threshold)) {
|
|
115935
|
+
throw new CLIError({
|
|
115936
|
+
userMessage: "--min-confidence must be between 0 and 1.",
|
|
115937
|
+
suggestion: "Choose a value between 0 and 1 (default: 0.5)."
|
|
115938
|
+
});
|
|
115939
|
+
}
|
|
115940
|
+
const results = await MemoryService.find(query, {
|
|
115941
|
+
collection: options.collection || "learnings",
|
|
115942
|
+
limit: limit2,
|
|
115943
|
+
threshold,
|
|
115944
|
+
app_slug: options.app
|
|
115945
|
+
});
|
|
115946
|
+
if (outputJson) {
|
|
115947
|
+
ctx.output.data(results);
|
|
115948
|
+
return;
|
|
115949
|
+
}
|
|
115950
|
+
if (results.length === 0) {
|
|
115951
|
+
ctx.output.data("No memories found.");
|
|
115952
|
+
return;
|
|
115953
|
+
}
|
|
115954
|
+
ctx.output.data(`
|
|
115955
|
+
Found ${results.length} memories:
|
|
115956
|
+
`);
|
|
115957
|
+
ctx.output.data(
|
|
115958
|
+
pad2("ID", 36) + " " + pad2("SCORE", 8) + " " + pad2("CONF", 6) + " " + pad2("AGE", 8) + " CONTENT"
|
|
115959
|
+
);
|
|
115960
|
+
ctx.output.data("-".repeat(100));
|
|
115961
|
+
for (const result of results) {
|
|
115962
|
+
const confidence = calculateConfidence(result.memory);
|
|
115963
|
+
const ageDays = Math.floor(result.age_days);
|
|
115964
|
+
const ageStr = ageDays === 0 ? "today" : ageDays === 1 ? "1 day" : `${ageDays} days`;
|
|
115965
|
+
const contentPreview = result.memory.content.length > 40 ? result.memory.content.slice(0, 37) + "..." : result.memory.content;
|
|
115966
|
+
ctx.output.data(
|
|
115967
|
+
pad2(result.memory.id, 36) + " " + pad2(result.score.toFixed(2), 8) + " " + pad2(formatConfidence(confidence), 6) + " " + pad2(ageStr, 8) + " " + contentPreview
|
|
115968
|
+
);
|
|
115969
|
+
if (result.memory.metadata.tags && result.memory.metadata.tags.length > 0) {
|
|
115970
|
+
ctx.output.data(
|
|
115971
|
+
pad2("", 36) + " " + pad2("", 8) + " " + pad2("", 6) + " " + pad2("", 8) + ` Tags: ${result.memory.metadata.tags.join(", ")}`
|
|
115972
|
+
);
|
|
115973
|
+
}
|
|
115974
|
+
}
|
|
115975
|
+
ctx.output.data("");
|
|
115976
|
+
} catch (error) {
|
|
115977
|
+
handleMemoryError(ctx, error, "Failed to search memories.");
|
|
115978
|
+
}
|
|
115979
|
+
}
|
|
115980
|
+
|
|
115981
|
+
// src/commands/memory/get.ts
|
|
115982
|
+
init_esm_shims();
|
|
115983
|
+
var handleMemoryError2 = (ctx, error, message, suggestion = "Verify memory service configuration and try again.") => {
|
|
115984
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
115985
|
+
userMessage: message,
|
|
115986
|
+
suggestion,
|
|
115987
|
+
cause: error
|
|
115988
|
+
});
|
|
115989
|
+
ctx.output.error(formatError(cliError));
|
|
115990
|
+
process.exitCode = cliError.exitCode;
|
|
115991
|
+
};
|
|
115992
|
+
async function get2(ctx, id, options) {
|
|
115993
|
+
const outputJson = options.json === true || ctx.format === "json";
|
|
115994
|
+
try {
|
|
115995
|
+
const memory = await MemoryService.get(
|
|
115996
|
+
id,
|
|
115997
|
+
options.collection || "learnings"
|
|
115998
|
+
);
|
|
115999
|
+
if (!memory) {
|
|
116000
|
+
throw new CLIError({
|
|
116001
|
+
userMessage: "Memory not found.",
|
|
116002
|
+
suggestion: "Verify the memory ID and collection."
|
|
116003
|
+
});
|
|
116004
|
+
}
|
|
116005
|
+
if (outputJson) {
|
|
116006
|
+
ctx.output.data(memory);
|
|
116007
|
+
return;
|
|
116008
|
+
}
|
|
116009
|
+
const confidence = calculateConfidence(memory);
|
|
116010
|
+
const createdAt = new Date(memory.metadata.created_at);
|
|
116011
|
+
const lastValidated = memory.metadata.last_validated_at ? new Date(memory.metadata.last_validated_at) : null;
|
|
116012
|
+
ctx.output.data("\n\u{1F4CB} Memory Details:");
|
|
116013
|
+
ctx.output.data(` ID: ${memory.id}`);
|
|
116014
|
+
ctx.output.data(` Collection: ${memory.metadata.collection}`);
|
|
116015
|
+
ctx.output.data(` Source: ${memory.metadata.source}`);
|
|
116016
|
+
ctx.output.data(` Confidence: ${(confidence * 100).toFixed(0)}%`);
|
|
116017
|
+
ctx.output.data(` Created: ${createdAt.toLocaleString()}`);
|
|
116018
|
+
if (lastValidated) {
|
|
116019
|
+
ctx.output.data(` Validated: ${lastValidated.toLocaleString()}`);
|
|
116020
|
+
}
|
|
116021
|
+
if (memory.metadata.app_slug) {
|
|
116022
|
+
ctx.output.data(` App: ${memory.metadata.app_slug}`);
|
|
116023
|
+
}
|
|
116024
|
+
if (memory.metadata.tags && memory.metadata.tags.length > 0) {
|
|
116025
|
+
ctx.output.data(` Tags: ${memory.metadata.tags.join(", ")}`);
|
|
116026
|
+
}
|
|
116027
|
+
ctx.output.data("\n\u{1F4DD} Content:");
|
|
116028
|
+
ctx.output.data(` ${memory.content}
|
|
116029
|
+
`);
|
|
116030
|
+
if (memory.metadata.votes) {
|
|
116031
|
+
const { upvotes, downvotes, citations, success_rate } = memory.metadata.votes;
|
|
116032
|
+
if (upvotes > 0 || downvotes > 0 || citations > 0) {
|
|
116033
|
+
ctx.output.data("\u{1F4CA} Votes:");
|
|
116034
|
+
ctx.output.data(` Upvotes: ${upvotes}`);
|
|
116035
|
+
ctx.output.data(` Downvotes: ${downvotes}`);
|
|
116036
|
+
ctx.output.data(` Citations: ${citations}`);
|
|
116037
|
+
ctx.output.data(
|
|
116038
|
+
` Success Rate: ${(success_rate * 100).toFixed(0)}%
|
|
116039
|
+
`
|
|
116040
|
+
);
|
|
116041
|
+
}
|
|
116042
|
+
}
|
|
116043
|
+
} catch (error) {
|
|
116044
|
+
handleMemoryError2(ctx, error, "Failed to fetch memory.");
|
|
116045
|
+
}
|
|
116046
|
+
}
|
|
116047
|
+
|
|
116048
|
+
// src/commands/memory/stats.ts
|
|
116049
|
+
init_esm_shims();
|
|
116050
|
+
var handleMemoryError3 = (ctx, error, message, suggestion = "Verify memory service configuration and try again.") => {
|
|
116051
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
116052
|
+
userMessage: message,
|
|
116053
|
+
suggestion,
|
|
116054
|
+
cause: error
|
|
116055
|
+
});
|
|
116056
|
+
ctx.output.error(formatError(cliError));
|
|
116057
|
+
process.exitCode = cliError.exitCode;
|
|
116058
|
+
};
|
|
116059
|
+
async function stats3(ctx, options) {
|
|
116060
|
+
const outputJson = options.json === true || ctx.format === "json";
|
|
116061
|
+
try {
|
|
116062
|
+
const statsResult = await VotingService.stats(options.collection);
|
|
116063
|
+
if (outputJson) {
|
|
116064
|
+
ctx.output.data(statsResult);
|
|
116065
|
+
return;
|
|
116066
|
+
}
|
|
116067
|
+
const collections = Object.keys(statsResult);
|
|
116068
|
+
if (collections.length === 0) {
|
|
116069
|
+
ctx.output.data("No memories found");
|
|
116070
|
+
return;
|
|
116071
|
+
}
|
|
116072
|
+
ctx.output.data("\nMemory Statistics");
|
|
116073
|
+
ctx.output.data("\u2500".repeat(60));
|
|
116074
|
+
for (const collection of collections) {
|
|
116075
|
+
const stats4 = statsResult[collection];
|
|
116076
|
+
if (!stats4) continue;
|
|
116077
|
+
ctx.output.data(`
|
|
116078
|
+
${collection}:`);
|
|
116079
|
+
ctx.output.data(` Total memories: ${stats4.count}`);
|
|
116080
|
+
ctx.output.data(
|
|
116081
|
+
` Avg confidence: ${(stats4.avg_confidence * 100).toFixed(1)}%`
|
|
116082
|
+
);
|
|
116083
|
+
ctx.output.data(` Upvotes: ${stats4.total_upvotes}`);
|
|
116084
|
+
ctx.output.data(` Downvotes: ${stats4.total_downvotes}`);
|
|
116085
|
+
ctx.output.data(` Citations: ${stats4.total_citations}`);
|
|
116086
|
+
ctx.output.data(
|
|
116087
|
+
` Avg success rate: ${(stats4.avg_success_rate * 100).toFixed(1)}%`
|
|
116088
|
+
);
|
|
116089
|
+
}
|
|
116090
|
+
ctx.output.data("");
|
|
116091
|
+
} catch (error) {
|
|
116092
|
+
handleMemoryError3(ctx, error, "Failed to fetch memory statistics.");
|
|
116093
|
+
}
|
|
116094
|
+
}
|
|
116095
|
+
async function stale(ctx, options) {
|
|
116096
|
+
const outputJson = options.json === true || ctx.format === "json";
|
|
116097
|
+
try {
|
|
116098
|
+
const threshold = options.threshold ?? 0.25;
|
|
116099
|
+
if (threshold < 0 || threshold > 1 || Number.isNaN(threshold)) {
|
|
116100
|
+
throw new CLIError({
|
|
116101
|
+
userMessage: "--threshold must be between 0 and 1.",
|
|
116102
|
+
suggestion: "Choose a value between 0 and 1 (default: 0.25)."
|
|
116103
|
+
});
|
|
116104
|
+
}
|
|
116105
|
+
const collections = options.collection ? [options.collection] : await VotingService._listCollections();
|
|
116106
|
+
const staleMemories = [];
|
|
116107
|
+
for (const collection of collections) {
|
|
116108
|
+
const memories = await VotingService._fetchAllMemories(collection);
|
|
116109
|
+
for (const memory of memories) {
|
|
116110
|
+
const confidence = calculateConfidence(memory);
|
|
116111
|
+
if (confidence < threshold) {
|
|
116112
|
+
const createdAt = new Date(memory.metadata.created_at);
|
|
116113
|
+
const lastValidatedAt = memory.metadata.last_validated_at ? new Date(memory.metadata.last_validated_at) : void 0;
|
|
116114
|
+
const referenceDate = lastValidatedAt || createdAt;
|
|
116115
|
+
const ageDays = (Date.now() - referenceDate.getTime()) / (24 * 60 * 60 * 1e3);
|
|
116116
|
+
const contentPreview = memory.content.length > 60 ? memory.content.slice(0, 57) + "..." : memory.content;
|
|
116117
|
+
staleMemories.push({
|
|
116118
|
+
id: memory.id,
|
|
116119
|
+
collection,
|
|
116120
|
+
confidence,
|
|
116121
|
+
age_days: ageDays,
|
|
116122
|
+
content_preview: contentPreview
|
|
116123
|
+
});
|
|
116124
|
+
}
|
|
116125
|
+
}
|
|
116126
|
+
}
|
|
116127
|
+
if (outputJson) {
|
|
116128
|
+
ctx.output.data(staleMemories);
|
|
116129
|
+
return;
|
|
116130
|
+
}
|
|
116131
|
+
if (staleMemories.length === 0) {
|
|
116132
|
+
ctx.output.data(
|
|
116133
|
+
`No stale memories found (threshold: ${(threshold * 100).toFixed(0)}%)`
|
|
116134
|
+
);
|
|
116135
|
+
return;
|
|
116136
|
+
}
|
|
116137
|
+
ctx.output.data(
|
|
116138
|
+
`
|
|
116139
|
+
Stale Memories (confidence < ${(threshold * 100).toFixed(0)}%)`
|
|
116140
|
+
);
|
|
116141
|
+
ctx.output.data("\u2500".repeat(80));
|
|
116142
|
+
for (const mem of staleMemories) {
|
|
116143
|
+
ctx.output.data(`
|
|
116144
|
+
${mem.id} [${mem.collection}]`);
|
|
116145
|
+
ctx.output.data(` Confidence: ${(mem.confidence * 100).toFixed(1)}%`);
|
|
116146
|
+
ctx.output.data(` Age: ${mem.age_days.toFixed(1)} days`);
|
|
116147
|
+
ctx.output.data(` Preview: ${mem.content_preview}`);
|
|
116148
|
+
}
|
|
116149
|
+
ctx.output.data("");
|
|
116150
|
+
} catch (error) {
|
|
116151
|
+
handleMemoryError3(ctx, error, "Failed to fetch stale memories.");
|
|
116152
|
+
}
|
|
116153
|
+
}
|
|
116154
|
+
|
|
116155
|
+
// src/commands/memory/store.ts
|
|
116156
|
+
init_esm_shims();
|
|
116157
|
+
var handleMemoryError4 = (ctx, error, message, suggestion = "Verify memory service configuration and try again.") => {
|
|
116158
|
+
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
116159
|
+
userMessage: message,
|
|
116160
|
+
suggestion,
|
|
116161
|
+
cause: error
|
|
116162
|
+
});
|
|
116163
|
+
ctx.output.error(formatError(cliError));
|
|
116164
|
+
process.exitCode = cliError.exitCode;
|
|
116165
|
+
};
|
|
116166
|
+
async function store(ctx, content, options) {
|
|
116167
|
+
const outputJson = options.json === true || ctx.format === "json";
|
|
116168
|
+
try {
|
|
116169
|
+
const memory = await MemoryService.store(content, {
|
|
116170
|
+
collection: options.collection || "learnings",
|
|
116171
|
+
source: "human",
|
|
116172
|
+
app_slug: options.app,
|
|
116173
|
+
tags: options.tags?.split(",").map((t2) => t2.trim()) ?? []
|
|
116174
|
+
});
|
|
116175
|
+
if (outputJson) {
|
|
116176
|
+
ctx.output.data(memory);
|
|
116177
|
+
return;
|
|
116178
|
+
}
|
|
116179
|
+
ctx.output.data(`\u2713 Stored memory: ${memory.id}`);
|
|
116180
|
+
if (memory.metadata.tags && memory.metadata.tags.length > 0) {
|
|
116181
|
+
ctx.output.data(` Tags: ${memory.metadata.tags.join(", ")}`);
|
|
116182
|
+
}
|
|
116183
|
+
if (memory.metadata.app_slug) {
|
|
116184
|
+
ctx.output.data(` App: ${memory.metadata.app_slug}`);
|
|
116185
|
+
}
|
|
116186
|
+
} catch (error) {
|
|
116187
|
+
handleMemoryError4(ctx, error, "Failed to store memory.");
|
|
116188
|
+
}
|
|
116189
|
+
}
|
|
116190
|
+
|
|
116191
|
+
// src/commands/memory/vote.ts
|
|
116192
|
+
init_esm_shims();
|
|
116193
|
+
var handleMemoryError5 = (ctx, error, message, suggestion = "Verify memory service configuration and try again.") => {
|
|
113703
116194
|
const cliError = error instanceof CLIError ? error : new CLIError({
|
|
113704
116195
|
userMessage: message,
|
|
113705
116196
|
suggestion,
|
|
@@ -115844,6 +118335,529 @@ async function wizard(ctx, options = {}) {
|
|
|
115844
118335
|
}
|
|
115845
118336
|
}
|
|
115846
118337
|
|
|
118338
|
+
// src/core/auto-update.ts
|
|
118339
|
+
init_esm_shims();
|
|
118340
|
+
import { spawn } from "child_process";
|
|
118341
|
+
import { writeFile as writeFile7 } from "fs/promises";
|
|
118342
|
+
import { homedir as homedir2 } from "os";
|
|
118343
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
118344
|
+
var CONFIG_DIR_NAME = "skill-cli";
|
|
118345
|
+
var AUTO_UPDATE_STATE_FILE = "auto-update.json";
|
|
118346
|
+
var DEFAULT_PACKAGE = "@skillrecordings/cli";
|
|
118347
|
+
var REGISTRY_BASE = "https://registry.npmjs.org/";
|
|
118348
|
+
var CHECK_THROTTLE_MS = 60 * 60 * 1e3;
|
|
118349
|
+
var UPDATE_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
118350
|
+
var AutoUpdateStore = class {
|
|
118351
|
+
filePath;
|
|
118352
|
+
now;
|
|
118353
|
+
constructor(options = {}) {
|
|
118354
|
+
const configDir = resolveConfigDir(options.configDir);
|
|
118355
|
+
this.filePath = join12(configDir, AUTO_UPDATE_STATE_FILE);
|
|
118356
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
118357
|
+
}
|
|
118358
|
+
getNow() {
|
|
118359
|
+
return this.now();
|
|
118360
|
+
}
|
|
118361
|
+
async load() {
|
|
118362
|
+
try {
|
|
118363
|
+
if (await pathExists(this.filePath)) {
|
|
118364
|
+
const data2 = await readJson(this.filePath);
|
|
118365
|
+
if (isAutoUpdateState(data2)) return data2;
|
|
118366
|
+
}
|
|
118367
|
+
} catch {
|
|
118368
|
+
}
|
|
118369
|
+
return {};
|
|
118370
|
+
}
|
|
118371
|
+
async save(state) {
|
|
118372
|
+
try {
|
|
118373
|
+
await ensureDir(dirname4(this.filePath));
|
|
118374
|
+
await writeFile7(this.filePath, JSON.stringify(state, null, 2), "utf-8");
|
|
118375
|
+
} catch {
|
|
118376
|
+
}
|
|
118377
|
+
}
|
|
118378
|
+
};
|
|
118379
|
+
function resolveConfigDir(configDir) {
|
|
118380
|
+
if (configDir) return configDir;
|
|
118381
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
118382
|
+
if (xdgConfigHome && xdgConfigHome.trim() !== "") {
|
|
118383
|
+
return join12(xdgConfigHome, CONFIG_DIR_NAME);
|
|
118384
|
+
}
|
|
118385
|
+
return join12(homedir2(), ".config", CONFIG_DIR_NAME);
|
|
118386
|
+
}
|
|
118387
|
+
function isAutoUpdateState(value) {
|
|
118388
|
+
if (!value || typeof value !== "object") return false;
|
|
118389
|
+
const state = value;
|
|
118390
|
+
if (state.lastCheckAt !== void 0 && typeof state.lastCheckAt !== "string") {
|
|
118391
|
+
return false;
|
|
118392
|
+
}
|
|
118393
|
+
if (state.lastUpdateAt !== void 0 && typeof state.lastUpdateAt !== "string") {
|
|
118394
|
+
return false;
|
|
118395
|
+
}
|
|
118396
|
+
if (state.lastKnownVersion !== void 0 && typeof state.lastKnownVersion !== "string") {
|
|
118397
|
+
return false;
|
|
118398
|
+
}
|
|
118399
|
+
return true;
|
|
118400
|
+
}
|
|
118401
|
+
function isAutoUpdateDisabled() {
|
|
118402
|
+
return process.env.SKILL_NO_AUTO_UPDATE === "1";
|
|
118403
|
+
}
|
|
118404
|
+
function isWithinWindow(timestamp, now, windowMs) {
|
|
118405
|
+
if (!timestamp) return false;
|
|
118406
|
+
const parsed = Date.parse(timestamp);
|
|
118407
|
+
if (Number.isNaN(parsed)) return false;
|
|
118408
|
+
return now.getTime() - parsed < windowMs;
|
|
118409
|
+
}
|
|
118410
|
+
function normalizePackageName(name) {
|
|
118411
|
+
if (name.startsWith("@")) {
|
|
118412
|
+
return name.replace("/", "%2F");
|
|
118413
|
+
}
|
|
118414
|
+
return name;
|
|
118415
|
+
}
|
|
118416
|
+
function parseSemver(version) {
|
|
118417
|
+
const trimmed = version.trim().replace(/^v/, "");
|
|
118418
|
+
if (!trimmed) return null;
|
|
118419
|
+
const [core, prerelease] = trimmed.split("-", 2);
|
|
118420
|
+
if (!core) return null;
|
|
118421
|
+
const parts = core.split(".");
|
|
118422
|
+
if (parts.length < 3) return null;
|
|
118423
|
+
const major = Number.parseInt(parts[0] ?? "", 10);
|
|
118424
|
+
const minor = Number.parseInt(parts[1] ?? "", 10);
|
|
118425
|
+
const patch = Number.parseInt(parts[2] ?? "", 10);
|
|
118426
|
+
if (Number.isNaN(major) || Number.isNaN(minor) || Number.isNaN(patch)) {
|
|
118427
|
+
return null;
|
|
118428
|
+
}
|
|
118429
|
+
return { major, minor, patch, prerelease };
|
|
118430
|
+
}
|
|
118431
|
+
function compareSemver(a, b) {
|
|
118432
|
+
const parsedA = parseSemver(a);
|
|
118433
|
+
const parsedB = parseSemver(b);
|
|
118434
|
+
if (!parsedA || !parsedB) return 0;
|
|
118435
|
+
if (parsedA.major !== parsedB.major) {
|
|
118436
|
+
return parsedA.major > parsedB.major ? 1 : -1;
|
|
118437
|
+
}
|
|
118438
|
+
if (parsedA.minor !== parsedB.minor) {
|
|
118439
|
+
return parsedA.minor > parsedB.minor ? 1 : -1;
|
|
118440
|
+
}
|
|
118441
|
+
if (parsedA.patch !== parsedB.patch) {
|
|
118442
|
+
return parsedA.patch > parsedB.patch ? 1 : -1;
|
|
118443
|
+
}
|
|
118444
|
+
if (parsedA.prerelease && !parsedB.prerelease) return -1;
|
|
118445
|
+
if (!parsedA.prerelease && parsedB.prerelease) return 1;
|
|
118446
|
+
if (parsedA.prerelease && parsedB.prerelease) {
|
|
118447
|
+
if (parsedA.prerelease === parsedB.prerelease) return 0;
|
|
118448
|
+
return parsedA.prerelease > parsedB.prerelease ? 1 : -1;
|
|
118449
|
+
}
|
|
118450
|
+
return 0;
|
|
118451
|
+
}
|
|
118452
|
+
function resolvePackageManager(userAgent) {
|
|
118453
|
+
const ua = userAgent ?? process.env.npm_config_user_agent ?? "";
|
|
118454
|
+
if (ua.startsWith("bun")) return "bun";
|
|
118455
|
+
if (ua.includes("bun")) return "bun";
|
|
118456
|
+
if (ua.startsWith("npm")) return "npm";
|
|
118457
|
+
return "npm";
|
|
118458
|
+
}
|
|
118459
|
+
async function checkForUpdate(options) {
|
|
118460
|
+
const packageName = options.packageName ?? DEFAULT_PACKAGE;
|
|
118461
|
+
const fetchFn = options.fetchFn ?? fetch;
|
|
118462
|
+
const store2 = new AutoUpdateStore({
|
|
118463
|
+
configDir: options.configDir,
|
|
118464
|
+
now: options.now
|
|
118465
|
+
});
|
|
118466
|
+
const now = store2.getNow();
|
|
118467
|
+
const state = await store2.load();
|
|
118468
|
+
if (isWithinWindow(state.lastCheckAt, now, CHECK_THROTTLE_MS)) {
|
|
118469
|
+
const latest = state.lastKnownVersion;
|
|
118470
|
+
const updateAvailable2 = latest ? compareSemver(latest, options.currentVersion) > 0 : false;
|
|
118471
|
+
return { updateAvailable: updateAvailable2, latestVersion: latest, checked: false };
|
|
118472
|
+
}
|
|
118473
|
+
let latestVersion;
|
|
118474
|
+
try {
|
|
118475
|
+
const registryUrl = `${REGISTRY_BASE}${normalizePackageName(packageName)}`;
|
|
118476
|
+
const response = await fetchFn(registryUrl, {
|
|
118477
|
+
headers: { Accept: "application/json" }
|
|
118478
|
+
});
|
|
118479
|
+
if (response.ok) {
|
|
118480
|
+
const data2 = await response.json();
|
|
118481
|
+
latestVersion = data2["dist-tags"]?.latest;
|
|
118482
|
+
}
|
|
118483
|
+
} catch {
|
|
118484
|
+
}
|
|
118485
|
+
state.lastCheckAt = now.toISOString();
|
|
118486
|
+
if (latestVersion) {
|
|
118487
|
+
state.lastKnownVersion = latestVersion;
|
|
118488
|
+
}
|
|
118489
|
+
await store2.save(state);
|
|
118490
|
+
const updateAvailable = latestVersion !== void 0 && compareSemver(latestVersion, options.currentVersion) > 0;
|
|
118491
|
+
return { updateAvailable, latestVersion, checked: true };
|
|
118492
|
+
}
|
|
118493
|
+
async function performUpdate(options = {}) {
|
|
118494
|
+
const packageName = options.packageName ?? DEFAULT_PACKAGE;
|
|
118495
|
+
const store2 = new AutoUpdateStore({
|
|
118496
|
+
configDir: options.configDir,
|
|
118497
|
+
now: options.now
|
|
118498
|
+
});
|
|
118499
|
+
const now = store2.getNow();
|
|
118500
|
+
const packageManager = resolvePackageManager(options.userAgent);
|
|
118501
|
+
const spawnFn = options.spawnFn ?? spawn;
|
|
118502
|
+
const [command, args] = packageManager === "bun" ? ["bun", ["add", "-g", packageName]] : ["npm", ["install", "-g", packageName]];
|
|
118503
|
+
const exitCode = await new Promise((resolve7) => {
|
|
118504
|
+
try {
|
|
118505
|
+
const child = spawnFn(command, args, {
|
|
118506
|
+
stdio: "ignore",
|
|
118507
|
+
env: process.env
|
|
118508
|
+
});
|
|
118509
|
+
child.on("error", () => resolve7(1));
|
|
118510
|
+
child.on("close", (code) => resolve7(code ?? 1));
|
|
118511
|
+
} catch {
|
|
118512
|
+
resolve7(1);
|
|
118513
|
+
}
|
|
118514
|
+
});
|
|
118515
|
+
if (exitCode === 0) {
|
|
118516
|
+
const state = await store2.load();
|
|
118517
|
+
state.lastUpdateAt = now.toISOString();
|
|
118518
|
+
await store2.save(state);
|
|
118519
|
+
return true;
|
|
118520
|
+
}
|
|
118521
|
+
return false;
|
|
118522
|
+
}
|
|
118523
|
+
async function autoUpdateAfterCommand(options) {
|
|
118524
|
+
if (isAutoUpdateDisabled()) return;
|
|
118525
|
+
if (options.isDevMode) return;
|
|
118526
|
+
if (options.commandName === "mcp") return;
|
|
118527
|
+
if (options.format === "json") return;
|
|
118528
|
+
const store2 = new AutoUpdateStore({
|
|
118529
|
+
configDir: options.configDir,
|
|
118530
|
+
now: options.now
|
|
118531
|
+
});
|
|
118532
|
+
const now = store2.getNow();
|
|
118533
|
+
const state = await store2.load();
|
|
118534
|
+
if (isWithinWindow(state.lastUpdateAt, now, UPDATE_THROTTLE_MS)) return;
|
|
118535
|
+
const check = await (options.checkForUpdateFn ?? checkForUpdate)({
|
|
118536
|
+
currentVersion: options.currentVersion,
|
|
118537
|
+
packageName: options.packageName,
|
|
118538
|
+
configDir: options.configDir,
|
|
118539
|
+
now: options.now
|
|
118540
|
+
});
|
|
118541
|
+
if (!check.updateAvailable) return;
|
|
118542
|
+
state.lastUpdateAt = now.toISOString();
|
|
118543
|
+
await store2.save(state);
|
|
118544
|
+
const updated = await (options.performUpdateFn ?? performUpdate)({
|
|
118545
|
+
packageName: options.packageName,
|
|
118546
|
+
configDir: options.configDir,
|
|
118547
|
+
now: options.now
|
|
118548
|
+
});
|
|
118549
|
+
if (updated) return;
|
|
118550
|
+
}
|
|
118551
|
+
|
|
118552
|
+
// src/core/hint-engine.ts
|
|
118553
|
+
init_esm_shims();
|
|
118554
|
+
var DEFAULT_MAX_HINTS = 2;
|
|
118555
|
+
var getCommandCount = (state, command) => state.commands[command]?.count ?? 0;
|
|
118556
|
+
var hasCommand = (state, command) => getCommandCount(state, command) > 0;
|
|
118557
|
+
var hasCommandPrefix = (state, prefix) => Object.entries(state.commands).some(
|
|
118558
|
+
([name, entry]) => name.startsWith(prefix) && entry.count > 0
|
|
118559
|
+
);
|
|
118560
|
+
var hasMilestone = (state, milestone) => state.milestones[milestone]?.achieved ?? false;
|
|
118561
|
+
var shouldSuppressHints = (context) => context.quiet === true || context.format === "json";
|
|
118562
|
+
var resolveMaxHints = (context) => context.maxHints ?? DEFAULT_MAX_HINTS;
|
|
118563
|
+
var toHint = (rule) => ({
|
|
118564
|
+
id: rule.id,
|
|
118565
|
+
message: rule.message,
|
|
118566
|
+
audience: rule.audience
|
|
118567
|
+
});
|
|
118568
|
+
var DEFAULT_HINT_RULES = [
|
|
118569
|
+
{
|
|
118570
|
+
id: "onboarding.wizard",
|
|
118571
|
+
audience: "onboarding",
|
|
118572
|
+
message: "New here? Run `skill wizard` to set up your first product.",
|
|
118573
|
+
showWhen: (state) => state.totalRuns <= 2 && !hasCommand(state, "wizard"),
|
|
118574
|
+
retireWhen: (state) => hasCommand(state, "wizard") || hasMilestone(state, "wizard_completed")
|
|
118575
|
+
},
|
|
118576
|
+
{
|
|
118577
|
+
id: "onboarding.auth",
|
|
118578
|
+
audience: "onboarding",
|
|
118579
|
+
message: "Configure credentials with `skill init` to unlock the full CLI.",
|
|
118580
|
+
showWhen: (state) => state.totalRuns >= 1 && !hasMilestone(state, "auth_configured"),
|
|
118581
|
+
retireWhen: (state) => hasMilestone(state, "auth_configured")
|
|
118582
|
+
},
|
|
118583
|
+
{
|
|
118584
|
+
id: "discovery.health",
|
|
118585
|
+
audience: "discovery",
|
|
118586
|
+
message: "Check integrations fast with `skill health <app-slug>`.",
|
|
118587
|
+
showWhen: (state) => state.totalRuns >= 2 && !hasCommand(state, "health"),
|
|
118588
|
+
retireWhen: (state) => hasCommand(state, "health")
|
|
118589
|
+
},
|
|
118590
|
+
{
|
|
118591
|
+
id: "discovery.front.inbox",
|
|
118592
|
+
audience: "discovery",
|
|
118593
|
+
message: "List recent conversations via `skill front inbox <name-or-id>`.",
|
|
118594
|
+
showWhen: (state) => state.totalRuns >= 1 && !hasCommand(state, "front.inbox"),
|
|
118595
|
+
retireWhen: (state) => hasCommand(state, "front.inbox")
|
|
118596
|
+
},
|
|
118597
|
+
{
|
|
118598
|
+
id: "discovery.inngest",
|
|
118599
|
+
audience: "discovery",
|
|
118600
|
+
message: "Inspect workflows with `skill inngest stats --after 1d`.",
|
|
118601
|
+
showWhen: (state) => state.totalRuns >= 3 && !hasCommandPrefix(state, "inngest."),
|
|
118602
|
+
retireWhen: (state) => hasCommandPrefix(state, "inngest.")
|
|
118603
|
+
},
|
|
118604
|
+
{
|
|
118605
|
+
id: "discovery.axiom",
|
|
118606
|
+
audience: "discovery",
|
|
118607
|
+
message: 'Query logs quickly with `skill axiom query "<APL>" --since 24h`.',
|
|
118608
|
+
showWhen: (state) => state.totalRuns >= 3 && !hasCommandPrefix(state, "axiom."),
|
|
118609
|
+
retireWhen: (state) => hasCommandPrefix(state, "axiom.")
|
|
118610
|
+
},
|
|
118611
|
+
{
|
|
118612
|
+
id: "context.front.triage",
|
|
118613
|
+
audience: "contextual",
|
|
118614
|
+
postRun: true,
|
|
118615
|
+
message: "Tip: `skill front triage <inbox-id>` surfaces unassigned threads.",
|
|
118616
|
+
showWhen: (state, context) => context.command === "front.inbox" && !hasCommand(state, "front.triage"),
|
|
118617
|
+
retireWhen: (state) => hasCommand(state, "front.triage")
|
|
118618
|
+
},
|
|
118619
|
+
{
|
|
118620
|
+
id: "context.front.conversation",
|
|
118621
|
+
audience: "contextual",
|
|
118622
|
+
postRun: true,
|
|
118623
|
+
message: "Tip: `skill front conversation <id> -m` shows the full thread.",
|
|
118624
|
+
showWhen: (state, context) => context.command === "front.message" && !hasCommand(state, "front.conversation"),
|
|
118625
|
+
retireWhen: (state) => hasCommand(state, "front.conversation")
|
|
118626
|
+
},
|
|
118627
|
+
{
|
|
118628
|
+
id: "context.inngest.run",
|
|
118629
|
+
audience: "contextual",
|
|
118630
|
+
postRun: true,
|
|
118631
|
+
message: "Tip: drill in with `skill inngest run <id>` or `skill inngest trace <run-id>`.",
|
|
118632
|
+
showWhen: (state, context) => context.command === "inngest.failures" && !hasCommand(state, "inngest.run") && !hasCommand(state, "inngest.trace"),
|
|
118633
|
+
retireWhen: (state) => hasCommand(state, "inngest.run") || hasCommand(state, "inngest.trace")
|
|
118634
|
+
}
|
|
118635
|
+
];
|
|
118636
|
+
var HintEngine = class {
|
|
118637
|
+
rules;
|
|
118638
|
+
constructor(rules = DEFAULT_HINT_RULES) {
|
|
118639
|
+
this.rules = rules;
|
|
118640
|
+
}
|
|
118641
|
+
getHints(state, context) {
|
|
118642
|
+
if (shouldSuppressHints(context)) return [];
|
|
118643
|
+
const maxHints = resolveMaxHints(context);
|
|
118644
|
+
if (maxHints <= 0) return [];
|
|
118645
|
+
const hints = [];
|
|
118646
|
+
for (const rule of this.rules) {
|
|
118647
|
+
if (rule.postRun) continue;
|
|
118648
|
+
if (!rule.showWhen(state, context)) continue;
|
|
118649
|
+
if (rule.retireWhen(state, context)) continue;
|
|
118650
|
+
hints.push(toHint(rule));
|
|
118651
|
+
if (hints.length >= maxHints) break;
|
|
118652
|
+
}
|
|
118653
|
+
return hints;
|
|
118654
|
+
}
|
|
118655
|
+
getPostRunHint(state, context) {
|
|
118656
|
+
if (shouldSuppressHints(context)) return null;
|
|
118657
|
+
const maxHints = resolveMaxHints(context);
|
|
118658
|
+
if (maxHints <= 0) return null;
|
|
118659
|
+
const previouslyShown = context.previouslyShown ?? 0;
|
|
118660
|
+
if (previouslyShown >= maxHints) return null;
|
|
118661
|
+
for (const rule of this.rules) {
|
|
118662
|
+
if (!rule.postRun) continue;
|
|
118663
|
+
if (!rule.showWhen(state, context)) continue;
|
|
118664
|
+
if (rule.retireWhen(state, context)) continue;
|
|
118665
|
+
return toHint(rule);
|
|
118666
|
+
}
|
|
118667
|
+
return null;
|
|
118668
|
+
}
|
|
118669
|
+
};
|
|
118670
|
+
var writeHints = (hints, stderr) => {
|
|
118671
|
+
for (const hint of hints) {
|
|
118672
|
+
stderr.write(`${hint.message}
|
|
118673
|
+
`);
|
|
118674
|
+
}
|
|
118675
|
+
};
|
|
118676
|
+
|
|
118677
|
+
// src/core/telemetry.ts
|
|
118678
|
+
init_esm_shims();
|
|
118679
|
+
import { Axiom as Axiom2 } from "@axiomhq/js";
|
|
118680
|
+
var DEFAULT_DATASET = "support-agent";
|
|
118681
|
+
var axiomClient = null;
|
|
118682
|
+
function isTelemetryDisabled() {
|
|
118683
|
+
return process.env.SKILL_NO_TELEMETRY === "1";
|
|
118684
|
+
}
|
|
118685
|
+
function getAxiomClient2() {
|
|
118686
|
+
const token = process.env.AXIOM_TOKEN;
|
|
118687
|
+
if (!token) return null;
|
|
118688
|
+
if (!axiomClient) {
|
|
118689
|
+
axiomClient = new Axiom2({ token });
|
|
118690
|
+
}
|
|
118691
|
+
return axiomClient;
|
|
118692
|
+
}
|
|
118693
|
+
function resolveTelemetryUser() {
|
|
118694
|
+
const raw = process.env.USER ?? process.env.LOGNAME ?? process.env.USERNAME ?? "";
|
|
118695
|
+
const trimmed = raw.trim();
|
|
118696
|
+
if (!trimmed) return void 0;
|
|
118697
|
+
const base = trimmed.split("@")[0]?.trim();
|
|
118698
|
+
return base || void 0;
|
|
118699
|
+
}
|
|
118700
|
+
async function sendTelemetryEvent(event) {
|
|
118701
|
+
if (isTelemetryDisabled()) return;
|
|
118702
|
+
const client = getAxiomClient2();
|
|
118703
|
+
if (!client) return;
|
|
118704
|
+
const dataset = process.env.AXIOM_DATASET || DEFAULT_DATASET;
|
|
118705
|
+
const safeEvent = {
|
|
118706
|
+
command: event.command,
|
|
118707
|
+
duration: event.duration,
|
|
118708
|
+
success: event.success,
|
|
118709
|
+
platform: event.platform,
|
|
118710
|
+
user: event.user
|
|
118711
|
+
};
|
|
118712
|
+
try {
|
|
118713
|
+
await client.ingest(dataset, {
|
|
118714
|
+
_time: (/* @__PURE__ */ new Date()).toISOString(),
|
|
118715
|
+
...safeEvent
|
|
118716
|
+
});
|
|
118717
|
+
} catch {
|
|
118718
|
+
}
|
|
118719
|
+
}
|
|
118720
|
+
|
|
118721
|
+
// src/core/usage-tracker.ts
|
|
118722
|
+
init_esm_shims();
|
|
118723
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
118724
|
+
import { homedir as homedir3 } from "os";
|
|
118725
|
+
import { dirname as dirname5, join as join13 } from "path";
|
|
118726
|
+
var CONFIG_DIR_NAME2 = "skill-cli";
|
|
118727
|
+
var USAGE_FILE_NAME = "usage.json";
|
|
118728
|
+
function resolveConfigDir2(configDir) {
|
|
118729
|
+
if (configDir) return configDir;
|
|
118730
|
+
const xdgConfigHome = process.env.XDG_CONFIG_HOME;
|
|
118731
|
+
if (xdgConfigHome && xdgConfigHome.trim() !== "") {
|
|
118732
|
+
return join13(xdgConfigHome, CONFIG_DIR_NAME2);
|
|
118733
|
+
}
|
|
118734
|
+
return join13(homedir3(), ".config", CONFIG_DIR_NAME2);
|
|
118735
|
+
}
|
|
118736
|
+
function createDefaultState(now) {
|
|
118737
|
+
return {
|
|
118738
|
+
firstRun: now.toISOString(),
|
|
118739
|
+
totalRuns: 0,
|
|
118740
|
+
commands: {},
|
|
118741
|
+
milestones: {}
|
|
118742
|
+
};
|
|
118743
|
+
}
|
|
118744
|
+
function isUsageState(value) {
|
|
118745
|
+
if (!value || typeof value !== "object") return false;
|
|
118746
|
+
const state = value;
|
|
118747
|
+
if (typeof state.firstRun !== "string") return false;
|
|
118748
|
+
if (typeof state.totalRuns !== "number") return false;
|
|
118749
|
+
if (!state.commands || typeof state.commands !== "object") return false;
|
|
118750
|
+
if (!state.milestones || typeof state.milestones !== "object") return false;
|
|
118751
|
+
for (const entry of Object.values(state.commands)) {
|
|
118752
|
+
if (!entry || typeof entry !== "object") return false;
|
|
118753
|
+
if (typeof entry.count !== "number") return false;
|
|
118754
|
+
if (typeof entry.firstRun !== "string") return false;
|
|
118755
|
+
if (typeof entry.lastRun !== "string") return false;
|
|
118756
|
+
}
|
|
118757
|
+
for (const entry of Object.values(state.milestones)) {
|
|
118758
|
+
if (!entry || typeof entry !== "object") return false;
|
|
118759
|
+
if (typeof entry.achieved !== "boolean") return false;
|
|
118760
|
+
if (entry.achievedAt !== void 0 && typeof entry.achievedAt !== "string") {
|
|
118761
|
+
return false;
|
|
118762
|
+
}
|
|
118763
|
+
}
|
|
118764
|
+
return true;
|
|
118765
|
+
}
|
|
118766
|
+
var UsageTracker = class {
|
|
118767
|
+
filePath;
|
|
118768
|
+
now;
|
|
118769
|
+
statePromise;
|
|
118770
|
+
constructor(options = {}) {
|
|
118771
|
+
const configDir = resolveConfigDir2(options.configDir);
|
|
118772
|
+
this.filePath = join13(configDir, USAGE_FILE_NAME);
|
|
118773
|
+
this.now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
118774
|
+
}
|
|
118775
|
+
async loadState() {
|
|
118776
|
+
if (!this.statePromise) {
|
|
118777
|
+
this.statePromise = this.loadStateInternal();
|
|
118778
|
+
}
|
|
118779
|
+
return this.statePromise;
|
|
118780
|
+
}
|
|
118781
|
+
async loadStateInternal() {
|
|
118782
|
+
try {
|
|
118783
|
+
if (await pathExists(this.filePath)) {
|
|
118784
|
+
const data2 = await readJson(this.filePath);
|
|
118785
|
+
if (isUsageState(data2)) {
|
|
118786
|
+
return data2;
|
|
118787
|
+
}
|
|
118788
|
+
}
|
|
118789
|
+
} catch {
|
|
118790
|
+
}
|
|
118791
|
+
return createDefaultState(this.now());
|
|
118792
|
+
}
|
|
118793
|
+
async saveState(state) {
|
|
118794
|
+
try {
|
|
118795
|
+
await ensureDir(dirname5(this.filePath));
|
|
118796
|
+
await writeFile8(this.filePath, JSON.stringify(state, null, 2), "utf-8");
|
|
118797
|
+
} catch {
|
|
118798
|
+
}
|
|
118799
|
+
}
|
|
118800
|
+
async record(command, _opts = {}) {
|
|
118801
|
+
const state = await this.loadState();
|
|
118802
|
+
const nowIso = this.now().toISOString();
|
|
118803
|
+
state.totalRuns += 1;
|
|
118804
|
+
const existing = state.commands[command];
|
|
118805
|
+
if (existing) {
|
|
118806
|
+
existing.count += 1;
|
|
118807
|
+
existing.lastRun = nowIso;
|
|
118808
|
+
} else {
|
|
118809
|
+
state.commands[command] = {
|
|
118810
|
+
count: 1,
|
|
118811
|
+
firstRun: nowIso,
|
|
118812
|
+
lastRun: nowIso
|
|
118813
|
+
};
|
|
118814
|
+
}
|
|
118815
|
+
await this.saveState(state);
|
|
118816
|
+
return state;
|
|
118817
|
+
}
|
|
118818
|
+
async getUsage() {
|
|
118819
|
+
return this.loadState();
|
|
118820
|
+
}
|
|
118821
|
+
async getCommandCount(command) {
|
|
118822
|
+
const state = await this.loadState();
|
|
118823
|
+
return state.commands[command]?.count ?? 0;
|
|
118824
|
+
}
|
|
118825
|
+
async hasMilestone(name) {
|
|
118826
|
+
const state = await this.loadState();
|
|
118827
|
+
return state.milestones[name]?.achieved ?? false;
|
|
118828
|
+
}
|
|
118829
|
+
async setMilestone(name) {
|
|
118830
|
+
const state = await this.loadState();
|
|
118831
|
+
const existing = state.milestones[name];
|
|
118832
|
+
if (!existing || !existing.achieved) {
|
|
118833
|
+
state.milestones[name] = {
|
|
118834
|
+
achieved: true,
|
|
118835
|
+
achievedAt: this.now().toISOString()
|
|
118836
|
+
};
|
|
118837
|
+
await this.saveState(state);
|
|
118838
|
+
}
|
|
118839
|
+
}
|
|
118840
|
+
async totalRuns() {
|
|
118841
|
+
const state = await this.loadState();
|
|
118842
|
+
return state.totalRuns;
|
|
118843
|
+
}
|
|
118844
|
+
async daysSinceFirstRun() {
|
|
118845
|
+
const state = await this.loadState();
|
|
118846
|
+
const firstRunMs = Date.parse(state.firstRun);
|
|
118847
|
+
if (Number.isNaN(firstRunMs)) return 0;
|
|
118848
|
+
const diffMs = this.now().getTime() - firstRunMs;
|
|
118849
|
+
if (diffMs <= 0) return 0;
|
|
118850
|
+
return Math.floor(diffMs / (1e3 * 60 * 60 * 24));
|
|
118851
|
+
}
|
|
118852
|
+
};
|
|
118853
|
+
var cachedUsageTracker;
|
|
118854
|
+
function getUsageTracker() {
|
|
118855
|
+
if (!cachedUsageTracker) {
|
|
118856
|
+
cachedUsageTracker = new UsageTracker();
|
|
118857
|
+
}
|
|
118858
|
+
return cachedUsageTracker;
|
|
118859
|
+
}
|
|
118860
|
+
|
|
115847
118861
|
// src/mcp/server.ts
|
|
115848
118862
|
init_esm_shims();
|
|
115849
118863
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -116348,13 +119362,64 @@ var runtimeTarget = `bun-${process.platform}-${process.arch}`;
|
|
|
116348
119362
|
var buildVersion = typeof BUILD_VERSION !== "undefined" && BUILD_VERSION.length > 0 ? BUILD_VERSION : "0.0.0-dev";
|
|
116349
119363
|
var buildCommit = typeof BUILD_COMMIT !== "undefined" && BUILD_COMMIT.length > 0 ? BUILD_COMMIT : "dev";
|
|
116350
119364
|
var buildTarget = typeof BUILD_TARGET !== "undefined" && BUILD_TARGET.length > 0 ? BUILD_TARGET : runtimeTarget;
|
|
119365
|
+
var isDevBuild = buildVersion.includes("dev") || buildCommit === "dev";
|
|
116351
119366
|
var versionLabel = `skill v${buildVersion} (${buildCommit}) ${buildTarget}`;
|
|
116352
119367
|
var program2 = new Command4();
|
|
116353
|
-
|
|
119368
|
+
var hintEngine = new HintEngine();
|
|
119369
|
+
var usageTracker = getUsageTracker();
|
|
119370
|
+
var usageState = await (async () => {
|
|
119371
|
+
try {
|
|
119372
|
+
return await usageTracker.getUsage();
|
|
119373
|
+
} catch {
|
|
119374
|
+
return null;
|
|
119375
|
+
}
|
|
119376
|
+
})();
|
|
119377
|
+
var hintCounts = /* @__PURE__ */ new WeakMap();
|
|
119378
|
+
var commandStartTimes = /* @__PURE__ */ new WeakMap();
|
|
119379
|
+
var resolveCommandName = (command) => {
|
|
119380
|
+
const names = [];
|
|
119381
|
+
let current = command;
|
|
119382
|
+
while (current) {
|
|
119383
|
+
const name = current.name();
|
|
119384
|
+
if (name) names.unshift(name);
|
|
119385
|
+
current = current.parent;
|
|
119386
|
+
}
|
|
119387
|
+
if (names[0] === "skill") names.shift();
|
|
119388
|
+
return names.join(".");
|
|
119389
|
+
};
|
|
119390
|
+
var resolveHintContext = (command) => {
|
|
119391
|
+
const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
|
|
119392
|
+
...command.parent?.opts(),
|
|
119393
|
+
...command.opts()
|
|
119394
|
+
};
|
|
119395
|
+
const outputJson = opts.json === true || opts.format === "json";
|
|
119396
|
+
const suppressForPipe = process.env.SKILL_CLI_FORCE_HINTS === "1" ? false : process.stdout.isTTY !== true;
|
|
119397
|
+
return {
|
|
119398
|
+
command: resolveCommandName(command),
|
|
119399
|
+
format: outputJson ? "json" : opts.format,
|
|
119400
|
+
quiet: opts.quiet === true || suppressForPipe
|
|
119401
|
+
};
|
|
119402
|
+
};
|
|
119403
|
+
var resolveMilestones = (commandName) => {
|
|
119404
|
+
switch (commandName) {
|
|
119405
|
+
case "wizard":
|
|
119406
|
+
return ["wizard_completed"];
|
|
119407
|
+
case "auth.setup":
|
|
119408
|
+
case "init":
|
|
119409
|
+
return ["auth_configured"];
|
|
119410
|
+
default:
|
|
119411
|
+
return [];
|
|
119412
|
+
}
|
|
119413
|
+
};
|
|
119414
|
+
program2.name("skill").description(getRootAdaptiveDescription(usageState)).version(versionLabel).option("-f, --format <format>", "Output format (json|text|table)").option("-v, --verbose", "Enable verbose output").option("-q, --quiet", "Suppress non-error output").option(
|
|
116354
119415
|
"--rate-limit <n>",
|
|
116355
119416
|
"Override Front API rate limit per minute",
|
|
116356
119417
|
(v) => Number.parseInt(v, 10)
|
|
116357
119418
|
);
|
|
119419
|
+
program2.addHelpText(
|
|
119420
|
+
"after",
|
|
119421
|
+
"\n Need help? Start with:\n skill auth setup Set up credentials (1Password)\n skill front inbox See what needs attention\n skill --help This message\n"
|
|
119422
|
+
);
|
|
116358
119423
|
program2.hook("preAction", (thisCommand, actionCommand) => {
|
|
116359
119424
|
const opts = typeof actionCommand.optsWithGlobals === "function" ? actionCommand.optsWithGlobals() : thisCommand.opts();
|
|
116360
119425
|
const rateLimit = opts.rateLimit;
|
|
@@ -116362,18 +119427,105 @@ program2.hook("preAction", (thisCommand, actionCommand) => {
|
|
|
116362
119427
|
process.env.SKILL_RATE_LIMIT = String(rateLimit);
|
|
116363
119428
|
}
|
|
116364
119429
|
});
|
|
119430
|
+
program2.hook("preAction", (_thisCommand, actionCommand) => {
|
|
119431
|
+
commandStartTimes.set(actionCommand, Date.now());
|
|
119432
|
+
});
|
|
119433
|
+
program2.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
119434
|
+
try {
|
|
119435
|
+
const context = resolveHintContext(actionCommand);
|
|
119436
|
+
const state = await usageTracker.getUsage();
|
|
119437
|
+
const hints = hintEngine.getHints(state, context);
|
|
119438
|
+
writeHints(hints, process.stderr);
|
|
119439
|
+
hintCounts.set(actionCommand, hints.length);
|
|
119440
|
+
} catch {
|
|
119441
|
+
}
|
|
119442
|
+
});
|
|
119443
|
+
program2.hook("postAction", async (_thisCommand, actionCommand) => {
|
|
119444
|
+
try {
|
|
119445
|
+
const context = resolveHintContext(actionCommand);
|
|
119446
|
+
const state = await usageTracker.record(context.command);
|
|
119447
|
+
const milestones = resolveMilestones(context.command);
|
|
119448
|
+
for (const milestone of milestones) {
|
|
119449
|
+
await usageTracker.setMilestone(milestone);
|
|
119450
|
+
}
|
|
119451
|
+
const previouslyShown = hintCounts.get(actionCommand) ?? 0;
|
|
119452
|
+
const postHint = hintEngine.getPostRunHint(state, {
|
|
119453
|
+
...context,
|
|
119454
|
+
previouslyShown
|
|
119455
|
+
});
|
|
119456
|
+
if (postHint) writeHints([postHint], process.stderr);
|
|
119457
|
+
} catch {
|
|
119458
|
+
}
|
|
119459
|
+
try {
|
|
119460
|
+
const startTime = commandStartTimes.get(actionCommand) ?? Date.now();
|
|
119461
|
+
const duration = Math.max(0, Date.now() - startTime);
|
|
119462
|
+
const exitCode = process.exitCode ?? 0;
|
|
119463
|
+
const commandName = resolveCommandName(actionCommand);
|
|
119464
|
+
void sendTelemetryEvent({
|
|
119465
|
+
command: commandName,
|
|
119466
|
+
duration,
|
|
119467
|
+
success: exitCode === 0,
|
|
119468
|
+
platform: process.platform,
|
|
119469
|
+
user: resolveTelemetryUser()
|
|
119470
|
+
});
|
|
119471
|
+
} catch {
|
|
119472
|
+
}
|
|
119473
|
+
try {
|
|
119474
|
+
const context = resolveHintContext(actionCommand);
|
|
119475
|
+
await autoUpdateAfterCommand({
|
|
119476
|
+
commandName: context.command,
|
|
119477
|
+
currentVersion: buildVersion,
|
|
119478
|
+
format: context.format,
|
|
119479
|
+
isDevMode: isDevBuild
|
|
119480
|
+
});
|
|
119481
|
+
} catch {
|
|
119482
|
+
}
|
|
119483
|
+
});
|
|
116365
119484
|
program2.command("init").description("Initialize a new app integration (quick mode)").argument(
|
|
116366
119485
|
"[name]",
|
|
116367
119486
|
"Name of the integration (required in non-interactive mode)"
|
|
116368
|
-
).option("--json", "Output result as JSON (machine-readable)").action(
|
|
116369
|
-
|
|
119487
|
+
).option("--json", "Output result as JSON (machine-readable)").action(async (name, options, command) => {
|
|
119488
|
+
const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
|
|
119489
|
+
...command.parent?.opts(),
|
|
119490
|
+
...command.opts()
|
|
119491
|
+
};
|
|
119492
|
+
const ctx = await createContext({
|
|
119493
|
+
format: options.json ? "json" : opts.format,
|
|
119494
|
+
verbose: opts.verbose,
|
|
119495
|
+
quiet: opts.quiet
|
|
119496
|
+
});
|
|
119497
|
+
await init2(ctx, name, options);
|
|
119498
|
+
});
|
|
119499
|
+
program2.command("wizard").description("Interactive wizard for setting up a new property").option("--json", "Output result as JSON (machine-readable)").action(async (options, command) => {
|
|
119500
|
+
const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
|
|
119501
|
+
...command.parent?.opts(),
|
|
119502
|
+
...command.opts()
|
|
119503
|
+
};
|
|
119504
|
+
const ctx = await createContext({
|
|
119505
|
+
format: options.json ? "json" : opts.format,
|
|
119506
|
+
verbose: opts.verbose,
|
|
119507
|
+
quiet: opts.quiet
|
|
119508
|
+
});
|
|
119509
|
+
await wizard(ctx, options);
|
|
119510
|
+
});
|
|
116370
119511
|
program2.command("health").description("Test integration endpoint health").argument(
|
|
116371
119512
|
"[slug|url]",
|
|
116372
119513
|
"App slug (from database) or URL (e.g., https://totaltypescript.com)"
|
|
116373
119514
|
).option(
|
|
116374
119515
|
"-s, --secret <secret>",
|
|
116375
119516
|
"Webhook secret (required for direct URL mode)"
|
|
116376
|
-
).option("-l, --list", "List all registered apps").option("--ids-only", "Output only IDs (one per line)").option("--json", "Output result as JSON (machine-readable)").action(
|
|
119517
|
+
).option("-l, --list", "List all registered apps").option("--ids-only", "Output only IDs (one per line)").option("--json", "Output result as JSON (machine-readable)").action(async (slugOrUrl, options, command) => {
|
|
119518
|
+
const opts = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : {
|
|
119519
|
+
...command.parent?.opts(),
|
|
119520
|
+
...command.opts()
|
|
119521
|
+
};
|
|
119522
|
+
const ctx = await createContext({
|
|
119523
|
+
format: options.json ? "json" : opts.format,
|
|
119524
|
+
verbose: opts.verbose,
|
|
119525
|
+
quiet: opts.quiet
|
|
119526
|
+
});
|
|
119527
|
+
await health2(ctx, slugOrUrl, options);
|
|
119528
|
+
});
|
|
116377
119529
|
program2.command("eval").description("Run evals against a dataset").argument("<type>", "Eval type (e.g., routing)").argument("<dataset>", "Path to dataset JSON file").option("--json", "Output result as JSON (machine-readable)").option(
|
|
116378
119530
|
"--min-precision <number>",
|
|
116379
119531
|
"Minimum precision threshold (default: 0.92)",
|
|
@@ -116410,8 +119562,8 @@ program2.command("eval").description("Run evals against a dataset").argument("<t
|
|
|
116410
119562
|
});
|
|
116411
119563
|
});
|
|
116412
119564
|
registerDbStatusCommand(program2);
|
|
116413
|
-
registerFrontCommands(program2);
|
|
116414
|
-
registerInngestCommands(program2);
|
|
119565
|
+
registerFrontCommands(program2, usageState);
|
|
119566
|
+
registerInngestCommands(program2, usageState);
|
|
116415
119567
|
registerAxiomCommands(program2);
|
|
116416
119568
|
registerEvalLocalCommands(program2);
|
|
116417
119569
|
registerEvalPipelineCommands(program2);
|
|
@@ -116421,12 +119573,15 @@ registerDatasetCommands(program2);
|
|
|
116421
119573
|
registerResponseCommands(program2);
|
|
116422
119574
|
registerToolsCommands(program2);
|
|
116423
119575
|
registerMemoryCommands(program2);
|
|
119576
|
+
registerLinearCommands(program2);
|
|
116424
119577
|
registerFaqCommands(program2);
|
|
116425
119578
|
registerDeployCommands(program2);
|
|
116426
119579
|
registerKbCommands(program2);
|
|
116427
|
-
registerAuthCommands(program2);
|
|
119580
|
+
registerAuthCommands(program2, usageState);
|
|
116428
119581
|
registerPluginSyncCommand(program2);
|
|
116429
|
-
program2.command("mcp").description(
|
|
119582
|
+
program2.command("mcp").description(
|
|
119583
|
+
"Start MCP server for AI coding agent integration.\n Exposes 9 Front tools over JSON-RPC stdio for Claude Code, Cursor, etc.\n Tools: inbox, conversation, message, assign, reply, tag, archive, search, report\n Usage: skill mcp (then connect your AI editor to stdin/stdout)"
|
|
119584
|
+
).action(async () => {
|
|
116430
119585
|
const server = createMcpServer();
|
|
116431
119586
|
await server.start();
|
|
116432
119587
|
});
|