cwm-assistant 0.2.1 → 0.3.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.
@@ -5,10 +5,10 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/cli.ts
8
- import { spawn as spawn7 } from "node:child_process";
9
- import fs23 from "node:fs";
8
+ import { spawn as spawn8 } from "node:child_process";
9
+ import fs24 from "node:fs";
10
10
  import os9 from "node:os";
11
- import path26 from "node:path";
11
+ import path27 from "node:path";
12
12
  import { createInterface } from "node:readline/promises";
13
13
 
14
14
  // ../../node_modules/@sinclair/typebox/build/esm/type/guard/value.mjs
@@ -2619,7 +2619,7 @@ var Type = type_exports2;
2619
2619
 
2620
2620
  // ../../packages/contracts/src/assistant.ts
2621
2621
  var ASSISTANT_PROTOCOL = 1;
2622
- var ASSISTANT_VERSION = "0.2.1";
2622
+ var ASSISTANT_VERSION = "0.3.1";
2623
2623
  var ASSISTANT_PORT = 46322;
2624
2624
 
2625
2625
  // ../../packages/contracts/src/index.ts
@@ -4031,7 +4031,10 @@ function registerLocalRoutes(app, store, options = {}) {
4031
4031
  fs11.mkdirSync(preferred, { recursive: true });
4032
4032
  return { path: fs11.realpathSync(preferred) };
4033
4033
  });
