haitask 0.1.0 → 0.1.2

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/README.md CHANGED
@@ -28,19 +28,16 @@ git clone https://github.com/HidayetHidayetov/haitask.git && cd haitask && npm i
28
28
 
29
29
  ## Quick start
30
30
 
31
- 1. **Credentials (one-time)**
32
- Create a `.env` (e.g. in your home dir or a shared config folder) with:
33
- - One AI provider key: `GROQ_API_KEY`, `DEEPSEEK_API_KEY`, or `OPENAI_API_KEY`
34
- - Jira: `JIRA_BASE_URL`, `JIRA_EMAIL`, `JIRA_API_TOKEN`
35
- Copy from `.env.example` in the repo.
36
-
37
- 2. **Per project (one-time)**
31
+ 1. **Per project (one-time)**
38
32
  In the Git repo where you want to create Jira issues:
39
33
  ```bash
40
34
  cd /path/to/your/repo
41
35
  haitask init
42
36
  ```
43
- This creates `.haitaskrc`. Edit it: set `jira.projectKey`, `jira.baseUrl`, and optionally `rules.allowedBranches` and `rules.commitPrefixes`.
37
+ **Interactive setup:** you’ll be asked for Jira base URL, project key, issue type, AI provider (groq / deepseek / openai), allowed branches, and commit prefixes. A `.haitaskrc` file is created from your answers. You’ll then choose where to store API keys: **this project** (`.env` in the repo) or **global** (`~/.haitask/.env`, shared across projects). A template `.env` is created in the chosen place — add your own keys there (never commit real keys).
38
+
39
+ 2. **Add your API keys**
40
+ Edit the `.env` that was created (project or `~/.haitask/.env`): set one AI key (`GROQ_API_KEY`, `DEEPSEEK_API_KEY`, or `OPENAI_API_KEY`) and Jira keys (`JIRA_BASE_URL`, `JIRA_EMAIL`, `JIRA_API_TOKEN`). Optional: `JIRA_ACCOUNT_ID` to auto-assign issues.
44
41
 
45
42
  3. **Create a Jira issue from the latest commit**
46
43
  After committing:
@@ -56,7 +53,7 @@ git clone https://github.com/HidayetHidayetov/haitask.git && cd haitask && npm i
56
53
 
57
54
  | Command | Description |
58
55
  |--------|-------------|
59
- | `haitask init` | Create `.haitaskrc` in cwd (no overwrite if present). Validates env. |
56
+ | `haitask init` | Interactive setup: prompts for Jira/AI/rules → writes `.haitaskrc`, optional `.env` (project or ~/.haitask/.env). |
60
57
  | `haitask run` | Run pipeline: Git → AI → Jira (create issue). |
61
58
  | `haitask run --dry` | Same as above but skips the Jira API call. |
62
59
 
@@ -65,7 +62,7 @@ git clone https://github.com/HidayetHidayetov/haitask.git && cd haitask && npm i
65
62
  ## Configuration
66
63
 
67
64
  - **`.haitaskrc`** (project root): Jira `baseUrl`, `projectKey`, `issueType`; AI `provider` and `model`; `rules.allowedBranches` and `rules.commitPrefixes`. Single source of truth for behaviour.
68
- - **`.env`**: API keys only. Prefer loading it from the directory where you run `haitask` (or the same folder as the cloned repo when using `npm link`).
65
+ - **`.env`**: API keys only. Loaded in order: **project** `.env` (current directory), then **global** `~/.haitask/.env`. So you can use one global `.env` for all projects or override per repo.
69
66
 
70
67
  **AI providers** (set `ai.provider` in `.haitaskrc`): `groq` (default, free), `deepseek` (free), `openai` (paid). Set the corresponding key in `.env`.
71
68
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "haitask",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Hidayet AI Task — Generate Jira tasks from Git commits using AI",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -41,4 +41,4 @@
41
41
  "dotenv": "^16.4.5",
42
42
  "execa": "^9.5.2"
43
43
  }
44
- }
44
+ }
@@ -1,40 +1,112 @@
1
1
  /**
2
- * haitask init — Create .haitaskrc, validate .env
3
- * Thin handler: delegates to config layer, handles CLI output only.
2
+ * haitask init — Interactive setup: prompts → .haitaskrc, optional .env placement.
4
3
  */
5
4
 
5
+ import { createInterface } from 'readline';
6
+ import { writeFileSync, existsSync, mkdirSync } from 'fs';
7
+ import { resolve } from 'path';
6
8
  import { createDefaultConfigFile, validateEnv } from '../config/init.js';
