@chakresh/kresh 0.1.2 → 0.1.4

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/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "@chakresh/kresh",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "bin": {
5
5
  "kresh": "./src/index.js"
6
6
  },
7
7
  "type": "module",
8
8
  "dependencies": {
9
- "@chakresh/kresh": "^0.1.1",
10
9
  "axios": "^1.6.8",
11
10
  "chalk": "^5.3.0",
12
11
  "commander": "^11.1.0",
@@ -8,7 +8,9 @@ import { logger } from '../utils/logger.js';
8
8
  /**
9
9
  * Installs a skill from the registry locally.
10
10
  */
11
- export async function installSkill(skillSlug) {
11
+ import { cliAuthFlow, getToken } from '../services/auth.js';
12
+
13
+ export async function installSkill(skillSlug, isRetry = false) {
12
14
  const spinner = ora(`Fetching skill "${skillSlug}"...`).start();
13
15
  try {
14
16
  const response = await api.get(`/api/skills/${skillSlug}`);
@@ -20,15 +22,32 @@ export async function installSkill(skillSlug) {
20
22
  spinner.succeed(`Successfully installed ${logger.bold(metadata.name)} (v${metadata.currentVersion}) by @${metadata.ownerUsername}`);
21
23
  logger.info(`Saved to: ${logger.bold(savedDir)}`);
22
24
  } catch (error) {
23
- spinner.fail(`Installation failed for "${skillSlug}"`);
24
- if (error.response) {
25
- if (error.response.status === 404) {
26
- logger.error(`Skill "${skillSlug}" was not found in the registry.`);
25
+ spinner.stop();
26
+ if (error.response && error.response.status === 404 && !isRetry) {
27
+ // It might be a private skill, let's try authenticating
28
+ logger.info(`Skill "${skillSlug}" not found publicly. This might be a private skill.`);
29
+
30
+ const hasToken = getToken();
31
+ if (!hasToken) {
32
+ logger.info('Attempting to authenticate via browser to access private skills...');
33
+ try {
34
+ await cliAuthFlow();
35
+ // Retry the installation now that we have a token
36
+ logger.info('Authentication successful. Retrying download...');
37
+ return await installSkill(skillSlug, true);
38
+ } catch (authErr) {
39
+ logger.error('Authentication failed: ' + authErr.message);
40
+ }
27
41
  } else {
28
- logger.error(`Registry error: ${error.response.data?.error || error.response.statusText}`);
42
+ logger.error(`Skill "${skillSlug}" was not found in the registry, even with your authenticated session.`);
29
43
  }
30
44
  } else {
31
- logger.error(`Connection error: ${error.message}`);
45
+ spinner.fail(`Installation failed for "${skillSlug}"`);
46
+ if (error.response) {
47
+ logger.error(`Registry error: ${error.response.data?.error || error.response.statusText}`);
48
+ } else {
49
+ logger.error(`Connection error: ${error.message}`);
50
+ }
32
51
  }
33
52
  }
34
53
  }
@@ -1,6 +1,7 @@
1
1
  import axios from 'axios';
2
+ import { getToken } from './auth.js';
2
3
 
3
- const baseURL = process.env.KRESH_API_URL || 'https://kresh.vercel.app';
4
+ const baseURL = process.env.KRESH_API_URL || 'http://localhost:3000'; // Assume localhost for local testing
4
5
 
5
6
  export const api = axios.create({
6
7
  baseURL,
@@ -10,3 +11,12 @@ export const api = axios.create({
10
11
  'Content-Type': 'application/json'
11
12
  }
12
13
  });
14
+
15
+ api.interceptors.request.use((config) => {
16
+ const token = getToken();
17
+ if (token) {
18
+ config.headers.Authorization = `Bearer ${token}`;
19
+ }
20
+ return config;
21
+ });
22
+
@@ -0,0 +1,96 @@
1
+ import http from 'http';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { exec } from 'child_process';
6
+ import { logger } from '../utils/logger.js';
7
+ import ora from 'ora';
8
+
9
+ const KRESH_DIR = path.join(os.homedir(), '.kresh');
10
+ const CONFIG_FILE = path.join(KRESH_DIR, 'config.json');
11
+
12
+ export function getToken() {
13
+ try {
14
+ if (fs.existsSync(CONFIG_FILE)) {
15
+ const config = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8'));
16
+ return config.token || null;
17
+ }
18
+ } catch (err) {
19
+ // ignore
20
+ }
21
+ return null;
22
+ }
23
+
24
+ export function setToken(token) {
25
+ try {
26
+ if (!fs.existsSync(KRESH_DIR)) {
27
+ fs.mkdirSync(KRESH_DIR, { recursive: true });
28
+ }
29
+ const config = fs.existsSync(CONFIG_FILE) ? JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf-8')) : {};
30
+ config.token = token;
31
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');
32
+ } catch (err) {
33
+ logger.error('Failed to save config: ' + err.message);
34
+ }
35
+ }
36
+
37
+ function openBrowser(url) {
38
+ const startCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
39
+ exec(`${startCmd} "${url}"`, (err) => {
40
+ if (err) {
41
+ logger.warn(`Could not open browser automatically. Please visit: ${url}`);
42
+ }
43
+ });
44
+ }
45
+
46
+ export async function cliAuthFlow() {
47
+ return new Promise((resolve, reject) => {
48
+ const server = http.createServer((req, res) => {
49
+ const url = new URL(req.url, `http://${req.headers.host}`);
50
+ if (url.pathname === '/callback') {
51
+ const token = url.searchParams.get('token');
52
+ if (token) {
53
+ res.writeHead(200, { 'Content-Type': 'text/html' });
54
+ res.end(`
55
+ <html>
56
+ <head><title>Kresh CLI Authenticated</title></head>
57
+ <body style="font-family: monospace; padding: 2rem; text-align: center; background: #000; color: #fff;">
58
+ <h2 style="color: #4ade80;">Success!</h2>
59
+ <p>The Kresh CLI has been authenticated. You can close this window and return to your terminal.</p>
60
+ </body>
61
+ </html>
62
+ `);
63
+
64
+ setToken(token);
65
+
66
+ setTimeout(() => {
67
+ server.close();
68
+ resolve(token);
69
+ }, 500);
70
+ } else {
71
+ res.writeHead(400, { 'Content-Type': 'text/plain' });
72
+ res.end('Missing token in callback.');
73
+ server.close();
74
+ reject(new Error('Missing token'));
75
+ }
76
+ } else {
77
+ res.writeHead(404);
78
+ res.end();
79
+ }
80
+ });
81
+
82
+ server.listen(0, '127.0.0.1', () => {
83
+ const port = server.address().port;
84
+ const baseUrl = process.env.KRESH_API_URL || 'https://kresh.vercel.app';
85
+ const authUrl = `${baseUrl}/cli/auth?port=${port}`;
86
+
87
+ const spinner = ora('Opening browser for authentication...').start();
88
+ openBrowser(authUrl);
89
+ spinner.succeed(`Waiting for authentication... (if browser doesn't open, visit ${authUrl})`);
90
+ });
91
+
92
+ server.on('error', (err) => {
93
+ reject(err);
94
+ });
95
+ });
96
+ }
@@ -2,13 +2,59 @@ import fs from 'fs/promises';
2
2
  import path from 'path';
3
3
 
4
4
  /**
5
- * Writes the SKILL.md and metadata.json files directly in a folder named after the skill's slug inside the skills folder.
5
+ * Helper to find the workspace or project root directory by scanning up.
6
+ */
7
+ async function getWorkspaceRoot(startDir = process.cwd()) {
8
+ let currentDir = startDir;
9
+ while (true) {
10
+ try {
11
+ const hasPackageJson = await fs.access(path.join(currentDir, 'package.json')).then(() => true).catch(() => false);
12
+ const isKreshRoot = await fs.access(path.join(currentDir, 'next.config.mjs')).then(() => true).catch(() => false);
13
+
14
+ if (hasPackageJson && isKreshRoot) {
15
+ return currentDir;
16
+ }
17
+ } catch (e) {
18
+ // Ignore
19
+ }
20
+
21
+ const parentDir = path.dirname(currentDir);
22
+ if (parentDir === currentDir) {
23
+ break;
24
+ }
25
+ currentDir = parentDir;
26
+ }
27
+
28
+ // Secondary check: look for any package.json up the tree
29
+ currentDir = startDir;
30
+ while (true) {
31
+ try {
32
+ const hasPackageJson = await fs.access(path.join(currentDir, 'package.json')).then(() => true).catch(() => false);
33
+ if (hasPackageJson) {
34
+ return currentDir;
35
+ }
36
+ } catch (e) {
37
+ // Ignore
38
+ }
39
+ const parentDir = path.dirname(currentDir);
40
+ if (parentDir === currentDir) {
41
+ break;
42
+ }
43
+ currentDir = parentDir;
44
+ }
45
+
46
+ return startDir;
47
+ }
48
+
49
+ /**
50
+ * Writes the SKILL.md and metadata.json files directly in a folder named after the skill's slug (excluding @owner scope folder) inside the skills folder.
6
51
  */
7
52
  export async function writeLocalSkill(slug, skillContent, metadata) {
8
53
  try {
9
- // Sanitize folder name, use slug
10
- const folderName = slug || metadata.slug;
11
- const targetDir = path.join(process.cwd(), 'skills', folderName);
54
+ const rootDir = await getWorkspaceRoot();
55
+ const folderName = (slug || metadata.slug).split('/').pop();
56
+ const targetDir = path.join(rootDir, 'skills', folderName);
57
+
12
58
  await fs.mkdir(targetDir, { recursive: true });
13
59
  await fs.writeFile(path.join(targetDir, 'SKILL.md'), skillContent, 'utf8');
14
60
  await fs.writeFile(path.join(targetDir, 'metadata.json'), JSON.stringify(metadata, null, 2), 'utf8');
@@ -24,11 +70,21 @@ export async function writeLocalSkill(slug, skillContent, metadata) {
24
70
  */
25
71
  export async function readLocalSkillMetadata(slug) {
26
72
  try {
27
- const metadataPath = path.join(process.cwd(), 'skills', slug, 'metadata.json');
73
+ const rootDir = await getWorkspaceRoot();
74
+ const folderName = slug.split('/').pop();
75
+ const metadataPath = path.join(rootDir, 'skills', folderName, 'metadata.json');
28
76
  const data = await fs.readFile(metadataPath, 'utf8');
29
77
  return JSON.parse(data);
30
78
  } catch (error) {
31
- return null;
79
+ // Also try legacy path for backwards compatibility
80
+ try {
81
+ const rootDir = await getWorkspaceRoot();
82
+ const legacyPath = path.join(rootDir, 'skills', slug, 'metadata.json');
83
+ const data = await fs.readFile(legacyPath, 'utf8');
84
+ return JSON.parse(data);
85
+ } catch (e) {
86
+ return null;
87
+ }
32
88
  }
33
89
  }
34
90
 
@@ -37,20 +93,26 @@ export async function readLocalSkillMetadata(slug) {
37
93
  */
38
94
  export async function removeLocalSkill(slug) {
39
95
  try {
40
- const targetDir = path.join(process.cwd(), 'skills', slug);
96
+ const rootDir = await getWorkspaceRoot();
97
+ const folderName = slug.split('/').pop();
98
+ const targetDir = path.join(rootDir, 'skills', folderName);
41
99
  await fs.rm(targetDir, { recursive: true, force: true });
42
100
 
101
+ // Also clean up legacy directory if it exists
102
+ const legacyDir = path.join(rootDir, 'skills', slug);
103
+ await fs.rm(legacyDir, { recursive: true, force: true });
104
+
43
105
  // Clean up empty parent directory (e.g. @username) if it becomes empty
44
106
  if (slug.includes('/')) {
45
107
  const parts = slug.split('/');
46
- const parentDir = path.join(process.cwd(), 'skills', parts[0]);
108
+ const parentDir = path.join(rootDir, 'skills', parts[0]);
47
109
  try {
48
110
  const files = await fs.readdir(parentDir);
49
111
  if (files.length === 0) {
50
112
  await fs.rmdir(parentDir);
51
113
  }
52
114
  } catch (e) {
53
- // Ignore errors if directory is not empty or doesn't exist
115
+ // Ignore
54
116
  }
55
117
  }
56
118
  return true;
@@ -65,7 +127,8 @@ export async function removeLocalSkill(slug) {
65
127
  */
66
128
  export async function listLocalSkills() {
67
129
  try {
68
- const skillsDir = path.join(process.cwd(), 'skills');
130
+ const rootDir = await getWorkspaceRoot();
131
+ const skillsDir = path.join(rootDir, 'skills');
69
132
  let entries = [];
70
133
  try {
71
134
  entries = await fs.readdir(skillsDir, { withFileTypes: true });
@@ -78,7 +141,7 @@ export async function listLocalSkills() {
78
141
  for (const entry of entries) {
79
142
  if (entry.isDirectory()) {
80
143
  if (entry.name.startsWith('@')) {
81
- // It's a user scope directory, scan subdirectories inside it
144
+ // Legacy support: It's a user scope directory, scan subdirectories inside it
82
145
  const scopeDir = path.join(skillsDir, entry.name);
83
146
  let subEntries = [];
84
147
  try {
@@ -103,16 +166,22 @@ export async function listLocalSkills() {
103
166
  }
104
167
  }
105
168
  } else {
106
- // Standard/global skill directory
107
- const metadata = await readLocalSkillMetadata(entry.name);
108
- if (metadata) {
109
- skills.push({
110
- slug: metadata.slug || entry.name,
111
- installed: true,
112
- version: metadata.version || metadata.currentVersion || 'unknown',
113
- name: metadata.name || 'unknown',
114
- description: metadata.description || ''
115
- });
169
+ // New format: Directly under skills/
170
+ try {
171
+ const metadataPath = path.join(skillsDir, entry.name, 'metadata.json');
172
+ const data = await fs.readFile(metadataPath, 'utf8');
173
+ const metadata = JSON.parse(data);
174
+ if (metadata) {
175
+ skills.push({
176
+ slug: metadata.slug || entry.name,
177
+ installed: true,
178
+ version: metadata.version || metadata.currentVersion || 'unknown',
179
+ name: metadata.name || 'unknown',
180
+ description: metadata.description || ''
181
+ });
182
+ }
183
+ } catch (e) {
184
+ // Ignore if metadata is invalid/missing
116
185
  }
117
186
  }
118
187
  }