@avantmedia/af 0.0.1

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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +539 -0
  3. package/af +2 -0
  4. package/bun-upgrade.ts +130 -0
  5. package/commands/bun.ts +55 -0
  6. package/commands/changes.ts +35 -0
  7. package/commands/e2e.ts +12 -0
  8. package/commands/help.ts +236 -0
  9. package/commands/install-extension.ts +133 -0
  10. package/commands/jira.ts +577 -0
  11. package/commands/licenses.ts +32 -0
  12. package/commands/npm.ts +55 -0
  13. package/commands/scaffold.ts +105 -0
  14. package/commands/setup.tsx +156 -0
  15. package/commands/spec.ts +405 -0
  16. package/commands/stop-hook.ts +90 -0
  17. package/commands/todo.ts +208 -0
  18. package/commands/versions.ts +150 -0
  19. package/commands/watch.ts +344 -0
  20. package/commands/worktree.ts +424 -0
  21. package/components/change-select.tsx +71 -0
  22. package/components/confirm.tsx +41 -0
  23. package/components/file-conflict.tsx +52 -0
  24. package/components/input.tsx +53 -0
  25. package/components/layout.tsx +70 -0
  26. package/components/messages.tsx +48 -0
  27. package/components/progress.tsx +71 -0
  28. package/components/select.tsx +90 -0
  29. package/components/status-display.tsx +74 -0
  30. package/components/table.tsx +79 -0
  31. package/generated/setup-manifest.ts +67 -0
  32. package/git-worktree.ts +184 -0
  33. package/main.ts +12 -0
  34. package/npm-upgrade.ts +117 -0
  35. package/package.json +83 -0
  36. package/resources/copy-prompt-reporter.ts +443 -0
  37. package/router.ts +220 -0
  38. package/setup/.claude/commands/commit-work.md +47 -0
  39. package/setup/.claude/commands/complete-work.md +34 -0
  40. package/setup/.claude/commands/e2e.md +29 -0
  41. package/setup/.claude/commands/start-work.md +51 -0
  42. package/setup/.claude/skills/pm/SKILL.md +294 -0
  43. package/setup/.claude/skills/pm/templates/api-endpoint.md +69 -0
  44. package/setup/.claude/skills/pm/templates/bug-fix.md +77 -0
  45. package/setup/.claude/skills/pm/templates/feature.md +87 -0
  46. package/setup/.claude/skills/pm/templates/ui-component.md +78 -0
  47. package/utils/change-select-render.tsx +44 -0
  48. package/utils/claude.ts +9 -0
  49. package/utils/config.ts +58 -0
  50. package/utils/env.ts +53 -0
  51. package/utils/git.ts +120 -0
  52. package/utils/ink-render.tsx +50 -0
  53. package/utils/openspec.ts +54 -0
  54. package/utils/output.ts +104 -0
  55. package/utils/proposal.ts +160 -0
  56. package/utils/resources.ts +64 -0
  57. package/utils/setup-files.ts +230 -0
