@manishbht/helpcode 0.2.3 → 0.3.5

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 (55) 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 +113 -0
  9. package/dist/src/commands/plan.js.map +1 -0
  10. package/dist/src/commands/run.js +51 -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 +29 -0
  28. package/dist/src/core/keys.js +92 -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/providers.d.ts +60 -0
  34. package/dist/src/core/providers.js +117 -0
  35. package/dist/src/core/providers.js.map +1 -0
  36. package/dist/src/core/remoteRouter.d.ts +27 -0
  37. package/dist/src/core/remoteRouter.js +71 -0
  38. package/dist/src/core/remoteRouter.js.map +1 -0
  39. package/dist/src/core/selector.d.ts +9 -4
  40. package/dist/src/core/selector.js +85 -29
  41. package/dist/src/core/selector.js.map +1 -1
  42. package/dist/src/core/souschef.d.ts +63 -0
  43. package/dist/src/core/souschef.js +55 -0
  44. package/dist/src/core/souschef.js.map +1 -0
  45. package/dist/src/core/state.d.ts +13 -1
  46. package/dist/src/core/state.js +47 -5
  47. package/dist/src/core/state.js.map +1 -1
  48. package/dist/src/core/triage.d.ts +14 -1
  49. package/dist/src/core/triage.js +23 -5
  50. package/dist/src/core/triage.js.map +1 -1
  51. package/dist/src/index.d.ts +8 -7
  52. package/dist/src/index.js +21 -8
  53. package/dist/src/index.js.map +1 -1
  54. package/dist/src/types.d.ts +70 -1
  55. package/package.json +1 -1
