@aibridge/cli 0.0.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/LICENSE +21 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +6 -0
- package/dist/context-BLjTHa41.mjs +1529 -0
- package/dist/index.d.mts +184 -0
- package/dist/index.mjs +2 -0
- package/package.json +53 -0
- package/src/app.exit-code.test.ts +91 -0
- package/src/app.ts +49 -0
- package/src/cli.ts +5 -0
- package/src/commands/image-gen/command.ts +77 -0
- package/src/commands/image-gen/impl.ts +268 -0
- package/src/commands/implement/command.ts +50 -0
- package/src/commands/implement/impl.ts +99 -0
- package/src/commands/plan/command.ts +56 -0
- package/src/commands/plan/impl.ts +172 -0
- package/src/commands/plan/plan.test.ts +19 -0
- package/src/commands/quota/command.ts +30 -0
- package/src/commands/quota/impl.ts +109 -0
- package/src/commands/review/command.ts +58 -0
- package/src/commands/review/impl.ts +211 -0
- package/src/commands/review/review.test.ts +54 -0
- package/src/commands/runs/command.ts +53 -0
- package/src/commands/runs/impl.ts +171 -0
- package/src/commands/subagent/command.ts +62 -0
- package/src/commands/subagent/impl.ts +87 -0
- package/src/context.ts +10 -0
- package/src/delegate.test.ts +180 -0
- package/src/delegate.ts +46 -0
- package/src/driver.ts +56 -0
- package/src/drivers.ts +44 -0
- package/src/exitCode.test.ts +44 -0
- package/src/exitCode.ts +24 -0
- package/src/flagMapping.test.ts +99 -0
- package/src/index.ts +37 -0
- package/src/models.test.ts +107 -0
- package/src/models.ts +159 -0
- package/src/parsers.ts +24 -0
- package/src/quotaPreflight.test.ts +178 -0
- package/src/quotaPreflight.ts +103 -0
- package/src/runlog.ts +195 -0
package/src/runlog.ts
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
appendFileSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
rmSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from 'node:fs';
|
|
11
|
+
import { homedir } from 'node:os';
|
|
12
|
+
import { join } from 'node:path';
|
|
13
|
+
|
|
14
|
+
export interface RunMeta {
|
|
15
|
+
readonly id: string;
|
|
16
|
+
readonly command: string;
|
|
17
|
+
readonly detail: string;
|
|
18
|
+
pid: number | null;
|
|
19
|
+
readonly startedAt: string;
|
|
20
|
+
endedAt: string | null;
|
|
21
|
+
status: 'running' | 'done' | 'error' | 'timeout' | 'stale';
|
|
22
|
+
exitCode: number | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RunLog {
|
|
26
|
+
readonly id: string;
|
|
27
|
+
readonly dir: string;
|
|
28
|
+
setPid(pid: number): void;
|
|
29
|
+
stdout(chunk: string): void;
|
|
30
|
+
stderr(chunk: string): void;
|
|
31
|
+
finish(status: 'done' | 'error' | 'timeout', exitCode: number | null): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function getTimestamp(): string {
|
|
35
|
+
const d = new Date();
|
|
36
|
+
const yyyy = d.getFullYear();
|
|
37
|
+
const MM = String(d.getMonth() + 1).padStart(2, '0');
|
|
38
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
39
|
+
const HH = String(d.getHours()).padStart(2, '0');
|
|
40
|
+
const mm = String(d.getMinutes()).padStart(2, '0');
|
|
41
|
+
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
42
|
+
return `${yyyy}${MM}${dd}-${HH}${mm}${ss}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function pruneOldRuns(runsDir: string): void {
|
|
46
|
+
try {
|
|
47
|
+
if (!existsSync(runsDir)) return;
|
|
48
|
+
const entries = readdirSync(runsDir, { withFileTypes: true });
|
|
49
|
+
const dirs = entries
|
|
50
|
+
.filter(e => e.isDirectory())
|
|
51
|
+
.map(e => e.name)
|
|
52
|
+
.sort();
|
|
53
|
+
|
|
54
|
+
if (dirs.length > 50) {
|
|
55
|
+
const toDelete = dirs.slice(0, dirs.length - 50);
|
|
56
|
+
for (const d of toDelete) {
|
|
57
|
+
try {
|
|
58
|
+
rmSync(join(runsDir, d), { recursive: true, force: true });
|
|
59
|
+
} catch {
|
|
60
|
+
// ignore
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
} catch {
|
|
65
|
+
// ignore
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function startRun(command: string, detail: string): RunLog {
|
|
70
|
+
const runsDir = join(homedir(), '.aibridge', 'runs');
|
|
71
|
+
try {
|
|
72
|
+
mkdirSync(runsDir, { recursive: true });
|
|
73
|
+
pruneOldRuns(runsDir);
|
|
74
|
+
|
|
75
|
+
const id = `${getTimestamp()}-${command}-${randomBytes(2).toString('hex')}`;
|
|
76
|
+
const dir = join(runsDir, id);
|
|
77
|
+
mkdirSync(dir, { recursive: true });
|
|
78
|
+
|
|
79
|
+
const meta: RunMeta = {
|
|
80
|
+
id,
|
|
81
|
+
command,
|
|
82
|
+
detail,
|
|
83
|
+
pid: null,
|
|
84
|
+
startedAt: new Date().toISOString(),
|
|
85
|
+
endedAt: null,
|
|
86
|
+
status: 'running',
|
|
87
|
+
exitCode: null,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const metaJsonPath = join(dir, 'meta.json');
|
|
91
|
+
const stdoutLogPath = join(dir, 'stdout.log');
|
|
92
|
+
const stderrLogPath = join(dir, 'stderr.log');
|
|
93
|
+
|
|
94
|
+
writeFileSync(metaJsonPath, JSON.stringify(meta, null, 2), 'utf8');
|
|
95
|
+
writeFileSync(stdoutLogPath, '', 'utf8');
|
|
96
|
+
writeFileSync(stderrLogPath, '', 'utf8');
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
id,
|
|
100
|
+
dir,
|
|
101
|
+
setPid(pid: number) {
|
|
102
|
+
try {
|
|
103
|
+
meta.pid = pid;
|
|
104
|
+
writeFileSync(metaJsonPath, JSON.stringify(meta, null, 2), 'utf8');
|
|
105
|
+
} catch {
|
|
106
|
+
// ignore
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
stdout(chunk: string) {
|
|
110
|
+
try {
|
|
111
|
+
appendFileSync(stdoutLogPath, chunk, 'utf8');
|
|
112
|
+
} catch {
|
|
113
|
+
// ignore
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
stderr(chunk: string) {
|
|
117
|
+
try {
|
|
118
|
+
appendFileSync(stderrLogPath, chunk, 'utf8');
|
|
119
|
+
} catch {
|
|
120
|
+
// ignore
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
finish(status: 'done' | 'error' | 'timeout', exitCode: number | null) {
|
|
124
|
+
try {
|
|
125
|
+
meta.status = status;
|
|
126
|
+
meta.exitCode = exitCode;
|
|
127
|
+
meta.endedAt = new Date().toISOString();
|
|
128
|
+
writeFileSync(metaJsonPath, JSON.stringify(meta, null, 2), 'utf8');
|
|
129
|
+
} catch {
|
|
130
|
+
// ignore
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
} catch {
|
|
135
|
+
return {
|
|
136
|
+
id: '',
|
|
137
|
+
dir: '',
|
|
138
|
+
setPid() {},
|
|
139
|
+
stdout() {},
|
|
140
|
+
stderr() {},
|
|
141
|
+
finish() {},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function listRuns(): RunMeta[] {
|
|
147
|
+
const runsDir = join(homedir(), '.aibridge', 'runs');
|
|
148
|
+
if (!existsSync(runsDir)) return [];
|
|
149
|
+
try {
|
|
150
|
+
const entries = readdirSync(runsDir, { withFileTypes: true });
|
|
151
|
+
const runs: RunMeta[] = [];
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
if (entry.isDirectory()) {
|
|
154
|
+
try {
|
|
155
|
+
const metaPath = join(runsDir, entry.name, 'meta.json');
|
|
156
|
+
if (existsSync(metaPath)) {
|
|
157
|
+
const content = readFileSync(metaPath, 'utf8');
|
|
158
|
+
const parsed = JSON.parse(content) as RunMeta;
|
|
159
|
+
if (
|
|
160
|
+
parsed &&
|
|
161
|
+
typeof parsed === 'object' &&
|
|
162
|
+
parsed.id &&
|
|
163
|
+
parsed.startedAt &&
|
|
164
|
+
typeof parsed.detail === 'string'
|
|
165
|
+
) {
|
|
166
|
+
runs.push(parsed);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} catch {
|
|
170
|
+
// skip
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return runs.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
175
|
+
} catch {
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function readRunLogs(id: string): { meta: RunMeta; stdout: string; stderr: string } | null {
|
|
181
|
+
const runsDir = join(homedir(), '.aibridge', 'runs');
|
|
182
|
+
const dir = join(runsDir, id);
|
|
183
|
+
const metaPath = join(dir, 'meta.json');
|
|
184
|
+
const stdoutPath = join(dir, 'stdout.log');
|
|
185
|
+
const stderrPath = join(dir, 'stderr.log');
|
|
186
|
+
if (!existsSync(metaPath)) return null;
|
|
187
|
+
try {
|
|
188
|
+
const meta = JSON.parse(readFileSync(metaPath, 'utf8')) as RunMeta;
|
|
189
|
+
const stdout = existsSync(stdoutPath) ? readFileSync(stdoutPath, 'utf8') : '';
|
|
190
|
+
const stderr = existsSync(stderrPath) ? readFileSync(stderrPath, 'utf8') : '';
|
|
191
|
+
return { meta, stdout, stderr };
|
|
192
|
+
} catch {
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
}
|