cyrus-edge-worker 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Ceedar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,293 @@
1
+ # @cyrus/edge-worker
2
+
3
+ Unified edge worker for processing Linear issues with Claude. Handles webhook events, manages Claude sessions, and posts comments back to Linear.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pnpm add @cyrus/edge-worker
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ The EdgeWorker supports multiple repository/Linear workspace pairs. Each repository configuration includes:
14
+ - Git repository path and branch
15
+ - Linear workspace ID and OAuth token
16
+ - Workspace directory for issue processing
17
+
18
+ ### Single Repository Example (CLI)
19
+
20
+ ```typescript
21
+ import { EdgeWorker } from '@cyrus/edge-worker'
22
+
23
+ const edgeWorker = new EdgeWorker({
24
+ // Connection
25
+ proxyUrl: 'https://edge-proxy.example.com',
26
+
27
+ // Claude
28
+ claudePath: '/usr/local/bin/claude',
29
+ defaultAllowedTools: ['bash', 'edit', 'read'],
30
+
31
+ // Single repository configuration
32
+ repositories: [{
33
+ id: 'main-repo',
34
+ name: 'Main Repository',
35
+ repositoryPath: '/home/user/projects/main',
36
+ baseBranch: 'main',
37
+ linearWorkspaceId: 'workspace-123',
38
+ linearToken: await oauthHelper.getAccessToken(),
39
+ workspaceBaseDir: '/home/user/.cyrus/workspaces/main'
40
+ }],
41
+
42
+ // Optional handlers
43
+ handlers: {
44
+ // Custom workspace creation (e.g., git worktrees)
45
+ createWorkspace: async (issue, repository) => {
46
+ const path = await createGitWorktree(
47
+ repository.repositoryPath,
48
+ issue.identifier,
49
+ repository.baseBranch
50
+ )
51
+ return { path, isGitWorktree: true }
52
+ },
53
+
54
+ // Log errors
55
+ onError: (error) => {
56
+ console.error('EdgeWorker error:', error)
57
+ }
58
+ },
59
+
60
+ // Features
61
+ features: {
62
+ enableContinuation: true,
63
+ enableTokenLimitHandling: true
64
+ }
65
+ })
66
+
67
+ // Start processing
68
+ await edgeWorker.start()
69
+ ```
70
+
71
+ ### Multi-Repository Example (Electron)
72
+
73
+ ```typescript
74
+ import { EdgeWorker } from '@cyrus/edge-worker'
75
+
76
+ // Load repository configurations from user settings
77
+ const repositories = userSettings.repositories.map(repo => ({
78
+ id: repo.id,
79
+ name: repo.name,
80
+ repositoryPath: repo.path,
81
+ baseBranch: repo.branch || 'main',
82
+ linearWorkspaceId: repo.linearWorkspaceId,
83
+ linearToken: repo.linearToken, // Each repo can have its own token
84
+ workspaceBaseDir: path.join(app.getPath('userData'), 'workspaces', repo.id),
85
+ isActive: repo.enabled
86
+ }))
87
+
88
+ const edgeWorker = new EdgeWorker({
89
+ proxyUrl: config.proxyUrl,
90
+ claudePath: getClaudePath(),
91
+
92
+ // Multiple repositories
93
+ repositories,
94
+
95
+ // UI updates with repository context
96
+ handlers: {
97
+ onClaudeEvent: (issueId, event, repositoryId) => {
98
+ // Update UI with Claude's progress
99
+ mainWindow.webContents.send('claude-event', {
100
+ issueId,
101
+ event,
102
+ repository: repositories.find(r => r.id === repositoryId)
103
+ })
104
+ },
105
+
106
+ onSessionStart: (issueId, issue, repositoryId) => {
107
+ const repo = repositories.find(r => r.id === repositoryId)
108
+ // Show notification
109
+ new Notification({
110
+ title: `Processing Issue in ${repo.name}`,
111
+ body: `Working on ${issue.identifier}: ${issue.title}`
112
+ }).show()
113
+ },
114
+
115
+ createWorkspace: async (issue, repository) => {
116
+ // Create git worktree for the specific repository
117
+ const worktreePath = await createWorktree(
118
+ repository.repositoryPath,
119
+ issue.identifier,
120
+ repository.baseBranch
121
+ )
122
+ return { path: worktreePath, isGitWorktree: true }
123
+ }
124
+ }
125
+ })
126
+
127
+ await edgeWorker.start()
128
+ ```
129
+
130
+ ## Configuration
131
+
132
+ ### Required Config
133
+
134
+ - `proxyUrl`: URL of the edge proxy server
135
+ - `claudePath`: Path to Claude CLI executable
136
+ - `repositories`: Array of repository configurations
137
+
138
+ ### Repository Configuration
139
+
140
+ Each repository in the `repositories` array requires:
141
+
142
+ - `id`: Unique identifier for the repository
143
+ - `name`: Display name for the repository
144
+ - `repositoryPath`: Local git repository path
145
+ - `baseBranch`: Branch to create worktrees from (e.g., 'main')
146
+ - `linearWorkspaceId`: Linear workspace/team ID
147
+ - `linearToken`: OAuth token for this Linear workspace
148
+ - `workspaceBaseDir`: Where to create issue workspaces
149
+
150
+ Optional per-repository settings:
151
+ - `isActive`: Whether to process webhooks (default: true)
152
+ - `promptTemplatePath`: Custom prompt template for this repo
153
+
154
+ ### Optional Handlers
155
+
156
+ All handlers are optional and now include repository context:
157
+
158
+ - `createWorkspace(issue, repository)`: Custom workspace creation
159
+ - `onClaudeEvent(issueId, event, repositoryId)`: Claude event updates
160
+ - `onSessionStart(issueId, issue, repositoryId)`: Session started
161
+ - `onSessionEnd(issueId, exitCode, repositoryId)`: Session ended
162
+ - `onError(error, context)`: Error handling
163
+
164
+ ### Features
165
+
166
+ - `enableContinuation`: Use `--continue` flag for follow-up comments (default: true)
167
+ - `enableTokenLimitHandling`: Auto-restart on token limits (default: true)
168
+ - `enableAttachmentDownload`: Download issue attachments (default: false)
169
+
170
+ ## Events
171
+
172
+ The EdgeWorker extends EventEmitter and emits:
173
+
174
+ - `connected`: Connected to proxy (token)
175
+ - `disconnected`: Disconnected from proxy (token, reason)
176
+ - `session:started`: Claude session started (issueId, issue, repositoryId)
177
+ - `session:ended`: Claude session ended (issueId, exitCode, repositoryId)
178
+ - `claude:event`: Any Claude event (issueId, event, repositoryId)
179
+ - `claude:response`: Claude text response (issueId, text, repositoryId)
180
+ - `claude:tool-use`: Claude used a tool (issueId, tool, input, repositoryId)
181
+ - `error`: Error occurred (error, context)
182
+
183
+ ## Architecture
184
+
185
+ ```
186
+ Your App (CLI/Electron)
187
+ ↓ provides repository configs
188
+ EdgeWorker
189
+ ↓ manages multiple repositories
190
+ ├─→ Repository 1 (token A) ─→ Linear Workspace 1
191
+ ├─→ Repository 2 (token A) ─→ Linear Workspace 1
192
+ └─→ Repository 3 (token B) ─→ Linear Workspace 2
193
+ ↓ connects to proxy (grouped by token)
194
+ Edge Proxy
195
+ ↓ webhooks from all workspaces
196
+ Linear
197
+ ```
198
+
199
+ Key features:
200
+ - Multiple repositories can share the same Linear workspace/token
201
+ - Repositories with different tokens connect separately to minimize connections
202
+ - Each repository has its own workspace directory and configuration
203
+ - OAuth tokens serve dual purpose: proxy auth and Linear API calls
204
+
205
+ ## Prompt Templates
206
+
207
+ The EdgeWorker supports customizable prompt templates to tailor Claude's behavior for different repositories or workflows.
208
+
209
+ ### Default Template
210
+
211
+ If no custom template is specified, EdgeWorker uses a built-in template that helps Claude determine whether to:
212
+ 1. **Execute** - When requirements are clear, implement the solution
213
+ 2. **Clarify** - When requirements are vague, ask clarifying questions
214
+
215
+ ### Custom Templates
216
+
217
+ You can provide custom templates at two levels:
218
+ - **Global**: Via `config.features.promptTemplatePath`
219
+ - **Per-repository**: Via `repository.promptTemplatePath` (takes precedence)
220
+
221
+ ### Template Variables
222
+
223
+ Templates use Handlebars-style variables that are automatically replaced:
224
+
225
+ - `{{repository_name}}` - Name of the repository
226
+ - `{{issue_id}}` - Linear issue ID
227
+ - `{{issue_title}}` - Issue title
228
+ - `{{issue_description}}` - Full issue description
229
+ - `{{issue_state}}` - Current issue state
230
+ - `{{issue_priority}}` - Priority level (0-4)
231
+ - `{{issue_url}}` - Direct link to Linear issue
232
+ - `{{comment_history}}` - All previous comments formatted
233
+ - `{{latest_comment}}` - Most recent comment text
234
+ - `{{working_directory}}` - Current working directory
235
+ - `{{base_branch}}` - Base git branch (e.g., 'main')
236
+ - `{{branch_name}}` - Issue branch name
237
+
238
+ ### Example Custom Template
239
+
240
+ ```markdown
241
+ You are an expert {{repository_name}} developer.
242
+
243
+ ## Current Task
244
+ **Issue**: {{issue_title}} (#{{issue_id}})
245
+ **Priority**: {{issue_priority}}
246
+ **Status**: {{issue_state}}
247
+
248
+ ## Description
249
+ {{issue_description}}
250
+
251
+ ## Previous Discussion
252
+ {{comment_history}}
253
+
254
+ ## Technical Context
255
+ - Repository: {{repository_name}}
256
+ - Working Directory: {{working_directory}}
257
+ - Branch: {{branch_name}} (from {{base_branch}})
258
+
259
+ ## Instructions
260
+ 1. Review the issue requirements carefully
261
+ 2. Check existing code patterns in the repository
262
+ 3. Implement a solution following project conventions
263
+ 4. Ensure all tests pass
264
+ 5. Create a descriptive pull request
265
+
266
+ Remember to ask clarifying questions if requirements are unclear.
267
+ ```
268
+
269
+ ### Using Custom Templates
270
+
271
+ ```typescript
272
+ // Global template for all repositories
273
+ const edgeWorker = new EdgeWorker({
274
+ // ... other config
275
+ features: {
276
+ promptTemplatePath: './prompts/default.md'
277
+ }
278
+ })
279
+
280
+ // Per-repository templates
281
+ const edgeWorker = new EdgeWorker({
282
+ repositories: [{
283
+ id: 'frontend',
284
+ name: 'Frontend App',
285
+ // ... other config
286
+ promptTemplatePath: './prompts/frontend-specific.md'
287
+ }, {
288
+ id: 'backend',
289
+ name: 'Backend API',
290
+ // ... other config
291
+ promptTemplatePath: './prompts/backend-specific.md'
292
+ }]
293
+ })
@@ -0,0 +1,142 @@
1
+ import { EventEmitter } from 'events';
2
+ import type { EdgeWorkerConfig, EdgeWorkerEvents } from './types.js';
3
+ export declare interface EdgeWorker {
4
+ on<K extends keyof EdgeWorkerEvents>(event: K, listener: EdgeWorkerEvents[K]): this;
5
+ emit<K extends keyof EdgeWorkerEvents>(event: K, ...args: Parameters<EdgeWorkerEvents[K]>): boolean;
6
+ }
7
+ /**
8
+ * Unified edge worker that orchestrates NDJSON streaming, Claude processing, and Linear integration
9
+ */
10
+ export declare class EdgeWorker extends EventEmitter {
11
+ private config;
12
+ private repositories;
13
+ private linearClients;
14
+ private ndjsonClients;
15
+ private sessionManager;
16
+ private claudeRunners;
17
+ private sessionToRepo;
18
+ private issueToCommentId;
19
+ private issueToReplyContext;
20
+ constructor(config: EdgeWorkerConfig);
21
+ /**
22
+ * Start the edge worker
23
+ */
24
+ start(): Promise<void>;
25
+ /**
26
+ * Stop the edge worker
27
+ */
28
+ stop(): Promise<void>;
29
+ /**
30
+ * Handle connection established
31
+ */
32
+ private handleConnect;
33
+ /**
34
+ * Handle disconnection
35
+ */
36
+ private handleDisconnect;
37
+ /**
38
+ * Handle errors
39
+ */
40
+ private handleError;
41
+ /**
42
+ * Check if Claude logs exist for a workspace
43
+ */
44
+ private hasExistingLogs;
45
+ /**
46
+ * Handle webhook events from proxy
47
+ */
48
+ private handleWebhook;
49
+ /**
50
+ * Handle Agent API notifications
51
+ */
52
+ private handleAgentNotification;
53
+ /**
54
+ * Handle legacy webhook format
55
+ */
56
+ private handleLegacyWebhook;
57
+ /**
58
+ * Find the repository configuration for a webhook
59
+ */
60
+ private findRepositoryForWebhook;
61
+ /**
62
+ * Extract workspace ID from webhook data
63
+ */
64
+ private extractWorkspaceId;
65
+ /**
66
+ * Handle issue assignment
67
+ */
68
+ private handleIssueAssigned;
69
+ /**
70
+ * Handle new comment on issue
71
+ */
72
+ private handleNewComment;
73
+ /**
74
+ * Handle issue unassignment
75
+ */
76
+ private handleIssueUnassigned;
77
+ /**
78
+ * Handle Claude events
79
+ */
80
+ private handleClaudeEvent;
81
+ /**
82
+ * Handle Claude process exit
83
+ */
84
+ private handleClaudeExit;
85
+ /**
86
+ * Handle token limit by restarting session
87
+ */
88
+ private handleTokenLimit;
89
+ /**
90
+ * Build initial prompt for issue
91
+ */
92
+ private buildInitialPrompt;
93
+ /**
94
+ * Extract text content from Claude event
95
+ */
96
+ private extractTextContent;
97
+ /**
98
+ * Report status back to proxy
99
+ */
100
+ private reportStatus;
101
+ /**
102
+ * Get connection status
103
+ */
104
+ getConnectionStatus(): Map<string, boolean>;
105
+ /**
106
+ * Get active sessions
107
+ */
108
+ getActiveSessions(): string[];
109
+ /**
110
+ * Post initial comment when assigned to issue
111
+ */
112
+ private postInitialComment;
113
+ /**
114
+ * Post a comment to Linear
115
+ */
116
+ private postComment;
117
+ /**
118
+ * Update initial comment with TODO checklist
119
+ */
120
+ private updateCommentWithTodos;
121
+ /**
122
+ * Format todos as Linear checklist markdown
123
+ */
124
+ private formatTodosAsChecklist;
125
+ /**
126
+ * Extract attachment URLs from text (issue description or comment)
127
+ */
128
+ private extractAttachmentUrls;
129
+ /**
130
+ * Download attachments from Linear issue
131
+ */
132
+ private downloadIssueAttachments;
133
+ /**
134
+ * Download a single attachment from Linear
135
+ */
136
+ private downloadAttachment;
137
+ /**
138
+ * Generate a markdown section describing downloaded attachments
139
+ */
140
+ private generateAttachmentManifest;
141
+ }
142
+ //# sourceMappingURL=EdgeWorker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EdgeWorker.d.ts","sourceRoot":"","sources":["../src/EdgeWorker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAKrC,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAoB,MAAM,YAAY,CAAA;AAUtF,MAAM,CAAC,OAAO,WAAW,UAAU;IACjC,EAAE,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;IACnF,IAAI,CAAC,CAAC,SAAS,MAAM,gBAAgB,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAA;CACpG;AAED;;GAEG;AACH,qBAAa,UAAW,SAAQ,YAAY;IAC1C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,aAAa,CAAuC;IAC5D,OAAO,CAAC,aAAa,CAAuC;IAC5D,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,aAAa,CAAuC;IAC5D,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,gBAAgB,CAAiC;IACzD,OAAO,CAAC,mBAAmB,CAAmE;gBAElF,MAAM,EAAE,gBAAgB;IAiDpC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB3B;;OAEG;IACH,OAAO,CAAC,aAAa;IAKrB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAKxB;;OAEG;IACH,OAAO,CAAC,WAAW;IAKnB;;OAEG;YACW,eAAe;IAkB7B;;OAEG;YACW,aAAa;IAoC3B;;OAEG;YACW,uBAAuB;IAuBrC;;OAEG;YACW,mBAAmB;IAUjC;;OAEG;IACH,OAAO,CAAC,wBAAwB;IAOhC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAS1B;;OAEG;YACW,mBAAmB;IA4EjC;;OAEG;YACW,gBAAgB;IAmJ9B;;OAEG;YACW,qBAAqB;IAmCnC;;OAEG;YACW,iBAAiB;IAuD/B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;OAEG;YACW,gBAAgB;IAkB9B;;OAEG;YACW,kBAAkB;IAsFhC;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAoB1B;;OAEG;YACW,YAAY;IAW1B;;OAEG;IACH,mBAAmB,IAAI,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC;IAQ3C;;OAEG;IACH,iBAAiB,IAAI,MAAM,EAAE;IAI7B;;OAEG;YACW,kBAAkB;IA+BhC;;OAEG;YACW,WAAW;IAwCzB;;OAEG;YACW,sBAAsB;IA6BpC;;OAEG;IACH,OAAO,CAAC,sBAAsB;IAS9B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAW7B;;OAEG;YACW,wBAAwB;IAmHtC;;OAEG;YACW,kBAAkB;IAmDhC;;OAEG;IACH,OAAO,CAAC,0BAA0B;CA4DnC"}