@theonlykaks/kaks 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/.env.example +2 -0
- package/LICENSE +21 -0
- package/cli.js +56 -0
- package/package.json +34 -0
- package/readme.md +88 -0
- package/src/commands/ask.js +81 -0
- package/src/commands/config.js +213 -0
- package/src/commands/explain.js +79 -0
- package/src/commands/go.js +51 -0
- package/src/commands/init.js +169 -0
- package/src/commands/open.js +61 -0
- package/src/commands/shared.js +679 -0
- package/src/commands/start.js +130 -0
- package/src/commands/summarize.js +231 -0
package/.env.example
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Arush Khasru
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/cli.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import 'dotenv/config';
|
|
4
|
+
import process from 'node:process';
|
|
5
|
+
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
|
|
8
|
+
import { registerAskCommand } from './src/commands/ask.js';
|
|
9
|
+
import { registerConfigCommand } from './src/commands/config.js';
|
|
10
|
+
import { registerExplainCommand } from './src/commands/explain.js';
|
|
11
|
+
import { registerGoCommand } from './src/commands/go.js';
|
|
12
|
+
import { registerInitCommand } from './src/commands/init.js';
|
|
13
|
+
import { registerOpenCommand } from './src/commands/open.js';
|
|
14
|
+
import { registerStartCommand } from './src/commands/start.js';
|
|
15
|
+
import { registerSummarizeCommand } from './src/commands/summarize.js';
|
|
16
|
+
import { CONFIG_PATH, pathExists } from './src/commands/shared.js';
|
|
17
|
+
|
|
18
|
+
const program = new Command();
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.name('kaks')
|
|
22
|
+
.description('AI-powered developer assistant and workspace launcher')
|
|
23
|
+
.version('0.0.1')
|
|
24
|
+
.showHelpAfterError()
|
|
25
|
+
.showSuggestionAfterError()
|
|
26
|
+
.addHelpText('after', `
|
|
27
|
+
|
|
28
|
+
Examples:
|
|
29
|
+
$ kaks init
|
|
30
|
+
$ kaks ask "How do I read a file async in Node.js?"
|
|
31
|
+
$ kaks explain package.json
|
|
32
|
+
$ kaks summarize app.log --tail 200
|
|
33
|
+
$ kaks open myapp
|
|
34
|
+
$ kaks start myapp
|
|
35
|
+
$ kaks go github.com
|
|
36
|
+
`);
|
|
37
|
+
|
|
38
|
+
registerAskCommand(program);
|
|
39
|
+
registerExplainCommand(program);
|
|
40
|
+
registerSummarizeCommand(program);
|
|
41
|
+
registerOpenCommand(program);
|
|
42
|
+
registerStartCommand(program);
|
|
43
|
+
registerGoCommand(program);
|
|
44
|
+
registerConfigCommand(program);
|
|
45
|
+
registerInitCommand(program);
|
|
46
|
+
|
|
47
|
+
if (process.argv.length <= 2) {
|
|
48
|
+
if (!(await pathExists(CONFIG_PATH))) {
|
|
49
|
+
console.log('Welcome to kaks-cli.');
|
|
50
|
+
console.log('It looks like this is your first time. Run: kaks init\n');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
program.outputHelp();
|
|
54
|
+
} else {
|
|
55
|
+
await program.parseAsync(process.argv);
|
|
56
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@theonlykaks/kaks",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Hello_to_the_cli",
|
|
5
|
+
"keywords": [],
|
|
6
|
+
"homepage": "https://github.com/ArushKhasru/kaks#readme",
|
|
7
|
+
"bugs": {
|
|
8
|
+
"url": "https://github.com/ArushKhasru/kaks/issues"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/ArushKhasru/kaks.git"
|
|
13
|
+
},
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"author": "kaks",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "index.js",
|
|
18
|
+
"bin": {
|
|
19
|
+
"kaks": "cli.js"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"axios": "^1.16.1",
|
|
26
|
+
"chalk": "^5.6.2",
|
|
27
|
+
"commander": "^14.0.3",
|
|
28
|
+
"dotenv": "^17.4.2",
|
|
29
|
+
"execa": "^9.6.1",
|
|
30
|
+
"inquirer": "^13.4.3",
|
|
31
|
+
"nodemon": "^3.1.14",
|
|
32
|
+
"ora": "^9.4.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# kaks CLI
|
|
2
|
+
|
|
3
|
+
AI-powered developer assistant and workspace launcher for Windows-first workflows.
|
|
4
|
+
|
|
5
|
+
**Status:** Work in progress. Core commands are implemented and usable.
|
|
6
|
+
|
|
7
|
+
## What works today
|
|
8
|
+
- `kaks init` interactive setup for provider, model, editor, and optional project registration
|
|
9
|
+
- `kaks ask` AI Q&A with optional clipboard copy
|
|
10
|
+
- `kaks explain` AI file explanations with detail/section controls
|
|
11
|
+
- `kaks summarize` log summarization with local JSON summary and optional AI insight
|
|
12
|
+
- `kaks open` open project editor, browser URLs, and file explorer
|
|
13
|
+
- `kaks start` run configured services concurrently (attached or detached)
|
|
14
|
+
- `kaks go` normalize/open/copy/print URLs quickly
|
|
15
|
+
- `kaks config` set/get/list/add-project/remove-project/edit global config
|
|
16
|
+
|
|
17
|
+
## Install (local dev)
|
|
18
|
+
```bash
|
|
19
|
+
git clone https://github.com/ArushKhasru/Kaks.git
|
|
20
|
+
cd kaks
|
|
21
|
+
npm install
|
|
22
|
+
npm link
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Then run:
|
|
26
|
+
```bash
|
|
27
|
+
kaks --help
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
You can also run directly:
|
|
31
|
+
```bash
|
|
32
|
+
node cli.js --help
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
```bash
|
|
37
|
+
kaks init
|
|
38
|
+
kaks ask "How do I read a file async in Node.js?"
|
|
39
|
+
kaks explain package.json
|
|
40
|
+
kaks summarize app.log --tail 200
|
|
41
|
+
kaks open myapp
|
|
42
|
+
kaks start myapp
|
|
43
|
+
kaks go github.com
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Configuration
|
|
47
|
+
Global config is stored at `~/.kaks/config.json`. Project-local config is `.kaks.json`.
|
|
48
|
+
|
|
49
|
+
Environment variables:
|
|
50
|
+
- `GEMINI_API_KEY`
|
|
51
|
+
- `OPENAI_API_KEY`
|
|
52
|
+
|
|
53
|
+
`kaks init` can also write the key into a local `.env` file.
|
|
54
|
+
|
|
55
|
+
Example config:
|
|
56
|
+
```jsonc
|
|
57
|
+
{
|
|
58
|
+
"ai": {
|
|
59
|
+
"provider": "gemini",
|
|
60
|
+
"model": "gemini-2.0-flash",
|
|
61
|
+
"temperature": 0.7,
|
|
62
|
+
"maxTokens": 2048
|
|
63
|
+
},
|
|
64
|
+
"projects": {
|
|
65
|
+
"myapp": {
|
|
66
|
+
"path": "D:\\projects\\myapp",
|
|
67
|
+
"browser": "http://localhost:3000",
|
|
68
|
+
"editor": "code",
|
|
69
|
+
"services": [
|
|
70
|
+
{ "name": "frontend", "cmd": "npm run dev", "cwd": "./client", "port": 3000 },
|
|
71
|
+
{ "name": "backend", "cmd": "npm run dev", "cwd": "./server", "port": 5000 }
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"defaults": {
|
|
76
|
+
"editor": "code",
|
|
77
|
+
"browser": "default",
|
|
78
|
+
"shell": "powershell"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`kaks ask` reads `ai.context` from `.kaks.json` to enrich prompts.
|
|
84
|
+
|
|
85
|
+
## AI providers
|
|
86
|
+
- **Gemini**: uses `GEMINI_API_KEY`
|
|
87
|
+
- **OpenAI**: uses `OPENAI_API_KEY`
|
|
88
|
+
- **Ollama**: uses `http://localhost:11434` with no API key
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
completeWithAi,
|
|
5
|
+
copyToClipboard,
|
|
6
|
+
handleCommandError,
|
|
7
|
+
loadGlobalConfig,
|
|
8
|
+
loadLocalConfig,
|
|
9
|
+
runWithSpinner,
|
|
10
|
+
} from './shared.js';
|
|
11
|
+
|
|
12
|
+
const SYSTEM_PROMPT = [
|
|
13
|
+
'You are a senior developer assistant.',
|
|
14
|
+
'Answer concisely, use markdown when helpful, and include code examples for coding questions.',
|
|
15
|
+
'Prefer practical, directly usable guidance.',
|
|
16
|
+
].join(' ');
|
|
17
|
+
|
|
18
|
+
export function registerAskCommand(program) {
|
|
19
|
+
program
|
|
20
|
+
.command('ask [question...]')
|
|
21
|
+
.description('Ask an AI-powered developer question')
|
|
22
|
+
.option('--model <model>', 'Override the configured model')
|
|
23
|
+
.option('--no-stream', 'Disable streaming output')
|
|
24
|
+
.option('--copy', 'Copy the answer to the clipboard')
|
|
25
|
+
.addHelpText('after', `
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
$ kaks ask "How do I read a file async in Node.js?"
|
|
29
|
+
$ kaks ask --model gpt-4o-mini "Explain Promise.allSettled"
|
|
30
|
+
`)
|
|
31
|
+
.action(async (questionParts = [], options) => {
|
|
32
|
+
try {
|
|
33
|
+
await ask(questionParts, options);
|
|
34
|
+
} catch (error) {
|
|
35
|
+
handleCommandError(error);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function ask(questionParts = [], options = {}) {
|
|
41
|
+
const question = await resolveQuestion(questionParts);
|
|
42
|
+
const config = await loadGlobalConfig();
|
|
43
|
+
const localConfig = await loadLocalConfig();
|
|
44
|
+
|
|
45
|
+
const context = localConfig?.ai?.context
|
|
46
|
+
? `\n\nProject context:\n${localConfig.ai.context}`
|
|
47
|
+
: '';
|
|
48
|
+
|
|
49
|
+
const answer = await runWithSpinner('Reading...', () => completeWithAi({
|
|
50
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
51
|
+
userPrompt: `${question}${context}`,
|
|
52
|
+
config,
|
|
53
|
+
model: options.model,
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
console.log(`\n${answer}\n`);
|
|
57
|
+
|
|
58
|
+
if (options.copy) {
|
|
59
|
+
await copyToClipboard(answer);
|
|
60
|
+
console.log('Copied answer to clipboard.');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function resolveQuestion(questionParts) {
|
|
65
|
+
const question = Array.isArray(questionParts) ? questionParts.join(' ').trim() : String(questionParts ?? '').trim();
|
|
66
|
+
|
|
67
|
+
if (question) {
|
|
68
|
+
return question;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const answer = await inquirer.prompt([
|
|
72
|
+
{
|
|
73
|
+
type: 'input',
|
|
74
|
+
name: 'question',
|
|
75
|
+
message: 'Question',
|
|
76
|
+
validate: (value) => Boolean(value.trim()) || 'Enter a question.',
|
|
77
|
+
},
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
return answer.question.trim();
|
|
81
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import process from 'node:process';
|
|
3
|
+
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
CONFIG_PATH,
|
|
8
|
+
CliError,
|
|
9
|
+
deleteByPath,
|
|
10
|
+
getByPath,
|
|
11
|
+
handleCommandError,
|
|
12
|
+
launchEditor,
|
|
13
|
+
loadGlobalConfig,
|
|
14
|
+
parseConfigValue,
|
|
15
|
+
resolveUserPath,
|
|
16
|
+
saveGlobalConfig,
|
|
17
|
+
setByPath,
|
|
18
|
+
validateConfigValue,
|
|
19
|
+
} from './shared.js';
|
|
20
|
+
|
|
21
|
+
export function registerConfigCommand(program) {
|
|
22
|
+
const configCommand = program
|
|
23
|
+
.command('config')
|
|
24
|
+
.description('Manage global kaks configuration');
|
|
25
|
+
|
|
26
|
+
configCommand
|
|
27
|
+
.command('set <key> <value>')
|
|
28
|
+
.description('Set a config value using dot notation')
|
|
29
|
+
.action(async (key, rawValue) => {
|
|
30
|
+
try {
|
|
31
|
+
await setConfig(key, rawValue);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
handleCommandError(error);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
configCommand
|
|
38
|
+
.command('get <key>')
|
|
39
|
+
.description('Read a config value using dot notation')
|
|
40
|
+
.action(async (key) => {
|
|
41
|
+
try {
|
|
42
|
+
await getConfig(key);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
handleCommandError(error);
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
configCommand
|
|
49
|
+
.command('list')
|
|
50
|
+
.description('Print the full global configuration')
|
|
51
|
+
.action(async () => {
|
|
52
|
+
try {
|
|
53
|
+
await listConfig();
|
|
54
|
+
} catch (error) {
|
|
55
|
+
handleCommandError(error);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
configCommand
|
|
60
|
+
.command('add-project <name>')
|
|
61
|
+
.description('Register a project preset')
|
|
62
|
+
.option('--path <path>', 'Project root path')
|
|
63
|
+
.option('--browser <url>', 'Default URL to open for this project')
|
|
64
|
+
.option('--editor <editor>', 'Editor command for this project')
|
|
65
|
+
.action(async (name, options) => {
|
|
66
|
+
try {
|
|
67
|
+
await addProject(name, options);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
handleCommandError(error);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
configCommand
|
|
74
|
+
.command('remove-project <name>')
|
|
75
|
+
.description('Remove a project preset')
|
|
76
|
+
.option('--yes', 'Skip confirmation')
|
|
77
|
+
.action(async (name, options) => {
|
|
78
|
+
try {
|
|
79
|
+
await removeProject(name, options);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
handleCommandError(error);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
configCommand
|
|
86
|
+
.command('edit')
|
|
87
|
+
.description('Open the global config file in your editor')
|
|
88
|
+
.action(async () => {
|
|
89
|
+
try {
|
|
90
|
+
await editConfig();
|
|
91
|
+
} catch (error) {
|
|
92
|
+
handleCommandError(error);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function setConfig(key, rawValue) {
|
|
98
|
+
const value = parseConfigValue(rawValue);
|
|
99
|
+
validateConfigValue(key, value);
|
|
100
|
+
|
|
101
|
+
const config = await loadGlobalConfig();
|
|
102
|
+
setByPath(config, key, value);
|
|
103
|
+
await saveGlobalConfig(config);
|
|
104
|
+
console.log(`Set ${key}.`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function getConfig(key) {
|
|
108
|
+
const config = await loadGlobalConfig();
|
|
109
|
+
const value = getByPath(config, key);
|
|
110
|
+
|
|
111
|
+
if (value === undefined) {
|
|
112
|
+
throw new CliError(`Config key not found: ${key}`);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
console.log(typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export async function listConfig() {
|
|
119
|
+
const config = await loadGlobalConfig();
|
|
120
|
+
console.log(JSON.stringify(config, null, 2));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function addProject(name, options = {}) {
|
|
124
|
+
const config = await loadGlobalConfig();
|
|
125
|
+
const answers = await promptForMissingProjectFields(name, options);
|
|
126
|
+
const projectPath = resolveUserPath(answers.path);
|
|
127
|
+
|
|
128
|
+
config.projects ??= {};
|
|
129
|
+
config.projects[name] = {
|
|
130
|
+
path: projectPath,
|
|
131
|
+
browser: answers.browser || undefined,
|
|
132
|
+
editor: answers.editor || undefined,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
await saveGlobalConfig(config);
|
|
136
|
+
console.log(`Added project "${name}" -> ${projectPath}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function removeProject(name, options = {}) {
|
|
140
|
+
const config = await loadGlobalConfig();
|
|
141
|
+
|
|
142
|
+
if (!config.projects?.[name]) {
|
|
143
|
+
throw new CliError(`Project not found: ${name}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!options.yes) {
|
|
147
|
+
const { confirmed } = await inquirer.prompt([
|
|
148
|
+
{
|
|
149
|
+
type: 'confirm',
|
|
150
|
+
name: 'confirmed',
|
|
151
|
+
message: `Remove project "${name}"?`,
|
|
152
|
+
default: false,
|
|
153
|
+
},
|
|
154
|
+
]);
|
|
155
|
+
|
|
156
|
+
if (!confirmed) {
|
|
157
|
+
console.log('Canceled.');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
deleteByPath(config, `projects.${name}`);
|
|
163
|
+
await saveGlobalConfig(config);
|
|
164
|
+
console.log(`Removed project "${name}".`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function editConfig() {
|
|
168
|
+
const config = await loadGlobalConfig();
|
|
169
|
+
await saveGlobalConfig(config);
|
|
170
|
+
await launchEditor(config.defaults?.editor ?? 'code', CONFIG_PATH);
|
|
171
|
+
console.log(`Opening ${CONFIG_PATH}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function promptForMissingProjectFields(name, options) {
|
|
175
|
+
const questions = [];
|
|
176
|
+
|
|
177
|
+
if (!options.path) {
|
|
178
|
+
questions.push({
|
|
179
|
+
type: 'input',
|
|
180
|
+
name: 'path',
|
|
181
|
+
message: `Path for ${name}`,
|
|
182
|
+
default: process.cwd(),
|
|
183
|
+
filter: (value) => path.resolve(value),
|
|
184
|
+
validate: (value) => Boolean(value.trim()) || 'Enter a project path.',
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!options.browser) {
|
|
189
|
+
questions.push({
|
|
190
|
+
type: 'input',
|
|
191
|
+
name: 'browser',
|
|
192
|
+
message: 'Default browser URL',
|
|
193
|
+
default: '',
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!options.editor) {
|
|
198
|
+
questions.push({
|
|
199
|
+
type: 'input',
|
|
200
|
+
name: 'editor',
|
|
201
|
+
message: 'Editor command',
|
|
202
|
+
default: '',
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const answers = questions.length ? await inquirer.prompt(questions) : {};
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
path: options.path ?? answers.path,
|
|
210
|
+
browser: options.browser ?? answers.browser,
|
|
211
|
+
editor: options.editor ?? answers.editor,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { CliError, completeWithAi, detectLanguage, handleCommandError, loadGlobalConfig, readTextFileWithLimits, runWithSpinner } from './shared.js';
|
|
2
|
+
|
|
3
|
+
const SYSTEM_PROMPT = [
|
|
4
|
+
'You are a code and configuration file explainer.',
|
|
5
|
+
'Explain the file in plain English for a developer.',
|
|
6
|
+
'Cover purpose, structure, important sections, and any risks or noteworthy details.',
|
|
7
|
+
].join(' ');
|
|
8
|
+
|
|
9
|
+
const VALID_DETAIL_LEVELS = new Set(['low', 'medium', 'high']);
|
|
10
|
+
|
|
11
|
+
export function registerExplainCommand(program) {
|
|
12
|
+
program
|
|
13
|
+
.command('explain <filepath>')
|
|
14
|
+
.description('Explain a source, config, or text file with AI')
|
|
15
|
+
.option('--detail <level>', 'Depth of explanation: low, medium, or high', 'medium')
|
|
16
|
+
.option('--section <name>', 'Explain only a named section or topic')
|
|
17
|
+
.option('--model <model>', 'Override the configured model')
|
|
18
|
+
.addHelpText('after', `
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
$ kaks explain docker-compose.yml
|
|
22
|
+
$ kaks explain package.json --detail high
|
|
23
|
+
$ kaks explain src/app.js --section middleware
|
|
24
|
+
`)
|
|
25
|
+
.action(async (filepath, options) => {
|
|
26
|
+
try {
|
|
27
|
+
await explain(filepath, options);
|
|
28
|
+
} catch (error) {
|
|
29
|
+
handleCommandError(error);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export async function explain(filepath, options = {}) {
|
|
35
|
+
const detail = String(options.detail ?? 'medium').toLowerCase();
|
|
36
|
+
if (!VALID_DETAIL_LEVELS.has(detail)) {
|
|
37
|
+
throw new CliError('Invalid detail level. Use one of: low, medium, high.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const file = await readTextFileWithLimits(filepath);
|
|
41
|
+
|
|
42
|
+
if (!file.content.trim()) {
|
|
43
|
+
throw new CliError(`File is empty: ${filepath}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (file.warned) {
|
|
47
|
+
console.warn(`Large file: ${file.displayPath} (${file.size} bytes).`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (file.truncated) {
|
|
51
|
+
console.warn('File exceeds 500KB, so only the first 500KB will be explained.');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const config = await loadGlobalConfig();
|
|
55
|
+
const language = detectLanguage(file.absolutePath);
|
|
56
|
+
const section = options.section ? `\nFocus only on this section or topic: ${options.section}` : '';
|
|
57
|
+
|
|
58
|
+
const prompt = [
|
|
59
|
+
`File: ${file.displayPath}`,
|
|
60
|
+
`Detected format: ${language}`,
|
|
61
|
+
`Detail level: ${detail}`,
|
|
62
|
+
section,
|
|
63
|
+
'',
|
|
64
|
+
'Content:',
|
|
65
|
+
`\`\`\`${language}`,
|
|
66
|
+
file.content,
|
|
67
|
+
'```',
|
|
68
|
+
].filter(Boolean).join('\n');
|
|
69
|
+
|
|
70
|
+
const explanation = await runWithSpinner(`Reading ${file.displayPath}...`, () => completeWithAi({
|
|
71
|
+
systemPrompt: SYSTEM_PROMPT,
|
|
72
|
+
userPrompt: prompt,
|
|
73
|
+
config,
|
|
74
|
+
model: options.model,
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
console.log(`\nFile Explanation: ${file.displayPath}\n`);
|
|
78
|
+
console.log(`${explanation}\n`);
|
|
79
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertValidUrl,
|
|
3
|
+
copyToClipboard,
|
|
4
|
+
handleCommandError,
|
|
5
|
+
normalizeUrl,
|
|
6
|
+
openTarget,
|
|
7
|
+
} from './shared.js';
|
|
8
|
+
|
|
9
|
+
export function registerGoCommand(program) {
|
|
10
|
+
program
|
|
11
|
+
.command('go <url>')
|
|
12
|
+
.description('Normalize and open a URL quickly')
|
|
13
|
+
.option('--browser <name>', 'Browser override: chrome, firefox, edge, or default', 'default')
|
|
14
|
+
.option('--copy', 'Copy the normalized URL instead of opening it')
|
|
15
|
+
.option('--print', 'Print the normalized URL instead of opening it')
|
|
16
|
+
.addHelpText('after', `
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
$ kaks go github.com
|
|
20
|
+
$ kaks go github.com/kaksKhasru/kaks
|
|
21
|
+
$ kaks go localhost:3000
|
|
22
|
+
$ kaks go docs.google.com --copy
|
|
23
|
+
`)
|
|
24
|
+
.action(async (url, options) => {
|
|
25
|
+
try {
|
|
26
|
+
await go(url, options);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
handleCommandError(error);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export async function go(url, options = {}) {
|
|
34
|
+
const normalizedUrl = normalizeUrl(url);
|
|
35
|
+
assertValidUrl(normalizedUrl);
|
|
36
|
+
|
|
37
|
+
if (options.print) {
|
|
38
|
+
console.log(normalizedUrl);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (options.copy) {
|
|
43
|
+
await copyToClipboard(normalizedUrl);
|
|
44
|
+
console.log(`Copied to clipboard: ${normalizedUrl}`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
await openTarget(normalizedUrl, options.browser);
|
|
49
|
+
const suffix = options.browser && options.browser !== 'default' ? ` (${options.browser})` : '';
|
|
50
|
+
console.log(`Opening ${normalizedUrl}${suffix}`);
|
|
51
|
+
}
|