@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/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: writeFile7, readFile: readFile10, mkdir: mkdir2 } = __require("fs/promises");
19841
- var { dirname: dirname4, resolve: resolve7 } = __require("path");
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(dirname4(resolvedPath), { recursive: true });
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 writeFile7(resolvedPath, JSON.stringify(data2, null, 2), { flush: true });
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 = addComment;
58571
+ exports.default = addComment2;
58572
58572
  var _addComments = require_addComments();
58573
- function addComment(node, type, content, line) {
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 = 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 addComment(type, content, line) {
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/password"
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("Manage CLI auth status");
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("Front conversations, inboxes, tags, archival, and reporting");
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("Inngest API commands");
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/memory/index.ts
113469
+ // src/commands/linear/index.ts
113413
113470
  init_esm_shims();
113414
113471
 
113415
- // src/commands/memory/find.ts
113472
+ // src/commands/linear/assign.ts
113416
113473
  init_esm_shims();
113417
- var handleMemoryError = (ctx, error, message, suggestion = "Verify memory service configuration and try again.") => {
113418
- const cliError = error instanceof CLIError ? error : new CLIError({
113419
- userMessage: message,
113420
- suggestion,
113421
- cause: error
113422
- });
113423
- ctx.output.error(formatError(cliError));
113424
- process.exitCode = cliError.exitCode;
113425
- };
113426
- function pad2(str2, width) {
113427
- return str2.padEnd(width).slice(0, width);
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
- function formatConfidence(confidence) {
113430
- return `${(confidence * 100).toFixed(0)}%`;
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
- async function find5(ctx, query, options) {
113433
- const outputJson = options.json === true || ctx.format === "json";
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 limit2 = options.limit ? parseInt(options.limit, 10) : 10;
113436
- const threshold = options.minConfidence ? parseFloat(options.minConfidence) : 0.5;
113437
- if (limit2 < 1 || limit2 > 100 || Number.isNaN(limit2)) {
113678
+ const client = getLinearClient();
113679
+ const issue = await client.issue(issueId);
113680
+ if (!issue) {
113438
113681
  throw new CLIError({
113439
- userMessage: "--limit must be between 1 and 100.",
113440
- suggestion: "Choose a value between 1 and 100 (default: 10)."
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
- if (threshold < 0 || threshold > 1 || Number.isNaN(threshold)) {
113444
- throw new CLIError({
113445
- userMessage: "--min-confidence must be between 0 and 1.",
113446
- suggestion: "Choose a value between 0 and 1 (default: 0.5)."
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
- const results = await MemoryService.find(query, {
113450
- collection: options.collection || "learnings",
113451
- limit: limit2,
113452
- threshold,
113453
- app_slug: options.app
113710
+ await client.updateIssue(issue.id, {
113711
+ assigneeId: assigneeId || void 0
113454
113712
  });
113455
- if (outputJson) {
113456
- ctx.output.data(results);
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
- if (results.length === 0) {
113460
- ctx.output.data("No memories found.");
113461
- return;
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
- Found ${results.length} memories:
113465
- `);
113466
- ctx.output.data(
113467
- pad2("ID", 36) + " " + pad2("SCORE", 8) + " " + pad2("CONF", 6) + " " + pad2("AGE", 8) + " CONTENT"
113468
- );
113469
- ctx.output.data("-".repeat(100));
113470
- for (const result of results) {
113471
- const confidence = calculateConfidence(result.memory);
113472
- const ageDays = Math.floor(result.age_days);
113473
- const ageStr = ageDays === 0 ? "today" : ageDays === 1 ? "1 day" : `${ageDays} days`;
113474
- const contentPreview = result.memory.content.length > 40 ? result.memory.content.slice(0, 37) + "..." : result.memory.content;
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
- pad2(result.memory.id, 36) + " " + pad2(result.score.toFixed(2), 8) + " " + pad2(formatConfidence(confidence), 6) + " " + pad2(ageStr, 8) + " " + contentPreview
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
- if (result.memory.metadata.tags && result.memory.metadata.tags.length > 0) {
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
- handleMemoryError(ctx, error, "Failed to search memories.");
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/memory/get.ts
113837
+ // src/commands/linear/comment.ts
113491
113838
  init_esm_shims();
113492
- var handleMemoryError2 = (ctx, error, message, suggestion = "Verify memory service configuration and try again.") => {
113493
- const cliError = error instanceof CLIError ? error : new CLIError({
113494
- userMessage: message,
113495
- suggestion,
113496
- cause: error
113497
- });
113498
- ctx.output.error(formatError(cliError));
113499
- process.exitCode = cliError.exitCode;
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 memory = await MemoryService.get(
113505
- id,
113506
- options.collection || "learnings"
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: "Memory not found.",
113511
- suggestion: "Verify the memory ID and collection."
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
- if (outputJson) {
113515
- ctx.output.data(memory);
113516
- return;
113517
- }
113518
- const confidence = calculateConfidence(memory);
113519
- const createdAt = new Date(memory.metadata.created_at);
113520
- const lastValidated = memory.metadata.last_validated_at ? new Date(memory.metadata.last_validated_at) : null;
113521
- ctx.output.data("\n\u{1F4CB} Memory Details:");
113522
- ctx.output.data(` ID: ${memory.id}`);
113523
- ctx.output.data(` Collection: ${memory.metadata.collection}`);
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
- if (memory.metadata.app_slug) {
113531
- ctx.output.data(` App: ${memory.metadata.app_slug}`);
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
- if (memory.metadata.tags && memory.metadata.tags.length > 0) {
113534
- ctx.output.data(` Tags: ${memory.metadata.tags.join(", ")}`);
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
- ctx.output.data("\n\u{1F4DD} Content:");
113537
- ctx.output.data(` ${memory.content}
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
- handleMemoryError2(ctx, error, "Failed to fetch memory.");
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/memory/stats.ts
113924
+ // src/commands/linear/comments.ts
113558
113925
  init_esm_shims();
113559
- var handleMemoryError3 = (ctx, error, message, suggestion = "Verify memory service configuration and try again.") => {
113560
- const cliError = error instanceof CLIError ? error : new CLIError({
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 statsResult = await VotingService.stats(options.collection);
113572
- if (outputJson) {
113573
- ctx.output.data(statsResult);
113574
- return;
113575
- }
113576
- const collections = Object.keys(statsResult);
113577
- if (collections.length === 0) {
113578
- ctx.output.data("No memories found");
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
- ctx.output.data("\nMemory Statistics");
113582
- ctx.output.data("\u2500".repeat(60));
113583
- for (const collection of collections) {
113584
- const stats4 = statsResult[collection];
113585
- if (!stats4) continue;
113586
- ctx.output.data(`
113587
- ${collection}:`);
113588
- ctx.output.data(` Total memories: ${stats4.count}`);
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
- ` Avg confidence: ${(stats4.avg_confidence * 100).toFixed(1)}%`
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
- ctx.output.data(` Upvotes: ${stats4.total_upvotes}`);
113593
- ctx.output.data(` Downvotes: ${stats4.total_downvotes}`);
113594
- ctx.output.data(` Citations: ${stats4.total_citations}`);
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
- ` Avg success rate: ${(stats4.avg_success_rate * 100).toFixed(1)}%`
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
- handleMemoryError3(ctx, error, "Failed to fetch memory statistics.");
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
- async function stale(ctx, options) {
113605
- const outputJson = options.json === true || ctx.format === "json";
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 threshold = options.threshold ?? 0.25;
113608
- if (threshold < 0 || threshold > 1 || Number.isNaN(threshold)) {
113609
- throw new CLIError({
113610
- userMessage: "--threshold must be between 0 and 1.",
113611
- suggestion: "Choose a value between 0 and 1 (default: 0.25)."
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
- const collections = options.collection ? [options.collection] : await VotingService._listCollections();
113615
- const staleMemories = [];
113616
- for (const collection of collections) {
113617
- const memories = await VotingService._fetchAllMemories(collection);
113618
- for (const memory of memories) {
113619
- const confidence = calculateConfidence(memory);
113620
- if (confidence < threshold) {
113621
- const createdAt = new Date(memory.metadata.created_at);
113622
- const lastValidatedAt = memory.metadata.last_validated_at ? new Date(memory.metadata.last_validated_at) : void 0;
113623
- const referenceDate = lastValidatedAt || createdAt;
113624
- const ageDays = (Date.now() - referenceDate.getTime()) / (24 * 60 * 60 * 1e3);
113625
- const contentPreview = memory.content.length > 60 ? memory.content.slice(0, 57) + "..." : memory.content;
113626
- staleMemories.push({
113627
- id: memory.id,
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
- if (outputJson) {
113637
- ctx.output.data(staleMemories);
113638
- return;
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
- if (staleMemories.length === 0) {
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
- `No stale memories found (threshold: ${(threshold * 100).toFixed(0)}%)`
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
- Stale Memories (confidence < ${(threshold * 100).toFixed(0)}%)`
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
- handleMemoryError3(ctx, error, "Failed to fetch stale memories.");
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/memory/store.ts
114199
+ // src/commands/linear/get.ts
113665
114200
  init_esm_shims();
113666
- var handleMemoryError4 = (ctx, error, message, suggestion = "Verify memory service configuration and try again.") => {
113667
- const cliError = error instanceof CLIError ? error : new CLIError({
113668
- userMessage: message,
113669
- suggestion,
113670
- cause: error
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
- async function store(ctx, content, options) {
113676
- const outputJson = options.json === true || ctx.format === "json";
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 memory = await MemoryService.store(content, {
113679
- collection: options.collection || "learnings",
113680
- source: "human",
113681
- app_slug: options.app,
113682
- tags: options.tags?.split(",").map((t2) => t2.trim()) ?? []
113683
- });
113684
- if (outputJson) {
113685
- ctx.output.data(memory);
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(`\u2713 Stored memory: ${memory.id}`);
113689
- if (memory.metadata.tags && memory.metadata.tags.length > 0) {
113690
- ctx.output.data(` Tags: ${memory.metadata.tags.join(", ")}`);
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 (memory.metadata.app_slug) {
113693
- ctx.output.data(` App: ${memory.metadata.app_slug}`);
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
- handleMemoryError4(ctx, error, "Failed to store memory.");
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/memory/vote.ts
114356
+ // src/commands/linear/label.ts
113701
114357
  init_esm_shims();
113702
- var handleMemoryError5 = (ctx, error, message, suggestion = "Verify memory service configuration and try again.") => {
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
- program2.name("skill").description("CLI tool for managing app integrations").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(
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(init2);
116369
- program2.command("wizard").description("Interactive wizard for setting up a new property").option("--json", "Output result as JSON (machine-readable)").action(wizard);
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(health2);
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("Start MCP server for AI coding agent integration").action(async () => {
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
  });