@ctrl-spc/cli 1.3.3 → 1.3.4

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.
@@ -697,8 +697,9 @@ export function createProjectPresenceReconciler(client, userId, profileName, mac
697
697
  const intervalMs = deps.intervalMs ?? 30_000;
698
698
  let timer;
699
699
  let chain = Promise.resolve();
700
- const runReconcile = async () => {
701
- await deps.refreshWorkspaces?.();
700
+ const runReconcile = async (refreshWorkspaces) => {
701
+ if (refreshWorkspaces)
702
+ await deps.refreshWorkspaces?.();
702
703
  const projects = await loadProjects(client, userId, machine.id);
703
704
  const currentIds = new Set(projects.map((project) => project.id));
704
705
  for (const project of projects) {
@@ -722,10 +723,10 @@ export function createProjectPresenceReconciler(client, userId, profileName, mac
722
723
  }
723
724
  };
724
725
  return {
725
- reconcile() {
726
+ reconcile(options = {}) {
726
727
  // Recover the queue after a transient failed poll; the caller still
727
728
  // receives this run's rejection, but later intervals keep reconciling.
728
- chain = chain.catch(() => { }).then(runReconcile);
729
+ chain = chain.catch(() => { }).then(() => runReconcile(options.refreshWorkspaces !== false));
729
730
  return chain;
730
731
  },
731
732
  start() {
@@ -841,7 +842,7 @@ export async function run() {
841
842
  loadProjects: loadRunnableMachineProjects,
842
843
  refreshWorkspaces: () => reconcileMachineWorkspaces(client, userId, machine),
843
844
  });
844
- await presence.reconcile();
845
+ await presence.reconcile({ refreshWorkspaces: false });
845
846
  presence.start();
846
847
  let mcpServer;
847
848
  try {
@@ -1143,17 +1143,18 @@ export function renderCompanionUi(token, kind, runtimePort = kind === 'local' ?
1143
1143
  const profile = snapshot.profiles.find((candidate) => candidate.kind === kind);
1144
1144
  const action = profile.state === 'ready' ? 'stop' : 'start';
1145
1145
  lastError = '';
1146
+ let succeeded = false;
1146
1147
  setBusy(true, action === 'start' ? 'Switching connection…' : 'Turning connection off…');
1147
1148
  try {
1148
- await api('/api/runtime/' + kind + '/' + action, { method: 'POST' });
1149
- await refresh(true);
1149
+ render(await api('/api/runtime/' + kind + '/' + action, { method: 'POST' }));
1150
+ succeeded = true;
1150
1151
  } catch (error) {
1151
1152
  lastError = error.message;
1152
1153
  notice.className = 'notice error';
1153
1154
  notice.textContent = lastError;
1154
1155
  } finally {
1155
1156
  setBusy(false);
1156
- await refresh(false);
1157
+ if (!succeeded) await refresh(false);
1157
1158
  }
1158
1159
  }
1159
1160
 
@@ -215,7 +215,7 @@ export async function startRuntime(kind) {
215
215
  throw new Error(`Port ${profile.port} is already used by an unlabeled process. Turn it off before continuing.`);
216
216
  }
217
217
  mkdirSync(runtimeDir(), { recursive: true });
218
- const logFd = openSync(logPath(kind), 'a', 0o600);
218
+ const logFd = openSync(logPath(kind), 'w', 0o600);
219
219
  let child;
220
220
  try {
221
221
  child = spawn(process.execPath, [profile.entryPath, '__broker'], {
@@ -236,11 +236,16 @@ export async function startRuntime(kind) {
236
236
  if (!child.pid)
237
237
  throw new Error(`${profile.label} did not start.`);
238
238
  writePrivateFile(pidPath(kind), String(child.pid));
239
+ let childExited = false;
240
+ child.once('exit', () => { childExited = true; });
239
241
  const ready = await waitFor(async () => {
242
+ if (childExited)
243
+ return true;
240
244
  const health = await probeBroker(profile.port);
241
245
  return health?.runtime?.kind === kind;
242
246
  }, 30_000, 250);
243
- if (!ready) {
247
+ const health = await probeBroker(profile.port);
248
+ if (!ready || health?.runtime?.kind !== kind) {
244
249
  rmSync(pidPath(kind), { force: true });
245
250
  const log = existsSync(logPath(kind)) ? readFileSync(logPath(kind), 'utf8').trim().split('\n').slice(-4).join('\n') : '';
246
251
  throw new Error(`${profile.label} did not become ready.${log ? `\n${log}` : ''}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ctrl-spc/cli",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "CTRL+SPC CLI — per-machine agent for browser login, project linking, presence, and the local MCP server.",
5
5
  "engines": {
6
6
  "node": ">=22"