@4pm/cli 0.2.1 → 0.3.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.
Files changed (2) hide show
  1. package/dist/index.js +95 -5
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -3930,7 +3930,7 @@ MCowBQYDK2VwAyEARlfzjaFhe5bhRrMp/ZuH6sVqg/S7f8B1dliuF21iDTw=
3930
3930
  `;
3931
3931
 
3932
3932
  // src/version.ts
3933
- var CLI_VERSION = "0.2.1";
3933
+ var CLI_VERSION = "0.3.0";
3934
3934
 
3935
3935
  // src/core/update.ts
3936
3936
  function compareSemver(a, b) {
@@ -3978,6 +3978,27 @@ async function updateViaDownload(tarballUrl, checksum, signature) {
3978
3978
  stdio: "inherit"
3979
3979
  });
3980
3980
  }
3981
+ async function updateToLatest(serverUrl) {
3982
+ const meta = await fetchCliVersion(serverUrl);
3983
+ if (compareSemver(CLI_VERSION, meta.latest) >= 0) {
3984
+ return { action: "already-latest", version: CLI_VERSION };
3985
+ }
3986
+ console.log(`Updating ${CLI_VERSION} \u2192 ${meta.latest}\u2026`);
3987
+ try {
3988
+ if (installedViaNpm()) {
3989
+ updateViaNpm(meta.latest);
3990
+ } else {
3991
+ await updateViaDownload(
3992
+ meta.source.tarballUrl,
3993
+ meta.source.checksum,
3994
+ meta.source.signature
3995
+ );
3996
+ }
3997
+ return { action: "updated", version: meta.latest };
3998
+ } catch (err) {
3999
+ return { action: "failed", error: String(err) };
4000
+ }
4001
+ }
3981
4002
  async function checkAndUpdate(serverUrl, autoUpdate) {
3982
4003
  let meta;
3983
4004
  try {
@@ -4814,6 +4835,7 @@ async function addProject(payload, onProgress) {
4814
4835
 
4815
4836
  // src/core/ws-client.ts
4816
4837
  var HEARTBEAT_MS = 3e4;
4838
+ var MAX_HASHCODE_INVALID_RETRIES = 5;
4817
4839
  var CLOSE_DRAINING = 4001;
4818
4840
  var OFFLINE_BUFFER_MAX_BYTES = 512 * 1024;
4819
4841
  var WsClient = class {
@@ -4838,16 +4860,32 @@ var WsClient = class {
4838
4860
  * Main lifecycle: connect + reconnect backoff until revoked.
4839
4861
  */
4840
4862
  async run() {
4863
+ let hashcodeInvalidStreak = 0;
4841
4864
  for (let attempt = 0; !this.stopped; attempt++) {
4842
4865
  try {
4843
4866
  await this.connectOnce();
4844
4867
  attempt = 0;
4868
+ hashcodeInvalidStreak = 0;
4845
4869
  } catch (err) {
4846
- if (err instanceof CliApiError && ["HASHCODE_INVALID", "HASHCODE_EXPIRED", "LINK_REVOKED"].includes(err.errorCode)) {
4847
- this.logout(err.errorCode);
4848
- return;
4870
+ if (err instanceof CliApiError) {
4871
+ if (["HASHCODE_EXPIRED", "LINK_REVOKED"].includes(err.errorCode)) {
4872
+ this.logout(err.errorCode);
4873
+ return;
4874
+ }
4875
+ if (err.errorCode === "HASHCODE_INVALID") {
4876
+ if (++hashcodeInvalidStreak > MAX_HASHCODE_INVALID_RETRIES) {
4877
+ this.logout(err.errorCode);
4878
+ return;
4879
+ }
4880
+ console.warn(
4881
+ `Token request rejected (HASHCODE_INVALID) \u2014 retrying (${hashcodeInvalidStreak}/${MAX_HASHCODE_INVALID_RETRIES}).`
4882
+ );
4883
+ } else {
4884
+ console.error(`Connection error: ${err}`);
4885
+ }
4886
+ } else {
4887
+ console.error(`Connection error: ${err}`);
4849
4888
  }
4850
- console.error(`Connection error: ${err}`);
4851
4889
  }
4852
4890
  if (this.stopped) return;
4853
4891
  const wait = backoffJitterMs(Math.min(attempt, 6), 1e3, 6e4);
@@ -5176,6 +5214,47 @@ function runUnlink(profileDir2, profileName) {
5176
5214
  console.log("\u2714 Deleted .cre \u2014 revoke the server-side link at web /machines.");
5177
5215
  }
5178
5216
 
5217
+ // src/commands/update.ts
5218
+ async function runUpdate(profileDir2, profileName, serverUrlFlag) {
5219
+ const serverUrl = (serverUrlFlag ?? readCredential(profileDir2, profileName)?.serverUrl ?? process.env.FOURPM_SERVER ?? "http://localhost:3001").replace(/\/$/, "");
5220
+ let result;
5221
+ try {
5222
+ result = await updateToLatest(serverUrl);
5223
+ } catch (err) {
5224
+ console.error(`Could not check for updates: ${err}`);
5225
+ process.exitCode = 1;
5226
+ return;
5227
+ }
5228
+ switch (result.action) {
5229
+ case "already-latest":
5230
+ console.log(`Already up to date (${result.version}).`);
5231
+ break;
5232
+ case "updated":
5233
+ console.log(`\u2714 Updated to ${result.version}. Run \`4pm start\` to reconnect.`);
5234
+ break;
5235
+ case "failed":
5236
+ console.error(`Update failed: ${result.error}`);
5237
+ process.exitCode = 1;
5238
+ break;
5239
+ }
5240
+ }
5241
+
5242
+ // src/commands/version.ts
5243
+ async function runVersion(profileDir2, profileName, serverUrlFlag) {
5244
+ console.log(`4pm cli ${CLI_VERSION}`);
5245
+ const serverUrl = serverUrlFlag ?? readCredential(profileDir2, profileName)?.serverUrl ?? process.env.FOURPM_SERVER;
5246
+ if (!serverUrl) return;
5247
+ try {
5248
+ const meta = await fetchCliVersion(serverUrl.replace(/\/$/, ""));
5249
+ const outdated = compareSemver(CLI_VERSION, meta.latest) < 0;
5250
+ const hint = outdated ? " (update available \u2014 run `4pm update`)" : " (up to date)";
5251
+ console.log(`latest: ${meta.latest}${hint}`);
5252
+ console.log(`minSupported: ${meta.minSupported}`);
5253
+ } catch (err) {
5254
+ console.warn(`Could not reach the server for the latest version: ${err}`);
5255
+ }
5256
+ }
5257
+
5179
5258
  // src/index.ts
