mnotes-cli 1.5.1 → 1.7.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.
@@ -4,6 +4,8 @@ export interface MnotesConfig {
4
4
  apiKey: string;
5
5
  serverUrl: string;
6
6
  workspaceId?: string;
7
+ /** Map of absolute directory path → workspace ID */
8
+ workspaces?: Record<string, string>;
7
9
  }
8
10
  export declare function configPath(): string;
9
11
  /**
@@ -62,10 +62,15 @@ function readConfig() {
62
62
  const raw = fs.readFileSync(configPath(), "utf-8");
63
63
  const parsed = JSON.parse(raw);
64
64
  if (typeof parsed.apiKey === "string" && typeof parsed.serverUrl === "string") {
65
+ let workspaces;
66
+ if (typeof parsed.workspaces === "object" && parsed.workspaces !== null && !Array.isArray(parsed.workspaces)) {
67
+ workspaces = parsed.workspaces;
68
+ }
65
69
  return {
66
70
  apiKey: parsed.apiKey,
67
71
  serverUrl: parsed.serverUrl,
68
72
  workspaceId: typeof parsed.workspaceId === "string" ? parsed.workspaceId : undefined,
73
+ workspaces,
69
74
  };
70
75
  }
71
76
  return null;
@@ -45,6 +45,26 @@ function ask(rl, question) {
45
45
  });
46
46
  });
47
47
  }
48
+ /** Save a directory → workspace mapping in config. */
49
+ function saveDirectoryMapping(dir, workspaceId) {
50
+ const stored = (0, login_1.readConfig)();
51
+ if (!stored) {
52
+ process.stderr.write("Error: not logged in. Run `mnotes login` first.\n");
53
+ process.exit(1);
54
+ }
55
+ const workspaces = stored.workspaces ?? {};
56
+ workspaces[dir] = workspaceId;
57
+ (0, login_1.writeConfig)({ ...stored, workspaces });
58
+ }
59
+ /** Remove a directory → workspace mapping from config. */
60
+ function removeDirectoryMapping(dir) {
61
+ const stored = (0, login_1.readConfig)();
62
+ if (!stored?.workspaces?.[dir])
63
+ return false;
64
+ delete stored.workspaces[dir];
65
+ (0, login_1.writeConfig)(stored);
66
+ return true;
67
+ }
48
68
  function registerWorkspaceCommand(program) {
49
69
  const ws = program
50
70
  .command("workspace")
@@ -62,20 +82,25 @@ function registerWorkspaceCommand(program) {
62
82
  return;
63
83
  }
64
84
  const stored = (0, login_1.readConfig)();
65
- const selectedId = stored?.workspaceId;
85
+ const cwd = process.cwd();
86
+ const dirMapped = stored?.workspaces?.[cwd];
87
+ const globalDefault = stored?.workspaceId;
66
88
  for (const w of workspaces) {
67
89
  const markers = [];
68
90
  if (w.isDefault)
69
91
  markers.push("default");
70
- if (w.id === selectedId)
71
- markers.push("selected");
92
+ if (w.id === dirMapped)
93
+ markers.push("linked");
94
+ else if (w.id === globalDefault)
95
+ markers.push("global");
72
96
  const suffix = markers.length > 0 ? ` (${markers.join(", ")})` : "";
73
97
  console.log(` ${w.name} [${w.slug}]${suffix}`);
74
98
  }
75
99
  });
76
100
  ws.command("select")
