ownkey 0.2.1 → 0.5.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.
Files changed (49) hide show
  1. package/README.md +64 -32
  2. package/dist/ai/factory.d.ts +23 -0
  3. package/dist/ai/factory.d.ts.map +1 -0
  4. package/dist/ai/factory.js +96 -0
  5. package/dist/ai/factory.js.map +1 -0
  6. package/dist/ai/gemini.d.ts +21 -0
  7. package/dist/ai/gemini.d.ts.map +1 -0
  8. package/dist/ai/gemini.js +87 -0
  9. package/dist/ai/gemini.js.map +1 -0
  10. package/dist/ai/provider.d.ts +39 -0
  11. package/dist/ai/provider.d.ts.map +1 -0
  12. package/dist/ai/provider.js +99 -0
  13. package/dist/ai/provider.js.map +1 -0
  14. package/dist/commands/config.js +3 -1
  15. package/dist/commands/config.js.map +1 -1
  16. package/dist/commands/suggest.d.ts.map +1 -1
  17. package/dist/commands/suggest.js +102 -10
  18. package/dist/commands/suggest.js.map +1 -1
  19. package/dist/config/defaults.d.ts.map +1 -1
  20. package/dist/config/defaults.js +5 -2
  21. package/dist/config/defaults.js.map +1 -1
  22. package/dist/config/keychain.d.ts.map +1 -1
  23. package/dist/config/keychain.js +12 -0
  24. package/dist/config/keychain.js.map +1 -1
  25. package/dist/db/client.d.ts.map +1 -1
  26. package/dist/db/client.js +41 -15
  27. package/dist/db/client.js.map +1 -1
  28. package/dist/types/index.d.ts +4 -1
  29. package/dist/types/index.d.ts.map +1 -1
  30. package/dist/types/index.js.map +1 -1
  31. package/package.json +10 -2
  32. package/.prettierrc +0 -9
  33. package/src/commands/apply.ts +0 -27
  34. package/src/commands/config.ts +0 -154
  35. package/src/commands/scan.ts +0 -114
  36. package/src/commands/suggest.ts +0 -37
  37. package/src/config/defaults.ts +0 -80
  38. package/src/config/keychain.ts +0 -125
  39. package/src/config/manager.ts +0 -166
  40. package/src/core/scanner.ts +0 -174
  41. package/src/db/client.ts +0 -151
  42. package/src/db/operations.ts +0 -217
  43. package/src/db/schema.ts +0 -118
  44. package/src/index.ts +0 -74
  45. package/src/types/index.ts +0 -198
  46. package/src/utils/encryption.ts +0 -54
  47. package/src/utils/logger.ts +0 -111
  48. package/tsconfig.json +0 -33
  49. /package/{src → dist}/db/migrations/001_init.sql +0 -0
