cyrus-ai 0.1.3 → 0.1.4

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 (3) hide show
  1. package/README.md +15 -0
  2. package/app.mjs +72 -50
  3. package/package.json +6 -5
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # cyrus-ai
2
+
3
+ AI development agent for Linear powered by Claude Code.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g cyrus-ai
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ cyrus
15
+ ```
package/app.mjs CHANGED
@@ -10,8 +10,28 @@ import { URL } from 'url'
10
10
  import open from 'open'
11
11
  import readline from 'readline'
12
12
 
13
- // Load environment variables
14
- dotenv.config({ path: '.env.cyrus' })
13
+ // Parse command line arguments
14
+ const args = process.argv.slice(2)
15
+ const envFileArg = args.find(arg => arg.startsWith('--env-file='))
16
+
17
+ // Handle --version argument
18
+ if (args.includes('--version')) {
19
+ try {
20
+ const pkg = JSON.parse(readFileSync('./package.json', 'utf-8'))
21
+ console.log(pkg.version)
22
+ } catch {
23
+ console.log('0.1.3') // fallback version
24
+ }
25
+ process.exit(0)
26
+ }
27
+
28
+ // Load environment variables only if --env-file is specified
29
+ if (envFileArg) {
30
+ const envFile = envFileArg.split('=')[1]
31
+ dotenv.config({ path: envFile })
32
+ }
33
+
34
+
15
35
 
16
36
  const __dirname = dirname(fileURLToPath(import.meta.url))
17
37
 
@@ -27,32 +47,32 @@ class EdgeApp {
27
47
 
28
48
  /**
29
49
  * Load edge configuration (credentials and repositories)
50
+ * Note: Strips promptTemplatePath from all repositories to ensure built-in template is used
30
51
  */
31
52
  loadEdgeConfig() {
32
53
  const edgeConfigPath = './.edge-config.json'
54
+ let config = { repositories: [] }
55
+
33
56
  if (existsSync(edgeConfigPath)) {
34
57
  try {
35
- return JSON.parse(readFileSync(edgeConfigPath, 'utf-8'))
58
+ config = JSON.parse(readFileSync(edgeConfigPath, 'utf-8'))
36
59
  } catch (e) {
37
60
  console.error('Failed to load edge config:', e.message)
38
61
  }
39
62
  }
40
63
 
41
- // Check for repositories.json for backward compatibility
42
- const configPath = process.env.REPOSITORIES_CONFIG_PATH || './repositories.json'
43
- try {
44
- const configContent = readFileSync(resolve(configPath), 'utf-8')
45
- const config = JSON.parse(configContent)
46
-
47
- if (config.repositories && config.repositories.length > 0) {
48
- console.log(`Loaded ${config.repositories.length} repository configurations from ${configPath}`)
49
- return { repositories: config.repositories }
50
- }
51
- } catch (error) {
52
- // No config file found
64
+ // Strip promptTemplatePath from all repositories to ensure built-in template is used
65
+ if (config.repositories) {
66
+ config.repositories = config.repositories.map(repo => {
67
+ const { promptTemplatePath, ...repoWithoutTemplate } = repo
68
+ if (promptTemplatePath) {
69
+ console.log(`Ignoring custom prompt template for repository: ${repo.name} (using built-in template)`)
70
+ }
71
+ return repoWithoutTemplate
72
+ })
53
73
  }
54
74
 
55
- return { repositories: [] }
75
+ return config
56
76
  }
57
77
 
58
78
  /**
@@ -85,9 +105,16 @@ class EdgeApp {
85
105
  const baseBranch = await question('Base branch (default: main): ') || 'main'
86
106
  const workspaceBaseDir = await question(`Workspace directory (default: ${repositoryPath}/workspaces): `) || `${repositoryPath}/workspaces`
87
107
 
88
- console.log('\nšŸ“ Prompt Template (optional)')
89
- console.log('Leave blank to use the built-in default template')
90
- const promptTemplatePath = await question('Custom prompt template path (press Enter to skip): ')
108
+ // Note: Prompt template is now hardcoded - no longer configurable
109
+
110
+ // Ask for MCP configuration
111
+ console.log('\nšŸ”§ MCP (Model Context Protocol) Configuration')
112
+ console.log('MCP allows Claude to access external tools and data sources.')
113
+ console.log('Examples: filesystem access, database connections, API integrations')
114
+ console.log('See: https://docs.anthropic.com/en/docs/claude-code/mcp')
115
+ console.log('')
116
+ const mcpConfigInput = await question('MCP config file path (optional, e.g., ./mcp-config.json): ')
117
+ const mcpConfigPath = mcpConfigInput.trim() || undefined
91
118
 
92
119
  // Ask for allowed tools configuration
93
120
  console.log('\nšŸ”§ Tool Configuration')
@@ -116,8 +143,8 @@ class EdgeApp {
116
143
  linearToken: linearCredentials.linearToken,
117
144
  workspaceBaseDir: resolve(workspaceBaseDir),
118
145
  isActive: true,
119
- ...(promptTemplatePath && { promptTemplatePath: resolve(promptTemplatePath) }),
120
- ...(allowedTools && { allowedTools })
146
+ ...(allowedTools && { allowedTools }),
147
+ ...(mcpConfigPath && { mcpConfigPath: resolve(mcpConfigPath) })
121
148
  }
122
149
 
123
150
  return repository
@@ -256,30 +283,10 @@ class EdgeApp {
256
283
  */
257
284
  async start() {
258
285
  try {
259
- // Validate proxy URL
260
- const proxyUrl = process.env.PROXY_URL
261
- if (!proxyUrl) {
262
- console.error('āŒ PROXY_URL environment variable is required')
263
- console.log('\nPlease add the following to your .env.cyrus file:')
264
- console.log('PROXY_URL=https://cyrus-proxy.ceedar.workers.dev')
265
- console.log('\nThis connects to the secure public Cyrus proxy for Linear OAuth and webhooks.')
266
- process.exit(1)
267
- }
286
+ // Set proxy URL with default
287
+ const proxyUrl = process.env.PROXY_URL || 'https://cyrus-proxy.ceedar.workers.dev'
268
288
 
269
- // Validate Claude CLI
270
- const claudePath = process.env.CLAUDE_PATH || 'claude'
271
- try {
272
- const { execSync } = await import('child_process')
273
- execSync(`which ${claudePath}`, { stdio: 'ignore' })
274
- } catch (error) {
275
- console.error(`āŒ Claude CLI not found at: ${claudePath}`)
276
- console.log('\nPlease ensure Claude Code is installed:')
277
- console.log('• Claude Pro/Max users: Install from https://claude.ai/code')
278
- console.log('• API users: Set ANTHROPIC_API_KEY in .env.cyrus and install claude-ai CLI')
279
- console.log('\nIf Claude is installed in a custom location, set CLAUDE_PATH in .env.cyrus:')
280
- console.log('CLAUDE_PATH=/path/to/claude')
281
- process.exit(1)
282
- }
289
+ // No need to validate Claude CLI - using Claude TypeScript SDK now
283
290
 
284
291
  // Start OAuth server immediately for easy access
285
292
  const oauthPort = 3457
@@ -437,8 +444,7 @@ class EdgeApp {
437
444
  const config = {
438
445
  proxyUrl,
439
446
  repositories,
440
- claudePath,
441
- allowedTools: process.env.ALLOWED_TOOLS?.split(',').map(t => t.trim()) || [],
447
+ defaultAllowedTools: process.env.ALLOWED_TOOLS?.split(',').map(t => t.trim()) || [],
442
448
  features: {
443
449
  enableContinuation: true
444
450
  },
@@ -537,8 +543,12 @@ class EdgeApp {
537
543
  throw new Error('Not a git repository')
538
544
  }
539
545
 
546
+ // Sanitize branch name by removing backticks to prevent command injection
547
+ const sanitizeBranchName = (name) => name ? name.replace(/`/g, '') : name
548
+
540
549
  // Use Linear's preferred branch name, or generate one if not available
