@roam-research/roam-tools-core 0.5.0 → 0.6.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 (47) hide show
  1. package/README.md +23 -8
  2. package/dist/index.d.ts +3 -7
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +8 -8
  5. package/dist/operations/blocks.d.ts +25 -20
  6. package/dist/operations/blocks.d.ts.map +1 -1
  7. package/dist/operations/blocks.js +66 -15
  8. package/dist/operations/datalog.d.ts +15 -0
  9. package/dist/operations/datalog.d.ts.map +1 -0
  10. package/dist/operations/datalog.js +16 -0
  11. package/dist/operations/files.d.ts +6 -7
  12. package/dist/operations/files.d.ts.map +1 -1
  13. package/dist/operations/files.js +18 -5
  14. package/dist/operations/navigation.d.ts +9 -10
  15. package/dist/operations/navigation.d.ts.map +1 -1
  16. package/dist/operations/navigation.js +4 -1
  17. package/dist/operations/pages.d.ts +15 -13
  18. package/dist/operations/pages.d.ts.map +1 -1
  19. package/dist/operations/pages.js +34 -10
  20. package/dist/operations/query.d.ts +10 -11
  21. package/dist/operations/query.d.ts.map +1 -1
  22. package/dist/operations/query.js +24 -6
  23. package/dist/operations/search.d.ts +7 -8
  24. package/dist/operations/search.d.ts.map +1 -1
  25. package/dist/operations/search.js +22 -6
  26. package/dist/tools.d.ts +37 -4
  27. package/dist/tools.d.ts.map +1 -1
  28. package/dist/tools.js +91 -80
  29. package/dist/types.d.ts +11 -10
  30. package/dist/types.d.ts.map +1 -1
  31. package/dist/types.js +14 -4
  32. package/package.json +7 -10
  33. package/dist/client.d.ts +0 -34
  34. package/dist/client.d.ts.map +0 -1
  35. package/dist/client.js +0 -275
  36. package/dist/connect.d.ts +0 -10
  37. package/dist/connect.d.ts.map +0 -1
  38. package/dist/connect.js +0 -477
  39. package/dist/graph-resolver.d.ts +0 -54
  40. package/dist/graph-resolver.d.ts.map +0 -1
  41. package/dist/graph-resolver.js +0 -338
  42. package/dist/operations/graphs.d.ts +0 -26
  43. package/dist/operations/graphs.d.ts.map +0 -1
  44. package/dist/operations/graphs.js +0 -214
  45. package/dist/roam-api.d.ts +0 -32
  46. package/dist/roam-api.d.ts.map +0 -1
  47. package/dist/roam-api.js +0 -50
@@ -4,12 +4,22 @@ import { textResult } from "../types.js";
4
4
  export const CreatePageSchema = z.object({
5
5
  title: z.string().describe("Page title"),
6
6
  markdown: z.string().optional().describe("Markdown content for the page"),
7
- uid: z.string().optional(),
7
+ uid: z
8
+ .string()
9
+ .optional()
10
+ .describe("Custom UID to assign to the new page. Omit to let Roam auto-generate one (recommended)"),
11
+ childrenViewType: z
12
+ .enum(["document", "bullet", "numbered"])
13
+ .optional()
14
+ .describe("How children are displayed (document, bullet, or numbered)"),
8
15
  });
9
16
  export const GetPageSchema = z.object({
10
17
  title: z.string().optional().describe("Page title (alternative to uid)"),
11
18
  uid: z.string().optional().describe("Page UID"),
12
- maxDepth: z.coerce.number().optional().describe("Max depth of children to include in markdown (omit for full tree)"),
19
+ maxDepth: z.coerce
20
+ .number()
21
+ .optional()
22
+ .describe("Max depth of children to include in markdown (omit for full tree)"),
13
23
  });
