slashdev 0.1.0 → 1.0.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.
- package/.gitmodules +3 -0
- package/CLAUDE.md +87 -0
- package/README.md +158 -21
- package/bin/check-setup.js +27 -0
- package/claude-skills/agentswarm/SKILL.md +479 -0
- package/claude-skills/bug-diagnosis/SKILL.md +34 -0
- package/claude-skills/code-review/SKILL.md +26 -0
- package/claude-skills/frontend-design/LICENSE.txt +177 -0
- package/claude-skills/frontend-design/SKILL.md +42 -0
- package/claude-skills/pr-description/SKILL.md +35 -0
- package/claude-skills/scope-estimate/SKILL.md +37 -0
- package/hooks/post-response.sh +242 -0
- package/package.json +11 -3
- package/skills/front-end-design/prompts/system.md +37 -0
- package/skills/front-end-testing/prompts/system.md +66 -0
- package/skills/github-manager/prompts/system.md +79 -0
- package/skills/product-expert/prompts/system.md +52 -0
- package/skills/server-admin/prompts/system.md +39 -0
- package/src/auth/index.js +115 -0
- package/src/cli.js +188 -18
- package/src/commands/setup-internals.js +137 -0
- package/src/commands/setup.js +104 -0
- package/src/commands/update.js +60 -0
- package/src/connections/index.js +449 -0
- package/src/connections/providers/github.js +71 -0
- package/src/connections/providers/servers.js +175 -0
- package/src/connections/registry.js +21 -0
- package/src/core/claude.js +78 -0
- package/src/core/codebase.js +119 -0
- package/src/core/config.js +110 -0
- package/src/index.js +8 -1
- package/src/info.js +54 -21
- package/src/skills/index.js +252 -0
- package/src/utils/ssh-keys.js +67 -0
- package/vendor/gstack/.env.example +5 -0
- package/vendor/gstack/autoplan/SKILL.md +1116 -0
- package/vendor/gstack/browse/SKILL.md +538 -0
- package/vendor/gstack/canary/SKILL.md +587 -0
- package/vendor/gstack/careful/SKILL.md +59 -0
- package/vendor/gstack/codex/SKILL.md +862 -0
- package/vendor/gstack/connect-chrome/SKILL.md +549 -0
- package/vendor/gstack/cso/ACKNOWLEDGEMENTS.md +14 -0
- package/vendor/gstack/cso/SKILL.md +929 -0
- package/vendor/gstack/design-consultation/SKILL.md +962 -0
- package/vendor/gstack/design-review/SKILL.md +1314 -0
- package/vendor/gstack/design-shotgun/SKILL.md +730 -0
- package/vendor/gstack/document-release/SKILL.md +718 -0
- package/vendor/gstack/freeze/SKILL.md +82 -0
- package/vendor/gstack/gstack-upgrade/SKILL.md +232 -0
- package/vendor/gstack/guard/SKILL.md +82 -0
- package/vendor/gstack/investigate/SKILL.md +504 -0
- package/vendor/gstack/land-and-deploy/SKILL.md +1367 -0
- package/vendor/gstack/office-hours/SKILL.md +1317 -0
- package/vendor/gstack/plan-ceo-review/SKILL.md +1537 -0
- package/vendor/gstack/plan-design-review/SKILL.md +1227 -0
- package/vendor/gstack/plan-eng-review/SKILL.md +1120 -0
- package/vendor/gstack/qa/SKILL.md +1136 -0
- package/vendor/gstack/qa/references/issue-taxonomy.md +85 -0
- package/vendor/gstack/qa/templates/qa-report-template.md +126 -0
- package/vendor/gstack/qa-only/SKILL.md +726 -0
- package/vendor/gstack/retro/SKILL.md +1197 -0
- package/vendor/gstack/review/SKILL.md +1138 -0
- package/vendor/gstack/review/TODOS-format.md +62 -0
- package/vendor/gstack/review/checklist.md +220 -0
- package/vendor/gstack/review/design-checklist.md +132 -0
- package/vendor/gstack/review/greptile-triage.md +220 -0
- package/vendor/gstack/setup-browser-cookies/SKILL.md +348 -0
- package/vendor/gstack/setup-deploy/SKILL.md +528 -0
- package/vendor/gstack/ship/SKILL.md +1931 -0
- package/vendor/gstack/unfreeze/SKILL.md +40 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { userInfo } from 'os';
|
|
4
|
+
import { detectSSHKeys } from '../../utils/ssh-keys.js';
|
|
5
|
+
|
|
6
|
+
export const serversProvider = {
|
|
7
|
+
id: 'servers',
|
|
8
|
+
name: 'Servers',
|
|
9
|
+
description: 'SSH server connections for server administration',
|
|
10
|
+
multiple: true,
|
|
11
|
+
|
|
12
|
+
async promptForCredentials() {
|
|
13
|
+
console.log();
|
|
14
|
+
console.log(chalk.dim(' Add a server connection for AI-assisted administration.'));
|
|
15
|
+
console.log(chalk.dim(' The server does not need to be reachable right now.'));
|
|
16
|
+
console.log();
|
|
17
|
+
|
|
18
|
+
const currentUser = userInfo().username;
|
|
19
|
+
|
|
20
|
+
const answers = await inquirer.prompt([
|
|
21
|
+
{
|
|
22
|
+
type: 'input',
|
|
23
|
+
name: 'name',
|
|
24
|
+
message: chalk.hex('#7a9fff')('Server alias (e.g. prod-web, staging-db):'),
|
|
25
|
+
validate: (input) => {
|
|
26
|
+
if (!input || input.trim().length === 0) {
|
|
27
|
+
return 'Server alias is required';
|
|
28
|
+
}
|
|
29
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(input.trim())) {
|
|
30
|
+
return 'Alias must be alphanumeric (hyphens and underscores allowed)';
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: 'input',
|
|
37
|
+
name: 'host',
|
|
38
|
+
message: chalk.hex('#7a9fff')('Host or IP address:'),
|
|
39
|
+
validate: (input) => {
|
|
40
|
+
if (!input || input.trim().length === 0) {
|
|
41
|
+
return 'Host is required';
|
|
42
|
+
}
|
|
43
|
+
return true;
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: 'input',
|
|
48
|
+
name: 'port',
|
|
49
|
+
message: chalk.hex('#7a9fff')('SSH port:'),
|
|
50
|
+
default: '22',
|
|
51
|
+
validate: (input) => {
|
|
52
|
+
const port = parseInt(input, 10);
|
|
53
|
+
if (isNaN(port) || port < 1 || port > 65535) {
|
|
54
|
+
return 'Port must be a number between 1 and 65535';
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
type: 'input',
|
|
61
|
+
name: 'username',
|
|
62
|
+
message: chalk.hex('#7a9fff')('SSH username:'),
|
|
63
|
+
default: currentUser,
|
|
64
|
+
validate: (input) => {
|
|
65
|
+
if (!input || input.trim().length === 0) {
|
|
66
|
+
return 'Username is required';
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: 'list',
|
|
73
|
+
name: 'authType',
|
|
74
|
+
message: chalk.hex('#7a9fff')('Authentication method:'),
|
|
75
|
+
choices: [
|
|
76
|
+
{ name: 'SSH Key', value: 'key' },
|
|
77
|
+
{ name: 'Password', value: 'password' },
|
|
78
|
+
],
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
const creds = {
|
|
83
|
+
name: answers.name.trim(),
|
|
84
|
+
host: answers.host.trim(),
|
|
85
|
+
port: parseInt(answers.port, 10),
|
|
86
|
+
username: answers.username.trim(),
|
|
87
|
+
authType: answers.authType,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (answers.authType === 'key') {
|
|
91
|
+
const detectedKeys = detectSSHKeys();
|
|
92
|
+
|
|
93
|
+
const keyChoices = detectedKeys.map((k) => ({
|
|
94
|
+
name: `${k.name} (${k.privatePath})`,
|
|
95
|
+
value: k.privatePath,
|
|
96
|
+
}));
|
|
97
|
+
keyChoices.push({ name: 'Enter custom path...', value: '__custom__' });
|
|
98
|
+
|
|
99
|
+
if (detectedKeys.length > 0) {
|
|
100
|
+
const { keyPath } = await inquirer.prompt([
|
|
101
|
+
{
|
|
102
|
+
type: 'list',
|
|
103
|
+
name: 'keyPath',
|
|
104
|
+
message: chalk.hex('#7a9fff')('Select SSH key:'),
|
|
105
|
+
choices: keyChoices,
|
|
106
|
+
},
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
if (keyPath === '__custom__') {
|
|
110
|
+
const { customPath } = await inquirer.prompt([
|
|
111
|
+
{
|
|
112
|
+
type: 'input',
|
|
113
|
+
name: 'customPath',
|
|
114
|
+
message: chalk.hex('#7a9fff')('Path to private key:'),
|
|
115
|
+
validate: (input) => {
|
|
116
|
+
if (!input || input.trim().length === 0) {
|
|
117
|
+
return 'Key path is required';
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
]);
|
|
123
|
+
creds.keyPath = customPath.trim();
|
|
124
|
+
} else {
|
|
125
|
+
creds.keyPath = keyPath;
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
console.log(chalk.dim(' No SSH keys detected in ~/.ssh/'));
|
|
129
|
+
const { customPath } = await inquirer.prompt([
|
|
130
|
+
{
|
|
131
|
+
type: 'input',
|
|
132
|
+
name: 'customPath',
|
|
133
|
+
message: chalk.hex('#7a9fff')('Path to private key:'),
|
|
134
|
+
validate: (input) => {
|
|
135
|
+
if (!input || input.trim().length === 0) {
|
|
136
|
+
return 'Key path is required';
|
|
137
|
+
}
|
|
138
|
+
return true;
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
]);
|
|
142
|
+
creds.keyPath = customPath.trim();
|
|
143
|
+
}
|
|
144
|
+
} else {
|
|
145
|
+
const { password } = await inquirer.prompt([
|
|
146
|
+
{
|
|
147
|
+
type: 'password',
|
|
148
|
+
name: 'password',
|
|
149
|
+
message: chalk.hex('#7a9fff')('SSH password:'),
|
|
150
|
+
mask: '*',
|
|
151
|
+
validate: (input) => {
|
|
152
|
+
if (!input || input.trim().length === 0) {
|
|
153
|
+
return 'Password is required';
|
|
154
|
+
}
|
|
155
|
+
return true;
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
]);
|
|
159
|
+
creds.password = password;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return creds;
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
async validate(credentials) {
|
|
166
|
+
return {
|
|
167
|
+
valid: true,
|
|
168
|
+
username: `${credentials.name} (${credentials.username}@${credentials.host}:${credentials.port})`,
|
|
169
|
+
};
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
createClient() {
|
|
173
|
+
return null;
|
|
174
|
+
},
|
|
175
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { githubProvider } from './providers/github.js';
|
|
2
|
+
import { serversProvider } from './providers/servers.js';
|
|
3
|
+
|
|
4
|
+
const CONNECTION_PROVIDERS = {
|
|
5
|
+
github: githubProvider,
|
|
6
|
+
servers: serversProvider,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export function getProvider(id) {
|
|
10
|
+
return CONNECTION_PROVIDERS[id] || null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function getAllProviders() {
|
|
14
|
+
return { ...CONNECTION_PROVIDERS };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getProviderIds() {
|
|
18
|
+
return Object.keys(CONNECTION_PROVIDERS);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { CONNECTION_PROVIDERS };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import Anthropic from '@anthropic-ai/sdk';
|
|
2
|
+
import { getApiKey } from './config.js';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
|
|
5
|
+
let client = null;
|
|
6
|
+
|
|
7
|
+
export function getClient() {
|
|
8
|
+
const apiKey = getApiKey();
|
|
9
|
+
|
|
10
|
+
if (!apiKey) {
|
|
11
|
+
throw new Error('Not authenticated. Run `slashdev login` first.');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (!client) {
|
|
15
|
+
client = new Anthropic({ apiKey });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return client;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function chat(options) {
|
|
22
|
+
const {
|
|
23
|
+
messages,
|
|
24
|
+
systemPrompt,
|
|
25
|
+
model = 'claude-sonnet-4-20250514',
|
|
26
|
+
maxTokens = 4096,
|
|
27
|
+
stream = false,
|
|
28
|
+
onStream = null,
|
|
29
|
+
} = options;
|
|
30
|
+
|
|
31
|
+
const client = getClient();
|
|
32
|
+
|
|
33
|
+
const requestOptions = {
|
|
34
|
+
model,
|
|
35
|
+
max_tokens: maxTokens,
|
|
36
|
+
messages,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (systemPrompt) {
|
|
40
|
+
requestOptions.system = systemPrompt;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (stream && onStream) {
|
|
44
|
+
// Streaming response
|
|
45
|
+
const response = await client.messages.create({
|
|
46
|
+
...requestOptions,
|
|
47
|
+
stream: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
let fullText = '';
|
|
51
|
+
for await (const event of response) {
|
|
52
|
+
if (event.type === 'content_block_delta' && event.delta.type === 'text_delta') {
|
|
53
|
+
const text = event.delta.text;
|
|
54
|
+
fullText += text;
|
|
55
|
+
onStream(text);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return fullText;
|
|
59
|
+
} else {
|
|
60
|
+
// Non-streaming response
|
|
61
|
+
const response = await client.messages.create(requestOptions);
|
|
62
|
+
return response.content[0].text;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function validateApiKey(apiKey) {
|
|
67
|
+
try {
|
|
68
|
+
const testClient = new Anthropic({ apiKey });
|
|
69
|
+
await testClient.messages.create({
|
|
70
|
+
model: 'claude-sonnet-4-20250514',
|
|
71
|
+
max_tokens: 10,
|
|
72
|
+
messages: [{ role: 'user', content: 'Hi' }],
|
|
73
|
+
});
|
|
74
|
+
return true;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
|
|
2
|
+
import { join, relative, extname } from 'path';
|
|
3
|
+
|
|
4
|
+
// Common file extensions for different categories
|
|
5
|
+
const FILE_TYPES = {
|
|
6
|
+
frontend: ['.tsx', '.jsx', '.ts', '.js', '.vue', '.svelte', '.css', '.scss', '.sass', '.less', '.html'],
|
|
7
|
+
test: ['.test.ts', '.test.tsx', '.test.js', '.test.jsx', '.spec.ts', '.spec.tsx', '.spec.js', '.spec.jsx'],
|
|
8
|
+
config: ['.json', '.yaml', '.yml', '.toml', '.env'],
|
|
9
|
+
docs: ['.md', '.mdx', '.txt', '.rst'],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// Directories to ignore
|
|
13
|
+
const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '.next', '.nuxt', 'coverage', '.cache'];
|
|
14
|
+
|
|
15
|
+
export function readFile(filePath) {
|
|
16
|
+
try {
|
|
17
|
+
return readFileSync(filePath, 'utf-8');
|
|
18
|
+
} catch (error) {
|
|
19
|
+
throw new Error(`Could not read file: ${filePath}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function fileExists(filePath) {
|
|
24
|
+
return existsSync(filePath);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function listFiles(dir, options = {}) {
|
|
28
|
+
const {
|
|
29
|
+
recursive = true,
|
|
30
|
+
extensions = null,
|
|
31
|
+
ignore = IGNORE_DIRS,
|
|
32
|
+
} = options;
|
|
33
|
+
|
|
34
|
+
const files = [];
|
|
35
|
+
|
|
36
|
+
function walk(currentDir) {
|
|
37
|
+
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
38
|
+
|
|
39
|
+
for (const entry of entries) {
|
|
40
|
+
const fullPath = join(currentDir, entry.name);
|
|
41
|
+
const relativePath = relative(dir, fullPath);
|
|
42
|
+
|
|
43
|
+
if (entry.isDirectory()) {
|
|
44
|
+
if (!ignore.includes(entry.name) && recursive) {
|
|
45
|
+
walk(fullPath);
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
if (!extensions || extensions.includes(extname(entry.name))) {
|
|
49
|
+
files.push({
|
|
50
|
+
path: fullPath,
|
|
51
|
+
relativePath,
|
|
52
|
+
name: entry.name,
|
|
53
|
+
ext: extname(entry.name),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
walk(dir);
|
|
61
|
+
return files;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getFrontendFiles(dir) {
|
|
65
|
+
return listFiles(dir, { extensions: FILE_TYPES.frontend });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getTestFiles(dir) {
|
|
69
|
+
return listFiles(dir, { extensions: FILE_TYPES.test });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function getProjectContext(dir) {
|
|
73
|
+
const context = {
|
|
74
|
+
hasPackageJson: fileExists(join(dir, 'package.json')),
|
|
75
|
+
hasTsConfig: fileExists(join(dir, 'tsconfig.json')),
|
|
76
|
+
hasVite: fileExists(join(dir, 'vite.config.ts')) || fileExists(join(dir, 'vite.config.js')),
|
|
77
|
+
hasNext: fileExists(join(dir, 'next.config.js')) || fileExists(join(dir, 'next.config.mjs')),
|
|
78
|
+
hasReact: false,
|
|
79
|
+
hasVue: false,
|
|
80
|
+
hasSvelte: false,
|
|
81
|
+
framework: 'unknown',
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (context.hasPackageJson) {
|
|
85
|
+
try {
|
|
86
|
+
const pkg = JSON.parse(readFile(join(dir, 'package.json')));
|
|
87
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
88
|
+
|
|
89
|
+
context.hasReact = 'react' in deps;
|
|
90
|
+
context.hasVue = 'vue' in deps;
|
|
91
|
+
context.hasSvelte = 'svelte' in deps;
|
|
92
|
+
|
|
93
|
+
if (context.hasNext) context.framework = 'Next.js';
|
|
94
|
+
else if (context.hasReact) context.framework = 'React';
|
|
95
|
+
else if (context.hasVue) context.framework = 'Vue';
|
|
96
|
+
else if (context.hasSvelte) context.framework = 'Svelte';
|
|
97
|
+
} catch (e) {
|
|
98
|
+
// Ignore parsing errors
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return context;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function buildFileContext(files, maxFiles = 10) {
|
|
106
|
+
const selected = files.slice(0, maxFiles);
|
|
107
|
+
let context = '';
|
|
108
|
+
|
|
109
|
+
for (const file of selected) {
|
|
110
|
+
try {
|
|
111
|
+
const content = readFile(file.path);
|
|
112
|
+
context += `\n--- ${file.relativePath} ---\n${content}\n`;
|
|
113
|
+
} catch (e) {
|
|
114
|
+
// Skip unreadable files
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return context;
|
|
119
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { existsSync, mkdirSync } from 'fs';
|
|
5
|
+
|
|
6
|
+
// Slashdev config directory
|
|
7
|
+
const SLASHDEV_DIR = join(homedir(), '.slashdev');
|
|
8
|
+
|
|
9
|
+
// Ensure the directory exists
|
|
10
|
+
if (!existsSync(SLASHDEV_DIR)) {
|
|
11
|
+
mkdirSync(SLASHDEV_DIR, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Main configuration store
|
|
15
|
+
const config = new Conf({
|
|
16
|
+
projectName: 'slashdev',
|
|
17
|
+
cwd: SLASHDEV_DIR,
|
|
18
|
+
configName: 'config',
|
|
19
|
+
defaults: {
|
|
20
|
+
version: '0.2.0',
|
|
21
|
+
skills: {
|
|
22
|
+
installed: ['front-end-design', 'product-expert', 'github-manager', 'front-end-testing'],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Credentials store (separate for security)
|
|
28
|
+
const credentials = new Conf({
|
|
29
|
+
projectName: 'slashdev',
|
|
30
|
+
cwd: SLASHDEV_DIR,
|
|
31
|
+
configName: 'credentials',
|
|
32
|
+
defaults: {
|
|
33
|
+
apiKey: null,
|
|
34
|
+
user: null,
|
|
35
|
+
connections: {},
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export function getSlashdevDir() {
|
|
40
|
+
return SLASHDEV_DIR;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getConfig(key) {
|
|
44
|
+
return key ? config.get(key) : config.store;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function setConfig(key, value) {
|
|
48
|
+
config.set(key, value);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getApiKey() {
|
|
52
|
+
return credentials.get('apiKey');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function setApiKey(apiKey) {
|
|
56
|
+
credentials.set('apiKey', apiKey);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function clearApiKey() {
|
|
60
|
+
credentials.delete('apiKey');
|
|
61
|
+
credentials.delete('user');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getUser() {
|
|
65
|
+
return credentials.get('user');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function setUser(user) {
|
|
69
|
+
credentials.set('user', user);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function isAuthenticated() {
|
|
73
|
+
return !!getApiKey();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Engineer email (for Claude Code integration / logging)
|
|
77
|
+
export function getEngineer() {
|
|
78
|
+
return config.get('engineer') || null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function setEngineer(email) {
|
|
82
|
+
config.set('engineer', email);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function getPackageVersion() {
|
|
86
|
+
return config.get('packageVersion') || null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getConnection(providerId) {
|
|
90
|
+
const connections = credentials.get('connections') || {};
|
|
91
|
+
return connections[providerId] || null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function setConnection(providerId, data) {
|
|
95
|
+
const connections = credentials.get('connections') || {};
|
|
96
|
+
connections[providerId] = data;
|
|
97
|
+
credentials.set('connections', connections);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function clearConnection(providerId) {
|
|
101
|
+
const connections = credentials.get('connections') || {};
|
|
102
|
+
delete connections[providerId];
|
|
103
|
+
credentials.set('connections', connections);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getAllConnections() {
|
|
107
|
+
return credentials.get('connections') || {};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export { config, credentials };
|
package/src/index.js
CHANGED
|
@@ -2,5 +2,12 @@
|
|
|
2
2
|
// Skills & workflows for AI agents in the Slashdev ecosystem
|
|
3
3
|
|
|
4
4
|
export { run } from './cli.js';
|
|
5
|
-
export { displayBanner
|
|
5
|
+
export { displayBanner } from './banner.js';
|
|
6
6
|
export { getPackageInfo, displayInfo, displayVersion, displayHelp } from './info.js';
|
|
7
|
+
export { login, logout, whoami } from './auth/index.js';
|
|
8
|
+
export { listSkills, runSkill, getSkillByCommand } from './skills/index.js';
|
|
9
|
+
export { getConfig, setConfig, getApiKey, isAuthenticated, getEngineer, setEngineer } from './core/config.js';
|
|
10
|
+
export { chat, getClient } from './core/claude.js';
|
|
11
|
+
export { connect, disconnect, listConnections, isConnected, getConnection, getConnectionClient } from './connections/index.js';
|
|
12
|
+
export { setup } from './commands/setup.js';
|
|
13
|
+
export { update } from './commands/update.js';
|
package/src/info.js
CHANGED
|
@@ -2,6 +2,7 @@ import chalk from 'chalk';
|
|
|
2
2
|
import { readFileSync } from 'fs';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { dirname, join } from 'path';
|
|
5
|
+
import { isAuthenticated, getUser } from './core/config.js';
|
|
5
6
|
|
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
8
|
const __dirname = dirname(__filename);
|
|
@@ -32,9 +33,19 @@ export function getPackageInfo() {
|
|
|
32
33
|
export function displayInfo() {
|
|
33
34
|
const pkg = getPackageInfo();
|
|
34
35
|
const dim = chalk.dim;
|
|
36
|
+
const authed = isAuthenticated();
|
|
37
|
+
const user = getUser();
|
|
35
38
|
|
|
36
39
|
console.log();
|
|
37
40
|
console.log(` ${blue.brand.bold(pkg.name)} ${dim('v' + pkg.version)}`);
|
|
41
|
+
|
|
42
|
+
// Auth status
|
|
43
|
+
if (authed) {
|
|
44
|
+
console.log(` ${chalk.hex('#4d7fff')('●')} ${dim('Logged in' + (user ? ` as ${user}` : ''))}`);
|
|
45
|
+
} else {
|
|
46
|
+
console.log(` ${dim('○ Not logged in — run')} ${blue.bright('slashdev login')}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
38
49
|
console.log();
|
|
39
50
|
|
|
40
51
|
// Main value proposition
|
|
@@ -43,27 +54,28 @@ export function displayInfo() {
|
|
|
43
54
|
console.log(` ${blue.light('the Slashdev engineering team.')}`);
|
|
44
55
|
console.log();
|
|
45
56
|
|
|
46
|
-
console.log(` ${blue.brand.bold('
|
|
57
|
+
console.log(` ${blue.brand.bold('Skills')}`);
|
|
47
58
|
console.log(` ${blue.dark('────────────────────────────────────────────────────────')}`);
|
|
48
|
-
console.log(` ${blue.bright('
|
|
49
|
-
console.log(` ${blue.bright('
|
|
50
|
-
console.log(` ${blue.bright('
|
|
51
|
-
console.log(` ${blue.bright('
|
|
59
|
+
console.log(` ${blue.bright('slashdev design')} ${blue.soft('<prompt>')} ${dim('UI/UX and component design')}`);
|
|
60
|
+
console.log(` ${blue.bright('slashdev product')} ${blue.soft('<prompt>')} ${dim('PRDs, specs, and user stories')}`);
|
|
61
|
+
console.log(` ${blue.bright('slashdev github')} ${blue.soft('<prompt>')} ${dim('PRs, issues, and releases')}`);
|
|
62
|
+
console.log(` ${blue.bright('slashdev test')} ${blue.soft('<prompt>')} ${dim('Unit and E2E test generation')}`);
|
|
63
|
+
console.log(` ${blue.bright('slashdev server')} ${blue.soft('<prompt>')} ${dim('Server administration')}`);
|
|
52
64
|
console.log();
|
|
53
65
|
|
|
54
|
-
console.log(` ${blue.brand.bold('
|
|
66
|
+
console.log(` ${blue.brand.bold('Account')}`);
|
|
55
67
|
console.log(` ${blue.dark('────────────────────────────────────────────────────────')}`);
|
|
56
|
-
console.log(` ${blue.bright('slashdev')}
|
|
57
|
-
console.log(` ${blue.bright('slashdev
|
|
58
|
-
console.log(` ${blue.bright('slashdev
|
|
68
|
+
console.log(` ${blue.bright('slashdev login')} ${dim('Authenticate with API key')}`);
|
|
69
|
+
console.log(` ${blue.bright('slashdev logout')} ${dim('Clear stored credentials')}`);
|
|
70
|
+
console.log(` ${blue.bright('slashdev whoami')} ${dim('Show auth status')}`);
|
|
71
|
+
console.log(` ${blue.bright('slashdev skills')} ${dim('List installed skills')}`);
|
|
59
72
|
console.log();
|
|
60
73
|
|
|
61
|
-
console.log(` ${blue.brand.bold('
|
|
74
|
+
console.log(` ${blue.brand.bold('Connections')}`);
|
|
62
75
|
console.log(` ${blue.dark('────────────────────────────────────────────────────────')}`);
|
|
63
|
-
console.log(` ${blue.bright('
|
|
64
|
-
console.log(` ${blue.bright('
|
|
65
|
-
console.log(` ${blue.bright('
|
|
66
|
-
console.log(` ${blue.bright('▸')} Slashdev skill marketplace`);
|
|
76
|
+
console.log(` ${blue.bright('slashdev connect')} ${blue.soft('[service]')} ${dim('Add a service connection')}`);
|
|
77
|
+
console.log(` ${blue.bright('slashdev disconnect')} ${blue.soft('[service]')} ${dim('Remove a connection')}`);
|
|
78
|
+
console.log(` ${blue.bright('slashdev connections')} ${dim('List all connections')}`);
|
|
67
79
|
console.log();
|
|
68
80
|
|
|
69
81
|
console.log(` ${dim('Learn more at')} ${blue.brand.underline('https://slashdev.io')}`);
|
|
@@ -79,16 +91,37 @@ export function displayHelp() {
|
|
|
79
91
|
const dim = chalk.dim;
|
|
80
92
|
|
|
81
93
|
console.log();
|
|
82
|
-
console.log(` ${blue.brand.bold('Usage')} ${dim('slashdev
|
|
94
|
+
console.log(` ${blue.brand.bold('Usage')} ${dim('slashdev <command> [options]')}`);
|
|
95
|
+
console.log();
|
|
96
|
+
|
|
97
|
+
console.log(` ${blue.brand.bold('Skills')}`);
|
|
98
|
+
console.log(` ${blue.dark('──────────────────────────────────────────')}`);
|
|
99
|
+
console.log(` ${blue.bright('design <prompt>')} ${dim('Front-end design help')}`);
|
|
100
|
+
console.log(` ${blue.bright('product <prompt>')} ${dim('Product management help')}`);
|
|
101
|
+
console.log(` ${blue.bright('github <prompt>')} ${dim('GitHub workflow help')}`);
|
|
102
|
+
console.log(` ${blue.bright('test <prompt>')} ${dim('Testing help')}`);
|
|
103
|
+
console.log(` ${blue.bright('server <prompt>')} ${dim('Server admin help')}`);
|
|
104
|
+
console.log();
|
|
105
|
+
|
|
106
|
+
console.log(` ${blue.brand.bold('Account')}`);
|
|
107
|
+
console.log(` ${blue.dark('──────────────────────────────────────────')}`);
|
|
108
|
+
console.log(` ${blue.bright('login')} ${dim('Authenticate')}`);
|
|
109
|
+
console.log(` ${blue.bright('logout')} ${dim('Sign out')}`);
|
|
110
|
+
console.log(` ${blue.bright('whoami')} ${dim('Check status')}`);
|
|
111
|
+
console.log(` ${blue.bright('skills')} ${dim('List skills')}`);
|
|
83
112
|
console.log();
|
|
113
|
+
|
|
84
114
|
console.log(` ${blue.brand.bold('Options')}`);
|
|
85
|
-
console.log(` ${blue.dark('
|
|
86
|
-
console.log(` ${blue.bright('-h, --help')}
|
|
87
|
-
console.log(` ${blue.bright('-v, --version')}
|
|
115
|
+
console.log(` ${blue.dark('──────────────────────────────────────────')}`);
|
|
116
|
+
console.log(` ${blue.bright('-h, --help')} ${dim('Show help')}`);
|
|
117
|
+
console.log(` ${blue.bright('-v, --version')} ${dim('Show version')}`);
|
|
88
118
|
console.log();
|
|
119
|
+
|
|
89
120
|
console.log(` ${blue.brand.bold('Examples')}`);
|
|
90
|
-
console.log(` ${blue.dark('
|
|
91
|
-
console.log(` ${dim('$')} slashdev
|
|
92
|
-
console.log(` ${dim('$')} slashdev
|
|
121
|
+
console.log(` ${blue.dark('──────────────────────────────────────────')}`);
|
|
122
|
+
console.log(` ${dim('$')} slashdev login`);
|
|
123
|
+
console.log(` ${dim('$')} slashdev design "review my Button component"`);
|
|
124
|
+
console.log(` ${dim('$')} slashdev product "write a PRD for user auth"`);
|
|
125
|
+
console.log(` ${dim('$')} slashdev test "generate tests for utils.ts"`);
|
|
93
126
|
console.log();
|
|
94
127
|
}
|