stakeout-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.
Files changed (56) hide show
  1. package/LICENSE +131 -0
  2. package/README.md +152 -0
  3. package/dist/commands/chat.d.ts +5 -0
  4. package/dist/commands/chat.js +162 -0
  5. package/dist/commands/clear.d.ts +7 -0
  6. package/dist/commands/clear.js +89 -0
  7. package/dist/commands/config.d.ts +10 -0
  8. package/dist/commands/config.js +64 -0
  9. package/dist/commands/dashboard.d.ts +5 -0
  10. package/dist/commands/dashboard.js +9 -0
  11. package/dist/commands/digest.d.ts +6 -0
  12. package/dist/commands/digest.js +113 -0
  13. package/dist/commands/export.d.ts +8 -0
  14. package/dist/commands/export.js +118 -0
  15. package/dist/commands/hook.d.ts +6 -0
  16. package/dist/commands/hook.js +57 -0
  17. package/dist/commands/init.d.ts +6 -0
  18. package/dist/commands/init.js +70 -0
  19. package/dist/commands/log.d.ts +9 -0
  20. package/dist/commands/log.js +103 -0
  21. package/dist/commands/note.d.ts +9 -0
  22. package/dist/commands/note.js +48 -0
  23. package/dist/commands/record.d.ts +6 -0
  24. package/dist/commands/record.js +106 -0
  25. package/dist/commands/repo.d.ts +7 -0
  26. package/dist/commands/repo.js +60 -0
  27. package/dist/commands/search.d.ts +5 -0
  28. package/dist/commands/search.js +69 -0
  29. package/dist/commands/stats.d.ts +1 -0
  30. package/dist/commands/stats.js +99 -0
  31. package/dist/commands/tag.d.ts +8 -0
  32. package/dist/commands/tag.js +61 -0
  33. package/dist/commands/tui.d.ts +1 -0
  34. package/dist/commands/tui.js +5 -0
  35. package/dist/commands/watch.d.ts +6 -0
  36. package/dist/commands/watch.js +101 -0
  37. package/dist/index.d.ts +2 -0
  38. package/dist/index.js +195 -0
  39. package/dist/lib/config.d.ts +5 -0
  40. package/dist/lib/config.js +51 -0
  41. package/dist/lib/database.d.ts +31 -0
  42. package/dist/lib/database.js +222 -0
  43. package/dist/lib/diff.d.ts +3 -0
  44. package/dist/lib/diff.js +118 -0
  45. package/dist/lib/summarizer.d.ts +2 -0
  46. package/dist/lib/summarizer.js +90 -0
  47. package/dist/tui/App.d.ts +1 -0
  48. package/dist/tui/App.js +125 -0
  49. package/dist/types/index.d.ts +38 -0
  50. package/dist/types/index.js +1 -0
  51. package/dist/web/public/app.js +387 -0
  52. package/dist/web/public/index.html +131 -0
  53. package/dist/web/public/styles.css +571 -0
  54. package/dist/web/server.d.ts +1 -0
  55. package/dist/web/server.js +402 -0
  56. package/package.json +69 -0
