@commonlyai/cli 0.1.57 → 0.1.60

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonlyai/cli",
3
- "version": "0.1.57",
3
+ "version": "0.1.60",
4
4
  "license": "Apache-2.0",
5
5
  "description": "The Commonly CLI — connect agents, manage pods, iterate fast",
6
6
  "type": "module",
@@ -34,6 +34,24 @@ import {
34
34
  import { daemonLogsDir, daemonSeatLogPath, readLogTail } from '../lib/daemon-logs.js';
35
35
  import { loadDaemonState, saveDaemonState } from '../lib/daemon-state.js';
36
36
 
37
+ // One supervised seat, as `commonly daemon status --verbose` names it. Two of these
38
+ // fields are worth reading closely: the seat's `instanceId` (the identity the
39
+ // platform routes by, so a seat attached to the wrong instance is otherwise
40
+ // invisible) and the supervisor's `restarts` flap counter — which is written,
41
+ // persisted and served, and was shown by no surface at all, so a seat that had
42
+ // been crash-looping looked exactly like a healthy one (TASK-072, 2026-09-19).
43
+ //
44
+ // `restarts` distinguishes 0 from absent on purpose: a state file written by an
45
+ // older daemon has no such field, and printing 0 for it would claim a reading the
46
+ // file does not carry.
47
+ export const formatSeatLine = (seat = {}) => {
48
+ const model = seat.model || 'default model';
49
+ const effort = seat.effort ? `/${seat.effort}` : '';
50
+ const error = seat.lastError ? ` error=${seat.lastError}` : '';
51
+ const restarts = seat.restarts === undefined || seat.restarts === null ? '-' : seat.restarts;
52
+ return ` ${seat.agentName}: ${seat.state} instance=${seat.instanceId || '-'} adapter=${seat.adapter || 'unknown'} model=${model}${effort} pid=${seat.pid || '-'} restarts=${restarts} lastTurn=${seat.lastTurnAt || 'never'}${error}`;
53
+ };
54
+
37
55
  const requireDaemonRecord = () => {
38
56
  const record = loadDaemonRecord();
39
57
  if (!record) {
@@ -397,12 +415,7 @@ Examples:
397
415
  console.log('Local supervisor state: no supervised seats.');
398
416
  } else {
399
417
  console.log('Local supervised seats:');
400
- for (const seat of local.seats) {
401
- const model = seat.model || 'default model';
402
- const effort = seat.effort ? `/${seat.effort}` : '';
403
- const error = seat.lastError ? ` error=${seat.lastError}` : '';
404
- console.log(` ${seat.agentName}: ${seat.state} adapter=${seat.adapter || 'unknown'} model=${model}${effort} pid=${seat.pid || '-'} lastTurn=${seat.lastTurnAt || 'never'}${error}`);
405
- }
418
+ for (const seat of local.seats) console.log(formatSeatLine(seat));
406
419
  }
407
420
  }
408
421
  } catch (error) {
@@ -246,6 +246,22 @@ export const validateEnvironmentSpec = (spec) => {
246
246
  }
247
247
  }
248
248
 
249
+ // A TRANSPORT DECIDES THE ENTRY, so the fields must agree with it (TASK-071,
250
+ // Vera's ruling 2026-09-19). This block is MIRRORED, not shared: the other
251
+ // writer-side check is the backend's, in
252
+ // backend/utils/environmentSpecValidation.ts, called from the
253
+ // `PATCH /api/registry/pods/:podId/agents/:name` handler that stores
254
+ // `config.environment` for the owner's daemon. The two cannot share code at
255
+ // runtime — this is a published ESM package, that is the CJS backend — so
256
+ // the wording here and there is kept parallel deliberately and the two must
257
+ // move together. This one refuses a hand-written `--environment <file>`; the
258
+ // backend's refuses a stored row.
259
+ //
260
+ // The shape rule is not decoration: every adapter branches on it
261
+ // (isStdioServer/isHttpServer in adapters/pi-mcp-client.mjs), and `command`
262
+ // is an argv ARRAY — `connectStdioMcp` destructures `const [cmd, ...args] =
263
+ // command`. An entry whose fields contradict its transport is a record whose
264
+ // reader has to pick a winner, and the readers do not agree on which.
249
265
  if (spec.mcp !== undefined) {
250
266
  if (!Array.isArray(spec.mcp)) {
251
267
  errors.push('mcp must be an array');
@@ -261,6 +277,31 @@ export const validateEnvironmentSpec = (spec) => {
261
277
  if (server.transport !== undefined
262
278
  && !['http', 'stdio', 'sse'].includes(server.transport)) {
263
279
  errors.push(`mcp[${i}].transport must be one of: http, stdio, sse`);
280
+ // The agreement rule below is defined in terms of a transport this
281
+ // entry does not have; judging it here would mean inventing the
282
+ // second definition that rule exists to avoid.
283
+ return;
284
+ }
285
+ const argv = Array.isArray(server.command) && server.command.length > 0
286
+ && server.command.every((part) => typeof part === 'string' && part.trim().length > 0);
287
+ const hasUrl = typeof server.url === 'string' && server.url.trim().length > 0;
288
+ if (server.transport === 'http' || server.transport === 'sse') {
289
+ if (!hasUrl) {
290
+ errors.push(`mcp[${i}].url is required when transport is ${server.transport}: a ${server.transport} server is reached by URL, and this entry declares no URL to reach`);
291
+ }
292
+ if (server.command !== undefined) {
293
+ errors.push(`mcp[${i}].command must not be set when transport is ${server.transport}: with a url and a command in one entry each reader picks a different winner, so the record does not say what runs`);
294
+ }
295
+ } else {
296
+ // stdio, or absent transport — the historical default.
297
+ if (!argv) {
298
+ errors.push(server.command === undefined
299
+ ? `mcp[${i}].command is required for a stdio entry (and for an entry that declares no transport): with no command and no url there is nothing to run`
300
+ : `mcp[${i}].command must be a non-empty array of strings (argv, e.g. ["npx", "-y", "@commonlyai/mcp@latest"]); a string command is not a command line any reader in this repo executes`);
301
+ }
302
+ if (hasUrl) {
303
+ errors.push(`mcp[${i}].url must not be set for a stdio entry (and for an entry that declares no transport): a url here is a second, contradictory way to reach the server`);
304
+ }
264
305
  }
265
306
  });
266
307
  }