7
9
  import { loadConfig } from '../config/load.js';
8
10
 
11
+ const DEFAULT_MODELS = { groq: 'llama-3.1-8b-instant', deepseek: 'deepseek-chat', openai: 'gpt-4o-mini' };
12
+
13
+ function question(rl, prompt, defaultVal = '') {
14
+ const def = defaultVal ? ` (${defaultVal})` : '';
15
+ return new Promise((resolve) => rl.question(prompt + def + ': ', (ans) => resolve((ans || defaultVal).trim())));
16
+ }
17
+
18
+ function getEnvExampleContent() {
19
+ return `# HAITASK — fill in your own API keys (never commit real keys)
20
+ # AI: set the key for the provider in .haitaskrc (ai.provider)
21
+ GROQ_API_KEY=
22
+ DEEPSEEK_API_KEY=
23
+ OPENAI_API_KEY=
24
+
25
+ # Jira (required)
26
+ JIRA_BASE_URL=
27
+ JIRA_EMAIL=
28
+ JIRA_API_TOKEN=
29
+ JIRA_ACCOUNT_ID=
30
+ `;
31
+ }
32
+
9
33
  export async function runInit() {
10
- const { created } = createDefaultConfigFile();
34
+ const cwd = process.cwd();
35
+ const rcPath = resolve(cwd, '.haitaskrc');
11
36
 
12
- if (!created) {
37
+ if (existsSync(rcPath)) {
13
38
  console.warn('haitask init: .haitaskrc already exists. Not overwriting.');
14
39
  process.exitCode = 1;
15
40
  return;
16
41
  }
17
42
 
18
- console.log('Created .haitaskrc');
43
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
44
+
45
+ try {
46
+ console.log('haitask init — answer the questions (Enter = use default).\n');
47
+
48
+ const jiraBaseUrl = await question(rl, 'Jira base URL', 'https://your-domain.atlassian.net');
49
+ const jiraProjectKey = await question(rl, 'Jira project key', 'PROJ');
50
+ const jiraIssueType = await question(rl, 'Jira issue type', 'Task');
51
+ const aiProvider = await question(rl, 'AI provider (groq | deepseek | openai)', 'groq');
52
+ const allowedBranchesStr = await question(rl, 'Allowed branches (comma-separated)', 'main,develop,master');
53
+ const commitPrefixesStr = await question(rl, 'Commit prefixes (comma-separated)', 'feat,fix,chore');
54
+
55
+ const allowedBranches = allowedBranchesStr.split(',').map((s) => s.trim()).filter(Boolean);
56
+ const commitPrefixes = commitPrefixesStr.split(',').map((s) => s.trim()).filter(Boolean);
57
+ const model = DEFAULT_MODELS[aiProvider.toLowerCase()] || DEFAULT_MODELS.groq;
58
+
59
+ const config = {
60
+ jira: { baseUrl: jiraBaseUrl, projectKey: jiraProjectKey, issueType: jiraIssueType },
61
+ ai: { provider: aiProvider.toLowerCase(), model },
62
+ rules: { allowedBranches, commitPrefixes },
63
+ };
64
+
65
+ writeFileSync(rcPath, JSON.stringify(config, null, 2), 'utf-8');
66
+ console.log('\nCreated .haitaskrc');
67
+
68
+ const where = await question(rl, 'Where to store API keys? (1 = this project .env, 2 = global ~/.haitask/.env)', '1');
69
+ const home = process.env.HOME || process.env.USERPROFILE;
70
+
71
+ if (where.trim() === '2' && home) {
72
+ const globalDir = resolve(home, '.haitask');
73
+ const globalEnv = resolve(globalDir, '.env');
74
+ if (!existsSync(globalDir)) mkdirSync(globalDir, { recursive: true });
75
+ if (!existsSync(globalEnv)) {
76
+ writeFileSync(globalEnv, getEnvExampleContent(), 'utf-8');
77
+ console.log('Created ~/.haitask/.env — add your API keys there (used by all projects).');
78
+ } else {
79
+ console.log('~/.haitask/.env already exists.');
80
+ }
81
+ } else {
82
+ const projectEnv = resolve(cwd, '.env');
83
+ if (!existsSync(projectEnv)) {
84
+ writeFileSync(projectEnv, getEnvExampleContent(), 'utf-8');
85
+ console.log('Created .env in this project — add your API keys there.');
86
+ } else {
87
+ console.log('.env already exists in this project.');
88
+ }
89
+ }
90
+ } finally {
91
+ rl.close();
92
+ }
19
93
 
20
- // Load config to check AI provider key
21
94
  let config;
22
95
  try {
23
96
  config = loadConfig();
24
- } catch (err) {
25
- // Config just created, might not be readable yet, skip AI key check
97
+ } catch {
26
98
  config = null;
27
99
  }
28
100
 
29
- const { valid, missing } = validateEnv(process.cwd(), config);
101
+ const { valid, missing } = validateEnv(cwd, config);
30
102
  if (!valid) {
31
- console.warn('Add these to .env before running "haitask run":', missing.join(', '));
32
- const provider = config?.ai?.provider?.toLowerCase();
33
- if (provider === 'groq') console.log('Get free Groq API key at: https://console.groq.com/keys');
34
- if (provider === 'deepseek') console.log('Get free Deepseek API key at: https://platform.deepseek.com/');
103
+ console.warn('\nAdd these to your .env before "haitask run":', missing.join(', '));
104
+ console.log('Env is read from: project .env, then ~/.haitask/.env');
105
+ if (config?.ai?.provider === 'groq') console.log('Groq key: https://console.groq.com/keys');
106
+ if (config?.ai?.provider === 'deepseek') console.log('Deepseek key: https://platform.deepseek.com/');
35
107
  process.exitCode = 1;
36
108
  return;
37
109
  }
38
110
 
39
- console.log('Environment (.env) looks good.');
111
+ console.log('\nEnvironment looks good. Run "haitask run" after your next commit.');
40
112
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Load .env: try cwd first, then ~/.haitask/.env (Option C — hybrid).
3
+ * So users can have one global .env for all projects or override per repo.
4
+ */
5
+
6
+ import { existsSync } from 'fs';
7
+ import { resolve } from 'path';
8
+ import { config as loadDotenv } from 'dotenv';
9
+
10
+ const CWD = process.cwd();
11
+ const HOME = process.env.HOME || process.env.USERPROFILE || '';
12
+
13
+ function loadEnv(path) {
14
+ if (path && existsSync(path)) {
15
+ loadDotenv({ path });
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Load environment: first .env in current directory, then ~/.haitask/.env.
21
+ * Call once at CLI entry (e.g. index.js).
22
+ */
23
+ export function loadEnvFiles() {
24
+ loadEnv(resolve(CWD, '.env'));
25
+ if (HOME) {
26
+ loadEnv(resolve(HOME, '.haitask', '.env'));
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Paths we check for .env (for messaging).
32
+ */
33
+ export function getEnvPaths() {
34
+ const paths = [resolve(CWD, '.env')];
35
+ if (HOME) paths.push(resolve(HOME, '.haitask', '.env'));
36
+ return paths;
37
+ }
@@ -5,7 +5,6 @@
5
5
 
6
6
  import { writeFileSync, existsSync } from 'fs';
7
7
  import { resolve } from 'path';
8
- import { config as loadEnv } from 'dotenv';
9
8
 
10
9
  const DEFAULT_RC = {
11
10
  jira: {
@@ -41,15 +40,13 @@ export function createDefaultConfigFile(dir = process.cwd()) {
41
40
  }
42
41
 
43
42
  /**
44
- * Load .env from dir and check required keys.
43
+ * Check required env keys (env is already loaded by loadEnvFiles at CLI entry).
45
44
  * If config is provided, also validates AI provider key.
46
- * @param {string} [dir] - Directory (default: process.cwd())
45
+ * @param {string} [dir] - Unused; kept for API compatibility
47
46
  * @param {object} [config] - Optional config to check AI provider key
48
47
  * @returns {{ valid: boolean, missing: string[] }}
49
48
  */
50
49
  export function validateEnv(dir = process.cwd(), config = null) {
51
- const envPath = resolve(dir, '.env');
52
- loadEnv({ path: envPath });
53
50
  const missing = [...REQUIRED_ENV_KEYS];
54
51
 
55
52
  // Check AI provider key if config provided
package/src/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import 'dotenv/config';
3
+ import { loadEnvFiles } from './config/env-loader.js';
4
+ loadEnvFiles();
4
5
  import { program } from 'commander';
5
6
  import { runInit } from './commands/init.js';
6
7
  import { runRun } from './commands/run.js';
@@ -8,7 +9,7 @@ import { runRun } from './commands/run.js';
8
9
  program
9
10
  .name('haitask')
10
11
  .description('HAITASK — Generate Jira tasks from Git commits using AI')
11
- .version('0.1.0');
12
+ .version('0.1.1');
12
13
 
13
14
  program
14
15
  .command('init')