@@ -0,0 +1,69 @@
1
+ # API Endpoint Template
2
+
3
+ Work breakdown for implementing a new REST or GraphQL API endpoint.
4
+
5
+ ## Task Structure
6
+
7
+ When breaking down an API endpoint epic, create these subtasks:
8
+
9
+ ### 1. Design & Planning
10
+ - **Summary**: Design {endpoint} API contract
11
+ - **Description**: Define request/response schemas, error codes, and edge cases
12
+ - **Estimate**: 1-2h
13
+
14
+ ### 2. Database/Entity Changes (if needed)
15
+ - **Summary**: Add database entities for {endpoint}
16
+ - **Description**: Create or modify TypeORM entities, generate migration
17
+ - **Estimate**: 1-2h
18
+
19
+ ### 3. Controller Implementation
20
+ - **Summary**: Implement {endpoint} controller
21
+ - **Description**: Create endpoint with request validation, business logic, response formatting
22
+ - **Estimate**: 2-4h
23
+
24
+ ### 4. Input Validation
25
+ - **Summary**: Add validation for {endpoint} inputs
26
+ - **Description**: Implement request validation with proper error messages
27
+ - **Estimate**: 1h
28
+
29
+ ### 5. Error Handling
30
+ - **Summary**: Add error handling for {endpoint}
31
+ - **Description**: Handle edge cases, return appropriate error codes
32
+ - **Estimate**: 1h
33
+
34
+ ### 6. Unit Tests
35
+ - **Summary**: Write unit tests for {endpoint}
36
+ - **Description**: Test controller logic, validation, error cases
37
+ - **Estimate**: 2-3h
38
+
39
+ ### 7. Integration Tests (optional)
40
+ - **Summary**: Write integration tests for {endpoint}
41
+ - **Description**: Test full request/response cycle with database
42
+ - **Estimate**: 1-2h
43
+
44
+ ### 8. Documentation (optional)
45
+ - **Summary**: Document {endpoint} API
46
+ - **Description**: Add OpenAPI/Swagger docs or update API documentation
47
+ - **Estimate**: 30m
48
+
49
+ ## Example Jira Commands
50
+
51
+ ```bash
52
+ # Create subtasks for PROJ-100 (the epic)
53
+ af jira create --project PROJ --type Sub-task --summary "Design user preferences API contract" --parent PROJ-100
54
+
55
+ af jira create --project PROJ --type Sub-task --summary "Implement user preferences controller" --parent PROJ-100
56
+
57
+ af jira create --project PROJ --type Sub-task --summary "Add validation for user preferences inputs" --parent PROJ-100
58
+
59
+ af jira create --project PROJ --type Sub-task --summary "Write unit tests for user preferences endpoint" --parent PROJ-100
60
+ ```
61
+
62
+ ## Checklist
63
+
64
+ Before marking the epic as done:
65
+ - [ ] Endpoint accessible and returns expected responses
66
+ - [ ] Input validation working with clear error messages
67
+ - [ ] Error cases handled gracefully
68
+ - [ ] Unit tests passing
69
+ - [ ] No security vulnerabilities (auth, injection, etc.)
@@ -0,0 +1,77 @@
1
+ # Bug Fix Template
2
+
3
+ Work breakdown for investigating and fixing a bug.
4
+
5
+ ## Task Structure
6
+
7
+ When breaking down a bug fix, create these subtasks:
8
+
9
+ ### 1. Reproduce
10
+ - **Summary**: Reproduce {bug}
11
+ - **Description**: Confirm reproduction steps, document environment, capture evidence
12
+ - **Estimate**: 30m-1h
13
+
14
+ ### 2. Investigate
15
+ - **Summary**: Investigate root cause of {bug}
16
+ - **Description**: Debug, trace code paths, identify the source of the issue
17
+ - **Estimate**: 1-3h
18
+
19
+ ### 3. Implement Fix
20
+ - **Summary**: Fix {bug}
21
+ - **Description**: Implement the solution, handle edge cases
22
+ - **Estimate**: 1-3h
23
+
24
+ ### 4. Write Regression Test
25
+ - **Summary**: Add regression test for {bug}
26
+ - **Description**: Write test that would have caught this bug
27
+ - **Estimate**: 30m-1h
28
+
29
+ ### 5. Verify Fix
30
+ - **Summary**: Verify {bug} is fixed
31
+ - **Description**: Confirm original reproduction steps no longer reproduce the issue
32
+ - **Estimate**: 30m
33
+
34
+ ## Simplified Flow (Minor Bugs)
35
+
36
+ For simple bugs, combine into fewer tasks:
37
+
38
+ ### 1. Investigate & Fix
39
+ - **Summary**: Investigate and fix {bug}
40
+ - **Description**: Reproduce, find root cause, implement fix
41
+ - **Estimate**: 1-2h
42
+
43
+ ### 2. Add Test
44
+ - **Summary**: Add regression test for {bug}
45
+ - **Description**: Write test to prevent recurrence
46
+ - **Estimate**: 30m
47
+
48
+ ## Example Jira Commands
49
+
50
+ ```bash
51
+ # Create subtasks for PROJ-300 (the bug)
52
+ af jira create --project PROJ --type Sub-task --summary "Reproduce login timeout bug" --parent PROJ-300
53
+
54
+ af jira create --project PROJ --type Sub-task --summary "Investigate root cause of login timeout" --parent PROJ-300
55
+
56
+ af jira create --project PROJ --type Sub-task --summary "Fix login timeout bug" --parent PROJ-300
57
+
58
+ af jira create --project PROJ --type Sub-task --summary "Add regression test for login timeout" --parent PROJ-300
59
+ ```
60
+
61
+ ## Investigation Checklist
62
+
63
+ When investigating:
64
+ - [ ] Can reproduce consistently?
65
+ - [ ] What changed recently? (git log, deployments)
66
+ - [ ] Environment-specific? (browser, OS, data)
67
+ - [ ] Related to recent code changes?
68
+ - [ ] Are there similar past bugs?
69
+
70
+ ## Fix Checklist
71
+
72
+ Before marking as done:
73
+ - [ ] Bug no longer reproducible
74
+ - [ ] Regression test added
75
+ - [ ] No new issues introduced
76
+ - [ ] Related areas tested
77
+ - [ ] Root cause documented in ticket
@@ -0,0 +1,87 @@
1
+ # Feature Template
2
+
3
+ Work breakdown for implementing a general feature.
4
+
5
+ ## Task Structure
6
+
7
+ When breaking down a feature epic, create these subtasks:
8
+
9
+ ### 1. Requirements Analysis
10
+ - **Summary**: Analyze requirements for {feature}
11
+ - **Description**: Review specs, identify edge cases, clarify with stakeholders
12
+ - **Estimate**: 1-2h
13
+
14
+ ### 2. Technical Design
15
+ - **Summary**: Design implementation approach for {feature}
16
+ - **Description**: Plan architecture, data models, API changes, UI components
17
+ - **Estimate**: 1-3h
18
+
19
+ ### 3. Backend Implementation
20
+ - **Summary**: Implement backend for {feature}
21
+ - **Description**: Database changes, API endpoints, business logic
22
+ - **Estimate**: 2-8h (varies by complexity)
23
+
24
+ ### 4. Frontend Implementation
25
+ - **Summary**: Implement frontend for {feature}
26
+ - **Description**: UI components, state management, API integration
27
+ - **Estimate**: 2-8h (varies by complexity)
28
+
29
+ ### 5. Integration
30
+ - **Summary**: Integrate {feature} frontend and backend
31
+ - **Description**: Connect UI to API, handle loading/error states
32
+ - **Estimate**: 1-2h
33
+
34
+ ### 6. Testing
35
+ - **Summary**: Test {feature}
36
+ - **Description**: Unit tests, integration tests, E2E tests
37
+ - **Estimate**: 2-4h
38
+
39
+ ### 7. Documentation
40
+ - **Summary**: Document {feature}
41
+ - **Description**: Update user docs, API docs, or internal documentation
42
+ - **Estimate**: 1h
43
+
44
+ ## Scaling for Complexity
45
+
46
+ **Small Feature** (1-2 days):
47
+ - Combine analysis + design
48
+ - Single implementation task
49
+ - Basic testing
50
+
51
+ **Medium Feature** (3-5 days):
52
+ - Separate analysis and design
53
+ - Split backend/frontend
54
+ - Comprehensive testing
55
+
56
+ **Large Feature** (1+ weeks):
57
+ - Detailed requirements analysis
58
+ - Technical design document
59
+ - Multiple implementation phases
60
+ - Phased testing (unit -> integration -> E2E)
61
+ - Documentation and training
62
+
63
+ ## Example Jira Commands
64
+
65
+ ```bash
66
+ # Create subtasks for PROJ-400 (the epic)
67
+ af jira create --project PROJ --type Sub-task --summary "Analyze requirements for email notifications" --parent PROJ-400
68
+
69
+ af jira create --project PROJ --type Sub-task --summary "Design email notification system" --parent PROJ-400
70
+
71
+ af jira create --project PROJ --type Sub-task --summary "Implement email notification backend" --parent PROJ-400
72
+
73
+ af jira create --project PROJ --type Sub-task --summary "Implement email notification UI" --parent PROJ-400
74
+
75
+ af jira create --project PROJ --type Sub-task --summary "Write tests for email notifications" --parent PROJ-400
76
+ ```
77
+
78
+ ## Definition of Done
79
+
80
+ Before marking the epic as done:
81
+ - [ ] All acceptance criteria met
82
+ - [ ] Code reviewed and approved
83
+ - [ ] Tests passing (unit, integration, E2E as appropriate)
84
+ - [ ] No known bugs or regressions
85
+ - [ ] Documentation updated
86
+ - [ ] Deployed to staging and verified
87
+ - [ ] Product owner sign-off (if applicable)
@@ -0,0 +1,78 @@
1
+ # UI Component Template
2
+
3
+ Work breakdown for implementing a new React UI component or page.
4
+
5
+ ## Task Structure
6
+
7
+ When breaking down a UI component epic, create these subtasks:
8
+
9
+ ### 1. Design Review
10
+ - **Summary**: Review design for {component}
11
+ - **Description**: Analyze mockups/designs, identify edge cases, confirm responsive requirements
12
+ - **Estimate**: 30m-1h
13
+
14
+ ### 2. Component Structure
15
+ - **Summary**: Create {component} component structure
16
+ - **Description**: Set up component files, props interface, basic layout
17
+ - **Estimate**: 1-2h
18
+
19
+ ### 3. Styling
20
+ - **Summary**: Implement {component} styling
21
+ - **Description**: Apply design system styles, handle responsive breakpoints
22
+ - **Estimate**: 1-2h
23
+
24
+ ### 4. State Management
25
+ - **Summary**: Add state management for {component}
26
+ - **Description**: Implement local state, connect to global state if needed
27
+ - **Estimate**: 1-2h
28
+
29
+ ### 5. API Integration
30
+ - **Summary**: Connect {component} to API
31
+ - **Description**: Implement data fetching, loading states, error handling
32
+ - **Estimate**: 1-2h
33
+
34
+ ### 6. User Interactions
35
+ - **Summary**: Implement {component} interactions
36
+ - **Description**: Add event handlers, form validation, user feedback
37
+ - **Estimate**: 1-2h
38
+
39
+ ### 7. Accessibility
40
+ - **Summary**: Add accessibility to {component}
41
+ - **Description**: ARIA labels, keyboard navigation, screen reader support
42
+ - **Estimate**: 1h
43
+
44
+ ### 8. Unit Tests
45
+ - **Summary**: Write tests for {component}
46
+ - **Description**: Test component rendering, interactions, edge cases
47
+ - **Estimate**: 1-2h
48
+
49
+ ### 9. E2E Tests
50
+ - **Summary**: Add E2E test for {component}
51
+ - **Description**: Test user flow with Playwright
52
+ - **Estimate**: 1h
53
+
54
+ ## Example Jira Commands
55
+
56
+ ```bash
57
+ # Create subtasks for PROJ-200 (the epic)
58
+ af jira create --project PROJ --type Sub-task --summary "Create BookingCalendar component structure" --parent PROJ-200
59
+
60
+ af jira create --project PROJ --type Sub-task --summary "Implement BookingCalendar styling" --parent PROJ-200
61
+
62
+ af jira create --project PROJ --type Sub-task --summary "Connect BookingCalendar to booking API" --parent PROJ-200
63
+
64
+ af jira create --project PROJ --type Sub-task --summary "Write tests for BookingCalendar" --parent PROJ-200
65
+
66
+ af jira create --project PROJ --type Sub-task --summary "Add E2E test for BookingCalendar flow" --parent PROJ-200
67
+ ```
68
+
69
+ ## Checklist
70
+
71
+ Before marking the epic as done:
72
+ - [ ] Component matches design mockups
73
+ - [ ] Responsive on all target breakpoints
74
+ - [ ] Loading and error states implemented
75
+ - [ ] Keyboard accessible
76
+ - [ ] Unit tests passing
77
+ - [ ] E2E test covering main flow
78
+ - [ ] No console errors or warnings
@@ -0,0 +1,44 @@
1
+ import React from 'react';
2
+ import { ChangeSelect } from '../components/change-select.tsx';
3
+ import { render } from './ink-render.tsx';
4
+ import type { OngoingChange } from './openspec.ts';
5
+
6
+ /**
7
+ * Render the interactive change selection UI and return the selected change ID.
8
+ *
9
+ * @param changes - Array of ongoing changes to choose from
10
+ * @param prompt - Custom prompt text to display (default: "Select a change:")
11
+ * @returns Promise that resolves to selected change ID, or null if cancelled
12
+ */
13
+ export function renderChangeSelect(
14
+ changes: OngoingChange[],
15
+ prompt?: string,
16
+ ): Promise<string | null> {
17
+ return new Promise(resolve => {
18
+ let cancelled = false;
19
+
20
+ const { unmount } = render(
21
+ <ChangeSelect
22
+ changes={changes}
23
+ prompt={prompt}
24
+ onSelect={selectedId => {
25
+ unmount();
26
+ resolve(selectedId);
27
+ }}
28
+ onCancel={() => {
29
+ cancelled = true;
30
+ unmount();
31
+ resolve(null);
32
+ }}
33
+ />,
34
+ );
35
+
36
+ // Handle case where component is unmounted without selection
37
+ process.on('SIGINT', () => {
38
+ if (!cancelled) {
39
+ unmount();
40
+ resolve(null);
41
+ }
42
+ });
43
+ });
44
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Gets the configured agent command name from the ARTIFEX_AGENT environment variable.
3
+ * Falls back to 'claude' if not set.
4
+ *
5
+ * @returns The agent command name to use
6
+ */
7
+ export function getAgentCommand(): string {
8
+ return process.env.ARTIFEX_AGENT || 'claude';
9
+ }
@@ -0,0 +1,58 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ /**
5
+ * Configuration for the stop-hook command.
6
+ */
7
+ export interface StopHookConfig {
8
+ ignoredPaths: string[];
9
+ command: string;
10
+ }
11
+
12
+ /**
13
+ * Full af.json configuration structure.
14
+ */
15
+ export interface AfConfig {
16
+ stopHook?: Partial<StopHookConfig>;
17
+ }
18
+
19
+ /**
20
+ * Default configuration values.
21
+ */
22
+ const DEFAULT_STOP_HOOK_CONFIG: StopHookConfig = {
23
+ ignoredPaths: ['openspec/'],
24
+ command: 'af e2e',
25
+ };
26
+
27
+ /**
28
+ * Load configuration from af.json in the current working directory.
29
+ * Returns undefined if the file doesn't exist.
30
+ * Throws if the file exists but is invalid JSON.
31
+ */
32
+ export function loadAfConfig(): AfConfig | undefined {
33
+ const configPath = join(process.cwd(), 'af.json');
34
+
35
+ if (!existsSync(configPath)) {
36
+ return undefined;
37
+ }
38
+
39
+ const content = readFileSync(configPath, 'utf-8');
40
+ return JSON.parse(content) as AfConfig;
41
+ }
42
+
43
+ /**
44
+ * Get the stop-hook configuration, merging af.json settings with defaults.
45
+ * If ignoredPaths is specified in af.json, it completely replaces the defaults.
46
+ */
47
+ export function getStopHookConfig(): StopHookConfig {
48
+ const afConfig = loadAfConfig();
49
+
50
+ if (!afConfig?.stopHook) {
51
+ return DEFAULT_STOP_HOOK_CONFIG;
52
+ }
53
+
54
+ return {
55
+ ignoredPaths: afConfig.stopHook.ignoredPaths ?? DEFAULT_STOP_HOOK_CONFIG.ignoredPaths,
56
+ command: afConfig.stopHook.command ?? DEFAULT_STOP_HOOK_CONFIG.command,
57
+ };
58
+ }
package/utils/env.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+
4
+ /**
5
+ * Load environment variables from a .env file in the current working directory.
6
+ * Silently skips if the file doesn't exist - never fails.
7
+ *
8
+ * Features:
9
+ * - Parses KEY=value pairs
10
+ * - Handles quoted values (single and double quotes)
11
+ * - Ignores comments (lines starting with #)
12
+ * - Ignores empty lines
13
+ * - Does not overwrite existing environment variables
14
+ */
15
+ export function loadEnv(): void {
16
+ const envPath = join(process.cwd(), '.env');
17
+
18
+ if (!existsSync(envPath)) {
19
+ return; // Silently skip if .env doesn't exist
20
+ }
21
+
22
+ const content = readFileSync(envPath, 'utf-8');
23
+
24
+ for (const line of content.split('\n')) {
25
+ const trimmed = line.trim();
26
+
27
+ // Skip empty lines and comments
28
+ if (!trimmed || trimmed.startsWith('#')) {
29
+ continue;
30
+ }
31
+
32
+ const eqIndex = trimmed.indexOf('=');
33
+ if (eqIndex === -1) {
34
+ continue;
35
+ }
36
+
37
+ const key = trimmed.slice(0, eqIndex).trim();
38
+ let value = trimmed.slice(eqIndex + 1).trim();
39
+
40
+ // Remove surrounding quotes if present
41
+ if (
42
+ (value.startsWith('"') && value.endsWith('"')) ||
43
+ (value.startsWith("'") && value.endsWith("'"))
44
+ ) {
45
+ value = value.slice(1, -1);
46
+ }
47
+
48
+ // Don't overwrite existing environment variables
49
+ if (process.env[key] === undefined) {
50
+ process.env[key] = value;
51
+ }
52
+ }
53
+ }
package/utils/git.ts ADDED
@@ -0,0 +1,120 @@
1
+ import { execSync } from 'node:child_process';
2
+
3
+ /**
4
+ * Stage files in a specific directory for git commit.
5
+ *
6
+ * @param directory - The directory path to stage (relative to repo root)
7
+ * @returns true if staging succeeds, false otherwise
8
+ */
9
+ export function stageDirectory(directory: string): boolean {
10
+ try {
11
+ console.log(`git add "${directory}"`);
12
+ execSync(`git add "${directory}"`, { stdio: 'pipe' });
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Create a git commit with the specified message.
21
+ *
22
+ * @param message - The commit message
23
+ * @returns true if commit succeeds, false otherwise
24
+ */
25
+ export function createCommit(message: string): boolean {
26
+ try {
27
+ execSync(`git commit -m "${message}"`, { stdio: 'pipe' });
28
+ return true;
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Stage files and create a commit.
36
+ *
37
+ * @param directory - The directory to stage
38
+ * @param message - The commit message
39
+ * @returns Object with success status and optional error message
40
+ */
41
+ export function stageAndCommit(
42
+ directory: string,
43
+ message: string,
44
+ ): { success: boolean; error?: string } {
45
+ if (!stageDirectory(directory)) {
46
+ return { success: false, error: 'Failed to stage files' };
47
+ }
48
+
49
+ if (!createCommit(message)) {
50
+ return { success: false, error: 'Failed to create commit' };
51
+ }
52
+
53
+ return { success: true };
54
+ }
55
+
56
+ /**
57
+ * Stage all changes (tracked and untracked) and create a commit.
58
+ *
59
+ * @param message - The commit message
60
+ * @returns Object with success status and optional error message
61
+ */
62
+ export function stageAllAndCommit(message: string): { success: boolean; error?: string } {
63
+ try {
64
+ execSync('git add -A', { stdio: 'pipe' });
65
+ } catch {
66
+ return { success: false, error: 'Failed to stage files' };
67
+ }
68
+
69
+ if (!createCommit(message)) {
70
+ return { success: false, error: 'Failed to create commit' };
71
+ }
72
+
73
+ return { success: true };
74
+ }
75
+
76
+ /**
77
+ * Check if there are any changes to commit.
78
+ *
79
+ * @returns true if there are staged or unstaged changes, false otherwise
80
+ */
81
+ export function hasChangesToCommit(): boolean {
82
+ try {
83
+ const status = execSync('git status --porcelain', { stdio: 'pipe' }).toString();
84
+ return status.trim().length > 0;
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Stage all changes and create a commit with optional trailers.
92
+ *
93
+ * @param message - The commit message
94
+ * @param trailers - Array of trailer objects with key and value
95
+ * @returns Object with success status and optional error message
96
+ */
97
+ export function stageAllAndCommitWithTrailers(
98
+ message: string,
99
+ trailers: Array<{ key: string; value: string }> = [],
100
+ ): { success: boolean; error?: string } {
101
+ try {
102
+ execSync('git add .', { stdio: 'pipe' });
103
+ } catch {
104
+ return { success: false, error: 'Failed to stage files' };
105
+ }
106
+
107
+ // Build commit command with trailers
108
+ const trailerArgs = trailers.map(t => `--trailer "${t.key}: ${t.value}"`).join(' ');
109
+ const commitCmd = trailerArgs
110
+ ? `git commit -m "${message}" ${trailerArgs}`
111
+ : `git commit -m "${message}"`;
112
+
113
+ try {
114
+ execSync(commitCmd, { stdio: 'pipe' });
115
+ } catch {
116
+ return { success: false, error: 'Failed to create commit' };
117
+ }
118
+
119
+ return { success: true };
120
+ }
@@ -0,0 +1,50 @@
1
+ import { render as inkRender } from 'ink';
2
+ import type { ReactElement } from 'react';
3
+
4
+ /**
5
+ * Render an Ink component to the terminal with signal handling
6
+ * @param element The React element to render
7
+ * @returns Object with cleanup function and promise that resolves when component unmounts
8
+ */
9
+ export function render(element: ReactElement) {
10
+ const { unmount, waitUntilExit, clear } = inkRender(element);
11
+
12
+ // Handle graceful shutdown on SIGINT (Ctrl+C)
13
+ const handleSigInt = () => {
14
+ unmount();
15
+ process.exit(0);
16
+ };
17
+
18
+ // Handle graceful shutdown on SIGTERM
19
+ const handleSigTerm = () => {
20
+ unmount();
21
+ process.exit(0);
22
+ };
23
+
24
+ process.on('SIGINT', handleSigInt);
25
+ process.on('SIGTERM', handleSigTerm);
26
+
27
+ // Clean up signal handlers when component unmounts
28
+ const cleanup = () => {
29
+ process.off('SIGINT', handleSigInt);
30
+ process.off('SIGTERM', handleSigTerm);
31
+ unmount();
32
+ };
33
+
34
+ return {
35
+ unmount,
36
+ cleanup,
37
+ waitUntilExit,
38
+ clear,
39
+ };
40
+ }
41
+
42
+ /**
43
+ * Render an Ink component synchronously and wait for it to finish
44
+ * Useful for fire-and-forget static content
45
+ * @param element The React element to render
46
+ */
47
+ export function renderSync(element: ReactElement): void {
48
+ const { waitUntilExit } = render(element);
49
+ waitUntilExit();
50
+ }