14
24
  export const DeletePageSchema = z.object({
15
25
  uid: z.string().describe("Page UID to delete"),
@@ -17,21 +27,31 @@ export const DeletePageSchema = z.object({
17
27
  export const UpdatePageSchema = z.object({
18
28
  uid: z.string().describe("Page UID"),
19
29
  title: z.string().optional().describe("New page title"),
20
- childrenViewType: z.enum(["document", "bullet", "numbered"]).optional().describe("How children are displayed (document, bullet, or numbered)"),
21
- mergePages: z.boolean().optional().describe("If true, merge with existing page when renaming to a title that already exists (default: false)"),
30
+ childrenViewType: z
31
+ .enum(["document", "bullet", "numbered"])
32
+ .optional()
33
+ .describe("How children are displayed (document, bullet, or numbered)"),
34
+ mergePages: z
35
+ .boolean()
36
+ .optional()
37
+ .describe("If true, merge with existing page when renaming to a title that already exists (default: false)"),
22
38
  });
23
39
  export const GetGuidelinesSchema = z.object({});
24
40
  export async function createPage(client, params) {
41
+ const page = { title: params.title };
42
+ if (params.uid !== undefined)
43
+ page.uid = params.uid;
44
+ if (params.childrenViewType !== undefined)
45
+ page["children-view-type"] = params.childrenViewType;
25
46
  const response = await client.call("data.page.fromMarkdown", [
26
- {
27
- page: { title: params.title, uid: params.uid },
28
- "markdown-string": params.markdown,
29
- },
47
+ { page, "markdown-string": params.markdown },
30
48
  ]);
31
49
  return textResult(response.result ?? { uid: "" });
32
50
  }
33
51
  export async function getPage(client, params) {
34
- const apiParams = params.uid ? { uid: params.uid } : { title: params.title };
52
+ const apiParams = params.uid
53
+ ? { uid: params.uid }
54
+ : { title: params.title };
35
55
  if (params.maxDepth !== undefined)
36
56
  apiParams.maxDepth = params.maxDepth;
37
57
  const response = await client.call("data.ai.getPage", [apiParams]);
@@ -55,7 +75,11 @@ export async function updatePage(client, params) {
55
75
  }
56
76
  export async function getGuidelines(client) {
57
77
  const response = await client.call("data.ai.getGraphGuidelines", []);
58
- const result = response.result ?? { guidelines: null, starredPages: [], todaysDailyNotePage: null };
78
+ const result = response.result ?? {
79
+ guidelines: null,
80
+ starredPages: [],
81
+ todaysDailyNotePage: null,
82
+ };
59
83
  const dnpTitle = result.todaysDailyNotePage;
60
84
  const nextSteps = dnpTitle
61
85
  ? `Start by reading today's daily note page ("${dnpTitle}") with get_page — this is the user's primary workspace for the day. If you need more context, call search with an empty query for recently edited and viewed content. Skip these orientation steps only when the user has already given you a specific task to execute (e.g. "create a page called X").`
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
- import type { RoamClient } from "../client.js";
3
- import type { CallToolResult } from "../types.js";
2
+ import type { CallToolResult, RoamActionClient } from "../types.js";
4
3
  export declare const QuerySchema: z.ZodObject<{
5
4
  uid: z.ZodOptional<z.ZodString>;
6
5
  query: z.ZodOptional<z.ZodString>;
@@ -13,22 +12,22 @@ export declare const QuerySchema: z.ZodObject<{
13
12
  }, "strip", z.ZodTypeAny, {
14
13
  sort?: "created-date" | "edited-date" | "daily-note-date" | undefined;
15
14
  uid?: string | undefined;
16
- query?: string | undefined;
17
- sortOrder?: "asc" | "desc" | undefined;
18
- includePath?: boolean | undefined;
15
+ maxDepth?: number | undefined;
19
16
  offset?: number | undefined;
20
17
  limit?: number | undefined;
21
- maxDepth?: number | undefined;
18
+ sortOrder?: "asc" | "desc" | undefined;
19
+ includePath?: boolean | undefined;
20
+ query?: string | undefined;
22
21
  }, {
23
22
  sort?: "created-date" | "edited-date" | "daily-note-date" | undefined;
24
23
  uid?: string | undefined;
25
- query?: string | undefined;
26
- sortOrder?: "asc" | "desc" | undefined;
27
- includePath?: boolean | undefined;
24
+ maxDepth?: number | undefined;
28
25
  offset?: number | undefined;
29
26
  limit?: number | undefined;
30
- maxDepth?: number | undefined;
27
+ sortOrder?: "asc" | "desc" | undefined;
28
+ includePath?: boolean | undefined;
29
+ query?: string | undefined;
31
30
  }>;
32
31
  export type QueryParams = z.infer<typeof QuerySchema>;
33
- export declare function query(client: RoamClient, params: QueryParams): Promise<CallToolResult>;
32
+ export declare function query(client: RoamActionClient, params: QueryParams): Promise<CallToolResult>;
34
33
  //# sourceMappingURL=query.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/operations/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAiB,cAAc,EAAE,MAAM,aAAa,CAAC;AAKjE,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;EAStB,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,wBAAsB,KAAK,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CA2B5F"}
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/operations/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAiB,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAKnF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;EA+BtB,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAEtD,wBAAsB,KAAK,CACzB,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,cAAc,CAAC,CA2BzB"}
@@ -3,14 +3,32 @@ import { textResult } from "../types.js";
3
3
  // Schema for executing Roam queries ({{query: }} or {{[[query]]: }} blocks, NOT Datalog)
4
4
  // Supports two modes: UID mode (execute existing query block) or Query mode (raw query string)
5
5
  export const QuerySchema = z.object({
6
- uid: z.string().optional().describe("UID of a block containing {{query: ...}} or {{[[query]]: ...}} - uses the block's saved display settings and filters"),
7
- query: z.string().optional().describe("Raw Roam query string (e.g., \"{and: [[TODO]] {not: [[DONE]]}}\") - NOT Datalog - results are flat list, no user filters applied"),
8
- sort: z.enum(["created-date", "edited-date", "daily-note-date"]).optional().describe("Sort order (only for query mode, default: created-date)"),
9
- sortOrder: z.enum(["asc", "desc"]).optional().describe("Sort direction (only for query mode, default: desc)"),
10
- includePath: z.boolean().optional().describe("Include breadcrumb path in results (only for query mode, default: true)"),
6
+ uid: z
7
+ .string()
8
+ .optional()
9
+ .describe("UID of a block containing {{query: ...}} or {{[[query]]: ...}} - uses the block's saved display settings and filters"),
10
+ query: z
11
+ .string()
12
+ .optional()
13
+ .describe('Raw Roam query string (e.g., "{and: [[TODO]] {not: [[DONE]]}}") - NOT Datalog - results are flat list, no user filters applied'),
14
+ sort: z
15
+ .enum(["created-date", "edited-date", "daily-note-date"])
16
+ .optional()
17
+ .describe("Sort order (only for query mode, default: created-date)"),
18
+ sortOrder: z
19
+ .enum(["asc", "desc"])
20
+ .optional()
21
+ .describe("Sort direction (only for query mode, default: desc)"),
22
+ includePath: z
23
+ .boolean()
24
+ .optional()
25
+ .describe("Include breadcrumb path in results (only for query mode, default: true)"),
11
26
  offset: z.coerce.number().optional().describe("Skip first N results (default: 0)"),
12
27
  limit: z.coerce.number().optional().describe("Max results to return (default: 20)"),
13
- maxDepth: z.coerce.number().optional().describe("Max depth of children to include in markdown (default: 1)"),
28
+ maxDepth: z.coerce
29
+ .number()
30
+ .optional()
31
+ .describe("Max depth of children to include in markdown (default: 1)"),
14
32
  });
15
33
  export async function query(client, params) {
16
34
  // Validate: exactly one of uid or query must be provided
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
- import type { RoamClient } from "../client.js";
3
- import type { CallToolResult } from "../types.js";
2
+ import type { CallToolResult, RoamActionClient } from "../types.js";
4
3
  export declare const SearchSchema: z.ZodObject<{
5
4
  query: z.ZodString;
6
5
  scope: z.ZodOptional<z.ZodEnum<["pages", "blocks", "all"]>>;
@@ -10,17 +9,17 @@ export declare const SearchSchema: z.ZodObject<{
10
9
  maxDepth: z.ZodOptional<z.ZodNumber>;
11
10
  }, "strip", z.ZodTypeAny, {
12
11
  query: string;
13
- includePath?: boolean | undefined;
12
+ maxDepth?: number | undefined;
14
13
  offset?: number | undefined;
15
14
  limit?: number | undefined;
16
- maxDepth?: number | undefined;
15
+ includePath?: boolean | undefined;
17
16
  scope?: "pages" | "blocks" | "all" | undefined;
18
17
  }, {
19
18
  query: string;
20
- includePath?: boolean | undefined;
19
+ maxDepth?: number | undefined;
21
20
  offset?: number | undefined;
22
21
  limit?: number | undefined;
23
- maxDepth?: number | undefined;
22
+ includePath?: boolean | undefined;
24
23
  scope?: "pages" | "blocks" | "all" | undefined;
25
24
  }>;
26
25
  export declare const SearchTemplatesSchema: z.ZodObject<{
@@ -32,6 +31,6 @@ export declare const SearchTemplatesSchema: z.ZodObject<{
32
31
  }>;
33
32
  export type SearchParams = z.infer<typeof SearchSchema>;
34
33
  export type SearchTemplatesParams = z.infer<typeof SearchTemplatesSchema>;
35
- export declare function search(client: RoamClient, params: SearchParams): Promise<CallToolResult>;
36
- export declare function searchTemplates(client: RoamClient, params: SearchTemplatesParams): Promise<CallToolResult>;
34
+ export declare function search(client: RoamActionClient, params: SearchParams): Promise<CallToolResult>;
35
+ export declare function searchTemplates(client: RoamActionClient, params: SearchTemplatesParams): Promise<CallToolResult>;
37
36
  //# sourceMappingURL=search.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/operations/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,KAAK,EAAsE,cAAc,EAAE,MAAM,aAAa,CAAC;AAItH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;EAOvB,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;EAEhC,CAAC;AAGH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AACxD,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE1E,wBAAsB,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,cAAc,CAAC,CAY9F;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,cAAc,CAAC,CAKzB"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/operations/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAIV,cAAc,EACd,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAIrB,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;EAoBvB,CAAC;AAEH,eAAO,MAAM,qBAAqB;;;;;;EAOhC,CAAC;AAGH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AACxD,MAAM,MAAM,qBAAqB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAE1E,wBAAsB,MAAM,CAC1B,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,cAAc,CAAC,CAczB;AAED,wBAAsB,eAAe,CACnC,MAAM,EAAE,gBAAgB,EACxB,MAAM,EAAE,qBAAqB,GAC5B,OAAO,CAAC,cAAc,CAAC,CAKzB"}
@@ -2,15 +2,29 @@ import { z } from "zod";
2
2
  import { textResult } from "../types.js";
3
3
  // Schemas
4
4
  export const SearchSchema = z.object({
5
- query: z.string().describe("Search query — use empty string to get recently edited and viewed content"),
6
- scope: z.enum(["pages", "blocks", "all"]).optional().describe("Search scope: 'pages' for page titles only, 'blocks' for block content only, 'all' for both (default: 'all')"),
5
+ query: z
6
+ .string()
7
+ .describe("Search query — use empty string to get recently edited and viewed content"),
8
+ scope: z
9
+ .enum(["pages", "blocks", "all"])
10
+ .optional()
11
+ .describe("Search scope: 'pages' for page titles only, 'blocks' for block content only, 'all' for both (default: 'all')"),
7
12
  offset: z.coerce.number().optional().describe("Skip first N results (default: 0)"),
8
13
  limit: z.coerce.number().optional().describe("Max results (default: 20)"),
9
- includePath: z.boolean().optional().describe("Include breadcrumb path to each result (default: true)"),
10
- maxDepth: z.coerce.number().optional().describe("Max depth of children to include in markdown (default: 0)"),
14
+ includePath: z
15
+ .boolean()
16
+ .optional()
17
+ .describe("Include breadcrumb path to each result (default: true)"),
18
+ maxDepth: z.coerce
19
+ .number()
20
+ .optional()
21
+ .describe("Max depth of children to include in markdown (default: 0)"),
11
22
  });
12
23
  export const SearchTemplatesSchema = z.object({
13
- query: z.string().optional().describe("Keywords to filter templates by name (case-insensitive). Try relevant keywords first before listing all."),
24
+ query: z
25
+ .string()
26
+ .optional()
27
+ .describe("Keywords to filter templates by name (case-insensitive). Try relevant keywords first before listing all."),
14
28
  });
15
29
  export async function search(client, params) {
16
30
  const apiParams = {
@@ -22,7 +36,9 @@ export async function search(client, params) {
22
36
  };
23
37
  if (params.maxDepth !== undefined)
24
38
  apiParams.maxDepth = params.maxDepth;
25
- const response = await client.call("data.ai.search", [apiParams]);
39
+ const response = await client.call("data.ai.search", [
40
+ apiParams,
41
+ ]);
26
42
  return textResult(response.result ?? { total: 0, results: [] });
27
43
  }
28
44
  export async function searchTemplates(client, params) {
package/dist/tools.d.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import { z } from "zod";
2
- import type { CallToolResult } from "./types.js";
3
- import { RoamClient } from "./client.js";
2
+ import type { CallToolResult, AccessLevel, RoamActionClient, ToolGraph } from "./types.js";
4
3
  export interface ClientToolDefinition {
5
4
  name: string;
6
5
  description: string;
7
6
  schema: z.ZodObject<z.ZodRawShape>;
8
- action: (client: RoamClient, args: unknown) => Promise<CallToolResult>;
7
+ action: (client: RoamActionClient, args: unknown) => Promise<CallToolResult>;
9
8
  type: "client";
10
9
  }
11
10
  export interface StandaloneToolDefinition {
@@ -16,7 +15,41 @@ export interface StandaloneToolDefinition {
16
15
  type: "standalone";
17
16
  }
18
17
  export type ToolDefinition = ClientToolDefinition | StandaloneToolDefinition;
18
+ export declare function defineTool<T extends z.ZodRawShape>(name: string, description: string, schema: z.ZodObject<T>, action: (client: RoamActionClient, args: z.infer<z.ZodObject<T>>) => Promise<CallToolResult>): ClientToolDefinition;
19
+ export declare function defineStandaloneTool<T extends z.ZodRawShape>(name: string, description: string, schema: z.ZodObject<T>, action: (args: z.infer<z.ZodObject<T>>) => Promise<CallToolResult>): StandaloneToolDefinition;
20
+ export declare const dataTools: ClientToolDefinition[];
21
+ export declare const desktopUiTools: ClientToolDefinition[];
22
+ export declare const contentTools: ClientToolDefinition[];
19
23
  export declare const tools: ToolDefinition[];
20
24
  export declare function findTool(name: string): ToolDefinition | undefined;
21
- export declare function routeToolCall(toolName: string, args: Record<string, unknown>): Promise<CallToolResult>;
25
+ export interface RouteToolCallOptions {
26
+ /**
27
+ * Resolve a graph identifier (nickname/name) to a ToolGraph. Required.
28
+ * Local consumers use the resolver from @roam-research/roam-tools-local;
29
+ * hosted consumers wire their own (e.g., reading picker grants from RTDB).
30
+ */
31
+ resolveGraph: (providedGraph?: string) => Promise<ToolGraph>;
32
+ /**
33
+ * Construct a client for the resolved graph. Required.
34
+ * Local consumers return a RoamClient; hosted consumers return a transport
35
+ * that talks to proxy.api.roamresearch.com.
36
+ */
37
+ createClient: (graph: ToolGraph) => Promise<RoamActionClient> | RoamActionClient;
38
+ /**
39
+ * "local-sync" runs the desktop token-info side-flow on get_graph_guidelines:
40
+ * parallel getTokenInfo, access-level validation, status writes, and result
41
+ * enrichment. "skip" (default) disables that side-flow entirely. Graph-name
42
+ * prefix (prependGraphInfo) is unaffected by this mode and runs in both.
43
+ */
44
+ tokenInfoMode?: "local-sync" | "skip";
45
+ /**
46
+ * Only consulted in local-sync mode. Hosted callers may use this to write to
47
+ * their own grant store. If omitted, status changes are not persisted.
48
+ */
49
+ onTokenStatusUpdate?: (nickname: string, patch: {
50
+ accessLevel?: AccessLevel;
51
+ lastKnownTokenStatus?: "active" | "revoked";
52
+ }) => Promise<void>;
53
+ }
54
+ export declare function routeToolCall(toolName: string, args: Record<string, unknown>, options: RouteToolCallOptions): Promise<CallToolResult>;
22
55
  //# sourceMappingURL=tools.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,cAAc,EAAkC,MAAM,YAAY,CAAC;AAEjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA+BzC,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACnC,MAAM,EAAE,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACvE,IAAI,EAAE,QAAQ,CAAC;CAChB;AAGD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;IACnC,MAAM,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACnD,IAAI,EAAE,YAAY,CAAC;CACpB;AAED,MAAM,MAAM,cAAc,GAAG,oBAAoB,GAAG,wBAAwB,CAAC;AA+L7E,eAAO,MAAM,KAAK,EAAE,cAAc,EAGjC,CAAC;AAEF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAEjE;AA0FD,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,cAAc,CAAC,CA+HzB"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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"}
package/dist/tools.js CHANGED
@@ -1,24 +1,25 @@
1
1
  import { z } from "zod";
2
2
  import { RoamError } from "./types.js";
3
- import { RoamClient } from "./client.js";
4
- import { resolveGraph, getPort, updateGraphTokenStatus } from "./graph-resolver.js";
5
3
  import { CreatePageSchema, GetPageSchema, DeletePageSchema, UpdatePageSchema, GetGuidelinesSchema, createPage, getPage, deletePage, updatePage, getGuidelines, } from "./operations/pages.js";
6
4
  import { CreateBlockSchema, GetBlockSchema, UpdateBlockSchema, DeleteBlockSchema, MoveBlockSchema, GetBacklinksSchema, AddCommentSchema, GetCommentsSchema, createBlock, getBlock, updateBlock, deleteBlock, moveBlock, getBacklinks, addComment, getComments, } from "./operations/blocks.js";
7
- import { SearchSchema, SearchTemplatesSchema, search, searchTemplates } from "./operations/search.js";
5
+ import { SearchSchema, SearchTemplatesSchema, search, searchTemplates, } from "./operations/search.js";
8
6
  import { QuerySchema, query } from "./operations/query.js";
7
+ import { DatalogQuerySchema, datalogQuery } from "./operations/datalog.js";
9
8
  import { GetOpenWindowsSchema, GetSelectionSchema, OpenMainWindowSchema, OpenSidebarSchema, getOpenWindows, getSelection, openMainWindow, openSidebar, } from "./operations/navigation.js";
10
- import { FileGetSchema, FileUploadSchema, FileDeleteSchema, getFile, uploadFile, deleteFile } from "./operations/files.js";
11
- import { ListGraphsSchema, SetupNewGraphSchema, listGraphs, setupNewGraph } from "./operations/graphs.js";
9
+ import { FileGetSchema, FileUploadSchema, FileDeleteSchema, getFile, uploadFile, deleteFile, } from "./operations/files.js";
12
10
  // Common schema for graph parameter (used by most tools)
13
11
  const GraphSchema = z.object({
14
- graph: z.string().optional().describe("Graph nickname or name (optional - auto-selects if only one graph is configured)"),
12
+ graph: z
13
+ .string()
14
+ .optional()
15
+ .describe("Graph nickname or name (optional - auto-selects if only one graph is configured)"),
15
16
  });
16
17
  // Helper to extend any schema with graph parameter
17
18
  function withGraph(schema) {
18
19
  return schema.extend(GraphSchema.shape);
19
20
  }
20
21
  // Helper to create tool with graph parameter
21
- function defineTool(name, description, schema, action) {
22
+ export function defineTool(name, description, schema, action) {
22
23
  return {
23
24
  name,
24
25
  description,
@@ -28,7 +29,7 @@ function defineTool(name, description, schema, action) {
28
29
  };
29
30
  }
30
31
  // Helper to create standalone tool (no graph parameter, handles its own resolution)
31
- function defineStandaloneTool(name, description, schema, action) {
32
+ export function defineStandaloneTool(name, description, schema, action) {
32
33
  return {
33
34
  name,
34
35
  description,
@@ -37,43 +38,57 @@ function defineStandaloneTool(name, description, schema, action) {
37
38
  type: "standalone",
38
39
  };
39
40
  }
40
- // Graph Management Tools (standalone - handle their own resolution)
41
- const graphManagementTools = [
42
- defineStandaloneTool("list_graphs", "List all configured graphs with their nicknames. Also provides setup instructions for connecting additional graphs.", ListGraphsSchema, listGraphs),
43
- defineStandaloneTool("setup_new_graph", "Set up a new Roam graph for access, or list available graphs. Call without arguments to see which graphs are available in Roam Desktop. Call with graph and nickname to connect a specific graph — ask the user what they'd like to call the graph before choosing a nickname. The user will see an approval dialog in Roam desktop app and must approve the token request. If the graph is already configured, returns the existing configuration without making changes.", SetupNewGraphSchema, setupNewGraph),
44
- ];
45
41
  // Note appended to all client tool descriptions
46
42
  const GUIDELINES_NOTE = "\n\nNote: Call get_graph_guidelines first when starting to work with a graph.";
47
- // Content Tools (require graph/client)
48
- const contentTools = [
43
+ // Data Tools (require graph/client; reusable across local + hosted MCP transports)
44
+ export const dataTools = [
49
45
  defineTool("get_graph_guidelines", "IMPORTANT: Call this tool first when starting to work with a graph, before performing any other operations. Returns user-defined instructions and preferences for AI agents. The user may have specified naming conventions, preferred structures, or constraints that should guide your behavior. After receiving the response, follow the nextSteps field — it contains orientation actions you should take before proceeding.", GetGuidelinesSchema, getGuidelines),
50
46
  defineTool("create_page", "Create a new page in Roam, optionally with markdown content." + GUIDELINES_NOTE, CreatePageSchema, createPage),
51
- defineTool("create_block", "Create blocks from markdown content. Target by parentUid, pageTitle, or dailyNotePage (page created if needed). Use nestUnder to insert under a specific child block. Supports nested bulleted lists via markdown indentation." + GUIDELINES_NOTE, CreateBlockSchema, createBlock),
47
+ defineTool("create_block", "Create blocks from markdown content. Target by parentUid, pageTitle, or dailyNotePage (page created if needed). Use nestUnder to insert under a specific child block. Supports nested bulleted lists via markdown indentation." +
48
+ GUIDELINES_NOTE, CreateBlockSchema, createBlock),
52
49
  defineTool("update_block", "Update an existing block's content or properties." + GUIDELINES_NOTE, UpdateBlockSchema, updateBlock),
53
50
  defineTool("delete_block", "Delete a block and all its children." + GUIDELINES_NOTE, DeleteBlockSchema, deleteBlock),
54
51
  defineTool("move_block", "Move a block to a new location." + GUIDELINES_NOTE, MoveBlockSchema, moveBlock),
55
- defineTool("add_comment", "Add a comment to a block (comment thread, NOT a child block). Prefer `comment` for simple text; use `commentMarkdown` for structured content. Same-day calls on the same block append to your existing comment." + GUIDELINES_NOTE, AddCommentSchema, addComment),
56
- defineTool("get_comments", "Get comments on a block with author, timestamps, and edit info. If singleEditableUid is set, the comment can be edited with update_block. Only works for blocks, not pages." + GUIDELINES_NOTE, GetCommentsSchema, getComments),
52
+ defineTool("add_comment", "Add a comment to a block (comment thread, NOT a child block). Prefer `comment` for simple text; use `commentMarkdown` for structured content. Same-day calls on the same block append to your existing comment." +
53
+ GUIDELINES_NOTE, AddCommentSchema, addComment),
54
+ defineTool("get_comments", "Get comments on a block with author, timestamps, and edit info. If singleEditableUid is set, the comment can be edited with update_block. Only works for blocks, not pages." +
55
+ GUIDELINES_NOTE, GetCommentsSchema, getComments),
57
56
  defineTool("delete_page", "Delete a page and all its contents." + GUIDELINES_NOTE, DeletePageSchema, deletePage),
58
- defineTool("update_page", "Update a page's title or children view type. Set mergePages to true if renaming to a title that already exists." + GUIDELINES_NOTE, UpdatePageSchema, updatePage),
59
- defineTool("search", "Search for pages and blocks by text. Returns paginated results with markdown content and optional breadcrumb paths. Call with an empty query to get recently edited and viewed content — useful for understanding what the user is currently working on." + GUIDELINES_NOTE, SearchSchema, search),
60
- defineTool("search_templates", "Search Roam templates by name. When the user mentions 'my X template' or 'the X template', use this tool to find it. Templates are user-created reusable content blocks tagged with [[roam/templates]]. Returns template name, uid, and content as markdown." + GUIDELINES_NOTE, SearchTemplatesSchema, searchTemplates),
61
- defineTool("roam_query", 'Execute a Roam query ({{query: }} or {{[[query]]: }} blocks, NOT Datalog). Two modes: (1) UID mode - pass a block UID containing a query component to run it with saved settings/filters; (2) Query mode - pass a raw query string like "{and: [[TODO]] {not: [[DONE]]}}". Returns paginated results with markdown content.' + GUIDELINES_NOTE, QuerySchema, query),
62
- defineTool("get_page", "Get a page's content as markdown. Returns content with <roam> metadata tags containing UIDs - use these for follow-up operations but strip them when showing content to the user. Show remaining content verbatim, never paraphrase. Use maxDepth for large pages." + GUIDELINES_NOTE, GetPageSchema, getPage),
63
- defineTool("get_block", "Get a block's content as markdown. Returns content with <roam> metadata tags containing UIDs - use these for follow-up operations but strip them when showing content to the user. Show remaining content verbatim, never paraphrase. Use maxDepth for large blocks." + GUIDELINES_NOTE, GetBlockSchema, getBlock),
64
- defineTool("get_backlinks", "Get paginated backlinks (linked references) for a page or block, formatted as markdown. Returns total count and results with optional breadcrumb paths." + GUIDELINES_NOTE, GetBacklinksSchema, getBacklinks),
57
+ defineTool("update_page", "Update a page's title or children view type. Set mergePages to true if renaming to a title that already exists." +
58
+ GUIDELINES_NOTE, UpdatePageSchema, updatePage),
59
+ defineTool("search", "Search for pages and blocks by text. Returns paginated results with markdown content and optional breadcrumb paths. Call with an empty query to get recently edited and viewed content useful for understanding what the user is currently working on." +
60
+ GUIDELINES_NOTE, SearchSchema, search),
61
+ defineTool("search_templates", "Search Roam templates by name. When the user mentions 'my X template' or 'the X template', use this tool to find it. Templates are user-created reusable content blocks tagged with [[roam/templates]]. Returns template name, uid, and content as markdown." +
62
+ GUIDELINES_NOTE, SearchTemplatesSchema, searchTemplates),
63
+ defineTool("roam_query", 'Execute a Roam query ({{query: }} or {{[[query]]: }} blocks, NOT Datalog). Two modes: (1) UID mode - pass a block UID containing a query component to run it with saved settings/filters; (2) Query mode - pass a raw query string like "{and: [[TODO]] {not: [[DONE]]}}". Returns paginated results with markdown content.' +
64
+ GUIDELINES_NOTE, QuerySchema, query),
65
+ defineTool("datalog_query", "Execute a datomic-style datalog query against the graph's datascript database. Supported clauses: :find, :where, :in, and :timeout (ms). Inputs are positional parameters bound to :in variables after $. Write specific :where clauses to keep results bounded." +
66
+ GUIDELINES_NOTE, DatalogQuerySchema, datalogQuery),
67
+ defineTool("get_page", "Get a page's content as markdown. Returns content with <roam> metadata tags containing UIDs - use these for follow-up operations but strip them when showing content to the user. Show remaining content verbatim, never paraphrase. Use maxDepth for large pages." +
68
+ GUIDELINES_NOTE, GetPageSchema, getPage),
69
+ defineTool("get_block", "Get a block's content as markdown. Returns content with <roam> metadata tags containing UIDs - use these for follow-up operations but strip them when showing content to the user. Show remaining content verbatim, never paraphrase. Use maxDepth for large blocks." +
70
+ GUIDELINES_NOTE, GetBlockSchema, getBlock),
71
+ defineTool("get_backlinks", "Get paginated backlinks (linked references) for a page or block, formatted as markdown. Returns total count and results with optional breadcrumb paths." +
72
+ GUIDELINES_NOTE, GetBacklinksSchema, getBacklinks),
73
+ ];
74
+ // Desktop UI Tools (require local Roam Desktop — file ops + window/selection introspection;
75
+ // hosted MCP omits these because the parameters/effects assume a local environment).
76
+ export const desktopUiTools = [
65
77
  defineTool("get_open_windows", "Get the current view in the main window and all open sidebar windows." + GUIDELINES_NOTE, GetOpenWindowsSchema, getOpenWindows),
66
78
  defineTool("get_selection", "Get the currently focused block and any multi-selected blocks." + GUIDELINES_NOTE, GetSelectionSchema, getSelection),
67
79
  defineTool("open_main_window", "Navigate to a page or block in the main window." + GUIDELINES_NOTE, OpenMainWindowSchema, openMainWindow),
68
80
  defineTool("open_sidebar", "Open a page or block in the right sidebar." + GUIDELINES_NOTE, OpenSidebarSchema, openSidebar),
69
81
  defineTool("file_get", "Fetch a file hosted on Roam (handles decryption for encrypted graphs)." + GUIDELINES_NOTE, FileGetSchema, getFile),
70
- defineTool("file_upload", "Upload a file to Roam. Returns the Firebase storage URL. Usually you'll want to create a new block with the file as markdown: `![](url)`. Provide ONE of: filePath (preferred - local file, server reads directly), url (remote URL, server fetches), or base64 (raw data, fallback for sandboxed clients)." + GUIDELINES_NOTE, FileUploadSchema, uploadFile),
82
+ defineTool("file_upload", "Upload a file to Roam. Returns the Firebase storage URL. Usually you'll want to create a new block with the file as markdown: `![](url)`. Provide ONE of: filePath (preferred - local file, server reads directly), url (remote URL, server fetches), or base64 (raw data, fallback for sandboxed clients)." +
83
+ GUIDELINES_NOTE, FileUploadSchema, uploadFile),
71
84
  defineTool("file_delete", "Delete a file hosted on Roam." + GUIDELINES_NOTE, FileDeleteSchema, deleteFile),
72
85
  ];
73
- export const tools = [
74
- ...graphManagementTools,
75
- ...contentTools,
76
- ];
86
+ // Backwards-compatible aggregate of all client tools.
87
+ export const contentTools = [...dataTools, ...desktopUiTools];
88
+ // All client tools available in core. Local standalone tools (list_graphs,
89
+ // setup_new_graph) live in @roam-research/roam-tools-local since they touch
90
+ // ~/.roam-tools.json and the Roam Desktop API.
91
+ export const tools = [...dataTools, ...desktopUiTools];
77
92
  export function findTool(name) {
78
93
  return tools.find((t) => t.name === name);
79
94
  }
@@ -89,19 +104,13 @@ function prependGraphInfo(result, nickname) {
89
104
  if (first.type === "text") {
90
105
  return {
91
106
  ...result,
92
- content: [
93
- { ...first, text: `${prefix}\n\n${first.text}` },
94
- ...content.slice(1),
95
- ],
107
+ content: [{ ...first, text: `${prefix}\n\n${first.text}` }, ...content.slice(1)],
96
108
  };
97
109
  }
98
110
  // For image or other content types, prepend a text block
99
111
  return {
100
112
  ...result,
101
- content: [
102
- { type: "text", text: prefix },
103
- ...content,
104
- ],
113
+ content: [{ type: "text", text: prefix }, ...content],
105
114
  };
106
115
  }
107
116
  /**
@@ -159,46 +168,40 @@ function roamErrorResult(error) {
159
168
  isError: true,
160
169
  };
161
170
  }
162
- export async function routeToolCall(toolName, args) {
171
+ export async function routeToolCall(toolName, args, options) {
163
172
  const tool = findTool(toolName);
164
173
  if (!tool) {
165
174
  throw new Error(`Unknown tool: ${toolName}`);
166
175
  }
176
+ // Core only registers client tools. Standalone tools live in
177
+ // @roam-research/roam-tools-local; route them through that wrapper.
178
+ if (tool.type !== "client") {
179
+ throw new Error(`Tool ${toolName}: core's routeToolCall only handles client tools. ` +
180
+ `Standalone tools live in @roam-research/roam-tools-local.`);
181
+ }
167
182
  // Validate and parse args with Zod
168
183
  const parsed = tool.schema.safeParse(args);
169
184
  if (!parsed.success) {
170
185
  throw new Error(`Invalid arguments: ${parsed.error.message}`);
171
186
  }
172
- // Handle standalone tools (graph management)
173
- if (tool.type === "standalone") {
174
- try {
175
- return await tool.action(parsed.data);
176
- }
177
- catch (error) {
178
- if (error instanceof RoamError) {
179
- return roamErrorResult(error);
180
- }
181
- throw error;
182
- }
183
- }
184
- // Handle client tools (require graph resolution)
187
+ const tokenInfoMode = options.tokenInfoMode ?? "skip";
188
+ const updateTokenStatus = options.onTokenStatusUpdate;
185
189
  try {
186
- // Extract graph from validated args and resolve it
187
- const { graph, ...restArgs } = parsed.data;
188
- const resolvedGraph = await resolveGraph(graph);
189
- const port = await getPort();
190
- // Create client with full config
191
- const client = new RoamClient({
192
- graphName: resolvedGraph.name,
193
- graphType: resolvedGraph.type,
194
- token: resolvedGraph.token,
195
- port,
196
- });
197
- // Special handling for get_graph_guidelines: sync token info in parallel
198
- if (tool.name === "get_graph_guidelines") {
190
+ const { graph: graphArg, ...restArgs } = parsed.data;
191
+ const graph = await options.resolveGraph(graphArg);
192
+ const client = await options.createClient(graph);
193
+ // Special handling for get_graph_guidelines: sync token info in parallel.
194
+ // Only fires in local-sync mode AND when the client implements getTokenInfo.
195
+ // Bind early so TS narrows the optional method through the truthy check.
196
+ const getTokenInfoFn = client.getTokenInfo?.bind(client);
197
+ if (tool.name === "get_graph_guidelines" && tokenInfoMode === "local-sync" && getTokenInfoFn) {
198
+ // In local-sync mode, the resolver is expected to return ResolvedGraph
199
+ // (with lastKnownTokenStatus). If a custom resolver omits the field,
200
+ // the read returns undefined and behavior is identical.
201
+ const resolvedGraph = graph;
199
202
  const [actionSettled, tokenInfoSettled] = await Promise.allSettled([
200
203
  tool.action(client, restArgs),
201
- client.getTokenInfo(),
204
+ getTokenInfoFn(),
202
205
  ]);
203
206
  // getTokenInfo() never throws, so always fulfilled
204
207
  const tokenInfoResult = tokenInfoSettled.status === "fulfilled"
@@ -206,11 +209,15 @@ export async function routeToolCall(toolName, args) {
206
209
  : { status: "unknown" };
207
210
  // Handle revoked token FIRST (before examining action result)
208
211
  if (tokenInfoResult.status === "revoked") {
209
- if (resolvedGraph.lastKnownTokenStatus !== "revoked") {
212
+ if (updateTokenStatus && resolvedGraph.lastKnownTokenStatus !== "revoked") {
210
213
  try {
211
- await updateGraphTokenStatus(resolvedGraph.nickname, { lastKnownTokenStatus: "revoked" });
214
+ await updateTokenStatus(resolvedGraph.nickname, {
215
+ lastKnownTokenStatus: "revoked",
216
+ });
217
+ }
218
+ catch {
219
+ // best-effort status update
212
220
  }
213
- catch { }
214
221
  }
215
222
  const baseResult = actionSettled.status === "fulfilled"
216
223
  ? actionSettled.value
@@ -226,22 +233,24 @@ export async function routeToolCall(toolName, args) {
226
233
  const result = actionSettled.value;
227
234
  if (tokenInfoResult.status === "active") {
228
235
  const info = tokenInfoResult.info;
229
- // Validate access level before writing to prevent config corruption
236
+ // Validate access level before writing to prevent status corruption
230
237
  const validLevels = ["read-only", "read-append", "full"];
231
238
  const level = validLevels.includes(info.grantedAccessLevel)
232
239
  ? info.grantedAccessLevel
233
240
  : undefined;
234
- // Only write to config if something actually changed
241
+ // Only write if something actually changed
235
242
  const accessLevelChanged = level && resolvedGraph.accessLevel !== level;
236
243
  const tokenStatusChanged = resolvedGraph.lastKnownTokenStatus !== "active";
237
- if (accessLevelChanged || tokenStatusChanged) {
244
+ if (updateTokenStatus && (accessLevelChanged || tokenStatusChanged)) {
238
245
  try {
239
- await updateGraphTokenStatus(resolvedGraph.nickname, {
246
+ await updateTokenStatus(resolvedGraph.nickname, {
240
247
  ...(accessLevelChanged ? { accessLevel: level } : {}),
241
248
  lastKnownTokenStatus: "active",
242
249
  });
243
250
  }
244
- catch { }
251
+ catch {
252
+ // best-effort status update
253
+ }
245
254
  }
246
255
  if (!result.isError) {
247
256
  const enriched = enrichResultWithTokenInfo(result, info);
@@ -250,22 +259,24 @@ export async function routeToolCall(toolName, args) {
250
259
  return result;
251
260
  }
252
261
  // status === "unknown" — action succeeded, so token isn't revoked; clear stale status
253
- if (resolvedGraph.lastKnownTokenStatus !== "active") {
262
+ if (updateTokenStatus && resolvedGraph.lastKnownTokenStatus !== "active") {
254
263
  try {
255
- await updateGraphTokenStatus(resolvedGraph.nickname, { lastKnownTokenStatus: "active" });
264
+ await updateTokenStatus(resolvedGraph.nickname, { lastKnownTokenStatus: "active" });
265
+ }
266
+ catch {
267
+ // best-effort status update
256
268
  }
257
- catch { }
258
269
  }
259
270
  if (!result.isError) {
260
271
  return prependGraphInfo(result, resolvedGraph.nickname);
261
272
  }
262
273
  return result;
263
274
  }
264
- // Normal flow for all other tools
275
+ // Normal flow for all other tools (and get_graph_guidelines when token-info
276
+ // sync is skipped or unavailable). Graph-name prefix runs in both modes.
265
277
  const result = await tool.action(client, restArgs);
266
- // Prepend graph info to successful responses
267
278
  if (!result.isError) {
268
- return prependGraphInfo(result, resolvedGraph.nickname);
279
+ return prependGraphInfo(result, graph.nickname);
269
280
  }
270
281
  return result;
271
282
  }