package/src/db/client.ts DELETED
@@ -1,151 +0,0 @@
1
- import postgres from 'postgres';
2
- import { drizzle } from 'drizzle-orm/postgres-js';
3
- import { readFile } from 'fs/promises';
4
- import { join, dirname } from 'path';
5
- import { fileURLToPath } from 'url';
6
- import { DatabaseError } from '../types/index.js';
7
- import { logger } from '../utils/logger.js';
8
- import { configManager } from '../config/manager.js';
9
- import * as schema from './schema.js';
10
-
11
- const __filename = fileURLToPath(import.meta.url);
12
- const __dirname = dirname(__filename);
13
-
14
- export class DatabaseClient {
15
- private sql: postgres.Sql | null = null;
16
- private db: ReturnType<typeof drizzle> | null = null;
17
- private isLocalOnly: boolean = false;
18
-
19
- async connect(localOnly: boolean = false): Promise<void> {
20
- if (localOnly) {
21
- this.isLocalOnly = true;
22
- logger.info('Running in local-only mode (no database persistence)');
23
- return;
24
- }
25
-
26
- try {
27
- const config = await configManager.load();
28
-
29
- if (!config.supabaseUrl || !config.supabaseServiceRoleKey) {
30
- logger.warn('Supabase not configured. Running in local-only mode.');
31
- logger.info('Run "ownkey config" to set up database persistence.');
32
- this.isLocalOnly = true;
33
- return;
34
- }
35
-
36
- // Create connection string
37
- const connectionString = `${config.supabaseUrl}?sslmode=require`;
38
-
39
- this.sql = postgres(connectionString, {
40
- password: config.supabaseServiceRoleKey,
41
- max: 10,
42
- idle_timeout: 20,
43
- connect_timeout: 10,
44
- });
45
-
46
- this.db = drizzle(this.sql, { schema });
47
-
48
- // Test connection
49
- await this.sql`SELECT 1`;
50
- logger.verbose('Database connection established');
51
-
52
- // Run migrations
53
- await this.runMigrations();
54
- } catch (error) {
55
- logger.warn('Failed to connect to database. Running in local-only mode.');
56
- logger.debug('Database error:', error);
57
- this.isLocalOnly = true;
58
- this.sql = null;
59
- this.db = null;
60
- }
61
- }
62
-
63
- async disconnect(): Promise<void> {
64
- if (this.sql) {
65
- await this.sql.end();
66
- this.sql = null;
67
- this.db = null;
68
- logger.verbose('Database connection closed');
69
- }
70
- }
71
-
72
- getDb(): ReturnType<typeof drizzle> {
73
- if (this.isLocalOnly) {
74
- throw new DatabaseError('Database not available in local-only mode');
75
- }
76
- if (!this.db) {
77
- throw new DatabaseError('Database not connected');
78
- }
79
- return this.db;
80
- }
81
-
82
- isConnected(): boolean {
83
- return this.db !== null && !this.isLocalOnly;
84
- }
85
-
86
- isLocalOnlyMode(): boolean {
87
- return this.isLocalOnly;
88
- }
89
-
90
- private async runMigrations(): Promise<void> {
91
- if (!this.sql) {
92
- return;
93
- }
94
-
95
- try {
96
- logger.verbose('Checking database migrations...');
97
-
98
- // Check if migrations table exists
99
- const tableExists = await this.sql`
100
- SELECT EXISTS (
101
- SELECT FROM information_schema.tables
102
- WHERE table_name = 'schema_migrations'
103
- );
104
- `;
105
-
106
- if (!tableExists[0]?.exists) {
107
- logger.info('Initializing database schema...');
108
- await this.runMigration(1);
109
- logger.success('Database schema initialized');
110
- return;
111
- }
112
-
113
- // Check current version
114
- const currentVersion = await this.sql`
115
- SELECT MAX(version) as version FROM schema_migrations;
116
- `;
117
-
118
- const version = currentVersion[0]?.version || 0;
119
- logger.verbose(`Current schema version: ${version}`);
120
-
121
- // Run pending migrations
122
- const latestVersion = 1; // Update this as we add more migrations
123
- if (version < latestVersion) {
124
- for (let v = version + 1; v <= latestVersion; v++) {
125
- await this.runMigration(v);
126
- logger.success(`Applied migration ${v}`);
127
- }
128
- }
129
- } catch (error) {
130
- throw new DatabaseError('Failed to run migrations', error);
131
- }
132
- }
133
-
134
- private async runMigration(version: number): Promise<void> {
135
- if (!this.sql) {
136
- return;
137
- }
138
-
139
- const migrationFile = join(__dirname, 'migrations', `${String(version).padStart(3, '0')}_init.sql`);
140
-
141
- try {
142
- const migrationSql = await readFile(migrationFile, 'utf-8');
143
- await this.sql.unsafe(migrationSql);
144
- logger.verbose(`Migration ${version} applied successfully`);
145
- } catch (error) {
146
- throw new DatabaseError(`Failed to apply migration ${version}`, error);
147
- }
148
- }
149
- }
150
-
151
- export const dbClient = new DatabaseClient();
@@ -1,217 +0,0 @@
1
- import { eq, desc } from 'drizzle-orm';
2
- import { dbClient } from './client.js';
3
- import * as schema from './schema.js';
4
- import {
5
- Project,
6
- Run,
7
- SuggestionRecord,
8
- RunStatus,
9
- SuggestionAction,
10
- } from '../types/index.js';
11
- import { logger } from '../utils/logger.js';
12
-
13
- export class DatabaseOperations {
14
- // Projects
15
- async createProject(name: string, rootPath: string): Promise<Project | null> {
16
- if (dbClient.isLocalOnlyMode()) {
17
- logger.verbose('Skipping project creation (local-only mode)');
18
- return null;
19
- }
20
-
21
- const db = dbClient.getDb();
22
- const [project] = await db
23
- .insert(schema.projects)
24
- .values({ name, rootPath })
25
- .returning();
26
-
27
- return {
28
- id: project.id,
29
- name: project.name,
30
- rootPath: project.rootPath,
31
- createdAt: project.createdAt,
32
- updatedAt: project.updatedAt,
33
- };
34
- }
35
-
36
- async getProjectByPath(rootPath: string): Promise<Project | null> {
37
- if (dbClient.isLocalOnlyMode()) {
38
- return null;
39
- }
40
-
41
- const db = dbClient.getDb();
42
- const [project] = await db
43
- .select()
44
- .from(schema.projects)
45
- .where(eq(schema.projects.rootPath, rootPath))
46
- .limit(1);
47
-
48
- if (!project) return null;
49
-
50
- return {
51
- id: project.id,
52
- name: project.name,
53
- rootPath: project.rootPath,
54
- createdAt: project.createdAt,
55
- updatedAt: project.updatedAt,
56
- };
57
- }
58
-
59
- // Runs
60
- async createRun(projectId: string, command: string): Promise<Run | null> {
61
- if (dbClient.isLocalOnlyMode()) {
62
- logger.verbose('Skipping run creation (local-only mode)');
63
- return null;
64
- }
65
-
66
- const db = dbClient.getDb();
67
- const [run] = await db
68
- .insert(schema.runs)
69
- .values({ projectId, command, status: 'running' })
70
- .returning();
71
-
72
- return {
73
- id: run.id,
74
- projectId: run.projectId,
75
- command: run.command,
76
- status: run.status as RunStatus,
77
- summary: run.summary || undefined,
78
- startedAt: run.startedAt,
79
- finishedAt: run.finishedAt || undefined,
80
- };
81
- }
82
-
83
- async updateRunStatus(
84
- runId: string,
85
- status: RunStatus,
86
- summary?: string
87
- ): Promise<void> {
88
- if (dbClient.isLocalOnlyMode()) {
89
- return;
90
- }
91
-
92
- const db = dbClient.getDb();
93
- await db
94
- .update(schema.runs)
95
- .set({ status, summary, finishedAt: new Date() })
96
- .where(eq(schema.runs.id, runId));
97
- }
98
-
99
- async getRecentRuns(limit: number = 10): Promise<Run[]> {
100
- if (dbClient.isLocalOnlyMode()) {
101
- return [];
102
- }
103
-
104
- const db = dbClient.getDb();
105
- const runs = await db
106
- .select()
107
- .from(schema.runs)
108
- .orderBy(desc(schema.runs.startedAt))
109
- .limit(limit);
110
-
111
- return runs.map((run) => ({
112
- id: run.id,
113
- projectId: run.projectId,
114
- command: run.command,
115
- status: run.status as RunStatus,
116
- summary: run.summary || undefined,
117
- startedAt: run.startedAt,
118
- finishedAt: run.finishedAt || undefined,
119
- }));
120
- }
121
-
122
- // Suggestions
123
- async createSuggestion(
124
- projectId: string,
125
- suggestion: Omit<SuggestionRecord, 'id' | 'projectId' | 'createdAt'>
126
- ): Promise<SuggestionRecord | null> {
127
- if (dbClient.isLocalOnlyMode()) {
128
- logger.verbose('Skipping suggestion storage (local-only mode)');
129
- return null;
130
- }
131
-
132
- const db = dbClient.getDb();
133
- const [created] = await db
134
- .insert(schema.suggestions)
135
- .values({
136
- projectId,
137
- type: suggestion.type,
138
- severity: suggestion.severity,
139
- content: suggestion.content,
140
- diff: suggestion.diff,
141
- filePath: suggestion.filePath,
142
- rationale: suggestion.rationale,
143
- tags: suggestion.tags,
144
- })
145
- .returning();
146
-
147
- return {
148
- id: created.id,
149
- projectId: created.projectId,
150
- type: created.type as any,
151
- severity: created.severity as any,
152
- content: created.content,
153
- diff: created.diff,
154
- filePath: created.filePath,
155
- rationale: created.rationale,
156
- tags: created.tags as any,
157
- createdAt: created.createdAt,
158
- };
159
- }
160
-
161
- async getSuggestionById(id: string): Promise<SuggestionRecord | null> {
162
- if (dbClient.isLocalOnlyMode()) {
163
- return null;
164
- }
165
-
166
- const db = dbClient.getDb();
167
- const [suggestion] = await db
168
- .select()
169
- .from(schema.suggestions)
170
- .where(eq(schema.suggestions.id, id))
171
- .limit(1);
172
-
173
- if (!suggestion) return null;
174
-
175
- return {
176
- id: suggestion.id,
177
- projectId: suggestion.projectId,
178
- type: suggestion.type as any,
179
- severity: suggestion.severity as any,
180
- content: suggestion.content,
181
- diff: suggestion.diff,
182
- filePath: suggestion.filePath,
183
- rationale: suggestion.rationale,
184
- tags: suggestion.tags as any,
185
- createdAt: suggestion.createdAt,
186
- };
187
- }
188
-
189
- async linkRunSuggestion(runId: string, suggestionId: string): Promise<void> {
190
- if (dbClient.isLocalOnlyMode()) {
191
- return;
192
- }
193
-
194
- const db = dbClient.getDb();
195
- await db.insert(schema.runSuggestions).values({ runId, suggestionId });
196
- }
197
-
198
- // Suggestion History
199
- async recordSuggestionAction(
200
- suggestionId: string,
201
- action: SuggestionAction,
202
- details?: string
203
- ): Promise<void> {
204
- if (dbClient.isLocalOnlyMode()) {
205
- return;
206
- }
207
-
208
- const db = dbClient.getDb();
209
- await db.insert(schema.suggestionHistory).values({
210
- suggestionId,
211
- action,
212
- details,
213
- });
214
- }
215
- }
216
-
217
- export const dbOps = new DatabaseOperations();
package/src/db/schema.ts DELETED
@@ -1,118 +0,0 @@
1
- import { pgTable, uuid, text, timestamp, jsonb, integer } from 'drizzle-orm/pg-core';
2
- import { relations } from 'drizzle-orm';
3
-
4
- // Projects table
5
- export const projects = pgTable('projects', {
6
- id: uuid('id').primaryKey().defaultRandom(),
7
- name: text('name').notNull(),
8
- rootPath: text('root_path').notNull(),
9
- createdAt: timestamp('created_at').defaultNow().notNull(),
10
- updatedAt: timestamp('updated_at').defaultNow().notNull(),
11
- });
12
-
13
- // Runs table
14
- export const runs = pgTable('runs', {
15
- id: uuid('id').primaryKey().defaultRandom(),
16
- projectId: uuid('project_id')
17
- .references(() => projects.id)
18
- .notNull(),
19
- command: text('command').notNull(),
20
- status: text('status').notNull(), // 'running' | 'completed' | 'failed'
21
- summary: text('summary'),
22
- startedAt: timestamp('started_at').defaultNow().notNull(),
23
- finishedAt: timestamp('finished_at'),
24
- });
25
-
26
- // Suggestions table
27
- export const suggestions = pgTable('suggestions', {
28
- id: uuid('id').primaryKey().defaultRandom(),
29
- projectId: uuid('project_id')
30
- .references(() => projects.id)
31
- .notNull(),
32
- type: text('type').notNull(), // 'bug' | 'security' | 'performance' | etc.
33
- severity: text('severity').notNull(), // 'critical' | 'high' | 'medium' | 'low' | 'info'
34
- content: text('content').notNull(),
35
- diff: text('diff').notNull(),
36
- filePath: text('file_path').notNull(),
37
- rationale: text('rationale').notNull(),
38
- tags: jsonb('tags'),
39
- createdAt: timestamp('created_at').defaultNow().notNull(),
40
- });
41
-
42
- // Suggestion history table
43
- export const suggestionHistory = pgTable('suggestion_history', {
44
- id: uuid('id').primaryKey().defaultRandom(),
45
- suggestionId: uuid('suggestion_id')
46
- .references(() => suggestions.id)
47
- .notNull(),
48
- action: text('action').notNull(), // 'applied' | 'rejected' | 'undone'
49
- details: text('details'),
50
- actedAt: timestamp('acted_at').defaultNow().notNull(),
51
- });
52
-
53
- // Run suggestions junction table
54
- export const runSuggestions = pgTable('run_suggestions', {
55
- runId: uuid('run_id')
56
- .references(() => runs.id)
57
- .notNull(),
58
- suggestionId: uuid('suggestion_id')
59
- .references(() => suggestions.id)
60
- .notNull(),
61
- });
62
-
63
- // User API keys table (for fingerprints only)
64
- export const userApiKeys = pgTable('user_api_keys', {
65
- id: uuid('id').primaryKey().defaultRandom(),
66
- projectId: uuid('project_id').references(() => projects.id),
67
- provider: text('provider').notNull(),
68
- keyFingerprint: text('key_fingerprint').notNull(),
69
- addedAt: timestamp('added_at').defaultNow().notNull(),
70
- });
71
-
72
- // Schema migrations table
73
- export const schemaMigrations = pgTable('schema_migrations', {
74
- version: integer('version').primaryKey(),
75
- appliedAt: timestamp('applied_at').defaultNow().notNull(),
76
- });
77
-
78
- // Relations
79
- export const projectsRelations = relations(projects, ({ many }) => ({
80
- runs: many(runs),
81
- suggestions: many(suggestions),
82
- apiKeys: many(userApiKeys),
83
- }));
84
-
85
- export const runsRelations = relations(runs, ({ one, many }) => ({
86
- project: one(projects, {
87
- fields: [runs.projectId],
88
- references: [projects.id],
89
- }),
90
- runSuggestions: many(runSuggestions),
91
- }));
92
-
93
- export const suggestionsRelations = relations(suggestions, ({ one, many }) => ({
94
- project: one(projects, {
95
- fields: [suggestions.projectId],
96
- references: [projects.id],
97
- }),
98
- history: many(suggestionHistory),
99
- runSuggestions: many(runSuggestions),
100
- }));
101
-
102
- export const suggestionHistoryRelations = relations(suggestionHistory, ({ one }) => ({
103
- suggestion: one(suggestions, {
104
- fields: [suggestionHistory.suggestionId],
105
- references: [suggestions.id],
106
- }),
107
- }));
108
-
109
- export const runSuggestionsRelations = relations(runSuggestions, ({ one }) => ({
110
- run: one(runs, {
111
- fields: [runSuggestions.runId],
112
- references: [runs.id],
113
- }),
114
- suggestion: one(suggestions, {
115
- fields: [runSuggestions.suggestionId],
116
- references: [suggestions.id],
117
- }),
118
- }));
package/src/index.ts DELETED
@@ -1,74 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { Command } from 'commander';
4
- import { logger } from './utils/logger.js';
5
- import { configManager } from './config/manager.js';
6
- import { dbClient } from './db/client.js';
7
- import chalk from 'chalk';
8
-
9
- // Import commands
10
- import { configCommand } from './commands/config.js';
11
- import { scanCommand } from './commands/scan.js';
12
- import { suggestCommand } from './commands/suggest.js';
13
- import { applyCommand } from './commands/apply.js';
14
-
15
- const program = new Command();
16
-
17
- program
18
- .name('ownkey')
19
- .description('Local-first AI coding agent for intelligent codebase analysis')
20
- .version('0.2.0');
21
-
22
- // Global options
23
- program.option('--local-only', 'Run without database persistence');
24
- program.option('--verbose', 'Enable verbose logging');
25
- program.option('--debug', 'Enable debug logging');
26
- program.option('--quiet', 'Suppress non-essential output');
27
-
28
- // Commands
29
- program.addCommand(configCommand);
30
- program.addCommand(scanCommand);
31
- program.addCommand(suggestCommand);
32
- program.addCommand(applyCommand);
33
-
34
- // Pre-parse hook to set up logging and check config
35
- program.hook('preAction', async (thisCommand) => {
36
- await logger.init();
37
-
38
- const opts = thisCommand.opts();
39
-
40
- // Set log level
41
- if (opts.debug) {
42
- logger.setLevel('debug');
43
- } else if (opts.verbose) {
44
- logger.setLevel('verbose');
45
- } else if (opts.quiet) {
46
- logger.setLevel('quiet');
47
- }
48
-
49
- // Check if this is the config command
50
- const isConfigCommand = thisCommand.name() === 'config';
51
-
52
- // For non-config commands, check if config exists
53
- if (!isConfigCommand) {
54
- const configExists = await configManager.exists();
55
-
56
- if (!configExists) {
57
- console.log(chalk.yellow('\n⚠️ No configuration found.\n'));
58
- console.log('OwnKey needs to be configured before use.');
59
- console.log(chalk.cyan('\nRun: ownkey config\n'));
60
- process.exit(1);
61
- }
62
-
63
- // Connect to database (or enter local-only mode)
64
- await dbClient.connect(opts.localOnly);
65
- }
66
- });
67
-
68
- // Post-action hook to clean up
69
- program.hook('postAction', async () => {
70
- await dbClient.disconnect();
71
- });
72
-
73
- // Parse arguments
74
- program.parse();