gsd-pi 0.1.9 → 0.2.1

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/loader.js CHANGED
@@ -12,6 +12,7 @@ const pkgDir = resolve(dirname(fileURLToPath(import.meta.url)), '..', 'pkg');
12
12
  // MUST be set before any dynamic import of pi SDK fires — this is what config.js
13
13
  // reads to determine APP_NAME and CONFIG_DIR_NAME
14
14
  process.env.PI_PACKAGE_DIR = pkgDir;
15
+ process.env.PI_SKIP_VERSION_CHECK = '1'; // GSD ships its own update check — suppress pi's
15
16
  process.title = 'gsd';
16
17
  // Print branded banner on first launch (before ~/.gsd/ exists)
17
18
  if (!existsSync(appRoot)) {
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,79 @@ 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;
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
+ // ── Prompts ─────────────────────────────────────────────────────────────────
136
+ let savedCount = 0;
137
+ for (const key of missing) {
138
+ const value = await promptMasked(key.label, key.hint);
139
+ if (value.trim()) {
140
+ authStorage.set(key.provider, { type: 'api_key', key: value.trim() });
141
+ process.env[key.envVar] = value.trim();
142
+ process.stdout.write(` ${green}✓${reset} ${key.label} saved\n\n`);
143
+ savedCount++;
118
144
  }
119
- }
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;
145
+ else {
146
+ process.stdout.write(` ${dim}↷ ${key.label} skipped${reset}\n\n`);
125
147
  }
126
148
  }
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;
132
- }
149
+ // ── Footer ───────────────────────────────────────────────────────────────────
150
+ process.stdout.write(` ${dim}─────────────────────────────────────────────${reset}\n`);
151
+ if (savedCount > 0) {
152
+ process.stdout.write(` ${green}✓${reset} ${savedCount} key${savedCount > 1 ? 's' : ''} saved to ${dim}~/.gsd/agent/auth.json${reset}\n` +
153
+ ` ${dim}Run ${reset}${cyan}/login${reset}${dim} inside gsd to connect your LLM provider.${reset}\n\n`);
133
154
  }
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
- }
155
+ else {
156
+ process.stdout.write(` ${yellow}↷${reset} All keys skipped you can add them later via ${dim}~/.gsd/agent/auth.json${reset}\n` +
157
+ ` ${dim}Run ${reset}${cyan}/login${reset}${dim} inside gsd to connect your LLM provider.${reset}\n\n`);
140
158
  }
141
- process.stdout.write('[gsd] Keys saved. Starting...\n\n');
142
159
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gsd-pi",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "description": "GSD — Get Stuff Done coding agent",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -266,8 +266,8 @@ export function makeUI(theme: Theme, width: number): UI {
266
266
  optionUnselected: (num, label, description, opts = {}) => {
267
267
  const { isCommitted = false, isFocusDimmed = false } = opts;
268
268
  const marker = isCommitted ? theme.fg("success", ` ${GLYPH.check}`) : "";
269
- const labelColor = isFocusDimmed ? "dim" : "text";
270
- const descColor = isFocusDimmed ? "dim" : "muted";
269
+ const labelColor = isFocusDimmed ? (isCommitted ? "text" : "dim") : "text";
270
+ const descColor = isFocusDimmed ? (isCommitted ? "muted" : "dim") : "muted";
271
271
  return [
272
272
  ...wrap(`${INDENT.option} ${theme.fg(labelColor, `${num}. ${label}`)}${marker}`),
273
273
  ...wrapIndented(`${INDENT.description}${theme.fg(descColor, description)}`, INDENT.description),
@@ -287,7 +287,7 @@ export function makeUI(theme: Theme, width: number): UI {
287
287
  checkboxUnselected: (label, description, isChecked, isFocusDimmed = false) => {
288
288
  const box = isChecked ? theme.fg("success", GLYPH.checkedBox) : theme.fg("dim", GLYPH.uncheckedBox);
289
289
  const labelColor = isFocusDimmed ? (isChecked ? "text" : "dim") : "text";
290
- const descColor = isFocusDimmed ? "dim" : "muted";
290
+ const descColor = isFocusDimmed ? (isChecked ? "muted" : "dim") : "muted";
291
291
  return [
292
292
  add(`${INDENT.option} ${box} ${theme.fg(labelColor, label)}`),
293
293
  ...wrapIndented(`${INDENT.description}${theme.fg(descColor, description)}`, INDENT.description),