mnotes-cli 1.6.0 → 1.7.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.
@@ -40,6 +40,7 @@ const fs = __importStar(require("fs"));
40
40
  const path = __importStar(require("path"));
41
41
  const config_utils_1 = require("./config-utils");
42
42
  const workspace_prompt_1 = require("./workspace-prompt");
43
+ const config_1 = require("../../config");
43
44
  const claude_code_1 = require("../../templates/claude-code");
44
45
  const codex_1 = require("../../templates/codex");
45
46
  const openclaw_1 = require("../../templates/openclaw");
@@ -91,11 +92,11 @@ function printConnectionStatus() {
91
92
  * prompts interactively after validating the connection.
92
93
  */
93
94
  async function resolveWorkspace(opts) {
94
- // AC-5: --workspace flag works as before
95
- const fromFlag = opts.workspace || process.env.MNOTES_WORKSPACE_ID;
96
- if (fromFlag)
97
- return fromFlag;
98
- // AC-1/AC-2/AC-3/AC-4: Interactive workspace selection/creation
95
+ // Check flag, env, dir map, global config
96
+ const fromConfig = (0, config_1.resolveConfig)({ workspaceId: opts.workspace });
97
+ if (fromConfig.workspaceId)
98
+ return fromConfig.workspaceId;
99
+ // Nothing stored interactive selection/creation
99
100
  const resolved = await (0, workspace_prompt_1.resolveWorkspaceInteractively)(opts.url, opts.apiKey);
100
101
  return resolved.id;
101
102
  }
@@ -110,12 +111,9 @@ function normalizeBaseUrl(raw) {
110
111
  return raw.replace(/\/+$/, "").replace(/\/api\/mcp$/i, "");
111
112
  }
112
113
  async function handleClaudeCode(opts) {
113
- const url = normalizeBaseUrl(opts.url || process.env.MNOTES_URL || "https://mnotes.framework.by");
114
- const apiKey = opts.apiKey || process.env.MNOTES_API_KEY;
115
- if (!apiKey) {
116
- process.stderr.write("Error: API key required. Use --api-key or set MNOTES_API_KEY\n");
117
- process.exit(1);
118
- }
114
+ const config = (0, config_1.resolveConfig)(opts);
115
+ const url = normalizeBaseUrl(config.baseUrl);
116
+ const apiKey = config.apiKey;
119
117
  const validation = await (0, config_utils_1.validateConnection)(url, apiKey);
120
118
  if (!validation.ok) {
121
119
  process.stderr.write(`Error: Cannot connect to ${url}: ${validation.error}\n`);
@@ -179,12 +177,9 @@ function printScaffoldResults(results) {
179
177
  * Handles the `codex` integration target.
180
178
  */
181
179
  async function handleCodex(opts) {
182
- const url = normalizeBaseUrl(opts.url || process.env.MNOTES_URL || "https://mnotes.framework.by");
183
- const apiKey = opts.apiKey || process.env.MNOTES_API_KEY;
184
- if (!apiKey) {
185
- process.stderr.write("Error: API key required. Use --api-key or set MNOTES_API_KEY\n");
186
- process.exit(1);
187
- }
180
+ const config = (0, config_1.resolveConfig)(opts);
181
+ const url = normalizeBaseUrl(config.baseUrl);
182
+ const apiKey = config.apiKey;
188
183
  const validation = await (0, config_utils_1.validateConnection)(url, apiKey);
189
184
  if (!validation.ok) {
190
185
  process.stderr.write(`Error: Cannot connect to ${url}: ${validation.error}\n`);
@@ -216,13 +211,10 @@ async function handleCodex(opts) {
216
211
  * Handles the `openclaw` integration target.
217
212
  */
218
213
  async function handleOpenClaw(opts) {
219
- const url = normalizeBaseUrl(opts.url || process.env.MNOTES_URL || "https://mnotes.framework.by");
220
- const apiKey = opts.apiKey || process.env.MNOTES_API_KEY;
214
+ const config = (0, config_1.resolveConfig)(opts);
215
+ const url = normalizeBaseUrl(config.baseUrl);
216
+ const apiKey = config.apiKey;
221
217
  const configPath = opts.configPath || path.join(process.env.HOME || "~", ".openclaw", "mcp.json");
222
- if (!apiKey) {
223
- process.stderr.write("Error: API key required. Use --api-key or set MNOTES_API_KEY\n");
224
- process.exit(1);
225
- }
226
218
  const validation = await (0, config_utils_1.validateConnection)(url, apiKey);
227
219
  if (!validation.ok) {
228
220
  process.stderr.write(`Error: Cannot connect to ${url}: ${validation.error}\n`);
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnotes-cli",
3
- "version": "1.6.0",
3
+ "version": "1.7.1",
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"