@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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli.d.mts +1 -0
  3. package/dist/cli.mjs +6 -0
  4. package/dist/context-BLjTHa41.mjs +1529 -0
  5. package/dist/index.d.mts +184 -0
  6. package/dist/index.mjs +2 -0
  7. package/package.json +53 -0
  8. package/src/app.exit-code.test.ts +91 -0
  9. package/src/app.ts +49 -0
  10. package/src/cli.ts +5 -0
  11. package/src/commands/image-gen/command.ts +77 -0
  12. package/src/commands/image-gen/impl.ts +268 -0
  13. package/src/commands/implement/command.ts +50 -0
  14. package/src/commands/implement/impl.ts +99 -0
  15. package/src/commands/plan/command.ts +56 -0
  16. package/src/commands/plan/impl.ts +172 -0
  17. package/src/commands/plan/plan.test.ts +19 -0
  18. package/src/commands/quota/command.ts +30 -0
  19. package/src/commands/quota/impl.ts +109 -0
  20. package/src/commands/review/command.ts +58 -0
  21. package/src/commands/review/impl.ts +211 -0
  22. package/src/commands/review/review.test.ts +54 -0
  23. package/src/commands/runs/command.ts +53 -0
  24. package/src/commands/runs/impl.ts +171 -0
  25. package/src/commands/subagent/command.ts +62 -0
  26. package/src/commands/subagent/impl.ts +87 -0
  27. package/src/context.ts +10 -0
  28. package/src/delegate.test.ts +180 -0
  29. package/src/delegate.ts +46 -0
  30. package/src/driver.ts +56 -0
  31. package/src/drivers.ts +44 -0
  32. package/src/exitCode.test.ts +44 -0
  33. package/src/exitCode.ts +24 -0
  34. package/src/flagMapping.test.ts +99 -0
  35. package/src/index.ts +37 -0
  36. package/src/models.test.ts +107 -0
  37. package/src/models.ts +159 -0
  38. package/src/parsers.ts +24 -0
  39. package/src/quotaPreflight.test.ts +178 -0
  40. package/src/quotaPreflight.ts +103 -0
  41. 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
+ }