@proagentstore/cli 0.4.59 → 0.4.61

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.
@@ -159,10 +159,13 @@ export function switchRepoBranch(workDir, branch) {
159
159
  * `switchRepoBranch` follows and for the same reason: the cloud's picture of the tree is a read
160
160
  * taken moments earlier over a relay, and the write has to be safe against the tree as it IS.
161
161
  *
162
- * dirty refused. A fast-forward that touches a file with uncommitted edits is aborted by
163
- * git anyway, but one that does not touch it silently succeeds and leaves the diff
164
- * sitting on a base it was not written against. Refusing is the only answer that
165
- * cannot change what somebody's uncommitted work means.
162
+ * dirty NOT refused (#804 — it was at 0.4.59). Unlike a checkout, a fast-forward carries
163
+ * nothing across: an uncommitted edit to a file the incoming commits do not touch is
164
+ * the same edit afterwards, and one they DO touch makes git abort before writing a
165
+ * byte ("Your local changes … would be overwritten"). Untracked files likewise —
166
+ * left alone, unless an incoming commit adds that path, which git refuses by name.
167
+ * The check is therefore git's own, against the real history, and arrives as
168
+ * `error`. Nothing here stashes, cleans or commits on the owner's behalf.
166
169
  * off-branch refused when `branch` is given and HEAD is elsewhere. The declared branch is the
167
170
  * one the cloud judged stale; pulling whatever the checkout happens to be on would
168
171
  * be acting on a verdict about a different ref.
@@ -193,26 +196,26 @@ export function fastForwardRepo(workDir, opts = {}) {
193
196
  if (abbrev === "HEAD")
194
197
  return { ...base, refused: "detached" };
195
198
  const branch = abbrev;
199
+ // Read, reported, never a refusal (see the doc above). A status git will not give is `null`,
200
+ // not `false` — "clean" is a claim, and this is the field the owner's sentence is built from.
196
201
  let dirty;
197
202
  try {
198
203
  dirty = isDirty(workDir);
199
204
  }
200
- catch (e) {
201
- return { ...base, branch, error: gitFailureLine(e) };
205
+ catch {
206
+ dirty = null;
202
207
  }
203
- if (dirty)
204
- return { ...base, branch, dirty: true, refused: "dirty" };
205
208
  if (want !== null && branch !== want)
206
- return { ...base, branch, refused: "off-branch" };
209
+ return { ...base, branch, dirty, refused: "off-branch" };
207
210
  let upstream;
208
211
  try {
209
212
  upstream = git(workDir, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]).trim();
210
213
  }
211
214
  catch {
212
- return { ...base, branch, refused: "no-upstream" };
215
+ return { ...base, branch, dirty, refused: "no-upstream" };
213
216
  }
214
217
  if (!upstream)
215
- return { ...base, branch, refused: "no-upstream" };
218
+ return { ...base, branch, dirty, refused: "no-upstream" };
216
219
  let from;
217
220
  try {
218
221
  from = git(workDir, ["rev-parse", "HEAD"]).trim() || null;
@@ -226,9 +229,11 @@ export function fastForwardRepo(workDir, opts = {}) {
226
229
  git(workDir, gitWriteArgv("fast-forward"), { timeout: 30_000, env: networkGitEnv() });
227
230
  }
228
231
  catch (e) {
229
- // `--ff-only` refused, the network failed, or auth was needed and (correctly) not prompted
230
- // for. In every case git left the tree as it was; the sentence says which.
231
- return { ...base, branch, upstream, from, to: from, error: gitFailureLine(e) };
232
+ // `--ff-only` refused, an incoming commit would overwrite a local change or an untracked
233
+ // path, the network failed, or auth was needed and (correctly) not prompted for. In every
234
+ // case git left the tree as it was; the sentence says which, and names the file when there
235
+ // is one.
236
+ return { ...base, branch, upstream, from, to: from, dirty, error: gitFailureLine(e) };
232
237
  }
233
238
  // CONFIRM, do not assume — read HEAD back and count what arrived from git, not from the
234
239
  // exit code.
package/dist/index.js CHANGED
@@ -1220,6 +1220,18 @@ function shouldRegisterOnOpen(reconnect, alreadyRegistered) {
1220
1220
  function pendingRegistrations(attached, registered) {
1221
1221
  return [...attached].filter((id) => !registered.has(id));
1222
1222
  }
1223
+ function partitionByPin(instances, thisNode, alsoKnownAs = []) {
1224
+ const here = [];
1225
+ const elsewhere = [];
1226
+ for (const inst of instances) (isEligible(inst, thisNode, alsoKnownAs) ? here : elsewhere).push(inst);
1227
+ return { here, elsewhere };
1228
+ }
1229
+ function registrationStatus(attached, registered) {
1230
+ const ids = [...attached];
1231
+ const have = ids.filter((id) => registered.has(id)).length;
1232
+ const total = ids.length;
1233
+ return { agents: `${have}/${total}`, state: have === total ? "ok" : have === 0 ? "fail" : "partial" };
1234
+ }
1223
1235
  function instanceLabel(inst) {
1224
1236
  const short = `${inst.id.slice(0, 8)}\u2026`;
1225
1237
  return inst.name ? `${inst.name} (${short})` : short;
@@ -1289,14 +1301,13 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
1289
1301
  return false;
1290
1302
  }
1291
1303
  };
1304
+ const attached = /* @__PURE__ */ new Map();
1305
+ const blocked = /* @__PURE__ */ new Set();
1292
1306
  const reportRegistration = () => {
1293
- const agents = `${registered.size}/${instanceIds.length}`;
1294
- const state = registered.size === instanceIds.length ? "ok" : registered.size === 0 ? "fail" : "partial";
1307
+ const { agents, state } = registrationStatus(attached.keys(), registered);
1295
1308
  writeLine(formatStatusLine({ registration: state, agents, reason: state === "ok" ? void 0 : lastRegisterError }));
1296
1309
  };
1297
1310
  for (const id of instanceIds) await registerRuntime(id);
1298
- const attached = /* @__PURE__ */ new Map();
1299
- const blocked = /* @__PURE__ */ new Set();
1300
1311
  const attach = (id, label = `${id.slice(0, 8)}\u2026`) => {
1301
1312
  if (attached.has(id)) return;
1302
1313
  const mintToken = () => requestPags("POST", `/v1/relay/${apiPathSegment(id)}/token`, { ...opts, pagsToken }, {}).then((r) => r.token);
@@ -1339,7 +1350,8 @@ async function connectViaRelay(instanceIds, localUrl, runnerToken, opts, force =
1339
1350
  };
1340
1351
  for (const id of instanceIds) attach(id, "");
1341
1352
  reportRegistration();
1342
- writeLine(registered.size === instanceIds.length ? `Runtime registered with PAGS \u2713 (${registered.size}/${instanceIds.length} agents)` : `Runtime registration incomplete: ${registered.size}/${instanceIds.length} agents \u2014 retried on each relay (re)connect${watchInstances ? " and every 20s while this runs" : ""}.`);
1353
+ const startup = registrationStatus(attached.keys(), registered);
1354
+ writeLine(startup.state === "ok" ? `Runtime registered with PAGS \u2713 (${startup.agents} agents)` : `Runtime registration incomplete: ${startup.agents} agents \u2014 retried on each relay (re)connect${watchInstances ? " and every 20s while this runs" : ""}.`);
1343
1355
  writeLine("");
1344
1356
  writeLine("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550");
1345
1357
  writeLine(` \u2705 CONNECTED \u2014 WebSocket relay \xB7 ${hostname3()}`);
@@ -1717,6 +1729,7 @@ var runnerCommand = createRunnerCommand();
1717
1729
  // src/commands/up.ts
1718
1730
  import { createRequire as createRequire2 } from "module";
1719
1731
  import { Command as Command8 } from "commander";
1732
+ import { hostname as hostname5 } from "os";
1720
1733
 
1721
1734
  // src/tui.ts
1722
1735
  import chalk from "chalk";
@@ -1888,6 +1901,7 @@ var upCommand = new Command8("up").description("Start the browser runner for all
1888
1901
  const data = await res.json();
1889
1902
  const active = (data.instances || []).filter((i) => i.status === "active");
1890
1903
  let instances = active;
1904
+ let elsewhere = [];
1891
1905
  if (opts.instance) {
1892
1906
  instances = instances.filter((i) => i.id === opts.instance || i.slug === opts.instance);
1893
1907
  } else {
@@ -1904,16 +1918,31 @@ var upCommand = new Command8("up").description("Start the browser runner for all
1904
1918
  writeLine(" Subscribe to an agent at https://proagentstore.online");
1905
1919
  process.exit(1);
1906
1920
  }
1907
- state.instances = instances.map((i) => ({ id: i.id, name: i.name || i.slug || i.id.slice(0, 8) }));
1908
- printStep(`Found ${instances.length} instance${instances.length === 1 ? "" : "s"}`, "ok");
1909
- for (const inst of state.instances) {
1910
- writeLine(` ${inst.name} (${inst.id.slice(0, 8)}...)`);
1911
- }
1912
1921
  await maybeClaimMachineNames({
1913
1922
  token: session.token,
1914
1923
  apiBase: API_BASE3,
1915
1924
  headless: opts.headless
1916
1925
  }).catch(() => void 0);
1926
+ if (!opts.instance) {
1927
+ const split = partitionByPin(instances, hostname5(), loadMachineIdentity(hostname5()).names);
1928
+ instances = split.here;
1929
+ elsewhere = split.elsewhere;
1930
+ }
1931
+ if (instances.length === 0) {
1932
+ printStep(`All ${elsewhere.length} of your runtime agents are pinned to other machines`, "ok");
1933
+ for (const inst of elsewhere) writeLine(` ${inst.name || inst.slug || inst.id.slice(0, 8)} \u2192 ${inst.config?.runnerNode}`);
1934
+ writeLine(' They are served there. To run one from this machine, change its "Runs on" pin in the console, then `pags up` again.');
1935
+ process.exit(0);
1936
+ }
1937
+ state.instances = instances.map((i) => ({ id: i.id, name: i.name || i.slug || i.id.slice(0, 8) }));
1938
+ printStep(`Found ${instances.length} instance${instances.length === 1 ? "" : "s"}`, "ok");
1939
+ for (const inst of state.instances) {
1940
+ writeLine(` ${inst.name} (${inst.id.slice(0, 8)}...)`);
1941
+ }
1942
+ if (elsewhere.length) {
1943
+ printStep(`${elsewhere.length} more pinned to other machines \u2014 served there, not attached here`, "ok");
1944
+ for (const inst of elsewhere) writeLine(` ${inst.name || inst.slug || inst.id.slice(0, 8)} \u2192 ${inst.config?.runnerNode}`);
1945
+ }
1917
1946
  state.activeInstance = instances.length === 1 ? instances[0].name || instances[0].slug || instances[0].id.slice(0, 8) : `${instances.length} agents`;
1918
1947
  printStep(`Connecting ${state.activeInstance}\u2026`, "wait");
1919
1948
  await stopRunnerProcesses();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proagentstore/cli",
3
- "version": "0.4.59",
3
+ "version": "0.4.61",
4
4
  "description": "CLI for creating, publishing, and running ProAgentStore agents",
5
5
  "license": "MIT",
6
6
  "type": "module",