gsd-pi 0.1.9 → 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/dist/wizard.d.ts +0 -5
- package/dist/wizard.js +78 -56
- package/package.json +1 -1
package/dist/wizard.d.ts
CHANGED
|
@@ -11,10 +11,5 @@ export declare function loadStoredEnvKeys(authStorage: AuthStorage): void;
|
|
|
11
11
|
* Anthropic auth is handled by pi's own OAuth/API key flow — we don't touch it.
|
|
12
12
|
* This wizard only collects Brave Search, Context7, and Jina keys which are needed
|
|
13
13
|
* for web search and documentation tools.
|
|
14
|
-
*
|
|
15
|
-
* Behavior:
|
|
16
|
-
* - All optional keys present (env or auth.json): return silently
|
|
17
|
-
* - Non-TTY with missing optional keys: warn to stderr and continue (non-fatal)
|
|
18
|
-
* - TTY with missing optional keys: interactive prompts, skip on empty input
|
|
19
14
|
*/
|
|
20
15
|
export declare function runWizardIfNeeded(authStorage: AuthStorage): Promise<void>;
|
package/dist/wizard.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { createInterface } from 'readline';
|
|
2
|
+
// ─── Colors ──────────────────────────────────────────────────────────────────
|
|
3
|
+
const cyan = '\x1b[36m';
|
|
4
|
+
const green = '\x1b[32m';
|
|
5
|
+
const yellow = '\x1b[33m';
|
|
6
|
+
const dim = '\x1b[2m';
|
|
7
|
+
const bold = '\x1b[1m';
|
|
8
|
+
const reset = '\x1b[0m';
|
|
9
|
+
// ─── Masked input ─────────────────────────────────────────────────────────────
|
|
2
10
|
/**
|
|
3
|
-
*
|
|
11
|
+
* Prompt for masked input using raw mode stdin.
|
|
4
12
|
* Handles backspace, Ctrl+C, and Enter.
|
|
5
13
|
* Falls back to plain readline if setRawMode is unavailable (e.g. some SSH contexts).
|
|
6
14
|
*/
|
|
7
|
-
async function promptMasked(
|
|
15
|
+
async function promptMasked(label, hint) {
|
|
8
16
|
return new Promise((resolve) => {
|
|
17
|
+
const question = ` ${cyan}›${reset} ${label} ${dim}${hint}${reset}\n `;
|
|
9
18
|
try {
|
|
10
19
|
process.stdout.write(question);
|
|
11
20
|
process.stdin.setRawMode(true);
|
|
@@ -21,19 +30,17 @@ async function promptMasked(question) {
|
|
|
21
30
|
resolve(value);
|
|
22
31
|
}
|
|
23
32
|
else if (ch === '\u0003') {
|
|
24
|
-
// Ctrl+C — restore raw mode and exit cleanly
|
|
25
33
|
process.stdin.setRawMode(false);
|
|
26
34
|
process.stdout.write('\n');
|
|
27
35
|
process.exit(0);
|
|
28
36
|
}
|
|
29
37
|
else if (ch === '\u007f') {
|
|
30
|
-
// Backspace
|
|
31
38
|
if (value.length > 0) {
|
|
32
39
|
value = value.slice(0, -1);
|
|
33
40
|
}
|
|
34
41
|
process.stdout.clearLine(0);
|
|
35
42
|
process.stdout.cursorTo(0);
|
|
36
|
-
process.stdout.write(
|
|
43
|
+
process.stdout.write(' ' + '*'.repeat(value.length));
|
|
37
44
|
}
|
|
38
45
|
else {
|
|
39
46
|
value += ch;
|
|
@@ -43,16 +50,16 @@ async function promptMasked(question) {
|
|
|
43
50
|
process.stdin.on('data', handler);
|
|
44
51
|
}
|
|
45
52
|
catch (_err) {
|
|
46
|
-
|
|
47
|
-
process.stdout.write(' (note: input will be visible)\n');
|
|
53
|
+
process.stdout.write(` ${dim}(input will be visible)${reset}\n `);
|
|
48
54
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
49
|
-
rl.question(
|
|
55
|
+
rl.question('', (answer) => {
|
|
50
56
|
rl.close();
|
|
51
57
|
resolve(answer);
|
|
52
58
|
});
|
|
53
59
|
}
|
|
54
60
|
});
|
|
55
61
|
}
|
|
62
|
+
// ─── Env hydration ────────────────────────────────────────────────────────────
|
|
56
63
|
/**
|
|
57
64
|
* Hydrate process.env from stored auth.json credentials for optional tool keys.
|
|
58
65
|
* Runs on every launch so extensions see Brave/Context7/Jina keys stored via the
|
|
@@ -74,69 +81,84 @@ export function loadStoredEnvKeys(authStorage) {
|
|
|
74
81
|
}
|
|
75
82
|
}
|
|
76
83
|
}
|
|
84
|
+
const API_KEYS = [
|
|
85
|
+
{
|
|
86
|
+
provider: 'brave',
|
|
87
|
+
envVar: 'BRAVE_API_KEY',
|
|
88
|
+
label: 'Brave Search',
|
|
89
|
+
hint: '(search-the-web + search_and_read tools)',
|
|
90
|
+
description: 'Web search and page extraction',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
provider: 'brave_answers',
|
|
94
|
+
envVar: 'BRAVE_ANSWERS_KEY',
|
|
95
|
+
label: 'Brave Answers',
|
|
96
|
+
hint: '(AI-summarised search answers)',
|
|
97
|
+
description: 'AI-generated search summaries',
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
provider: 'context7',
|
|
101
|
+
envVar: 'CONTEXT7_API_KEY',
|
|
102
|
+
label: 'Context7',
|
|
103
|
+
hint: '(up-to-date library docs)',
|
|
104
|
+
description: 'Live library and framework documentation',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
provider: 'jina',
|
|
108
|
+
envVar: 'JINA_API_KEY',
|
|
109
|
+
label: 'Jina AI',
|
|
110
|
+
hint: '(clean page extraction)',
|
|
111
|
+
description: 'High-quality web page content extraction',
|
|
112
|
+
},
|
|
113
|
+
];
|
|
77
114
|
/**
|
|
78
115
|
* Check for missing optional tool API keys and prompt for them if on a TTY.
|
|
79
116
|
*
|
|
80
117
|
* Anthropic auth is handled by pi's own OAuth/API key flow — we don't touch it.
|
|
81
118
|
* This wizard only collects Brave Search, Context7, and Jina keys which are needed
|
|
82
119
|
* for web search and documentation tools.
|
|
83
|
-
*
|
|
84
|
-
* Behavior:
|
|
85
|
-
* - All optional keys present (env or auth.json): return silently
|
|
86
|
-
* - Non-TTY with missing optional keys: warn to stderr and continue (non-fatal)
|
|
87
|
-
* - TTY with missing optional keys: interactive prompts, skip on empty input
|
|
88
120
|
*/
|
|
89
121
|
export async function runWizardIfNeeded(authStorage) {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
const needsContext7 = !authStorage.has('context7') && !process.env.CONTEXT7_API_KEY;
|
|
93
|
-
const needsJina = !authStorage.has('jina') && !process.env.JINA_API_KEY;
|
|
94
|
-
if (!needsBrave && !needsBraveAnswers && !needsContext7 && !needsJina) {
|
|
122
|
+
const missing = API_KEYS.filter(k => !authStorage.has(k.provider) && !process.env[k.envVar]);
|
|
123
|
+
if (missing.length === 0)
|
|
95
124
|
return;
|
|
96
|
-
|
|
97
|
-
const missing = [
|
|
98
|
-
needsBrave && 'Brave Search',
|
|
99
|
-
needsBraveAnswers && 'Brave Answers',
|
|
100
|
-
needsContext7 && 'Context7',
|
|
101
|
-
needsJina && 'Jina',
|
|
102
|
-
]
|
|
103
|
-
.filter(Boolean)
|
|
104
|
-
.join(', ');
|
|
105
|
-
// Non-TTY: just warn and let the session start without them
|
|
125
|
+
// Non-TTY: warn and continue
|
|
106
126
|
if (!process.stdin.isTTY) {
|
|
107
|
-
|
|
127
|
+
const names = missing.map(k => k.label).join(', ');
|
|
128
|
+
process.stderr.write(`[gsd] Warning: optional tool API keys not configured (${names}). Some tools may not work.\n`);
|
|
108
129
|
return;
|
|
109
130
|
}
|
|
110
|
-
//
|
|
111
|
-
process.stdout.write(`\n
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
process.env.BRAVE_API_KEY = key;
|
|
118
|
-
}
|
|
131
|
+
// ── Header ──────────────────────────────────────────────────────────────────
|
|
132
|
+
process.stdout.write(`\n ${bold}Optional API keys${reset}\n` +
|
|
133
|
+
` ${dim}─────────────────────────────────────────────${reset}\n` +
|
|
134
|
+
` These unlock additional tools. All optional — press ${cyan}Enter${reset} to skip any.\n\n`);
|
|
135
|
+
// Show what each key unlocks
|
|
136
|
+
for (const key of missing) {
|
|
137
|
+
process.stdout.write(` ${dim}•${reset} ${cyan}${key.label}${reset} ${dim}— ${key.description}${reset}\n`);
|
|
119
138
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
139
|
+
process.stdout.write('\n');
|
|
140
|
+
// ── Prompts ─────────────────────────────────────────────────────────────────
|
|
141
|
+
let savedCount = 0;
|
|
142
|
+
for (const key of missing) {
|
|
143
|
+
const value = await promptMasked(key.label, key.hint);
|
|
144
|
+
if (value.trim()) {
|
|
145
|
+
authStorage.set(key.provider, { type: 'api_key', key: value.trim() });
|
|
146
|
+
process.env[key.envVar] = value.trim();
|
|
147
|
+
process.stdout.write(` ${green}✓${reset} ${key.label} saved\n\n`);
|
|
148
|
+
savedCount++;
|
|
125
149
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const key = await promptMasked('Context7 API key (optional): ');
|
|
129
|
-
if (key) {
|
|
130
|
-
authStorage.set('context7', { type: 'api_key', key });
|
|
131
|
-
process.env.CONTEXT7_API_KEY = key;
|
|
150
|
+
else {
|
|
151
|
+
process.stdout.write(` ${dim}↷ ${key.label} skipped${reset}\n\n`);
|
|
132
152
|
}
|
|
133
153
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
154
|
+
// ── Footer ───────────────────────────────────────────────────────────────────
|
|
155
|
+
process.stdout.write(` ${dim}─────────────────────────────────────────────${reset}\n`);
|
|
156
|
+
if (savedCount > 0) {
|
|
157
|
+
process.stdout.write(` ${green}✓${reset} ${savedCount} key${savedCount > 1 ? 's' : ''} saved to ${dim}~/.gsd/agent/auth.json${reset}\n` +
|
|
158
|
+
` ${dim}Run ${reset}${cyan}/login${reset}${dim} inside gsd to connect your LLM provider.${reset}\n\n`);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
process.stdout.write(` ${yellow}↷${reset} All keys skipped — you can add them later via ${dim}~/.gsd/agent/auth.json${reset}\n` +
|
|
162
|
+
` ${dim}Run ${reset}${cyan}/login${reset}${dim} inside gsd to connect your LLM provider.${reset}\n\n`);
|
|
140
163
|
}
|
|
141
|
-
process.stdout.write('[gsd] Keys saved. Starting...\n\n');
|
|
142
164
|
}
|