memax-cli 0.1.0-alpha.5 → 0.1.0-alpha.50

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 (185) hide show
  1. package/assets/skills/memax-memory/SKILL.md +173 -0
  2. package/dist/commands/agent-sessions.d.ts +62 -0
  3. package/dist/commands/agent-sessions.d.ts.map +1 -0
  4. package/dist/commands/agent-sessions.js +1447 -0
  5. package/dist/commands/agent-sessions.js.map +1 -0
  6. package/dist/commands/agent-sessions.test.d.ts +2 -0
  7. package/dist/commands/agent-sessions.test.d.ts.map +1 -0
  8. package/dist/commands/agent-sessions.test.js +255 -0
  9. package/dist/commands/agent-sessions.test.js.map +1 -0
  10. package/dist/commands/agents.d.ts +3 -0
  11. package/dist/commands/agents.d.ts.map +1 -0
  12. package/dist/commands/agents.js +36 -0
  13. package/dist/commands/agents.js.map +1 -0
  14. package/dist/commands/ask.d.ts +15 -0
  15. package/dist/commands/ask.d.ts.map +1 -0
  16. package/dist/commands/ask.js +483 -0
  17. package/dist/commands/ask.js.map +1 -0
  18. package/dist/commands/auth.d.ts +7 -0
  19. package/dist/commands/auth.d.ts.map +1 -1
  20. package/dist/commands/auth.js +99 -8
  21. package/dist/commands/auth.js.map +1 -1
  22. package/dist/commands/capture.d.ts +19 -0
  23. package/dist/commands/capture.d.ts.map +1 -0
  24. package/dist/commands/capture.js +69 -0
  25. package/dist/commands/capture.js.map +1 -0
  26. package/dist/commands/config.d.ts +2 -0
  27. package/dist/commands/config.d.ts.map +1 -1
  28. package/dist/commands/config.js +13 -0
  29. package/dist/commands/config.js.map +1 -1
  30. package/dist/commands/delete.d.ts +3 -1
  31. package/dist/commands/delete.d.ts.map +1 -1
  32. package/dist/commands/delete.js +30 -5
  33. package/dist/commands/delete.js.map +1 -1
  34. package/dist/commands/dreams.d.ts +22 -0
  35. package/dist/commands/dreams.d.ts.map +1 -0
  36. package/dist/commands/dreams.js +235 -0
  37. package/dist/commands/dreams.js.map +1 -0
  38. package/dist/commands/dreams.test.d.ts +2 -0
  39. package/dist/commands/dreams.test.d.ts.map +1 -0
  40. package/dist/commands/dreams.test.js +39 -0
  41. package/dist/commands/dreams.test.js.map +1 -0
  42. package/dist/commands/hook.d.ts +2 -0
  43. package/dist/commands/hook.d.ts.map +1 -1
  44. package/dist/commands/hook.js +25 -103
  45. package/dist/commands/hook.js.map +1 -1
  46. package/dist/commands/hub.d.ts +37 -0
  47. package/dist/commands/hub.d.ts.map +1 -0
  48. package/dist/commands/hub.js +347 -0
  49. package/dist/commands/hub.js.map +1 -0
  50. package/dist/commands/hub.test.d.ts +2 -0
  51. package/dist/commands/hub.test.d.ts.map +1 -0
  52. package/dist/commands/hub.test.js +62 -0
  53. package/dist/commands/hub.test.js.map +1 -0
  54. package/dist/commands/list.d.ts +9 -2
  55. package/dist/commands/list.d.ts.map +1 -1
  56. package/dist/commands/list.js +118 -9
  57. package/dist/commands/list.js.map +1 -1
  58. package/dist/commands/list.test.d.ts +2 -0
  59. package/dist/commands/list.test.d.ts.map +1 -0
  60. package/dist/commands/list.test.js +20 -0
  61. package/dist/commands/list.test.js.map +1 -0
  62. package/dist/commands/login.d.ts +7 -1
  63. package/dist/commands/login.d.ts.map +1 -1
  64. package/dist/commands/login.js +85 -22
  65. package/dist/commands/login.js.map +1 -1
  66. package/dist/commands/mcp.d.ts.map +1 -1
  67. package/dist/commands/mcp.js +427 -60
  68. package/dist/commands/mcp.js.map +1 -1
  69. package/dist/commands/push.d.ts +7 -2
  70. package/dist/commands/push.d.ts.map +1 -1
  71. package/dist/commands/push.js +80 -12
  72. package/dist/commands/push.js.map +1 -1
  73. package/dist/commands/recall.d.ts +11 -1
  74. package/dist/commands/recall.d.ts.map +1 -1
  75. package/dist/commands/recall.js +228 -41
  76. package/dist/commands/recall.js.map +1 -1
  77. package/dist/commands/recall.test.d.ts +2 -0
  78. package/dist/commands/recall.test.d.ts.map +1 -0
  79. package/dist/commands/recall.test.js +31 -0
  80. package/dist/commands/recall.test.js.map +1 -0
  81. package/dist/commands/setup-hooks.d.ts +13 -0
  82. package/dist/commands/setup-hooks.d.ts.map +1 -0
  83. package/dist/commands/setup-hooks.js +193 -0
  84. package/dist/commands/setup-hooks.js.map +1 -0
  85. package/dist/commands/setup-instructions.d.ts +21 -0
  86. package/dist/commands/setup-instructions.d.ts.map +1 -0
  87. package/dist/commands/setup-instructions.js +172 -0
  88. package/dist/commands/setup-instructions.js.map +1 -0
  89. package/dist/commands/setup-mcp.d.ts +40 -0
  90. package/dist/commands/setup-mcp.d.ts.map +1 -0
  91. package/dist/commands/setup-mcp.js +414 -0
  92. package/dist/commands/setup-mcp.js.map +1 -0
  93. package/dist/commands/setup-types.d.ts +33 -0
  94. package/dist/commands/setup-types.d.ts.map +1 -0
  95. package/dist/commands/setup-types.js +60 -0
  96. package/dist/commands/setup-types.js.map +1 -0
  97. package/dist/commands/setup.d.ts +13 -1
  98. package/dist/commands/setup.d.ts.map +1 -1
  99. package/dist/commands/setup.js +289 -313
  100. package/dist/commands/setup.js.map +1 -1
  101. package/dist/commands/show.d.ts +5 -1
  102. package/dist/commands/show.d.ts.map +1 -1
  103. package/dist/commands/show.js +36 -14
  104. package/dist/commands/show.js.map +1 -1
  105. package/dist/commands/sync.d.ts +47 -3
  106. package/dist/commands/sync.d.ts.map +1 -1
  107. package/dist/commands/sync.js +1351 -135
  108. package/dist/commands/sync.js.map +1 -1
  109. package/dist/commands/sync.test.d.ts +2 -0
  110. package/dist/commands/sync.test.d.ts.map +1 -0
  111. package/dist/commands/sync.test.js +130 -0
  112. package/dist/commands/sync.test.js.map +1 -0
  113. package/dist/commands/topic.d.ts +32 -0
  114. package/dist/commands/topic.d.ts.map +1 -0
  115. package/dist/commands/topic.js +265 -0
  116. package/dist/commands/topic.js.map +1 -0
  117. package/dist/commands/topic.test.d.ts +2 -0
  118. package/dist/commands/topic.test.d.ts.map +1 -0
  119. package/dist/commands/topic.test.js +114 -0
  120. package/dist/commands/topic.test.js.map +1 -0
  121. package/dist/index.js +35 -125
  122. package/dist/index.js.map +1 -1
  123. package/dist/lib/client.d.ts +10 -0
  124. package/dist/lib/client.d.ts.map +1 -0
  125. package/dist/lib/client.js +104 -0
  126. package/dist/lib/client.js.map +1 -0
  127. package/dist/lib/client.test.d.ts +2 -0
  128. package/dist/lib/client.test.d.ts.map +1 -0
  129. package/dist/lib/client.test.js +44 -0
  130. package/dist/lib/client.test.js.map +1 -0
  131. package/dist/lib/config.d.ts +43 -0
  132. package/dist/lib/config.d.ts.map +1 -1
  133. package/dist/lib/config.js +72 -1
  134. package/dist/lib/config.js.map +1 -1
  135. package/dist/lib/credentials.d.ts +3 -0
  136. package/dist/lib/credentials.d.ts.map +1 -1
  137. package/dist/lib/credentials.js +24 -2
  138. package/dist/lib/credentials.js.map +1 -1
  139. package/dist/lib/hubs.d.ts +7 -0
  140. package/dist/lib/hubs.d.ts.map +1 -0
  141. package/dist/lib/hubs.js +33 -0
  142. package/dist/lib/hubs.js.map +1 -0
  143. package/dist/lib/hubs.test.d.ts +2 -0
  144. package/dist/lib/hubs.test.d.ts.map +1 -0
  145. package/dist/lib/hubs.test.js +58 -0
  146. package/dist/lib/hubs.test.js.map +1 -0
  147. package/dist/lib/project-context.d.ts +56 -0
  148. package/dist/lib/project-context.d.ts.map +1 -0
  149. package/dist/lib/project-context.js +225 -0
  150. package/dist/lib/project-context.js.map +1 -0
  151. package/dist/lib/project-context.test.d.ts +2 -0
  152. package/dist/lib/project-context.test.d.ts.map +1 -0
  153. package/dist/lib/project-context.test.js +75 -0
  154. package/dist/lib/project-context.test.js.map +1 -0
  155. package/dist/lib/prompt.d.ts +7 -0
  156. package/dist/lib/prompt.d.ts.map +1 -0
  157. package/dist/lib/prompt.js +41 -0
  158. package/dist/lib/prompt.js.map +1 -0
  159. package/dist/lib/trash.d.ts +6 -0
  160. package/dist/lib/trash.d.ts.map +1 -0
  161. package/dist/lib/trash.js +28 -0
  162. package/dist/lib/trash.js.map +1 -0
  163. package/package.json +17 -13
  164. package/.vscode/mcp.json +0 -8
  165. package/dist/lib/api.d.ts +0 -4
  166. package/dist/lib/api.d.ts.map +0 -1
  167. package/dist/lib/api.js +0 -95
  168. package/dist/lib/api.js.map +0 -1
  169. package/src/commands/auth.ts +0 -92
  170. package/src/commands/config.ts +0 -27
  171. package/src/commands/delete.ts +0 -20
  172. package/src/commands/hook.ts +0 -243
  173. package/src/commands/list.ts +0 -38
  174. package/src/commands/login.ts +0 -162
  175. package/src/commands/mcp.ts +0 -357
  176. package/src/commands/push.ts +0 -82
  177. package/src/commands/recall.ts +0 -163
  178. package/src/commands/setup.ts +0 -682
  179. package/src/commands/show.ts +0 -35
  180. package/src/commands/sync.ts +0 -403
  181. package/src/index.ts +0 -192
  182. package/src/lib/api.ts +0 -110
  183. package/src/lib/config.ts +0 -61
  184. package/src/lib/credentials.ts +0 -42
  185. package/tsconfig.json +0 -9
