@feelingmindful/thinking-graph 1.15.0 → 1.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -257,7 +257,7 @@ export class ThinkingGraph {
257
257
  { name: 'relate', used: relateUsed, purpose: 'connect nodes with typed edges' },
258
258
  { name: 'recall', used: null, purpose: 'query prior knowledge (not trackable)' },
259
259
  { name: 'learn', used: learnUsed, purpose: 'persist insights cross-project' },
260
- { name: 'research', used: researchUsed, purpose: 'web research via Perplexity/Firecrawl' },
260
+ { name: 'research', used: researchUsed, purpose: 'web research via parallel-cli' },
261
261
  ];
262
262
  lines.push('', '## Tool Utilization');
263
263
  for (const t of tools) {
package/dist/index.js CHANGED
@@ -56,7 +56,7 @@ server.tool('relate', 'Create a typed, directional relationship between two node
56
56
  server.tool('recall', 'Query the thinking graph and Obsidian vault — search by text, filter by type, traverse relationships, or search across projects.', recallSchema.shape, async (input) => recallHandler(graph, input, vault, projectSlug));
57
57
  server.tool('learn', 'Store durable knowledge — code facts, tech debt, insights, principles. Writes to both SQLite graph and Obsidian vault.', learnSchema.shape, async (input) => learnHandler(graph, input, vault, projectSlug));
58
58
  server.tool('export', 'Export the thinking graph as JSON or a human-readable markdown summary.', exportSchema.shape, async (input) => exportHandler(graph, input));
59
- server.tool('research', 'Research a topic using Perplexity/Firecrawl, then ingest findings into the graph and Obsidian vault. Two-phase: call once to get an action plan, then again with findings to store them.', researchSchema.shape, async (input) => researchHandler(graph, input, vault, projectSlug));
59
+ server.tool('research', 'Research a topic using parallel-cli, then ingest findings into the graph and Obsidian vault. Two-phase: call once to get an action plan, then again with findings to store them.', researchSchema.shape, async (input) => researchHandler(graph, input, vault, projectSlug));
60
60
  server.tool('recommend-skills', 'Recommend installed marketplace skills by area, verb, platform, or what they produce/detect. Use during reasoning to find skills that can help with the current task.', recommendSkillsSchema.shape, async (input) => recommendSkillsHandler(graph, input));
61
61
  server.tool('route-skills', 'Shortlist and rank installed skills for a task using routing heuristics based on platform, verb, areas, detections, and graph context.', routeSkillsSchema.shape, async (input) => routeSkillsHandler(graph, input));
62
62
  server.tool('plan-skills', 'Convert a request into an ordered skill execution plan with explicit approval gates before execution. This tool plans but does not execute skills.', planSkillsSchema.shape, async (input) => planSkillsHandler(graph, input));
@@ -13,7 +13,7 @@ export declare const planSkillsSchema: z.ZodObject<{
13
13
  }, "strip", z.ZodTypeAny, {
14
14
  request: string;
15
15
  areas?: string[] | undefined;
16
- platform?: "all" | "web" | "ios" | "android" | undefined;
16
+ platform?: "all" | "ios" | "android" | "web" | undefined;
17
17
  currentState?: "unknown" | "new" | "existing" | undefined;
18
18
  goalVerb?: string | undefined;
19
19
  detectedNeeds?: string[] | undefined;
@@ -23,7 +23,7 @@ export declare const planSkillsSchema: z.ZodObject<{
23
23
  }, {
24
24
  request: string;
25
25
  areas?: string[] | undefined;
26
- platform?: "all" | "web" | "ios" | "android" | undefined;
26
+ platform?: "all" | "ios" | "android" | "web" | undefined;
27
27
  currentState?: "unknown" | "new" | "existing" | undefined;
28
28
  goalVerb?: string | undefined;
29
29
  detectedNeeds?: string[] | undefined;
@@ -11,14 +11,14 @@ export declare const recommendSkillsSchema: z.ZodObject<{
11
11
  verb?: string | undefined;
12
12
  detects?: string | undefined;
13
13
  produces?: string | undefined;
14
- platform?: "all" | "web" | "ios" | "android" | undefined;
14
+ platform?: "all" | "ios" | "android" | "web" | undefined;
15
15
  area?: string | undefined;
16
16
  query?: string | undefined;
17
17
  }, {
18
18
  verb?: string | undefined;
19
19
  detects?: string | undefined;
20
20
  produces?: string | undefined;
21
- platform?: "all" | "web" | "ios" | "android" | undefined;
21
+ platform?: "all" | "ios" | "android" | "web" | undefined;
22
22
  area?: string | undefined;
23
23
  query?: string | undefined;
24
24
  }>;
@@ -27,12 +27,44 @@ export const researchSchema = z.object({
27
27
  findings: z.array(findingSchema).optional().describe('Results to store (phase 2)'),
28
28
  // Options
29
29
  projectId: z.string().optional(),
30
- scrapeUrls: z.array(z.string()).optional().describe('Specific URLs to scrape with Firecrawl'),
30
+ scrapeUrls: z.array(z.string()).optional().describe('Specific URLs to extract with `parallel-cli extract`'),
31
31
  recencyFilter: z.enum(['hour', 'day', 'week', 'month', 'year']).optional().describe('How recent results should be'),
32
32
  domainFilter: z.array(z.string()).optional().describe('Restrict to these domains'),
33
33
  notebookId: z.string().optional().describe('Specific NotebookLM notebook ID to query. If omitted, action plan will list notebooks first so the caller can pick the best match.'),
34
34
  skipGrounded: coerceBool.optional().describe('Skip NotebookLM step even when it would normally auto-prepend'),
35
35
  });
36
+ // parallel-cli is a shell CLI (run via Bash), not an MCP. Every research/scrape
37
+ // step shells out to `parallel-cli`. Recency filters map to `--after-date`.
38
+ const RECENCY_TO_DAYS = {
39
+ hour: 1,
40
+ day: 1,
41
+ week: 7,
42
+ month: 30,
43
+ year: 365,
44
+ };
45
+ function afterDateFor(recency) {
46
+ if (!recency)
47
+ return undefined;
48
+ const days = RECENCY_TO_DAYS[recency];
49
+ if (!days)
50
+ return undefined;
51
+ const d = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
52
+ return d.toISOString().slice(0, 10); // YYYY-MM-DD
53
+ }
54
+ // Shell-quote a single argument so queries with spaces/quotes stay intact.
55
+ function shq(value) {
56
+ return `'${value.replace(/'/g, `'\\''`)}'`;
57
+ }
58
+ function searchFlags(input, recencyDefault) {
59
+ const flags = [];
60
+ const afterDate = afterDateFor(input.recencyFilter ?? recencyDefault);
61
+ if (afterDate)
62
+ flags.push(`--after-date ${afterDate}`);
63
+ if (input.domainFilter?.length) {
64
+ flags.push(`--include-domains ${input.domainFilter.map(shq).join(',')}`);
65
+ }
66
+ return flags.length ? ` ${flags.join(' ')}` : '';
67
+ }
36
68
  function buildGroundedSteps(input) {
37
69
  const steps = [];
38
70
  const query = input.query;
@@ -75,90 +107,74 @@ function buildActionPlan(input) {
75
107
  if (input.intent === 'grounded_qa') {
76
108
  return steps;
77
109
  }
78
- // Choose Perplexity tool based on intent
110
+ // Choose the parallel-cli command based on intent.
111
+ // search → facts/quick; research run → deep multi-source/reasoning.
79
112
  switch (input.intent) {
80
113
  case 'fact_check':
81
114
  steps.push({
82
- tool: 'mcp__perplexity__perplexity_ask',
83
- description: 'Quick fact check with citations',
115
+ tool: 'Bash',
116
+ description: 'Quick fact check with citations via parallel-cli search',
84
117
  args: {
85
- messages: [{ role: 'user', content: query }],
86
- search_context_size: 'medium',
87
- ...(input.recencyFilter && { search_recency_filter: input.recencyFilter }),
88
- ...(input.domainFilter && { search_domain_filter: input.domainFilter }),
118
+ command: `parallel-cli search ${shq(query)}${searchFlags(input)}`,
89
119
  },
90
120
  });
91
121
  break;
92
122
  case 'compare':
93
123
  steps.push({
94
- tool: 'mcp__perplexity__perplexity_reason',
95
- description: 'Step-by-step comparison with web grounding',
124
+ tool: 'Bash',
125
+ description: 'Step-by-step comparison with web grounding via parallel-cli research run',
96
126
  args: {
97
- messages: [{ role: 'user', content: query }],
98
- search_context_size: 'high',
99
- ...(input.recencyFilter && { search_recency_filter: input.recencyFilter }),
100
- ...(input.domainFilter && { search_domain_filter: input.domainFilter }),
127
+ command: `parallel-cli research run --text ${shq(query)} --text-description ${shq('step-by-step reasoning')} --processor pro -o .premium/research/research-compare`,
101
128
  },
102
129
  });
103
130
  break;
104
131
  case 'explore':
105
132
  steps.push({
106
- tool: 'mcp__perplexity__perplexity_research',
107
- description: 'Deep multi-source research (30s+)',
133
+ tool: 'Bash',
134
+ description: 'Deep multi-source research (30s+) via parallel-cli research run',
108
135
  args: {
109
- messages: [{ role: 'user', content: query }],
110
- reasoning_effort: 'medium',
136
+ command: `parallel-cli research run --text ${shq(query)} --processor pro -o .premium/research/research-explore`,
111
137
  },
112
138
  });
113
139
  break;
114
140
  case 'how_to':
115
141
  steps.push({
116
- tool: 'mcp__perplexity__perplexity_ask',
117
- description: 'Find implementation guidance',
142
+ tool: 'Bash',
143
+ description: 'Find implementation guidance via parallel-cli search',
118
144
  args: {
119
- messages: [{ role: 'user', content: query }],
120
- search_context_size: 'high',
121
- ...(input.recencyFilter && { search_recency_filter: input.recencyFilter }),
122
- ...(input.domainFilter && { search_domain_filter: input.domainFilter }),
145
+ command: `parallel-cli search ${shq(query)} --mode advanced${searchFlags(input)}`,
123
146
  },
124
147
  });
125
148
  break;
126
149
  case 'current_state':
127
150
  steps.push({
128
- tool: 'mcp__perplexity__perplexity_ask',
129
- description: 'Get current state with recency filter',
151
+ tool: 'Bash',
152
+ description: 'Get current state with recency filter via parallel-cli search',
130
153
  args: {
131
- messages: [{ role: 'user', content: query }],
132
- search_recency_filter: input.recencyFilter ?? 'week',
133
- search_context_size: 'medium',
134
- ...(input.domainFilter && { search_domain_filter: input.domainFilter }),
154
+ command: `parallel-cli search ${shq(query)}${searchFlags(input, 'week')}`,
135
155
  },
136
156
  });
137
157
  break;
138
158
  }
139
- // If specific URLs provided, add Firecrawl scrape steps
159
+ // If specific URLs provided, add parallel-cli extract steps
140
160
  if (input.scrapeUrls?.length) {
141
161
  for (const url of input.scrapeUrls) {
142
162
  steps.push({
143
- tool: 'mcp__firecrawl__firecrawl_scrape',
144
- description: `Scrape ${url}`,
163
+ tool: 'Bash',
164
+ description: `Extract clean markdown from ${url} via parallel-cli extract`,
145
165
  args: {
146
- url,
147
- formats: ['markdown'],
148
- onlyMainContent: true,
166
+ command: `parallel-cli extract ${shq(url)}`,
149
167
  },
150
168
  });
151
169
  }
152
170
  }
153
- // For explore/compare, also suggest a Firecrawl search for additional sources
171
+ // For explore/compare, also suggest a parallel-cli search for additional sources
154
172
  if (input.intent === 'explore' || input.intent === 'compare') {
155
173
  steps.push({
156
- tool: 'mcp__firecrawl__firecrawl_search',
157
- description: 'Search for additional sources',
174
+ tool: 'Bash',
175
+ description: 'Search for additional sources via parallel-cli search',
158
176
  args: {
159
- query,
160
- limit: 5,
161
- sources: [{ type: 'web' }],
177
+ command: `parallel-cli search ${shq(query)} --max-results 5`,
162
178
  },
163
179
  });
164
180
  }
@@ -285,6 +301,7 @@ export async function researchHandler(graph, input, vault, projectSlug) {
285
301
  researchId: node.id,
286
302
  query: input.query,
287
303
  intent: input.intent,
304
+ prerequisite: 'parallel-cli must be installed and authenticated. Install: `npm i -g parallel-cli` (or `pipx install parallel-cli`). Auth: run `parallel-cli login` (device OAuth) or set PARALLEL_API_KEY. Verify with `parallel-cli auth`.',
288
305
  actionPlan,
289
306
  ingestStep: {
290
307
  tool: 'research',
@@ -11,7 +11,7 @@ export declare const routeSkillsSchema: z.ZodObject<{
11
11
  }, "strip", z.ZodTypeAny, {
12
12
  request: string;
13
13
  areas?: string[] | undefined;
14
- platform?: "all" | "web" | "ios" | "android" | undefined;
14
+ platform?: "all" | "ios" | "android" | "web" | undefined;
15
15
  currentState?: "unknown" | "new" | "existing" | undefined;
16
16
  goalVerb?: string | undefined;
17
17
  detectedNeeds?: string[] | undefined;
@@ -19,7 +19,7 @@ export declare const routeSkillsSchema: z.ZodObject<{
19
19
  }, {
20
20
  request: string;
21
21
  areas?: string[] | undefined;
22
- platform?: "all" | "web" | "ios" | "android" | undefined;
22
+ platform?: "all" | "ios" | "android" | "web" | undefined;
23
23
  currentState?: "unknown" | "new" | "existing" | undefined;
24
24
  goalVerb?: string | undefined;
25
25
  detectedNeeds?: string[] | undefined;
@@ -65,7 +65,7 @@ function buildSuggestions(type, thoughtNumber, relatedCount, stats, matchedSkill
65
65
  if (type === 'research') {
66
66
  suggestions.push({
67
67
  tool: 'research',
68
- when: 'Use the research tool instead — it generates an action plan with Perplexity/Firecrawl calls and handles ingestion',
68
+ when: 'Use the research tool instead — it generates an action plan with `parallel-cli` (search/research run/extract) calls and handles ingestion',
69
69
  example: { query: '<what to research>', intent: 'explore' },
70
70
  });
71
71
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feelingmindful/thinking-graph",
3
- "version": "1.15.0",
3
+ "version": "1.15.1",
4
4
  "description": "Persistent graph-based MCP thinking server for the feeling-mindful plugin marketplace",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",