@ariacode/cli 0.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/LICENSE +21 -0
- package/README.md +404 -0
- package/dist/actions.d.ts +271 -0
- package/dist/actions.js +1809 -0
- package/dist/actions.js.map +1 -0
- package/dist/agent.d.ts +26 -0
- package/dist/agent.js +182 -0
- package/dist/agent.js.map +1 -0
- package/dist/app.d.ts +14 -0
- package/dist/app.js +83 -0
- package/dist/app.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.js +157 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +170 -0
- package/dist/config.js +291 -0
- package/dist/config.js.map +1 -0
- package/dist/context.d.ts +58 -0
- package/dist/context.js +44 -0
- package/dist/context.js.map +1 -0
- package/dist/parser.d.ts +39 -0
- package/dist/parser.js +323 -0
- package/dist/parser.js.map +1 -0
- package/dist/prompts/ask.md +20 -0
- package/dist/prompts/explore.md +38 -0
- package/dist/prompts/patch.md +27 -0
- package/dist/prompts/plan.md +41 -0
- package/dist/prompts/prompts/ask.md +20 -0
- package/dist/prompts/prompts/explore.md +38 -0
- package/dist/prompts/prompts/patch.md +27 -0
- package/dist/prompts/prompts/plan.md +41 -0
- package/dist/prompts/prompts/review.md +33 -0
- package/dist/prompts/review.md +33 -0
- package/dist/provider.d.ts +148 -0
- package/dist/provider.js +486 -0
- package/dist/provider.js.map +1 -0
- package/dist/repo.d.ts +22 -0
- package/dist/repo.js +154 -0
- package/dist/repo.js.map +1 -0
- package/dist/safety.d.ts +48 -0
- package/dist/safety.js +140 -0
- package/dist/safety.js.map +1 -0
- package/dist/storage.d.ts +133 -0
- package/dist/storage.js +300 -0
- package/dist/storage.js.map +1 -0
- package/dist/tools.d.ts +70 -0
- package/dist/tools.js +654 -0
- package/dist/tools.js.map +1 -0
- package/dist/ui.d.ts +203 -0
- package/dist/ui.js +410 -0
- package/dist/ui.js.map +1 -0
- package/package.json +73 -0
package/dist/repo.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Detect project type based on framework indicators
|
|
5
|
+
* Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.6, 4.7, 4.8, 4.9
|
|
6
|
+
*/
|
|
7
|
+
export function detectProjectType(rootDir = process.cwd()) {
|
|
8
|
+
const rootPath = resolve(rootDir);
|
|
9
|
+
const packageJsonPath = join(rootPath, 'package.json');
|
|
10
|
+
// Check if package.json exists
|
|
11
|
+
if (!existsSync(packageJsonPath)) {
|
|
12
|
+
throw new Error('No package.json found in project root');
|
|
13
|
+
}
|
|
14
|
+
// Parse package.json
|
|
15
|
+
let packageJson = null;
|
|
16
|
+
try {
|
|
17
|
+
packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
throw new Error(`Failed to parse package.json: ${error}`);
|
|
21
|
+
}
|
|
22
|
+
const deps = {
|
|
23
|
+
...packageJson?.dependencies,
|
|
24
|
+
...packageJson?.devDependencies,
|
|
25
|
+
};
|
|
26
|
+
// Detect Next.js (Requirement 4.1)
|
|
27
|
+
const hasNextDep = !!deps?.next;
|
|
28
|
+
const hasNextConfig = existsSync(join(rootPath, 'next.config.js')) ||
|
|
29
|
+
existsSync(join(rootPath, 'next.config.mjs')) ||
|
|
30
|
+
existsSync(join(rootPath, 'next.config.ts')) ||
|
|
31
|
+
existsSync(join(rootPath, 'next.config.cjs'));
|
|
32
|
+
// Detect Prisma once for all project types
|
|
33
|
+
const prisma = detectPrisma(rootPath, deps);
|
|
34
|
+
const packageManager = detectPackageManager(rootPath);
|
|
35
|
+
if (hasNextDep || hasNextConfig) {
|
|
36
|
+
// Detect Next.js router type (Requirements 4.2, 4.3)
|
|
37
|
+
const router = detectNextJsRouter(rootPath);
|
|
38
|
+
return {
|
|
39
|
+
type: "nextjs",
|
|
40
|
+
framework: {
|
|
41
|
+
name: "Next.js",
|
|
42
|
+
version: deps?.next,
|
|
43
|
+
router,
|
|
44
|
+
},
|
|
45
|
+
hasPrisma: prisma.hasPrisma,
|
|
46
|
+
prismaSchemaPath: prisma.prismaSchemaPath,
|
|
47
|
+
packageJsonPath,
|
|
48
|
+
packageManager,
|
|
49
|
+
rootPath,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Detect Nest.js (Requirement 4.4)
|
|
53
|
+
const hasNestDep = !!deps?.['@nestjs/core'];
|
|
54
|
+
const hasNestCli = existsSync(join(rootPath, 'nest-cli.json'));
|
|
55
|
+
if (hasNestDep || hasNestCli) {
|
|
56
|
+
return {
|
|
57
|
+
type: "nestjs",
|
|
58
|
+
framework: {
|
|
59
|
+
name: "Nest.js",
|
|
60
|
+
version: deps?.['@nestjs/core'],
|
|
61
|
+
},
|
|
62
|
+
hasPrisma: prisma.hasPrisma,
|
|
63
|
+
prismaSchemaPath: prisma.prismaSchemaPath,
|
|
64
|
+
packageJsonPath,
|
|
65
|
+
packageManager,
|
|
66
|
+
rootPath,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
// Fallback to Node.js (Requirement 4.5)
|
|
70
|
+
return {
|
|
71
|
+
type: "nodejs",
|
|
72
|
+
hasPrisma: prisma.hasPrisma,
|
|
73
|
+
prismaSchemaPath: prisma.prismaSchemaPath,
|
|
74
|
+
packageJsonPath,
|
|
75
|
+
packageManager,
|
|
76
|
+
rootPath,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Detect Next.js router type (app vs pages)
|
|
81
|
+
* Requirements: 4.2, 4.3
|
|
82
|
+
*/
|
|
83
|
+
function detectNextJsRouter(rootPath) {
|
|
84
|
+
const appDir = join(rootPath, 'app');
|
|
85
|
+
const pagesDir = join(rootPath, 'pages');
|
|
86
|
+
// Check for app router indicators (Requirement 4.2)
|
|
87
|
+
if (existsSync(appDir)) {
|
|
88
|
+
try {
|
|
89
|
+
const appContents = readdirSync(appDir);
|
|
90
|
+
const hasAppRouter = appContents.some(file => file.startsWith('layout.') || file.startsWith('page.'));
|
|
91
|
+
if (hasAppRouter) {
|
|
92
|
+
return "app";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// Ignore read errors
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Check for pages router indicators (Requirement 4.3)
|
|
100
|
+
if (existsSync(pagesDir)) {
|
|
101
|
+
try {
|
|
102
|
+
const pagesContents = readdirSync(pagesDir);
|
|
103
|
+
const hasPagesRouter = pagesContents.some(file => file.startsWith('_app.') || file.startsWith('_document.'));
|
|
104
|
+
if (hasPagesRouter) {
|
|
105
|
+
return "pages";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Ignore read errors
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// If directories exist but no clear indicators, prefer app router
|
|
113
|
+
if (existsSync(appDir))
|
|
114
|
+
return "app";
|
|
115
|
+
if (existsSync(pagesDir))
|
|
116
|
+
return "pages";
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Detect Prisma presence and schema path
|
|
121
|
+
* Requirement: 4.6
|
|
122
|
+
*/
|
|
123
|
+
function detectPrisma(rootPath, deps) {
|
|
124
|
+
const hasPrismaDep = !!deps?.prisma || !!deps?.['@prisma/client'];
|
|
125
|
+
const prismaSchemaPath = join(rootPath, 'prisma', 'schema.prisma');
|
|
126
|
+
const hasPrismaSchema = existsSync(prismaSchemaPath);
|
|
127
|
+
if (hasPrismaDep || hasPrismaSchema) {
|
|
128
|
+
return {
|
|
129
|
+
hasPrisma: true,
|
|
130
|
+
prismaSchemaPath: hasPrismaSchema ? prismaSchemaPath : undefined,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return { hasPrisma: false };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Detect package manager from lockfile presence
|
|
137
|
+
* Requirement: 4.7
|
|
138
|
+
*/
|
|
139
|
+
function detectPackageManager(rootPath) {
|
|
140
|
+
if (existsSync(join(rootPath, 'pnpm-lock.yaml'))) {
|
|
141
|
+
return "pnpm";
|
|
142
|
+
}
|
|
143
|
+
if (existsSync(join(rootPath, 'yarn.lock'))) {
|
|
144
|
+
return "yarn";
|
|
145
|
+
}
|
|
146
|
+
if (existsSync(join(rootPath, 'bun.lockb'))) {
|
|
147
|
+
return "bun";
|
|
148
|
+
}
|
|
149
|
+
if (existsSync(join(rootPath, 'package-lock.json'))) {
|
|
150
|
+
return "npm";
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=repo.js.map
|
package/dist/repo.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repo.js","sourceRoot":"","sources":["../src/repo.ts"],"names":[],"mappings":"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"}
|
package/dist/safety.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safety error for boundary violations and resource limits
|
|
3
|
+
*/
|
|
4
|
+
export declare class SafetyError extends Error {
|
|
5
|
+
constructor(message: string);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Validate that a file path is within the project root boundary
|
|
9
|
+
* Prevents path traversal attacks and symlink escapes
|
|
10
|
+
*
|
|
11
|
+
* @param inputPath - Path to validate (can be relative or absolute)
|
|
12
|
+
* @param projectRoot - Absolute path to project root
|
|
13
|
+
* @throws SafetyError if path is outside project root
|
|
14
|
+
*
|
|
15
|
+
* Requirements: 7.1, 7.3, 7.4, 8.7, 23.4
|
|
16
|
+
*/
|
|
17
|
+
export declare function validatePath(inputPath: string, projectRoot: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Validate that a file size is within the configured limit
|
|
20
|
+
*
|
|
21
|
+
* @param filePath - Path to the file to check
|
|
22
|
+
* @param maxFileSizeKb - Maximum file size in kilobytes
|
|
23
|
+
* @throws SafetyError if file exceeds size limit
|
|
24
|
+
*
|
|
25
|
+
* Requirements: 7.7
|
|
26
|
+
*/
|
|
27
|
+
export declare function validateFileSize(filePath: string, maxFileSizeKb: number): void;
|
|
28
|
+
/**
|
|
29
|
+
* Validate that a patch size (number of files) is within the configured limit
|
|
30
|
+
*
|
|
31
|
+
* @param fileCount - Number of files in the patch
|
|
32
|
+
* @param maxFilesPerPatch - Maximum number of files allowed per patch
|
|
33
|
+
* @throws SafetyError if patch exceeds file count limit
|
|
34
|
+
*
|
|
35
|
+
* Requirements: 7.8
|
|
36
|
+
*/
|
|
37
|
+
export declare function validatePatchSize(fileCount: number, maxFilesPerPatch: number): void;
|
|
38
|
+
/**
|
|
39
|
+
* Validate that a shell command is in the allowlist
|
|
40
|
+
* Only the binary name is checked, not arguments
|
|
41
|
+
*
|
|
42
|
+
* @param command - Shell command to validate
|
|
43
|
+
* @param allowedCommands - List of allowed binary names
|
|
44
|
+
* @throws SafetyError if command is not in allowlist
|
|
45
|
+
*
|
|
46
|
+
* Requirements: 7.9, 23.5
|
|
47
|
+
*/
|
|
48
|
+
export declare function validateShellCommand(command: string, allowedCommands: string[]): void;
|
package/dist/safety.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import * as nodePath from "node:path";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
/**
|
|
4
|
+
* Safety error for boundary violations and resource limits
|
|
5
|
+
*/
|
|
6
|
+
export class SafetyError extends Error {
|
|
7
|
+
constructor(message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = "SafetyError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Validate that a file path is within the project root boundary
|
|
14
|
+
* Prevents path traversal attacks and symlink escapes
|
|
15
|
+
*
|
|
16
|
+
* @param inputPath - Path to validate (can be relative or absolute)
|
|
17
|
+
* @param projectRoot - Absolute path to project root
|
|
18
|
+
* @throws SafetyError if path is outside project root
|
|
19
|
+
*
|
|
20
|
+
* Requirements: 7.1, 7.3, 7.4, 8.7, 23.4
|
|
21
|
+
*/
|
|
22
|
+
export function validatePath(inputPath, projectRoot) {
|
|
23
|
+
// Resolve the input path relative to project root
|
|
24
|
+
const resolved = nodePath.resolve(projectRoot, inputPath);
|
|
25
|
+
// Check for path traversal using separator to prevent prefix bugs
|
|
26
|
+
// Example: /project should not validate for /projects/other
|
|
27
|
+
const isExactMatch = resolved === projectRoot;
|
|
28
|
+
const isWithinProject = resolved.startsWith(projectRoot + nodePath.sep);
|
|
29
|
+
if (!isExactMatch && !isWithinProject) {
|
|
30
|
+
throw new SafetyError(`Path outside project root: ${inputPath} resolves to ${resolved}`);
|
|
31
|
+
}
|
|
32
|
+
// Check for symlink escape
|
|
33
|
+
// For write operations, the file might not exist yet (ENOENT is acceptable)
|
|
34
|
+
try {
|
|
35
|
+
const real = fs.realpathSync(resolved);
|
|
36
|
+
const isRealExactMatch = real === projectRoot;
|
|
37
|
+
const isRealWithinProject = real.startsWith(projectRoot + nodePath.sep);
|
|
38
|
+
if (!isRealExactMatch && !isRealWithinProject) {
|
|
39
|
+
throw new SafetyError(`Symlink escape detected: ${inputPath} resolves to ${real} outside project root`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
// Handle ENOENT for write operations (file doesn't exist yet)
|
|
44
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
45
|
+
// File doesn't exist yet - this is acceptable for write operations
|
|
46
|
+
// The resolved path check above is sufficient
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Re-throw other errors
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Validate that a file size is within the configured limit
|
|
55
|
+
*
|
|
56
|
+
* @param filePath - Path to the file to check
|
|
57
|
+
* @param maxFileSizeKb - Maximum file size in kilobytes
|
|
58
|
+
* @throws SafetyError if file exceeds size limit
|
|
59
|
+
*
|
|
60
|
+
* Requirements: 7.7
|
|
61
|
+
*/
|
|
62
|
+
export function validateFileSize(filePath, maxFileSizeKb) {
|
|
63
|
+
try {
|
|
64
|
+
const stats = fs.statSync(filePath);
|
|
65
|
+
const fileSizeKb = stats.size / 1024;
|
|
66
|
+
if (fileSizeKb > maxFileSizeKb) {
|
|
67
|
+
throw new SafetyError(`File size exceeds limit: ${filePath} is ${fileSizeKb.toFixed(2)}KB, max is ${maxFileSizeKb}KB`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error instanceof SafetyError) {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
// If file doesn't exist or can't be read, let the caller handle it
|
|
75
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
76
|
+
throw new SafetyError(`File not found: ${filePath}`);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Validate that a patch size (number of files) is within the configured limit
|
|
83
|
+
*
|
|
84
|
+
* @param fileCount - Number of files in the patch
|
|
85
|
+
* @param maxFilesPerPatch - Maximum number of files allowed per patch
|
|
86
|
+
* @throws SafetyError if patch exceeds file count limit
|
|
87
|
+
*
|
|
88
|
+
* Requirements: 7.8
|
|
89
|
+
*/
|
|
90
|
+
export function validatePatchSize(fileCount, maxFilesPerPatch) {
|
|
91
|
+
if (fileCount > maxFilesPerPatch) {
|
|
92
|
+
throw new SafetyError(`Patch size exceeds limit: ${fileCount} files, max is ${maxFilesPerPatch}`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Extract the binary name from a shell command string
|
|
97
|
+
* Handles quoted commands and arguments
|
|
98
|
+
*
|
|
99
|
+
* @param command - Shell command string
|
|
100
|
+
* @returns Binary name (first token)
|
|
101
|
+
*/
|
|
102
|
+
function extractBinary(command) {
|
|
103
|
+
const trimmed = command.trim();
|
|
104
|
+
// Handle quoted commands
|
|
105
|
+
if (trimmed.startsWith('"')) {
|
|
106
|
+
const endQuote = trimmed.indexOf('"', 1);
|
|
107
|
+
if (endQuote !== -1) {
|
|
108
|
+
return trimmed.substring(1, endQuote);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
if (trimmed.startsWith("'")) {
|
|
112
|
+
const endQuote = trimmed.indexOf("'", 1);
|
|
113
|
+
if (endQuote !== -1) {
|
|
114
|
+
return trimmed.substring(1, endQuote);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Extract first token (split on whitespace)
|
|
118
|
+
const tokens = trimmed.split(/\s+/);
|
|
119
|
+
return tokens[0] || "";
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Validate that a shell command is in the allowlist
|
|
123
|
+
* Only the binary name is checked, not arguments
|
|
124
|
+
*
|
|
125
|
+
* @param command - Shell command to validate
|
|
126
|
+
* @param allowedCommands - List of allowed binary names
|
|
127
|
+
* @throws SafetyError if command is not in allowlist
|
|
128
|
+
*
|
|
129
|
+
* Requirements: 7.9, 23.5
|
|
130
|
+
*/
|
|
131
|
+
export function validateShellCommand(command, allowedCommands) {
|
|
132
|
+
const binary = extractBinary(command);
|
|
133
|
+
if (!binary) {
|
|
134
|
+
throw new SafetyError("Empty shell command");
|
|
135
|
+
}
|
|
136
|
+
if (!allowedCommands.includes(binary)) {
|
|
137
|
+
throw new SafetyError(`Shell command not allowed: ${binary}. Allowed commands: ${allowedCommands.join(", ")}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=safety.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"safety.js","sourceRoot":"","sources":["../src/safety.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B;;GAEG;AACH,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiB,EAAE,WAAmB;IACjE,kDAAkD;IAClD,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;IAE1D,kEAAkE;IAClE,4DAA4D;IAC5D,MAAM,YAAY,GAAG,QAAQ,KAAK,WAAW,CAAC;IAC9C,MAAM,eAAe,GAAG,QAAQ,CAAC,UAAU,CAAC,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAExE,IAAI,CAAC,YAAY,IAAI,CAAC,eAAe,EAAE,CAAC;QACtC,MAAM,IAAI,WAAW,CACnB,8BAA8B,SAAS,gBAAgB,QAAQ,EAAE,CAClE,CAAC;IACJ,CAAC;IAED,2BAA2B;IAC3B,4EAA4E;IAC5E,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;QAEvC,MAAM,gBAAgB,GAAG,IAAI,KAAK,WAAW,CAAC;QAC9C,MAAM,mBAAmB,GAAG,IAAI,CAAC,UAAU,CAAC,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QAExE,IAAI,CAAC,gBAAgB,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC9C,MAAM,IAAI,WAAW,CACnB,4BAA4B,SAAS,gBAAgB,IAAI,uBAAuB,CACjF,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,8DAA8D;QAC9D,IAAI,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACzE,mEAAmE;YACnE,8CAA8C;YAC9C,OAAO;QACT,CAAC;QAED,wBAAwB;QACxB,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAC9B,QAAgB,EAChB,aAAqB;IAErB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;QAErC,IAAI,UAAU,GAAG,aAAa,EAAE,CAAC;YAC/B,MAAM,IAAI,WAAW,CACnB,4BAA4B,QAAQ,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,cAAc,aAAa,IAAI,CAChG,CAAC;QACJ,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,WAAW,EAAE,CAAC;YACjC,MAAM,KAAK,CAAC;QACd,CAAC;QAED,mEAAmE;QACnE,IAAI,KAAK,YAAY,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACzE,MAAM,IAAI,WAAW,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC;QACvD,CAAC;QAED,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,SAAiB,EACjB,gBAAwB;IAExB,IAAI,SAAS,GAAG,gBAAgB,EAAE,CAAC;QACjC,MAAM,IAAI,WAAW,CACnB,6BAA6B,SAAS,kBAAkB,gBAAgB,EAAE,CAC3E,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,OAAe;IACpC,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAE/B,yBAAyB;IACzB,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACzC,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;YACpB,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACzC,IAAI,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;YACpB,OAAO,OAAO,CAAC,SAAS,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,4CAA4C;IAC5C,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACpC,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACzB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAe,EACf,eAAyB;IAEzB,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAEtC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,WAAW,CAAC,qBAAqB,CAAC,CAAC;IAC/C,CAAC;IAED,IAAI,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,WAAW,CACnB,8BAA8B,MAAM,uBAAuB,eAAe,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACxF,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import Database from "better-sqlite3";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export declare const SessionStatusSchema: z.ZodEnum<{
|
|
4
|
+
running: "running";
|
|
5
|
+
completed: "completed";
|
|
6
|
+
failed: "failed";
|
|
7
|
+
cancelled: "cancelled";
|
|
8
|
+
}>;
|
|
9
|
+
export type SessionStatus = z.infer<typeof SessionStatusSchema>;
|
|
10
|
+
export declare const MessageRoleSchema: z.ZodEnum<{
|
|
11
|
+
user: "user";
|
|
12
|
+
assistant: "assistant";
|
|
13
|
+
system: "system";
|
|
14
|
+
}>;
|
|
15
|
+
export type MessageRole = z.infer<typeof MessageRoleSchema>;
|
|
16
|
+
export declare const RiskLevelSchema: z.ZodEnum<{
|
|
17
|
+
low: "low";
|
|
18
|
+
medium: "medium";
|
|
19
|
+
high: "high";
|
|
20
|
+
}>;
|
|
21
|
+
export type RiskLevel = z.infer<typeof RiskLevelSchema>;
|
|
22
|
+
export interface Session {
|
|
23
|
+
id: string;
|
|
24
|
+
command: string;
|
|
25
|
+
projectRoot: string;
|
|
26
|
+
provider: string;
|
|
27
|
+
model: string;
|
|
28
|
+
status: SessionStatus;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
completedAt: string | null;
|
|
31
|
+
error: string | null;
|
|
32
|
+
}
|
|
33
|
+
export interface Message {
|
|
34
|
+
id: number;
|
|
35
|
+
sessionId: string;
|
|
36
|
+
role: MessageRole;
|
|
37
|
+
content: string;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
}
|
|
40
|
+
export interface ToolExecution {
|
|
41
|
+
id: number;
|
|
42
|
+
sessionId: string;
|
|
43
|
+
toolName: string;
|
|
44
|
+
input: string;
|
|
45
|
+
output: string | null;
|
|
46
|
+
error: string | null;
|
|
47
|
+
createdAt: string;
|
|
48
|
+
}
|
|
49
|
+
export interface Mutation {
|
|
50
|
+
id: number;
|
|
51
|
+
sessionId: string;
|
|
52
|
+
action: string;
|
|
53
|
+
affectedFiles: string;
|
|
54
|
+
riskLevel: RiskLevel;
|
|
55
|
+
reversible: boolean;
|
|
56
|
+
rollbackHints: string | null;
|
|
57
|
+
createdAt: string;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Get or create database connection to ~/.aria/history.db
|
|
61
|
+
* Sets file permissions to 600 (user-only read/write)
|
|
62
|
+
*/
|
|
63
|
+
export declare function getDatabase(): Database.Database;
|
|
64
|
+
/**
|
|
65
|
+
* Close database connection
|
|
66
|
+
*/
|
|
67
|
+
export declare function closeDatabase(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Get current schema version from database
|
|
70
|
+
* Returns 0 if schema_versions table doesn't exist
|
|
71
|
+
*/
|
|
72
|
+
export declare function getCurrentSchemaVersion(db: Database.Database): number;
|
|
73
|
+
/**
|
|
74
|
+
* Run database migrations sequentially
|
|
75
|
+
*/
|
|
76
|
+
export declare function runMigrations(db: Database.Database): void;
|
|
77
|
+
/**
|
|
78
|
+
* Initialize database with schema
|
|
79
|
+
*/
|
|
80
|
+
export declare function initializeDatabase(): Database.Database;
|
|
81
|
+
/**
|
|
82
|
+
* Create a new session
|
|
83
|
+
*/
|
|
84
|
+
export declare function createSession(db: Database.Database, session: {
|
|
85
|
+
id: string;
|
|
86
|
+
command: string;
|
|
87
|
+
projectRoot: string;
|
|
88
|
+
provider: string;
|
|
89
|
+
model: string;
|
|
90
|
+
}): void;
|
|
91
|
+
/**
|
|
92
|
+
* Update session status
|
|
93
|
+
*/
|
|
94
|
+
export declare function updateSessionStatus(db: Database.Database, sessionId: string, status: SessionStatus, error?: string): void;
|
|
95
|
+
/**
|
|
96
|
+
* Get a single session by ID
|
|
97
|
+
*/
|
|
98
|
+
export declare function getSession(db: Database.Database, sessionId: string): Session | null;
|
|
99
|
+
/**
|
|
100
|
+
* List sessions with pagination
|
|
101
|
+
*/
|
|
102
|
+
export declare function listSessions(db: Database.Database, options?: {
|
|
103
|
+
limit?: number;
|
|
104
|
+
offset?: number;
|
|
105
|
+
status?: SessionStatus;
|
|
106
|
+
}): Session[];
|
|
107
|
+
/**
|
|
108
|
+
* Log a message to the database
|
|
109
|
+
*/
|
|
110
|
+
export declare function logMessage(db: Database.Database, sessionId: string, role: MessageRole, content: string): void;
|
|
111
|
+
/**
|
|
112
|
+
* Log a tool execution to the database
|
|
113
|
+
*/
|
|
114
|
+
export declare function logToolExecution(db: Database.Database, sessionId: string, toolName: string, input: unknown, result: {
|
|
115
|
+
success: boolean;
|
|
116
|
+
data?: unknown;
|
|
117
|
+
error?: string;
|
|
118
|
+
}): void;
|
|
119
|
+
/**
|
|
120
|
+
* Log a mutation to the database
|
|
121
|
+
*/
|
|
122
|
+
export declare function logMutation(db: Database.Database, sessionId: string, mutation: {
|
|
123
|
+
action: string;
|
|
124
|
+
affectedFiles: string[];
|
|
125
|
+
riskLevel: RiskLevel;
|
|
126
|
+
reversible: boolean;
|
|
127
|
+
rollbackHints?: string[];
|
|
128
|
+
}): void;
|
|
129
|
+
/**
|
|
130
|
+
* Delete sessions older than retainDays
|
|
131
|
+
* Cascading deletes will remove associated messages, tool_executions, and mutations
|
|
132
|
+
*/
|
|
133
|
+
export declare function deleteOldSessions(db: Database.Database, retainDays: number): number;
|