@@ -1,11 +1,29 @@
1
1
  import chalk from "chalk";
2
- import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync, } from "node:fs";
3
- import { join, dirname } from "node:path";
4
- import { homedir, platform } from "node:os";
2
+ import { existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir } from "node:os";
5
5
  import { execSync } from "node:child_process";
6
+ import { createInterface } from "node:readline";
7
+ import { commandExists, resolveMemaxBin, } from "./setup-types.js";
8
+ import { ensureApiKey, ensureLocalAgentKey, setupMcpRemote, setupMcpOAuth, setupMcp, printMcpConfigs, removeMcpJson, removeMcpToml, } from "./setup-mcp.js";
9
+ import { setupHooks, removeHooks } from "./setup-hooks.js";
10
+ import { injectInstructions, removeInstructions, installSkills, removeSkills, } from "./setup-instructions.js";
11
+ // --- Agent definitions ---
6
12
  function getAgents() {
7
13
  const home = homedir();
8
- const isWin = platform() === "win32";
14
+ const cwd = process.cwd();
15
+ // Helper: standard { type: "url", url, headers? } shape used by Cursor, Windsurf, OpenClaw
16
+ const stdUrlEntry = (url, headers) => ({
17
+ type: "url",
18
+ url,
19
+ ...(headers ? { headers } : {}),
20
+ });
21
+ // Helper: { type: "http", url, headers? } shape used by VS Code, Copilot CLI
22
+ const httpEntry = (url, headers) => ({
23
+ type: "http",
24
+ url,
25
+ ...(headers ? { headers } : {}),
26
+ });
9
27
  return [
10
28
  {
11
29
  name: "Claude Code",
@@ -14,7 +32,10 @@ function getAgents() {
14
32
  format: "json-mcpServers",
15
33
  mcpKey: "mcpServers",
16
34
  hasHooks: true,
35
+ globalInstructionFile: join(home, ".claude", "CLAUDE.md"),
17
36
  detect: () => existsSync(join(home, ".claude")) || commandExists("claude"),
37
+ // Claude Code uses `claude mcp add` CLI, not JSON config — this is a fallback
38
+ remoteEntry: stdUrlEntry,
18
39
  },
19
40
  {
20
41
  name: "Cursor",
@@ -23,7 +44,9 @@ function getAgents() {
23
44
  format: "json-mcpServers",
24
45
  mcpKey: "mcpServers",
25
46
  hasHooks: false,
47
+ globalInstructionFile: null,
26
48
  detect: () => existsSync(join(home, ".cursor")) || commandExists("cursor"),
49
+ remoteEntry: stdUrlEntry,
27
50
  },
28
51
  {
29
52
  name: "Windsurf",
@@ -32,8 +55,10 @@ function getAgents() {
32
55
  format: "json-mcpServers",
33
56
  mcpKey: "mcpServers",
34
57
  hasHooks: false,
58
+ globalInstructionFile: null,
35
59
  detect: () => existsSync(join(home, ".codeium", "windsurf")) ||
36
60
  commandExists("windsurf"),
61
+ remoteEntry: stdUrlEntry,
37
62
  },
38
63
  {
39
64
  name: "Gemini CLI",
@@ -42,7 +67,13 @@ function getAgents() {
42
67
  format: "json-mcpServers",
43
68
  mcpKey: "mcpServers",
44
69
  hasHooks: true,
70
+ globalInstructionFile: join(home, ".gemini", "GEMINI.md"),
45
71
  detect: () => existsSync(join(home, ".gemini")) || commandExists("gemini"),
72
+ // Gemini CLI uses httpUrl for streamable HTTP; url is its SSE field
73
+ remoteEntry: (url, headers) => ({
74
+ httpUrl: url,
75
+ ...(headers ? { headers } : {}),
76
+ }),
46
77
  },
47
78
  {
48
79
  name: "GitHub Copilot CLI",
@@ -51,16 +82,32 @@ function getAgents() {
51
82
  format: "json-mcpServers",
52
83
  mcpKey: "mcpServers",
53
84
  hasHooks: false,
85
+ globalInstructionFile: null,
54
86
  detect: () => existsSync(join(home, ".copilot")) || commandExists("gh copilot"),
87
+ // Copilot CLI requires type + url + tools for both remote and local servers
88
+ remoteEntry: (url, headers) => ({
89
+ type: "http",
90
+ url,
91
+ tools: [{ type: "function" }],
92
+ ...(headers ? { headers } : {}),
93
+ }),
94
+ localEntry: (command, args) => ({
95
+ command,
96
+ args,
97
+ tools: [{ type: "function" }],
98
+ }),
55
99
  },
56
100
  {
57
101
  name: "Copilot (VS Code)",
58
102
  id: "vscode",
59
- configPath: join(".vscode", "mcp.json"), // project-level
103
+ configPath: join(".vscode", "mcp.json"),
60
104
  format: "json-servers",
61
105
  mcpKey: "servers",
62
106
  hasHooks: false,
107
+ globalInstructionFile: null,
63
108
  detect: () => existsSync(".vscode") || commandExists("code"),
109
+ // VS Code uses type: "http" for remote MCP servers
110
+ remoteEntry: httpEntry,
64
111
  },
65
112
  {
66
113
  name: "Codex CLI",
@@ -69,23 +116,106 @@ function getAgents() {
69
116
  format: "toml",
70
117
  mcpKey: "mcp_servers",
71
118
  hasHooks: false,
119
+ globalInstructionFile: join(home, ".codex", "AGENTS.md"),
72
120
  detect: () => existsSync(join(home, ".codex")) || commandExists("codex"),
121
+ // Codex TOML is handled separately; this is for JSON fallback reference
122
+ remoteEntry: stdUrlEntry,
123
+ },
124
+ {
125
+ name: "OpenClaw",
126
+ id: "openclaw",
127
+ configPath: join(home, ".openclaw", "openclaw.json"),
128
+ format: "json-mcpServers",
129
+ mcpKey: "mcp.servers",
130
+ hasHooks: false,
131
+ globalInstructionFile: null,
132
+ detect: () => existsSync(join(home, ".openclaw")) || commandExists("openclaw"),
133
+ remoteEntry: stdUrlEntry,
134
+ },
135
+ {
136
+ name: "OpenCode",
137
+ id: "opencode",
138
+ configPath: join(cwd, ".opencode", "opencode.jsonc"),
139
+ format: "json-mcpServers",
140
+ mcpKey: "mcp",
141
+ hasHooks: false,
142
+ globalInstructionFile: null,
143
+ detect: () => existsSync(join(cwd, ".opencode")) || commandExists("opencode"),
144
+ // OpenCode uses type: "remote" for remote, type: "local" for local
145
+ remoteEntry: (url, headers) => ({
146
+ type: "remote",
147
+ url,
148
+ ...(headers ? { headers } : {}),
149
+ }),
150
+ localEntry: (command, args) => ({
151
+ type: "local",
152
+ command,
153
+ args,
154
+ }),
73
155
  },
74
156
  ];
75
157
  }
76
158
  export async function setupCommand(options) {
77
159
  const enableMcp = options.all || options.mcp;
78
160
  const enableHooks = options.all || options.hooks;
79
- if (!enableMcp && !enableHooks) {
161
+ const enableInstructions = options.all || options.instructions;
162
+ if (!enableMcp && !enableHooks && !enableInstructions && !options.print) {
80
163
  printUsage();
81
164
  return;
82
165
  }
83
- // Resolve memax binary
84
- const memaxBin = resolveMemaxBin();
85
- if (!memaxBin) {
86
- console.error(chalk.red("\n Could not find memax binary.\n Install globally: npm install -g memax-cli@alpha\n"));
166
+ if (options.hub && options.agentSync) {
167
+ console.error(chalk.red("\n --agent-sync cannot be combined with --hub because hub-scoped MCP keys are restricted to that hub only.\n"));
168
+ process.exit(1);
169
+ }
170
+ // Hub scoping requires API key mode — OAuth grants are configured during
171
+ // the consent screen, not at setup time.
172
+ if (options.hub && !options.apiKey) {
173
+ console.error(chalk.red("\n --hub requires --api-key because hub scoping is configured per API key.\n Use: memax setup --mcp --api-key --hub <id>\n"));
174
+ process.exit(1);
175
+ }
176
+ // Permission flags only apply to remote API key mode — reject in OAuth and local mode
177
+ const permissionFlags = [
178
+ options.readOnly && "--read-only",
179
+ options.allowDelete && "--allow-delete",
180
+ options.allowOrganize && "--allow-organize",
181
+ options.agentSync && "--agent-sync",
182
+ ].filter(Boolean);
183
+ if (permissionFlags.length > 0 && (!options.apiKey || options.local)) {
184
+ console.error(chalk.red(`\n ${permissionFlags.join(", ")} only apply to remote API key mode.\n Use: memax setup --mcp --api-key ${permissionFlags.join(" ")}\n`));
185
+ process.exit(1);
186
+ }
187
+ // --api-key is meaningless with --local
188
+ if (options.apiKey && options.local) {
189
+ console.error(chalk.red("\n --api-key and --local are mutually exclusive.\n Use --api-key for remote API key mode, or --local for local CLI mode.\n"));
87
190
  process.exit(1);
88
191
  }
192
+ // --print: just output config JSON for manual copy/paste
193
+ if (options.print) {
194
+ await printMcpConfigs({
195
+ local: options.local ?? false,
196
+ apiKey: options.apiKey ?? false,
197
+ hub: options.hub,
198
+ readOnly: options.readOnly,
199
+ allowDelete: options.allowDelete,
200
+ allowOrganize: options.allowOrganize,
201
+ agentSync: options.agentSync,
202
+ });
203
+ return;
204
+ }
205
+ // Remote mode (default): OAuth auto-discovery (no API key in config)
206
+ // --api-key: use per-agent API keys (legacy mode, for CI/CD or agents without OAuth)
207
+ // --local: use local CLI binary (memax mcp serve)
208
+ const useRemote = !options.local;
209
+ const useApiKey = options.apiKey ?? false;
210
+ // Local mode: need memax binary
211
+ let memaxBin = null;
212
+ if (!useRemote || enableHooks) {
213
+ memaxBin = resolveMemaxBin();
214
+ if (!memaxBin && !useRemote) {
215
+ console.error(chalk.red("\n Could not find memax binary.\n Install globally: npm install -g memax-cli@alpha\n"));
216
+ process.exit(1);
217
+ }
218
+ }
89
219
  // Filter agents
90
220
  const allAgents = getAgents();
91
221
  const onlySet = options.only
@@ -111,21 +241,57 @@ export async function setupCommand(options) {
111
241
  return;
112
242
  }
113
243
  console.log(chalk.bold("\n Memax Setup\n"));
244
+ if (useRemote && useApiKey) {
245
+ console.log(chalk.gray(" Mode: remote server (API key)\n"));
246
+ }
247
+ else if (useRemote) {
248
+ console.log(chalk.gray(" Mode: remote server (OAuth)\n"));
249
+ }
250
+ else {
251
+ console.log(chalk.gray(" Mode: local CLI\n"));
252
+ }
114
253
  const results = [];
115
254
  for (const agent of agents) {
116
255
  const changes = [];
117
- // MCP setup
256
+ // MCP setup — OAuth (default), API key (--api-key), or local (--local)
118
257
  if (enableMcp) {
119
258
  try {
120
- setupMcp(agent, memaxBin);
121
- changes.push("MCP server");
259
+ if (useRemote && useApiKey) {
260
+ // Legacy API key mode — creates per-agent key with agent_name
261
+ const agentKey = await ensureApiKey(options.hub, agent.id, {
262
+ readOnly: options.readOnly,
263
+ allowDelete: options.allowDelete,
264
+ allowOrganize: options.allowOrganize,
265
+ agentSync: options.agentSync,
266
+ });
267
+ if (!agentKey) {
268
+ console.error(chalk.red(` ✗ ${agent.name}: Could not create API key. Run: memax login`));
269
+ continue;
270
+ }
271
+ setupMcpRemote(agent, agentKey);
272
+ changes.push("MCP server (API key)");
273
+ }
274
+ else if (useRemote) {
275
+ // OAuth mode (default) — no API key in config, agent auto-discovers auth
276
+ setupMcpOAuth(agent);
277
+ changes.push("MCP server (OAuth)");
278
+ }
279
+ else {
280
+ const localAgentKey = await ensureLocalAgentKey(agent.id);
281
+ if (!localAgentKey) {
282
+ console.error(chalk.red(` ✗ ${agent.name}: Could not provision local agent key. Run: memax login`));
283
+ continue;
284
+ }
285
+ setupMcp(agent, memaxBin);
286
+ changes.push("MCP server (local agent auth)");
287
+ }
122
288
  }
123
289
  catch (err) {
124
290
  console.log(chalk.red(` ✗ ${agent.name}: MCP setup failed — ${err.message}`));
125
291
  }
126
292
  }
127
- // Hook setup (only for agents that support it)
128
- if (enableHooks && agent.hasHooks) {
293
+ // Hook setup (only for agents that support it — needs local binary)
294
+ if (enableHooks && agent.hasHooks && memaxBin) {
129
295
  try {
130
296
  setupHooks(agent, memaxBin);
131
297
  changes.push("Context injection hook");
@@ -134,6 +300,26 @@ export async function setupCommand(options) {
134
300
  console.log(chalk.red(` ✗ ${agent.name}: Hook setup failed — ${err.message}`));
135
301
  }
136
302
  }
303
+ // Inject memax instructions into agent's global instruction file
304
+ if (enableInstructions && agent.globalInstructionFile) {
305
+ try {
306
+ injectInstructions(agent.globalInstructionFile);
307
+ changes.push("Instructions injected");
308
+ }
309
+ catch (err) {
310
+ console.log(chalk.red(` ✗ ${agent.name}: Instruction injection failed — ${err.message}`));
311
+ }
312
+ // Install memax skills for agents that support skill directories
313
+ try {
314
+ const skillCount = await installSkills(agent);
315
+ if (skillCount > 0) {
316
+ changes.push(`${skillCount} skill${skillCount > 1 ? "s" : ""} installed`);
317
+ }
318
+ }
319
+ catch (err) {
320
+ console.log(chalk.red(` ✗ ${agent.name}: Skill install failed — ${err.message}`));
321
+ }
322
+ }
137
323
  if (changes.length > 0) {
138
324
  results.push({ agent: agent.name, changes });
139
325
  }
@@ -153,8 +339,8 @@ export async function setupCommand(options) {
153
339
  console.log(chalk.gray("\n MCP tools available to all configured agents:"));
154
340
  console.log(chalk.gray(" • memax_recall — semantic search your knowledge"));
155
341
  console.log(chalk.gray(" • memax_push — save knowledge from sessions"));
156
- console.log(chalk.gray(" • memax_get — read full note by ID"));
157
- console.log(chalk.gray(" • memax_search — browse notes by category"));
342
+ console.log(chalk.gray(" • memax_get — read full memory by ID"));
343
+ console.log(chalk.gray(" • memax_list — browse memories"));
158
344
  if (enableHooks) {
159
345
  const hookAgents = results.filter((r) => r.changes.includes("Context injection hook"));
160
346
  if (hookAgents.length > 0) {
@@ -162,8 +348,32 @@ export async function setupCommand(options) {
162
348
  console.log(chalk.gray(" Every prompt gets relevant context injected automatically."));
163
349
  }
164
350
  }
165
- console.log(chalk.gray("\n Restart your agents for changes to take effect.\n"));
351
+ console.log(chalk.gray("\n Restart your agents for changes to take effect."));
352
+ // Offer to restore configs from cloud if this looks like a new device
353
+ if (enableInstructions || options.all) {
354
+ try {
355
+ const { syncAgentMemoryCommand } = await import("./sync.js");
356
+ const rl = createInterface({
357
+ input: process.stdin,
358
+ output: process.stdout,
359
+ });
360
+ const restore = await new Promise((resolve) => {
361
+ rl.question(chalk.gray("\n Restore agent configs from Memax cloud? [Y/n] "), (answer) => {
362
+ rl.close();
363
+ resolve(answer.trim().toLowerCase() !== "n");
364
+ });
365
+ });
366
+ if (restore) {
367
+ await syncAgentMemoryCommand({ skipConflicts: true });
368
+ }
369
+ }
370
+ catch {
371
+ // Sync not available (not logged in, etc.) — skip silently
372
+ }
373
+ }
374
+ console.log();
166
375
  }
376
+ // --- Teardown command ---
167
377
  export async function teardownCommand(options) {
168
378
  const allAgents = getAgents();
169
379
  const onlySet = options.only
@@ -178,19 +388,30 @@ export async function teardownCommand(options) {
178
388
  // Claude Code uses its own CLI
179
389
  if (agent.id === "claude-code") {
180
390
  if (commandExists("claude")) {
181
- try {
182
- execSync("claude mcp remove memax", { stdio: "pipe" });
183
- console.log(chalk.gray(` Removed MCP from ${agent.name}`));
184
- removed = true;
185
- }
186
- catch {
187
- // Not installed
391
+ // Remove from both user and project scope to clean up old
392
+ // (pre-scope) and new (user-scoped) installs
393
+ for (const scope of ["--scope user", ""]) {
394
+ try {
395
+ execSync(`claude mcp remove memax ${scope}`.trim(), {
396
+ stdio: "pipe",
397
+ });
398
+ console.log(chalk.gray(` Removed MCP from ${agent.name}${scope ? " (user scope)" : " (project scope)"}`));
399
+ removed = true;
400
+ }
401
+ catch {
402
+ // Not installed in this scope
403
+ }
188
404
  }
189
405
  }
190
406
  if (agent.hasHooks && existsSync(agent.configPath)) {
191
407
  if (removeHooks(agent))
192
408
  removed = true;
193
409
  }
410
+ if (agent.globalInstructionFile &&
411
+ removeInstructions(agent.globalInstructionFile)) {
412
+ console.log(chalk.gray(` Removed instructions from ${agent.name}`));
413
+ removed = true;
414
+ }
194
415
  continue;
195
416
  }
196
417
  if (!existsSync(agent.configPath))
@@ -205,6 +426,15 @@ export async function teardownCommand(options) {
205
426
  }
206
427
  if (agent.hasHooks && removeHooks(agent))
207
428
  removed = true;
429
+ if (agent.globalInstructionFile &&
430
+ removeInstructions(agent.globalInstructionFile)) {
431
+ console.log(chalk.gray(` Removed instructions from ${agent.name}`));
432
+ removed = true;
433
+ }
434
+ if (removeSkills(agent)) {
435
+ console.log(chalk.gray(` Removed skills from ${agent.name}`));
436
+ removed = true;
437
+ }
208
438
  }
209
439
  catch {
210
440
  // Skip agents we can't clean up
@@ -216,270 +446,7 @@ export async function teardownCommand(options) {
216
446
  }
217
447
  console.log(chalk.green("\n Memax integrations removed.\n Restart your agents for changes to take effect.\n"));
218
448
  }
219
- // --- MCP setup per agent ---
220
- function setupMcp(agent, bin) {
221
- // Claude Code has its own CLI for MCP management
222
- if (agent.id === "claude-code") {
223
- setupMcpClaudeCode(bin);
224
- return;
225
- }
226
- mkdirSync(dirname(agent.configPath), { recursive: true });
227
- if (agent.format === "toml") {
228
- setupMcpToml(agent, bin);
229
- return;
230
- }
231
- // JSON-based agents
232
- let config = {};
233
- if (existsSync(agent.configPath)) {
234
- try {
235
- config = JSON.parse(readFileSync(agent.configPath, "utf-8"));
236
- }
237
- catch {
238
- // Start fresh
239
- }
240
- }
241
- const servers = (config[agent.mcpKey] ?? {});
242
- servers.memax = {
243
- command: bin.command,
244
- args: [...bin.args, "mcp", "serve"],
245
- };
246
- config[agent.mcpKey] = servers;
247
- writeFileSync(agent.configPath, JSON.stringify(config, null, 2) + "\n");
248
- }
249
- function setupMcpClaudeCode(bin) {
250
- // Claude Code uses its own CLI for MCP — settings.json mcpServers is ignored
251
- if (!commandExists("claude")) {
252
- throw new Error("claude CLI not found in PATH");
253
- }
254
- // Remove existing first (idempotent)
255
- try {
256
- execSync("claude mcp remove memax", { stdio: "pipe" });
257
- }
258
- catch {
259
- // Not installed yet — fine
260
- }
261
- // claude mcp add <name> -- <command> [args...]
262
- const allArgs = [...bin.args, "mcp", "serve"];
263
- const cmd = `claude mcp add memax -- ${bin.command} ${allArgs.join(" ")}`;
264
- try {
265
- execSync(cmd, { stdio: "pipe" });
266
- }
267
- catch (err) {
268
- throw new Error(`claude mcp add failed: ${err.message}`);
269
- }
270
- }
271
- function setupMcpToml(agent, bin) {
272
- // Codex uses TOML — append or update the memax section
273
- let content = "";
274
- if (existsSync(agent.configPath)) {
275
- content = readFileSync(agent.configPath, "utf-8");
276
- }
277
- // Remove existing memax section if present
278
- content = content.replace(/\[mcp_servers\.memax\][\s\S]*?(?=\n\[|$)/, "");
279
- const args = [...bin.args, "mcp", "serve"].map((a) => `"${a}"`).join(", ");
280
- content = content.trim();
281
- if (content)
282
- content += "\n\n";
283
- content += `[mcp_servers.memax]\ncommand = "${bin.command}"\nargs = [${args}]\n`;
284
- writeFileSync(agent.configPath, content);
285
- }
286
- // --- Hook setup ---
287
- function setupHooks(agent, bin) {
288
- if (agent.id === "claude-code") {
289
- setupClaudeCodeHooks(agent, bin);
290
- }
291
- else if (agent.id === "gemini") {
292
- setupGeminiHooks(agent, bin);
293
- }
294
- }
295
- function setupClaudeCodeHooks(agent, bin) {
296
- const hookScript = writeHookScript(bin);
297
- let config = {};
298
- if (existsSync(agent.configPath)) {
299
- try {
300
- config = JSON.parse(readFileSync(agent.configPath, "utf-8"));
301
- }
302
- catch {
303
- // Start fresh
304
- }
305
- }
306
- const hooks = (config.hooks ?? {});
307
- // Remove existing memax hooks
308
- if (hooks["UserPromptSubmit"]) {
309
- hooks["UserPromptSubmit"] = hooks["UserPromptSubmit"].filter((h) => !h.hooks?.some((hh) => hh.command?.includes("memax")));
310
- }
311
- hooks["UserPromptSubmit"] = [
312
- ...(hooks["UserPromptSubmit"] ?? []),
313
- {
314
- matcher: "",
315
- hooks: [{ type: "command", command: hookScript, timeout: 30 }],
316
- },
317
- ];
318
- config.hooks = hooks;
319
- writeFileSync(agent.configPath, JSON.stringify(config, null, 2) + "\n");
320
- }
321
- function setupGeminiHooks(agent, bin) {
322
- const hookScript = writeHookScript(bin);
323
- let config = {};
324
- if (existsSync(agent.configPath)) {
325
- try {
326
- config = JSON.parse(readFileSync(agent.configPath, "utf-8"));
327
- }
328
- catch {
329
- // Start fresh
330
- }
331
- }
332
- const hooks = (config.hooks ?? {});
333
- // Remove existing memax hooks from both old ("Startup") and correct event
334
- for (const event of ["Startup", "BeforeAgent"]) {
335
- if (hooks[event]) {
336
- hooks[event] = hooks[event].filter((h) => !h.hooks?.some((hh) => hh.command?.includes("memax")));
337
- if (hooks[event].length === 0)
338
- delete hooks[event];
339
- }
340
- }
341
- // BeforeAgent fires after user submits a prompt — equivalent to Claude Code's PrePromptSubmit
342
- hooks["BeforeAgent"] = [
343
- ...(hooks["BeforeAgent"] ?? []),
344
- {
345
- matcher: "",
346
- hooks: [{ type: "command", command: hookScript, timeout: 30 }],
347
- },
348
- ];
349
- config.hooks = hooks;
350
- writeFileSync(agent.configPath, JSON.stringify(config, null, 2) + "\n");
351
- }
352
- // --- Teardown helpers ---
353
- function removeMcpJson(agent) {
354
- if (!existsSync(agent.configPath))
355
- return false;
356
- try {
357
- const config = JSON.parse(readFileSync(agent.configPath, "utf-8"));
358
- const servers = config[agent.mcpKey];
359
- if (!servers?.memax)
360
- return false;
361
- delete servers.memax;
362
- if (Object.keys(servers).length === 0)
363
- delete config[agent.mcpKey];
364
- writeFileSync(agent.configPath, JSON.stringify(config, null, 2) + "\n");
365
- console.log(chalk.gray(` Removed MCP from ${agent.name}`));
366
- return true;
367
- }
368
- catch {
369
- return false;
370
- }
371
- }
372
- function removeMcpToml(agent) {
373
- if (!existsSync(agent.configPath))
374
- return false;
375
- let content = readFileSync(agent.configPath, "utf-8");
376
- const before = content;
377
- content = content.replace(/\[mcp_servers\.memax\][\s\S]*?(?=\n\[|$)/, "");
378
- if (content === before)
379
- return false;
380
- writeFileSync(agent.configPath, content.trim() + "\n");
381
- console.log(chalk.gray(` Removed MCP from ${agent.name}`));
382
- return true;
383
- }
384
- function removeHooks(agent) {
385
- if (!existsSync(agent.configPath))
386
- return false;
387
- try {
388
- const config = JSON.parse(readFileSync(agent.configPath, "utf-8"));
389
- const hooks = config.hooks;
390
- if (!hooks)
391
- return false;
392
- let removed = false;
393
- for (const event of Object.keys(hooks)) {
394
- const before = hooks[event].length;
395
- hooks[event] = hooks[event].filter((h) => !h.command?.includes("memax") &&
396
- !h.hooks?.some((hh) => hh.command?.includes("memax")));
397
- if (hooks[event].length < before)
398
- removed = true;
399
- if (hooks[event].length === 0)
400
- delete hooks[event];
401
- }
402
- if (Object.keys(hooks).length === 0)
403
- delete config.hooks;
404
- if (removed) {
405
- writeFileSync(agent.configPath, JSON.stringify(config, null, 2) + "\n");
406
- console.log(chalk.gray(` Removed hooks from ${agent.name}`));
407
- }
408
- return removed;
409
- }
410
- catch {
411
- return false;
412
- }
413
- }
414
- function resolveMemaxBin() {
415
- // 1. Global install — use absolute path so agents find it without shell PATH
416
- if (commandExists("memax")) {
417
- try {
418
- const which = platform() === "win32" ? "where memax" : "which memax";
419
- const absPath = execSync(which, { encoding: "utf-8", stdio: "pipe" })
420
- .trim()
421
- .split("\n")[0];
422
- if (absPath) {
423
- return { command: absPath, args: [], shell: absPath };
424
- }
425
- }
426
- catch {
427
- // fall through
428
- }
429
- return { command: "memax", args: [], shell: "memax" };
430
- }
431
- // 2. Local repo build (faster than npx, always up-to-date during dev)
432
- const localBuild = join(process.cwd(), "packages", "cli", "dist", "index.js");
433
- if (existsSync(localBuild)) {
434
- return {
435
- command: "node",
436
- args: [localBuild],
437
- shell: `node ${localBuild}`,
438
- };
439
- }
440
- // 3. npx as last resort (slow startup — agents may timeout on first run)
441
- try {
442
- execSync("npx --yes memax-cli --version", {
443
- encoding: "utf-8",
444
- timeout: 15000,
445
- stdio: "pipe",
446
- });
447
- return {
448
- command: "npx",
449
- args: ["-y", "memax-cli"],
450
- shell: "npx -y memax-cli",
451
- };
452
- }
453
- catch {
454
- // npx failed
455
- }
456
- return null;
457
- }
458
- function commandExists(cmd) {
459
- try {
460
- const which = platform() === "win32" ? "where" : "which";
461
- execSync(`${which} ${cmd}`, { stdio: "pipe" });
462
- return true;
463
- }
464
- catch {
465
- return false;
466
- }
467
- }
468
- function writeHookScript(bin) {
469
- const hooksDir = join(homedir(), ".memax", "hooks");
470
- mkdirSync(hooksDir, { recursive: true });
471
- const isWindows = platform() === "win32";
472
- const scriptName = isWindows ? "context-inject.cmd" : "context-inject.sh";
473
- const scriptPath = join(hooksDir, scriptName);
474
- if (isWindows) {
475
- writeFileSync(scriptPath, WIN_HOOK.replace(/\$MEMAX/g, bin.shell));
476
- }
477
- else {
478
- writeFileSync(scriptPath, UNIX_HOOK.replace(/\$MEMAX/g, bin.shell));
479
- chmodSync(scriptPath, 0o755);
480
- }
481
- return scriptPath;
482
- }
449
+ // --- Usage ---
483
450
  function printUsage() {
484
451
  const agents = getAgents();
485
452
  const detected = agents.filter((a) => a.detect());
@@ -493,10 +460,15 @@ function printUsage() {
493
460
  console.log();
494
461
  }
495
462
  console.log(chalk.gray(" Usage:\n"));
496
- console.log(chalk.gray(" memax setup --mcp MCP tools for all detected agents"));
497
- console.log(chalk.gray(" memax setup --all MCP + hooks (where supported)"));
463
+ console.log(chalk.gray(" memax setup --mcp Remote MCP server for all detected agents"));
464
+ console.log(chalk.gray(" memax setup --instructions Inject memax usage instructions into agent configs"));
465
+ console.log(chalk.gray(" memax setup --all MCP + hooks + instructions"));
466
+ console.log(chalk.gray(" memax setup --mcp --local Use local CLI instead of remote server"));
467
+ console.log(chalk.gray(" memax setup --mcp --api-key Use API keys instead of OAuth (CI/CD)"));
468
+ console.log(chalk.gray(" memax setup --print Print MCP config to copy/paste"));
498
469
  console.log(chalk.gray(" memax setup --mcp --only claude-code,cursor"));
499
- console.log(chalk.gray(" memax setup --all --skip codex"));
470
+ console.log(chalk.gray(" memax setup --mcp --read-only"));
471
+ console.log(chalk.gray(" memax setup --mcp --hub memax-team --allow-organize"));
500
472
  console.log(chalk.gray(" memax teardown Remove all integrations\n"));
501
473
  console.log(chalk.gray(" Supported agents:"));
502
474
  for (const a of agents) {
@@ -507,25 +479,29 @@ function printUsage() {
507
479
  }
508
480
  console.log();
509
481
  }
510
- const UNIX_HOOK = `#!/bin/bash
511
- # Memax context injection — installed by: memax setup --hooks
512
- set -e
513
- INPUT=$(cat)
514
- PROMPT=$(echo "$INPUT" | jq -r '.prompt // empty')
515
- CWD=$(echo "$INPUT" | jq -r '.cwd // empty')
516
- if [ -z "$PROMPT" ] || [ \${#PROMPT} -lt 10 ]; then exit 0; fi
517
- case "$PROMPT" in [Yy]|[Yy]es|[Nn]|[Nn]o|ok|OK|sure|Sure|thanks|Thanks|y|n) exit 0 ;; esac
518
- if [ -n "$CWD" ]; then cd "$CWD" 2>/dev/null || true; fi
519
- CONTEXT=$($MEMAX recall "$PROMPT" --hook --limit 5 --max-tokens 3000 2>/dev/null) || exit 0
520
- if [ -n "$CONTEXT" ] && [ "$CONTEXT" != "<memax-context>" ]; then echo "$CONTEXT"; fi
521
- exit 0
522
- `;
523
- const WIN_HOOK = `@echo off
524
- REM Memax context injection installed by: memax setup --hooks
525
- set /p INPUT=
526
- for /f "tokens=*" %%a in ('echo %INPUT% ^| jq -r ".prompt // empty"') do set PROMPT=%%a
527
- if "%PROMPT%"=="" exit /b 0
528
- $MEMAX recall "%PROMPT%" --hook --limit 5 --max-tokens 3000 2>nul
529
- exit /b 0
530
- `;
482
+ export function registerSetupCommands(program) {
483
+ program
484
+ .command("setup")
485
+ .description("Set up AI agent integrations (auto-detects installed agents)")
486
+ .option("--mcp", "Enable MCP server (agent tools)")
487
+ .option("--hooks", "Enable context injection hooks")
488
+ .option("--instructions", "Inject memax instructions into agent config files")
489
+ .option("--all", "Enable MCP + hooks + instructions")
490
+ .option("--local", "Use local stdio MCP instead of remote server")
491
+ .option("--api-key", "Use per-agent API keys instead of OAuth (for CI/CD or agents without OAuth support)")
492
+ .option("--print", "Print MCP config JSON to copy/paste (no changes made)")
493
+ .option("--only <agents>", "Only configure these agents (comma-separated)")
494
+ .option("--skip <agents>", "Skip these agents (comma-separated)")
495
+ .option("--hub <id>", "Scope MCP key to a specific hub")
496
+ .option("--read-only", "Create remote MCP keys without write access")
497
+ .option("--allow-delete", "Allow remote MCP keys to delete memories")
498
+ .option("--allow-organize", "Allow remote MCP keys to organize topics and run dreams")
499
+ .option("--agent-sync", "Allow remote MCP keys to sync agent configs and sessions")
500
+ .action(setupCommand);
501
+ program
502
+ .command("teardown")
503
+ .description("Remove Memax integrations from agents")
504
+ .option("--only <agents>", "Only remove from these agents (comma-separated)")
505
+ .action(teardownCommand);
506
+ }
531
507
  //# sourceMappingURL=setup.js.map