@tryfridayai/cli 0.2.1 → 0.2.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/README.md +136 -0
- package/package.json +3 -2
- package/src/commands/chat/inputLine.js +510 -0
- package/src/commands/chat/slashCommands.js +417 -133
- package/src/commands/chat/smartAffordances.js +5 -0
- package/src/commands/chat/welcomeScreen.js +56 -88
- package/src/commands/chat.js +249 -113
- package/src/commands/install.js +46 -16
- package/src/commands/plugins.js +1 -10
- package/src/commands/schedule.js +1 -10
- package/src/commands/setup.js +49 -5
- package/src/commands/uninstall.js +1 -10
- package/src/resolveRuntime.js +31 -0
- package/src/secureKeyStore.js +220 -0
|
@@ -104,6 +104,11 @@ export function checkPreQueryHint(input) {
|
|
|
104
104
|
return ` ${ORANGE}You need a ${intent.keyLabel} key for ${intent.hint}. Try ${BOLD}/keys${RESET}`;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// Video intent — warn about cost
|
|
108
|
+
if (intent.capability === 'video') {
|
|
109
|
+
return ` ${ORANGE}⚠ Video generation can be expensive ($0.50–$5+ per video).${RESET}\n ${DIM}Monitor usage at your provider's dashboard (e.g. platform.openai.com/usage).${RESET}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
107
112
|
// Schedule intent — offer the /schedule command
|
|
108
113
|
if (intent.capability === 'schedule') {
|
|
109
114
|
return ` ${DIM}Tip: Use ${BOLD}/schedule${RESET}${DIM} to manage recurring tasks.${RESET}`;
|
|
@@ -1,48 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* chat/welcomeScreen.js — Branded welcome screen for Friday CLI chat
|
|
3
3
|
*
|
|
4
|
-
* Renders on `ready` message.
|
|
5
|
-
*
|
|
4
|
+
* Renders on `ready` message. Large ASCII art logo with the two
|
|
5
|
+
* vertical bars from the Friday brand, followed by capability
|
|
6
|
+
* indicators and quick-start hints. Inspired by Gemini CLI.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import fs from 'fs';
|
|
9
10
|
import path from 'path';
|
|
10
11
|
import os from 'os';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
PURPLE, TEAL, DIM, RESET, BOLD, ORANGE,
|
|
14
|
-
drawBox, capabilityIcon, hint,
|
|
15
|
-
} from './ui.js';
|
|
16
|
-
|
|
17
|
-
const require = createRequire(import.meta.url);
|
|
18
|
-
|
|
19
|
-
// ── Resolve paths ────────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
let runtimeDir;
|
|
22
|
-
try {
|
|
23
|
-
const runtimePkg = require.resolve('friday-runtime/package.json');
|
|
24
|
-
runtimeDir = path.dirname(runtimePkg);
|
|
25
|
-
} catch {
|
|
26
|
-
runtimeDir = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', '..', '..', '..', 'runtime');
|
|
27
|
-
}
|
|
12
|
+
import { PURPLE, TEAL, DIM, RESET } from './ui.js';
|
|
13
|
+
import { runtimeDir } from '../../resolveRuntime.js';
|
|
28
14
|
|
|
29
15
|
const CONFIG_DIR = process.env.FRIDAY_CONFIG_DIR || path.join(os.homedir(), '.friday');
|
|
30
|
-
const PLUGINS_FILE = path.join(CONFIG_DIR, 'plugins.json');
|
|
31
16
|
const ENV_FILE = path.join(CONFIG_DIR, '.env');
|
|
32
17
|
|
|
33
18
|
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
34
19
|
|
|
35
|
-
function readJsonSafe(filePath) {
|
|
36
|
-
try {
|
|
37
|
-
if (fs.existsSync(filePath)) {
|
|
38
|
-
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
39
|
-
}
|
|
40
|
-
} catch { /* ignore */ }
|
|
41
|
-
return null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
20
|
function envHasKey(keyName) {
|
|
45
|
-
// Check process.env first, then ~/.friday/.env
|
|
46
21
|
if (process.env[keyName]) return true;
|
|
47
22
|
try {
|
|
48
23
|
if (fs.existsSync(ENV_FILE)) {
|
|
@@ -55,7 +30,6 @@ function envHasKey(keyName) {
|
|
|
55
30
|
}
|
|
56
31
|
}
|
|
57
32
|
} catch { /* ignore */ }
|
|
58
|
-
// Also check the project-level .env
|
|
59
33
|
try {
|
|
60
34
|
const projectEnv = path.join(runtimeDir, '..', '.env');
|
|
61
35
|
if (fs.existsSync(projectEnv)) {
|
|
@@ -71,17 +45,25 @@ function envHasKey(keyName) {
|
|
|
71
45
|
return false;
|
|
72
46
|
}
|
|
73
47
|
|
|
74
|
-
// ──
|
|
48
|
+
// ── Logo colors ─────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
const GRAY_BAR = '\x1b[38;5;245m'; // left bar — gray
|
|
51
|
+
const WHITE_BAR = '\x1b[38;5;255m'; // right bar — bright white
|
|
52
|
+
|
|
53
|
+
// ── Main render ─────────────────────────────────────────────────────────
|
|
75
54
|
|
|
76
55
|
/**
|
|
77
|
-
* Render the branded welcome screen.
|
|
56
|
+
* Render the branded welcome screen with ASCII art logo.
|
|
78
57
|
* @returns {string} The welcome screen string to print
|
|
79
58
|
*/
|
|
80
59
|
export function renderWelcome() {
|
|
81
60
|
// Read version from package.json
|
|
82
61
|
let version = '0.2.0';
|
|
83
62
|
try {
|
|
84
|
-
const cliPkgPath = path.resolve(
|
|
63
|
+
const cliPkgPath = path.resolve(
|
|
64
|
+
path.dirname(new URL(import.meta.url).pathname),
|
|
65
|
+
'..', '..', '..', 'package.json',
|
|
66
|
+
);
|
|
85
67
|
const pkg = JSON.parse(fs.readFileSync(cliPkgPath, 'utf8'));
|
|
86
68
|
version = pkg.version || version;
|
|
87
69
|
} catch { /* ignore */ }
|
|
@@ -92,70 +74,56 @@ export function renderWelcome() {
|
|
|
92
74
|
const hasGoogle = envHasKey('GOOGLE_API_KEY');
|
|
93
75
|
const hasElevenLabs = envHasKey('ELEVENLABS_API_KEY');
|
|
94
76
|
|
|
95
|
-
// Determine capabilities
|
|
96
77
|
const chatOk = hasAnthropic || hasOpenAI || hasGoogle;
|
|
97
78
|
const imageOk = hasOpenAI || hasGoogle;
|
|
98
79
|
const voiceOk = hasOpenAI || hasElevenLabs || hasGoogle;
|
|
99
80
|
const videoOk = hasOpenAI || hasGoogle;
|
|
100
81
|
|
|
101
|
-
//
|
|
102
|
-
|
|
103
|
-
|
|
82
|
+
// ── ASCII art: two bars (logo) + block-letter FRIDAY ──────────────
|
|
83
|
+
//
|
|
84
|
+
// Layout: ██ ██ FRIDAY (block text)
|
|
85
|
+
//
|
|
86
|
+
// Bars extend 2 rows above and below the text for visual weight.
|
|
87
|
+
// Left bar is gray, right bar is bright white, text is brand purple.
|
|
88
|
+
|
|
89
|
+
const B = (row) => ` ${GRAY_BAR}██${RESET} ${WHITE_BAR}██${RESET}${row}`;
|
|
90
|
+
|
|
91
|
+
const art = [
|
|
92
|
+
B(''),
|
|
93
|
+
B(''),
|
|
94
|
+
B(` ${PURPLE}█████ ████ ███ ████ ███ █ █${RESET}`),
|
|
95
|
+
B(` ${PURPLE}█ █ █ █ █ █ █ █ █ █${RESET}`),
|
|
96
|
+
B(` ${PURPLE}████ ████ █ █ █ █████ █${RESET}`),
|
|
97
|
+
B(` ${PURPLE}█ █ █ █ █ █ █ █ █${RESET}`),
|
|
98
|
+
B(` ${PURPLE}█ █ █ ███ ████ █ █ █${RESET}`),
|
|
99
|
+
B(''),
|
|
100
|
+
B(` ${DIM}v${version}${RESET}`),
|
|
101
|
+
];
|
|
104
102
|
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const catalog = readJsonSafe(catalogPath);
|
|
111
|
-
if (catalog?.plugins) {
|
|
112
|
-
totalPlugins = Object.keys(catalog.plugins).length;
|
|
113
|
-
installedNames = installedPlugins
|
|
114
|
-
.map(id => catalog.plugins[id]?.name || id)
|
|
115
|
-
.filter(Boolean);
|
|
116
|
-
}
|
|
117
|
-
} catch { /* ignore */ }
|
|
103
|
+
// ── Capability indicators ─────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
const cap = (label, active) => active
|
|
106
|
+
? `${TEAL}\u25cf${RESET} ${label}`
|
|
107
|
+
: `${DIM}\u25cb ${label}${RESET}`;
|
|
118
108
|
|
|
119
|
-
// Build capability line
|
|
120
109
|
const caps = [
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
].join('
|
|
126
|
-
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const names = installedNames.slice(0, 4).join(', ');
|
|
131
|
-
const suffix = installedNames.length > 4 ? ` +${installedNames.length - 4} more` : '';
|
|
132
|
-
pluginLine = ` Plugins: ${TEAL}${names}${suffix}${RESET}`;
|
|
133
|
-
pluginLine += `${DIM} ${installedNames.length} of ${totalPlugins} installed${RESET}`;
|
|
134
|
-
} else {
|
|
135
|
-
pluginLine = ` ${DIM}Plugins: none installed${RESET}${DIM} 0 of ${totalPlugins} available${RESET}`;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Build missing-key hints
|
|
139
|
-
const hints = [];
|
|
140
|
-
if (!imageOk || !voiceOk || !videoOk) {
|
|
141
|
-
hints.push(` ${DIM}\u2191 ${ORANGE}/keys${DIM} to enable more capabilities${RESET}`);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// Assemble box content
|
|
145
|
-
const boxLines = [
|
|
110
|
+
cap('Chat', chatOk),
|
|
111
|
+
cap('Images', imageOk),
|
|
112
|
+
cap('Voice', voiceOk),
|
|
113
|
+
cap('Video', videoOk),
|
|
114
|
+
].join(' ');
|
|
115
|
+
|
|
116
|
+
// ── Assemble ──────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
const lines = [
|
|
146
119
|
'',
|
|
147
|
-
|
|
120
|
+
...art,
|
|
148
121
|
'',
|
|
149
|
-
|
|
122
|
+
` ${caps}`,
|
|
123
|
+
'',
|
|
124
|
+
` ${DIM}/help${RESET} commands ${DIM}\u00b7${RESET} ${DIM}/keys${RESET} API keys ${DIM}\u00b7${RESET} ${DIM}/model${RESET} configure`,
|
|
150
125
|
'',
|
|
151
|
-
` Type ${BOLD}/help${RESET} for commands, or just start talking.`,
|
|
152
126
|
];
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
boxLines.push(...hints);
|
|
156
|
-
}
|
|
157
|
-
boxLines.push('');
|
|
158
|
-
|
|
159
|
-
const title = `Friday v${version}`;
|
|
160
|
-
return '\n' + drawBox(title, boxLines);
|
|
127
|
+
|
|
128
|
+
return lines.join('\n');
|
|
161
129
|
}
|