@laitszkin/apollo-toolkit 4.1.4 → 5.0.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.
Files changed (205) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/bin/apollo-toolkit.ts +4 -0
  3. package/dist/bin/apollo-toolkit.js +4 -0
  4. package/package.json +4 -2
  5. package/packages/cli/dist/help-text-builder.d.ts +23 -0
  6. package/packages/cli/dist/help-text-builder.js +166 -0
  7. package/packages/cli/dist/index.d.ts +6 -17
  8. package/packages/cli/dist/index.js +52 -246
  9. package/packages/cli/dist/installer.d.ts +1 -0
  10. package/packages/cli/dist/installer.js +20 -7
  11. package/packages/cli/dist/parsers/install-parser.d.ts +15 -0
  12. package/packages/cli/dist/parsers/install-parser.js +87 -0
  13. package/packages/cli/dist/parsers/parser-utils.d.ts +9 -0
  14. package/packages/cli/dist/parsers/parser-utils.js +16 -0
  15. package/packages/cli/dist/parsers/tool-parser.d.ts +16 -0
  16. package/packages/cli/dist/parsers/tool-parser.js +58 -0
  17. package/packages/cli/dist/parsers/types.d.ts +50 -0
  18. package/packages/cli/dist/parsers/types.js +1 -0
  19. package/packages/cli/dist/parsers/uninstall-parser.d.ts +15 -0
  20. package/packages/cli/dist/parsers/uninstall-parser.js +67 -0
  21. package/packages/cli/dist/tool-registration.d.ts +2 -0
  22. package/packages/cli/dist/tool-registration.js +2 -0
  23. package/packages/cli/dist/tsconfig.tsbuildinfo +1 -1
  24. package/packages/cli/dist/types.d.ts +3 -1
  25. package/packages/cli/dist/updater.js +11 -5
  26. package/packages/cli/help-text-builder.ts +180 -0
  27. package/packages/cli/index.ts +59 -251
  28. package/packages/cli/installer.ts +19 -7
  29. package/packages/cli/package.json +6 -3
  30. package/packages/cli/parsers/install-parser.ts +94 -0
  31. package/packages/cli/parsers/parser-utils.ts +17 -0
  32. package/packages/cli/parsers/tool-parser.ts +65 -0
  33. package/packages/cli/parsers/types.ts +56 -0
  34. package/packages/cli/parsers/uninstall-parser.ts +75 -0
  35. package/packages/cli/tool-registration.ts +3 -0
  36. package/packages/cli/types.ts +6 -1
  37. package/packages/cli/updater.ts +11 -5
  38. package/packages/tool-registry/dist/registry.js +3 -4
  39. package/packages/tool-registry/dist/tsconfig.tsbuildinfo +1 -1
  40. package/packages/tool-registry/dist/types.d.ts +2 -9
  41. package/packages/tool-registry/package.json +3 -3
  42. package/packages/tool-registry/registry.ts +3 -4
  43. package/packages/tool-registry/tsconfig.json +6 -2
  44. package/packages/tool-registry/types.ts +3 -9
  45. package/packages/tool-utils/app-error.ts +97 -0
  46. package/packages/tool-utils/dist/app-error.d.ts +49 -0
  47. package/packages/tool-utils/dist/app-error.js +80 -0
  48. package/packages/tool-utils/dist/index.d.ts +5 -0
  49. package/packages/tool-utils/dist/index.js +3 -0
  50. package/packages/tool-utils/dist/platform-adapter.d.ts +48 -0
  51. package/packages/tool-utils/dist/platform-adapter.js +73 -0
  52. package/packages/tool-utils/dist/schema.d.ts +68 -0
  53. package/packages/tool-utils/dist/schema.js +67 -0
  54. package/packages/tool-utils/dist/tsconfig.tsbuildinfo +1 -1
  55. package/packages/tool-utils/index.ts +12 -0
  56. package/packages/tool-utils/package.json +3 -3
  57. package/packages/tool-utils/platform-adapter.ts +112 -0
  58. package/packages/tool-utils/schema.ts +122 -0
  59. package/packages/tools/architecture/dist/index.d.ts +13 -0
  60. package/packages/tools/architecture/dist/index.js +55 -57
  61. package/packages/tools/architecture/dist/index.test.js +17 -4
  62. package/packages/tools/architecture/dist/tsconfig.tsbuildinfo +1 -1
  63. package/packages/tools/architecture/index.test.ts +27 -14
  64. package/packages/tools/architecture/index.ts +85 -88
  65. package/packages/tools/architecture/package.json +3 -3
  66. package/packages/tools/codegraph/dist/index.js +12 -22
  67. package/packages/tools/codegraph/dist/tsconfig.tsbuildinfo +1 -1
  68. package/packages/tools/codegraph/index.ts +13 -22
  69. package/packages/tools/codegraph/package.json +3 -3
  70. package/packages/tools/create-review-report/dist/index.d.ts +1 -2
  71. package/packages/tools/create-review-report/dist/index.js +46 -77
  72. package/packages/tools/create-review-report/dist/tsconfig.tsbuildinfo +1 -1
  73. package/packages/tools/create-review-report/index.ts +52 -81
  74. package/packages/tools/create-review-report/package.json +3 -3
  75. package/packages/tools/create-specs/dist/index.d.ts +1 -2
  76. package/packages/tools/create-specs/dist/index.js +70 -123
  77. package/packages/tools/create-specs/dist/tsconfig.tsbuildinfo +1 -1
  78. package/packages/tools/create-specs/index.ts +82 -128
  79. package/packages/tools/create-specs/package.json +3 -3
  80. package/packages/tools/docs-to-voice/dist/index.d.ts +1 -2
  81. package/packages/tools/docs-to-voice/dist/index.js +116 -219
  82. package/packages/tools/docs-to-voice/dist/tsconfig.tsbuildinfo +1 -1
  83. package/packages/tools/docs-to-voice/index.ts +265 -385
  84. package/packages/tools/docs-to-voice/package.json +3 -3
  85. package/packages/tools/enforce-video-aspect-ratio/dist/index.d.ts +1 -2
  86. package/packages/tools/enforce-video-aspect-ratio/dist/index.js +77 -154
  87. package/packages/tools/enforce-video-aspect-ratio/dist/tsconfig.tsbuildinfo +1 -1
  88. package/packages/tools/enforce-video-aspect-ratio/index.ts +87 -172
  89. package/packages/tools/enforce-video-aspect-ratio/package.json +3 -3
  90. package/packages/tools/eval/dist/index.js +7 -0
  91. package/packages/tools/eval/dist/tsconfig.tsbuildinfo +1 -1
  92. package/packages/tools/eval/index.ts +8 -0
  93. package/packages/tools/eval/package.json +3 -3
  94. package/packages/tools/extract-conversations/dist/index.d.ts +1 -2
  95. package/packages/tools/extract-conversations/dist/index.js +31 -29
  96. package/packages/tools/extract-conversations/dist/tsconfig.tsbuildinfo +1 -1
  97. package/packages/tools/extract-conversations/index.ts +37 -30
  98. package/packages/tools/extract-conversations/package.json +3 -3
  99. package/packages/tools/extract-pdf-text/dist/index.d.ts +1 -2
  100. package/packages/tools/extract-pdf-text/dist/index.js +44 -65
  101. package/packages/tools/extract-pdf-text/dist/tsconfig.tsbuildinfo +1 -1
  102. package/packages/tools/extract-pdf-text/index.ts +55 -74
  103. package/packages/tools/extract-pdf-text/package.json +3 -3
  104. package/packages/tools/filter-logs/dist/index.js +60 -84
  105. package/packages/tools/filter-logs/dist/tsconfig.tsbuildinfo +1 -1
  106. package/packages/tools/filter-logs/index.ts +67 -97
  107. package/packages/tools/filter-logs/package.json +3 -3
  108. package/packages/tools/find-github-issues/dist/index.d.ts +10 -0
  109. package/packages/tools/find-github-issues/dist/index.js +34 -5
  110. package/packages/tools/find-github-issues/dist/tsconfig.tsbuildinfo +1 -1
  111. package/packages/tools/find-github-issues/index.ts +37 -5
  112. package/packages/tools/find-github-issues/package.json +3 -3
  113. package/packages/tools/generate-storyboard-images/dist/index.d.ts +1 -2
  114. package/packages/tools/generate-storyboard-images/dist/index.js +98 -173
  115. package/packages/tools/generate-storyboard-images/dist/tsconfig.tsbuildinfo +1 -1
  116. package/packages/tools/generate-storyboard-images/index.ts +100 -188
  117. package/packages/tools/generate-storyboard-images/package.json +3 -3
  118. package/packages/tools/open-github-issue/dist/index.d.ts +13 -0
  119. package/packages/tools/open-github-issue/dist/index.js +67 -68
  120. package/packages/tools/open-github-issue/dist/tsconfig.tsbuildinfo +1 -1
  121. package/packages/tools/open-github-issue/index.ts +71 -72
  122. package/packages/tools/open-github-issue/package.json +3 -3
  123. package/packages/tools/read-github-issue/dist/index.d.ts +16 -1
  124. package/packages/tools/read-github-issue/dist/index.js +32 -40
  125. package/packages/tools/read-github-issue/dist/tsconfig.tsbuildinfo +1 -1
  126. package/packages/tools/read-github-issue/index.ts +32 -45
  127. package/packages/tools/read-github-issue/package.json +3 -3
  128. package/packages/tools/render-error-book/dist/index.d.ts +1 -2
  129. package/packages/tools/render-error-book/dist/index.js +74 -95
  130. package/packages/tools/render-error-book/dist/tsconfig.tsbuildinfo +1 -1
  131. package/packages/tools/render-error-book/index.ts +88 -103
  132. package/packages/tools/render-error-book/package.json +3 -3
  133. package/packages/tools/render-katex/dist/index.d.ts +1 -2
  134. package/packages/tools/render-katex/dist/index.js +70 -157
  135. package/packages/tools/render-katex/dist/tsconfig.tsbuildinfo +1 -1
  136. package/packages/tools/render-katex/index.ts +138 -222
  137. package/packages/tools/render-katex/package.json +3 -3
  138. package/packages/tools/review-threads/dist/index.d.ts +12 -0
  139. package/packages/tools/review-threads/dist/index.js +83 -86
  140. package/packages/tools/review-threads/dist/tsconfig.tsbuildinfo +1 -1
  141. package/packages/tools/review-threads/index.ts +90 -84
  142. package/packages/tools/review-threads/package.json +3 -3
  143. package/packages/tools/search-logs/dist/index.js +100 -136
  144. package/packages/tools/search-logs/dist/tsconfig.tsbuildinfo +1 -1
  145. package/packages/tools/search-logs/index.ts +113 -145
  146. package/packages/tools/search-logs/package.json +3 -3
  147. package/packages/tools/sync-memory-index/dist/index.js +34 -28
  148. package/packages/tools/sync-memory-index/dist/tsconfig.tsbuildinfo +1 -1
  149. package/packages/tools/sync-memory-index/index.ts +37 -28
  150. package/packages/tools/sync-memory-index/package.json +3 -3
  151. package/packages/tools/validate-openai-agent-config/dist/index.js +13 -7
  152. package/packages/tools/validate-openai-agent-config/dist/tsconfig.tsbuildinfo +1 -1
  153. package/packages/tools/validate-openai-agent-config/index.ts +13 -7
  154. package/packages/tools/validate-openai-agent-config/package.json +3 -3
  155. package/packages/tools/validate-skill-frontmatter/dist/index.js +12 -6
  156. package/packages/tools/validate-skill-frontmatter/dist/tsconfig.tsbuildinfo +1 -1
  157. package/packages/tools/validate-skill-frontmatter/index.ts +12 -6
  158. package/packages/tools/validate-skill-frontmatter/package.json +3 -3
  159. package/packages/tui/dist/index.d.ts +2 -1
  160. package/packages/tui/dist/index.js +1 -0
  161. package/packages/tui/dist/stdio-adapter.d.ts +36 -0
  162. package/packages/tui/dist/stdio-adapter.js +69 -0
  163. package/packages/tui/dist/terminal.js +3 -1
  164. package/packages/tui/dist/tsconfig.tsbuildinfo +1 -1
  165. package/packages/tui/dist/types.d.ts +17 -0
  166. package/packages/tui/index.ts +2 -1
  167. package/packages/tui/package.json +6 -5
  168. package/packages/tui/stdio-adapter.ts +85 -0
  169. package/packages/tui/terminal.ts +3 -1
  170. package/packages/tui/tsconfig.json +5 -2
  171. package/packages/tui/types.ts +19 -0
  172. package/resources/project-architecture/assets/architecture.css +2 -1
  173. package/resources/project-architecture/atlas/atlas.history.log +1 -0
  174. package/resources/project-architecture/atlas/atlas.history.undo.json +13 -2
  175. package/resources/project-architecture/atlas/atlas.history.undo.stack.json +610 -0
  176. package/resources/project-architecture/atlas/atlas.index.yaml +81 -5
  177. package/resources/project-architecture/atlas/features/cli-dispatch.yaml +43 -0
  178. package/resources/project-architecture/atlas/features/terminal-ui.yaml +29 -0
  179. package/resources/project-architecture/atlas/features/tool-registry.yaml +22 -0
  180. package/resources/project-architecture/atlas/features/tool-utils.yaml +22 -0
  181. package/resources/project-architecture/features/cli-dispatch/arg-parser.html +40 -0
  182. package/resources/project-architecture/features/cli-dispatch/help-builder.html +40 -0
  183. package/resources/project-architecture/features/cli-dispatch/index.html +64 -0
  184. package/resources/project-architecture/features/cli-dispatch/installer-core.html +40 -0
  185. package/resources/project-architecture/features/cli-dispatch/tool-discovery.html +40 -0
  186. package/resources/project-architecture/features/cli-dispatch/update-checker.html +40 -0
  187. package/resources/project-architecture/features/terminal-ui/banner-display.html +40 -0
  188. package/resources/project-architecture/features/terminal-ui/index.html +50 -0
  189. package/resources/project-architecture/features/terminal-ui/interactive-prompts.html +40 -0
  190. package/resources/project-architecture/features/terminal-ui/terminal-detection.html +40 -0
  191. package/resources/project-architecture/features/tool-registry/formatter.html +40 -0
  192. package/resources/project-architecture/features/tool-registry/index.html +43 -0
  193. package/resources/project-architecture/features/tool-registry/registry-core.html +40 -0
  194. package/resources/project-architecture/features/tool-utils/index.html +43 -0
  195. package/resources/project-architecture/features/tool-utils/log-utils.html +40 -0
  196. package/resources/project-architecture/features/tool-utils/skill-discovery.html +40 -0
  197. package/resources/project-architecture/index.html +365 -121
  198. package/scripts/rewrite-imports.mjs +2 -2
  199. package/scripts/test.sh +144 -8
  200. package/skills/design/SKILL.md +57 -64
  201. package/skills/design/assets/templates/DESIGN.md +12 -0
  202. package/skills/design/references/code-smells.md +94 -0
  203. package/skills/design/references/module-boundary-adjustment.md +126 -0
  204. package/skills/design/references/module-internal-restructuring.md +132 -0
  205. package/skills/design/references/module-internal-simplification.md +164 -0
