@qelos/aidev 0.1.0 → 0.1.1
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 +1 -1
- package/dist/__tests__/git.test.d.ts +2 -0
- package/dist/__tests__/git.test.d.ts.map +1 -0
- package/dist/__tests__/git.test.js +35 -0
- package/dist/__tests__/git.test.js.map +1 -0
- package/dist/__tests__/init.test.d.ts +2 -0
- package/dist/__tests__/init.test.d.ts.map +1 -0
- package/dist/__tests__/init.test.js +157 -0
- package/dist/__tests__/init.test.js.map +1 -0
- package/dist/__tests__/platform.test.d.ts +2 -0
- package/dist/__tests__/platform.test.d.ts.map +1 -0
- package/dist/__tests__/platform.test.js +31 -0
- package/dist/__tests__/platform.test.js.map +1 -0
- package/dist/__tests__/run.test.d.ts +2 -0
- package/dist/__tests__/run.test.d.ts.map +1 -0
- package/dist/__tests__/run.test.js +77 -0
- package/dist/__tests__/run.test.js.map +1 -0
- package/dist/__tests__/schedule.test.d.ts +2 -0
- package/dist/__tests__/schedule.test.d.ts.map +1 -0
- package/dist/__tests__/schedule.test.js +49 -0
- package/dist/__tests__/schedule.test.js.map +1 -0
- package/dist/ai/base.d.ts +11 -0
- package/dist/ai/base.js.map +1 -0
- package/dist/ai/claude.d.ts +7 -0
- package/dist/ai/claude.js.map +1 -0
- package/dist/ai/cursor.d.ts +7 -0
- package/dist/ai/cursor.js.map +1 -0
- package/dist/ai/index.d.ts +5 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/help.d.ts +2 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/init.d.ts +19 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +7 -3
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/run.d.ts +9 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +3 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/schedule.d.ts +10 -0
- package/dist/commands/schedule.d.ts.map +1 -1
- package/dist/commands/schedule.js +2 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js.map +1 -0
- package/dist/git.d.ts +12 -0
- package/dist/git.js.map +1 -0
- package/dist/logger.d.ts +10 -0
- package/dist/logger.js.map +1 -0
- package/dist/platform.d.ts +9 -0
- package/dist/platform.js.map +1 -0
- package/dist/providers/base.d.ts +8 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/clickup.d.ts +15 -0
- package/dist/providers/clickup.js.map +1 -0
- package/dist/providers/index.d.ts +5 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/types.d.ts +31 -0
- package/dist/types.js.map +1 -0
- package/package.json +19 -5
- package/.claude/settings.local.json +0 -9
- package/.cursor/rules/aidev.mdc +0 -57
- package/CLAUDE.md +0 -32
- package/src/ai/base.ts +0 -11
- package/src/ai/claude.ts +0 -35
- package/src/ai/cursor.ts +0 -35
- package/src/ai/index.ts +0 -15
- package/src/cli.ts +0 -83
- package/src/commands/help.ts +0 -43
- package/src/commands/init.ts +0 -296
- package/src/commands/run.ts +0 -283
- package/src/commands/schedule.ts +0 -179
- package/src/config.ts +0 -59
- package/src/git.ts +0 -109
- package/src/logger.ts +0 -53
- package/src/platform.ts +0 -33
- package/src/providers/base.ts +0 -8
- package/src/providers/clickup.ts +0 -107
- package/src/providers/index.ts +0 -20
- package/src/types.ts +0 -33
- package/tsconfig.json +0 -19
package/src/commands/schedule.ts
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import * as readline from 'node:readline/promises';
|
|
3
|
-
import { stdin as input, stdout as output } from 'node:process';
|
|
4
|
-
import { logger } from '../logger';
|
|
5
|
-
import { isWindows, findBin } from '../platform';
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
|
|
8
|
-
// ─── Preset schedules ────────────────────────────────────────────────────────
|
|
9
|
-
|
|
10
|
-
const PRESETS: Array<{ label: string; cron: string }> = [
|
|
11
|
-
{ label: 'Every 15 minutes', cron: '*/15 * * * *' },
|
|
12
|
-
{ label: 'Every 30 minutes', cron: '*/30 * * * *' },
|
|
13
|
-
{ label: 'Every hour', cron: '0 * * * *' },
|
|
14
|
-
{ label: 'Every 5 hours', cron: '0 */5 * * *' },
|
|
15
|
-
{ label: 'Every day at 8am', cron: '0 8 * * *' },
|
|
16
|
-
];
|
|
17
|
-
|
|
18
|
-
async function pickCron(): Promise<string> {
|
|
19
|
-
console.log('\n Select a schedule:');
|
|
20
|
-
PRESETS.forEach((p, i) =>
|
|
21
|
-
console.log(` ${chalk.cyan(String(i + 1))}. ${p.label} ${chalk.dim(p.cron)}`)
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
const rl = readline.createInterface({ input, output });
|
|
25
|
-
try {
|
|
26
|
-
while (true) {
|
|
27
|
-
const raw = await rl.question(`\n Choice ${chalk.dim('[1]')}: `);
|
|
28
|
-
const val = raw.trim() || '1';
|
|
29
|
-
const idx = parseInt(val, 10);
|
|
30
|
-
if (idx >= 1 && idx <= PRESETS.length) return PRESETS[idx - 1].cron;
|
|
31
|
-
console.log(chalk.yellow(` Enter a number between 1 and ${PRESETS.length}.`));
|
|
32
|
-
}
|
|
33
|
-
} finally {
|
|
34
|
-
rl.close();
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// ─── Shared helpers ───────────────────────────────────────────────────────────
|
|
39
|
-
|
|
40
|
-
function getAidevBin(): string {
|
|
41
|
-
return findBin('aidev') ?? 'aidev';
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ─── Unix (crontab) ───────────────────────────────────────────────────────────
|
|
45
|
-
|
|
46
|
-
const UNIX_MARKER_PREFIX = '# aidev-cwd:';
|
|
47
|
-
|
|
48
|
-
function getCrontab(): string {
|
|
49
|
-
const result = spawnSync('crontab', ['-l'], { encoding: 'utf8' });
|
|
50
|
-
return result.status === 0 ? result.stdout || '' : '';
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function setCrontab(content: string): boolean {
|
|
54
|
-
const result = spawnSync('crontab', ['-'], { input: content, encoding: 'utf8' });
|
|
55
|
-
return result.status === 0;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function scheduleSetUnix(cronExpr: string): void {
|
|
59
|
-
const cwd = process.cwd();
|
|
60
|
-
const marker = `${UNIX_MARKER_PREFIX}${cwd}`;
|
|
61
|
-
const aidevBin = getAidevBin();
|
|
62
|
-
const newLine = `${cronExpr} cd ${cwd} && ${aidevBin} run ${marker}`;
|
|
63
|
-
|
|
64
|
-
const lines = getCrontab().split('\n').filter((l) => !l.includes(marker));
|
|
65
|
-
lines.push(newLine);
|
|
66
|
-
const updated = lines.join('\n').replace(/\n+$/, '') + '\n';
|
|
67
|
-
|
|
68
|
-
if (setCrontab(updated)) {
|
|
69
|
-
logger.success(`Cron schedule set: ${cronExpr}`);
|
|
70
|
-
logger.info(`Entry: ${newLine}`);
|
|
71
|
-
} else {
|
|
72
|
-
logger.error('Failed to update crontab');
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function scheduleGetUnix(): void {
|
|
78
|
-
const cwd = process.cwd();
|
|
79
|
-
const marker = `${UNIX_MARKER_PREFIX}${cwd}`;
|
|
80
|
-
const entry = getCrontab().split('\n').find((l) => l.includes(marker));
|
|
81
|
-
|
|
82
|
-
if (entry) {
|
|
83
|
-
logger.info(`Current schedule for ${cwd}:`);
|
|
84
|
-
console.log(entry);
|
|
85
|
-
} else {
|
|
86
|
-
logger.warn(`No schedule found for ${cwd}`);
|
|
87
|
-
logger.info('Use "aidev schedule set" to configure one.');
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ─── Windows (schtasks) ───────────────────────────────────────────────────────
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Converts a cron expression to schtasks /sc + /mo + /st arguments.
|
|
95
|
-
* Supports the subset used by the preset list.
|
|
96
|
-
*/
|
|
97
|
-
function cronToSchtasksArgs(cron: string): string[] | null {
|
|
98
|
-
// */N * * * * → every N minutes
|
|
99
|
-
const everyMin = cron.match(/^\*\/(\d+) \* \* \* \*$/);
|
|
100
|
-
if (everyMin) return ['/sc', 'MINUTE', '/mo', everyMin[1]];
|
|
101
|
-
|
|
102
|
-
// 0 * * * * → every hour
|
|
103
|
-
if (cron === '0 * * * *') return ['/sc', 'HOURLY', '/mo', '1'];
|
|
104
|
-
|
|
105
|
-
// 0 */N * * * → every N hours
|
|
106
|
-
const everyHour = cron.match(/^0 \*\/(\d+) \* \* \*$/);
|
|
107
|
-
if (everyHour) return ['/sc', 'HOURLY', '/mo', everyHour[1]];
|
|
108
|
-
|
|
109
|
-
// 0 H * * * → daily at H:00
|
|
110
|
-
const daily = cron.match(/^0 (\d+) \* \* \*$/);
|
|
111
|
-
if (daily) return ['/sc', 'DAILY', '/st', `${daily[1].padStart(2, '0')}:00`];
|
|
112
|
-
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Stable task name derived from cwd — safe for Task Scheduler. */
|
|
117
|
-
function windowsTaskName(cwd: string): string {
|
|
118
|
-
const sanitized = cwd.replace(/[:\\\/]+/g, '-').replace(/^-+|-+$/g, '');
|
|
119
|
-
return `aidev\\${sanitized}`;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function scheduleSetWindows(cronExpr: string): void {
|
|
123
|
-
const cwd = process.cwd();
|
|
124
|
-
const schtasksArgs = cronToSchtasksArgs(cronExpr);
|
|
125
|
-
if (!schtasksArgs) {
|
|
126
|
-
logger.error(
|
|
127
|
-
`Cron expression "${cronExpr}" cannot be mapped to Windows Task Scheduler.\n` +
|
|
128
|
-
' Use "aidev schedule set" (no argument) to choose a supported preset.'
|
|
129
|
-
);
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const taskName = windowsTaskName(cwd);
|
|
134
|
-
const aidevBin = getAidevBin();
|
|
135
|
-
// cmd /c: run command and exit; /d: change drive+dir
|
|
136
|
-
const command = `cmd /c cd /d "${cwd}" && "${aidevBin}" run`;
|
|
137
|
-
|
|
138
|
-
const result = spawnSync(
|
|
139
|
-
'schtasks',
|
|
140
|
-
['/create', '/f', '/tn', taskName, '/tr', command, ...schtasksArgs],
|
|
141
|
-
{ encoding: 'utf8' }
|
|
142
|
-
);
|
|
143
|
-
|
|
144
|
-
if (result.status === 0) {
|
|
145
|
-
logger.success(`Task Scheduler entry created: ${taskName}`);
|
|
146
|
-
logger.info(`Schedule: ${cronExpr}`);
|
|
147
|
-
} else {
|
|
148
|
-
logger.error(`Failed to create Task Scheduler entry:\n${result.stderr}`);
|
|
149
|
-
process.exit(1);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function scheduleGetWindows(): void {
|
|
154
|
-
const cwd = process.cwd();
|
|
155
|
-
const taskName = windowsTaskName(cwd);
|
|
156
|
-
|
|
157
|
-
const result = spawnSync('schtasks', ['/query', '/tn', taskName, '/fo', 'LIST'], {
|
|
158
|
-
encoding: 'utf8',
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
if (result.status === 0) {
|
|
162
|
-
logger.info(`Task Scheduler entry for ${cwd}:`);
|
|
163
|
-
console.log(result.stdout.trim());
|
|
164
|
-
} else {
|
|
165
|
-
logger.warn(`No Task Scheduler entry found for ${cwd}`);
|
|
166
|
-
logger.info('Use "aidev schedule set" to configure one.');
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
171
|
-
|
|
172
|
-
export async function scheduleSetCommand(cronExpr?: string): Promise<void> {
|
|
173
|
-
if (!cronExpr) cronExpr = await pickCron();
|
|
174
|
-
isWindows ? scheduleSetWindows(cronExpr) : scheduleSetUnix(cronExpr);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export async function scheduleGetCommand(): Promise<void> {
|
|
178
|
-
isWindows ? scheduleGetWindows() : scheduleGetUnix();
|
|
179
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import * as dotenv from 'dotenv';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
|
-
import { Config, AgentName } from './types';
|
|
5
|
-
import { detectRemote } from './git';
|
|
6
|
-
|
|
7
|
-
export function loadConfig(customEnvPath?: string): Config {
|
|
8
|
-
const envPath = customEnvPath
|
|
9
|
-
? path.resolve(customEnvPath)
|
|
10
|
-
: path.join(process.cwd(), '.env.aidev');
|
|
11
|
-
|
|
12
|
-
if (customEnvPath && !fs.existsSync(envPath)) {
|
|
13
|
-
throw new Error(`Env file not found: ${envPath}`);
|
|
14
|
-
}
|
|
15
|
-
if (fs.existsSync(envPath)) {
|
|
16
|
-
dotenv.config({ path: envPath });
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const required = ['CLICKUP_API_KEY', 'CLICKUP_TEAM_ID', 'CLICKUP_TAG'];
|
|
20
|
-
for (const key of required) {
|
|
21
|
-
if (!process.env[key]) {
|
|
22
|
-
throw new Error(`Missing required config: ${key}. Run 'aidev init' to create .env.aidev`);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const validAgents: AgentName[] = ['claude', 'cursor'];
|
|
27
|
-
const agentsRaw = process.env.AGENTS || 'claude,cursor';
|
|
28
|
-
const agents = agentsRaw
|
|
29
|
-
.split(',')
|
|
30
|
-
.map((s) => s.trim().toLowerCase())
|
|
31
|
-
.filter(Boolean) as AgentName[];
|
|
32
|
-
const invalid = agents.filter((a) => !validAgents.includes(a));
|
|
33
|
-
if (invalid.length) {
|
|
34
|
-
throw new Error(`Invalid agent(s): ${invalid.join(', ')}. Valid: ${validAgents.join(', ')}`);
|
|
35
|
-
}
|
|
36
|
-
if (agents.length === 0) {
|
|
37
|
-
throw new Error(`AGENTS must contain at least one agent. Valid: ${validAgents.join(', ')}`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const devNotesMode = (process.env.DEV_NOTES_MODE || 'smart') as Config['devNotesMode'];
|
|
41
|
-
if (!['smart', 'always'].includes(devNotesMode)) {
|
|
42
|
-
throw new Error(`Invalid DEV_NOTES_MODE: ${devNotesMode}. Must be smart or always`);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return {
|
|
46
|
-
provider: process.env.PROVIDER || 'clickup',
|
|
47
|
-
clickupApiKey: process.env.CLICKUP_API_KEY!,
|
|
48
|
-
clickupTeamId: process.env.CLICKUP_TEAM_ID!,
|
|
49
|
-
clickupTag: process.env.CLICKUP_TAG!,
|
|
50
|
-
clickupPendingStatus: process.env.CLICKUP_PENDING_STATUS || 'pending',
|
|
51
|
-
clickupInReviewStatus: process.env.CLICKUP_IN_REVIEW_STATUS || 'review',
|
|
52
|
-
assigneeTag: process.env.ASSIGNEE_TAG || '',
|
|
53
|
-
gitRemote: process.env.GIT_REMOTE || detectRemote() || 'origin',
|
|
54
|
-
githubBaseBranch: process.env.GITHUB_BASE_BRANCH || 'main',
|
|
55
|
-
githubRepo: process.env.GITHUB_REPO || '',
|
|
56
|
-
agents,
|
|
57
|
-
devNotesMode,
|
|
58
|
-
};
|
|
59
|
-
}
|
package/src/git.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { logger } from './logger';
|
|
3
|
-
|
|
4
|
-
function git(args: string[], cwd?: string): { stdout: string; stderr: string; status: number } {
|
|
5
|
-
const result = spawnSync('git', args, {
|
|
6
|
-
cwd: cwd || process.cwd(),
|
|
7
|
-
encoding: 'utf8',
|
|
8
|
-
});
|
|
9
|
-
return {
|
|
10
|
-
stdout: result.stdout || '',
|
|
11
|
-
stderr: result.stderr || '',
|
|
12
|
-
status: result.status ?? 1,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function remoteBranchExists(remote: string, branch: string): boolean {
|
|
17
|
-
const result = git(['ls-remote', '--heads', remote, branch]);
|
|
18
|
-
return result.status === 0 && result.stdout.trim().length > 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function fetchAndCheckout(remote: string, baseBranch: string): boolean {
|
|
22
|
-
logger.debug(`git fetch ${remote}`);
|
|
23
|
-
const fetch = git(['fetch', remote]);
|
|
24
|
-
if (fetch.status !== 0) {
|
|
25
|
-
logger.error(`git fetch failed: ${fetch.stderr}`);
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
logger.debug(`git checkout ${baseBranch}`);
|
|
30
|
-
const checkout = git(['checkout', baseBranch]);
|
|
31
|
-
if (checkout.status !== 0) {
|
|
32
|
-
logger.error(`git checkout ${baseBranch} failed: ${checkout.stderr}`);
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
logger.debug(`git pull ${remote} ${baseBranch}`);
|
|
37
|
-
const pull = git(['pull', remote, baseBranch]);
|
|
38
|
-
if (pull.status !== 0) {
|
|
39
|
-
logger.error(`git pull failed: ${pull.stderr}`);
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export function createBranch(branch: string): boolean {
|
|
47
|
-
logger.debug(`git checkout -b ${branch}`);
|
|
48
|
-
const result = git(['checkout', '-b', branch]);
|
|
49
|
-
if (result.status !== 0) {
|
|
50
|
-
logger.error(`git checkout -b ${branch} failed: ${result.stderr}`);
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function hasChanges(): boolean {
|
|
57
|
-
const result = git(['status', '--porcelain']);
|
|
58
|
-
return result.status === 0 && result.stdout.trim().length > 0;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function addAll(): boolean {
|
|
62
|
-
const result = git(['add', '-A']);
|
|
63
|
-
return result.status === 0;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function commit(message: string): boolean {
|
|
67
|
-
logger.debug(`git commit -m "${message}"`);
|
|
68
|
-
const result = git(['commit', '-m', message]);
|
|
69
|
-
if (result.status !== 0) {
|
|
70
|
-
logger.error(`git commit failed: ${result.stderr}`);
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function push(remote: string, branch: string): boolean {
|
|
77
|
-
logger.debug(`git push ${remote} ${branch}`);
|
|
78
|
-
const result = git(['push', remote, branch]);
|
|
79
|
-
if (result.status !== 0) {
|
|
80
|
-
logger.error(`git push failed: ${result.stderr}`);
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export function deleteBranch(branch: string): void {
|
|
87
|
-
git(['checkout', '-']);
|
|
88
|
-
git(['branch', '-D', branch]);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Returns the name of the first usable remote (prefers origin). */
|
|
92
|
-
export function detectRemote(): string | null {
|
|
93
|
-
// Verify origin exists first
|
|
94
|
-
const originCheck = git(['remote', 'get-url', 'origin']);
|
|
95
|
-
if (originCheck.status === 0) return 'origin';
|
|
96
|
-
|
|
97
|
-
// Fall back to first listed remote
|
|
98
|
-
const list = git(['remote']);
|
|
99
|
-
const first = list.stdout.trim().split('\n')[0]?.trim();
|
|
100
|
-
return first || null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export function slugify(text: string): string {
|
|
104
|
-
return text
|
|
105
|
-
.toLowerCase()
|
|
106
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
107
|
-
.replace(/^-|-$/g, '')
|
|
108
|
-
.slice(0, 50);
|
|
109
|
-
}
|
package/src/logger.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
|
|
5
|
-
const LOG_FILE = path.join(process.cwd(), 'aidev.log');
|
|
6
|
-
|
|
7
|
-
// Strip ANSI escape codes for clean file output
|
|
8
|
-
function strip(s: string): string {
|
|
9
|
-
return s.replace(/\x1B\[[0-9;]*m/g, '');
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function timestamp(): string {
|
|
13
|
-
return new Date().toISOString();
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function writeLog(level: string, msg: string): void {
|
|
17
|
-
const line = `${timestamp()} [${level}] ${strip(msg)}\n`;
|
|
18
|
-
fs.appendFileSync(LOG_FILE, line, 'utf8');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function logRunStart(): void {
|
|
22
|
-
const sep = '─'.repeat(60);
|
|
23
|
-
fs.appendFileSync(LOG_FILE, `\n${sep}\n${timestamp()} [run] started\n${sep}\n`, 'utf8');
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const logger = {
|
|
27
|
-
info: (msg: string) => {
|
|
28
|
-
console.log(chalk.blue('[aidev]'), msg);
|
|
29
|
-
writeLog('info', msg);
|
|
30
|
-
},
|
|
31
|
-
success: (msg: string) => {
|
|
32
|
-
console.log(chalk.green('[aidev]'), msg);
|
|
33
|
-
writeLog('success', msg);
|
|
34
|
-
},
|
|
35
|
-
warn: (msg: string) => {
|
|
36
|
-
console.log(chalk.yellow('[aidev]'), msg);
|
|
37
|
-
writeLog('warn', msg);
|
|
38
|
-
},
|
|
39
|
-
error: (msg: string) => {
|
|
40
|
-
console.error(chalk.red('[aidev]'), msg);
|
|
41
|
-
writeLog('error', msg);
|
|
42
|
-
},
|
|
43
|
-
task: (msg: string) => {
|
|
44
|
-
console.log(chalk.cyan('[task]'), msg);
|
|
45
|
-
writeLog('task', msg);
|
|
46
|
-
},
|
|
47
|
-
debug: (msg: string) => {
|
|
48
|
-
if (process.env.DEBUG) {
|
|
49
|
-
console.log(chalk.gray('[debug]'), msg);
|
|
50
|
-
writeLog('debug', msg);
|
|
51
|
-
}
|
|
52
|
-
},
|
|
53
|
-
};
|
package/src/platform.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import * as fs from 'node:fs';
|
|
2
|
-
import * as path from 'node:path';
|
|
3
|
-
|
|
4
|
-
export const isWindows = process.platform === 'win32';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Finds the full path of a binary by searching PATH (and PATHEXT on Windows).
|
|
8
|
-
* Returns the resolved path, or null if not found.
|
|
9
|
-
* Uses only Node.js fs — no `which` / `where` subprocess.
|
|
10
|
-
*/
|
|
11
|
-
export function findBin(name: string): string | null {
|
|
12
|
-
const dirs = (process.env.PATH ?? '').split(path.delimiter).filter(Boolean);
|
|
13
|
-
const exts = isWindows
|
|
14
|
-
? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM').split(';').map((e) => e.toLowerCase())
|
|
15
|
-
: [''];
|
|
16
|
-
|
|
17
|
-
for (const dir of dirs) {
|
|
18
|
-
for (const ext of exts) {
|
|
19
|
-
const full = path.join(dir, name + ext);
|
|
20
|
-
try {
|
|
21
|
-
fs.accessSync(full, fs.constants.F_OK);
|
|
22
|
-
return full;
|
|
23
|
-
} catch {
|
|
24
|
-
// not here — keep searching
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function commandExists(name: string): boolean {
|
|
32
|
-
return findBin(name) !== null;
|
|
33
|
-
}
|
package/src/providers/base.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Task, Comment } from '../types';
|
|
2
|
-
|
|
3
|
-
export interface TaskProvider {
|
|
4
|
-
fetchTasks(): Promise<Task[]>;
|
|
5
|
-
postComment(taskId: string, text: string): Promise<void>;
|
|
6
|
-
getComments(taskId: string): Promise<Comment[]>;
|
|
7
|
-
updateStatus(taskId: string, status: string): Promise<void>;
|
|
8
|
-
}
|
package/src/providers/clickup.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import { Task, Comment, Config } from '../types';
|
|
2
|
-
import { TaskProvider } from './base';
|
|
3
|
-
import { logger } from '../logger';
|
|
4
|
-
|
|
5
|
-
export class ClickUpProvider implements TaskProvider {
|
|
6
|
-
private apiKey: string;
|
|
7
|
-
private teamId: string;
|
|
8
|
-
private tag: string;
|
|
9
|
-
private assigneeTag: string;
|
|
10
|
-
|
|
11
|
-
constructor(config: Config) {
|
|
12
|
-
this.apiKey = config.clickupApiKey;
|
|
13
|
-
this.teamId = config.clickupTeamId;
|
|
14
|
-
this.tag = config.clickupTag;
|
|
15
|
-
this.assigneeTag = config.assigneeTag;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
private async request<T>(path: string, options: RequestInit = {}): Promise<T> {
|
|
19
|
-
const url = `https://api.clickup.com/api/v2${path}`;
|
|
20
|
-
const res = await fetch(url, {
|
|
21
|
-
...options,
|
|
22
|
-
headers: {
|
|
23
|
-
Authorization: this.apiKey,
|
|
24
|
-
'Content-Type': 'application/json',
|
|
25
|
-
...(options.headers || {}),
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
if (!res.ok) {
|
|
30
|
-
const body = await res.text();
|
|
31
|
-
throw new Error(`ClickUp API error ${res.status}: ${body}`);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return res.json() as Promise<T>;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async fetchTasks(): Promise<Task[]> {
|
|
38
|
-
logger.debug(`Fetching tasks with tag "${this.tag}" from team ${this.teamId}`);
|
|
39
|
-
|
|
40
|
-
interface RawTask {
|
|
41
|
-
id: string;
|
|
42
|
-
name: string;
|
|
43
|
-
description?: string;
|
|
44
|
-
status: { status: string };
|
|
45
|
-
url: string;
|
|
46
|
-
tags: Array<{ name: string }>;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface TasksResponse {
|
|
50
|
-
tasks: RawTask[];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const data = await this.request<TasksResponse>(
|
|
54
|
-
`/team/${this.teamId}/task?tags[]=${encodeURIComponent(this.tag)}&subtasks=true&include_closed=false`
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
return data.tasks.map((t) => ({
|
|
58
|
-
id: t.id,
|
|
59
|
-
name: t.name,
|
|
60
|
-
description: t.description || '',
|
|
61
|
-
status: t.status.status.toLowerCase(),
|
|
62
|
-
url: t.url,
|
|
63
|
-
tags: t.tags.map((tag) => tag.name),
|
|
64
|
-
}));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async postComment(taskId: string, text: string): Promise<void> {
|
|
68
|
-
logger.debug(`Posting comment to task ${taskId}`);
|
|
69
|
-
await this.request(`/task/${taskId}/comment`, {
|
|
70
|
-
method: 'POST',
|
|
71
|
-
body: JSON.stringify({ comment_text: text }),
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
async getComments(taskId: string): Promise<Comment[]> {
|
|
76
|
-
logger.debug(`Fetching comments for task ${taskId}`);
|
|
77
|
-
|
|
78
|
-
interface RawComment {
|
|
79
|
-
id: string;
|
|
80
|
-
comment_text: string;
|
|
81
|
-
user: { username: string; id: number };
|
|
82
|
-
date: string;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
interface CommentsResponse {
|
|
86
|
-
comments: RawComment[];
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const data = await this.request<CommentsResponse>(`/task/${taskId}/comment`);
|
|
90
|
-
|
|
91
|
-
return data.comments.map((c) => ({
|
|
92
|
-
id: c.id,
|
|
93
|
-
text: c.comment_text,
|
|
94
|
-
author: c.user.username,
|
|
95
|
-
authorId: String(c.user.id),
|
|
96
|
-
date: parseInt(c.date, 10),
|
|
97
|
-
}));
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async updateStatus(taskId: string, status: string): Promise<void> {
|
|
101
|
-
logger.debug(`Updating task ${taskId} status to "${status}"`);
|
|
102
|
-
await this.request(`/task/${taskId}`, {
|
|
103
|
-
method: 'PUT',
|
|
104
|
-
body: JSON.stringify({ status }),
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
package/src/providers/index.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Config } from '../types';
|
|
2
|
-
import { TaskProvider } from './base';
|
|
3
|
-
import { ClickUpProvider } from './clickup';
|
|
4
|
-
|
|
5
|
-
export function createProvider(config: Config): TaskProvider {
|
|
6
|
-
switch (config.provider.toLowerCase()) {
|
|
7
|
-
case 'clickup':
|
|
8
|
-
return new ClickUpProvider(config);
|
|
9
|
-
case 'jira':
|
|
10
|
-
throw new Error('Jira provider is not yet implemented. Contributions welcome!');
|
|
11
|
-
case 'notion':
|
|
12
|
-
throw new Error('Notion provider is not yet implemented. Contributions welcome!');
|
|
13
|
-
case 'trello':
|
|
14
|
-
throw new Error('Trello provider is not yet implemented. Contributions welcome!');
|
|
15
|
-
default:
|
|
16
|
-
throw new Error(`Unknown provider: ${config.provider}`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export { TaskProvider };
|
package/src/types.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
export interface Task {
|
|
2
|
-
id: string;
|
|
3
|
-
name: string;
|
|
4
|
-
description: string;
|
|
5
|
-
status: string;
|
|
6
|
-
url: string;
|
|
7
|
-
tags: string[];
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export interface Comment {
|
|
11
|
-
id: string;
|
|
12
|
-
text: string;
|
|
13
|
-
author: string;
|
|
14
|
-
authorId: string;
|
|
15
|
-
date: number; // epoch ms
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type AgentName = 'claude' | 'cursor';
|
|
19
|
-
|
|
20
|
-
export interface Config {
|
|
21
|
-
provider: string;
|
|
22
|
-
clickupApiKey: string;
|
|
23
|
-
clickupTeamId: string;
|
|
24
|
-
clickupTag: string;
|
|
25
|
-
clickupPendingStatus: string;
|
|
26
|
-
clickupInReviewStatus: string;
|
|
27
|
-
assigneeTag: string;
|
|
28
|
-
gitRemote: string;
|
|
29
|
-
githubBaseBranch: string;
|
|
30
|
-
githubRepo: string;
|
|
31
|
-
agents: AgentName[];
|
|
32
|
-
devNotesMode: 'smart' | 'always';
|
|
33
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2020",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"lib": ["ES2020"],
|
|
6
|
-
"outDir": "./dist",
|
|
7
|
-
"rootDir": "./src",
|
|
8
|
-
"strict": true,
|
|
9
|
-
"esModuleInterop": true,
|
|
10
|
-
"skipLibCheck": true,
|
|
11
|
-
"forceConsistentCasingInFileNames": true,
|
|
12
|
-
"resolveJsonModule": true,
|
|
13
|
-
"declaration": true,
|
|
14
|
-
"declarationMap": true,
|
|
15
|
-
"sourceMap": true
|
|
16
|
-
},
|
|
17
|
-
"include": ["src/**/*"],
|
|
18
|
-
"exclude": ["node_modules", "dist"]
|
|
19
|
-
}
|