@tryfridayai/cli 0.2.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/bin/friday.js +13 -0
- package/bin/postinstall.js +27 -0
- package/package.json +47 -0
- package/src/cli.js +132 -0
- package/src/commands/chat/slashCommands.js +976 -0
- package/src/commands/chat/smartAffordances.js +139 -0
- package/src/commands/chat/ui.js +146 -0
- package/src/commands/chat/welcomeScreen.js +161 -0
- package/src/commands/chat.js +763 -0
- package/src/commands/install.js +152 -0
- package/src/commands/plugins.js +65 -0
- package/src/commands/schedule.js +278 -0
- package/src/commands/serve.js +206 -0
- package/src/commands/setup.js +187 -0
- package/src/commands/uninstall.js +76 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* friday setup — Guided onboarding wizard
|
|
3
|
+
*
|
|
4
|
+
* Walks the user through:
|
|
5
|
+
* 1. Anthropic API key entry
|
|
6
|
+
* 2. Permission profile selection
|
|
7
|
+
* 3. Workspace configuration
|
|
8
|
+
*
|
|
9
|
+
* Stores config at ~/.friday/config.json
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import os from 'os';
|
|
15
|
+
import readline from 'readline';
|
|
16
|
+
|
|
17
|
+
const CONFIG_DIR = process.env.FRIDAY_CONFIG_DIR || path.join(os.homedir(), '.friday');
|
|
18
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
19
|
+
const ENV_FILE = path.join(CONFIG_DIR, '.env');
|
|
20
|
+
|
|
21
|
+
function ensureConfigDir() {
|
|
22
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
23
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function loadConfig() {
|
|
28
|
+
try {
|
|
29
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
30
|
+
return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
} catch {
|
|
33
|
+
// ignore
|
|
34
|
+
}
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function saveConfig(config) {
|
|
39
|
+
ensureConfigDir();
|
|
40
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function saveEnvFile(vars) {
|
|
44
|
+
ensureConfigDir();
|
|
45
|
+
const lines = Object.entries(vars)
|
|
46
|
+
.filter(([, v]) => v)
|
|
47
|
+
.map(([k, v]) => `${k}=${v}`);
|
|
48
|
+
fs.writeFileSync(ENV_FILE, lines.join('\n') + '\n', 'utf8');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function ask(rl, question) {
|
|
52
|
+
return new Promise((resolve) => {
|
|
53
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function maskKey(key) {
|
|
58
|
+
if (!key || key.length < 8) return '****';
|
|
59
|
+
return key.slice(0, 7) + '...' + key.slice(-4);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export default async function setup(args) {
|
|
63
|
+
const rl = readline.createInterface({
|
|
64
|
+
input: process.stdin,
|
|
65
|
+
output: process.stdout,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const config = loadConfig();
|
|
69
|
+
const envVars = {};
|
|
70
|
+
|
|
71
|
+
console.log('');
|
|
72
|
+
console.log(' Welcome to Friday!');
|
|
73
|
+
console.log('');
|
|
74
|
+
|
|
75
|
+
// Step 1: Anthropic API key
|
|
76
|
+
const existingKey = process.env.ANTHROPIC_API_KEY || config.anthropicApiKey;
|
|
77
|
+
if (existingKey) {
|
|
78
|
+
console.log(` Anthropic API key found: ${maskKey(existingKey)}`);
|
|
79
|
+
const change = await ask(rl, ' Change it? (y/N): ');
|
|
80
|
+
if (change.toLowerCase() === 'y') {
|
|
81
|
+
const key = await ask(rl, ' Paste your Anthropic API key: ');
|
|
82
|
+
if (key) {
|
|
83
|
+
envVars.ANTHROPIC_API_KEY = key;
|
|
84
|
+
config.anthropicApiKey = key;
|
|
85
|
+
console.log(' Key saved.');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
console.log(' Friday uses Claude by Anthropic as its AI engine.');
|
|
90
|
+
console.log(' Get a key at: https://console.anthropic.com/settings/keys');
|
|
91
|
+
console.log('');
|
|
92
|
+
const key = await ask(rl, ' Paste your API key: ');
|
|
93
|
+
if (!key) {
|
|
94
|
+
console.log(' No key provided. You can set ANTHROPIC_API_KEY in your environment later.');
|
|
95
|
+
} else {
|
|
96
|
+
envVars.ANTHROPIC_API_KEY = key;
|
|
97
|
+
config.anthropicApiKey = key;
|
|
98
|
+
console.log(' Key saved.');
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
console.log('');
|
|
102
|
+
|
|
103
|
+
// Step 2: Permission profile
|
|
104
|
+
console.log(' Choose a permission profile:');
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log(' 1. Developer (recommended)');
|
|
107
|
+
console.log(' Auto-approves file and terminal operations in your workspace.');
|
|
108
|
+
console.log('');
|
|
109
|
+
console.log(' 2. Safe');
|
|
110
|
+
console.log(' Read-only by default. Asks before writing files or running commands.');
|
|
111
|
+
console.log('');
|
|
112
|
+
console.log(' 3. Locked');
|
|
113
|
+
console.log(' Asks permission for every action. Maximum control.');
|
|
114
|
+
console.log('');
|
|
115
|
+
|
|
116
|
+
const profileChoice = await ask(rl, ' Choose (1/2/3): ');
|
|
117
|
+
const profiles = { '1': 'developer', '2': 'safe', '3': 'locked' };
|
|
118
|
+
config.permissionProfile = profiles[profileChoice] || 'developer';
|
|
119
|
+
console.log(` Profile set to: ${config.permissionProfile}`);
|
|
120
|
+
console.log('');
|
|
121
|
+
|
|
122
|
+
// Step 3: Default workspace
|
|
123
|
+
const defaultWorkspace = config.workspace || path.join(os.homedir(), 'FridayWorkspace');
|
|
124
|
+
console.log(' Friday needs a workspace directory — a folder where it can');
|
|
125
|
+
console.log(' create and edit files. Enter a folder name or full path.');
|
|
126
|
+
console.log('');
|
|
127
|
+
const workspace = await ask(rl, ` Workspace path (${defaultWorkspace}): `);
|
|
128
|
+
let resolvedWorkspace = workspace || defaultWorkspace;
|
|
129
|
+
// If user typed just a name (no path separator), put it in home directory
|
|
130
|
+
if (resolvedWorkspace && !resolvedWorkspace.includes(path.sep) && !resolvedWorkspace.startsWith('~')) {
|
|
131
|
+
resolvedWorkspace = path.join(os.homedir(), resolvedWorkspace);
|
|
132
|
+
}
|
|
133
|
+
// Expand ~ to home directory
|
|
134
|
+
if (resolvedWorkspace.startsWith('~')) {
|
|
135
|
+
resolvedWorkspace = path.join(os.homedir(), resolvedWorkspace.slice(1));
|
|
136
|
+
}
|
|
137
|
+
config.workspace = path.resolve(resolvedWorkspace);
|
|
138
|
+
|
|
139
|
+
// Create workspace if it doesn't exist
|
|
140
|
+
if (!fs.existsSync(config.workspace)) {
|
|
141
|
+
fs.mkdirSync(config.workspace, { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
console.log(` Workspace created: ${config.workspace}`);
|
|
144
|
+
console.log('');
|
|
145
|
+
|
|
146
|
+
// Step 4: Optional provider keys
|
|
147
|
+
console.log(' Optional: Add API keys for additional providers.');
|
|
148
|
+
console.log(' (Press Enter to skip any)');
|
|
149
|
+
console.log('');
|
|
150
|
+
|
|
151
|
+
const openaiKey = await ask(rl, ' OpenAI API key (for image/video gen): ');
|
|
152
|
+
if (openaiKey) {
|
|
153
|
+
envVars.OPENAI_API_KEY = openaiKey;
|
|
154
|
+
config.openaiApiKey = openaiKey;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const googleKey = await ask(rl, ' Google AI API key (for Gemini/Imagen): ');
|
|
158
|
+
if (googleKey) {
|
|
159
|
+
envVars.GOOGLE_API_KEY = googleKey;
|
|
160
|
+
config.googleApiKey = googleKey;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const elevenKey = await ask(rl, ' ElevenLabs API key (for voice): ');
|
|
164
|
+
if (elevenKey) {
|
|
165
|
+
envVars.ELEVENLABS_API_KEY = elevenKey;
|
|
166
|
+
config.elevenlabsApiKey = elevenKey;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Save everything
|
|
170
|
+
config.setupComplete = true;
|
|
171
|
+
config.setupDate = new Date().toISOString();
|
|
172
|
+
saveConfig(config);
|
|
173
|
+
|
|
174
|
+
if (Object.keys(envVars).length > 0) {
|
|
175
|
+
saveEnvFile(envVars);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
console.log('');
|
|
179
|
+
console.log(' Setup complete!');
|
|
180
|
+
console.log('');
|
|
181
|
+
|
|
182
|
+
rl.close();
|
|
183
|
+
|
|
184
|
+
// Automatically launch chat after setup
|
|
185
|
+
const chatModule = await import('./chat.js');
|
|
186
|
+
await chatModule.default({ ...args, workspace: config.workspace });
|
|
187
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* friday uninstall <plugin> — Remove an installed plugin
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import readline from 'readline';
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
let runtimeDir;
|
|
11
|
+
try {
|
|
12
|
+
const runtimePkg = require.resolve('friday-runtime/package.json');
|
|
13
|
+
runtimeDir = path.dirname(runtimePkg);
|
|
14
|
+
} catch {
|
|
15
|
+
runtimeDir = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..', '..', 'runtime');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const DIM = '\x1b[2m';
|
|
19
|
+
const RESET = '\x1b[0m';
|
|
20
|
+
const BOLD = '\x1b[1m';
|
|
21
|
+
const GREEN = '\x1b[32m';
|
|
22
|
+
const RED = '\x1b[31m';
|
|
23
|
+
|
|
24
|
+
function ask(rl, question) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
rl.question(question, (answer) => resolve(answer.trim()));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default async function uninstall(args) {
|
|
31
|
+
const pluginId = args._[1];
|
|
32
|
+
|
|
33
|
+
const { PluginManager } = await import(path.join(runtimeDir, 'src', 'plugins', 'PluginManager.js'));
|
|
34
|
+
const pm = new PluginManager();
|
|
35
|
+
|
|
36
|
+
if (!pluginId) {
|
|
37
|
+
console.log('');
|
|
38
|
+
console.log(` ${BOLD}Usage:${RESET} friday uninstall <plugin>`);
|
|
39
|
+
console.log('');
|
|
40
|
+
const installed = pm.listInstalled();
|
|
41
|
+
if (installed.length === 0) {
|
|
42
|
+
console.log(` No plugins installed.`);
|
|
43
|
+
} else {
|
|
44
|
+
console.log(` ${BOLD}Installed plugins:${RESET}`);
|
|
45
|
+
for (const p of installed) {
|
|
46
|
+
console.log(` ${BOLD}${p.id}${RESET} ${p.name}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
console.log('');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!pm.isInstalled(pluginId)) {
|
|
54
|
+
console.log('');
|
|
55
|
+
console.log(` ${RED}Plugin '${pluginId}' is not installed.${RESET}`);
|
|
56
|
+
console.log('');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const manifest = pm.getPluginManifest(pluginId);
|
|
61
|
+
const name = manifest?.name || pluginId;
|
|
62
|
+
|
|
63
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
64
|
+
const confirm = await ask(rl, ` Uninstall ${name}? (y/N): `);
|
|
65
|
+
rl.close();
|
|
66
|
+
|
|
67
|
+
if (confirm.toLowerCase() !== 'y') {
|
|
68
|
+
console.log(` ${DIM}Cancelled.${RESET}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pm.uninstall(pluginId);
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log(` ${GREEN}${name} uninstalled.${RESET}`);
|
|
75
|
+
console.log('');
|
|
76
|
+
}
|