@manishbht/helpcode 0.2.3 → 0.3.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.
Files changed (49) hide show
  1. package/dist/bin/helpcode.js +0 -0
  2. package/dist/src/commands/ask.js +15 -1
  3. package/dist/src/commands/ask.js.map +1 -1
  4. package/dist/src/commands/cockpit.d.ts +13 -0
  5. package/dist/src/commands/cockpit.js +33 -0
  6. package/dist/src/commands/cockpit.js.map +1 -0
  7. package/dist/src/commands/plan.d.ts +14 -0
  8. package/dist/src/commands/plan.js +117 -0
  9. package/dist/src/commands/plan.js.map +1 -0
  10. package/dist/src/commands/run.js +55 -4
  11. package/dist/src/commands/run.js.map +1 -1
  12. package/dist/src/core/cockpit.d.ts +27 -0
  13. package/dist/src/core/cockpit.js +109 -0
  14. package/dist/src/core/cockpit.js.map +1 -0
  15. package/dist/src/core/cockpitHtml.d.ts +52 -0
  16. package/dist/src/core/cockpitHtml.js +395 -0
  17. package/dist/src/core/cockpitHtml.js.map +1 -0
  18. package/dist/src/core/consent.d.ts +20 -0
  19. package/dist/src/core/consent.js +34 -0
  20. package/dist/src/core/consent.js.map +1 -0
  21. package/dist/src/core/decompose.d.ts +45 -0
  22. package/dist/src/core/decompose.js +86 -0
  23. package/dist/src/core/decompose.js.map +1 -0
  24. package/dist/src/core/gemini.d.ts +41 -0
  25. package/dist/src/core/gemini.js +91 -0
  26. package/dist/src/core/gemini.js.map +1 -0
  27. package/dist/src/core/keys.d.ts +23 -0
  28. package/dist/src/core/keys.js +68 -0
  29. package/dist/src/core/keys.js.map +1 -0
  30. package/dist/src/core/llmSelector.d.ts +6 -0
  31. package/dist/src/core/llmSelector.js +22 -7
  32. package/dist/src/core/llmSelector.js.map +1 -1
  33. package/dist/src/core/selector.d.ts +9 -4
  34. package/dist/src/core/selector.js +88 -29
  35. package/dist/src/core/selector.js.map +1 -1
  36. package/dist/src/core/souschef.d.ts +63 -0
  37. package/dist/src/core/souschef.js +55 -0
  38. package/dist/src/core/souschef.js.map +1 -0
  39. package/dist/src/core/state.d.ts +13 -1
  40. package/dist/src/core/state.js +47 -5
  41. package/dist/src/core/state.js.map +1 -1
  42. package/dist/src/core/triage.d.ts +14 -1
  43. package/dist/src/core/triage.js +23 -5
  44. package/dist/src/core/triage.js.map +1 -1
  45. package/dist/src/index.d.ts +8 -7
  46. package/dist/src/index.js +21 -8
  47. package/dist/src/index.js.map +1 -1
  48. package/dist/src/types.d.ts +64 -1
  49. package/package.json +1 -1
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Minimal client for the Google Gemini API (free tier), via REST.
3
+ *
4
+ * Mirrors the Ollama client's discipline:
5
+ * - Zero runtime dependencies: plain fetch, no SDK.
6
+ * - fetch is injectable so tests never hit the live API and CI passes offline.
7
+ * - Every failure becomes a GeminiError; callers fall back to a cheaper
8
+ * worker (local) or deterministic path. The remote path can NEVER break the
9
+ * core flow.
10
+ *
11
+ * PRIVACY: the free tier may use inputs/outputs to train Google's models. The
12
+ * sous-chef privacy gate (core/souschef.ts) ensures only non-code task input
13
+ * (decomposition) reaches here unless the user opts into allowRemoteCode. This
14
+ * client does not enforce that — it's enforced upstream at routing time.
15
+ */
16
+ export declare class GeminiError extends Error {
17
+ /** True if the failure was a 429 RESOURCE_EXHAUSTED (free quota used up). */
18
+ readonly quotaExhausted: boolean;
19
+ constructor(message: string, quotaExhausted?: boolean);
20
+ }
21
+ type FetchImpl = (url: string, init?: RequestInit) => Promise<Response>;
22
+ export interface GeminiCallOptions {
23
+ fetchImpl?: FetchImpl;
24
+ }
25
+ export interface GeminiConfig {
26
+ apiKey: string;
27
+ /** Model id, e.g. "gemini-2.5-flash-lite". */
28
+ model: string;
29
+ timeoutMs: number;
30
+ }
31
+ /** Build the generateContent endpoint URL for a model + key. */
32
+ export declare function buildGeminiUrl(model: string, apiKey: string): string;
33
+ /** Extract concatenated text from a generateContent response body. */
34
+ export declare function parseGeminiResponse(body: any): string;
35
+ /**
36
+ * Generate text from Gemini. Returns the model's text on success; throws a
37
+ * GeminiError on any failure (with quotaExhausted set for 429s so the router
38
+ * can mark the provider throttled).
39
+ */
40
+ export declare function geminiGenerate(prompt: string, config: GeminiConfig, opts?: GeminiCallOptions): Promise<string>;
41
+ export {};
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Minimal client for the Google Gemini API (free tier), via REST.
3
+ *
4
+ * Mirrors the Ollama client's discipline:
5
+ * - Zero runtime dependencies: plain fetch, no SDK.
6
+ * - fetch is injectable so tests never hit the live API and CI passes offline.
7
+ * - Every failure becomes a GeminiError; callers fall back to a cheaper
8
+ * worker (local) or deterministic path. The remote path can NEVER break the
9
+ * core flow.
10
+ *
11
+ * PRIVACY: the free tier may use inputs/outputs to train Google's models. The
12
+ * sous-chef privacy gate (core/souschef.ts) ensures only non-code task input
13
+ * (decomposition) reaches here unless the user opts into allowRemoteCode. This
14
+ * client does not enforce that — it's enforced upstream at routing time.
15
+ */
16
+ export class GeminiError extends Error {
17
+ /** True if the failure was a 429 RESOURCE_EXHAUSTED (free quota used up). */
18
+ quotaExhausted;
19
+ constructor(message, quotaExhausted = false) {
20
+ super(message);
21
+ this.name = 'GeminiError';
22
+ this.quotaExhausted = quotaExhausted;
23
+ }
24
+ }
25
+ const DEFAULT_GENERATE_TIMEOUT = 20000;
26
+ const API_BASE = 'https://generativelanguage.googleapis.com/v1beta/models';
27
+ /** Build the generateContent endpoint URL for a model + key. */
28
+ export function buildGeminiUrl(model, apiKey) {
29
+ return `${API_BASE}/${model}:generateContent?key=${apiKey}`;
30
+ }
31
+ /** Extract concatenated text from a generateContent response body. */
32
+ export function parseGeminiResponse(body) {
33
+ const cand = body?.candidates?.[0];
34
+ const parts = cand?.content?.parts;
35
+ if (!Array.isArray(parts))
36
+ return '';
37
+ return parts.map((p) => (typeof p?.text === 'string' ? p.text : '')).join('');
38
+ }
39
+ function getFetch(opts) {
40
+ return opts?.fetchImpl ?? globalThis.fetch;
41
+ }
42
+ /**
43
+ * Generate text from Gemini. Returns the model's text on success; throws a
44
+ * GeminiError on any failure (with quotaExhausted set for 429s so the router
45
+ * can mark the provider throttled).
46
+ */
47
+ export async function geminiGenerate(prompt, config, opts) {
48
+ const fetchImpl = getFetch(opts);
49
+ const timeoutMs = config.timeoutMs ?? DEFAULT_GENERATE_TIMEOUT;
50
+ const url = buildGeminiUrl(config.model, config.apiKey);
51
+ const requestBody = {
52
+ contents: [{ parts: [{ text: prompt }] }],
53
+ };
54
+ const controller = new AbortController();
55
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
56
+ let response;
57
+ try {
58
+ response = await fetchImpl(url, {
59
+ method: 'POST',
60
+ headers: { 'content-type': 'application/json' },
61
+ body: JSON.stringify(requestBody),
62
+ signal: controller.signal,
63
+ });
64
+ }
65
+ catch (e) {
66
+ clearTimeout(timer);
67
+ const why = e?.name === 'AbortError' ? 'timed out' : e.message;
68
+ throw new GeminiError(`request failed: ${why}`);
69
+ }
70
+ clearTimeout(timer);
71
+ // Parse the body (may be an error envelope).
72
+ let body;
73
+ try {
74
+ body = await response.json();
75
+ }
76
+ catch {
77
+ throw new GeminiError(`bad response (status ${response.status}, non-JSON body)`);
78
+ }
79
+ if (!response.ok) {
80
+ const status = body?.error?.status ?? '';
81
+ const msg = body?.error?.message ?? `HTTP ${response.status}`;
82
+ const isQuota = response.status === 429 || status === 'RESOURCE_EXHAUSTED';
83
+ throw new GeminiError(`Gemini error: ${msg}`, isQuota);
84
+ }
85
+ const text = parseGeminiResponse(body).trim();
86
+ if (!text) {
87
+ throw new GeminiError('Gemini returned no usable text');
88
+ }
89
+ return text;
90
+ }
91
+ //# sourceMappingURL=gemini.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gemini.js","sourceRoot":"","sources":["../../../src/core/gemini.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,OAAO,WAAY,SAAQ,KAAK;IACpC,6EAA6E;IACpE,cAAc,CAAU;IACjC,YAAY,OAAe,EAAE,cAAc,GAAG,KAAK;QACjD,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC;IACvC,CAAC;CACF;AAeD,MAAM,wBAAwB,GAAG,KAAK,CAAC;AACvC,MAAM,QAAQ,GAAG,yDAAyD,CAAC;AAE3E,gEAAgE;AAChE,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,MAAc;IAC1D,OAAO,GAAG,QAAQ,IAAI,KAAK,wBAAwB,MAAM,EAAE,CAAC;AAC9D,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,mBAAmB,CAAC,IAAS;IAC3C,MAAM,IAAI,GAAG,IAAI,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,KAAK,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC;IACnC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACrC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACrF,CAAC;AAED,SAAS,QAAQ,CAAC,IAAwB;IACxC,OAAO,IAAI,EAAE,SAAS,IAAK,UAAU,CAAC,KAAmB,CAAC;AAC5D,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAAc,EACd,MAAoB,EACpB,IAAwB;IAExB,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACjC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,IAAI,wBAAwB,CAAC;IAC/D,MAAM,GAAG,GAAG,cAAc,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;IAExD,MAAM,WAAW,GAAG;QAClB,QAAQ,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC;KAC1C,CAAC;IAEF,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAE9D,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;YAC9B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;YACjC,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,YAAY,CAAC,KAAK,CAAC,CAAC;QACpB,MAAM,GAAG,GAAI,CAAW,EAAE,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAE,CAAW,CAAC,OAAO,CAAC;QACrF,MAAM,IAAI,WAAW,CAAC,mBAAmB,GAAG,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,YAAY,CAAC,KAAK,CAAC,CAAC;IAEpB,6CAA6C;IAC7C,IAAI,IAAS,CAAC;IACd,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,WAAW,CAAC,wBAAwB,QAAQ,CAAC,MAAM,kBAAkB,CAAC,CAAC;IACnF,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC;QACzC,MAAM,GAAG,GAAG,IAAI,EAAE,KAAK,EAAE,OAAO,IAAI,QAAQ,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC9D,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,oBAAoB,CAAC;QAC3E,MAAM,IAAI,WAAW,CAAC,iBAAiB,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;IACzD,CAAC;IAED,MAAM,IAAI,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,WAAW,CAAC,gCAAgC,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * API key loading for remote sous-chefs (v0.3.2).
3
+ *
4
+ * Keys are secrets and must never touch project.json (which is committed).
5
+ * Resolution order:
6
+ * 1. GEMINI_API_KEY environment variable (precedence; nothing on disk)
7
+ * 2. .helpcode/keys.json { "gemini": "..." } (gitignored by init)
8
+ *
9
+ * Returns null if no key is configured — in which case the remote sous-chef
10
+ * simply doesn't exist and helpcode behaves exactly as v0.3.1 (local only).
11
+ * Never throws: a missing or malformed keys.json yields null, not an error.
12
+ */
13
+ /**
14
+ * Resolve the Gemini API key. `env` is injectable for testing (defaults to
15
+ * process.env). Returns the key string, or null if none is configured.
16
+ */
17
+ export declare function loadGeminiKey(cwd?: string, env?: Record<string, string | undefined>): string | null;
18
+ /**
19
+ * Whether a keys.json exists but is NOT gitignored — a footgun worth warning
20
+ * about (the user could commit their key). Best-effort: checks for a literal
21
+ * keys.json or .helpcode/ entry in .gitignore.
22
+ */
23
+ export declare function keysFileAtRiskOfCommit(cwd?: string): boolean;
@@ -0,0 +1,68 @@
1
+ /**
2
+ * API key loading for remote sous-chefs (v0.3.2).
3
+ *
4
+ * Keys are secrets and must never touch project.json (which is committed).
5
+ * Resolution order:
6
+ * 1. GEMINI_API_KEY environment variable (precedence; nothing on disk)
7
+ * 2. .helpcode/keys.json { "gemini": "..." } (gitignored by init)
8
+ *
9
+ * Returns null if no key is configured — in which case the remote sous-chef
10
+ * simply doesn't exist and helpcode behaves exactly as v0.3.1 (local only).
11
+ * Never throws: a missing or malformed keys.json yields null, not an error.
12
+ */
13
+ import * as fs from 'fs';
14
+ import * as path from 'path';
15
+ const KEYS_FILE = path.join('.helpcode', 'keys.json');
16
+ /**
17
+ * Resolve the Gemini API key. `env` is injectable for testing (defaults to
18
+ * process.env). Returns the key string, or null if none is configured.
19
+ */
20
+ export function loadGeminiKey(cwd = process.cwd(), env = process.env) {
21
+ // 1. Environment variable wins (and leaves nothing on disk).
22
+ const fromEnv = env.GEMINI_API_KEY;
23
+ if (typeof fromEnv === 'string' && fromEnv.trim().length > 0) {
24
+ return fromEnv.trim();
25
+ }
26
+ // 2. .helpcode/keys.json
27
+ const file = path.join(cwd, KEYS_FILE);
28
+ if (!fs.existsSync(file))
29
+ return null;
30
+ try {
31
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
32
+ const k = parsed?.gemini;
33
+ if (typeof k === 'string' && k.trim().length > 0)
34
+ return k.trim();
35
+ return null;
36
+ }
37
+ catch {
38
+ // Malformed file: behave as if no key (never throw).
39
+ return null;
40
+ }
41
+ }
42
+ /**
43
+ * Whether a keys.json exists but is NOT gitignored — a footgun worth warning
44
+ * about (the user could commit their key). Best-effort: checks for a literal
45
+ * keys.json or .helpcode/ entry in .gitignore.
46
+ */
47
+ export function keysFileAtRiskOfCommit(cwd = process.cwd()) {
48
+ const keysPath = path.join(cwd, KEYS_FILE);
49
+ if (!fs.existsSync(keysPath))
50
+ return false;
51
+ const gitignorePath = path.join(cwd, '.gitignore');
52
+ if (!fs.existsSync(gitignorePath))
53
+ return true; // exists, nothing ignoring it
54
+ try {
55
+ const ignore = fs.readFileSync(gitignorePath, 'utf-8');
56
+ const lines = ignore.split(/\r?\n/).map(l => l.trim());
57
+ const covered = lines.some(l => l === '.helpcode/' ||
58
+ l === '.helpcode' ||
59
+ l === '.helpcode/keys.json' ||
60
+ l === 'keys.json' ||
61
+ l === '.helpcode/*');
62
+ return !covered;
63
+ }
64
+ catch {
65
+ return true;
66
+ }
67
+ }
68
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/core/keys.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAEtD;;;GAGG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAc,OAAO,CAAC,GAAG,EAAE,EAC3B,MAA0C,OAAO,CAAC,GAAG;IAErD,6DAA6D;IAC7D,MAAM,OAAO,GAAG,GAAG,CAAC,cAAc,CAAC;IACnC,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7D,OAAO,OAAO,CAAC,IAAI,EAAE,CAAC;IACxB,CAAC;IAED,yBAAyB;IACzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IACvC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1D,MAAM,CAAC,GAAG,MAAM,EAAE,MAAM,CAAC;QACzB,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAClE,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,qDAAqD;QACrD,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,MAAc,OAAO,CAAC,GAAG,EAAE;IAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAE3C,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IACnD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,8BAA8B;IAE9E,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACvD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAC7B,CAAC,KAAK,YAAY;YAClB,CAAC,KAAK,WAAW;YACjB,CAAC,KAAK,qBAAqB;YAC3B,CAAC,KAAK,WAAW;YACjB,CAAC,KAAK,aAAa,CACpB,CAAC;QACF,OAAO,CAAC,OAAO,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -56,6 +56,12 @@ export declare function parseSelectionResponse(response: string, candidates: Can
56
56
  * "model ran but found nothing" (empty array) from "couldn't reach model"
57
57
  * (throw) — both lead to fallback, but the caller may log them differently.
58
58
  */
59
+ /**
60
+ * Backend-agnostic file selection: builds the prompt, calls the provided
61
+ * generate function (Ollama or Gemini or any text model), parses + validates.
62
+ * The empty-file filter and hallucination guard apply regardless of backend.
63
+ */
64
+ export declare function selectFilesWithGenerate(task: string, candidatePaths: string[], projectRoot: string, count: number, generateFn: (prompt: string) => Promise<string>): Promise<Selection[]>;
59
65
  export declare function llmSelectFiles(task: string, candidatePaths: string[], projectRoot: string, opts: {
60
66
  host: string;
61
67
  model: string;
@@ -71,7 +71,8 @@ export function buildSelectionPrompt(task, candidates, count) {
71
71
  lines.push(`- ${c.path}: ${c.signature}`);
72
72
  }
73
73
  lines.push('');
74
- lines.push(`Return the ${count} files most relevant to the task, most relevant first.`);
74
+ lines.push(`Return UP TO ${count} files that are genuinely relevant to the task, most relevant first.`);
75
+ lines.push('Include FEWER than ' + count + ' if fewer are truly relevant — do not pad the list with files that are empty or unrelated.');
75
76
  lines.push('Format each line exactly as:');
76
77
  lines.push(' PATH | one-line reason');
77
78
  lines.push('Only use paths from the candidate list above. No prose, no other text.');
@@ -118,17 +119,31 @@ export function parseSelectionResponse(response, candidates) {
118
119
  * "model ran but found nothing" (empty array) from "couldn't reach model"
119
120
  * (throw) — both lead to fallback, but the caller may log them differently.
120
121
  */
121
- export async function llmSelectFiles(task, candidatePaths, projectRoot, opts) {
122
- const candidates = candidatePaths.map(p => ({
122
+ /**
123
+ * Backend-agnostic file selection: builds the prompt, calls the provided
124
+ * generate function (Ollama or Gemini or any text model), parses + validates.
125
+ * The empty-file filter and hallucination guard apply regardless of backend.
126
+ */
127
+ export async function selectFilesWithGenerate(task, candidatePaths, projectRoot, count, generateFn) {
128
+ const meaningful = candidatePaths.filter(p => {
129
+ try {
130
+ return fs.readFileSync(p, 'utf-8').trim().length >= 10;
131
+ }
132
+ catch {
133
+ return false;
134
+ }
135
+ });
136
+ const candidates = meaningful.map(p => ({
123
137
  path: path.relative(projectRoot, p),
124
138
  signature: buildSignature(p),
125
139
  }));
126
- const prompt = buildSelectionPrompt(task, candidates, opts.count);
127
- const response = await generate(opts.host, opts.model, prompt, {
128
- timeoutMs: opts.timeoutMs,
129
- });
140
+ const prompt = buildSelectionPrompt(task, candidates, count);
141
+ const response = await generateFn(prompt);
130
142
  return parseSelectionResponse(response, candidates);
131
143
  }
144
+ export async function llmSelectFiles(task, candidatePaths, projectRoot, opts) {
145
+ return selectFilesWithGenerate(task, candidatePaths, projectRoot, opts.count, (prompt) => generate(opts.host, opts.model, prompt, { timeoutMs: opts.timeoutMs }));
146
+ }
132
147
  // Re-export for callers that want to detect transport errors specifically.
133
148
  export { OllamaError };
134
149
  //# sourceMappingURL=llmSelector.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"llmSelector.js","sourceRoot":"","sources":["../../../src/core/llmSelector.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"llmSelector.js","sourceRoot":"","sources":["../../../src/core/llmSelector.ts"],"names":[],"mappings":"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"}
@@ -23,14 +23,19 @@ export interface SelectedFile {
23
23
  export interface SelectionResult {
24
24
  files: SelectedFile[];
25
25
  /** Which strategy actually produced the result. */
26
- strategy: 'llm' | 'heuristic';
26
+ strategy: 'llm' | 'remote' | 'heuristic';
27
27
  /** If the LLM was attempted but fell back, why. Empty otherwise. */
28
28
  fallbackReason: string;
29
+ /** For the remote strategy: the model used (for the cockpit). */
30
+ remoteModel?: string;
29
31
  }
30
32
  /**
31
- * The entry point used by `ask`. Chooses the LLM strategy when configured and
32
- * reachable, otherwise the heuristic. Never throws any LLM failure degrades
33
- * to the heuristic with a recorded reason.
33
+ * The entry point used by `ask`. Chooses, cheapest-first:
34
+ * local LLM (Ollama) remote free-tier (if opted in) keyword heuristic.
35
+ * Never throws — any model failure degrades to the next option with a reason.
36
+ *
37
+ * The remote branch only runs when allowRemoteCode is on AND a key is present:
38
+ * file selection sends file signatures (code), so it's privacy-gated.
34
39
  */
35
40
  export declare function selectFilesWithStrategy(taskDescription: string, config: ProjectConfig, opts?: {
36
41
  forceHeuristic?: boolean;
@@ -14,8 +14,32 @@
14
14
  */
15
15
  import * as fs from 'fs';
16
16
  import * as path from 'path';
17
- import { isOllamaReachable, OllamaError } from './ollama.js';
18
- import { llmSelectFiles } from './llmSelector.js';
17
+ import { isOllamaReachable } from './ollama.js';
18
+ import { llmSelectFiles, selectFilesWithGenerate } from './llmSelector.js';
19
+ import { geminiGenerate } from './gemini.js';
20
+ import { loadGeminiKey } from './keys.js';
21
+ import { shouldShowRemoteCodeNotice, remoteCodeNoticeText } from './consent.js';
22
+ import { loadState, saveState } from './state.js';
23
+ const REMOTE_SELECTION_MODEL = 'gemini-2.5-flash-lite';
24
+ /**
25
+ * Show the one-time "code is leaving the machine to a free tier" notice if it
26
+ * hasn't been shown before, and persist that it has. Best-effort; never throws.
27
+ */
28
+ function maybeShowRemoteCodeNotice(task, model) {
29
+ try {
30
+ const state = loadState();
31
+ const alreadyShown = state.flags?.remoteCodeNoticeShown === true;
32
+ if (shouldShowRemoteCodeNotice({ allowRemoteCode: true, task, alreadyShown })) {
33
+ // eslint-disable-next-line no-console
34
+ console.error('\n' + remoteCodeNoticeText(model) + '\n');
35
+ state.flags = { ...(state.flags ?? {}), remoteCodeNoticeShown: true };
36
+ saveState(state);
37
+ }
38
+ }
39
+ catch {
40
+ // never block selection on the notice
41
+ }
42
+ }
19
43
  const IGNORE_DIRS = new Set([
20
44
  '.git', '.venv', 'venv', 'env', 'node_modules', '__pycache__',
21
45
  '.pytest_cache', '.mypy_cache', 'dist', 'build', '.next',
@@ -29,42 +53,77 @@ const MAX_RESULTS = 6;
29
53
  const RECENCY_WINDOW_DAYS = 14;
30
54
  const LLM_CANDIDATE_CAP = 60; // cap files sent to the model (design §3.1)
31
55
  /**
32
- * The entry point used by `ask`. Chooses the LLM strategy when configured and
33
- * reachable, otherwise the heuristic. Never throws any LLM failure degrades
34
- * to the heuristic with a recorded reason.
56
+ * The entry point used by `ask`. Chooses, cheapest-first:
57
+ * local LLM (Ollama) remote free-tier (if opted in) keyword heuristic.
58
+ * Never throws — any model failure degrades to the next option with a reason.
59
+ *
60
+ * The remote branch only runs when allowRemoteCode is on AND a key is present:
61
+ * file selection sends file signatures (code), so it's privacy-gated.
35
62
  */
36
63
  export async function selectFilesWithStrategy(taskDescription, config, opts = {}) {
37
64
  const ollama = config.ollama;
38
65
  const wantLlm = !opts.forceHeuristic && ollama?.enabled === true;
39
66
  if (wantLlm && ollama) {
40
67
  const reachable = await isOllamaReachable(ollama.host, { timeoutMs: 1000 });
41
- if (!reachable) {
42
- return heuristicResult(taskDescription, config, 'Ollama not reachable');
43
- }
44
- try {
45
- const allFiles = walkAllSourceFiles(config).slice(0, LLM_CANDIDATE_CAP);
46
- const selections = await llmSelectFiles(taskDescription, allFiles, config.root, {
47
- host: ollama.host,
48
- model: ollama.model,
49
- count: MAX_RESULTS,
50
- timeoutMs: ollama.timeoutMs,
51
- });
52
- if (selections.length === 0) {
53
- return heuristicResult(taskDescription, config, 'model returned no usable files');
68
+ if (reachable) {
69
+ try {
70
+ const allFiles = walkAllSourceFiles(config).slice(0, LLM_CANDIDATE_CAP);
71
+ const selections = await llmSelectFiles(taskDescription, allFiles, config.root, {
72
+ host: ollama.host,
73
+ model: ollama.model,
74
+ count: MAX_RESULTS,
75
+ timeoutMs: ollama.timeoutMs,
76
+ });
77
+ if (selections.length > 0) {
78
+ const files = selections.map(s => ({
79
+ filepath: path.join(config.root, s.path),
80
+ reason: s.reason,
81
+ }));
82
+ return { files, strategy: 'llm', fallbackReason: '' };
83
+ }
84
+ // fall through to remote/heuristic
85
+ }
86
+ catch {
87
+ // fall through to remote/heuristic
54
88
  }
55
- // Map relative paths back to absolute for the caller
56
- const files = selections.map(s => ({
57
- filepath: path.join(config.root, s.path),
58
- reason: s.reason,
59
- }));
60
- return { files, strategy: 'llm', fallbackReason: '' };
61
- }
62
- catch (e) {
63
- const why = e instanceof OllamaError ? e.message : e.message;
64
- return heuristicResult(taskDescription, config, why);
65
89
  }
66
90
  }
67
- return heuristicResult(taskDescription, config, '');
91
+ // Remote branch: only if NOT forcing heuristic, the project opted into
92
+ // sending code remotely, and a key is configured. Selection sends code
93
+ // (file signatures), so the privacy gate requires allowRemoteCode.
94
+ if (!opts.forceHeuristic && config.remote?.allowRemoteCode === true) {
95
+ const remote = await tryRemoteSelection(taskDescription, config);
96
+ if (remote)
97
+ return remote;
98
+ }
99
+ return heuristicResult(taskDescription, config, wantLlm ? 'local model unavailable; used keyword heuristic' : '');
100
+ }
101
+ /**
102
+ * Attempt remote file selection via Gemini. Returns null on any failure (caller
103
+ * falls back to heuristic). Shows the one-time code-consent notice on first use.
104
+ */
105
+ async function tryRemoteSelection(taskDescription, config) {
106
+ const key = loadGeminiKey();
107
+ if (!key)
108
+ return null;
109
+ // One-time consent notice (code is about to leave the machine to a free tier).
110
+ maybeShowRemoteCodeNotice('file_selection', REMOTE_SELECTION_MODEL);
111
+ try {
112
+ const allFiles = walkAllSourceFiles(config).slice(0, LLM_CANDIDATE_CAP);
113
+ const selections = await selectFilesWithGenerate(taskDescription, allFiles, config.root, MAX_RESULTS, (prompt) => geminiGenerate(prompt, {
114
+ apiKey: key, model: REMOTE_SELECTION_MODEL, timeoutMs: 30000,
115
+ }));
116
+ if (selections.length === 0)
117
+ return null;
118
+ const files = selections.map(s => ({
119
+ filepath: path.join(config.root, s.path),
120
+ reason: s.reason,
121
+ }));
122
+ return { files, strategy: 'remote', fallbackReason: '', remoteModel: REMOTE_SELECTION_MODEL };
123
+ }
124
+ catch {
125
+ return null;
126
+ }
68
127
  }
69
128
  function heuristicResult(taskDescription, config, fallbackReason) {
70
129
  const files = selectFiles(taskDescription, config).map(filepath => ({
@@ -1 +1 @@
1
- {"version":3,"file":"selector.js","sourceRoot":"","sources":["../../../src/core/selector.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"selector.js","sourceRoot":"","sources":["../../../src/core/selector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The sous-chef layer (v0.3.2) — makes local and remote workers interchangeable
3
+ * behind one interface, with a privacy gate and cheapest-capable-first routing.
4
+ *
5
+ * Design doc: docs/v0.3.2-remote-souschef-design.md
6
+ *
7
+ * The privacy gate is the load-bearing rule: a REMOTE free-tier worker may only
8
+ * receive data the user has accepted leaving the machine. By default that means
9
+ * decomposition only (input is the task description, not code). A per-project
10
+ * `allowRemoteCode` opt-in lets consenting users route code-bearing tasks too.
11
+ *
12
+ * Routing is cheapest-capable-first: local before remote. A remote worker is
13
+ * only chosen when (a) the privacy gate permits the task, (b) no local worker
14
+ * can handle it, and (c) the worker's own canHandle() says yes (e.g. has quota).
15
+ */
16
+ import { SousChefTask } from '../types.js';
17
+ export interface QuotaStatus {
18
+ /** Free requests used in the current day, if tracked. */
19
+ usedToday: number;
20
+ /** Daily limit, if known. */
21
+ limitPerDay: number | null;
22
+ /** True if the provider is currently throttled (e.g. after a 429). */
23
+ throttled: boolean;
24
+ }
25
+ export interface WorkerContext {
26
+ /** Project opt-in: may code-bearing tasks go to remote free tiers? */
27
+ allowRemoteCode: boolean;
28
+ }
29
+ export interface SousChef {
30
+ /** Stable id, e.g. "local:qwen2.5-coder:7b" or "gemini:2.5-flash-lite". */
31
+ id: string;
32
+ kind: 'local' | 'remote';
33
+ /**
34
+ * Whether this worker can take this task right now, ignoring privacy (that's
35
+ * enforced separately in canWorkerHandle). Used for quota/availability vetoes.
36
+ */
37
+ canHandle: (task: SousChefTask, ctx: WorkerContext) => boolean;
38
+ /** Do the work. Throws on failure; callers fall back to a cheaper worker. */
39
+ run: (prompt: string, opts?: {
40
+ timeoutMs?: number;
41
+ }) => Promise<string>;
42
+ /** Remote workers report quota for the cockpit + routing; local returns null. */
43
+ quota: () => QuotaStatus | null;
44
+ }
45
+ /**
46
+ * The privacy gate. Which tasks may a REMOTE worker receive?
47
+ * - decomposition: always (input is the user's task description, no code)
48
+ * - everything else: only if the project opted into allowRemoteCode
49
+ *
50
+ * Local workers are never gated by this — code never leaves the machine.
51
+ */
52
+ export declare function isRemoteAllowedForTask(task: SousChefTask, allowRemoteCode: boolean): boolean;
53
+ /**
54
+ * Can this specific worker handle this task, accounting for privacy AND the
55
+ * worker's own availability veto?
56
+ */
57
+ export declare function canWorkerHandle(worker: SousChef, task: SousChefTask, ctx: WorkerContext): boolean;
58
+ /**
59
+ * Pick the cheapest capable worker. Local workers are cheaper than remote, so
60
+ * they're preferred; within a kind, the first in the list wins (stable order).
61
+ * Returns null if nothing can handle the task.
62
+ */
63
+ export declare function pickWorker(workers: SousChef[], task: SousChefTask, ctx: WorkerContext): SousChef | null;