package/LICENSE ADDED
@@ -0,0 +1,131 @@
1
+ # PolyForm Noncommercial License 1.0.0
2
+
3
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
4
+
5
+ ## Acceptance
6
+
7
+ In order to get any license under these terms, you must agree
8
+ to them as both strict obligations and conditions to all
9
+ your licenses.
10
+
11
+ ## Copyright License
12
+
13
+ The licensor grants you a copyright license for the
14
+ software to do everything you might do with the software
15
+ that would otherwise infringe the licensor's copyright
16
+ in it for any permitted purpose. However, you may
17
+ only distribute the software according to [Distribution
18
+ License](#distribution-license) and make changes or new works
19
+ based on the software according to [Changes and New Works
20
+ License](#changes-and-new-works-license).
21
+
22
+ ## Distribution License
23
+
24
+ The licensor grants you an additional copyright license
25
+ to distribute copies of the software. Your license
26
+ to distribute covers distributing the software with
27
+ changes and new works permitted by [Changes and New Works
28
+ License](#changes-and-new-works-license).
29
+
30
+ ## Notices
31
+
32
+ You must ensure that anyone who gets a copy of any part of
33
+ the software from you also gets a copy of these terms or the
34
+ URL for them above, as well as copies of any plain-text lines
35
+ beginning with `Required Notice:` that the licensor provided
36
+ with the software. For example:
37
+
38
+ > Required Notice: Copyright Casey (https://github.com/yourusername)
39
+
40
+ ## Changes and New Works License
41
+
42
+ The licensor grants you an additional copyright license to
43
+ make changes and new works based on the software for any
44
+ permitted purpose.
45
+
46
+ ## Patent License
47
+
48
+ The licensor grants you a patent license for the software that
49
+ covers patent claims the licensor can license, or becomes able
50
+ to license, that you would infringe by using the software.
51
+
52
+ ## Noncommercial Purposes
53
+
54
+ Any noncommercial purpose is a permitted purpose.
55
+
56
+ ## Personal Uses
57
+
58
+ Personal use for research, experiment, and testing for
59
+ the benefit of public knowledge, personal study, private
60
+ entertainment, hobby projects, amateur pursuits, or religious
61
+ observance, without any anticipated commercial application,
62
+ is use for a permitted purpose.
63
+
64
+ ## Noncommercial Organizations
65
+
66
+ Use by any charitable organization, educational institution,
67
+ public research organization, public safety or health
68
+ organization, environmental protection organization,
69
+ or government institution is use for a permitted purpose
70
+ regardless of the source of funding or obligations resulting
71
+ from the funding.
72
+
73
+ ## Fair Use
74
+
75
+ You may have "fair use" rights for the software under the
76
+ law. These terms do not limit them.
77
+
78
+ ## No Other Rights
79
+
80
+ These terms do not allow you to sublicense or transfer any of
81
+ your licenses to anyone else, or prevent the licensor from
82
+ granting licenses to anyone else. These terms do not imply
83
+ any other licenses.
84
+
85
+ ## Patent Defense
86
+
87
+ If you make any written claim that the software infringes or
88
+ contributes to infringement of any patent, your patent license
89
+ for the software granted under these terms ends immediately. If
90
+ your company makes such a claim, your patent license ends
91
+ immediately for work on behalf of your company.
92
+
93
+ ## Violations
94
+
95
+ The first time you are notified in writing that you have
96
+ violated any of these terms, or done anything with the software
97
+ not covered by your licenses, your licenses can nonetheless
98
+ continue if you come into full compliance with these terms,
99
+ and take practical steps to correct past violations, within
100
+ 32 days of receiving notice. Otherwise, all your licenses
101
+ end immediately.
102
+
103
+ ## No Liability
104
+
105
+ ***As far as the law allows, the software comes as is, without
106
+ any warranty or condition, and the licensor will not be liable
107
+ to you for any damages arising out of these terms or the use
108
+ or nature of the software, under any kind of legal claim.***
109
+
110
+ ## Definitions
111
+
112
+ The **licensor** is the individual or entity offering these
113
+ terms, and the **software** is the software the licensor makes
114
+ available under these terms.
115
+
116
+ **You** refers to the individual or entity agreeing to these
117
+ terms.
118
+
119
+ **Your company** is any legal entity, sole proprietorship,
120
+ or other kind of organization that you work for, plus all
121
+ organizations that have control over, are under the control of,
122
+ or are under common control with that organization. **Control**
123
+ means ownership of substantially all the assets of an entity,
124
+ or the power to direct its management and policies by vote,
125
+ contract, or otherwise. Control can be direct or indirect.
126
+
127
+ **Your licenses** are all the licenses granted to you for the
128
+ software under these terms.
129
+
130
+ **Use** means anything you do with the software requiring one
131
+ of your licenses.
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # Stakeout
2
+
3
+ Surveillance ops for your codebase. AI-powered change tracking that turns your git history into readable summaries.
4
+
5
+ **Stop reading diffs. Start understanding changes.**
6
+
7
+ ## What It Does
8
+
9
+ Every time you commit (or manually record), Stakeout:
10
+ 1. Captures the diff
11
+ 2. Sends it to an AI (local Ollama or OpenAI)
12
+ 3. Generates a human-readable summary focused on *why* and *impact*
13
+ 4. Stores it in a searchable database
14
+
15
+ Later you can:
16
+ - Browse your dev history as readable summaries
17
+ - Search through past changes
18
+ - Chat with AI about your codebase evolution
19
+ - Generate digests ("what happened this week?")
20
+ - Tag, favorite, and annotate entries
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm install -g stakeout
26
+ # or
27
+ npx stakeout
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ```bash
33
+ # Initialize in your repo
34
+ stakeout init
35
+
36
+ # Record current changes
37
+ stakeout record
38
+
39
+ # View recent summaries
40
+ stakeout log
41
+
42
+ # See what you did this week
43
+ stakeout log --since "1 week"
44
+ ```
45
+
46
+ ## Commands
47
+
48
+ | Command | What It Does |
49
+ |---------|--------------|
50
+ | `stakeout init` | Initialize for a repo, optionally install git hook |
51
+ | `stakeout record` | Record and summarize current changes |
52
+ | `stakeout log` | View recorded summaries |
53
+ | `stakeout search <query>` | Search through summaries |
54
+ | `stakeout chat` | Chat with AI about your codebase history |
55
+ | `stakeout digest` | Generate high-level digest of recent activity |
56
+ | `stakeout stats` | Show statistics and analytics |
57
+ | `stakeout watch` | Watch for changes and auto-record |
58
+ | `stakeout hook` | Install git post-commit hook for auto-recording |
59
+ | `stakeout dashboard` | Open web UI |
60
+ | `stakeout tui` | Open terminal UI |
61
+
62
+ ## AI Configuration
63
+
64
+ Stakeout uses **Ollama by default** (free, local, private).
65
+
66
+ ```bash
67
+ # Check current config
68
+ stakeout config --show
69
+
70
+ # Use Ollama (default)
71
+ stakeout config --provider ollama
72
+ stakeout config --ollama-model llama3.1
73
+
74
+ # Or use OpenAI
75
+ stakeout config --provider openai
76
+ stakeout config --openai-key sk-...
77
+ stakeout config --openai-model gpt-4o-mini
78
+ ```
79
+
80
+ ## Auto-Recording
81
+
82
+ Set it and forget it:
83
+
84
+ ```bash
85
+ # Install git hook (records on every commit)
86
+ stakeout hook
87
+
88
+ # Or watch mode (records on file changes)
89
+ stakeout watch
90
+ ```
91
+
92
+ ## Organization
93
+
94
+ ```bash
95
+ # Tag entries
96
+ stakeout tag 42 --add "feature,auth"
97
+
98
+ # Mark as favorite
99
+ stakeout note 42 --favorite
100
+
101
+ # Add notes
102
+ stakeout note 42 --note "This fixed the login bug"
103
+
104
+ # Mark breaking changes
105
+ stakeout note 42 --breaking
106
+
107
+ # Filter by tag
108
+ stakeout log --tag feature
109
+
110
+ # Show only favorites
111
+ stakeout log --favorites
112
+ ```
113
+
114
+ ## Export
115
+
116
+ ```bash
117
+ # Export to markdown
118
+ stakeout export --format markdown --since "1 month" -o changelog.md
119
+
120
+ # Export to JSON
121
+ stakeout export --format json -o history.json
122
+ ```
123
+
124
+ ## Multi-Repo
125
+
126
+ ```bash
127
+ # Add repos to track
128
+ stakeout repo --add /path/to/project
129
+
130
+ # List tracked repos
131
+ stakeout repo --list
132
+ ```
133
+
134
+ ## Storage
135
+
136
+ Everything stored locally in `~/.stakeout/`:
137
+ - `stakeout.db` — SQLite database
138
+ - `config.json` — settings
139
+
140
+ Your code never leaves your machine (unless you choose OpenAI).
141
+
142
+ ## Why?
143
+
144
+ Because "what did I do last week?" shouldn't require archaeology.
145
+
146
+ Because commit messages are often "fix" or "wip" or nothing at all.
147
+
148
+ Because AI can read your diffs and tell you what actually changed.
149
+
150
+ ---
151
+
152
+ *Built for devs who want their git history to make sense.*
@@ -0,0 +1,5 @@
1
+ interface ChatOptions {
2
+ clear?: boolean;
3
+ }
4
+ export declare function chat(options: ChatOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,162 @@
1
+ import chalk from 'chalk';
2
+ import * as readline from 'readline';
3
+ import { getEntries, getDb, addChatMessage, getChatHistory, clearChatHistory } from '../lib/database.js';
4
+ import { loadConfig } from '../lib/config.js';
5
+ import { Ollama } from 'ollama';
6
+ import OpenAI from 'openai';
7
+ const SYSTEM_PROMPT = `You are STAKEOUT AI, an intelligent assistant that helps developers understand their codebase history and changes.
8
+
9
+ You have access to a database of recorded code changes, each with:
10
+ - A timestamp
11
+ - An AI-generated summary of what changed
12
+ - The directories and files affected
13
+ - Optional commit hash and message
14
+ - Tags, notes, and whether it's marked as breaking
15
+
16
+ Your job is to:
17
+ 1. Answer questions about what changed and when
18
+ 2. Identify patterns in development activity
19
+ 3. Help find specific changes or features
20
+ 4. Provide insights about the codebase evolution
21
+
22
+ Be concise but helpful. Use the context provided to give accurate answers.
23
+ If you don't have enough information, say so.`;
24
+ export async function chat(options) {
25
+ if (options.clear) {
26
+ clearChatHistory();
27
+ console.log(chalk.green('Chat history cleared.'));
28
+ return;
29
+ }
30
+ console.log(chalk.bold.green('\n🔍 STAKEOUT AI Chat\n'));
31
+ console.log(chalk.dim('Ask questions about your codebase history.'));
32
+ console.log(chalk.dim('Type "exit" to quit, "clear" to reset history.\n'));
33
+ console.log(chalk.dim('─'.repeat(50)));
34
+ const rl = readline.createInterface({
35
+ input: process.stdin,
36
+ output: process.stdout
37
+ });
38
+ const prompt = () => {
39
+ rl.question(chalk.cyan('\nYou: '), async (input) => {
40
+ const trimmed = input.trim();
41
+ if (!trimmed) {
42
+ prompt();
43
+ return;
44
+ }
45
+ if (trimmed.toLowerCase() === 'exit') {
46
+ console.log(chalk.dim('\nGoodbye!'));
47
+ rl.close();
48
+ return;
49
+ }
50
+ if (trimmed.toLowerCase() === 'clear') {
51
+ clearChatHistory();
52
+ console.log(chalk.yellow('Chat history cleared.'));
53
+ prompt();
54
+ return;
55
+ }
56
+ try {
57
+ // Get context from database
58
+ const context = await gatherContext(trimmed);
59
+ // Get chat history
60
+ const history = getChatHistory(10);
61
+ // Add user message to history
62
+ addChatMessage('user', trimmed);
63
+ // Generate response
64
+ console.log(chalk.dim('\nThinking...'));
65
+ const response = await generateResponse(trimmed, context, history);
66
+ // Add assistant response to history
67
+ addChatMessage('assistant', response);
68
+ console.log(chalk.green('\nSTAKEOUT: ') + response);
69
+ }
70
+ catch (error) {
71
+ console.error(chalk.red('\nError: ' + error.message));
72
+ }
73
+ prompt();
74
+ });
75
+ };
76
+ prompt();
77
+ }
78
+ async function gatherContext(query) {
79
+ const db = getDb();
80
+ // Get recent entries
81
+ const recentEntries = getEntries({ limit: 20 });
82
+ // Get stats
83
+ const total = db.prepare('SELECT COUNT(*) as count FROM entries').get().count;
84
+ const today = new Date();
85
+ today.setHours(0, 0, 0, 0);
86
+ const todayCount = db.prepare('SELECT COUNT(*) as count FROM entries WHERE timestamp >= ?').get(today.toISOString()).count;
87
+ const weekAgo = new Date();
88
+ weekAgo.setDate(weekAgo.getDate() - 7);
89
+ const weekCount = db.prepare('SELECT COUNT(*) as count FROM entries WHERE timestamp >= ?').get(weekAgo.toISOString()).count;
90
+ // Search for relevant entries based on query keywords
91
+ const keywords = query.toLowerCase().split(/\s+/).filter(w => w.length > 2);
92
+ let relevantEntries = [];
93
+ if (keywords.length > 0) {
94
+ const searchPattern = keywords.map(k => `%${k}%`);
95
+ const conditions = keywords.map(() => '(summary LIKE ? OR directories LIKE ? OR files_changed LIKE ?)').join(' OR ');
96
+ const params = searchPattern.flatMap(p => [p, p, p]);
97
+ relevantEntries = db.prepare(`
98
+ SELECT * FROM entries
99
+ WHERE ${conditions}
100
+ ORDER BY timestamp DESC
101
+ LIMIT 10
102
+ `).all(...params);
103
+ }
104
+ // Build context string
105
+ let context = `DATABASE STATS:\n`;
106
+ context += `- Total entries: ${total}\n`;
107
+ context += `- Today: ${todayCount}\n`;
108
+ context += `- This week: ${weekCount}\n\n`;
109
+ if (relevantEntries.length > 0) {
110
+ context += `RELEVANT ENTRIES (matching query keywords):\n`;
111
+ for (const entry of relevantEntries) {
112
+ const dirs = JSON.parse(entry.directories);
113
+ context += `\n[#${entry.id} - ${new Date(entry.timestamp).toLocaleDateString()}]\n`;
114
+ context += `Summary: ${entry.summary}\n`;
115
+ context += `Directories: ${dirs.join(', ')}\n`;
116
+ if (entry.commit_hash)
117
+ context += `Commit: ${entry.commit_hash.slice(0, 7)}\n`;
118
+ if (entry.tags && entry.tags !== '[]')
119
+ context += `Tags: ${entry.tags}\n`;
120
+ if (entry.is_breaking)
121
+ context += `⚠️ BREAKING CHANGE\n`;
122
+ }
123
+ }
124
+ context += `\nRECENT ENTRIES:\n`;
125
+ for (const entry of recentEntries.slice(0, 5)) {
126
+ context += `\n[#${entry.id} - ${new Date(entry.timestamp).toLocaleDateString()}]\n`;
127
+ context += `Summary: ${entry.summary}\n`;
128
+ context += `Directories: ${entry.directories.join(', ')}\n`;
129
+ }
130
+ return context;
131
+ }
132
+ async function generateResponse(query, context, history) {
133
+ const config = loadConfig();
134
+ const messages = [
135
+ { role: 'system', content: SYSTEM_PROMPT },
136
+ { role: 'system', content: `CONTEXT:\n${context}` },
137
+ ...history.map(h => ({ role: h.role, content: h.content })),
138
+ { role: 'user', content: query }
139
+ ];
140
+ if (config.llm_provider === 'ollama') {
141
+ const ollama = new Ollama({ host: config.ollama_host });
142
+ const response = await ollama.chat({
143
+ model: config.ollama_model,
144
+ messages,
145
+ options: { temperature: 0.7 }
146
+ });
147
+ return response.message.content.trim();
148
+ }
149
+ else {
150
+ if (!config.openai_api_key) {
151
+ throw new Error('OpenAI API key not configured');
152
+ }
153
+ const openai = new OpenAI({ apiKey: config.openai_api_key });
154
+ const response = await openai.chat.completions.create({
155
+ model: config.openai_model,
156
+ messages,
157
+ temperature: 0.7,
158
+ max_tokens: 1000
159
+ });
160
+ return response.choices[0]?.message?.content?.trim() ?? 'Unable to generate response';
161
+ }
162
+ }
@@ -0,0 +1,7 @@
1
+ interface ClearOptions {
2
+ before?: string;
3
+ all?: boolean;
4
+ yes?: boolean;
5
+ }
6
+ export declare function clear(options: ClearOptions): Promise<void>;
7
+ export {};
@@ -0,0 +1,89 @@
1
+ import chalk from 'chalk';
2
+ import { getDb } from '../lib/database.js';
3
+ import * as readline from 'readline';
4
+ export async function clear(options) {
5
+ const db = getDb();
6
+ let count;
7
+ let description;
8
+ if (options.all) {
9
+ const result = db.prepare('SELECT COUNT(*) as count FROM entries').get();
10
+ count = result.count;
11
+ description = 'ALL entries';
12
+ }
13
+ else if (options.before) {
14
+ const before = parseBefore(options.before);
15
+ const result = db.prepare('SELECT COUNT(*) as count FROM entries WHERE timestamp < ?').get(before);
16
+ count = result.count;
17
+ description = `entries before ${new Date(before).toLocaleDateString()}`;
18
+ }
19
+ else {
20
+ console.log(chalk.yellow('Please specify --all or --before <time>'));
21
+ console.log(chalk.dim('Examples:'));
22
+ console.log(chalk.dim(' stakeout clear --before "30 days"'));
23
+ console.log(chalk.dim(' stakeout clear --before "2024-01-01"'));
24
+ console.log(chalk.dim(' stakeout clear --all'));
25
+ return;
26
+ }
27
+ if (count === 0) {
28
+ console.log(chalk.yellow('No entries to delete.'));
29
+ return;
30
+ }
31
+ console.log(chalk.yellow(`\nThis will delete ${count} ${description}.`));
32
+ if (!options.yes) {
33
+ const confirmed = await confirm('Are you sure? (y/N) ');
34
+ if (!confirmed) {
35
+ console.log(chalk.dim('Cancelled.'));
36
+ return;
37
+ }
38
+ }
39
+ if (options.all) {
40
+ db.prepare('DELETE FROM entries').run();
41
+ }
42
+ else if (options.before) {
43
+ const before = parseBefore(options.before);
44
+ db.prepare('DELETE FROM entries WHERE timestamp < ?').run(before);
45
+ }
46
+ console.log(chalk.green(`✓ Deleted ${count} entries.`));
47
+ }
48
+ function parseBefore(before) {
49
+ // Try relative time first
50
+ const match = before.match(/^(\d+)\s*(day|days|week|weeks|month|months)$/i);
51
+ if (match) {
52
+ const now = new Date();
53
+ const amount = parseInt(match[1], 10);
54
+ const unit = match[2].toLowerCase();
55
+ switch (unit) {
56
+ case 'day':
57
+ case 'days':
58
+ now.setDate(now.getDate() - amount);
59
+ break;
60
+ case 'week':
61
+ case 'weeks':
62
+ now.setDate(now.getDate() - (amount * 7));
63
+ break;
64
+ case 'month':
65
+ case 'months':
66
+ now.setMonth(now.getMonth() - amount);
67
+ break;
68
+ }
69
+ return now.toISOString();
70
+ }
71
+ // Try parsing as date
72
+ const parsed = new Date(before);
73
+ if (!isNaN(parsed.getTime())) {
74
+ return parsed.toISOString();
75
+ }
76
+ throw new Error(`Invalid date format: ${before}`);
77
+ }
78
+ function confirm(prompt) {
79
+ const rl = readline.createInterface({
80
+ input: process.stdin,
81
+ output: process.stdout
82
+ });
83
+ return new Promise(resolve => {
84
+ rl.question(prompt, answer => {
85
+ rl.close();
86
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
87
+ });
88
+ });
89
+ }
@@ -0,0 +1,10 @@
1
+ interface ConfigOptions {
2
+ provider?: string;
3
+ ollamaModel?: string;
4
+ ollamaHost?: string;
5
+ openaiKey?: string;
6
+ openaiModel?: string;
7
+ show?: boolean;
8
+ }
9
+ export declare function config(options: ConfigOptions): Promise<void>;
10
+ export {};
@@ -0,0 +1,64 @@
1
+ import chalk from 'chalk';
2
+ import { loadConfig, saveConfig, getConfigPath } from '../lib/config.js';
3
+ export async function config(options) {
4
+ const currentConfig = loadConfig();
5
+ // If --show or no options, display current config
6
+ if (options.show || Object.keys(options).length === 0) {
7
+ console.log(chalk.bold('\n⚙️ STAKEOUT CONFIG\n'));
8
+ console.log(chalk.dim(`Location: ${getConfigPath()}\n`));
9
+ console.log(chalk.cyan('LLM Provider:'), currentConfig.llm_provider);
10
+ console.log('');
11
+ console.log(chalk.dim('Ollama:'));
12
+ console.log(` Model: ${currentConfig.ollama_model}`);
13
+ console.log(` Host: ${currentConfig.ollama_host}`);
14
+ console.log('');
15
+ console.log(chalk.dim('OpenAI:'));
16
+ console.log(` Model: ${currentConfig.openai_model}`);
17
+ console.log(` API Key: ${currentConfig.openai_api_key ? '****' + currentConfig.openai_api_key.slice(-4) : chalk.yellow('not set')}`);
18
+ console.log('');
19
+ console.log(chalk.dim('Ignore patterns:'));
20
+ currentConfig.ignore_patterns.slice(0, 5).forEach(p => {
21
+ console.log(` - ${p}`);
22
+ });
23
+ if (currentConfig.ignore_patterns.length > 5) {
24
+ console.log(chalk.dim(` ... and ${currentConfig.ignore_patterns.length - 5} more`));
25
+ }
26
+ console.log('');
27
+ return;
28
+ }
29
+ // Update config based on options
30
+ let updated = false;
31
+ if (options.provider) {
32
+ if (options.provider !== 'ollama' && options.provider !== 'openai') {
33
+ console.error(chalk.red('Provider must be "ollama" or "openai"'));
34
+ process.exit(1);
35
+ }
36
+ currentConfig.llm_provider = options.provider;
37
+ updated = true;
38
+ console.log(chalk.green(`✓ Provider set to: ${options.provider}`));
39
+ }
40
+ if (options.ollamaModel) {
41
+ currentConfig.ollama_model = options.ollamaModel;
42
+ updated = true;
43
+ console.log(chalk.green(`✓ Ollama model set to: ${options.ollamaModel}`));
44
+ }
45
+ if (options.ollamaHost) {
46
+ currentConfig.ollama_host = options.ollamaHost;
47
+ updated = true;
48
+ console.log(chalk.green(`✓ Ollama host set to: ${options.ollamaHost}`));
49
+ }
50
+ if (options.openaiKey) {
51
+ currentConfig.openai_api_key = options.openaiKey;
52
+ updated = true;
53
+ console.log(chalk.green(`✓ OpenAI API key set`));
54
+ }
55
+ if (options.openaiModel) {
56
+ currentConfig.openai_model = options.openaiModel;
57
+ updated = true;
58
+ console.log(chalk.green(`✓ OpenAI model set to: ${options.openaiModel}`));
59
+ }
60
+ if (updated) {
61
+ saveConfig(currentConfig);
62
+ console.log(chalk.dim('\nConfig saved.'));
63
+ }
64
+ }
@@ -0,0 +1,5 @@
1
+ interface DashboardOptions {
2
+ port?: string;
3
+ }
4
+ export declare function dashboard(options: DashboardOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,9 @@
1
+ import chalk from 'chalk';
2
+ import { createServer } from '../web/server.js';
3
+ export async function dashboard(options) {
4
+ const port = options.port ? parseInt(options.port, 10) : 3333;
5
+ console.log(chalk.dim('Starting STAKEOUT Command Center...'));
6
+ createServer(port);
7
+ console.log(chalk.green(`Dashboard: http://localhost:${port}`));
8
+ console.log(chalk.dim('\nPress Ctrl+C to stop'));
9
+ }
@@ -0,0 +1,6 @@
1
+ interface DigestOptions {
2
+ since?: string;
3
+ path?: string;
4
+ }
5
+ export declare function digest(options: DigestOptions): Promise<void>;
6
+ export {};