4034
- app.post("/v1/tools/detect", async () => ({ tools: detectToolCommands() }));
4034
+ app.post("/v1/tools/detect", async () => ({
4035
+ tools: detectToolCommands(),
4036
+ platform: process.platform
4037
+ }));
4035
4038
  app.get("/v1/workspaces", async (request) => {
4036
4039
  const grant = requestGrant(request);
4037
4040
  return Object.values(store.state.mounts).filter((mount) => mount.workbenchId === grant.workbenchId && mount.userId === grant.userId).map((mount) => ({ ...mount, exists: fs11.existsSync(mount.directory) }));
@@ -5554,20 +5557,20 @@ function registerQueryRoutes(app, store) {
5554
5557
  app.get("/v1/repositories", async (request) => {
5555
5558
  const grant = requestGrant(request);
5556
5559
  const prefix = scopeKey(grant.workbenchId, grant.userId, "");
5557
- return Object.entries(store.state.repositories).filter(([key]) => key.startsWith(prefix)).map(([key, path27]) => ({ id: key.slice(prefix.length), path: path27 }));
5560
+ return Object.entries(store.state.repositories).filter(([key]) => key.startsWith(prefix)).map(([key, path28]) => ({ id: key.slice(prefix.length), path: path28 }));
5558
5561
  });
5559
5562
  app.post("/v1/repositories/inspect", async (request) => {
5560
5563
  const body = request.body;
5561
- const path27 = body.path;
5562
- if (!path27 || !(await import("node:path")).default.isAbsolute(path27))
5564
+ const path28 = body.path;
5565
+ if (!path28 || !(await import("node:path")).default.isAbsolute(path28))
5563
5566
  throw new Error("\u8BF7\u9009\u62E9\u7EDD\u5BF9\u8DEF\u5F84");
5564
5567
  return git4(async () => ({
5565
- root: await runGit(path27, ["rev-parse", "--show-toplevel"]),
5566
- remoteUrl: (await runGit(path27, ["remote", "get-url", "origin"]).catch(() => "")).replace(
5568
+ root: await runGit(path28, ["rev-parse", "--show-toplevel"]),
5569
+ remoteUrl: (await runGit(path28, ["remote", "get-url", "origin"]).catch(() => "")).replace(
5567
5570
  /(https?:\/\/)[^/@\s]+@/gi,
5568
5571
  "$1"
5569
5572
  ),
5570
- branches: (await runGit(path27, [
5573
+ branches: (await runGit(path28, [
5571
5574
  "for-each-ref",
5572
5575
  "--format=%(refname:short)",
5573
5576
  "refs/heads",
@@ -5985,8 +5988,121 @@ function registerReviewRoute(app, store, jobs) {
5985
5988
  });
5986
5989
  }
5987
5990
 
5991
+ // src/upgrade.ts
5992
+ import { spawn as spawn7 } from "node:child_process";
5993
+ import fs23 from "node:fs";
5994
+ import path26 from "node:path";
5995
+ function packageRoot(entry) {
5996
+ let directory = path26.dirname(path26.resolve(entry));
5997
+ for (let depth = 0; depth < 4; depth++) {
5998
+ if (fs23.existsSync(path26.join(directory, "package.json"))) return directory;
5999
+ directory = path26.dirname(directory);
6000
+ }
6001
+ return path26.dirname(path26.dirname(path26.resolve(entry)));
6002
+ }
6003
+ function isGlobalInstall(root, prefix) {
6004
+ if (!prefix) return false;
6005
+ const relative = path26.relative(path26.resolve(prefix), path26.resolve(root));
6006
+ return Boolean(relative) && !relative.startsWith("..") && !path26.isAbsolute(relative);
6007
+ }
6008
+ function looksGlobal(root) {
6009
+ const modules = path26.dirname(path26.resolve(root));
6010
+ if (path26.basename(modules) !== "node_modules") return false;
6011
+ return !fs23.existsSync(path26.join(path26.dirname(modules), "package.json"));
6012
+ }
6013
+ function isNewer(candidate, current) {
6014
+ const parse = (value) => value.split("-")[0].split(".").map((part) => Number(part) || 0);
6015
+ const [a, b] = [parse(candidate), parse(current)];
6016
+ for (let index = 0; index < Math.max(a.length, b.length); index++) {
6017
+ const [left, right] = [a[index] ?? 0, b[index] ?? 0];
6018
+ if (left !== right) return left > right;
6019
+ }
6020
+ return !candidate.includes("-") && current.includes("-");
6021
+ }
6022
+ async function latestPublished() {
6023
+ const response = await fetch("https://registry.npmjs.org/cwm-assistant/latest", {
6024
+ signal: AbortSignal.timeout(5e3),
6025
+ headers: { Accept: "application/vnd.npm.install-v1+json" }
6026
+ });
6027
+ if (!response.ok) throw new Error("\u65E0\u6CD5\u67E5\u8BE2 npm \u4E0A\u7684\u6700\u65B0\u7248\u672C");
6028
+ const data = await response.json();
6029
+ if (!data.version) throw new Error("npm \u8FD4\u56DE\u7684\u7248\u672C\u4FE1\u606F\u65E0\u6548");
6030
+ return data.version;
6031
+ }
6032
+ var statusFile = (store) => path26.join(store.directory, "upgrade.json");
6033
+ function readStatus(store) {
6034
+ try {
6035
+ return JSON.parse(fs23.readFileSync(statusFile(store), "utf8"));
6036
+ } catch {
6037
+ return void 0;
6038
+ }
6039
+ }
6040
+ function registerUpgradeRoutes(app, store, entry, options = {}) {
6041
+ const latestVersion = options.latest || latestPublished;
6042
+ const root = packageRoot(entry);
6043
+ const prefix = () => store.state.installPrefix || process.env.npm_config_prefix;
6044
+ const global = isGlobalInstall(root, prefix()) || looksGlobal(root);
6045
+ app.get("/v1/upgrade", async () => {
6046
+ let latest = "";
6047
+ let error = "";
6048
+ try {
6049
+ latest = await latestVersion();
6050
+ } catch (cause) {
6051
+ error = cause.message;
6052
+ }
6053
+ const status = readStatus(store);
6054
+ const busy = Object.values(store.state.jobs).some((job) => job.state === "running");
6055
+ return {
6056
+ version: ASSISTANT_VERSION,
6057
+ latest,
6058
+ available: Boolean(latest) && isNewer(latest, ASSISTANT_VERSION),
6059
+ // A source run (or an install we cannot locate) is not replaced from the page.
6060
+ upgradeable: global,
6061
+ reason: global ? "" : "\u5F53\u524D\u4E0D\u662F\u5168\u5C40 npm \u5B89\u88C5\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u5347\u7EA7",
6062
+ error,
6063
+ busy,
6064
+ // A previous attempt that never reached the new version left this behind.
6065
+ previous: status && status.target !== ASSISTANT_VERSION ? status : void 0
6066
+ };
6067
+ });
6068
+ app.post("/v1/upgrade", async (_request, reply) => {
6069
+ if (!global) return reply.code(409).send({ error: "\u5F53\u524D\u4E0D\u662F\u5168\u5C40 npm \u5B89\u88C5\uFF0C\u8BF7\u5728\u7EC8\u7AEF\u5347\u7EA7" });
6070
+ if (Object.values(store.state.jobs).some((job) => job.state === "running"))
6071
+ return reply.code(409).send({ error: "\u672C\u5730\u4EFB\u52A1\u8FD0\u884C\u4E2D\uFF0C\u8BF7\u7B49\u5F85\u5B8C\u6210\u540E\u518D\u5347\u7EA7" });
6072
+ let latest;
6073
+ try {
6074
+ latest = await latestVersion();
6075
+ } catch (cause) {
6076
+ return reply.code(502).send({ error: cause.message });
6077
+ }
6078
+ if (!isNewer(latest, ASSISTANT_VERSION)) return { latest, started: false };
6079
+ fs23.writeFileSync(
6080
+ statusFile(store),
6081
+ JSON.stringify({ target: latest, state: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString() }),
6082
+ { mode: 384 }
6083
+ );
6084
+ const child = spawn7(
6085
+ process.execPath,
6086
+ [
6087
+ path26.join(root, "scripts", "upgrade.mjs"),
6088
+ "--version",
6089
+ latest,
6090
+ "--state-dir",
6091
+ store.directory
6092
+ ],
6093
+ { detached: true, stdio: "ignore", windowsHide: true, cwd: root }
6094
+ );
6095
+ child.unref();
6096
+ if (store.state.autostart)
6097
+ await configureAutostart(false, store.directory, entry).catch(() => {
6098
+ });
6099
+ setTimeout(() => process.exit(0), 300);
6100
+ return { latest, started: true };
6101
+ });
6102
+ }
6103
+
5988
6104
  // src/server.ts
5989
- async function buildAssistant(store, options = {}) {
6105
+ async function buildAssistant(store, options = {}, entry, upgrade = {}) {
5990
6106
  const app = Fastify({ logger: false, bodyLimit: 1024 * 1024 });
5991
6107
  const jobs = new LocalJobs(store);
5992
6108
  app.setErrorHandler(
@@ -6001,7 +6117,7 @@ async function buildAssistant(store, options = {}) {
6001
6117
  return;
6002
6118
  }
6003
6119
  const origin = request.headers.origin || "";
6004
- const path27 = request.url.split("?")[0];
6120
+ const path28 = request.url.split("?")[0];
6005
6121
  let workbench = store.state.workbenches[origin];
6006
6122
  if (!workbench && trustedOrigin(origin)) {
6007
6123
  try {
@@ -6024,7 +6140,7 @@ async function buildAssistant(store, options = {}) {
6024
6140
  reply.header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-CWM-Binding");
6025
6141
  return reply.code(204).send();
6026
6142
  }
6027
- if (path27 === "/v1/health" || path27 === "/v1/pairings") return;
6143
+ if (path28 === "/v1/health" || path28 === "/v1/pairings") return;
6028
6144
  try {
6029
6145
  const token = request.headers.authorization?.replace(/^Bearer /, "") || "";
6030
6146
  const grant = verifyGrant(token, workbench.publicKey);
@@ -6143,6 +6259,8 @@ async function buildAssistant(store, options = {}) {
6143
6259
  input: job.input,
6144
6260
  state: job.state,
6145
6261
  error: job.error,
6262
+ // The list is used to pick the newest job per workspace, so ordering needs a timestamp.
6263
+ updatedAt: "updatedAt" in job ? job.updatedAt : void 0,
6146
6264
  deviceId: store.state.identity.id
6147
6265
  }));
6148
6266
  });
@@ -6172,6 +6290,7 @@ async function buildAssistant(store, options = {}) {
6172
6290
  return { ok: true };
6173
6291
  });
6174
6292
  registerLocalRoutes(app, store, options);
6293
+ if (entry) registerUpgradeRoutes(app, store, entry, upgrade);
6175
6294
  registerQueryRoutes(app, store);
6176
6295
  registerAdoptRoutes(app, store, jobs);
6177
6296
  registerDeleteRoute(app, store, jobs);
@@ -6195,7 +6314,7 @@ async function cli(args, entry) {
6195
6314
  return;
6196
6315
  }
6197
6316
  const dirIndex = args.indexOf("--state-dir");
6198
- const directory = dirIndex >= 0 ? path26.resolve(args[dirIndex + 1]) : defaultStateDirectory();
6317
+ const directory = dirIndex >= 0 ? path27.resolve(args[dirIndex + 1]) : defaultStateDirectory();
6199
6318
  const command = args[0] || "status";
6200
6319
  const store = new StateStore(directory);
6201
6320
  const control = async (action) => {
@@ -6221,14 +6340,14 @@ async function cli(args, entry) {
6221
6340
  }
6222
6341
  if (store.state.autostart) await startAutostart();
6223
6342
  else {
6224
- const log = fs23.openSync(path26.join(directory, "assistant.log"), "a", 384);
6225
- const child = spawn7(process.execPath, [entry, "serve", "--state-dir", directory], {
6343
+ const log = fs24.openSync(path27.join(directory, "assistant.log"), "a", 384);
6344
+ const child = spawn8(process.execPath, [entry, "serve", "--state-dir", directory], {
6226
6345
  detached: true,
6227
6346
  windowsHide: true,
6228
6347
  stdio: ["ignore", log, log]
6229
6348
  });
6230
6349
  child.unref();
6231
- fs23.closeSync(log);
6350
+ fs24.closeSync(log);
6232
6351
  }
6233
6352
  const deadline = Date.now() + 1e4;
6234
6353
  while (Date.now() < deadline) {
@@ -6243,21 +6362,21 @@ async function cli(args, entry) {
6243
6362
  throw new Error("\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u8FD0\u884C cwm-assistant logs \u68C0\u67E5\u7AEF\u53E3\u548C\u8FD0\u884C\u73AF\u5883");
6244
6363
  }
6245
6364
  if (command === "serve") {
6246
- const lock = path26.join(directory, "process.lock");
6247
- if (fs23.existsSync(lock)) {
6248
- const pid = Number(fs23.readFileSync(lock, "utf8"));
6365
+ const lock = path27.join(directory, "process.lock");
6366
+ if (fs24.existsSync(lock)) {
6367
+ const pid = Number(fs24.readFileSync(lock, "utf8"));
6249
6368
  try {
6250
6369
  process.kill(pid, 0);
6251
6370
  throw new Error("Workspace \u52A9\u624B\u5DF2\u8FD0\u884C");
6252
6371
  } catch (error) {
6253
6372
  if (error.code !== "ESRCH") throw error;
6254
- fs23.unlinkSync(lock);
6373
+ fs24.unlinkSync(lock);
6255
6374
  }
6256
6375
  }
6257
- fs23.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
6376
+ fs24.writeFileSync(lock, String(process.pid), { flag: "wx", mode: 384 });
6258
6377
  store.reload();
6259
6378
  store.recover();
6260
- const { app, jobs } = await buildAssistant(store);
6379
+ const { app, jobs } = await buildAssistant(store, {}, entry);
6261
6380
  app.post("/control", async (request, reply) => {
6262
6381
  const action = request.body.action;
6263
6382
  if (action === "status")
@@ -6278,12 +6397,12 @@ async function cli(args, entry) {
6278
6397
  return reply.code(400).send({ error: "\u63A7\u5236\u64CD\u4F5C\u65E0\u6548" });
6279
6398
  });
6280
6399
  app.addHook("onClose", async () => {
6281
- fs23.rmSync(lock, { force: true });
6400
+ fs24.rmSync(lock, { force: true });
6282
6401
  });
6283
6402
  try {
6284
6403
  await app.listen({ host: "127.0.0.1", port: ASSISTANT_PORT });
6285
6404
  } catch (error) {
6286
- fs23.rmSync(lock, { force: true });
6405
+ fs24.rmSync(lock, { force: true });
6287
6406
  throw error;
6288
6407
  }
6289
6408
  for (const signal of ["SIGTERM", "SIGINT"])
@@ -6292,6 +6411,9 @@ async function cli(args, entry) {
6292
6411
  }
6293
6412
  if (command === "start") return start();
6294
6413
  if (command === "install") {
6414
+ const prefixIndex = args.indexOf("--prefix");
6415
+ const installPrefix = prefixIndex >= 0 ? args[prefixIndex + 1] : store.state.installPrefix;
6416
+ if (installPrefix) store.state.installPrefix = installPrefix;
6295
6417
  const running = await control("status").catch(() => void 0);
6296
6418
  if (running?.busy) {
6297
6419
  console.error(
@@ -6305,18 +6427,21 @@ async function cli(args, entry) {
6305
6427
  await control("stop").catch(() => {
6306
6428
  });
6307
6429
  const deadline = Date.now() + 1e4;
6308
- while (fs23.existsSync(path26.join(directory, "process.lock")) && Date.now() < deadline) {
6430
+ while (fs24.existsSync(path27.join(directory, "process.lock")) && Date.now() < deadline) {
6309
6431
  await new Promise((resolve) => setTimeout(resolve, 100));
6310
6432
  }
6311
6433
  }
6434
+ let autostart = false;
6312
6435
  try {
6313
6436
  await configureAutostart(true, directory, entry);
6314
- store.reload();
6315
- store.state.autostart = true;
6316
- store.save();
6437
+ autostart = true;
6317
6438
  } catch {
6318
6439
  console.error("\u672A\u80FD\u6CE8\u518C\u767B\u5F55\u81EA\u542F\u52A8\uFF0C\u52A9\u624B\u5C06\u53EA\u4F5C\u4E3A\u540E\u53F0\u8FDB\u7A0B\u8FD0\u884C\u3002");
6319
6440
  }
6441
+ store.reload();
6442
+ store.state.autostart = autostart;
6443
+ if (installPrefix) store.state.installPrefix = installPrefix;
6444
+ store.save();
6320
6445
  await start();
6321
6446
  return;
6322
6447
  }
@@ -6334,9 +6459,9 @@ async function cli(args, entry) {
6334
6459
  return;
6335
6460
  }
6336
6461
  if (command === "logs") {
6337
- const file = path26.join(directory, "assistant.log");
6462
+ const file = path27.join(directory, "assistant.log");
6338
6463
  console.log(
6339
- fs23.existsSync(file) ? fs23.readFileSync(file, "utf8").split("\n").slice(-100).join("\n") : "\u6682\u65E0\u65E5\u5FD7"
6464
+ fs24.existsSync(file) ? fs24.readFileSync(file, "utf8").split("\n").slice(-100).join("\n") : "\u6682\u65E0\u65E5\u5FD7"
6340
6465
  );
6341
6466
  return;
6342
6467
  }
@@ -6351,10 +6476,10 @@ async function cli(args, entry) {
6351
6476
  await control("stop").catch(() => {
6352
6477
  });
6353
6478
  const deadline = Date.now() + 1e4;
6354
- while (fs23.existsSync(path26.join(directory, "process.lock")) && Date.now() < deadline) {
6479
+ while (fs24.existsSync(path27.join(directory, "process.lock")) && Date.now() < deadline) {
6355
6480
  await new Promise((resolve) => setTimeout(resolve, 100));
6356
6481
  }
6357
- if (fs23.existsSync(path26.join(directory, "process.lock")))
6482
+ if (fs24.existsSync(path27.join(directory, "process.lock")))
6358
6483
  throw new Error("\u52A9\u624B\u5C1A\u672A\u505C\u6B62\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\uFF1B\u672A\u4FEE\u6539\u672C\u5730\u72B6\u6001");
6359
6484
  }
6360
6485
  store.reload();
@@ -6389,11 +6514,11 @@ async function cli(args, entry) {
6389
6514
  {
6390
6515
  node: process.version,
6391
6516
  nodePath: process.execPath,
6392
- entryExists: fs23.existsSync(entry),
6517
+ entryExists: fs24.existsSync(entry),
6393
6518
  git: store.state.git,
6394
6519
  gitVersion,
6395
6520
  platform: process.platform,
6396
- rootExists: Boolean(store.state.root && fs23.existsSync(store.state.root)),
6521
+ rootExists: Boolean(store.state.root && fs24.existsSync(store.state.root)),
6397
6522
  configuredWorkbenches: Object.keys(store.state.workbenches),
6398
6523
  autostart: store.state.autostart,
6399
6524
  process: await control("status").catch(() => "\u672A\u8FD0\u884C")
@@ -6434,9 +6559,9 @@ async function cli(args, entry) {
6434
6559
  }
6435
6560
  }
6436
6561
  if (rootArg) {
6437
- if (!path26.isAbsolute(rootArg) || !fs23.statSync(rootArg).isDirectory())
6562
+ if (!path27.isAbsolute(rootArg) || !fs24.statSync(rootArg).isDirectory())
6438
6563
  throw new Error("\u5DE5\u4F5C\u76EE\u5F55\u5FC5\u987B\u662F\u5DF2\u5B58\u5728\u7684\u7EDD\u5BF9\u8DEF\u5F84");
6439
- store.state.root = fs23.realpathSync(rootArg);
6564
+ store.state.root = fs24.realpathSync(rootArg);
6440
6565
  }
6441
6566
  const auto = wantAutostart || !assumeYes && await ask("\u767B\u5F55\u7535\u8111\u65F6\u81EA\u52A8\u542F\u52A8\uFF1F[Y/n] ") !== "n";
6442
6567
  store.save();
@@ -6466,4 +6591,4 @@ async function cli(args, entry) {
6466
6591
  export {
6467
6592
  cli
6468
6593
  };
6469
- //# sourceMappingURL=cli-Q3EWGNQT.js.map
6594
+ //# sourceMappingURL=cli-GOWF7QRM.js.map