@@ -6,6 +6,7 @@ import { tmpdir } from 'node:os';
6
6
  import { join as joinPath } from 'node:path';
7
7
  import { cwd } from 'node:process';
8
8
  import type { ToolDefinition, ToolContext } from '@laitszkin/tool-registry';
9
+ import { UserInputError, SystemError } from '@laitszkin/tool-utils';
9
10
 
10
11
  const GITHUB_API_BASE = 'https://api.github.com';
11
12
  const README_ACCEPT = 'application/vnd.github.raw+json';
@@ -76,6 +77,29 @@ const PAYLOAD_FIELDS = new Set([
76
77
  'dry_run',
77
78
  ]);
78
79
 
80
+ const HELP_TEXT = `
81
+ Usage: open-github-issue [options]
82
+
83
+ Options:
84
+ --payload-file <path> Path to JSON payload file with issue fields
85
+ --title <text> Issue title
86
+ --issue-type <type> Issue type (problem|feature|performance|security|docs|observability)
87
+ --problem-description <text> Description of the problem
88
+ --suspected-cause <text> Suspected root cause
89
+ --reproduction <text> Reproduction steps
90
+ --proposal <text> Feature proposal description
91
+ --reason <text> Reason for the feature
92
+ --suggested-architecture <text> Suggested architecture
93
+ --impact <text> Impact description
94
+ --evidence <text> Evidence supporting the issue
95
+ --suggested-action <text> Suggested action
96
+ --severity <text> Severity level (for security issues)
97
+ --affected-scope <text> Affected scope
98
+ --repo <owner/repo> Target repository (e.g., owner/repo)
99
+ --dry-run Validate and print issue body without publishing
100
+ --help, -h Show this help message
101
+ `;
102
+
79
103
  interface OpenIssueArgs {
80
104
  payloadFile: string | null;
81
105
  title: string | null;
@@ -93,6 +117,7 @@ interface OpenIssueArgs {
93
117
  affectedScope: string | null;
94
118
  repo: string | null;
95
119
  dryRun: boolean;
120
+ helpRequested: boolean;
96
121
  }
97
122
 
98
123
  function parseArgs(argv: string[]): OpenIssueArgs {
@@ -113,6 +138,7 @@ function parseArgs(argv: string[]): OpenIssueArgs {
113
138
  affectedScope: null,
114
139
  repo: null,
115
140
  dryRun: false,
141
+ helpRequested: false,
116
142
  };
117
143
 
118
144
  let i = 0;
@@ -167,6 +193,10 @@ function parseArgs(argv: string[]): OpenIssueArgs {
167
193
  case '--dry-run':
168
194
  args.dryRun = true;
169
195
  break;
196
+ case '--help':
197
+ case '-h':
198
+ args.helpRequested = true;
199
+ break;
170
200
  default:
171
201
  if (!arg.startsWith('-')) {
172
202
  // unsupported positional — ignore per Python behavior
@@ -217,7 +247,7 @@ function readPayloadFile(rawPath: string): PayloadEntry {
217
247
 
218
248
  if (rawPath === '-') {
219
249
  // We cannot read stdin here easily; throw clear error
220
- throw new Error('stdin payload (-) is not supported in handler mode; use a file path');
250
+ throw new UserInputError('stdin payload (-) is not supported in handler mode; use a file path');
221
251
  } else {
222
252
  rawContent = readFileSync(rawPath, 'utf-8');
223
253
  context = rawPath;
@@ -227,18 +257,18 @@ function readPayloadFile(rawPath: string): PayloadEntry {
227
257
  try {
228
258
  payload = JSON.parse(rawContent);
229
259
  } catch (exc) {
230
- throw new Error(`Invalid JSON payload in ${context}: ${(exc as Error).message}`);
260
+ throw new UserInputError(`Invalid JSON payload in ${context}: ${(exc as Error).message}`, undefined, { cause: exc });
231
261
  }
232
262
 
233
263
  if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
234
- throw new Error(`Invalid JSON payload in ${context}: top-level value must be an object.`);
264
+ throw new UserInputError(`Invalid JSON payload in ${context}: top-level value must be an object.`);
235
265
  }
236
266
 
237
267
  const normalized: PayloadEntry = {};
238
268
  for (const [rawKey, value] of Object.entries(payload as Record<string, unknown>)) {
239
269
  const key = normalizeKey(rawKey);
240
270
  if (!PAYLOAD_FIELDS.has(key)) {
241
- throw new Error(`Unsupported payload key: ${rawKey}`);
271
+ throw new UserInputError(`Unsupported payload key: ${rawKey}`);
242
272
  }
243
273
  normalized[key] = value;
244
274
  }
@@ -249,15 +279,17 @@ function readAtFileValue(fieldName: string, value: string | null): string | null
249
279
  if (value == null) return null;
250
280
  if (value.startsWith('@@')) return value.slice(1);
251
281
  if (value === '@-') {
252
- throw new Error('stdin reading (@-) is not supported in handler mode');
282
+ throw new UserInputError('stdin reading (@-) is not supported in handler mode');
253
283
  }
254
284
  if (value.startsWith('@') && value.length > 1) {
255
285
  const filePath = value.slice(1);
256
286
  try {
257
287
  return readFileSync(filePath, 'utf-8');
258
288
  } catch (exc) {
259
- throw new Error(
289
+ throw new UserInputError(
260
290
  `Unable to read @${fieldName} file ${filePath}: ${(exc as Error).message}`,
291
+ undefined,
292
+ { cause: exc },
261
293
  );
262
294
  }
263
295
  }
@@ -266,7 +298,7 @@ function readAtFileValue(fieldName: string, value: string | null): string | null
266
298
 
267
299
  function requireNonEmpty(value: string | null | undefined, message: string): void {
268
300
  if (!(value || '').trim()) {
269
- throw new Error(message);
301
+ throw new UserInputError(message);
270
302
  }
271
303
  }
272
304
 
@@ -288,7 +320,7 @@ function getToken(env: Record<string, string | undefined>): string | null {
288
320
  function validateRepo(repo: string): string {
289
321
  const candidate = repo.trim();
290
322
  if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(candidate)) {
291
- throw new Error('Invalid repo format. Use owner/repo.');
323
+ throw new UserInputError('Invalid repo format. Use owner/repo.');
292
324
  }
293
325
  return candidate;
294
326
  }
@@ -607,7 +639,7 @@ async function createIssueWithGh(
607
639
  ]);
608
640
 
609
641
  if (result.exitCode !== 0) {
610
- throw new Error(result.stderr.trim() || 'gh issue create failed');
642
+ throw new SystemError(result.stderr.trim() || 'gh issue create failed');
611
643
  }
612
644
 
613
645
  const urlMatch = result.stdout.match(
@@ -636,7 +668,7 @@ async function createIssueWithToken(
636
668
  const parsed = JSON.parse(response);
637
669
  const issueUrl: string | undefined = parsed.html_url;
638
670
  if (!issueUrl) {
639
- throw new Error('Issue created but response did not include html_url');
671
+ throw new SystemError('Issue created but response did not include html_url');
640
672
  }
641
673
  return issueUrl;
642
674
  }
@@ -692,7 +724,7 @@ function validateIssueContent(args: OpenIssueArgs): void {
692
724
  requireNonEmpty(args.problemDescription, 'Problem issues require --problem-description.');
693
725
  requireNonEmpty(args.suspectedCause, 'Problem issues require --suspected-cause.');
694
726
  if (!hasRequiredProblemBddSections(args.problemDescription || '')) {
695
- throw new Error(
727
+ throw new UserInputError(
696
728
  'Problem issues require --problem-description to include ' +
697
729
  'Expected Behavior (BDD), Current Behavior (BDD), and Behavior Gap sections.',
698
730
  );
@@ -708,7 +740,7 @@ function hydrateArgs(args: OpenIssueArgs): OpenIssueArgs {
708
740
  for (const [key, value] of Object.entries(payload)) {
709
741
  if (key === 'dry_run') {
710
742
  if (typeof value !== 'boolean') {
711
- throw new Error("Payload field 'dry_run' must be a boolean.");
743
+ throw new UserInputError("Payload field 'dry_run' must be a boolean.");
712
744
  }
713
745
  if (!result.dryRun) {
714
746
  result.dryRun = value;
@@ -719,10 +751,10 @@ function hydrateArgs(args: OpenIssueArgs): OpenIssueArgs {
719
751
  // String fields
720
752
  if (TEXT_FIELDS.includes(key as (typeof TEXT_FIELDS)[number])) {
721
753
  if (value !== null && typeof value !== 'string') {
722
- throw new Error(`Payload field '${key}' must be a string or null.`);
754
+ throw new UserInputError(`Payload field '${key}' must be a string or null.`);
723
755
  }
724
756
  } else if (typeof value !== 'string') {
725
- throw new Error(`Payload field '${key}' must be a string.`);
757
+ throw new UserInputError(`Payload field '${key}' must be a string.`);
726
758
  }
727
759
 
728
760
  const currentVal = (result as Record<string, unknown>)[key];
@@ -737,7 +769,7 @@ function hydrateArgs(args: OpenIssueArgs): OpenIssueArgs {
737
769
  result.issueType = ISSUE_TYPE_PROBLEM;
738
770
  }
739
771
  if (!ISSUE_TYPES.includes(result.issueType as (typeof ISSUE_TYPES)[number])) {
740
- throw new Error(`Invalid issue_type: ${result.issueType}`);
772
+ throw new UserInputError(`Invalid issue_type: ${result.issueType}`);
741
773
  }
742
774
 
743
775
  // Resolve @-prefixed file values
@@ -749,7 +781,7 @@ function hydrateArgs(args: OpenIssueArgs): OpenIssueArgs {
749
781
 
750
782
  // Title is required
751
783
  if (!(result.title || '').trim()) {
752
- throw new Error('Issue title is required. Pass --title or include title in --payload-file.');
784
+ throw new UserInputError('Issue title is required. Pass --title or include title in --payload-file.');
753
785
  }
754
786
 
755
787
  return result;
@@ -764,10 +796,7 @@ async function resolveRepoAsync(
764
796
  // Try to resolve from git remote
765
797
  const result = await runCommand('git', ['remote', 'get-url', 'origin']);
766
798
  if (result.exitCode !== 0) {
767
- context.stderr!.write(
768
- 'Unable to resolve origin remote. Pass --repo owner/repo.\n',
769
- );
770
- throw new Error('--repo resolution failed');
799
+ throw new UserInputError('Unable to resolve origin remote. Pass --repo owner/repo.');
771
800
  }
772
801
 
773
802
  const remote = result.stdout.trim();
@@ -775,10 +804,7 @@ async function resolveRepoAsync(
775
804
  /github\.com[:/](?<owner>[A-Za-z0-9_.-]+)\/(?<repo>[A-Za-z0-9_.-]+?)(?:\.git)?$/,
776
805
  );
777
806
  if (!match?.groups) {
778
- context.stderr!.write(
779
- 'Origin remote is not a GitHub repository. Pass --repo owner/repo.\n',
780
- );
781
- throw new Error('--repo resolution failed');
807
+ throw new UserInputError('Unable to resolve origin remote. Pass --repo owner/repo.');
782
808
  }
783
809
 
784
810
  return `${match.groups.owner}/${match.groups.repo}`;
@@ -797,30 +823,37 @@ interface IssueResult {
797
823
  publish_error: string;
798
824
  }
799
825
 
826
+ /**
827
+ * openGitHubIssueHandler — Known carryover from createToolRunner migration.
828
+ *
829
+ * Reason for not using createToolRunner:
830
+ * - Positional subcommand architecture (create/draft) with 15+ tool-specific
831
+ * flags doesn't map cleanly to createToolRunner's options schema.
832
+ * - Error handling follows the AppError convention (UserInputError/SystemError
833
+ * throws) which is handled by the CLI boundary's formatAppError.
834
+ * - Argument parsing and help text are handled manually — 83-line parseArgs().
835
+ *
836
+ * See DESIGN.md §2.3 for the full architecture discussion.
837
+ * --help is now supported.
838
+ */
800
839
  export async function openGitHubIssueHandler(
801
840
  argv: string[],
802
841
  context: ToolContext,
803
842
  ): Promise<number> {
804
843
  const { stdout, stderr, env } = context;
805
844
 
806
- let args: OpenIssueArgs;
807
- try {
808
- args = hydrateArgs(parseArgs(argv));
809
- validateIssueContent(args);
810
- } catch (err) {
811
- stderr!.write(`Error: ${(err as Error).message}\n`);
812
- return 1;
845
+ const parsed = parseArgs(argv);
846
+ if (parsed.helpRequested) {
847
+ stdout!.write(HELP_TEXT + '\n');
848
+ return 0;
813
849
  }
850
+ const args = hydrateArgs(parsed);
851
+ validateIssueContent(args);
814
852
 
815
853
  const ghAuthenticated = await hasGhAuth();
816
854
  const token = getToken(env || {});
817
855
 
818
- let repo: string;
819
- try {
820
- repo = await resolveRepoAsync(args.repo, context);
821
- } catch {
822
- return 1;
823
- }
856
+ const repo = await resolveRepoAsync(args.repo, context);
824
857
 
825
858
  const readmeContent = await fetchRemoteReadme(repo, ghAuthenticated, token);
826
859
  const language = detectIssueLanguage(readmeContent);
@@ -898,9 +931,7 @@ export async function openGitHubIssueHandler(
898
931
 
899
932
  if (mode === 'draft-only') {
900
933
  if (publishError) {
901
- stderr!.write(
902
- `Issue publish failed. Return draft only: ${publishError}\n`,
903
- );
934
+ throw new SystemError(`Issue publish failed. Return draft only: ${publishError}`);
904
935
  } else {
905
936
  stderr!.write(
906
937
  'No authenticated gh CLI session and no GitHub token found. ' +
@@ -914,38 +945,6 @@ export async function openGitHubIssueHandler(
914
945
 
915
946
  // ---- Tool definition ----
916
947
 
917
- const FLAG_MAP: Record<string, { flag: string; type: 'string' | 'boolean' }> = {
918
- payloadFile: { flag: '--payload-file', type: 'string' },
919
- title: { flag: '--title', type: 'string' },
920
- issueType: { flag: '--issue-type', type: 'string' },
921
- problemDescription: { flag: '--problem-description', type: 'string' },
922
- suspectedCause: { flag: '--suspected-cause', type: 'string' },
923
- reproduction: { flag: '--reproduction', type: 'string' },
924
- proposal: { flag: '--proposal', type: 'string' },
925
- reason: { flag: '--reason', type: 'string' },
926
- suggestedArchitecture: { flag: '--suggested-architecture', type: 'string' },
927
- impact: { flag: '--impact', type: 'string' },
928
- evidence: { flag: '--evidence', type: 'string' },
929
- suggestedAction: { flag: '--suggested-action', type: 'string' },
930
- severity: { flag: '--severity', type: 'string' },
931
- affectedScope: { flag: '--affected-scope', type: 'string' },
932
- repo: { flag: '--repo', type: 'string' },
933
- dryRun: { flag: '--dry-run', type: 'boolean' },
934
- };
935
-
936
- function buildArgsFromYargs(argv: Record<string, unknown>): string[] {
937
- const args: string[] = [];
938
- for (const [camel, { flag, type }] of Object.entries(FLAG_MAP)) {
939
- const value = argv[camel];
940
- if (type === 'boolean') {
941
- if (value) args.push(flag);
942
- } else if (value !== undefined && value !== null) {
943
- args.push(flag, String(value));
944
- }
945
- }
946
- return args;
947
- }
948
-
949
948
  export const tool: ToolDefinition = {
950
949
  name: 'open-github-issue',
951
950
  category: 'GitHub workflows',
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@laitszkin/tool-open-github-issue",
3
- "version": "4.0.8",
4
- "description": "Apollo Toolkit CLI tool",
3
+ "version": "5.0.0",
4
+ "description": "Apollo Toolkit \u2014 CLI tool",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -19,4 +19,4 @@
19
19
  "dependencies": {
20
20
  "@laitszkin/tool-registry": "*"
21
21
  }
22
- }
22
+ }
@@ -1,3 +1,18 @@
1
1
  import type { ToolDefinition, ToolContext } from '@laitszkin/tool-registry';
2
- export declare function readGitHubIssueHandler(argv: string[], context: ToolContext): Promise<number>;
2
+ interface ReadIssueArgs {
3
+ issue: string | null;
4
+ repo: string | null;
5
+ comments: boolean;
6
+ json: boolean;
7
+ }
8
+ /**
9
+ * readGitHubIssueHandler — Wrapped in createToolRunner for schema-based
10
+ * argument parsing. The schema (see tool export) declares --repo, --json,
11
+ * --comments, and --help. Positional <issue> argument comes via positionals[0].
12
+ *
13
+ * Error handling uses UserInputError/SystemError which propagate through
14
+ * createToolRunner's catch block to formatAppError.
15
+ */
16
+ export declare function readGitHubIssueHandler(args: ReadIssueArgs, context: ToolContext): Promise<number>;
3
17
  export declare const tool: ToolDefinition;
18
+ export {};
@@ -1,36 +1,6 @@
1
1
  import { execFile } from 'node:child_process';
2
+ import { UserInputError, SystemError, createToolRunner } from '../../../tool-utils/dist/index.js';
2
3
  const ISSUE_FIELDS = 'number,title,body,state,author,labels,assignees,comments,createdAt,updatedAt,closedAt,url';
3
- function parseArgs(argv) {
4
- const args = {
5
- issue: null,
6
- repo: null,
7
- comments: false,
8
- json: false,
9
- };
10
- let i = 0;
11
- while (i < argv.length) {
12
- const arg = argv[i];
13
- switch (arg) {
14
- case '--repo':
15
- if (i + 1 < argv.length)
16
- args.repo = argv[++i];
17
- break;
18
- case '--comments':
19
- args.comments = true;
20
- break;
21
- case '--json':
22
- args.json = true;
23
- break;
24
- default:
25
- if (!arg.startsWith('-')) {
26
- args.issue = arg;
27
- }
28
- break;
29
- }
30
- i++;
31
- }
32
- return args;
33
- }
34
4
  function runGh(cmdArgs) {
35
5
  return new Promise((resolve) => {
36
6
  execFile('gh', cmdArgs, { maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
@@ -100,26 +70,30 @@ function printSummary(issue, includeComments, context) {
100
70
  }
101
71
  }
102
72
  }
103
- export async function readGitHubIssueHandler(argv, context) {
73
+ /**
74
+ * readGitHubIssueHandler — Wrapped in createToolRunner for schema-based
75
+ * argument parsing. The schema (see tool export) declares --repo, --json,
76
+ * --comments, and --help. Positional <issue> argument comes via positionals[0].
77
+ *
78
+ * Error handling uses UserInputError/SystemError which propagate through
79
+ * createToolRunner's catch block to formatAppError.
80
+ */
81
+ export async function readGitHubIssueHandler(args, context) {
104
82
  const { stdout, stderr } = context;
105
- const args = parseArgs(argv);
106
83
  if (!args.issue) {
107
- stderr.write('Error: issue number or URL is required.\n');
108
- return 1;
84
+ throw new UserInputError('Issue number or URL is required.');
109
85
  }
110
86
  const cmd = buildCommand(args);
111
87
  const result = await runGh(cmd);
112
88
  if (result.exitCode !== 0) {
113
- stderr.write(result.stderr.trim() || 'gh issue view failed.\n');
114
- return result.exitCode;
89
+ throw new SystemError(result.stderr.trim() || 'gh issue view failed');
115
90
  }
116
91
  let issue;
117
92
  try {
118
93
  issue = JSON.parse(result.stdout);
119
94
  }
120
95
  catch {
121
- stderr.write('Error: unable to parse gh output as JSON.\n');
122
- return 1;
96
+ throw new SystemError('Unable to parse gh output as JSON');
123
97
  }
124
98
  if (args.json) {
125
99
  stdout.write(JSON.stringify(issue, null, 2) + '\n');
@@ -133,5 +107,23 @@ export const tool = {
133
107
  name: 'read-github-issue',
134
108
  category: 'GitHub workflows',
135
109
  description: 'Read GitHub issue details through gh.',
136
- handler: readGitHubIssueHandler,
110
+ handler: createToolRunner({
111
+ options: {
112
+ repo: { type: 'string' },
113
+ json: { type: 'boolean' },
114
+ comments: { type: 'boolean' },
115
+ },
116
+ allowPositionals: true,
117
+ usage: 'apltk read-github-issue [options] <issue>',
118
+ description: 'Read GitHub issue details through gh.',
119
+ handler: async (values, positionals, context) => {
120
+ const args = {
121
+ issue: positionals[0] ?? null,
122
+ repo: values.repo ?? null,
123
+ comments: values.comments === true,
124
+ json: values.json === true,
125
+ };
126
+ return readGitHubIssueHandler(args, context);
127
+ },
128
+ }),
137
129
  };