@xmemo/client 0.4.127 → 0.4.128

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 (3) hide show
  1. package/README.md +12 -0
  2. package/package.json +1 -1
  3. package/src/cli.js +89 -2
package/README.md CHANGED
@@ -16,9 +16,21 @@ internal scripts are not part of this npm package.
16
16
  npm install -g @xmemo/client
17
17
  ```
18
18
 
19
+ Upgrade an existing global install:
20
+
21
+ ```bash
22
+ xmemo update
23
+ # or
24
+ xmemo --update
25
+ ```
26
+
27
+ Both commands run `npm install -g @xmemo/client@latest`. Use
28
+ `xmemo update --dry-run` to print the exact command without changing anything.
29
+
19
30
  ## Commands
20
31
 
21
32
  ```bash
33
+ xmemo update
22
34
  xmemo setup codex
23
35
  xmemo setup codex --yes
24
36
  xmemo smoke --client codex
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xmemo/client",
3
- "version": "0.4.127",
3
+ "version": "0.4.128",
4
4
  "description": "Privacy-first CLI and MCP setup helper for XMemo.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
2
2
  import http from 'node:http';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
+ import { spawn } from 'node:child_process';
5
6
  import { randomUUID } from 'node:crypto';
6
7
 
7
8
  const PRODUCT_NAME = 'XMemo';
@@ -9,7 +10,7 @@ const PACKAGE_NAME = '@xmemo/client';
9
10
  const FALLBACK_PACKAGE_NAME = '@yonro/xmemo-client';
10
11
  const COMMAND_NAME = 'xmemo';
11
12
  const LEGACY_COMMAND_NAME = 'memory-os';
12
- const CLI_VERSION = '0.4.127';
13
+ const CLI_VERSION = '0.4.128';
13
14
  const DEFAULT_SERVICE_URL = 'https://xmemo.dev';
14
15
  const TOKEN_ENV_VAR = 'XMEMO_KEY';
15
16
  const LEGACY_TOKEN_ENV_VAR = 'MEMORY_OS_MCP_TOKEN';
@@ -64,6 +65,10 @@ export async function run(args, io = defaultIo()) {
64
65
  return 0;
65
66
  }
66
67
 
68
+ if (command === 'update' || command === '--update') {
69
+ return await updateCommand(args.slice(1), io);
70
+ }
71
+
67
72
  if (command === 'doctor') {
68
73
  return await doctorCommand(args.slice(1), io);
69
74
  }
@@ -128,7 +133,8 @@ function defaultIo() {
128
133
  stdin: process.stdin,
129
134
  stdout: process.stdout,
130
135
  stderr: process.stderr,
131
- fetch: globalThis.fetch
136
+ fetch: globalThis.fetch,
137
+ spawn
132
138
  };
133
139
  }
134
140
 
