@dinoxx/dinox-cli 1.0.18 → 1.0.20

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 (118) hide show
  1. package/README.md +807 -418
  2. package/dist/auth/userInfo.d.ts +2 -0
  3. package/dist/auth/userInfo.js +28 -11
  4. package/dist/cli.js +15 -1
  5. package/dist/commands/auth/index.d.ts +2 -0
  6. package/dist/commands/auth/index.js +217 -163
  7. package/dist/commands/boxes/index.d.ts +2 -0
  8. package/dist/commands/boxes/index.js +119 -75
  9. package/dist/commands/boxes/repo.d.ts +8 -0
  10. package/dist/commands/boxes/repo.js +41 -0
  11. package/dist/commands/config/index.d.ts +2 -0
  12. package/dist/commands/config/index.js +85 -36
  13. package/dist/commands/descriptor/register.d.ts +8 -0
  14. package/dist/commands/descriptor/register.js +113 -0
  15. package/dist/commands/descriptor/types.d.ts +37 -0
  16. package/dist/commands/descriptor/types.js +17 -0
  17. package/dist/commands/graph/index.d.ts +2 -0
  18. package/dist/commands/graph/index.js +143 -73
  19. package/dist/commands/info/index.d.ts +2 -0
  20. package/dist/commands/info/index.js +41 -14
  21. package/dist/commands/mcp/index.js +11 -10
  22. package/dist/commands/mcp/server.js +8 -4
  23. package/dist/commands/mcp/tools.js +30 -104
  24. package/dist/commands/mcp/types.d.ts +8 -1
  25. package/dist/commands/meta/index.d.ts +2 -0
  26. package/dist/commands/meta/index.js +64 -33
  27. package/dist/commands/meta/repo.d.ts +6 -0
  28. package/dist/commands/meta/repo.js +6 -0
  29. package/dist/commands/notes/actions.d.ts +10 -0
  30. package/dist/commands/notes/actions.js +403 -0
  31. package/dist/commands/notes/descriptors.d.ts +4 -0
  32. package/dist/commands/notes/descriptors.js +250 -0
  33. package/dist/commands/notes/dryRun.d.ts +31 -0
  34. package/dist/commands/notes/dryRun.js +130 -0
  35. package/dist/commands/notes/index.d.ts +2 -51
  36. package/dist/commands/notes/index.js +2 -581
  37. package/dist/commands/notes/repo.d.ts +9 -0
  38. package/dist/commands/notes/repo.js +56 -10
  39. package/dist/commands/notes/runtime.d.ts +28 -0
  40. package/dist/commands/notes/runtime.js +185 -0
  41. package/dist/commands/notes/searchOutput.d.ts +10 -2
  42. package/dist/commands/notes/searchOutput.js +14 -2
  43. package/dist/commands/notes/searchResponse.d.ts +51 -0
  44. package/dist/commands/notes/searchResponse.js +56 -0
  45. package/dist/commands/notes/searchSql.js +2 -0
  46. package/dist/commands/prompt/index.d.ts +2 -0
  47. package/dist/commands/prompt/index.js +122 -72
  48. package/dist/commands/prompt/repo.d.ts +7 -0
  49. package/dist/commands/prompt/repo.js +46 -0
  50. package/dist/commands/schema/commandMetadata.d.ts +2 -0
  51. package/dist/commands/schema/commandMetadata.js +18 -0
  52. package/dist/commands/schema/globalOptions.d.ts +3 -0
  53. package/dist/commands/schema/globalOptions.js +3 -0
  54. package/dist/commands/schema/index.d.ts +4 -0
  55. package/dist/commands/schema/index.js +109 -0
  56. package/dist/commands/schema/renderSkills.d.ts +6 -0
  57. package/dist/commands/schema/renderSkills.js +216 -0
  58. package/dist/commands/schema/spec.d.ts +6 -0
  59. package/dist/commands/schema/spec.js +40 -0
  60. package/dist/commands/schema/types.d.ts +47 -0
  61. package/dist/commands/schema/types.js +1 -0
  62. package/dist/commands/storage/index.d.ts +4 -0
  63. package/dist/commands/storage/index.js +292 -0
  64. package/dist/commands/storage/repo.d.ts +162 -0
  65. package/dist/commands/storage/repo.js +770 -0
  66. package/dist/commands/sync.d.ts +2 -0
  67. package/dist/commands/sync.js +41 -27
  68. package/dist/commands/tags/index.d.ts +2 -0
  69. package/dist/commands/tags/index.js +121 -79
  70. package/dist/commands/tags/repo.d.ts +7 -0
  71. package/dist/commands/tags/repo.js +52 -0
  72. package/dist/commands/todo/dryRun.d.ts +23 -0
  73. package/dist/commands/todo/dryRun.js +133 -0
  74. package/dist/commands/todo/index.d.ts +2 -0
  75. package/dist/commands/todo/index.js +285 -135
  76. package/dist/commands/todo/repo.d.ts +11 -0
  77. package/dist/commands/todo/repo.js +37 -9
  78. package/dist/commands/update.d.ts +3 -0
  79. package/dist/commands/update.js +35 -13
  80. package/dist/commands/writeDryRun.d.ts +62 -0
  81. package/dist/commands/writeDryRun.js +131 -0
  82. package/dist/config/paths.d.ts +2 -1
  83. package/dist/config/paths.js +11 -1
  84. package/dist/config/resolve.js +7 -5
  85. package/dist/constants/links.d.ts +1 -1
  86. package/dist/constants/links.js +1 -1
  87. package/dist/daemon/ensure.js +3 -2
  88. package/dist/daemon/index.js +17 -15
  89. package/dist/dinox.js +9 -13
  90. package/dist/powersync/runtime.d.ts +1 -1
  91. package/dist/powersync/runtime.js +5 -3
  92. package/dist/powersync/schema/index.d.ts +27 -0
  93. package/dist/powersync/schema/index.js +2 -0
  94. package/dist/powersync/schema/note.d.ts +1 -0
  95. package/dist/powersync/schema/note.js +1 -0
  96. package/dist/powersync/schema/projects.d.ts +3 -0
  97. package/dist/powersync/schema/projects.js +3 -0
  98. package/dist/powersync/schema/storage.d.ts +24 -0
  99. package/dist/powersync/schema/storage.js +24 -0
  100. package/dist/skills/check.d.ts +6 -0
  101. package/dist/skills/check.js +254 -0
  102. package/dist/utils/bidlinkCanonicalization.d.ts +2 -0
  103. package/dist/utils/bidlinkCanonicalization.js +161 -0
  104. package/dist/utils/dryRun.d.ts +13 -0
  105. package/dist/utils/dryRun.js +69 -0
  106. package/dist/utils/output.d.ts +19 -0
  107. package/dist/utils/output.js +155 -1
  108. package/dist/utils/terminalSanitization.d.ts +2 -0
  109. package/dist/utils/terminalSanitization.js +33 -0
  110. package/dist/utils/tiptapMarkdown.d.ts +4 -1
  111. package/dist/utils/tiptapMarkdown.js +285 -113
  112. package/dist/utils/tiptapMarkdownSchema.d.ts +37 -0
  113. package/dist/utils/tiptapMarkdownSchema.js +577 -0
  114. package/dist/utils/tiptapMarkdownSchemaUtils.d.ts +74 -0
  115. package/dist/utils/tiptapMarkdownSchemaUtils.js +394 -0
  116. package/dist/utils/updateNotice.d.ts +7 -0
  117. package/dist/utils/updateNotice.js +64 -0
  118. package/package.json +25 -3
