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 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
- * Internal helper: prompt for masked input using raw mode stdin.
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(question) {
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(question + '*'.repeat(value.length));
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
- // setRawMode not available — fall back to plain readline
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(question, (answer) => {
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 needsBrave = !authStorage.has('brave') && !process.env.BRAVE_API_KEY;
91
- const needsBraveAnswers = !authStorage.has('brave_answers') && !process.env.BRAVE_ANSWERS_KEY;
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
- process.stderr.write(`[gsd] Warning: optional tool API keys not configured (${missing}). Some tools may not work.\n`);
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
- // TTY: interactive prompts for each missing key
111
- process.stdout.write(`\n[gsd] Some optional tool API keys are not configured: ${missing}\n`);
112
- process.stdout.write('[gsd] Press Enter to skip any key you want to set up later.\n\n');
113
- if (needsBrave) {
114
- const key = await promptMasked('Brave Search API key (optional, for web search + LLM context): ');
115
- if (key) {
116
- authStorage.set('brave', { type: 'api_key', key });
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
- if (needsBraveAnswers) {
121
- const key = await promptMasked('Brave Answers API key (optional, for AI-generated answers): ');
122
- if (key) {
123
- authStorage.set('brave_answers', { type: 'api_key', key });
124
- process.env.BRAVE_ANSWERS_KEY = key;
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
- if (needsContext7) {
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
- if (needsJina) {
135
- const key = await promptMasked('Jina AI API key (optional): ');
136
- if (key) {
137
- authStorage.set('jina', { type: 'api_key', key });
138
- process.env.JINA_API_KEY = key;
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-pi",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "GSD — Get Stuff Done coding agent",
5
5
  "license": "MIT",
6
6
  "repository": {