@@ -137,6 +143,8 @@ function writeHelp(io) {
137
143
  writeLine(io.stdout, `Fallback npm package: ${FALLBACK_PACKAGE_NAME}; legacy command alias: ${LEGACY_COMMAND_NAME}`);
138
144
  writeLine(io.stdout, '');
139
145
  writeLine(io.stdout, 'Usage:');
146
+ writeLine(io.stdout, ` ${COMMAND_NAME} update [--dry-run] [--json]`);
147
+ writeLine(io.stdout, ` ${COMMAND_NAME} --update [--dry-run] [--json]`);
140
148
  writeLine(io.stdout, ` ${COMMAND_NAME} doctor [--base-url <https://api.example.com>] [--json]`);
141
149
  writeLine(io.stdout, ` ${COMMAND_NAME} discovery show [--base-url <https://api.example.com>] [--json]`);
142
150
  writeLine(io.stdout, ` ${COMMAND_NAME} setup [codex|cursor] [--url <https://api.example.com>] [--write|--yes] [--json]`);
@@ -162,6 +170,49 @@ function writeHelp(io) {
162
170
  writeLine(io.stdout, '`login` and `token add` store credentials only in the user-scoped XMemo CLI config directory.');
163
171
  }
164
172
 
173
+ async function updateCommand(args, io) {
174
+ const outputJson = hasFlag(args, '--json');
175
+ const dryRun = hasFlag(args, '--dry-run');
176
+ const npmCommand = npmExecutable();
177
+ const npmArgs = ['install', '-g', `${PACKAGE_NAME}@latest`];
178
+ const report = {
179
+ package: PACKAGE_NAME,
180
+ command: [npmCommand, ...npmArgs],
181
+ dryRun,
182
+ tokenSent: false,
183
+ projectFilesModified: false
184
+ };
185
+
186
+ if (dryRun) {
187
+ if (outputJson) {
188
+ writeLine(io.stdout, JSON.stringify(report, null, 2));
189
+ } else {
190
+ writeLine(io.stdout, `Update command: ${report.command.join(' ')}`);
191
+ writeLine(io.stdout, 'Dry run only; no changes made.');
192
+ }
193
+ return 0;
194
+ }
195
+
196
+ if (!outputJson) {
197
+ writeLine(io.stdout, `Updating ${PACKAGE_NAME} with: ${report.command.join(' ')}`);
198
+ }
199
+ const result = await runProcess(npmCommand, npmArgs, io, { stream: !outputJson });
200
+ report.exitCode = result.code;
201
+ report.completed = result.code === 0;
202
+
203
+ if (outputJson) {
204
+ writeLine(io.stdout, JSON.stringify(report, null, 2));
205
+ }
206
+ if (result.code !== 0) {
207
+ const detail = result.stderr.trim() || result.stdout.trim() || `exit code ${result.code}`;
208
+ throw new UsageError(`Update failed: ${detail}`);
209
+ }
210
+ if (!outputJson) {
211
+ writeLine(io.stdout, `Update complete. Run \`${COMMAND_NAME} --version\` to confirm.`);
212
+ }
213
+ return 0;
214
+ }
215
+
165
216
  async function doctorCommand(args, io) {
166
217
  const baseUrl = normalizeBaseUrl(baseUrlOption(args, io.env));
167
218
  const outputJson = hasFlag(args, '--json');
@@ -1876,6 +1927,42 @@ async function sleep(ms) {
1876
1927
  await new Promise((resolve) => setTimeout(resolve, ms));
1877
1928
  }
1878
1929
 
1930
+
1931
+ function npmExecutable() {
1932
+ return os.platform() === 'win32' ? 'npm.cmd' : 'npm';
1933
+ }
1934
+
1935
+
1936
+ async function runProcess(command, args, io, { stream = true } = {}) {
1937
+ const spawnFn = io.spawn ?? spawn;
1938
+ return await new Promise((resolve, reject) => {
1939
+ const child = spawnFn(command, args, {
1940
+ stdio: ['ignore', 'pipe', 'pipe']
1941
+ });
1942
+ let stdout = '';
1943
+ let stderr = '';
1944
+ child.stdout?.on('data', (chunk) => {
1945
+ const text = String(chunk);
1946
+ stdout += text;
1947
+ if (stream) {
1948
+ io.stdout.write(text);
1949
+ }
1950
+ });
1951
+ child.stderr?.on('data', (chunk) => {
1952
+ const text = String(chunk);
1953
+ stderr += text;
1954
+ if (stream) {
1955
+ io.stderr.write(text);
1956
+ }
1957
+ });
1958
+ child.on('error', reject);
1959
+ child.on('close', (code) => {
1960
+ resolve({ code: code ?? 0, stdout, stderr });
1961
+ });
1962
+ });
1963
+ }
1964
+
1965
+
1879
1966
  async function readAll(stream) {
1880
1967
  let content = '';
1881
1968
  for await (const chunk of stream) {