cyrus-ai 0.1.2 → 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 +73 -36
  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,15 +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'
288
+
289
+ // No need to validate Claude CLI - using Claude TypeScript SDK now
268
290
 
269
291
  // Start OAuth server immediately for easy access
270
292
  const oauthPort = 3457
@@ -422,8 +444,7 @@ class EdgeApp {
422
444
  const config = {
423
445
  proxyUrl,
424
446
  repositories,
425
- claudePath: process.env.CLAUDE_PATH || 'claude',
426
- allowedTools: process.env.ALLOWED_TOOLS?.split(',').map(t => t.trim()) || [],
447
+ defaultAllowedTools: process.env.ALLOWED_TOOLS?.split(',').map(t => t.trim()) || [],
427
448
  features: {
428
449
  enableContinuation: true
429
450
  },
@@ -522,8 +543,12 @@ class EdgeApp {
522
543
  throw new Error('Not a git repository')
523
544
  }
524
545
 
546
+ // Sanitize branch name by removing backticks to prevent command injection
547
+ const sanitizeBranchName = (name) => name ? name.replace(/`/g, '') : name
548
+
525
549
  // Use Linear's preferred branch name, or generate one if not available
526
- 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)
527
552
  const workspacePath = join(repository.workspaceBaseDir, issue.identifier)
528
553
 
529
554
  // Ensure workspace directory exists
@@ -562,10 +587,22 @@ class EdgeApp {
562
587
  // Branch doesn't exist, we'll create it
563
588
  }
564
589
 
565
- // Create the worktree
566
- 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}`)
567
604
  const worktreeCmd = createBranch
568
- ? `git worktree add "${workspacePath}" -b "${branchName}" "${repository.baseBranch}"`
605
+ ? `git worktree add "${workspacePath}" -b "${branchName}" "${remoteBranch}"`
569
606
  : `git worktree add "${workspacePath}" "${branchName}"`
570
607
 
571
608
  execSync(worktreeCmd, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyrus-ai",
3
- "version": "0.1.2",
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-parser": "0.0.1",
43
- "cyrus-ndjson-client": "0.0.1",
43
+ "cyrus-edge-worker": "0.0.1",
44
44
  "cyrus-claude-runner": "0.0.1",
45
- "cyrus-edge-worker": "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
  }