threadlines 0.2.6 → 0.2.9

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.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * GitHub Actions Environment - Complete Isolation
3
+ * GitHub Actions Environment
4
4
  *
5
5
  * All GitHub-specific logic is contained in this file.
6
6
  * No dependencies on other environment implementations.
@@ -52,10 +52,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
52
52
  exports.getGitHubContext = getGitHubContext;
53
53
  const simple_git_1 = __importDefault(require("simple-git"));
54
54
  const fs = __importStar(require("fs"));
55
- const repo_1 = require("./repo");
56
55
  const diff_1 = require("./diff");
57
56
  /**
58
- * Gets all GitHub context in one call - completely isolated from other environments.
57
+ * Gets all GitHub context
59
58
  */
60
59
  async function getGitHubContext(repoRoot) {
61
60
  const git = (0, simple_git_1.default)(repoRoot);
@@ -70,7 +69,6 @@ async function getGitHubContext(repoRoot) {
70
69
  const branchName = await getBranchName();
71
70
  const context = detectContext();
72
71
  const commitSha = getCommitSha(context);
73
- // Get commit author (fails loudly if unavailable)
74
72
  // Note: commitSha parameter not needed - GitHub reads from GITHUB_EVENT_PATH JSON
75
73
  const commitAuthor = await getCommitAuthor();
76
74
  // Get commit message if we have a SHA
@@ -96,17 +94,24 @@ async function getGitHubContext(repoRoot) {
96
94
  }
97
95
  /**
98
96
  * Gets diff for GitHub Actions CI environment
97
+ *
98
+ * Strategy:
99
+ * - PR context: Compare source branch vs target branch (full PR diff)
100
+ * - Any push (main or feature branch): Compare last commit only (HEAD~1...HEAD)
101
+ *
102
+ * Note: Unlike GitLab/Bitbucket, we don't need to fetch branches on-demand here.
103
+ * GitHub Actions' `actions/checkout` automatically fetches both base and head refs
104
+ * for pull_request events, even with the default shallow clone (fetch-depth: 1).
105
+ * The refs `origin/${GITHUB_BASE_REF}` and `origin/${GITHUB_HEAD_REF}` are available
106
+ * immediately after checkout.
99
107
  */
100
108
  async function getDiff(repoRoot) {
101
109
  const git = (0, simple_git_1.default)(repoRoot);
102
- const defaultBranch = await (0, repo_1.getDefaultBranchName)(repoRoot);
103
- // Determine context from GitHub environment variables
104
110
  const eventName = process.env.GITHUB_EVENT_NAME;
105
111
  const baseRef = process.env.GITHUB_BASE_REF;
106
112
  const headRef = process.env.GITHUB_HEAD_REF;
107
- const refName = process.env.GITHUB_REF_NAME;
108
- const commitSha = process.env.GITHUB_SHA;
109
- // Scenario 1: PR Context
113
+ // PR Context: Compare source vs target branch
114
+ // No fetch needed - GitHub Actions provides both refs automatically
110
115
  if (eventName === 'pull_request') {
111
116
  if (!baseRef || !headRef) {
112
117
  throw new Error('GitHub PR context detected but GITHUB_BASE_REF or GITHUB_HEAD_REF is missing. ' +
@@ -120,36 +125,14 @@ async function getDiff(repoRoot) {
120
125
  changedFiles
121
126
  };
122
127
  }
123
- // Scenario 2 & 4: Default Branch Push
124
- if (refName === defaultBranch && commitSha) {
125
- try {
126
- const diff = await git.diff([`origin/${defaultBranch}~1...origin/${defaultBranch}`, '-U200']);
127
- const diffSummary = await git.diffSummary([`origin/${defaultBranch}~1...origin/${defaultBranch}`]);
128
- const changedFiles = diffSummary.files.map(f => f.file);
129
- return {
130
- diff: diff || '',
131
- changedFiles
132
- };
133
- }
134
- catch (error) {
135
- const errorMessage = error instanceof Error ? error.message : 'Unknown error';
136
- throw new Error(`Could not get diff for default branch '${defaultBranch}'. ` +
137
- `This might be the first commit on the branch. Error: ${errorMessage}`);
138
- }
139
- }
140
- // Scenario 3: Feature Branch Push
141
- if (refName) {
142
- const diff = await git.diff([`origin/${defaultBranch}...origin/${refName}`, '-U200']);
143
- const diffSummary = await git.diffSummary([`origin/${defaultBranch}...origin/${refName}`]);
144
- const changedFiles = diffSummary.files.map(f => f.file);
145
- return {
146
- diff: diff || '',
147
- changedFiles
148
- };
149
- }
150
- throw new Error('GitHub Actions environment detected but no valid context found. ' +
151
- 'Expected GITHUB_EVENT_NAME="pull_request" (with GITHUB_BASE_REF/GITHUB_HEAD_REF) ' +
152
- 'or GITHUB_REF_NAME for branch context.');
128
+ // Any push (main or feature branch): Review last commit only
129
+ const diff = await git.diff(['HEAD~1...HEAD', '-U200']);
130
+ const diffSummary = await git.diffSummary(['HEAD~1...HEAD']);
131
+ const changedFiles = diffSummary.files.map(f => f.file);
132
+ return {
133
+ diff: diff || '',
134
+ changedFiles
135
+ };
153
136
  }
154
137
  /**
155
138
  * Gets repository name for GitHub Actions
@@ -175,10 +158,13 @@ async function getBranchName() {
175
158
  return refName;
176
159
  }
177
160
  /**
178
- * Detects GitHub context (PR, branch, or commit)
161
+ * Detects GitHub context (PR or commit)
162
+ *
163
+ * - PR context: When GITHUB_EVENT_NAME is 'pull_request'
164
+ * - Commit context: Any push (main or feature branch) - reviews single commit
179
165
  */
180
166
  function detectContext() {
181
- // 1. Check for PR context
167
+ // PR context
182
168
  if (process.env.GITHUB_EVENT_NAME === 'pull_request') {
183
169
  const targetBranch = process.env.GITHUB_BASE_REF;
184
170
  const sourceBranch = process.env.GITHUB_HEAD_REF;
@@ -192,22 +178,16 @@ function detectContext() {
192
178
  };
193
179
  }
194
180
  }
195
- // 2. Check for branch context
196
- if (process.env.GITHUB_REF_NAME) {
197
- return {
198
- type: 'branch',
199
- branchName: process.env.GITHUB_REF_NAME
200
- };
201
- }
202
- // 3. Check for commit context
181
+ // Any push (main or feature branch) → commit context
203
182
  if (process.env.GITHUB_SHA) {
204
183
  return {
205
184
  type: 'commit',
206
185
  commitSha: process.env.GITHUB_SHA
207
186
  };
208
187
  }
209
- // 4. Fallback to local (shouldn't happen in GitHub Actions, but TypeScript needs it)
210
- return { type: 'local' };
188
+ throw new Error('GitHub Actions: Could not detect context. ' +
189
+ 'Expected GITHUB_EVENT_NAME="pull_request" or GITHUB_SHA to be set. ' +
190
+ 'This should be automatically provided by GitHub Actions.');
211
191
  }
212
192
  /**
213
193
  * Gets commit SHA from context
@@ -216,7 +196,7 @@ function getCommitSha(context) {
216
196
  if (context.type === 'commit') {
217
197
  return context.commitSha;
218
198
  }
219
- if (context.type === 'branch' || context.type === 'pr') {
199
+ if (context.type === 'pr') {
220
200
  return process.env.GITHUB_SHA;
221
201
  }
222
202
  return undefined;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * GitLab CI Environment - Complete Isolation
3
+ * GitLab CI Environment
4
4
  *
5
5
  * All GitLab-specific logic is contained in this file.
6
6
  * No dependencies on other environment implementations.
@@ -19,8 +19,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.getGitLabContext = getGitLabContext;
20
20
  const simple_git_1 = __importDefault(require("simple-git"));
21
21
  const diff_1 = require("./diff");
22
+ const logger_1 = require("../utils/logger");
22
23
  /**
23
- * Gets all GitLab context in one call - completely isolated from other environments.
24
+ * Gets all GitLab context
24
25
  */
25
26
  async function getGitLabContext(repoRoot) {
26
27
  const git = (0, simple_git_1.default)(repoRoot);
@@ -61,59 +62,35 @@ async function getGitLabContext(repoRoot) {
61
62
  /**
62
63
  * Get diff for GitLab CI environment
63
64
  *
64
- * GitLab CI does a shallow clone of ONLY the current branch. The default branch
65
- * (e.g., origin/main) is NOT available by default. We fetch it on-demand.
65
+ * Strategy:
66
+ * - MR context: Fetch target branch, compare source vs target (full MR diff)
67
+ * - Any push (main or feature branch): Compare last commit only (HEAD~1...HEAD)
66
68
  *
67
- * Scenarios handled:
68
- *
69
- * 1. MR Context (CI_MERGE_REQUEST_IID is set):
70
- * - Fetch target branch, then diff target vs source
71
- *
72
- * 2. Feature Branch Push (CI_COMMIT_REF_NAME != CI_DEFAULT_BRANCH):
73
- * - Fetch default branch, then diff default vs feature
74
- *
75
- * 3. Default Branch Push (CI_COMMIT_REF_NAME == CI_DEFAULT_BRANCH):
76
- * - Use HEAD~1...HEAD (last commit only, no fetch needed)
69
+ * Note: GitLab CI does a shallow clone, so we fetch the target branch for MR context.
70
+ * For regular pushes, HEAD~1...HEAD works without additional fetching.
77
71
  */
78
72
  async function getDiff(repoRoot) {
79
73
  const git = (0, simple_git_1.default)(repoRoot);
80
- // Get GitLab CI environment variables
81
74
  const mrIid = process.env.CI_MERGE_REQUEST_IID;
82
75
  const targetBranch = process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
83
76
  const sourceBranch = process.env.CI_MERGE_REQUEST_SOURCE_BRANCH_NAME;
84
- const refName = process.env.CI_COMMIT_REF_NAME;
85
- const defaultBranch = process.env.CI_DEFAULT_BRANCH || 'main';
86
- // Scenario 1: MR Context
77
+ // MR Context: Fetch target branch and compare
87
78
  if (mrIid) {
88
79
  if (!targetBranch || !sourceBranch) {
89
80
  throw new Error('GitLab MR context detected but CI_MERGE_REQUEST_TARGET_BRANCH_NAME or ' +
90
81
  'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME is missing. ' +
91
82
  'This should be automatically provided by GitLab CI.');
92
83
  }
93
- console.log(` [GitLab] Fetching target branch: origin/${targetBranch}`);
84
+ logger_1.logger.debug(`Fetching target branch: origin/${targetBranch}`);
94
85
  await git.fetch(['origin', `${targetBranch}:refs/remotes/origin/${targetBranch}`, '--depth=1']);
95
86
  const diff = await git.diff([`origin/${targetBranch}...origin/${sourceBranch}`, '-U200']);
96
87
  const diffSummary = await git.diffSummary([`origin/${targetBranch}...origin/${sourceBranch}`]);
97
88
  const changedFiles = diffSummary.files.map(f => f.file);
98
89
  return { diff: diff || '', changedFiles };
99
90
  }
100
- if (!refName) {
101
- throw new Error('GitLab CI: CI_COMMIT_REF_NAME environment variable is not set. ' +
102
- 'This should be automatically provided by GitLab CI.');
103
- }
104
- // Scenario 3: Default Branch Push
105
- if (refName === defaultBranch) {
106
- console.log(` [GitLab] Push to default branch (${defaultBranch}), using HEAD~1...HEAD`);
107
- const diff = await git.diff(['HEAD~1...HEAD', '-U200']);
108
- const diffSummary = await git.diffSummary(['HEAD~1...HEAD']);
109
- const changedFiles = diffSummary.files.map(f => f.file);
110
- return { diff: diff || '', changedFiles };
111
- }
112
- // Scenario 2: Feature Branch Push
113
- console.log(` [GitLab] Feature branch push, fetching default branch: origin/${defaultBranch}`);
114
- await git.fetch(['origin', `${defaultBranch}:refs/remotes/origin/${defaultBranch}`, '--depth=1']);
115
- const diff = await git.diff([`origin/${defaultBranch}...origin/${refName}`, '-U200']);
116
- const diffSummary = await git.diffSummary([`origin/${defaultBranch}...origin/${refName}`]);
91
+ // Any push (main or feature branch): Review last commit only
92
+ const diff = await git.diff(['HEAD~1...HEAD', '-U200']);
93
+ const diffSummary = await git.diffSummary(['HEAD~1...HEAD']);
117
94
  const changedFiles = diffSummary.files.map(f => f.file);
118
95
  return { diff: diff || '', changedFiles };
119
96
  }
@@ -140,10 +117,13 @@ async function getBranchName() {
140
117
  return refName;
141
118
  }
142
119
  /**
143
- * Detects GitLab context (MR, branch, or commit)
120
+ * Detects GitLab context (MR or commit)
121
+ *
122
+ * - MR context: When CI_MERGE_REQUEST_IID is set
123
+ * - Commit context: Any push (main or feature branch) - reviews single commit
144
124
  */
145
125
  function detectContext() {
146
- // 1. Check for MR context
126
+ // MR context
147
127
  const mrIid = process.env.CI_MERGE_REQUEST_IID;
148
128
  if (mrIid) {
149
129
  const targetBranch = process.env.CI_MERGE_REQUEST_TARGET_BRANCH_NAME;
@@ -157,22 +137,16 @@ function detectContext() {
157
137
  };
158
138
  }
159
139
  }
160
- // 2. Check for branch context
161
- if (process.env.CI_COMMIT_REF_NAME) {
162
- return {
163
- type: 'branch',
164
- branchName: process.env.CI_COMMIT_REF_NAME
165
- };
166
- }
167
- // 3. Check for commit context
140
+ // Any push (main or feature branch) → commit context
168
141
  if (process.env.CI_COMMIT_SHA) {
169
142
  return {
170
143
  type: 'commit',
171
144
  commitSha: process.env.CI_COMMIT_SHA
172
145
  };
173
146
  }
174
- // 4. Fallback to local (shouldn't happen in GitLab CI, but TypeScript needs it)
175
- return { type: 'local' };
147
+ throw new Error('GitLab CI: Could not detect context. ' +
148
+ 'Expected CI_MERGE_REQUEST_IID or CI_COMMIT_SHA to be set. ' +
149
+ 'This should be automatically provided by GitLab CI.');
176
150
  }
177
151
  /**
178
152
  * Gets commit SHA from context
@@ -181,7 +155,7 @@ function getCommitSha(context) {
181
155
  if (context.type === 'commit') {
182
156
  return context.commitSha;
183
157
  }
184
- if (context.type === 'branch' || context.type === 'mr') {
158
+ if (context.type === 'mr') {
185
159
  return process.env.CI_COMMIT_SHA;
186
160
  }
187
161
  return undefined;
package/dist/git/local.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Local Environment - Complete Isolation
3
+ * Local Environment
4
4
  *
5
5
  * All Local-specific logic is contained in this file.
6
6
  * No dependencies on other environment implementations.
@@ -19,7 +19,7 @@ exports.getLocalContext = getLocalContext;
19
19
  const simple_git_1 = __importDefault(require("simple-git"));
20
20
  const diff_1 = require("./diff");
21
21
  /**
22
- * Gets all Local context in one call - completely isolated from other environments.
22
+ * Gets all Local context
23
23
  */
24
24
  async function getLocalContext(repoRoot, commitSha) {
25
25
  const git = (0, simple_git_1.default)(repoRoot);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Vercel Environment - Complete Isolation
3
+ * Vercel Environment
4
4
  *
5
5
  * All Vercel-specific logic is contained in this file.
6
6
  * No dependencies on other environment implementations.
@@ -20,7 +20,7 @@ const simple_git_1 = __importDefault(require("simple-git"));
20
20
  const child_process_1 = require("child_process");
21
21
  const diff_1 = require("./diff");
22
22
  /**
23
- * Gets all Vercel context in one call - completely isolated from other environments.
23
+ * Gets all Vercel context
24
24
  */
25
25
  async function getVercelContext(repoRoot) {
26
26
  const git = (0, simple_git_1.default)(repoRoot);
package/dist/index.js CHANGED
@@ -49,6 +49,7 @@ if (fs.existsSync(envLocalPath)) {
49
49
  const commander_1 = require("commander");
50
50
  const check_1 = require("./commands/check");
51
51
  const init_1 = require("./commands/init");
52
+ const logger_1 = require("./utils/logger");
52
53
  const program = new commander_1.Command();
53
54
  program
54
55
  .name('threadlines')
@@ -57,29 +58,39 @@ program
57
58
  program
58
59
  .command('init')
59
60
  .description('Create a template threadline file to get started')
60
- .action(init_1.initCommand);
61
+ .option('--debug', 'Enable debug logging (verbose output)')
62
+ .action((options) => {
63
+ if (options.debug) {
64
+ (0, logger_1.enableDebug)();
65
+ }
66
+ (0, init_1.initCommand)();
67
+ });
61
68
  program
62
69
  .command('check')
63
70
  .description('Check code against your threadlines')
64
- .option('--api-url <url>', 'Threadline server URL', process.env.THREADLINE_API_URL || 'https://devthreadline.com')
65
71
  .option('--full', 'Show all results (compliant, attention, not_relevant). Default: only attention items')
66
- .option('--branch <name>', 'Review all commits in branch vs base (e.g., --branch feature/new-feature)')
67
72
  .option('--commit <ref>', 'Review specific commit. Accepts commit SHA or git reference (e.g., HEAD, HEAD~1, abc123). Example: --commit HEAD')
68
73
  .option('--file <path>', 'Review entire file (all lines as additions)')
69
74
  .option('--folder <path>', 'Review all files in folder recursively')
70
75
  .option('--files <paths...>', 'Review multiple specified files')
76
+ .option('--debug', 'Enable debug logging (verbose output)')
71
77
  .addHelpText('after', `
72
78
  Examples:
73
79
  $ threadlines check # Check staged/unstaged changes (local dev)
74
80
  $ threadlines check --commit HEAD # Check latest commit locally
75
- $ threadlines check --branch main # Check all commits in branch vs base
76
81
  $ threadlines check --file src/api.ts # Check entire file
77
82
  $ threadlines check --full # Show all results (not just attention items)
83
+ $ threadlines check --debug # Enable verbose debug output
78
84
 
79
85
  Auto-detection in CI:
80
- - CI with branch detected → reviews all commits in branch vs base
81
- - CI with commit SHA detected → reviews specific commit
86
+ - PR/MR context → reviews all changes in the PR/MR
87
+ - Push to any branch → reviews the commit being pushed
82
88
  - Local development → reviews staged/unstaged changes
83
89
  `)
84
- .action(check_1.checkCommand);
90
+ .action((options) => {
91
+ if (options.debug) {
92
+ (0, logger_1.enableDebug)();
93
+ }
94
+ (0, check_1.checkCommand)(options);
95
+ });
85
96
  program.parse();
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.DEFAULT_CONFIG = void 0;
40
+ exports.loadConfig = loadConfig;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const simple_git_1 = __importDefault(require("simple-git"));
44
+ exports.DEFAULT_CONFIG = {
45
+ mode: 'online',
46
+ api_url: 'https://devthreadline.com',
47
+ openai_model: 'gpt-5.2',
48
+ openai_service_tier: 'Flex',
49
+ diff_context_lines: 10,
50
+ };
51
+ /**
52
+ * Finds the git root directory by walking up from startDir.
53
+ * Returns startDir if not in a git repository.
54
+ */
55
+ async function findGitRoot(startDir) {
56
+ try {
57
+ const git = (0, simple_git_1.default)(startDir);
58
+ const isRepo = await git.checkIsRepo();
59
+ if (isRepo) {
60
+ return (await git.revparse(['--show-toplevel'])).trim();
61
+ }
62
+ }
63
+ catch {
64
+ // Not a git repo or error - return startDir
65
+ }
66
+ return startDir;
67
+ }
68
+ /**
69
+ * Loads configuration from .threadlinerc file.
70
+ *
71
+ * Priority:
72
+ * 1. Built-in defaults
73
+ * 2. .threadlinerc file (if exists) - merged with defaults
74
+ *
75
+ * Searches for .threadlinerc starting from startDir, walking up to git root.
76
+ * If no file found, returns defaults.
77
+ */
78
+ async function loadConfig(startDir) {
79
+ // Start with defaults
80
+ const config = { ...exports.DEFAULT_CONFIG };
81
+ // Find git root to limit search scope
82
+ const gitRoot = await findGitRoot(startDir);
83
+ // Look for .threadlinerc starting from startDir, up to git root
84
+ let currentDir = startDir;
85
+ let configPath = null;
86
+ while (true) {
87
+ const candidatePath = path.join(currentDir, '.threadlinerc');
88
+ if (fs.existsSync(candidatePath)) {
89
+ configPath = candidatePath;
90
+ break;
91
+ }
92
+ // Stop at git root
93
+ if (path.resolve(currentDir) === path.resolve(gitRoot)) {
94
+ break;
95
+ }
96
+ // Move up one directory
97
+ const parentDir = path.dirname(currentDir);
98
+ if (parentDir === currentDir) {
99
+ // Reached filesystem root
100
+ break;
101
+ }
102
+ currentDir = parentDir;
103
+ }
104
+ // If config file found, parse and merge
105
+ if (configPath) {
106
+ try {
107
+ const configContent = fs.readFileSync(configPath, 'utf-8');
108
+ const fileConfig = JSON.parse(configContent);
109
+ // Merge file config into defaults (file overrides defaults)
110
+ Object.assign(config, fileConfig);
111
+ }
112
+ catch (error) {
113
+ // If file exists but can't be parsed, log warning but continue with defaults
114
+ console.warn(`Warning: Failed to parse .threadlinerc at ${configPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
115
+ }
116
+ }
117
+ return config;
118
+ }
@@ -1,74 +1,115 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
38
5
  Object.defineProperty(exports, "__esModule", { value: true });
39
6
  exports.getThreadlineApiKey = getThreadlineApiKey;
40
7
  exports.getThreadlineAccount = getThreadlineAccount;
41
- const fs = __importStar(require("fs"));
42
- const path = __importStar(require("path"));
43
- const dotenv_1 = __importDefault(require("dotenv"));
44
- /**
45
- * Loads environment variables from .env.local file in the project root
46
- * (where the user runs the command, not the CLI package directory)
47
- */
48
- function loadEnvLocal() {
49
- const projectRoot = process.cwd();
50
- const envLocalPath = path.join(projectRoot, '.env.local');
51
- if (fs.existsSync(envLocalPath)) {
52
- dotenv_1.default.config({ path: envLocalPath });
53
- }
54
- }
8
+ exports.getOpenAIConfig = getOpenAIConfig;
9
+ exports.logOpenAIConfig = logOpenAIConfig;
10
+ exports.isDirectModeAvailable = isDirectModeAvailable;
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const logger_1 = require("./logger");
13
+ // Default values for OpenAI configuration
14
+ const OPENAI_MODEL_DEFAULT = 'gpt-5.2';
15
+ const OPENAI_SERVICE_TIER_DEFAULT = 'Flex';
55
16
  /**
56
17
  * Gets THREADLINE_API_KEY from environment.
57
- * Priority: process.env.THREADLINE_API_KEY → .env.local file
18
+ *
19
+ * Note: .env.local is automatically loaded at CLI startup (see index.ts).
20
+ * In CI/CD, environment variables are injected directly into process.env.
58
21
  */
59
22
  function getThreadlineApiKey() {
60
- // Load .env.local if it exists (doesn't override existing env vars)
61
- loadEnvLocal();
62
- // Check environment variable (from shell or CI/CD)
63
- return process.env.THREADLINE_API_KEY;
23
+ const apiKey = process.env.THREADLINE_API_KEY;
24
+ if (apiKey) {
25
+ logger_1.logger.debug('THREADLINE_API_KEY: found (value hidden for security)');
26
+ }
27
+ else {
28
+ logger_1.logger.debug('THREADLINE_API_KEY: not set');
29
+ }
30
+ return apiKey;
64
31
  }
65
32
  /**
66
33
  * Gets THREADLINE_ACCOUNT from environment.
67
- * Priority: process.env.THREADLINE_ACCOUNT → .env.local file
34
+ *
35
+ * Note: .env.local is automatically loaded at CLI startup (see index.ts).
36
+ * In CI/CD, environment variables are injected directly into process.env.
68
37
  */
69
38
  function getThreadlineAccount() {
70
- // Load .env.local if it exists (doesn't override existing env vars)
71
- loadEnvLocal();
72
- // Check environment variable (from shell or CI/CD)
73
- return process.env.THREADLINE_ACCOUNT;
39
+ const account = process.env.THREADLINE_ACCOUNT;
40
+ if (account) {
41
+ logger_1.logger.debug(`THREADLINE_ACCOUNT: ${account}`);
42
+ }
43
+ else {
44
+ logger_1.logger.debug('THREADLINE_ACCOUNT: not set');
45
+ }
46
+ return account;
47
+ }
48
+ /**
49
+ * Gets OpenAI configuration from environment variables.
50
+ *
51
+ * Required:
52
+ * - OPENAI_API_KEY: Your OpenAI API key
53
+ *
54
+ * Optional (with defaults):
55
+ * - OPENAI_MODEL: Model to use (default: gpt-5.2)
56
+ * - OPENAI_SERVICE_TIER: Service tier (default: Flex)
57
+ *
58
+ * Returns undefined if OPENAI_API_KEY is not set.
59
+ *
60
+ * Note: .env.local is automatically loaded at CLI startup (see index.ts).
61
+ * In CI/CD, environment variables are injected directly into process.env.
62
+ */
63
+ function getOpenAIConfig() {
64
+ const apiKey = process.env.OPENAI_API_KEY;
65
+ if (!apiKey) {
66
+ logger_1.logger.debug('OPENAI_API_KEY: not set (direct mode unavailable)');
67
+ return undefined;
68
+ }
69
+ logger_1.logger.debug('OPENAI_API_KEY: found (value hidden for security)');
70
+ const model = process.env.OPENAI_MODEL || OPENAI_MODEL_DEFAULT;
71
+ const serviceTier = process.env.OPENAI_SERVICE_TIER || OPENAI_SERVICE_TIER_DEFAULT;
72
+ if (process.env.OPENAI_MODEL) {
73
+ logger_1.logger.debug(`OPENAI_MODEL: ${model} (from environment)`);
74
+ }
75
+ else {
76
+ logger_1.logger.debug(`OPENAI_MODEL: ${model} (using default)`);
77
+ }
78
+ if (process.env.OPENAI_SERVICE_TIER) {
79
+ logger_1.logger.debug(`OPENAI_SERVICE_TIER: ${serviceTier} (from environment)`);
80
+ }
81
+ else {
82
+ logger_1.logger.debug(`OPENAI_SERVICE_TIER: ${serviceTier} (using default)`);
83
+ }
84
+ return {
85
+ apiKey,
86
+ model,
87
+ serviceTier
88
+ };
89
+ }
90
+ /**
91
+ * Logs the OpenAI configuration being used.
92
+ * Call this when starting direct LLM mode to inform the user.
93
+ */
94
+ function logOpenAIConfig(config) {
95
+ console.log(chalk_1.default.blue('OpenAI Direct Mode:'));
96
+ console.log(chalk_1.default.gray(` Model: ${config.model}${config.model === OPENAI_MODEL_DEFAULT ? ' (default)' : ''}`));
97
+ console.log(chalk_1.default.gray(` Service Tier: ${config.serviceTier}${config.serviceTier === OPENAI_SERVICE_TIER_DEFAULT ? ' (default)' : ''}`));
98
+ console.log('');
99
+ }
100
+ /**
101
+ * Checks if direct OpenAI mode is available (OPENAI_API_KEY is set).
102
+ *
103
+ * Note: .env.local is automatically loaded at CLI startup (see index.ts).
104
+ * In CI/CD, environment variables are injected directly into process.env.
105
+ */
106
+ function isDirectModeAvailable() {
107
+ const available = !!process.env.OPENAI_API_KEY;
108
+ if (available) {
109
+ logger_1.logger.debug('Direct OpenAI mode: available (OPENAI_API_KEY found)');
110
+ }
111
+ else {
112
+ logger_1.logger.debug('Direct OpenAI mode: unavailable (OPENAI_API_KEY not set)');
113
+ }
114
+ return available;
74
115
  }