541
- const branchName = issue.branchName || `${issue.identifier}-${issue.title?.toLowerCase().replace(/\s+/g, '-').substring(0, 30)}`
550
+ const rawBranchName = issue.branchName || `${issue.identifier}-${issue.title?.toLowerCase().replace(/\s+/g, '-').substring(0, 30)}`
551
+ const branchName = sanitizeBranchName(rawBranchName)
542
552
  const workspacePath = join(repository.workspaceBaseDir, issue.identifier)
543
553
 
544
554
  // Ensure workspace directory exists
@@ -577,10 +587,22 @@ class EdgeApp {
577
587
  // Branch doesn't exist, we'll create it
578
588
  }
579
589
 
580
- // Create the worktree
581
- console.log(`Creating git worktree at ${workspacePath} from ${repository.baseBranch}`)
590
+ // Fetch latest changes from remote
591
+ console.log('Fetching latest changes from remote...')
592
+ try {
593
+ execSync('git fetch origin', {
594
+ cwd: repository.repositoryPath,
595
+ stdio: 'pipe'
596
+ })
597
+ } catch (e) {
598
+ console.warn('Warning: git fetch failed, proceeding with local branch:', e.message)
599
+ }
600
+
601
+ // Create the worktree from remote branch
602
+ const remoteBranch = `origin/${repository.baseBranch}`
603
+ console.log(`Creating git worktree at ${workspacePath} from ${remoteBranch}`)
582
604
  const worktreeCmd = createBranch
583
- ? `git worktree add "${workspacePath}" -b "${branchName}" "${repository.baseBranch}"`
605
+ ? `git worktree add "${workspacePath}" -b "${branchName}" "${remoteBranch}"`
584
606
  : `git worktree add "${workspacePath}" "${branchName}"`
585
607
 
586
608
  execSync(worktreeCmd, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyrus-ai",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "description": "AI-powered Linear issue automation using Claude",
5
5
  "main": "app.mjs",
6
6
  "bin": {
@@ -10,7 +10,8 @@
10
10
  "files": [
11
11
  "app.mjs",
12
12
  "services/**/*.mjs",
13
- "utils/**/*.mjs"
13
+ "utils/**/*.mjs",
14
+ "README.md"
14
15
  ],
15
16
  "publishConfig": {
16
17
  "access": "public"
@@ -39,10 +40,9 @@
39
40
  "open": "^10.0.0",
40
41
  "zod": "^3.24.4",
41
42
  "cyrus-core": "0.0.1",
42
- "cyrus-claude-runner": "0.0.1",
43
- "cyrus-ndjson-client": "0.0.1",
44
43
  "cyrus-edge-worker": "0.0.1",
45
- "cyrus-claude-parser": "0.0.1"
44
+ "cyrus-claude-runner": "0.0.1",
45
+ "cyrus-ndjson-client": "0.0.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@vitest/ui": "^3.1.4",
@@ -53,6 +53,7 @@
53
53
  "start": "node app.mjs",
54
54
  "dev": "nodemon app.mjs",
55
55
  "test": "vitest run",
56
+ "test:run": "vitest run --passWithNoTests",
56
57
  "test:watch": "vitest"
57
58
  }
58
59
  }