5180
5259
  function flagValue(args, name) {
5181
5260
  const index = args.indexOf(`--${name}`);
@@ -5197,6 +5276,15 @@ async function main() {
5197
5276
  case "unlink":
5198
5277
  runUnlink(dir, profileName);
5199
5278
  break;
5279
+ case "version":
5280
+ case "--version":
5281
+ case "-v":
5282
+ await runVersion(dir, profileName, flagValue(rest, "server"));
5283
+ break;
5284
+ case "update":
5285
+ case "upgrade":
5286
+ await runUpdate(dir, profileName, flagValue(rest, "server"));
5287
+ break;
5200
5288
  default:
5201
5289
  console.log(
5202
5290
  [
@@ -5206,6 +5294,8 @@ async function main() {
5206
5294
  " 4pm [--profile <name>] link [--server URL] Pair with the server (hashcode 1\u21922\u21923)",
5207
5295
  " 4pm [--profile <name>] start Connect WS, receive commands from the server",
5208
5296
  " 4pm [--profile <name>] unlink Delete the local credential (.cre)",
5297
+ " 4pm version [--server URL] Show the installed version (+ latest from the server)",
5298
+ " 4pm update [--server URL] Update to the latest version now",
5209
5299
  "",
5210
5300
  "Without --profile \u21D2 the `default` profile (ADR-0014)."
5211
5301
  ].join("\n")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4pm/cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "private": false,
5
5
  "description": "4PM CLI — drives Claude/Codex CLIs on AI worker machines (Node + TypeScript)",
6
6
  "type": "module",