@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.
@@ -0,0 +1,169 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import process from 'node:process';
4
+
5
+ import inquirer from 'inquirer';
6
+
7
+ import {
8
+ CONFIG_PATH,
9
+ getDefaultConfig,
10
+ handleCommandError,
11
+ pathExists,
12
+ resolveUserPath,
13
+ saveGlobalConfig,
14
+ } from './shared.js';
15
+
16
+ const PROVIDERS = {
17
+ gemini: { label: 'Gemini', envKey: 'GEMINI_API_KEY', model: 'gemini-2.0-flash' },
18
+ openai: { label: 'OpenAI', envKey: 'OPENAI_API_KEY', model: 'gpt-4o-mini' },
19
+ ollama: { label: 'Ollama', envKey: 'LLAMA_API_KEY', model: 'llama3.1' },
20
+ };
21
+
22
+ export function registerInitCommand(program) {
23
+ program
24
+ .command('init')
25
+ .description('Run first-time kaks setup')
26
+ .option('--force', 'Overwrite existing global kaks config')
27
+ .addHelpText('after', `
28
+
29
+ Examples:
30
+ $ kaks init
31
+ $ kaks init --force
32
+ `)
33
+ .action(async (options) => {
34
+ try {
35
+ await init(options);
36
+ } catch (error) {
37
+ handleCommandError(error);
38
+ }
39
+ });
40
+ }
41
+
42
+ export async function init(options = {}) {
43
+ console.log('kaks-cli setup\n');
44
+
45
+ if (await pathExists(CONFIG_PATH)) {
46
+ const shouldContinue = options.force || (await inquirer.prompt([
47
+ {
48
+ type: 'confirm',
49
+ name: 'overwrite',
50
+ message: `Config already exists at ${CONFIG_PATH}. Overwrite it?`,
51
+ default: false,
52
+ },
53
+ ])).overwrite;
54
+
55
+ if (!shouldContinue) {
56
+ console.log('Canceled.');
57
+ return;
58
+ }
59
+ }
60
+
61
+ const providerAnswer = await inquirer.prompt([
62
+ {
63
+ type: 'list',
64
+ name: 'provider',
65
+ message: 'AI provider',
66
+ choices: Object.entries(PROVIDERS).map(([value, provider]) => ({
67
+ name: provider.label,
68
+ value,
69
+ })),
70
+ default: 'gemini',
71
+ },
72
+ ]);
73
+
74
+ const provider = PROVIDERS[providerAnswer.provider];
75
+ const setupAnswers = await inquirer.prompt([
76
+ {
77
+ type: 'input',
78
+ name: 'model',
79
+ message: 'Model',
80
+ default: provider.model,
81
+ },
82
+ {
83
+ type: 'password',
84
+ name: 'apiKey',
85
+ message: provider.envKey ? `${provider.envKey} (leave blank to use an existing environment variable)` : 'Ollama does not require an API key. Press enter.',
86
+ mask: '*',
87
+ when: () => Boolean(provider.envKey),
88
+ },
89
+ {
90
+ type: 'input',
91
+ name: 'editor',
92
+ message: 'Default editor command',
93
+ default: 'code',
94
+ },
95
+ {
96
+ type: 'confirm',
97
+ name: 'addProject',
98
+ message: 'Register the current directory as a project?',
99
+ default: true,
100
+ },
101
+ {
102
+ type: 'input',
103
+ name: 'projectName',
104
+ message: 'Project name',
105
+ default: path.basename(process.cwd()),
106
+ when: (answers) => answers.addProject,
107
+ validate: (value) => Boolean(value.trim()) || 'Enter a project name.',
108
+ },
109
+ {
110
+ type: 'input',
111
+ name: 'projectUrl',
112
+ message: 'Project browser URL',
113
+ default: '',
114
+ when: (answers) => answers.addProject,
115
+ },
116
+ ]);
117
+
118
+ const config = getDefaultConfig();
119
+ config.ai.provider = providerAnswer.provider;
120
+ config.ai.model = setupAnswers.model;
121
+ config.defaults.editor = setupAnswers.editor;
122
+
123
+ if (provider.envKey) {
124
+ config.ai.apiKeyEnv = provider.envKey;
125
+ }
126
+
127
+ if (setupAnswers.addProject) {
128
+ config.projects[setupAnswers.projectName] = {
129
+ path: resolveUserPath(process.cwd()),
130
+ browser: setupAnswers.projectUrl || undefined,
131
+ editor: setupAnswers.editor,
132
+ };
133
+ }
134
+
135
+ await saveGlobalConfig(config);
136
+
137
+ if (provider.envKey && setupAnswers.apiKey) {
138
+ await upsertEnvValue(path.join(process.cwd(), '.env'), provider.envKey, setupAnswers.apiKey);
139
+ }
140
+
141
+ console.log(`\nWrote ${CONFIG_PATH}`);
142
+ if (provider.envKey && setupAnswers.apiKey) {
143
+ console.log(`Updated .env with ${provider.envKey}.`);
144
+ }
145
+ console.log('Setup complete. Try: kaks ask "What can you do?"');
146
+ }
147
+
148
+ async function upsertEnvValue(envPath, key, value) {
149
+ let current = '';
150
+ try {
151
+ current = await fs.readFile(envPath, 'utf8');
152
+ } catch (error) {
153
+ if (error.code !== 'ENOENT') {
154
+ throw error;
155
+ }
156
+ }
157
+
158
+ const line = `${key}=${value}`;
159
+ const pattern = new RegExp(`^${escapeRegExp(key)}=.*$`, 'm');
160
+ const next = pattern.test(current)
161
+ ? current.replace(pattern, line)
162
+ : `${current.trimEnd()}${current.trim() ? '\n' : ''}${line}\n`;
163
+
164
+ await fs.writeFile(envPath, next, 'utf8');
165
+ }
166
+
167
+ function escapeRegExp(value) {
168
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
169
+ }
@@ -0,0 +1,61 @@
1
+ import {
2
+ getProjectOpenUrls,
3
+ handleCommandError,
4
+ launchEditor,
5
+ launchExplorer,
6
+ loadGlobalConfig,
7
+ normalizeUrl,
8
+ openTarget,
9
+ resolveProject,
10
+ } from './shared.js';
11
+
12
+ export function registerOpenCommand(program) {
13
+ program
14
+ .command('open [projectName]')
15
+ .description('Open a configured project in editor, browser, and file explorer')
16
+ .option('--no-browser', 'Skip opening browser URLs')
17
+ .option('--no-editor', 'Skip opening the editor')
18
+ .option('--no-explorer', 'Skip opening the file explorer')
19
+ .addHelpText('after', `
20
+
21
+ Examples:
22
+ $ kaks open myapp
23
+ $ kaks open myapp --no-explorer
24
+ `)
25
+ .action(async (projectName, options) => {
26
+ try {
27
+ await openProject(projectName, options);
28
+ } catch (error) {
29
+ handleCommandError(error);
30
+ }
31
+ });
32
+ }
33
+
34
+ export async function openProject(projectName, options = {}) {
35
+ const config = await loadGlobalConfig();
36
+ const resolved = await resolveProject(config, projectName);
37
+ const { name, project } = resolved;
38
+
39
+ console.log(`Opening project: ${name}`);
40
+
41
+ if (options.editor !== false) {
42
+ await launchEditor(project.editor, project.path);
43
+ console.log(`OK Editor -> ${project.path}`);
44
+ }
45
+
46
+ if (options.browser !== false) {
47
+ const urls = getProjectOpenUrls(project);
48
+ for (const url of urls) {
49
+ const normalizedUrl = normalizeUrl(url);
50
+ await openTarget(normalizedUrl, project.browserCommand);
51
+ console.log(`OK Browser -> ${normalizedUrl}`);
52
+ }
53
+ }
54
+
55
+ if (options.explorer !== false) {
56
+ await launchExplorer(project.path);
57
+ console.log(`OK File Explorer -> ${project.path}`);
58
+ }
59
+
60
+ console.log(`\nProject "${name}" is ready.`);
61
+ }