@@ -1,12 +1,15 @@
1
- import { printYaml } from '../../utils/output.js';
2
- import { sendDaemonRequest } from '../../daemon/client.js';
3
- import { ensureDaemonRunning } from '../../daemon/ensure.js';
4
- import { DinoxError } from '../../utils/errors.js';
5
1
  import { loadConfig } from '../../config/store.js';
6
2
  import { resolveConfig } from '../../config/resolve.js';
7
3
  import { connectPowerSync } from '../../powersync/runtime.js';
4
+ import { sendDaemonRequest } from '../../daemon/client.js';
5
+ import { ensureDaemonRunning } from '../../daemon/ensure.js';
6
+ import { registerCommandGroup } from '../descriptor/register.js';
7
+ import { FORMAT_OPTIONS, OFFLINE_OPTIONS, SYNC_TIMEOUT_OPTIONS } from '../schema/globalOptions.js';
8
+ import { DinoxError } from '../../utils/errors.js';
9
+ import { printSuccess } from '../../utils/output.js';
8
10
  import { parseSyncTimeoutMs } from '../../utils/syncTimeout.js';
9
11
  import { getBacklinks, getGraphStats, getOutlinks, getRelatedNotes } from './repo.js';
12
+ const GRAPH_GLOBAL_OPTIONS = [...FORMAT_OPTIONS, ...OFFLINE_OPTIONS, ...SYNC_TIMEOUT_OPTIONS];
10
13
  function parseDepth(value) {
11
14
  if (value === undefined || value === null || value === '') {
12
15
  return 2;
@@ -26,8 +29,7 @@ function parseDepth(value) {
26
29
  }
27
30
  async function withOfflineDb(command, fn) {
28
31
  const globals = command.optsWithGlobals?.() ?? {};
29
- const offline = Boolean(globals.offline);
30
- if (!offline) {
32
+ if (!Boolean(globals.offline)) {
31
33
  throw new DinoxError('withOfflineDb called without --offline');
32
34
  }
33
35
  const config = resolveConfig(await loadConfig());
@@ -40,74 +42,142 @@ async function withOfflineDb(command, fn) {
40
42
  await db.close().catch(() => undefined);
41
43
  }
42
44
  }
43
- export function registerGraphCommands(program) {
44
- const graph = program.command('graph').description('Query knowledge graph');
45
- graph
46
- .command('backlinks')
47
- .description('Get notes that link to this note')
48
- .argument('<id>', 'note id')
49
- .action(async (id, _options, command) => {
50
- const globals = command.optsWithGlobals?.() ?? {};
51
- const offline = Boolean(globals.offline);
52
- if (offline) {
53
- const result = await withOfflineDb(command, (db) => getBacklinks(db, id));
54
- printYaml(result);
55
- return;
56
- }
57
- await ensureDaemonRunning();
58
- const result = await sendDaemonRequest('graph.backlinks', { id });
59
- printYaml(result);
60
- });
61
- graph
62
- .command('outlinks')
63
- .description('Get notes that this note links to')
64
- .argument('<id>', 'note id')
65
- .action(async (id, _options, command) => {
66
- const globals = command.optsWithGlobals?.() ?? {};
67
- const offline = Boolean(globals.offline);
68
- if (offline) {
69
- const result = await withOfflineDb(command, (db) => getOutlinks(db, id));
70
- printYaml(result);
71
- return;
45
+ async function runGraphCommand(context, onlineAction, offlineAction) {
46
+ const globals = context.globals;
47
+ if (Boolean(globals.offline)) {
48
+ printSuccess(await withOfflineDb(context.command, offlineAction));
49
+ return;
50
+ }
51
+ await ensureDaemonRunning();
52
+ printSuccess(await onlineAction());
53
+ }
54
+ const graphBacklinksCommand = {
55
+ name: 'backlinks',
56
+ description: 'Get notes that link to a target note',
57
+ arguments: [{ syntax: '<id>', description: 'Note id' }],
58
+ schema: {
59
+ path: 'graph.backlinks',
60
+ risk: 'read',
61
+ auth: 'required',
62
+ globalOptions: GRAPH_GLOBAL_OPTIONS,
63
+ output: {
64
+ type: 'structured',
65
+ summary: 'Backlink graph payload',
66
+ fields: ['note_id', 'backlinks'],
67
+ },
68
+ mcp: {
69
+ toolName: 'dinox_graph_backlinks',
70
+ description: 'Get notes that link to this note',
71
+ properties: [{ name: 'id', type: 'string', description: 'Note ID', required: true }],
72
+ },
73
+ examples: ['dino graph backlinks <id> --format json'],
74
+ },
75
+ action: async (context) => {
76
+ const id = context.args.id;
77
+ if (!id) {
78
+ throw new DinoxError('Note id is required');
72
79
  }
73
- await ensureDaemonRunning();
74
- const result = await sendDaemonRequest('graph.outlinks', { id });
75
- printYaml(result);
76
- });
77
- graph
78
- .command('stats')
79
- .description('Get graph statistics')
80
- .action(async (_options, command) => {
81
- const globals = command.optsWithGlobals?.() ?? {};
82
- const offline = Boolean(globals.offline);
83
- if (offline) {
84
- const result = await withOfflineDb(command, (db) => getGraphStats(db));
85
- printYaml(result);
86
- return;
80
+ await runGraphCommand(context, () => sendDaemonRequest('graph.backlinks', { id }), (db) => getBacklinks(db, id));
81
+ },
82
+ };
83
+ const graphOutlinksCommand = {
84
+ name: 'outlinks',
85
+ description: 'Get notes that this note links to',
86
+ arguments: [{ syntax: '<id>', description: 'Note id' }],
87
+ schema: {
88
+ path: 'graph.outlinks',
89
+ risk: 'read',
90
+ auth: 'required',
91
+ globalOptions: GRAPH_GLOBAL_OPTIONS,
92
+ output: {
93
+ type: 'structured',
94
+ summary: 'Outlink graph payload',
95
+ fields: ['note_id', 'outlinks'],
96
+ },
97
+ mcp: {
98
+ toolName: 'dinox_graph_outlinks',
99
+ description: 'Get notes that this note links to',
100
+ properties: [{ name: 'id', type: 'string', description: 'Note ID', required: true }],
101
+ },
102
+ examples: ['dino graph outlinks <id> --format json'],
103
+ },
104
+ action: async (context) => {
105
+ const id = context.args.id;
106
+ if (!id) {
107
+ throw new DinoxError('Note id is required');
87
108
  }
88
- await ensureDaemonRunning();
89
- const result = await sendDaemonRequest('graph.stats');
90
- printYaml(result);
91
- });
92
- graph
93
- .command('related')
94
- .description('Get related notes within N degrees of separation')
95
- .argument('<id>', 'note id')
96
- .option('--depth <n>', 'degrees of separation (1-5, default: 2)')
97
- .action(async (id, options, command) => {
98
- const depth = parseDepth(options.depth);
99
- const globals = command.optsWithGlobals?.() ?? {};
100
- const offline = Boolean(globals.offline);
101
- if (offline) {
102
- const result = await withOfflineDb(command, (db) => getRelatedNotes(db, id, depth));
103
- printYaml(result);
104
- return;
109
+ await runGraphCommand(context, () => sendDaemonRequest('graph.outlinks', { id }), (db) => getOutlinks(db, id));
110
+ },
111
+ };
112
+ const graphRelatedCommand = {
113
+ name: 'related',
114
+ description: 'Get notes related within N degrees of separation',
115
+ arguments: [{ syntax: '<id>', description: 'Note id' }],
116
+ options: [{ flags: '--depth <n>', description: 'Degrees of separation (1-5)', defaultValue: '2' }],
117
+ schema: {
118
+ path: 'graph.related',
119
+ risk: 'read',
120
+ auth: 'required',
121
+ globalOptions: GRAPH_GLOBAL_OPTIONS,
122
+ output: {
123
+ type: 'structured',
124
+ summary: 'Related-notes graph payload',
125
+ fields: ['note_id', 'depth', 'related_notes'],
126
+ },
127
+ mcp: {
128
+ toolName: 'dinox_graph_related',
129
+ description: 'Get related notes within N degrees',
130
+ properties: [
131
+ { name: 'id', type: 'string', description: 'Note ID', required: true },
132
+ { name: 'depth', type: 'number', description: 'Degrees (1-5, default: 2)' },
133
+ ],
134
+ },
135
+ examples: ['dino graph related <id> --depth 2 --format json'],
136
+ },
137
+ action: async (context) => {
138
+ const id = context.args.id;
139
+ if (!id) {
140
+ throw new DinoxError('Note id is required');
105
141
  }
106
- await ensureDaemonRunning();
107
- const result = await sendDaemonRequest('graph.related', { id, depth });
108
- printYaml(result);
109
- });
110
- graph.action((_options, command) => {
111
- command.outputHelp();
112
- });
142
+ const depth = parseDepth(context.options.depth);
143
+ await runGraphCommand(context, () => sendDaemonRequest('graph.related', { id, depth }), (db) => getRelatedNotes(db, id, depth));
144
+ },
145
+ };
146
+ const graphStatsCommand = {
147
+ name: 'stats',
148
+ description: 'Get graph statistics for notes and links',
149
+ schema: {
150
+ path: 'graph.stats',
151
+ risk: 'read',
152
+ auth: 'required',
153
+ globalOptions: GRAPH_GLOBAL_OPTIONS,
154
+ output: {
155
+ type: 'structured',
156
+ summary: 'Graph statistics payload',
157
+ entity: 'graph_stats',
158
+ fields: ['notes_count', 'links_count', 'connected_notes_count', 'isolated_notes_count'],
159
+ },
160
+ mcp: {
161
+ toolName: 'dinox_graph_stats',
162
+ description: 'Get graph statistics for notes and links',
163
+ properties: [],
164
+ },
165
+ examples: ['dino graph stats --format json'],
166
+ },
167
+ action: async (context) => {
168
+ await runGraphCommand(context, () => sendDaemonRequest('graph.stats'), (db) => getGraphStats(db));
169
+ },
170
+ };
171
+ export const graphCommandGroup = {
172
+ name: 'graph',
173
+ description: 'Query knowledge graph',
174
+ commands: [
175
+ graphBacklinksCommand,
176
+ graphOutlinksCommand,
177
+ graphRelatedCommand,
178
+ graphStatsCommand,
179
+ ],
180
+ };
181
+ export function registerGraphCommands(program) {
182
+ registerCommandGroup(program, graphCommandGroup);
113
183
  }
@@ -1,2 +1,4 @@
1
1
  import { Command } from 'commander';
2
+ import type { CommandDescriptor } from '../descriptor/types.js';
3
+ export declare const infoCommandDescriptor: CommandDescriptor;
2
4
  export declare function registerInfoCommand(program: Command): void;
@@ -1,23 +1,50 @@
1
+ import { registerStandaloneCommand } from '../descriptor/register.js';
2
+ import { FORMAT_OPTIONS } from '../schema/globalOptions.js';
1
3
  import { SKILLS_REPO_URL } from '../../constants/links.js';
2
- import { printYaml } from '../../utils/output.js';
4
+ import { clearPendingStructuredNoticeProvider, getRequestedStructuredOutputFormat, printHumanReadableLine, printSuccess, setPendingStructuredNoticeProvider, } from '../../utils/output.js';
5
+ import { getUpdateNotice } from '../../utils/updateNotice.js';
3
6
  import { getPackageVersion } from '../../utils/version.js';
4
- export function registerInfoCommand(program) {
5
- program
6
- .command('info')
7
- .description('Show CLI version info')
8
- .action((_options, command) => {
9
- const globals = command.optsWithGlobals?.() ?? {};
10
- const jsonOutput = Boolean(globals.json);
7
+ export const infoCommandDescriptor = {
8
+ name: 'info',
9
+ description: 'Show CLI version and bundled skills location',
10
+ schema: {
11
+ path: 'info',
12
+ risk: 'read',
13
+ auth: 'none',
14
+ globalOptions: FORMAT_OPTIONS,
15
+ output: {
16
+ type: 'structured',
17
+ summary: 'CLI version metadata',
18
+ fields: ['name', 'version', 'skills_repo'],
19
+ },
20
+ examples: ['dino info', 'dino info --format json'],
21
+ },
22
+ action: async (_context) => {
23
+ const version = getPackageVersion();
11
24
  const payload = {
12
25
  name: 'dino',
13
- version: getPackageVersion(),
26
+ version,
14
27
  skills_repo: SKILLS_REPO_URL,
15
28
  };
16
- if (jsonOutput) {
17
- printYaml(payload);
29
+ if (getRequestedStructuredOutputFormat()) {
30
+ const updateNotice = await getUpdateNotice(version);
31
+ setPendingStructuredNoticeProvider(() => updateNotice
32
+ ? {
33
+ update: updateNotice,
34
+ }
35
+ : null);
36
+ try {
37
+ printSuccess(payload);
38
+ }
39
+ finally {
40
+ clearPendingStructuredNoticeProvider();
41
+ }
18
42
  return;
19
43
  }
20
- console.log(payload.version);
21
- console.log(`skills repo: ${payload.skills_repo}`);
22
- });
44
+ printHumanReadableLine(payload.version);
45
+ printHumanReadableLine(`skills repo: ${payload.skills_repo}`);
46
+ },
47
+ };
48
+ export function registerInfoCommand(program) {
49
+ registerStandaloneCommand(program, infoCommandDescriptor);
23
50
  }
@@ -3,6 +3,7 @@ import { mcpTools } from './tools.js';
3
3
  import { isLoopbackHost, resolveMCPAuthToken } from './auth.js';
4
4
  import { DEFAULT_MCP_PORT } from './types.js';
5
5
  import { DinoxError } from '../../utils/errors.js';
6
+ import { printHumanReadableLine } from '../../utils/output.js';
6
7
  function parsePort(value) {
7
8
  if (value === undefined || value === null || value === '') {
8
9
  return DEFAULT_MCP_PORT;
@@ -49,20 +50,20 @@ export function registerMCPCommands(program) {
49
50
  if (!loopback && !token) {
50
51
  throw new DinoxError('--token (or DINOX_MCP_TOKEN) is required when --host is non-loopback');
51
52
  }
52
- console.log('Starting Dinox MCP server...');
53
- console.log(`Host: ${host}`);
54
- console.log(`Port: ${port}`);
55
- console.log(`Auth: ${token ? 'enabled' : 'disabled (loopback only)'}`);
56
- console.log('Protocol: Model Context Protocol (MCP)');
53
+ printHumanReadableLine('Starting Dinox MCP server...');
54
+ printHumanReadableLine(`Host: ${host}`);
55
+ printHumanReadableLine(`Port: ${port}`);
56
+ printHumanReadableLine(`Auth: ${token ? 'enabled' : 'disabled (loopback only)'}`);
57
+ printHumanReadableLine('Protocol: Model Context Protocol (MCP)');
57
58
  if (!loopback) {
58
- console.log('warning: binding to a non-loopback host may expose your local data; use firewall/auth as needed');
59
+ printHumanReadableLine('warning: binding to a non-loopback host may expose your local data; use firewall/auth as needed');
59
60
  }
60
- console.log('');
61
- console.log('Available tools:');
61
+ printHumanReadableLine('');
62
+ printHumanReadableLine('Available tools:');
62
63
  for (const tool of mcpTools) {
63
- console.log(` - ${tool.name}`);
64
+ printHumanReadableLine(` - ${tool.name}`);
64
65
  }
65
- console.log('');
66
+ printHumanReadableLine('');
66
67
  const { server, mcpServer } = await startMCPServer(port, host, token);
67
68
  let shuttingDown = false;
68
69
  const shutdown = async () => {
@@ -6,6 +6,7 @@ import { resolveAuthIdentity } from '../../auth/userInfo.js';
6
6
  import { loadConfig } from '../../config/store.js';
7
7
  import { resolveConfig } from '../../config/resolve.js';
8
8
  import { connectPowerSync } from '../../powersync/runtime.js';
9
+ import { canonicalizeBidlinksInMarkdown } from '../../utils/bidlinkCanonicalization.js';
9
10
  import { parseCreatedAtRange } from '../notes/searchTime.js';
10
11
  import { parseNoteSearchFields, projectNoteSearchFields } from '../notes/searchOutput.js';
11
12
  import { createNote, getNoteDetails, resolveZettelBoxIdsForCreate, resolveZettelBoxIdsForSearch, searchNotes, validateTagsForCreate, } from '../notes/repo.js';
@@ -13,6 +14,7 @@ import { getBacklinks, getGraphStats, getOutlinks, getRelatedNotes } from '../gr
13
14
  import { searchTodoTasks } from '../todo/repo.js';
14
15
  import { convertMarkdownToNoteContent } from '../../utils/tiptapMarkdown.js';
15
16
  import { generateId } from '../../utils/id.js';
17
+ import { printHumanReadableLine } from '../../utils/output.js';
16
18
  import { getPackageVersion } from '../../utils/version.js';
17
19
  const MAX_MCP_REQUEST_BYTES = 1 * 1024 * 1024;
18
20
  const DEFAULT_SEARCH_LIMIT = 50;
@@ -214,7 +216,8 @@ export class MCPServer {
214
216
  case 'dinox_search_notes': {
215
217
  const keyword = typeof args.query === 'string' ? args.query : '';
216
218
  const tagsExpression = typeof args.tags === 'string' ? args.tags.trim() : '';
217
- const { names: boxNames, matchEmpty: matchEmptyBoxes } = parseBoxFilter(args.box);
219
+ const rawBoxes = args.boxes ?? args.box;
220
+ const { names: boxNames, matchEmpty: matchEmptyBoxes } = parseBoxFilter(rawBoxes);
218
221
  const zettelBoxIds = await resolveZettelBoxIdsForSearch(this.db, boxNames);
219
222
  const daysInput = typeof args.days === 'number' ? String(args.days) : args.days;
220
223
  const { createdAtFrom, createdAtTo } = parseCreatedAtRange({
@@ -269,7 +272,8 @@ export class MCPServer {
269
272
  const rawConfig = await loadConfig();
270
273
  const config = resolveConfig(rawConfig);
271
274
  const identity = await resolveAuthIdentity(rawConfig, config);
272
- const { contentJson, contentText } = convertMarkdownToNoteContent(contentMd);
275
+ const canonicalContentMd = await canonicalizeBidlinksInMarkdown(this.db, contentMd, identity.userId);
276
+ const { contentJson, contentText } = convertMarkdownToNoteContent(canonicalContentMd);
273
277
  const tags = await validateTagsForCreate(this.db, normalizeStringArray(args.tags));
274
278
  const zettelBoxIds = await resolveZettelBoxIdsForCreate(this.db, normalizeStringArray(args.boxes));
275
279
  const id = generateId();
@@ -277,7 +281,7 @@ export class MCPServer {
277
281
  id,
278
282
  title,
279
283
  noteType: 'crawl',
280
- contentMd,
284
+ contentMd: canonicalContentMd,
281
285
  contentText,
282
286
  contentJson,
283
287
  tags,
@@ -386,7 +390,7 @@ export async function startMCPServer(port = DEFAULT_MCP_PORT, host = '127.0.0.1'
386
390
  server.headersTimeout = 5_000;
387
391
  server.requestTimeout = 10_000;
388
392
  server.listen(port, host, () => {
389
- console.log(`MCP server listening on http://${host}:${port}`);
393
+ printHumanReadableLine(`MCP server listening on http://${host}:${port}`);
390
394
  });
391
395
  return { server, mcpServer };
392
396
  }
@@ -1,104 +1,30 @@
1
- export const mcpTools = [
2
- {
3
- name: 'dinox_search_notes',
4
- description: 'Search notes by keyword, tags, date range, or SQL expression',
5
- inputSchema: {
6
- type: 'object',
7
- properties: {
8
- query: { type: 'string', description: 'Search keyword' },
9
- tags: { type: 'string', description: 'Tag expression (AND/OR/NOT)' },
10
- from: { type: 'string', description: 'Start date (YYYY-MM-DD)' },
11
- to: { type: 'string', description: 'End date (YYYY-MM-DD)' },
12
- days: { type: 'number', description: 'Recent N days' },
13
- box: { type: 'string', description: 'Zettel box name' },
14
- limit: { type: 'number', description: 'Max results (default: 50)' },
15
- offset: { type: 'number', description: 'Result offset (default: 0)' },
16
- fields: {
17
- type: 'array',
18
- items: { type: 'string' },
19
- description: 'Returned fields: id,title,summary,tags,created_at,zettel_boxes',
20
- },
21
- },
22
- },
23
- },
24
- {
25
- name: 'dinox_get_note',
26
- description: 'Get note details by ID',
27
- inputSchema: {
28
- type: 'object',
29
- properties: {
30
- id: { type: 'string', description: 'Note ID' },
31
- },
32
- required: ['id'],
33
- },
34
- },
35
- {
36
- name: 'dinox_create_note',
37
- description: 'Create a new note',
38
- inputSchema: {
39
- type: 'object',
40
- properties: {
41
- title: { type: 'string', description: 'Note title' },
42
- content: { type: 'string', description: 'Markdown content' },
43
- tags: { type: 'array', items: { type: 'string' }, description: 'Tags' },
44
- boxes: { type: 'array', items: { type: 'string' }, description: 'Zettel boxes' },
45
- },
46
- required: ['title', 'content'],
47
- },
48
- },
49
- {
50
- name: 'dinox_graph_backlinks',
51
- description: 'Get notes that link to this note',
52
- inputSchema: {
53
- type: 'object',
54
- properties: {
55
- id: { type: 'string', description: 'Note ID' },
56
- },
57
- required: ['id'],
58
- },
59
- },
60
- {
61
- name: 'dinox_graph_outlinks',
62
- description: 'Get notes that this note links to',
63
- inputSchema: {
64
- type: 'object',
65
- properties: {
66
- id: { type: 'string', description: 'Note ID' },
67
- },
68
- required: ['id'],
69
- },
70
- },
71
- {
72
- name: 'dinox_graph_related',
73
- description: 'Get related notes within N degrees',
74
- inputSchema: {
75
- type: 'object',
76
- properties: {
77
- id: { type: 'string', description: 'Note ID' },
78
- depth: { type: 'number', description: 'Degrees (1-5, default: 2)' },
79
- },
80
- required: ['id'],
81
- },
82
- },
83
- {
84
- name: 'dinox_graph_stats',
85
- description: 'Get graph statistics for notes and links',
86
- inputSchema: {
87
- type: 'object',
88
- properties: {},
89
- },
90
- },
91
- {
92
- name: 'dinox_search_todos',
93
- description: 'Search todo tasks',
94
- inputSchema: {
95
- type: 'object',
96
- properties: {
97
- query: { type: 'string', description: 'Task text query' },
98
- status: { type: 'string', enum: ['all', 'completed', 'uncompleted'], description: 'Task status' },
99
- tags: { type: 'array', items: { type: 'string' }, description: 'Task tags' },
100
- limit: { type: 'number', description: 'Max results (default: 50)' },
101
- },
102
- },
103
- },
104
- ];
1
+ import { listMcpCommandSchemas } from '../schema/spec.js';
2
+ function toMcpProperty(property) {
3
+ if (property.type === 'string[]') {
4
+ return {
5
+ type: 'array',
6
+ items: { type: 'string' },
7
+ ...(property.description ? { description: property.description } : {}),
8
+ ...(property.enum ? { enum: property.enum } : {}),
9
+ };
10
+ }
11
+ return {
12
+ type: property.type,
13
+ ...(property.description ? { description: property.description } : {}),
14
+ ...(property.enum ? { enum: property.enum } : {}),
15
+ };
16
+ }
17
+ export const mcpTools = listMcpCommandSchemas().map((schema) => {
18
+ const mcp = schema.mcp;
19
+ const properties = Object.fromEntries(mcp.properties.map((property) => [property.name, toMcpProperty(property)]));
20
+ const required = mcp.properties.filter((property) => property.required).map((property) => property.name);
21
+ return {
22
+ name: mcp.toolName,
23
+ description: mcp.description,
24
+ inputSchema: {
25
+ type: 'object',
26
+ properties,
27
+ ...(required.length > 0 ? { required } : {}),
28
+ },
29
+ };
30
+ });
@@ -3,7 +3,14 @@ export type MCPTool = {
3
3
  description: string;
4
4
  inputSchema: {
5
5
  type: 'object';
6
- properties: Record<string, any>;
6
+ properties: Record<string, {
7
+ type: 'string' | 'number' | 'boolean' | 'array';
8
+ description?: string;
9
+ enum?: string[];
10
+ items?: {
11
+ type: 'string';
12
+ };
13
+ }>;
7
14
  required?: string[];
8
15
  };
9
16
  };
@@ -1,2 +1,4 @@
1
1
  import { Command } from 'commander';
2
+ import type { CommandGroupDescriptor } from '../descriptor/types.js';
3
+ export declare const metaCommandGroup: CommandGroupDescriptor;
2
4
  export declare function registerMetaCommands(program: Command): void;