orient-cli 0.2.1 → 0.3.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.
Files changed (46) hide show
  1. package/dist/extensions/orient-playwright.d.ts +7 -0
  2. package/dist/extensions/orient-playwright.js +8 -0
  3. package/dist/extensions/orient-playwright.js.map +1 -0
  4. package/dist/extensions/orient-web-search.d.ts +5 -0
  5. package/dist/extensions/orient-web-search.js +6 -0
  6. package/dist/extensions/orient-web-search.js.map +1 -0
  7. package/dist/integrations/notebooklm/setup.d.ts +94 -0
  8. package/dist/integrations/notebooklm/setup.js +311 -0
  9. package/dist/integrations/notebooklm/setup.js.map +1 -0
  10. package/dist/integrations/playwright/bridge.d.ts +85 -0
  11. package/dist/integrations/playwright/bridge.js +225 -0
  12. package/dist/integrations/playwright/bridge.js.map +1 -0
  13. package/dist/integrations/playwright/playwright-extension.d.ts +22 -0
  14. package/dist/integrations/playwright/playwright-extension.js +209 -0
  15. package/dist/integrations/playwright/playwright-extension.js.map +1 -0
  16. package/dist/integrations/web-search/bridge.d.ts +79 -0
  17. package/dist/integrations/web-search/bridge.js +298 -0
  18. package/dist/integrations/web-search/bridge.js.map +1 -0
  19. package/dist/integrations/web-search/web-search-extension.d.ts +26 -0
  20. package/dist/integrations/web-search/web-search-extension.js +122 -0
  21. package/dist/integrations/web-search/web-search-extension.js.map +1 -0
  22. package/dist/orient/orient-extension.js +149 -1
  23. package/dist/orient/orient-extension.js.map +1 -1
  24. package/dist/package-paths.js +6 -1
  25. package/dist/package-paths.js.map +1 -1
  26. package/node_modules/@orient-cli/agent-core/package.json +2 -2
  27. package/node_modules/@orient-cli/ai/package.json +1 -1
  28. package/node_modules/@orient-cli/coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  29. package/node_modules/@orient-cli/coding-agent/dist/modes/interactive/interactive-mode.js +2 -2
  30. package/node_modules/@orient-cli/coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  31. package/node_modules/@orient-cli/coding-agent/package.json +4 -4
  32. package/node_modules/@orient-cli/tui/package.json +1 -1
  33. package/node_modules/mime-db/HISTORY.md +541 -0
  34. package/node_modules/mime-db/LICENSE +23 -0
  35. package/node_modules/mime-db/README.md +109 -0
  36. package/node_modules/mime-db/db.json +9342 -0
  37. package/node_modules/mime-db/index.js +12 -0
  38. package/node_modules/mime-db/package.json +56 -0
  39. package/node_modules/mime-types/HISTORY.md +428 -0
  40. package/node_modules/mime-types/LICENSE +23 -0
  41. package/node_modules/mime-types/README.md +126 -0
  42. package/node_modules/mime-types/index.js +211 -0
  43. package/node_modules/mime-types/mimeScore.js +57 -0
  44. package/node_modules/mime-types/package.json +49 -0
  45. package/package.json +13 -6
  46. package/scripts/postinstall.mjs +235 -0
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Web search bridge (MIT, derived from mrkrsl/web-search-mcp)
3
+ * ============================================================
4
+ *
5
+ * ORIENT ships a default web-search layer that uses the same
6
+ * "scrape Bing / Brave / DuckDuckGo via playwright" pattern that
7
+ * https://github.com/mrkrsl/web-search-mcp@MIT implements as a
8
+ * standalone MCP server. We adopt that approach directly in
9
+ * TypeScript here instead of spawning the MCP server as a
10
+ * subprocess — it avoids (a) the MCP protocol overhead, (b) the
11
+ * extra install step, and (c) the dependency on an 8-month-stale
12
+ * upstream build. All three engine scrapers, the priority-order
13
+ * fallback, and the content extraction pattern are reimplemented
14
+ * from the upstream's published README + source layout.
15
+ *
16
+ * Upstream attribution: https://github.com/mrkrsl/web-search-mcp
17
+ * Upstream license: MIT
18
+ *
19
+ * Three operations match the upstream MCP tool surface:
20
+ *
21
+ * - `search(query, { limit, includeContent })` — "full-web-search"
22
+ * equivalent. Returns an array of `SearchResult` with optional
23
+ * full page text content per result.
24
+ *
25
+ * - `searchSummaries(query, { limit })` — "get-web-search-summaries"
26
+ * equivalent. Returns the same shape but with content omitted
27
+ * (faster, no second-pass fetch per URL).
28
+ *
29
+ * - `getPageContent(url)` — "get-single-web-page-content"
30
+ * equivalent. Direct content extraction for one URL the agent
31
+ * already has.
32
+ *
33
+ * Environment variables honored (names match upstream):
34
+ *
35
+ * MAX_CONTENT_LENGTH — max chars returned per page (default 8000)
36
+ * DEFAULT_TIMEOUT — per-navigation timeout in ms (default 20000)
37
+ * BROWSER_HEADLESS — "true" or "false" (default true)
38
+ * FORCE_MULTI_ENGINE_SEARCH — force fallback engines even on Bing hit
39
+ *
40
+ * Playwright is already a dependency of orient-cli (shipped for
41
+ * @playwright/cli and the notebooklm login flow), so importing
42
+ * `playwright` here adds zero additional install weight.
43
+ */
44
+ // Lazy-import playwright so environments that don't have chromium
45
+ // installed yet (e.g. right after npm install, before the postinstall
46
+ // script has run) still let orient-cli start — they just fail the
47
+ // tool call with a helpful error.
48
+ async function loadPlaywright() {
49
+ // @ts-ignore — playwright is a regular dep; this import is lazy
50
+ // so the bridge file can be imported in builds that skipped the
51
+ // postinstall step.
52
+ const mod = await import("playwright");
53
+ return mod;
54
+ }
55
+ function readEnv() {
56
+ const num = (name, fallback) => {
57
+ const raw = process.env[name];
58
+ if (!raw)
59
+ return fallback;
60
+ const parsed = Number.parseInt(raw, 10);
61
+ return Number.isFinite(parsed) ? parsed : fallback;
62
+ };
63
+ const bool = (name, fallback) => {
64
+ const raw = process.env[name];
65
+ if (raw === undefined)
66
+ return fallback;
67
+ return raw.toLowerCase() === "true" || raw === "1";
68
+ };
69
+ return {
70
+ maxContentLength: num("MAX_CONTENT_LENGTH", 8000),
71
+ defaultTimeout: num("DEFAULT_TIMEOUT", 20000),
72
+ headless: bool("BROWSER_HEADLESS", true),
73
+ forceMulti: bool("FORCE_MULTI_ENGINE_SEARCH", false),
74
+ };
75
+ }
76
+ // ============================================================================
77
+ // Browser management — one chromium process, reused across calls
78
+ // ============================================================================
79
+ let sharedBrowser = null;
80
+ let sharedContext = null;
81
+ async function getContext() {
82
+ if (sharedContext)
83
+ return sharedContext;
84
+ const env = readEnv();
85
+ const pw = await loadPlaywright();
86
+ sharedBrowser = await pw.chromium.launch({ headless: env.headless });
87
+ sharedContext = await sharedBrowser.newContext({
88
+ userAgent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
89
+ viewport: { width: 1280, height: 800 },
90
+ });
91
+ sharedContext.setDefaultNavigationTimeout(env.defaultTimeout);
92
+ return sharedContext;
93
+ }
94
+ export async function closeBrowser() {
95
+ if (sharedContext) {
96
+ await sharedContext.close().catch(() => undefined);
97
+ sharedContext = null;
98
+ }
99
+ if (sharedBrowser) {
100
+ await sharedBrowser.close().catch(() => undefined);
101
+ sharedBrowser = null;
102
+ }
103
+ }
104
+ // ============================================================================
105
+ // Engine scrapers — each returns a page of SearchResult (no content)
106
+ // ============================================================================
107
+ async function searchBing(query, limit) {
108
+ const ctx = await getContext();
109
+ const page = await ctx.newPage();
110
+ try {
111
+ const url = `https://www.bing.com/search?q=${encodeURIComponent(query)}&count=${limit * 2}`;
112
+ await page.goto(url, { waitUntil: "domcontentloaded" });
113
+ // Bing result blocks live under li.b_algo with h2 > a, p.b_lineclamp or .b_caption for snippet.
114
+ const results = await page.$$eval("li.b_algo", (nodes, cap) => {
115
+ return nodes.slice(0, cap).map((node) => {
116
+ const a = node.querySelector("h2 a");
117
+ const snippetEl = node.querySelector("p.b_lineclamp2, p.b_lineclamp3, p.b_lineclamp4, .b_caption p") ||
118
+ node.querySelector(".b_caption");
119
+ return {
120
+ title: a?.textContent?.trim() ?? "",
121
+ url: a?.href ?? "",
122
+ snippet: (snippetEl?.textContent ?? "").trim(),
123
+ };
124
+ });
125
+ }, limit);
126
+ return results
127
+ .filter((r) => r.url.startsWith("http"))
128
+ .map((r) => ({ ...r, engine: "bing" }));
129
+ }
130
+ finally {
131
+ await page.close().catch(() => undefined);
132
+ }
133
+ }
134
+ async function searchBrave(query, limit) {
135
+ const ctx = await getContext();
136
+ const page = await ctx.newPage();
137
+ try {
138
+ const url = `https://search.brave.com/search?q=${encodeURIComponent(query)}`;
139
+ await page.goto(url, { waitUntil: "domcontentloaded" });
140
+ const results = await page.$$eval("div.snippet, .snippet, [data-type='web']", (nodes, cap) => {
141
+ return nodes.slice(0, cap).map((node) => {
142
+ const a = node.querySelector("a");
143
+ const titleEl = node.querySelector(".title, h3, .snippet-title");
144
+ const snippetEl = node.querySelector(".snippet-description, .desc, p");
145
+ return {
146
+ title: (titleEl?.textContent ?? a?.textContent ?? "").trim(),
147
+ url: a?.href ?? "",
148
+ snippet: (snippetEl?.textContent ?? "").trim(),
149
+ };
150
+ });
151
+ }, limit);
152
+ return results
153
+ .filter((r) => r.url.startsWith("http") && !r.url.includes("brave.com"))
154
+ .map((r) => ({ ...r, engine: "brave" }));
155
+ }
156
+ finally {
157
+ await page.close().catch(() => undefined);
158
+ }
159
+ }
160
+ async function searchDuckDuckGo(query, limit) {
161
+ const ctx = await getContext();
162
+ const page = await ctx.newPage();
163
+ try {
164
+ // DuckDuckGo's HTML endpoint is the simplest to scrape — no JS.
165
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`;
166
+ await page.goto(url, { waitUntil: "domcontentloaded" });
167
+ const results = await page.$$eval("div.result", (nodes, cap) => {
168
+ return nodes.slice(0, cap).map((node) => {
169
+ const a = node.querySelector("a.result__a");
170
+ const snippetEl = node.querySelector(".result__snippet");
171
+ return {
172
+ title: a?.textContent?.trim() ?? "",
173
+ url: a?.href ?? "",
174
+ snippet: (snippetEl?.textContent ?? "").trim(),
175
+ };
176
+ });
177
+ }, limit);
178
+ return results
179
+ .filter((r) => r.url.startsWith("http"))
180
+ .map((r) => ({ ...r, engine: "duckduckgo" }));
181
+ }
182
+ finally {
183
+ await page.close().catch(() => undefined);
184
+ }
185
+ }
186
+ const ENGINE_IMPLS = {
187
+ bing: searchBing,
188
+ brave: searchBrave,
189
+ duckduckgo: searchDuckDuckGo,
190
+ };
191
+ // ============================================================================
192
+ // Content extraction — run page.evaluate() to pull main-text from a URL
193
+ // ============================================================================
194
+ async function extractPageText(page, maxLength) {
195
+ const text = await page.evaluate(() => {
196
+ // Prefer semantic containers; fall back to <body>.
197
+ const selectors = [
198
+ "main",
199
+ "article",
200
+ "[role='main']",
201
+ "#content",
202
+ ".content",
203
+ ".post",
204
+ ".article",
205
+ "body",
206
+ ];
207
+ for (const sel of selectors) {
208
+ const el = document.querySelector(sel);
209
+ if (el && el.innerText?.length > 100) {
210
+ return el.innerText;
211
+ }
212
+ }
213
+ return document.body?.innerText ?? "";
214
+ });
215
+ const collapsed = text.replace(/\s+/g, " ").trim();
216
+ return collapsed.length > maxLength ? `${collapsed.slice(0, maxLength)}…` : collapsed;
217
+ }
218
+ export async function getPageContent(url) {
219
+ const env = readEnv();
220
+ const ctx = await getContext();
221
+ const page = await ctx.newPage();
222
+ try {
223
+ await page.goto(url, { waitUntil: "domcontentloaded" });
224
+ const title = (await page.title().catch(() => "")) || "";
225
+ const content = await extractPageText(page, env.maxContentLength);
226
+ return { url, title, content };
227
+ }
228
+ finally {
229
+ await page.close().catch(() => undefined);
230
+ }
231
+ }
232
+ // ============================================================================
233
+ // Top-level search orchestration (engine priority + full-content fetch)
234
+ // ============================================================================
235
+ /**
236
+ * Run a web search with the priority-order fallback pattern mrkrsl/
237
+ * web-search-mcp established: try Bing first, fall through to Brave,
238
+ * fall through to DuckDuckGo. If `includeContent` is true, fetch full
239
+ * page text for each result in parallel (bounded to 5 concurrent).
240
+ */
241
+ export async function search(query, options = {}) {
242
+ const env = readEnv();
243
+ const limit = options.limit ?? 5;
244
+ const includeContent = options.includeContent ?? false;
245
+ const engines = options.engines ?? ["bing", "brave", "duckduckgo"];
246
+ let results = [];
247
+ const errors = [];
248
+ for (const engine of engines) {
249
+ try {
250
+ const impl = ENGINE_IMPLS[engine];
251
+ results = await impl(query, limit);
252
+ if (results.length > 0 && !env.forceMulti)
253
+ break;
254
+ if (env.forceMulti && results.length >= limit)
255
+ break;
256
+ }
257
+ catch (err) {
258
+ errors.push({ engine, error: err.message });
259
+ }
260
+ }
261
+ if (results.length === 0) {
262
+ const detail = errors.map((e) => `${e.engine}: ${e.error}`).join(" | ");
263
+ throw new Error(`No engine returned results for "${query}"${detail ? ` (${detail})` : ""}`);
264
+ }
265
+ if (includeContent) {
266
+ // Fetch content in parallel, bounded.
267
+ const ctx = await getContext();
268
+ const fetchOne = async (result) => {
269
+ const page = await ctx.newPage();
270
+ try {
271
+ await page.goto(result.url, { waitUntil: "domcontentloaded" });
272
+ result.content = await extractPageText(page, env.maxContentLength);
273
+ }
274
+ catch {
275
+ result.content = "";
276
+ }
277
+ finally {
278
+ await page.close().catch(() => undefined);
279
+ }
280
+ return result;
281
+ };
282
+ const CONCURRENCY = 5;
283
+ const pending = [];
284
+ for (const r of results) {
285
+ pending.push(fetchOne(r));
286
+ if (pending.length >= CONCURRENCY) {
287
+ await Promise.all(pending.splice(0));
288
+ }
289
+ }
290
+ await Promise.all(pending);
291
+ }
292
+ return results;
293
+ }
294
+ /** Shortcut: search without full content fetch. */
295
+ export async function searchSummaries(query, options = {}) {
296
+ return search(query, { ...options, includeContent: false });
297
+ }
298
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../../src/integrations/web-search/bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Web-search extension for ORIENT CLI
3
+ * ====================================
4
+ *
5
+ * Registers three LLM-callable tools that the agent uses whenever it
6
+ * detects a query requires web information — anywhere from "what's
7
+ * the latest version of X" to full Research-phase grounding. Backed
8
+ * by the web-search bridge which reimplements mrkrsl/web-search-mcp
9
+ * directly in TypeScript against playwright.
10
+ *
11
+ * The agent is told about these tools via the standard ExtensionAPI
12
+ * tool registration surface. Pi's tool router handles the rest —
13
+ * when the LLM produces a tool call matching one of these names, the
14
+ * handler runs and the result lands in the next turn's context.
15
+ *
16
+ * Used by:
17
+ * - All phases: the agent picks these up automatically when a
18
+ * user message needs fresh web data.
19
+ * - Tier 1 + Tier 2 Research: the orient-core extension tells
20
+ * the agent to use `web_search` / `web_search_full` as its
21
+ * primary research tool.
22
+ * - Tier 3 Research: the 2-option picker lets the user pick
23
+ * between this built-in path or the notebooklm-py path.
24
+ */
25
+ import type { ExtensionAPI } from "@orient-cli/coding-agent";
26
+ export default function webSearchExtension(pi: ExtensionAPI): Promise<void>;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Web-search extension for ORIENT CLI
3
+ * ====================================
4
+ *
5
+ * Registers three LLM-callable tools that the agent uses whenever it
6
+ * detects a query requires web information — anywhere from "what's
7
+ * the latest version of X" to full Research-phase grounding. Backed
8
+ * by the web-search bridge which reimplements mrkrsl/web-search-mcp
9
+ * directly in TypeScript against playwright.
10
+ *
11
+ * The agent is told about these tools via the standard ExtensionAPI
12
+ * tool registration surface. Pi's tool router handles the rest —
13
+ * when the LLM produces a tool call matching one of these names, the
14
+ * handler runs and the result lands in the next turn's context.
15
+ *
16
+ * Used by:
17
+ * - All phases: the agent picks these up automatically when a
18
+ * user message needs fresh web data.
19
+ * - Tier 1 + Tier 2 Research: the orient-core extension tells
20
+ * the agent to use `web_search` / `web_search_full` as its
21
+ * primary research tool.
22
+ * - Tier 3 Research: the 2-option picker lets the user pick
23
+ * between this built-in path or the notebooklm-py path.
24
+ */
25
+ import { Type } from "@sinclair/typebox";
26
+ import { closeBrowser, getPageContent, search, searchSummaries } from "./bridge.js";
27
+ export default async function webSearchExtension(pi) {
28
+ pi.on("session_start", (_event, ctx) => {
29
+ ctx.ui.setStatus("web-search", "web-search ready (bing → brave → duckduckgo)");
30
+ });
31
+ pi.registerTool({
32
+ name: "web_search",
33
+ label: "Web Search",
34
+ description: "Search the web and return result titles, URLs, and snippets. Fast (~2s). Use when a single pass of results is enough to answer. Engines tried in order: Bing → Brave → DuckDuckGo.",
35
+ promptSnippet: "Search the web for up-to-date information.",
36
+ parameters: Type.Object({
37
+ query: Type.String({ description: "Search query" }),
38
+ limit: Type.Optional(Type.Number({ description: "Max results (default 5)" })),
39
+ }),
40
+ async execute(_toolCallId, params) {
41
+ try {
42
+ const results = await searchSummaries(params.query, { limit: params.limit ?? 5 });
43
+ const body = results
44
+ .map((r, i) => `[${i + 1}] ${r.title}\n ${r.url}\n ${r.snippet}\n (${r.engine})`)
45
+ .join("\n\n");
46
+ return {
47
+ content: [{ type: "text", text: body || "(no results)" }],
48
+ details: null,
49
+ };
50
+ }
51
+ catch (err) {
52
+ return {
53
+ content: [{ type: "text", text: `web_search failed: ${err.message}` }],
54
+ details: null,
55
+ };
56
+ }
57
+ },
58
+ });
59
+ pi.registerTool({
60
+ name: "web_search_full",
61
+ label: "Web Search (Full Content)",
62
+ description: "Search the web AND fetch full page content for each result in one call. Slower (~10-20s per 5 results) but gives the agent everything it needs to ground an answer in source text. Use in the Research phase, for deep-dives, or when summaries aren't enough.",
63
+ promptSnippet: "Search the web and extract full content from each result.",
64
+ parameters: Type.Object({
65
+ query: Type.String({ description: "Search query" }),
66
+ limit: Type.Optional(Type.Number({ description: "Max results (default 3 — content fetch is expensive)" })),
67
+ }),
68
+ async execute(_toolCallId, params) {
69
+ try {
70
+ const results = await search(params.query, { limit: params.limit ?? 3, includeContent: true });
71
+ const body = results
72
+ .map((r, i) => `[${i + 1}] ${r.title}\n ${r.url}\n ${r.snippet}\n (${r.engine})\n\n ${r.content ?? "(no content)"}`)
73
+ .join("\n\n---\n\n");
74
+ return { content: [{ type: "text", text: body || "(no results)" }], details: null };
75
+ }
76
+ catch (err) {
77
+ return {
78
+ content: [{ type: "text", text: `web_search_full failed: ${err.message}` }],
79
+ details: null,
80
+ };
81
+ }
82
+ },
83
+ });
84
+ pi.registerTool({
85
+ name: "web_page_content",
86
+ label: "Fetch Page Content",
87
+ description: "Extract the main text content of a single URL the agent already knows about. Use when the agent has a link from an earlier message, a git log, or a prior search and needs the page body without running another search.",
88
+ promptSnippet: "Extract the main content of a URL.",
89
+ parameters: Type.Object({
90
+ url: Type.String({ description: "Absolute URL (https://...)" }),
91
+ }),
92
+ async execute(_toolCallId, params) {
93
+ try {
94
+ const result = await getPageContent(params.url);
95
+ return {
96
+ content: [
97
+ {
98
+ type: "text",
99
+ text: `Title: ${result.title}\nURL: ${result.url}\n\n${result.content}`,
100
+ },
101
+ ],
102
+ details: null,
103
+ };
104
+ }
105
+ catch (err) {
106
+ return {
107
+ content: [{ type: "text", text: `web_page_content failed: ${err.message}` }],
108
+ details: null,
109
+ };
110
+ }
111
+ },
112
+ });
113
+ // The shared chromium process is cleaned up by Node's process-exit
114
+ // hook — pi's extension API doesn't expose a `session_end` event
115
+ // yet. See MIGRATION.md for the fork-level TODO to add one. In the
116
+ // meantime, chromium gets torn down when the orient process
117
+ // terminates, which is the common case.
118
+ process.on("exit", () => {
119
+ closeBrowser().catch(() => undefined);
120
+ });
121
+ }
122
+ //# sourceMappingURL=web-search-extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-search-extension.js","sourceRoot":"","sources":["../../../src/integrations/web-search/web-search-extension.ts"],"names":[],"mappings":"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"}
@@ -14,11 +14,13 @@ import { getTemplatesDir } from "../package-paths.js";
14
14
  import { createChangePacket, getActivePacket, getPacketDir, initOrient, listChangePackets, readPacketConfig, resolveActivePacketDir, setActivePacket, writePacketConfig, } from "./change-packet.js";
15
15
  import { appendClarificationLog, applyResolutions, buildClarifySelectOptions, CUSTOM_ANSWER_OPTION, formatClarifyPrompt, MAX_MARKERS_PER_SESSION, scanMarkers, } from "./clarify.js";
16
16
  import { decideClear, formatClearMessage } from "./clear-command.js";
17
+ import { describeStatus as describeNotebookLMStatus, setupNotebookLM } from "../integrations/notebooklm/setup.js";
18
+ import { NotebookLMBridge } from "../integrations/notebooklm/bridge.js";
17
19
  import { describeMode, readMode, writeMode } from "./mode.js";
18
20
  import { formatPhaseProgress, PHASES } from "./phases.js";
19
21
  import { buildExecuteReminder, buildPlanReminder, deletePlan, getPlanFilePath, readPlan, readState, renderStatus as renderPlanStatus, startPlanning, transitionToExecuting, transitionToPlanning, } from "./plan-execute.js";
20
22
  import { OrientStateMachine } from "./state-machine.js";
21
- import { buildTierSelectOptions, describeTierBudget, parseTierFromSelection, setDefaultTier, TIERS, } from "./tiers.js";
23
+ import { buildTierSelectOptions, describeTierBudget, getEffectiveTier, parseTierFromSelection, setDefaultTier, TIERS, } from "./tiers.js";
22
24
  import { Phase } from "./types.js";
23
25
  import { decideWizardAction, optIntoOrientFramework, optOutOfOrientFramework } from "./wizard.js";
24
26
  export default function orientExtension(pi) {
@@ -290,8 +292,154 @@ export default function orientExtension(pi) {
290
292
  // Load the skill for this phase (the agent will then guide the user)
291
293
  ctx.ui.notify(`Entering ${phaseInfo.label} phase for "${active.name}"\n` +
292
294
  `Use /skill:orient-${command.split(".")[1]} for detailed phase guidance.`, "info");
295
+ // ============================================================
296
+ // Research phase tier-aware backend selection
297
+ // ============================================================
298
+ //
299
+ // Tier 1 / Tier 2 — use the built-in web-search layer
300
+ // (web_search / web_search_full, backed by bing → brave →
301
+ // duckduckgo via playwright). The agent picks up those
302
+ // tools automatically through the web-search extension
303
+ // so we only need to confirm the phase was entered.
304
+ //
305
+ // Tier 3 — offer the user a choice between two research
306
+ // backends:
307
+ // 1. Built-in web search (same as tier 1/2, but the
308
+ // Research skill tells the agent to run deeper
309
+ // multi-query passes).
310
+ // 2. NotebookLM (notebooklm-py) for cited, grounded
311
+ // answers against a persistent notebook — requires
312
+ // a one-time Google sign-in via a headed Chromium
313
+ // window (`notebooklm login`). Auto-installed at
314
+ // orient-cli install time; auth is the only manual
315
+ // step.
316
+ //
317
+ // The pick is persisted for the remainder of the packet
318
+ // so phase re-entries don't re-prompt.
319
+ if (phase === Phase.Research) {
320
+ const tierredConfig = sm.getConfig();
321
+ const tier = getEffectiveTier(tierredConfig, Phase.Research);
322
+ if (tier === 1 || tier === 2) {
323
+ ctx.ui.notify(`Research backend: built-in web search (tier ${tier}). The agent uses web_search / web_search_full directly.`, "info");
324
+ }
325
+ else if (tier === 3) {
326
+ const existing = tierredConfig.researchBackend;
327
+ let chosen = existing;
328
+ if (!chosen) {
329
+ const selection = await ctx.ui.select("Tier-3 Research backend", [
330
+ "1 — Built-in web search (fast, free, no login, multi-engine scraping)",
331
+ "2 — NotebookLM (cited answers, requires one-time Google sign-in)",
332
+ ]);
333
+ if (selection) {
334
+ chosen = selection.trim().startsWith("2") ? "notebooklm" : "web-search";
335
+ }
336
+ }
337
+ if (chosen === "notebooklm") {
338
+ ctx.ui.notify("Tier-3 Research: NotebookLM backend selected.", "info");
339
+ const ready = await ensureNotebookLMReady(ctx, "tier-3 Research / NotebookLM option");
340
+ if (!ready) {
341
+ ctx.ui.notify("Falling back to built-in web search for this session. Run /orient.research.setup once a display is available, then re-enter Research.", "warning");
342
+ chosen = "web-search";
343
+ }
344
+ }
345
+ else {
346
+ ctx.ui.notify("Tier-3 Research: built-in web search selected. The agent will run multi-query passes via web_search_full and aggregate results into Research.md.", "info");
347
+ }
348
+ // Persist the choice on the packet config.
349
+ const updated = {
350
+ ...sm.getConfig(),
351
+ researchBackend: chosen ?? "web-search",
352
+ };
353
+ writePacketConfig(active.dir, updated);
354
+ }
355
+ }
356
+ },
357
+ });
358
+ }
359
+ // ========================================================================
360
+ // Research setup command + session_start status hint
361
+ // ========================================================================
362
+ //
363
+ // Users can also run the setup flow manually at any time via
364
+ // /orient.research.setup. This is useful (a) on a headless server
365
+ // where the auto-trigger skipped the login step, (b) when the user
366
+ // wants to pre-warm the install before hitting the Research phase,
367
+ // and (c) for re-authing when the Google cookies expire.
368
+ pi.registerCommand("orient.research.setup", {
369
+ description: "Install + authenticate notebooklm-py for tier-3 Research",
370
+ handler: async (_args, ctx) => {
371
+ await ensureNotebookLMReady(ctx, "manual setup");
372
+ },
373
+ });
374
+ // Surface the notebooklm status on session_start so users know
375
+ // whether tier-3 Research is ready before they hit the phase.
376
+ pi.on("session_start", async (_event, ctx) => {
377
+ try {
378
+ const status = await describeNotebookLMStatus();
379
+ ctx.ui.setStatus("notebooklm", status);
380
+ }
381
+ catch {
382
+ // Silent fall-through — status hint is decorative.
383
+ }
384
+ });
385
+ /**
386
+ * Consent-gated install + auth runner. Returns true iff notebooklm
387
+ * is ready after the flow (installed + authenticated). On failure
388
+ * returns false with a helpful notify() message — the caller can
389
+ * still use lower-tier research with pi's built-in tools.
390
+ */
391
+ async function ensureNotebookLMReady(ctx, reason) {
392
+ const bridge = new NotebookLMBridge();
393
+ const availability = await bridge.checkAvailability();
394
+ if (availability.installed && availability.authenticated) {
395
+ ctx.ui.notify(`NotebookLM already ready (${reason}).`, "info");
396
+ return true;
397
+ }
398
+ ctx.ui.notify([
399
+ `NotebookLM setup required (${reason}).`,
400
+ "",
401
+ "This will:",
402
+ " 1. pip install --user \"notebooklm-py[browser]\"",
403
+ " 2. python3 -m playwright install chromium (~170MB)",
404
+ " 3. notebooklm login — opens a Chromium window; sign in to Google",
405
+ "",
406
+ "Credentials persist at ~/.notebooklm/profiles/default/ — one-time per machine.",
407
+ "",
408
+ "Press ctrl+c now to abort, or wait — setup starts in ~2 seconds...",
409
+ ].join("\n"), "info");
410
+ // Optional confirmation if the fork exposes ctx.ui.confirm().
411
+ const confirmFn = ctx.ui.confirm;
412
+ if (typeof confirmFn === "function") {
413
+ const ok = await confirmFn("NotebookLM setup", "Install notebooklm-py and sign in to Google now?");
414
+ if (!ok) {
415
+ ctx.ui.notify("NotebookLM setup deferred. Tier-3 Research will run with pi's built-in tools only until you run /orient.research.setup.", "warning");
416
+ return false;
417
+ }
418
+ }
419
+ else {
420
+ // No confirm method — brief delay so the user can abort with
421
+ // ctrl+c if they change their mind.
422
+ await new Promise((resolve) => setTimeout(resolve, 2000));
423
+ }
424
+ ctx.ui.notify("Running notebooklm-py setup... progress streams below.", "info");
425
+ const renderStep = (step) => {
426
+ const icons = {
427
+ pending: "○",
428
+ running: "⟳",
429
+ ok: "✓",
430
+ skipped: "-",
431
+ failed: "✗",
432
+ };
433
+ const detail = step.detail ? ` ${step.detail}` : "";
434
+ return ` ${icons[step.status]} ${step.label}${detail}`;
435
+ };
436
+ const result = await setupNotebookLM({
437
+ onProgress: (step) => {
438
+ ctx.ui.notify(renderStep(step), step.status === "failed" ? "warning" : "info");
293
439
  },
294
440
  });
441
+ ctx.ui.notify(result.message, result.success ? "info" : "warning");
442
+ return result.success;
295
443
  }
296
444
  // ========================================================================
297
445
  // ORIENT Tools (callable by the LLM agent)