77
- .description("Select default workspace (saved to config)")
78
- .action(async () => {
101
+ .description("Select workspace for current directory (saved to config)")
102
+ .option("--global", "Set as global default instead of per-directory")
103
+ .action(async (opts) => {
79
104
  const globalOpts = program.opts();
80
105
  const config = (0, config_1.resolveConfig)(globalOpts);
81
106
  const client = (0, client_1.createClient)(config.baseUrl, config.apiKey);
@@ -86,7 +111,8 @@ function registerWorkspaceCommand(program) {
86
111
  return;
87
112
  }
88
113
  const stored = (0, login_1.readConfig)();
89
- const selectedId = stored?.workspaceId;
114
+ const cwd = process.cwd();
115
+ const currentId = stored?.workspaces?.[cwd] || stored?.workspaceId;
90
116
  const rl = readline.createInterface({
91
117
  input: process.stdin,
92
118
  output: process.stderr,
@@ -95,7 +121,7 @@ function registerWorkspaceCommand(program) {
95
121
  process.stderr.write("\nWorkspaces:\n");
96
122
  for (let i = 0; i < workspaces.length; i++) {
97
123
  const w = workspaces[i];
98
- const marker = w.id === selectedId ? " *" : "";
124
+ const marker = w.id === currentId ? " *" : "";
99
125
  process.stderr.write(` ${i + 1}. ${w.name} [${w.slug}]${marker}\n`);
100
126
  }
101
127
  const answer = await ask(rl, `\nSelect workspace [1-${workspaces.length}]: `);
@@ -105,52 +131,138 @@ function registerWorkspaceCommand(program) {
105
131
  process.exit(1);
106
132
  }
107
133
  const selected = workspaces[choice - 1];
108
- const existing = stored ?? { apiKey: config.apiKey, serverUrl: config.baseUrl };
109
- (0, login_1.writeConfig)({ ...existing, workspaceId: selected.id });
110
- console.log(`Selected workspace: ${selected.name} [${selected.slug}]`);
134
+ if (opts.global) {
135
+ const existing = stored ?? { apiKey: config.apiKey, serverUrl: config.baseUrl };
136
+ (0, login_1.writeConfig)({ ...existing, workspaceId: selected.id });
137
+ console.log(`Global default workspace: ${selected.name} [${selected.slug}]`);
138
+ }
139
+ else {
140
+ saveDirectoryMapping(cwd, selected.id);
141
+ console.log(`Linked ${cwd} → ${selected.name} [${selected.slug}]`);
142
+ }
111
143
  }
112
144
  finally {
113
145
  rl.close();
114
146
  }
115
147
  });
116
- ws.command("create <name>")
148
+ ws.command("link")
149
+ .description("Link current directory to a workspace")
150
+ .argument("[workspace-id]", "Workspace ID or slug (interactive if omitted)")
151
+ .action(async (workspaceIdOrSlug) => {
152
+ const globalOpts = program.opts();
153
+ const config = (0, config_1.resolveConfig)(globalOpts);
154
+ const client = (0, client_1.createClient)(config.baseUrl, config.apiKey);
155
+ const res = await client.listWorkspaces();
156
+ const workspaces = res.data;
157
+ if (workspaces.length === 0) {
158
+ console.log("No workspaces found. Create one with: mnotes workspace create <name>");
159
+ return;
160
+ }
161
+ const cwd = process.cwd();
162
+ let selected;
163
+ if (workspaceIdOrSlug) {
164
+ selected = workspaces.find((w) => w.id === workspaceIdOrSlug || w.slug === workspaceIdOrSlug);
165
+ if (!selected) {
166
+ process.stderr.write(`Error: workspace "${workspaceIdOrSlug}" not found.\n`);
167
+ process.exit(1);
168
+ }
169
+ }
170
+ else {
171
+ const rl = readline.createInterface({
172
+ input: process.stdin,
173
+ output: process.stderr,
174
+ });
175
+ try {
176
+ process.stderr.write("\nWorkspaces:\n");
177
+ for (let i = 0; i < workspaces.length; i++) {
178
+ const w = workspaces[i];
179
+ process.stderr.write(` ${i + 1}. ${w.name} [${w.slug}]\n`);
180
+ }
181
+ const answer = await ask(rl, `\nLink to workspace [1-${workspaces.length}]: `);
182
+ const choice = parseInt(answer, 10);
183
+ if (choice < 1 || choice > workspaces.length || isNaN(choice)) {
184
+ process.stderr.write("Invalid selection.\n");
185
+ process.exit(1);
186
+ }
187
+ selected = workspaces[choice - 1];
188
+ }
189
+ finally {
190
+ rl.close();
191
+ }
192
+ }
193
+ saveDirectoryMapping(cwd, selected.id);
194
+ console.log(`Linked ${cwd} → ${selected.name} [${selected.slug}]`);
195
+ });
196
+ ws.command("unlink")
197
+ .description("Remove workspace mapping for current directory")
198
+ .action(async () => {
199
+ const cwd = process.cwd();
200
+ const removed = removeDirectoryMapping(cwd);
201
+ if (removed) {
202
+ console.log(`Unlinked ${cwd}`);
203
+ }
204
+ else {
205
+ console.log(`No workspace linked to ${cwd}`);
206
+ }
207
+ });
208
+ ws.command("create")
117
209
  .description("Create a new workspace")
210
+ .argument("<name>", "Workspace name")
118
211
  .action(async (name) => {
119
212
  const globalOpts = program.opts();
120
213
  const config = (0, config_1.resolveConfig)(globalOpts);
121
214
  const client = (0, client_1.createClient)(config.baseUrl, config.apiKey);
122
215
  const res = await client.createWorkspace(name);
123
216
  console.log(`Created workspace: ${res.data.name} [${res.data.slug}]`);
124
- const stored = (0, login_1.readConfig)();
125
- if (!stored?.workspaceId) {
126
- const existing = stored ?? { apiKey: config.apiKey, serverUrl: config.baseUrl };
127
- (0, login_1.writeConfig)({ ...existing, workspaceId: res.data.id });
128
- console.log("Auto-selected as default workspace.");
129
- }
217
+ // Auto-link to current directory
218
+ const cwd = process.cwd();
219
+ saveDirectoryMapping(cwd, res.data.id);
220
+ console.log(`Linked ${cwd} ${res.data.name}`);
130
221
  });
131
222
  ws.command("current")
132
- .description("Show currently selected workspace")
223
+ .description("Show workspace for current directory")
133
224
  .action(async () => {
134
225
  const stored = (0, login_1.readConfig)();
135
- if (stored?.workspaceId) {
136
- const config = (0, config_1.resolveConfig)(program.opts());
137
- const client = (0, client_1.createClient)(config.baseUrl, config.apiKey);
138
- try {
139
- const res = await client.listWorkspaces();
140
- const current = res.data.find((w) => w.id === stored.workspaceId);
141
- if (current) {
142
- console.log(`Current workspace: ${current.name} [${current.slug}]`);
143
- }
144
- else {
145
- console.log(`Current workspace ID: ${stored.workspaceId} (not found on server)`);
146
- }
226
+ const cwd = process.cwd();
227
+ // Check directory mapping first
228
+ const dirMapped = stored?.workspaces?.[cwd];
229
+ const effectiveId = dirMapped || stored?.workspaceId;
230
+ const source = dirMapped ? "linked" : stored?.workspaceId ? "global" : null;
231
+ if (!effectiveId) {
232
+ console.log(`No workspace for ${cwd}`);
233
+ console.log("Run: mnotes workspace select");
234
+ return;
235
+ }
236
+ const config = (0, config_1.resolveConfig)(program.opts());
237
+ const client = (0, client_1.createClient)(config.baseUrl, config.apiKey);
238
+ try {
239
+ const res = await client.listWorkspaces();
240
+ const current = res.data.find((w) => w.id === effectiveId);
241
+ if (current) {
242
+ console.log(`${current.name} [${current.slug}] (${source})`);
147
243
  }
148
- catch {
149
- console.log(`Current workspace ID: ${stored.workspaceId}`);
244
+ else {
245
+ console.log(`${effectiveId} (${source}, not found on server)`);
150
246
  }
151
247
  }
152
- else {
153
- console.log("No workspace selected. Run: mnotes workspace select");
248
+ catch {
249
+ console.log(`${effectiveId} (${source})`);
250
+ }
251
+ });
252
+ ws.command("mappings")
253
+ .description("Show all directory → workspace mappings")
254
+ .action(async () => {
255
+ const stored = (0, login_1.readConfig)();
256
+ const workspaces = stored?.workspaces;
257
+ if (!workspaces || Object.keys(workspaces).length === 0) {
258
+ console.log("No directory mappings. Use: mnotes workspace link");
259
+ return;
260
+ }
261
+ for (const [dir, wsId] of Object.entries(workspaces)) {
262
+ console.log(` ${dir} → ${wsId}`);
263
+ }
264
+ if (stored?.workspaceId) {
265
+ console.log(`\n Global default: ${stored.workspaceId}`);
154
266
  }
155
267
  });
156
268
  }
package/dist/config.js CHANGED
@@ -2,6 +2,34 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveConfig = resolveConfig;
4
4
  const login_1 = require("./commands/login");
5
+ /**
6
+ * Resolve workspace ID from (in priority order):
7
+ * 1. Explicit flag/option
8
+ * 2. MNOTES_WORKSPACE_ID env var
9
+ * 3. Per-directory mapping in config (cwd → workspaceId)
10
+ * 4. Global default workspaceId in config
11
+ */
12
+ function resolveWorkspaceId(explicit, stored) {
13
+ if (explicit)
14
+ return explicit;
15
+ if (process.env.MNOTES_WORKSPACE_ID)
16
+ return process.env.MNOTES_WORKSPACE_ID;
17
+ // Check per-directory mapping
18
+ if (stored?.workspaces) {
19
+ const cwd = process.cwd();
20
+ // Exact match first, then walk up parent directories
21
+ let dir = cwd;
22
+ while (true) {
23
+ if (stored.workspaces[dir])
24
+ return stored.workspaces[dir];
25
+ const parent = require("path").dirname(dir);
26
+ if (parent === dir)
27
+ break; // reached root
28
+ dir = parent;
29
+ }
30
+ }
31
+ return stored?.workspaceId;
32
+ }
5
33
  function resolveConfig(opts) {
6
34
  const stored = (0, login_1.readConfig)();
7
35
  const apiKey = opts.apiKey || process.env.MNOTES_API_KEY || stored?.apiKey;
@@ -10,6 +38,6 @@ function resolveConfig(opts) {
10
38
  process.exit(1);
11
39
  }
12
40
  const baseUrl = opts.url || process.env.MNOTES_URL || stored?.serverUrl || "https://mnotes.framework.by";
13
- const workspaceId = opts.workspaceId || process.env.MNOTES_WORKSPACE_ID || stored?.workspaceId;
41
+ const workspaceId = resolveWorkspaceId(opts.workspaceId, stored ?? undefined);
14
42
  return { apiKey, baseUrl, workspaceId };
15
43
  }
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ const program = new commander_1.Command();
15
15
  program
16
16
  .name("mnotes")
17
17
  .description("CLI for m-notes AI knowledge base")
18
- .version(process.env.npm_package_version ?? "1.2.0")
18
+ .version(require("../package.json").version)
19
19
  .option("--api-key <key>", "API key (or set MNOTES_API_KEY)")
20
20
  .option("--url <url>", "Base URL (or set MNOTES_URL)")
21
21
  .option("--json", "Output as JSON");
@@ -38,17 +38,70 @@ Recall knowledge **before acting**. Specifically:
38
38
  - At session start → \`project_context_load\` loads everything relevant
39
39
  - When the user asks about past work → \`session_context_resume\`
40
40
 
41
+ ## Notes — Your Working Documents
42
+
43
+ m-notes is not just a knowledge base — it's a full note-taking system. **Use notes actively** to create and maintain living documents:
44
+
45
+ ### When to Create Notes
46
+ - **Meeting notes** → \`create_note\` after any planning discussion or decision
47
+ - **Investigation logs** → create a note when debugging, append findings as you go with \`append_to_note\`
48
+ - **Design docs** → write architecture or design decisions as full notes, not just knowledge entries
49
+ - **Task summaries** → after completing a story/task, create a note summarizing what was done
50
+ - **Checklists and plans** → create notes with markdown checklists for multi-step work
51
+ - **Daily notes** → use \`daily_note\` to create/get today's note for quick captures
52
+
53
+ ### When to Edit Notes
54
+ - **Append progress** → use \`append_to_note\` to add to existing notes as work progresses
55
+ - **Update docs** → when code changes invalidate existing notes, update them with \`update_note\`
56
+ - **Tag and organize** → use \`manage_tags\` and folder tools to keep notes findable
57
+
58
+ ### Note vs Knowledge Entry
59
+ | Use a **note** when... | Use **knowledge_store** when... |
60
+ |---|---|
61
+ | Content is long-form (paragraphs, lists, docs) | Content is a single fact or decision |
62
+ | Document will be updated over time | Entry is a permanent record |
63
+ | Needs folder organization | Needs key/tag retrieval |
64
+ | Meeting notes, plans, investigations | Architecture decisions, gotchas, patterns |
65
+
66
+ **When in doubt, create a note.** Notes are searchable, linkable, and visible in the UI.
67
+
41
68
  ## MCP Tools Reference
42
69
 
70
+ ### Session & Context
43
71
  | Tool | When to use |
44
72
  |------|------------|
45
73
  | \`project_context_load\` | Session start — loads project context |
46
74
  | \`session_context_resume\` | Resume from previous session |
47
- | \`knowledge_store\` | Store any knowledge entry (key, content, tags) |
75
+ | \`session_log\` | Log session summary at end |
76
+
77
+ ### Knowledge (quick structured entries)
78
+ | Tool | When to use |
79
+ |------|------------|
80
+ | \`knowledge_store\` | Store a knowledge entry (key, content, tags) |
48
81
  | \`recall_knowledge\` | Semantic search across stored knowledge |
49
82
  | \`bulk_knowledge_recall\` | Recall by tag patterns (e.g., all \`arch/*\`) |
50
83
  | \`knowledge_snapshot\` | Export all knowledge at once |
51
- | \`session_log\` | Log session summary at end |
84
+
85
+ ### Notes (full documents)
86
+ | Tool | When to use |
87
+ |------|------------|
88
+ | \`create_note\` | Create a new note (title, content, folderId) |
89
+ | \`update_note\` | Replace note content |
90
+ | \`append_to_note\` | Add content to an existing note |
91
+ | \`get_note\` | Read a note by ID |
92
+ | \`get_note_by_title\` | Find a note by title |
93
+ | \`search_notes\` | Full-text or semantic search |
94
+ | \`list_notes\` | List notes in a folder |
95
+ | \`daily_note\` | Create or get today's daily note |
96
+ | \`manage_tags\` | Add/remove tags on notes |
97
+ | \`pin_note\` / \`toggle_star\` | Pin or star important notes |
98
+
99
+ ### Organization
100
+ | Tool | When to use |
101
+ |------|------------|
102
+ | \`list_folders\` | List folders in workspace |
103
+ | \`create_folder\` | Create a new folder |
104
+ | \`move_note\` | Move note to a different folder |
52
105
  | \`context_fetch\` | Search notes by query |
53
106
 
54
107
  All tools require \`workspaceId: "${opts.workspaceId}"\`.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnotes-cli",
3
- "version": "1.5.1",
3
+ "version": "1.7.0",
4
4
  "description": "CLI for m-notes AI knowledge base — manage notes, search, and CRUD from the terminal",
5
5
  "bin": {
6
6
  "mnotes": "./dist/index.js"