@@ -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,30 @@
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 { firstAvailableProvider } from './remoteRouter.js';
20
+ import { shouldShowRemoteCodeNotice, remoteCodeNoticeText } from './consent.js';
21
+ import { loadState, saveState } from './state.js';
22
+ /**
23
+ * Show the one-time "code is leaving the machine to a free tier" notice if it
24
+ * hasn't been shown before, and persist that it has. Best-effort; never throws.
25
+ */
26
+ function maybeShowRemoteCodeNotice(task, model) {
27
+ try {
28
+ const state = loadState();
29
+ const alreadyShown = state.flags?.remoteCodeNoticeShown === true;
30
+ if (shouldShowRemoteCodeNotice({ allowRemoteCode: true, task, alreadyShown })) {
31
+ // eslint-disable-next-line no-console
32
+ console.error('\n' + remoteCodeNoticeText(model) + '\n');
33
+ state.flags = { ...(state.flags ?? {}), remoteCodeNoticeShown: true };
34
+ saveState(state);
35
+ }
36
+ }
37
+ catch {
38
+ // never block selection on the notice
39
+ }
40
+ }
19
41
  const IGNORE_DIRS = new Set([
20
42
  '.git', '.venv', 'venv', 'env', 'node_modules', '__pycache__',
21
43
  '.pytest_cache', '.mypy_cache', 'dist', 'build', '.next',
@@ -29,42 +51,76 @@ const MAX_RESULTS = 6;
29
51
  const RECENCY_WINDOW_DAYS = 14;
30
52
  const LLM_CANDIDATE_CAP = 60; // cap files sent to the model (design §3.1)
31
53
  /**
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.
54
+ * The entry point used by `ask`. Chooses, cheapest-first:
55
+ * local LLM (Ollama) remote free-tier (if opted in) keyword heuristic.
56
+ * Never throws — any model failure degrades to the next option with a reason.
57
+ *
58
+ * The remote branch only runs when allowRemoteCode is on AND a key is present:
59
+ * file selection sends file signatures (code), so it's privacy-gated.
35
60
  */
36
61
  export async function selectFilesWithStrategy(taskDescription, config, opts = {}) {
37
62
  const ollama = config.ollama;
38
63
  const wantLlm = !opts.forceHeuristic && ollama?.enabled === true;
39
64
  if (wantLlm && ollama) {
40
65
  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');
66
+ if (reachable) {
67
+ try {
68
+ const allFiles = walkAllSourceFiles(config).slice(0, LLM_CANDIDATE_CAP);
69
+ const selections = await llmSelectFiles(taskDescription, allFiles, config.root, {
70
+ host: ollama.host,
71
+ model: ollama.model,
72
+ count: MAX_RESULTS,
73
+ timeoutMs: ollama.timeoutMs,
74
+ });
75
+ if (selections.length > 0) {
76
+ const files = selections.map(s => ({
77
+ filepath: path.join(config.root, s.path),
78
+ reason: s.reason,
79
+ }));
80
+ return { files, strategy: 'llm', fallbackReason: '' };
81
+ }
82
+ // fall through to remote/heuristic
83
+ }
84
+ catch {
85
+ // fall through to remote/heuristic
54
86
  }
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
87
  }
66
88
  }
67
- return heuristicResult(taskDescription, config, '');
89
+ // Remote branch: only if NOT forcing heuristic, the project opted into
90
+ // sending code remotely, and a key is configured. Selection sends code
91
+ // (file signatures), so the privacy gate requires allowRemoteCode.
92
+ if (!opts.forceHeuristic && config.remote?.allowRemoteCode === true) {
93
+ const remote = await tryRemoteSelection(taskDescription, config);
94
+ if (remote)
95
+ return remote;
96
+ }
97
+ return heuristicResult(taskDescription, config, wantLlm ? 'local model unavailable; used keyword heuristic' : '');
98
+ }
99
+ /**
100
+ * Attempt remote file selection via the first available free-tier provider.
101
+ * Returns null on any failure (caller falls back to heuristic). Shows the
102
+ * one-time code-consent notice on first use.
103
+ */
104
+ async function tryRemoteSelection(taskDescription, config) {
105
+ const provider = firstAvailableProvider(process.env, config.remote?.provider);
106
+ if (!provider)
107
+ return null;
108
+ // One-time consent notice (code is about to leave the machine to a free tier).
109
+ maybeShowRemoteCodeNotice('file_selection', `${provider.label} · ${provider.model}`);
110
+ try {
111
+ const allFiles = walkAllSourceFiles(config).slice(0, LLM_CANDIDATE_CAP);
112
+ const selections = await selectFilesWithGenerate(taskDescription, allFiles, config.root, MAX_RESULTS, (prompt) => provider.generate(prompt));
113
+ if (selections.length === 0)
114
+ return null;
115
+ const files = selections.map(s => ({
116
+ filepath: path.join(config.root, s.path),
117
+ reason: s.reason,
118
+ }));
119
+ return { files, strategy: 'remote', fallbackReason: '', remoteModel: provider.model };
120
+ }
121
+ catch {
122
+ return null;
123
+ }
68
124
  }
69
125
  function heuristicResult(taskDescription, config, fallbackReason) {
70
126
  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;
@@ -0,0 +1,55 @@
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
+ /**
17
+ * The privacy gate. Which tasks may a REMOTE worker receive?
18
+ * - decomposition: always (input is the user's task description, no code)
19
+ * - everything else: only if the project opted into allowRemoteCode
20
+ *
21
+ * Local workers are never gated by this — code never leaves the machine.
22
+ */
23
+ export function isRemoteAllowedForTask(task, allowRemoteCode) {
24
+ if (task === 'decomposition')
25
+ return true;
26
+ return allowRemoteCode;
27
+ }
28
+ /**
29
+ * Can this specific worker handle this task, accounting for privacy AND the
30
+ * worker's own availability veto?
31
+ */
32
+ export function canWorkerHandle(worker, task, ctx) {
33
+ if (worker.kind === 'remote' && !isRemoteAllowedForTask(task, ctx.allowRemoteCode)) {
34
+ return false;
35
+ }
36
+ return worker.canHandle(task, ctx);
37
+ }
38
+ /**
39
+ * Pick the cheapest capable worker. Local workers are cheaper than remote, so
40
+ * they're preferred; within a kind, the first in the list wins (stable order).
41
+ * Returns null if nothing can handle the task.
42
+ */
43
+ export function pickWorker(workers, task, ctx) {
44
+ const capable = workers.filter(w => canWorkerHandle(w, task, ctx));
45
+ if (capable.length === 0)
46
+ return null;
47
+ // local (cheaper) before remote; preserve input order within a kind.
48
+ const localFirst = [...capable].sort((a, b) => {
49
+ if (a.kind === b.kind)
50
+ return 0;
51
+ return a.kind === 'local' ? -1 : 1;
52
+ });
53
+ return localFirst[0];
54
+ }
55
+ //# sourceMappingURL=souschef.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"souschef.js","sourceRoot":"","sources":["../../../src/core/souschef.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAiCH;;;;;;GAMG;AACH,MAAM,UAAU,sBAAsB,CACpC,IAAkB,EAClB,eAAwB;IAExB,IAAI,IAAI,KAAK,eAAe;QAAE,OAAO,IAAI,CAAC;IAC1C,OAAO,eAAe,CAAC;AACzB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAgB,EAChB,IAAkB,EAClB,GAAkB;IAElB,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,sBAAsB,CAAC,IAAI,EAAE,GAAG,CAAC,eAAe,CAAC,EAAE,CAAC;QACnF,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,MAAM,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,UAAU,CACxB,OAAmB,EACnB,IAAkB,EAClB,GAAkB;IAElB,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC;IACnE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,qEAAqE;IACrE,MAAM,UAAU,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAC5C,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI;YAAE,OAAO,CAAC,CAAC;QAChC,OAAO,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;IACH,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC;AACvB,CAAC"}
@@ -4,8 +4,20 @@
4
4
  * State is intentionally readable JSON. Users can `cat` it to see exactly
5
5
  * what helpcode is thinking. No black-box memory.
6
6
  */
7
- import { AgentState, CurrentTask } from '../types.js';
7
+ import { AgentState, CurrentTask, SousChefEvent } from '../types.js';
8
8
  export declare function loadState(cwd?: string): AgentState;
9
9
  export declare function saveState(state: AgentState, cwd?: string): void;
10
+ /**
11
+ * Append a sous-chef event to an in-memory state object (capped). Use this
12
+ * when the caller already holds a loaded state and will saveState() itself —
13
+ * avoids the read-modify-write clobber of two independent load/save cycles.
14
+ */
15
+ export declare function appendSousChefEvent(state: AgentState, event: SousChefEvent): void;
16
+ /**
17
+ * Append a sous-chef event and persist immediately. Use this only when the
18
+ * caller does NOT already hold a state it will save (standalone recording).
19
+ * If you have a loaded state, prefer appendSousChefEvent + your own saveState.
20
+ */
21
+ export declare function recordSousChefEvent(event: SousChefEvent, cwd?: string): void;
10
22
  export declare function createTask(description: string): CurrentTask;
11
23
  export declare function resetState(cwd?: string): void;
@@ -9,11 +9,14 @@ import * as path from 'path';
9
9
  import { HelpcodeError, ErrorCode } from '../lib/errors.js';
10
10
  const STATE_DIR = '.helpcode';
11
11
  const STATE_FILE = path.join(STATE_DIR, 'state.json');
12
+ /** Keep the sous-chef log bounded so state.json stays small. */
13
+ const MAX_SOUSCHEF_EVENTS = 50;
12
14
  function emptyState() {
13
15
  return {
14
- version: 1,
16
+ version: 2,
15
17
  currentTask: null,
16
18
  history: [],
19
+ sousChefLog: [],
17
20
  };
18
21
  }
19
22
  export function loadState(cwd = process.cwd()) {
@@ -23,10 +26,7 @@ export function loadState(cwd = process.cwd()) {
23
26
  }
24
27
  try {
25
28
  const parsed = JSON.parse(fs.readFileSync(file, 'utf-8'));
26
- if (parsed.version !== 1) {
27
- throw new HelpcodeError(ErrorCode.STATE_ERROR, `Unknown state version: ${parsed.version}`, 'This state was written by a different helpcode version. Run `helpcode reset` to start fresh.');
28
- }
29
- return parsed;
29
+ return migrate(parsed);
30
30
  }
31
31
  catch (e) {
32
32
  if (e instanceof HelpcodeError)
@@ -34,6 +34,27 @@ export function loadState(cwd = process.cwd()) {
34
34
  throw new HelpcodeError(ErrorCode.STATE_ERROR, `Could not parse state file: ${e.message}`, 'Run `helpcode reset` to start fresh (this won\'t touch your code).');
35
35
  }
36
36
  }
37
+ /**
38
+ * Migrate older state forward. v1 had no sousChefLog; we add an empty one and
39
+ * bump the version rather than erroring, so existing installs keep working.
40
+ */
41
+ function migrate(parsed) {
42
+ if (parsed.version === 2) {
43
+ // Defensive: ensure the log array exists even if hand-edited away.
44
+ if (!Array.isArray(parsed.sousChefLog))
45
+ parsed.sousChefLog = [];
46
+ return parsed;
47
+ }
48
+ if (parsed.version === 1) {
49
+ return {
50
+ version: 2,
51
+ currentTask: parsed.currentTask ?? null,
52
+ history: Array.isArray(parsed.history) ? parsed.history : [],
53
+ sousChefLog: [],
54
+ };
55
+ }
56
+ throw new HelpcodeError(ErrorCode.STATE_ERROR, `Unknown state version: ${parsed.version}`, 'This state was written by a newer helpcode. Upgrade, or run `helpcode reset`.');
57
+ }
37
58
  export function saveState(state, cwd = process.cwd()) {
38
59
  const dir = path.join(cwd, STATE_DIR);
39
60
  if (!fs.existsSync(dir))
@@ -43,6 +64,27 @@ export function saveState(state, cwd = process.cwd()) {
43
64
  fs.writeFileSync(tmp, JSON.stringify(state, null, 2), 'utf-8');
44
65
  fs.renameSync(tmp, file);
45
66
  }
67
+ /**
68
+ * Append a sous-chef event to an in-memory state object (capped). Use this
69
+ * when the caller already holds a loaded state and will saveState() itself —
70
+ * avoids the read-modify-write clobber of two independent load/save cycles.
71
+ */
72
+ export function appendSousChefEvent(state, event) {
73
+ state.sousChefLog.push(event);
74
+ if (state.sousChefLog.length > MAX_SOUSCHEF_EVENTS) {
75
+ state.sousChefLog = state.sousChefLog.slice(-MAX_SOUSCHEF_EVENTS);
76
+ }
77
+ }
78
+ /**
79
+ * Append a sous-chef event and persist immediately. Use this only when the
80
+ * caller does NOT already hold a state it will save (standalone recording).
81
+ * If you have a loaded state, prefer appendSousChefEvent + your own saveState.
82
+ */
83
+ export function recordSousChefEvent(event, cwd = process.cwd()) {
84
+ const state = loadState(cwd);
85
+ appendSousChefEvent(state, event);
86
+ saveState(state, cwd);
87
+ }
46
88
  export function createTask(description) {
47
89
  const now = new Date().toISOString();
48
90
  return {
@@ -1 +1 @@
1
- {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/core/state.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"state.js","sourceRoot":"","sources":["../../../src/core/state.ts"],"names":[],"mappings":"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"}
@@ -16,8 +16,10 @@ import { OllamaSettings } from '../types.js';
16
16
  export interface TriageResult {
17
17
  /** The compacted output to put in the brief. */
18
18
  text: string;
19
- /** True if the local model produced the summary; false if fallback. */
19
+ /** True if a model (local or remote) produced the summary; false if fallback. */
20
20
  triaged: boolean;
21
+ /** True if the remote model produced it (for the cockpit). */
22
+ remote?: boolean;
21
23
  }
22
24
  type GenerateImpl = (host: string, model: string, prompt: string, opts?: {
23
25
  timeoutMs?: number;
@@ -25,6 +27,12 @@ type GenerateImpl = (host: string, model: string, prompt: string, opts?: {
25
27
  export interface TriageDeps {
26
28
  /** Injectable model call for testing. Defaults to the real Ollama client. */
27
29
  generateImpl?: GenerateImpl;
30
+ /**
31
+ * Optional remote-model fallback (prompt → text). Supplied by the caller ONLY
32
+ * when allowRemoteCode is on and a key is present (triage output is
33
+ * code-bearing). Tried after local fails, before deterministic truncation.
34
+ */
35
+ remoteGenerate?: (prompt: string) => Promise<string>;
28
36
  }
29
37
  /** Is this output worth sending to the model? Long, and Ollama enabled. */
30
38
  export declare function shouldTriage(output: string, ollama: Pick<OllamaSettings, 'enabled'>): boolean;
@@ -32,6 +40,11 @@ export declare function buildTriagePrompt(output: string): string;
32
40
  /**
33
41
  * Triage command output. Returns a compacted version plus whether the model
34
42
  * was used. Never throws — any failure falls back to deterministic truncation.
43
+ *
44
+ * Order: local model → remote model (if provided; privacy-gated by the caller)
45
+ * → deterministic truncation. `deps.remoteGenerate` is supplied by the caller
46
+ * only when allowRemoteCode is on and a key is present — triage output can
47
+ * contain code, so it's code-bearing.
35
48
  */
36
49
  export declare function triageOutput(output: string, ollama: OllamaSettings, deps?: TriageDeps): Promise<TriageResult>;
37
50
  export {};
@@ -41,6 +41,11 @@ export function buildTriagePrompt(output) {
41
41
  /**
42
42
  * Triage command output. Returns a compacted version plus whether the model
43
43
  * was used. Never throws — any failure falls back to deterministic truncation.
44
+ *
45
+ * Order: local model → remote model (if provided; privacy-gated by the caller)
46
+ * → deterministic truncation. `deps.remoteGenerate` is supplied by the caller
47
+ * only when allowRemoteCode is on and a key is present — triage output can
48
+ * contain code, so it's code-bearing.
44
49
  */
45
50
  export async function triageOutput(output, ollama, deps = {}) {
46
51
  // Short output, or triage disabled: return as-is or truncate, no model.
@@ -51,19 +56,32 @@ export async function triageOutput(output, ollama, deps = {}) {
51
56
  : output;
52
57
  return { text, triaged: false };
53
58
  }
59
+ const prompt = buildTriagePrompt(output);
60
+ // 1. Local model (cheapest).
54
61
  const generateImpl = deps.generateImpl ?? generate;
55
62
  try {
56
- const summary = await generateImpl(ollama.host, ollama.model, buildTriagePrompt(output), { timeoutMs: ollama.timeoutMs });
63
+ const summary = await generateImpl(ollama.host, ollama.model, prompt, { timeoutMs: ollama.timeoutMs });
57
64
  if (summary && summary.trim().length > 0) {
58
65
  return { text: summary.trim(), triaged: true };
59
66
  }
60
- // Empty model result: fall back.
61
- return { text: truncateLines(output, FALLBACK_MAX_LINES, 'lines'), triaged: false };
62
67
  }
63
68
  catch (e) {
64
- // Transport/timeout/etc.: fall back to deterministic truncation.
65
69
  void (e instanceof OllamaError);
66
- return { text: truncateLines(output, FALLBACK_MAX_LINES, 'lines'), triaged: false };
70
+ // fall through to remote / deterministic
71
+ }
72
+ // 2. Remote model, if the caller provided one (privacy-gated upstream).
73
+ if (deps.remoteGenerate) {
74
+ try {
75
+ const summary = await deps.remoteGenerate(prompt);
76
+ if (summary && summary.trim().length > 0) {
77
+ return { text: summary.trim(), triaged: true, remote: true };
78
+ }
79
+ }
80
+ catch {
81
+ // fall through to deterministic
82
+ }
67
83
  }
84
+ // 3. Deterministic truncation — always works.
85
+ return { text: truncateLines(output, FALLBACK_MAX_LINES, 'lines'), triaged: false };
68
86
  }
69
87
  //# sourceMappingURL=triage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"triage.js","sourceRoot":"","sources":["../../../src/core/triage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD,iEAAiE;AACjE,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAClC,6CAA6C;AAC7C,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAqB9B,2EAA2E;AAC3E,MAAM,UAAU,YAAY,CAC1B,MAAc,EACd,MAAuC;IAEvC,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAClC,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,sBAAsB,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO;QACL,6EAA6E;QAC7E,wEAAwE;QACxE,gEAAgE;QAChE,gCAAgC;QAChC,yFAAyF;QACzF,gDAAgD;QAChD,EAAE;QACF,gBAAgB;QAChB,MAAM;QACN,oBAAoB;KACrB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAc,EACd,MAAsB,EACtB,OAAmB,EAAE;IAErB,wEAAwE;IACxE,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;QAC/C,MAAM,IAAI,GAAG,SAAS,GAAG,kBAAkB;YACzC,CAAC,CAAC,aAAa,CAAC,MAAM,EAAE,kBAAkB,EAAE,OAAO,CAAC;YACpD,CAAC,CAAC,MAAM,CAAC;QACX,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAClC,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,QAAQ,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,YAAY,CAChC,MAAM,CAAC,IAAI,EACX,MAAM,CAAC,KAAK,EACZ,iBAAiB,CAAC,MAAM,CAAC,EACzB,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAChC,CAAC;QACF,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACjD,CAAC;QACD,iCAAiC;QACjC,OAAO,EAAE,IAAI,EAAE,aAAa,CAAC,MAAM,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACtF,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,iEAAiE;QACjE,KAAK,CAAC,CAAC,YAAY,WAAW,CAAC,CAAC;QAChC,OAAO,EAAE,IAAI,EAAE,aAAa,CAAC,MAAM,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACtF,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"triage.js","sourceRoot":"","sources":["../../../src/core/triage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnD,iEAAiE;AACjE,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAClC,6CAA6C;AAC7C,MAAM,kBAAkB,GAAG,EAAE,CAAC;AA6B9B,2EAA2E;AAC3E,MAAM,UAAU,YAAY,CAC1B,MAAc,EACd,MAAuC;IAEvC,IAAI,CAAC,MAAM,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAClC,OAAO,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,GAAG,sBAAsB,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAc;IAC9C,OAAO;QACL,6EAA6E;QAC7E,wEAAwE;QACxE,gEAAgE;QAChE,gCAAgC;QAChC,yFAAyF;QACzF,gDAAgD;QAChD,EAAE;QACF,gBAAgB;QAChB,MAAM;QACN,oBAAoB;KACrB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAAc,EACd,MAAsB,EACtB,OAAmB,EAAE;IAErB,wEAAwE;IACxE,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC;QAC/C,MAAM,IAAI,GAAG,SAAS,GAAG,kBAAkB;YACzC,CAAC,CAAC,aAAa,CAAC,MAAM,EAAE,kBAAkB,EAAE,OAAO,CAAC;YACpD,CAAC,CAAC,MAAM,CAAC;QACX,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAClC,CAAC;IAED,MAAM,MAAM,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAEzC,6BAA6B;IAC7B,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,QAAQ,CAAC;IACnD,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,YAAY,CAChC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CACnE,CAAC;QACF,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACzC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACjD,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,KAAK,CAAC,CAAC,YAAY,WAAW,CAAC,CAAC;QAChC,yCAAyC;IAC3C,CAAC;IAED,wEAAwE;IACxE,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;QACxB,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;YAClD,IAAI,OAAO,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACzC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YAC/D,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,gCAAgC;QAClC,CAAC;IACH,CAAC;IAED,8CAA8C;IAC9C,OAAO,EAAE,IAAI,EAAE,aAAa,CAAC,MAAM,EAAE,kBAAkB,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;AACtF,CAAC"}
@@ -1,12 +1,13 @@
1
1
  /**
2
2
  * helpcode — CLI router.
3
3
  *
4
- * Six commands, kept deliberately small:
5
- * init detect project and set up .helpcode/
6
- * ask compose a prompt for Claude.ai
7
- * apply apply Claude's reply
8
- * run run a shell command, get compact output
9
- * status show current state
10
- * reset clear state
4
+ * Commands, kept deliberately small:
5
+ * init detect project and set up .helpcode/
6
+ * ask compose a prompt for Claude.ai
7
+ * apply apply Claude's reply
8
+ * run run a shell command, get compact output
9
+ * status show current state
10
+ * cockpit show the sous-chef kitchen this session (v0.3)
11
+ * reset clear state
11
12
  */
12
13
  export declare function run(argv: string[]): Promise<number>;
package/dist/src/index.js CHANGED
@@ -1,23 +1,26 @@
1
1
  /**
2
2
  * helpcode — CLI router.
3
3
  *
4
- * Six commands, kept deliberately small:
5
- * init detect project and set up .helpcode/
6
- * ask compose a prompt for Claude.ai
7
- * apply apply Claude's reply
8
- * run run a shell command, get compact output
9
- * status show current state
10
- * reset clear state
4
+ * Commands, kept deliberately small:
5
+ * init detect project and set up .helpcode/
6
+ * ask compose a prompt for Claude.ai
7
+ * apply apply Claude's reply
8
+ * run run a shell command, get compact output
9
+ * status show current state
10
+ * cockpit show the sous-chef kitchen this session (v0.3)
11
+ * reset clear state
11
12
  */
12
13
  import { handleInit } from './commands/init.js';
13
14
  import { handleAsk } from './commands/ask.js';
14
15
  import { handleApply } from './commands/apply.js';
15
16
  import { handleRun } from './commands/run.js';
16
17
  import { handleStatus } from './commands/status.js';
18
+ import { handlePlan } from './commands/plan.js';
19
+ import { handleCockpit } from './commands/cockpit.js';
17
20
  import { handleReset } from './commands/reset.js';
18
21
  import { HelpcodeError } from './lib/errors.js';
19
22
  import { c, log } from './lib/ui.js';
20
- const VERSION = '0.2.3';
23
+ const VERSION = '0.3.5';
21
24
  const HELP = `helpcode v${VERSION}
22
25
 
23
26
  A local agent that makes Claude.ai conversations efficient enough to ship
@@ -32,9 +35,11 @@ COMMANDS:
32
35
  --files <paths...> include specific files (skips auto-selection)
33
36
  --no-llm force keyword selection even if Ollama is on
34
37
  --explain-selection show why each file was chosen
38
+ plan "<task>" Propose an ordered breakdown of a big task (local model)
35
39
  apply [--yes] [--dry-run] Apply Claude's pasted reply (from stdin)
36
40
  run "<command>" Run a shell command, get compact output
37
41
  status Show current task and state
42
+ cockpit [--html] [--out F] Show the kitchen; --html writes a visual cockpit
38
43
  reset [--yes] Clear state (project config is untouched)
39
44
 
40
45
  GLOBAL FLAGS:
@@ -66,6 +71,10 @@ export async function run(argv) {
66
71
  explainSelection: rest.includes('--explain-selection'),
67
72
  });
68
73
  }
74
+ case 'plan': {
75
+ const task = rest.filter(a => !a.startsWith('--')).join(' ');
76
+ return await handlePlan(task);
77
+ }
69
78
  case 'apply':
70
79
  return await handleApply({
71
80
  yes: rest.includes('--yes'),
@@ -78,6 +87,10 @@ export async function run(argv) {
78
87
  }
79
88
  case 'status':
80
89
  return await handleStatus();
90
+ case 'cockpit': {
91
+ const out = extractFlagValues(rest, '--out')[0];
92
+ return await handleCockpit({ html: rest.includes('--html'), out });
93
+ }
81
94
  case 'reset':
82
95
  return await handleReset({ yes: rest.includes('--yes') });
83
96
  default: