@link-assistant/hive-mind 1.0.5 → 1.1.0
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/CHANGELOG.md +20 -0
- package/package.json +1 -1
- package/src/agent.lib.mjs +3 -1
- package/src/agent.prompts.lib.mjs +43 -5
- package/src/claude.lib.mjs +3 -1
- package/src/claude.prompts.lib.mjs +44 -5
- package/src/codex.lib.mjs +3 -1
- package/src/codex.prompts.lib.mjs +43 -5
- package/src/opencode.lib.mjs +3 -1
- package/src/opencode.prompts.lib.mjs +44 -4
- package/src/solve.config.lib.mjs +5 -0
- package/src/solve.mjs +13 -2
- package/src/solve.repository.lib.mjs +47 -2
- package/src/usage-limit.lib.mjs +105 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @link-assistant/hive-mind
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4c46685: Add --enable-workspaces option for separate workspace directories
|
|
8
|
+
|
|
9
|
+
This feature adds support for creating separate workspace directories for all AI tools (claude, opencode, codex, agent). When enabled with `--enable-workspaces`, the tool creates a structured workspace:
|
|
10
|
+
- `/tmp/hive-mind-solve-gh-{owner}/{repo}-issue-{issueNumber}-workspace-{timestamp}/repository` - for the cloned repo
|
|
11
|
+
- `/tmp/hive-mind-solve-gh-{owner}/{repo}-issue-{issueNumber}-workspace-{timestamp}/tmp` - for temp files, logs, downloads
|
|
12
|
+
|
|
13
|
+
The workspace tmp directory is passed to all tool prompts, with explicit examples for saving CI logs, diffs, and command outputs.
|
|
14
|
+
|
|
15
|
+
- Add relative time display for usage limit reset messages in GitHub comments
|
|
16
|
+
|
|
17
|
+
When the AI tool hits its usage limit, GitHub comments now show the reset time in a more user-friendly format:
|
|
18
|
+
- Before: `11:00 PM`
|
|
19
|
+
- After: `in 1h 23m (11:00 PM UTC)`
|
|
20
|
+
|
|
21
|
+
This helps users in different timezones understand when the limit will reset more quickly.
|
|
22
|
+
|
|
3
23
|
## 1.0.5
|
|
4
24
|
|
|
5
25
|
### Patch Changes
|
package/package.json
CHANGED
package/src/agent.lib.mjs
CHANGED
|
@@ -245,7 +245,7 @@ export const handleAgentRuntimeSwitch = async () => {
|
|
|
245
245
|
|
|
246
246
|
// Main function to execute Agent with prompts and settings
|
|
247
247
|
export const executeAgent = async params => {
|
|
248
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, agentPath = 'agent', $ } = params;
|
|
248
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, agentPath = 'agent', $ } = params;
|
|
249
249
|
|
|
250
250
|
// Import prompt building functions from agent.prompts.lib.mjs
|
|
251
251
|
const { buildUserPrompt, buildSystemPrompt } = await import('./agent.prompts.lib.mjs');
|
|
@@ -258,6 +258,7 @@ export const executeAgent = async params => {
|
|
|
258
258
|
prUrl,
|
|
259
259
|
branchName,
|
|
260
260
|
tempDir,
|
|
261
|
+
workspaceTmpDir,
|
|
261
262
|
isContinueMode,
|
|
262
263
|
mergeStateStatus,
|
|
263
264
|
forkedRepo,
|
|
@@ -276,6 +277,7 @@ export const executeAgent = async params => {
|
|
|
276
277
|
prNumber,
|
|
277
278
|
branchName,
|
|
278
279
|
tempDir,
|
|
280
|
+
workspaceTmpDir,
|
|
279
281
|
isContinueMode,
|
|
280
282
|
forkedRepo,
|
|
281
283
|
argv,
|
|
@@ -11,7 +11,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mj
|
|
|
11
11
|
* @returns {string} The formatted user prompt
|
|
12
12
|
*/
|
|
13
13
|
export const buildUserPrompt = params => {
|
|
14
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
14
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
15
15
|
|
|
16
16
|
const promptLines = [];
|
|
17
17
|
|
|
@@ -26,6 +26,11 @@ export const buildUserPrompt = params => {
|
|
|
26
26
|
promptLines.push(`Your prepared branch: ${branchName}`);
|
|
27
27
|
promptLines.push(`Your prepared working directory: ${tempDir}`);
|
|
28
28
|
|
|
29
|
+
// Workspace tmp directory for logs and temp files (when --enable-workspaces is used)
|
|
30
|
+
if (workspaceTmpDir) {
|
|
31
|
+
promptLines.push(`Your prepared tmp directory for logs and downloads: ${workspaceTmpDir}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
// PR info if available
|
|
30
35
|
if (prUrl) {
|
|
31
36
|
promptLines.push(`Your prepared Pull Request: ${prUrl}`);
|
|
@@ -76,7 +81,7 @@ export const buildUserPrompt = params => {
|
|
|
76
81
|
* @returns {string} The formatted system prompt
|
|
77
82
|
*/
|
|
78
83
|
export const buildSystemPrompt = params => {
|
|
79
|
-
const { owner, repo, issueNumber, prNumber, branchName, argv } = params;
|
|
84
|
+
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv } = params;
|
|
80
85
|
|
|
81
86
|
// Build thinking instruction based on --think level
|
|
82
87
|
let thinkLine = '';
|
|
@@ -90,9 +95,42 @@ export const buildSystemPrompt = params => {
|
|
|
90
95
|
thinkLine = `\n${thinkMessages[argv.think]}\n`;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
|
|
98
|
+
// Build workspace-specific instructions and examples
|
|
99
|
+
let workspaceInstructions = '';
|
|
100
|
+
if (workspaceTmpDir) {
|
|
101
|
+
workspaceInstructions = `
|
|
102
|
+
Workspace tmp directory.
|
|
103
|
+
- Use ${workspaceTmpDir} for all temporary files, logs, and downloads.
|
|
104
|
+
- When saving command output to files, save to ${workspaceTmpDir}/command-output.log.
|
|
105
|
+
- When downloading CI logs, save to ${workspaceTmpDir}/ci-logs/.
|
|
106
|
+
- When saving diffs for review, save to ${workspaceTmpDir}/diffs/.
|
|
107
|
+
- When creating debug files, save to ${workspaceTmpDir}/debug/.
|
|
108
|
+
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
94
111
|
|
|
95
|
-
|
|
112
|
+
// Build CI command examples with workspace tmp paths
|
|
113
|
+
let ciExamples = '';
|
|
114
|
+
if (workspaceTmpDir) {
|
|
115
|
+
ciExamples = `
|
|
116
|
+
CI investigation with workspace tmp directory.
|
|
117
|
+
- When downloading CI run logs:
|
|
118
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log > ${workspaceTmpDir}/ci-logs/run-RUN_ID.log
|
|
119
|
+
- When downloading failed job logs:
|
|
120
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log-failed > ${workspaceTmpDir}/ci-logs/run-RUN_ID-failed.log
|
|
121
|
+
- When listing CI runs with details:
|
|
122
|
+
gh run list --repo ${owner}/${repo} --branch ${branchName} --limit 5 --json databaseId,conclusion,createdAt,headSha > ${workspaceTmpDir}/ci-logs/recent-runs.json
|
|
123
|
+
- When saving PR diff for review:
|
|
124
|
+
gh pr diff ${prNumber} --repo ${owner}/${repo} > ${workspaceTmpDir}/diffs/pr-${prNumber}.diff
|
|
125
|
+
- When saving command output with stderr:
|
|
126
|
+
npm test 2>&1 | tee ${workspaceTmpDir}/test-output.log
|
|
127
|
+
- When investigating issue details:
|
|
128
|
+
gh issue view ${issueNumber} --repo ${owner}/${repo} --json body,comments > ${workspaceTmpDir}/issue-${issueNumber}.json
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return `You are AI issue solver using @link-assistant/agent.${thinkLine}
|
|
133
|
+
${workspaceInstructions}General guidelines.
|
|
96
134
|
- When you execute commands, always save their logs to files for easier reading if the output becomes large.
|
|
97
135
|
- When running commands, do not set a timeout yourself — let them run as long as needed.
|
|
98
136
|
- When running sudo commands (especially package installations), always run them in the background to avoid timeout issues.
|
|
@@ -185,7 +223,7 @@ GitHub CLI command patterns.
|
|
|
185
223
|
- When adding PR comment, use gh pr comment NUMBER --body "text" --repo OWNER/REPO.
|
|
186
224
|
- When adding issue comment, use gh issue comment NUMBER --body "text" --repo OWNER/REPO.
|
|
187
225
|
- When viewing PR details, use gh pr view NUMBER --repo OWNER/REPO.
|
|
188
|
-
- When filtering with jq, use gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${getArchitectureCareSubPrompt(argv)}`;
|
|
226
|
+
- When filtering with jq, use gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${ciExamples}${getArchitectureCareSubPrompt(argv)}`;
|
|
189
227
|
};
|
|
190
228
|
|
|
191
229
|
// Export all functions as default object too
|
package/src/claude.lib.mjs
CHANGED
|
@@ -413,7 +413,7 @@ export const checkPlaywrightMcpAvailability = async () => {
|
|
|
413
413
|
* @returns {Object} Result of the execution including success status and session info
|
|
414
414
|
*/
|
|
415
415
|
export const executeClaude = async params => {
|
|
416
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, setLogFile, getLogFile, formatAligned, getResourceSnapshot, claudePath, $ } = params;
|
|
416
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, setLogFile, getLogFile, formatAligned, getResourceSnapshot, claudePath, $ } = params;
|
|
417
417
|
// Import prompt building functions from claude.prompts.lib.mjs
|
|
418
418
|
const { buildUserPrompt, buildSystemPrompt } = await import('./claude.prompts.lib.mjs');
|
|
419
419
|
// Build the user prompt
|
|
@@ -424,6 +424,7 @@ export const executeClaude = async params => {
|
|
|
424
424
|
prUrl,
|
|
425
425
|
branchName,
|
|
426
426
|
tempDir,
|
|
427
|
+
workspaceTmpDir,
|
|
427
428
|
isContinueMode,
|
|
428
429
|
mergeStateStatus,
|
|
429
430
|
forkedRepo,
|
|
@@ -443,6 +444,7 @@ export const executeClaude = async params => {
|
|
|
443
444
|
prUrl,
|
|
444
445
|
branchName,
|
|
445
446
|
tempDir,
|
|
447
|
+
workspaceTmpDir,
|
|
446
448
|
isContinueMode,
|
|
447
449
|
forkedRepo,
|
|
448
450
|
argv,
|
|
@@ -11,7 +11,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mj
|
|
|
11
11
|
* @returns {string} The formatted user prompt
|
|
12
12
|
*/
|
|
13
13
|
export const buildUserPrompt = params => {
|
|
14
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, forkedRepo, feedbackLines, owner, repo, argv, contributingGuidelines } = params;
|
|
14
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, owner, repo, argv, contributingGuidelines } = params;
|
|
15
15
|
|
|
16
16
|
const promptLines = [];
|
|
17
17
|
|
|
@@ -26,6 +26,11 @@ export const buildUserPrompt = params => {
|
|
|
26
26
|
promptLines.push(`Your prepared branch: ${branchName}`);
|
|
27
27
|
promptLines.push(`Your prepared working directory: ${tempDir}`);
|
|
28
28
|
|
|
29
|
+
// Workspace tmp directory for logs and temp files (when --enable-workspaces is used)
|
|
30
|
+
if (workspaceTmpDir) {
|
|
31
|
+
promptLines.push(`Your prepared tmp directory for logs and downloads: ${workspaceTmpDir}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
// PR info if available
|
|
30
35
|
if (prUrl) {
|
|
31
36
|
promptLines.push(`Your prepared Pull Request: ${prUrl}`);
|
|
@@ -82,7 +87,7 @@ export const buildUserPrompt = params => {
|
|
|
82
87
|
* @returns {string} The formatted system prompt
|
|
83
88
|
*/
|
|
84
89
|
export const buildSystemPrompt = params => {
|
|
85
|
-
const { owner, repo, issueNumber, prNumber, branchName, argv } = params;
|
|
90
|
+
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv } = params;
|
|
86
91
|
|
|
87
92
|
// Build thinking instruction based on --think level
|
|
88
93
|
let thinkLine = '';
|
|
@@ -96,10 +101,44 @@ export const buildSystemPrompt = params => {
|
|
|
96
101
|
thinkLine = `\n${thinkMessages[argv.think]}\n`;
|
|
97
102
|
}
|
|
98
103
|
|
|
104
|
+
// Build workspace-specific instructions and examples
|
|
105
|
+
let workspaceInstructions = '';
|
|
106
|
+
if (workspaceTmpDir) {
|
|
107
|
+
workspaceInstructions = `
|
|
108
|
+
Workspace tmp directory.
|
|
109
|
+
- Use ${workspaceTmpDir} for all temporary files, logs, and downloads.
|
|
110
|
+
- When saving command output to files, save to ${workspaceTmpDir}/command-output.log.
|
|
111
|
+
- When downloading CI logs, save to ${workspaceTmpDir}/ci-logs/.
|
|
112
|
+
- When saving diffs for review, save to ${workspaceTmpDir}/diffs/.
|
|
113
|
+
- When creating debug files, save to ${workspaceTmpDir}/debug/.
|
|
114
|
+
|
|
115
|
+
`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Build CI command examples with workspace tmp paths
|
|
119
|
+
let ciExamples = '';
|
|
120
|
+
if (workspaceTmpDir) {
|
|
121
|
+
ciExamples = `
|
|
122
|
+
CI investigation with workspace tmp directory.
|
|
123
|
+
- When downloading CI run logs:
|
|
124
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log > ${workspaceTmpDir}/ci-logs/run-RUN_ID.log
|
|
125
|
+
- When downloading failed job logs:
|
|
126
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log-failed > ${workspaceTmpDir}/ci-logs/run-RUN_ID-failed.log
|
|
127
|
+
- When listing CI runs with details:
|
|
128
|
+
gh run list --repo ${owner}/${repo} --branch ${branchName} --limit 5 --json databaseId,conclusion,createdAt,headSha > ${workspaceTmpDir}/ci-logs/recent-runs.json
|
|
129
|
+
- When saving PR diff for review:
|
|
130
|
+
gh pr diff ${prNumber} --repo ${owner}/${repo} > ${workspaceTmpDir}/diffs/pr-${prNumber}.diff
|
|
131
|
+
- When saving command output with stderr:
|
|
132
|
+
npm test 2>&1 | tee ${workspaceTmpDir}/test-output.log
|
|
133
|
+
- When investigating issue details:
|
|
134
|
+
gh issue view ${issueNumber} --repo ${owner}/${repo} --json body,comments > ${workspaceTmpDir}/issue-${issueNumber}.json
|
|
135
|
+
|
|
136
|
+
`;
|
|
137
|
+
}
|
|
138
|
+
|
|
99
139
|
// Use backticks for jq commands to avoid quote escaping issues
|
|
100
140
|
return `You are an AI issue solver. You prefer to find the root cause of each and every issue. When you talk, you prefer to speak with facts which you have double-checked yourself or cite sources that provide evidence, like quote actual code or give references to documents or pages found on the internet. You are polite and patient, and prefer to assume good intent, trying your best to be helpful. If you are unsure or have assumptions, you prefer to test them yourself or ask questions to clarify requirements.${thinkLine}
|
|
101
|
-
|
|
102
|
-
General guidelines.
|
|
141
|
+
${workspaceInstructions}General guidelines.
|
|
103
142
|
- When you execute commands, always save their logs to files for easier reading if the output becomes large.
|
|
104
143
|
- When running commands, do not set a timeout yourself — let them run as long as needed (default timeout - 2 minutes is more than enough), and once they finish, review the logs in the file.
|
|
105
144
|
- When running sudo commands (especially package installations like apt-get, yum, npm install, etc.), always run them in the background to avoid timeout issues and permission errors when the process needs to be killed. Use the run_in_background parameter or append & to the command.${
|
|
@@ -243,7 +282,7 @@ Plan sub-agent usage.
|
|
|
243
282
|
- When using the Plan sub-agent, you can add it as the first item in your todo list.
|
|
244
283
|
- When you delegate planning, use the Task tool with subagent_type="Plan" before starting implementation work.`
|
|
245
284
|
: ''
|
|
246
|
-
}${getArchitectureCareSubPrompt(argv)}`;
|
|
285
|
+
}${ciExamples}${getArchitectureCareSubPrompt(argv)}`;
|
|
247
286
|
};
|
|
248
287
|
|
|
249
288
|
// Export all functions as default object too
|
package/src/codex.lib.mjs
CHANGED
|
@@ -115,7 +115,7 @@ export const handleCodexRuntimeSwitch = async () => {
|
|
|
115
115
|
|
|
116
116
|
// Main function to execute Codex with prompts and settings
|
|
117
117
|
export const executeCodex = async params => {
|
|
118
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, codexPath = 'codex', $ } = params;
|
|
118
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, codexPath = 'codex', $ } = params;
|
|
119
119
|
|
|
120
120
|
// Import prompt building functions from codex.prompts.lib.mjs
|
|
121
121
|
const { buildUserPrompt, buildSystemPrompt } = await import('./codex.prompts.lib.mjs');
|
|
@@ -128,6 +128,7 @@ export const executeCodex = async params => {
|
|
|
128
128
|
prUrl,
|
|
129
129
|
branchName,
|
|
130
130
|
tempDir,
|
|
131
|
+
workspaceTmpDir,
|
|
131
132
|
isContinueMode,
|
|
132
133
|
mergeStateStatus,
|
|
133
134
|
forkedRepo,
|
|
@@ -146,6 +147,7 @@ export const executeCodex = async params => {
|
|
|
146
147
|
prNumber,
|
|
147
148
|
branchName,
|
|
148
149
|
tempDir,
|
|
150
|
+
workspaceTmpDir,
|
|
149
151
|
isContinueMode,
|
|
150
152
|
forkedRepo,
|
|
151
153
|
argv,
|
|
@@ -11,7 +11,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mj
|
|
|
11
11
|
* @returns {string} The formatted user prompt
|
|
12
12
|
*/
|
|
13
13
|
export const buildUserPrompt = params => {
|
|
14
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
14
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
15
15
|
|
|
16
16
|
const promptLines = [];
|
|
17
17
|
|
|
@@ -26,6 +26,11 @@ export const buildUserPrompt = params => {
|
|
|
26
26
|
promptLines.push(`Your prepared branch: ${branchName}`);
|
|
27
27
|
promptLines.push(`Your prepared working directory: ${tempDir}`);
|
|
28
28
|
|
|
29
|
+
// Workspace tmp directory for logs and temp files (when --enable-workspaces is used)
|
|
30
|
+
if (workspaceTmpDir) {
|
|
31
|
+
promptLines.push(`Your prepared tmp directory for logs and downloads: ${workspaceTmpDir}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
// PR info if available
|
|
30
35
|
if (prUrl) {
|
|
31
36
|
promptLines.push(`Your prepared Pull Request: ${prUrl}`);
|
|
@@ -76,7 +81,7 @@ export const buildUserPrompt = params => {
|
|
|
76
81
|
* @returns {string} The formatted system prompt
|
|
77
82
|
*/
|
|
78
83
|
export const buildSystemPrompt = params => {
|
|
79
|
-
const { owner, repo, issueNumber, prNumber, branchName, argv } = params;
|
|
84
|
+
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv } = params;
|
|
80
85
|
|
|
81
86
|
// Build thinking instruction based on --think level
|
|
82
87
|
let thinkLine = '';
|
|
@@ -90,9 +95,42 @@ export const buildSystemPrompt = params => {
|
|
|
90
95
|
thinkLine = `\n${thinkMessages[argv.think]}\n`;
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
|
|
98
|
+
// Build workspace-specific instructions and examples
|
|
99
|
+
let workspaceInstructions = '';
|
|
100
|
+
if (workspaceTmpDir) {
|
|
101
|
+
workspaceInstructions = `
|
|
102
|
+
Workspace tmp directory.
|
|
103
|
+
- Use ${workspaceTmpDir} for all temporary files, logs, and downloads.
|
|
104
|
+
- When saving command output to files, save to ${workspaceTmpDir}/command-output.log.
|
|
105
|
+
- When downloading CI logs, save to ${workspaceTmpDir}/ci-logs/.
|
|
106
|
+
- When saving diffs for review, save to ${workspaceTmpDir}/diffs/.
|
|
107
|
+
- When creating debug files, save to ${workspaceTmpDir}/debug/.
|
|
108
|
+
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
94
111
|
|
|
95
|
-
|
|
112
|
+
// Build CI command examples with workspace tmp paths
|
|
113
|
+
let ciExamples = '';
|
|
114
|
+
if (workspaceTmpDir) {
|
|
115
|
+
ciExamples = `
|
|
116
|
+
CI investigation with workspace tmp directory.
|
|
117
|
+
- When downloading CI run logs:
|
|
118
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log > ${workspaceTmpDir}/ci-logs/run-RUN_ID.log
|
|
119
|
+
- When downloading failed job logs:
|
|
120
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log-failed > ${workspaceTmpDir}/ci-logs/run-RUN_ID-failed.log
|
|
121
|
+
- When listing CI runs with details:
|
|
122
|
+
gh run list --repo ${owner}/${repo} --branch ${branchName} --limit 5 --json databaseId,conclusion,createdAt,headSha > ${workspaceTmpDir}/ci-logs/recent-runs.json
|
|
123
|
+
- When saving PR diff for review:
|
|
124
|
+
gh pr diff ${prNumber} --repo ${owner}/${repo} > ${workspaceTmpDir}/diffs/pr-${prNumber}.diff
|
|
125
|
+
- When saving command output with stderr:
|
|
126
|
+
npm test 2>&1 | tee ${workspaceTmpDir}/test-output.log
|
|
127
|
+
- When investigating issue details:
|
|
128
|
+
gh issue view ${issueNumber} --repo ${owner}/${repo} --json body,comments > ${workspaceTmpDir}/issue-${issueNumber}.json
|
|
129
|
+
`;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return `You are AI issue solver using OpenAI Codex.${thinkLine}
|
|
133
|
+
${workspaceInstructions}General guidelines.
|
|
96
134
|
- When you execute commands, always save their logs to files for easier reading if the output becomes large.
|
|
97
135
|
- When running commands, do not set a timeout yourself — let them run as long as needed (default timeout - 2 minutes is more than enough), and once they finish, review the logs in the file.
|
|
98
136
|
- When running sudo commands (especially package installations like apt-get, yum, npm install, etc.), always run them in the background to avoid timeout issues and permission errors when the process needs to be killed. Use the run_in_background parameter or append & to the command.
|
|
@@ -193,7 +231,7 @@ GitHub CLI command patterns.
|
|
|
193
231
|
- When adding PR comment, use gh pr comment NUMBER --body "text" --repo OWNER/REPO.
|
|
194
232
|
- When adding issue comment, use gh issue comment NUMBER --body "text" --repo OWNER/REPO.
|
|
195
233
|
- When viewing PR details, use gh pr view NUMBER --repo OWNER/REPO.
|
|
196
|
-
- When filtering with jq, use gh api repos/\${owner}/\${repo}/pulls/\${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${getArchitectureCareSubPrompt(argv)}`;
|
|
234
|
+
- When filtering with jq, use gh api repos/\${owner}/\${repo}/pulls/\${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${ciExamples}${getArchitectureCareSubPrompt(argv)}`;
|
|
197
235
|
};
|
|
198
236
|
|
|
199
237
|
// Export all functions as default object too
|
package/src/opencode.lib.mjs
CHANGED
|
@@ -109,7 +109,7 @@ export const handleOpenCodeRuntimeSwitch = async () => {
|
|
|
109
109
|
|
|
110
110
|
// Main function to execute OpenCode with prompts and settings
|
|
111
111
|
export const executeOpenCode = async params => {
|
|
112
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, opencodePath = 'opencode', $ } = params;
|
|
112
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, mergeStateStatus, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv, log, formatAligned, getResourceSnapshot, opencodePath = 'opencode', $ } = params;
|
|
113
113
|
|
|
114
114
|
// Import prompt building functions from opencode.prompts.lib.mjs
|
|
115
115
|
const { buildUserPrompt, buildSystemPrompt } = await import('./opencode.prompts.lib.mjs');
|
|
@@ -122,6 +122,7 @@ export const executeOpenCode = async params => {
|
|
|
122
122
|
prUrl,
|
|
123
123
|
branchName,
|
|
124
124
|
tempDir,
|
|
125
|
+
workspaceTmpDir,
|
|
125
126
|
isContinueMode,
|
|
126
127
|
mergeStateStatus,
|
|
127
128
|
forkedRepo,
|
|
@@ -140,6 +141,7 @@ export const executeOpenCode = async params => {
|
|
|
140
141
|
prNumber,
|
|
141
142
|
branchName,
|
|
142
143
|
tempDir,
|
|
144
|
+
workspaceTmpDir,
|
|
143
145
|
isContinueMode,
|
|
144
146
|
forkedRepo,
|
|
145
147
|
argv,
|
|
@@ -11,7 +11,7 @@ import { getArchitectureCareSubPrompt } from './architecture-care.prompts.lib.mj
|
|
|
11
11
|
* @returns {string} The formatted user prompt
|
|
12
12
|
*/
|
|
13
13
|
export const buildUserPrompt = params => {
|
|
14
|
-
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
14
|
+
const { issueUrl, issueNumber, prNumber, prUrl, branchName, tempDir, workspaceTmpDir, isContinueMode, forkedRepo, feedbackLines, forkActionsUrl, owner, repo, argv } = params;
|
|
15
15
|
|
|
16
16
|
const promptLines = [];
|
|
17
17
|
|
|
@@ -26,6 +26,11 @@ export const buildUserPrompt = params => {
|
|
|
26
26
|
promptLines.push(`Your prepared branch: ${branchName}`);
|
|
27
27
|
promptLines.push(`Your prepared working directory: ${tempDir}`);
|
|
28
28
|
|
|
29
|
+
// Workspace tmp directory for logs and temp files (when --enable-workspaces is used)
|
|
30
|
+
if (workspaceTmpDir) {
|
|
31
|
+
promptLines.push(`Your prepared tmp directory for logs and downloads: ${workspaceTmpDir}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
// PR info if available
|
|
30
35
|
if (prUrl) {
|
|
31
36
|
promptLines.push(`Your prepared Pull Request: ${prUrl}`);
|
|
@@ -76,7 +81,7 @@ export const buildUserPrompt = params => {
|
|
|
76
81
|
* @returns {string} The formatted system prompt
|
|
77
82
|
*/
|
|
78
83
|
export const buildSystemPrompt = params => {
|
|
79
|
-
const { owner, repo, issueNumber, prNumber, branchName, argv } = params;
|
|
84
|
+
const { owner, repo, issueNumber, prNumber, branchName, workspaceTmpDir, argv } = params;
|
|
80
85
|
|
|
81
86
|
// Build thinking instruction based on --think level
|
|
82
87
|
let thinkLine = '';
|
|
@@ -90,6 +95,41 @@ export const buildSystemPrompt = params => {
|
|
|
90
95
|
thinkLine = `\n${thinkMessages[argv.think]}\n`;
|
|
91
96
|
}
|
|
92
97
|
|
|
98
|
+
// Build workspace-specific instructions and examples
|
|
99
|
+
let workspaceInstructions = '';
|
|
100
|
+
if (workspaceTmpDir) {
|
|
101
|
+
workspaceInstructions = `
|
|
102
|
+
Workspace tmp directory.
|
|
103
|
+
- Use ${workspaceTmpDir} for all temporary files, logs, and downloads.
|
|
104
|
+
- When saving command output to files, save to ${workspaceTmpDir}/command-output.log.
|
|
105
|
+
- When downloading CI logs, save to ${workspaceTmpDir}/ci-logs/.
|
|
106
|
+
- When saving diffs for review, save to ${workspaceTmpDir}/diffs/.
|
|
107
|
+
- When creating debug files, save to ${workspaceTmpDir}/debug/.
|
|
108
|
+
|
|
109
|
+
`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Build CI command examples with workspace tmp paths
|
|
113
|
+
let ciExamples = '';
|
|
114
|
+
if (workspaceTmpDir) {
|
|
115
|
+
ciExamples = `
|
|
116
|
+
CI investigation with workspace tmp directory.
|
|
117
|
+
- When downloading CI run logs:
|
|
118
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log > ${workspaceTmpDir}/ci-logs/run-RUN_ID.log
|
|
119
|
+
- When downloading failed job logs:
|
|
120
|
+
gh run view RUN_ID --repo ${owner}/${repo} --log-failed > ${workspaceTmpDir}/ci-logs/run-RUN_ID-failed.log
|
|
121
|
+
- When listing CI runs with details:
|
|
122
|
+
gh run list --repo ${owner}/${repo} --branch ${branchName} --limit 5 --json databaseId,conclusion,createdAt,headSha > ${workspaceTmpDir}/ci-logs/recent-runs.json
|
|
123
|
+
- When saving PR diff for review:
|
|
124
|
+
gh pr diff ${prNumber} --repo ${owner}/${repo} > ${workspaceTmpDir}/diffs/pr-${prNumber}.diff
|
|
125
|
+
- When saving command output with stderr:
|
|
126
|
+
npm test 2>&1 | tee ${workspaceTmpDir}/test-output.log
|
|
127
|
+
- When investigating issue details:
|
|
128
|
+
gh issue view ${issueNumber} --repo ${owner}/${repo} --json body,comments > ${workspaceTmpDir}/issue-${issueNumber}.json
|
|
129
|
+
|
|
130
|
+
`;
|
|
131
|
+
}
|
|
132
|
+
|
|
93
133
|
return `You are AI issue solver using OpenCode.${thinkLine}
|
|
94
134
|
|
|
95
135
|
General guidelines.
|
|
@@ -103,7 +143,7 @@ General guidelines.
|
|
|
103
143
|
- When testing your assumptions, use the experiment scripts, and add it to experiments folder.
|
|
104
144
|
- When your experiments can show real world use case of the software, add it to examples folder.
|
|
105
145
|
- When you face something extremely hard, use divide and conquer — it always helps.
|
|
106
|
-
|
|
146
|
+
${workspaceInstructions}
|
|
107
147
|
Initial research.
|
|
108
148
|
- When you start, make sure you create detailed plan for yourself and follow your todo list step by step, make sure that as many points from these guidelines are added to your todo list to keep track of everything that can help you solve the issue with highest possible quality.
|
|
109
149
|
- When you read issue, read all details and comments thoroughly.
|
|
@@ -184,7 +224,7 @@ GitHub CLI command patterns.
|
|
|
184
224
|
- When adding PR comment, use gh pr comment NUMBER --body "text" --repo OWNER/REPO.
|
|
185
225
|
- When adding issue comment, use gh issue comment NUMBER --body "text" --repo OWNER/REPO.
|
|
186
226
|
- When viewing PR details, use gh pr view NUMBER --repo OWNER/REPO.
|
|
187
|
-
- When filtering with jq, use gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${getArchitectureCareSubPrompt(argv)}`;
|
|
227
|
+
- When filtering with jq, use gh api repos/${owner}/${repo}/pulls/${prNumber}/comments --paginate --jq 'reverse | .[0:5]'.${ciExamples}${getArchitectureCareSubPrompt(argv)}`;
|
|
188
228
|
};
|
|
189
229
|
|
|
190
230
|
// Export all functions as default object too
|
package/src/solve.config.lib.mjs
CHANGED
|
@@ -253,6 +253,11 @@ export const createYargsConfig = yargsInstance => {
|
|
|
253
253
|
choices: ['claude', 'opencode', 'codex', 'agent'],
|
|
254
254
|
default: 'claude',
|
|
255
255
|
})
|
|
256
|
+
.option('enable-workspaces', {
|
|
257
|
+
type: 'boolean',
|
|
258
|
+
description: 'Use separate workspace directory structure with repository/ and tmp/ folders. Works with all tools (claude, opencode, codex, agent). Experimental feature.',
|
|
259
|
+
default: false,
|
|
260
|
+
})
|
|
256
261
|
.option('interactive-mode', {
|
|
257
262
|
type: 'boolean',
|
|
258
263
|
description: '[EXPERIMENTAL] Post Claude output as PR comments in real-time. Only supported for --tool claude.',
|
package/src/solve.mjs
CHANGED
|
@@ -65,6 +65,9 @@ const { executeClaude } = claudeLib;
|
|
|
65
65
|
const githubLinking = await import('./github-linking.lib.mjs');
|
|
66
66
|
const { extractLinkedIssueNumber } = githubLinking;
|
|
67
67
|
|
|
68
|
+
const usageLimitLib = await import('./usage-limit.lib.mjs');
|
|
69
|
+
const { formatResetTimeWithRelative } = usageLimitLib;
|
|
70
|
+
|
|
68
71
|
const errorHandlers = await import('./solve.error-handlers.lib.mjs');
|
|
69
72
|
const { createUncaughtExceptionHandler, createUnhandledRejectionHandler, handleMainExecutionError } = errorHandlers;
|
|
70
73
|
|
|
@@ -508,7 +511,9 @@ if (isPrUrl) {
|
|
|
508
511
|
await log(`📝 Issue mode: Working with issue #${issueNumber}`);
|
|
509
512
|
}
|
|
510
513
|
// Create or find temporary directory for cloning the repository
|
|
511
|
-
|
|
514
|
+
// Pass workspace info for --enable-workspaces mode (works with all tools)
|
|
515
|
+
const workspaceInfo = argv.enableWorkspaces ? { owner, repo, issueNumber } : null;
|
|
516
|
+
const { tempDir, workspaceTmpDir } = await setupTempDirectory(argv, workspaceInfo);
|
|
512
517
|
// Populate cleanup context for signal handlers
|
|
513
518
|
cleanupContext.tempDir = tempDir;
|
|
514
519
|
cleanupContext.argv = argv;
|
|
@@ -761,6 +766,7 @@ try {
|
|
|
761
766
|
prUrl,
|
|
762
767
|
branchName,
|
|
763
768
|
tempDir,
|
|
769
|
+
workspaceTmpDir,
|
|
764
770
|
isContinueMode,
|
|
765
771
|
mergeStateStatus,
|
|
766
772
|
forkedRepo,
|
|
@@ -789,6 +795,7 @@ try {
|
|
|
789
795
|
prUrl,
|
|
790
796
|
branchName,
|
|
791
797
|
tempDir,
|
|
798
|
+
workspaceTmpDir,
|
|
792
799
|
isContinueMode,
|
|
793
800
|
mergeStateStatus,
|
|
794
801
|
forkedRepo,
|
|
@@ -817,6 +824,7 @@ try {
|
|
|
817
824
|
prUrl,
|
|
818
825
|
branchName,
|
|
819
826
|
tempDir,
|
|
827
|
+
workspaceTmpDir,
|
|
820
828
|
isContinueMode,
|
|
821
829
|
mergeStateStatus,
|
|
822
830
|
forkedRepo,
|
|
@@ -858,6 +866,7 @@ try {
|
|
|
858
866
|
prUrl,
|
|
859
867
|
branchName,
|
|
860
868
|
tempDir,
|
|
869
|
+
workspaceTmpDir,
|
|
861
870
|
isContinueMode,
|
|
862
871
|
mergeStateStatus,
|
|
863
872
|
forkedRepo,
|
|
@@ -961,7 +970,9 @@ try {
|
|
|
961
970
|
const tool = argv.tool || 'claude';
|
|
962
971
|
const resumeCmd = tool === 'claude' ? buildClaudeResumeCommand({ tempDir, sessionId, model: argv.model }) : null;
|
|
963
972
|
const resumeSection = resumeCmd ? `To resume after the limit resets, use:\n\`\`\`bash\n${resumeCmd}\n\`\`\`` : `Session ID: \`${sessionId}\``;
|
|
964
|
-
|
|
973
|
+
// Format the reset time with relative time if available
|
|
974
|
+
const formattedResetTime = resetTime ? formatResetTimeWithRelative(resetTime) : null;
|
|
975
|
+
const failureComment = formattedResetTime ? `❌ **Usage Limit Reached**\n\nThe AI tool has reached its usage limit. The limit will reset at: **${formattedResetTime}**\n\n${resumeSection}` : `❌ **Usage Limit Reached**\n\nThe AI tool has reached its usage limit. Please wait for the limit to reset.\n\n${resumeSection}`;
|
|
965
976
|
|
|
966
977
|
const commentResult = await $`gh pr comment ${prNumber} --repo ${owner}/${repo} --body ${failureComment}`;
|
|
967
978
|
if (commentResult.code === 0) {
|
|
@@ -199,11 +199,35 @@ export const validateForkParent = async (forkRepo, expectedUpstream) => {
|
|
|
199
199
|
}
|
|
200
200
|
};
|
|
201
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Build workspace directory name according to the specification:
|
|
204
|
+
* /tmp/hive-mind-solve-gh-{owner}/{repo}-issue-{issueNumber}-workspace-{timestamp}
|
|
205
|
+
*
|
|
206
|
+
* @param {string} owner - Repository owner
|
|
207
|
+
* @param {string} repo - Repository name
|
|
208
|
+
* @param {number|string} issueNumber - Issue number
|
|
209
|
+
* @param {number} timestamp - Unix timestamp
|
|
210
|
+
* @returns {string} The workspace directory path
|
|
211
|
+
*/
|
|
212
|
+
export const buildWorkspacePath = (owner, repo, issueNumber, timestamp) => {
|
|
213
|
+
// Format: /tmp/hive-mind-solve-gh-{owner}/{repo}-issue-{issueNumber}-workspace-{timestamp}
|
|
214
|
+
const baseDir = path.join(os.tmpdir(), `hive-mind-solve-gh-${owner}`);
|
|
215
|
+
const workspaceDir = path.join(baseDir, `${repo}-issue-${issueNumber}-workspace-${timestamp}`);
|
|
216
|
+
return workspaceDir;
|
|
217
|
+
};
|
|
218
|
+
|
|
202
219
|
// Create or find temporary directory for cloning the repository
|
|
203
|
-
|
|
220
|
+
// When --enable-workspaces is used, creates:
|
|
221
|
+
// {workspace}/repository - for the cloned repo
|
|
222
|
+
// {workspace}/tmp - for temp files, logs, downloads
|
|
223
|
+
export const setupTempDirectory = async (argv, workspaceInfo = null) => {
|
|
204
224
|
let tempDir;
|
|
225
|
+
let workspaceTmpDir = null;
|
|
205
226
|
let isResuming = argv.resume;
|
|
206
227
|
|
|
228
|
+
// Check if workspace mode should be enabled (works with all tools)
|
|
229
|
+
const useWorkspaces = argv.enableWorkspaces;
|
|
230
|
+
|
|
207
231
|
if (isResuming) {
|
|
208
232
|
// When resuming, try to find existing directory or create a new one
|
|
209
233
|
const scriptDir = path.dirname(process.argv[1]);
|
|
@@ -229,13 +253,34 @@ export const setupTempDirectory = async argv => {
|
|
|
229
253
|
await fs.mkdir(tempDir, { recursive: true });
|
|
230
254
|
await log(`Creating temporary directory for resumed session: ${tempDir}`);
|
|
231
255
|
}
|
|
256
|
+
} else if (useWorkspaces && workspaceInfo) {
|
|
257
|
+
// Workspace mode: create structured workspace with repository/ and tmp/ subdirectories
|
|
258
|
+
const { owner, repo, issueNumber } = workspaceInfo;
|
|
259
|
+
const timestamp = Date.now();
|
|
260
|
+
const workspaceDir = buildWorkspacePath(owner, repo, issueNumber, timestamp);
|
|
261
|
+
|
|
262
|
+
// Create the workspace structure:
|
|
263
|
+
// {workspace}/repository - where the repo will be cloned
|
|
264
|
+
// {workspace}/tmp - for temp files, logs, command outputs
|
|
265
|
+
const repoDir = path.join(workspaceDir, 'repository');
|
|
266
|
+
workspaceTmpDir = path.join(workspaceDir, 'tmp');
|
|
267
|
+
|
|
268
|
+
await fs.mkdir(repoDir, { recursive: true });
|
|
269
|
+
await fs.mkdir(workspaceTmpDir, { recursive: true });
|
|
270
|
+
|
|
271
|
+
tempDir = repoDir;
|
|
272
|
+
|
|
273
|
+
await log(`\n${formatAligned('📂', 'Workspace mode:', 'ENABLED')}`);
|
|
274
|
+
await log(formatAligned('', 'Workspace root:', workspaceDir, 2));
|
|
275
|
+
await log(formatAligned('', 'Repository dir:', repoDir, 2));
|
|
276
|
+
await log(formatAligned('', 'Temp dir:', workspaceTmpDir, 2));
|
|
232
277
|
} else {
|
|
233
278
|
tempDir = path.join(os.tmpdir(), `gh-issue-solver-${Date.now()}`);
|
|
234
279
|
await fs.mkdir(tempDir, { recursive: true });
|
|
235
280
|
await log(`\nCreating temporary directory: ${tempDir}`);
|
|
236
281
|
}
|
|
237
282
|
|
|
238
|
-
return { tempDir, isResuming };
|
|
283
|
+
return { tempDir, workspaceTmpDir, isResuming };
|
|
239
284
|
};
|
|
240
285
|
|
|
241
286
|
// Try to initialize an empty repository by creating a simple README.md
|
package/src/usage-limit.lib.mjs
CHANGED
|
@@ -142,6 +142,111 @@ export function detectUsageLimit(message) {
|
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* Parse time string (e.g., "11:00 PM") and convert to Date object for today
|
|
147
|
+
*
|
|
148
|
+
* @param {string} timeStr - Time string in format "HH:MM AM/PM"
|
|
149
|
+
* @returns {Date|null} - Date object or null if parsing fails
|
|
150
|
+
*/
|
|
151
|
+
export function parseResetTime(timeStr) {
|
|
152
|
+
if (!timeStr || typeof timeStr !== 'string') {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Match pattern like "11:00 PM" or "11:00PM"
|
|
157
|
+
const match = timeStr.match(/(\d{1,2}):(\d{2})\s*(AM|PM)/i);
|
|
158
|
+
if (!match) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
let hour = parseInt(match[1], 10);
|
|
163
|
+
const minute = parseInt(match[2], 10);
|
|
164
|
+
const ampm = match[3].toUpperCase();
|
|
165
|
+
|
|
166
|
+
// Convert to 24-hour format
|
|
167
|
+
if (ampm === 'PM' && hour !== 12) {
|
|
168
|
+
hour += 12;
|
|
169
|
+
} else if (ampm === 'AM' && hour === 12) {
|
|
170
|
+
hour = 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Create date for today with the parsed time
|
|
174
|
+
const now = new Date();
|
|
175
|
+
const resetDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hour, minute, 0, 0);
|
|
176
|
+
|
|
177
|
+
// If the time is in the past today, assume it's tomorrow
|
|
178
|
+
if (resetDate <= now) {
|
|
179
|
+
resetDate.setDate(resetDate.getDate() + 1);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return resetDate;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Format relative time (e.g., "in 1h 23m")
|
|
187
|
+
*
|
|
188
|
+
* @param {Date} resetDate - Date object for reset time
|
|
189
|
+
* @returns {string} - Formatted relative time string
|
|
190
|
+
*/
|
|
191
|
+
export function formatRelativeTime(resetDate) {
|
|
192
|
+
if (!resetDate || !(resetDate instanceof Date)) {
|
|
193
|
+
return '';
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const now = new Date();
|
|
197
|
+
const diffMs = resetDate - now;
|
|
198
|
+
|
|
199
|
+
if (diffMs <= 0) {
|
|
200
|
+
return 'now';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const totalSeconds = Math.floor(diffMs / 1000);
|
|
204
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
205
|
+
const totalHours = Math.floor(totalMinutes / 60);
|
|
206
|
+
const totalDays = Math.floor(totalHours / 24);
|
|
207
|
+
|
|
208
|
+
const days = totalDays;
|
|
209
|
+
const hours = totalHours % 24;
|
|
210
|
+
const minutes = totalMinutes % 60;
|
|
211
|
+
|
|
212
|
+
const parts = [];
|
|
213
|
+
if (days > 0) parts.push(`${days}d`);
|
|
214
|
+
if (hours > 0) parts.push(`${hours}h`);
|
|
215
|
+
if (minutes > 0 || parts.length === 0) parts.push(`${minutes}m`);
|
|
216
|
+
|
|
217
|
+
return `in ${parts.join(' ')}`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Format reset time with relative time and UTC time
|
|
222
|
+
* Example: "in 1h 23m (11:00 PM UTC)"
|
|
223
|
+
*
|
|
224
|
+
* @param {string} resetTime - Time string in format "HH:MM AM/PM"
|
|
225
|
+
* @returns {string} - Formatted string with relative and absolute time
|
|
226
|
+
*/
|
|
227
|
+
export function formatResetTimeWithRelative(resetTime) {
|
|
228
|
+
if (!resetTime) {
|
|
229
|
+
return resetTime;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const resetDate = parseResetTime(resetTime);
|
|
233
|
+
if (!resetDate) {
|
|
234
|
+
// If we can't parse it, return the original time
|
|
235
|
+
return resetTime;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const relativeTime = formatRelativeTime(resetDate);
|
|
239
|
+
|
|
240
|
+
// Format the UTC time
|
|
241
|
+
const utcHours = resetDate.getUTCHours();
|
|
242
|
+
const utcMinutes = resetDate.getUTCMinutes();
|
|
243
|
+
const utcAmPm = utcHours >= 12 ? 'PM' : 'AM';
|
|
244
|
+
const utcHour12 = utcHours % 12 || 12;
|
|
245
|
+
const utcTimeStr = `${utcHour12}:${String(utcMinutes).padStart(2, '0')} ${utcAmPm} UTC`;
|
|
246
|
+
|
|
247
|
+
return `${relativeTime} (${utcTimeStr})`;
|
|
248
|
+
}
|
|
249
|
+
|
|
145
250
|
/**
|
|
146
251
|
* Format usage limit error message for console output
|
|
147
252
|
*
|