@phnx-labs/agents-cli 1.20.14 → 1.20.15

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.
@@ -23,7 +23,7 @@ function resolveRepoPath(target) {
23
23
  }
24
24
  return path.join(HOME, `.agents-${trimmed}`);
25
25
  }
26
- import { ensureAgentsDir, getExtraRepoDir, getSystemAgentsDir, getUserAgentsDir, readMeta, resolveExtraRepoDir, updateMeta, } from '../lib/state.js';
26
+ import { applyExtraAliasToVersions, ensureAgentsDir, getExtraRepoDir, getSystemAgentsDir, getUserAgentsDir, readMeta, resolveExtraRepoDir, updateMeta, } from '../lib/state.js';
27
27
  import { parseSource, pullRepo, commitAndPush, isGitRepo, isSystemRepoOrigin } from '../lib/git.js';
28
28
  import { DEFAULT_SYSTEM_REPO } from '../lib/types.js';
29
29
  import { ALL_AGENT_IDS, isAgentName, resolveAgentName } from '../lib/agents.js';
@@ -259,6 +259,7 @@ export function registerRepoCommands(program) {
259
259
  const commit = log.latest?.hash.slice(0, 8) || 'unknown';
260
260
  extras[alias] = { url: targetDir, path: targetDir, enabled: true };
261
261
  updateMeta({ extraRepos: extras });
262
+ syncExtraAliasAcrossVersions(alias, true);
262
263
  spinner.succeed(`Created ${targetDir} (${commit})`);
263
264
  console.log(chalk.gray(`\nRegistered as "${alias}". Edit files there, then add your own git remote when ready.`));
264
265
  }
@@ -306,6 +307,7 @@ export function registerRepoCommands(program) {
306
307
  if (parsed.type === 'local') {
307
308
  extras[alias] = { url: parsed.url, path: parsed.url, enabled: true };
308
309
  updateMeta({ extraRepos: extras });
310
+ syncExtraAliasAcrossVersions(alias, true);
309
311
  syncMarketplacesForDefaults();
310
312
  console.log(chalk.green(`Registered local repo "${alias}" -> ${parsed.url}`));
311
313
  return;
@@ -342,6 +344,7 @@ export function registerRepoCommands(program) {
342
344
  }
343
345
  extras[alias] = { url: parsed.url, path: targetDir, enabled: true };
344
346
  updateMeta({ extraRepos: extras });
347
+ syncExtraAliasAcrossVersions(alias, true);
345
348
  syncMarketplacesForDefaults();
346
349
  console.log(chalk.gray(`\nRegistered as "${alias}". Skills and commands from this repo will be`));
347
350
  console.log(chalk.gray(`picked up automatically the next time you launch any agent.`));
@@ -379,6 +382,7 @@ export function registerRepoCommands(program) {
379
382
  }
380
383
  delete extras[alias];
381
384
  updateMeta({ extraRepos: extras });
385
+ syncExtraAliasAcrossVersions(alias, false);
382
386
  syncMarketplacesForDefaults();
383
387
  console.log(chalk.green(`Removed "${alias}"`));
384
388
  });
@@ -592,6 +596,19 @@ function collectRepoTargets(alias) {
592
596
  }
593
597
  return [found];
594
598
  }
599
+ /**
600
+ * Keep already-installed versions' selectors in sync with an extra-repo change:
601
+ * add `<alias>:*` when the repo is registered/enabled, strip it when removed.
602
+ * Newly-installed versions inherit it from `defaultPatterns()` at scaffold time,
603
+ * so without this a repo added after install is invisible to existing versions.
604
+ */
605
+ function syncExtraAliasAcrossVersions(alias, add) {
606
+ const n = applyExtraAliasToVersions(alias, add);
607
+ if (n > 0) {
608
+ const verb = add ? 'Added to' : 'Removed from';
609
+ console.log(chalk.gray(`${verb} ${n} existing version selector${n === 1 ? '' : 's'}.`));
610
+ }
611
+ }
595
612
  async function toggle(alias, enabled) {
596
613
  const meta = readMeta();
597
614
  const extras = { ...(meta.extraRepos || {}) };
@@ -606,6 +623,10 @@ async function toggle(alias, enabled) {
606
623
  }
607
624
  extras[alias] = { ...extras[alias], enabled };
608
625
  updateMeta({ extraRepos: extras });
626
+ // Re-enabling backfills the alias into existing versions; disabling leaves the
627
+ // selectors (resolution skips disabled extras) so a later enable is a no-op.
628
+ if (enabled)
629
+ syncExtraAliasAcrossVersions(alias, true);
609
630
  syncMarketplacesForDefaults();
610
631
  console.log(chalk.green(`${enabled ? 'Enabled' : 'Disabled'} "${alias}"`));
611
632
  }
@@ -1701,24 +1701,34 @@ export class BrowserService {
1701
1701
  targetId: tabId,
1702
1702
  flatten: true,
1703
1703
  }));
1704
- // Inject a one-shot stealth shim before any page script runs. Chromium
1705
- // unconditionally exposes navigator.webdriver = true when a remote-debug
1706
- // transport is attached; Cloudflare Turnstile, hCaptcha, and similar bot
1707
- // checks read that property first. For browsers agents-cli spawns the
1708
- // --disable-blink-features=AutomationControlled launch flag already
1709
- // covers this, but for attach-to-running profiles (the Comet / Arc /
1710
- // Brave case where the user launched the browser themselves) the flag
1711
- // is unavailable Page.addScriptToEvaluateOnNewDocument is the only
1712
- // lever. Non-page targets (workers, service workers) will reject these
1713
- // calls; we swallow the error and keep going.
1714
- try {
1715
- await conn.cdp.send('Page.enable', {}, sessionId);
1716
- await conn.cdp.send('Page.addScriptToEvaluateOnNewDocument', {
1717
- source: "Object.defineProperty(navigator,'webdriver',{get:()=>undefined});",
1718
- }, sessionId);
1719
- }
1720
- catch {
1721
- // Target doesn't support Page domain nothing to inject.
1704
+ // Inject a stealth shim before any page script runs. Chromium exposes
1705
+ // navigator.webdriver = true whenever a remote-debug transport is attached;
1706
+ // Cloudflare Turnstile, hCaptcha, and similar bot checks read it first.
1707
+ //
1708
+ // Only attach-to-running profiles (conn.pid === 0 — Comet / Arc / Brave the
1709
+ // user launched themselves) need this. Browsers agents-cli spawns already
1710
+ // carry the --disable-blink-features=AutomationControlled launch flag, which
1711
+ // makes navigator.webdriver a native Navigator.prototype getter returning
1712
+ // false indistinguishable from an untouched browser. Injecting on top of
1713
+ // that is actively harmful: it defines an OWN getter on the instance, and an
1714
+ // own `webdriver` descriptor (native lives on the prototype) returning
1715
+ // `undefined` (native returns `false`) is itself a tampering signal that
1716
+ // bot.sannysoft.com and similar tests flag as "WebDriver present".
1717
+ //
1718
+ // When we do inject (attach mode), mirror native semantics exactly: define
1719
+ // on Navigator.prototype and return false, so no own descriptor leaks and
1720
+ // the value matches a real browser. Non-page targets (workers, service
1721
+ // workers) reject these calls; swallow the error and keep going.
1722
+ if (conn.pid === 0) {
1723
+ try {
1724
+ await conn.cdp.send('Page.enable', {}, sessionId);
1725
+ await conn.cdp.send('Page.addScriptToEvaluateOnNewDocument', {
1726
+ source: "Object.defineProperty(Navigator.prototype,'webdriver',{get:()=>false,configurable:true});",
1727
+ }, sessionId);
1728
+ }
1729
+ catch {
1730
+ // Target doesn't support Page domain — nothing to inject.
1731
+ }
1722
1732
  }
1723
1733
  conn.sessionCache.set(tabId, sessionId);
1724
1734
  return sessionId;
package/dist/lib/shims.js CHANGED
@@ -685,7 +685,10 @@ export function ensureVersionedAliasCurrent(agent, version) {
685
685
  createVersionedAlias(agent, version);
686
686
  return 'created';
687
687
  }
688
- if (!isVersionedAliasCurrent(agent, version)) {
688
+ // Upgrade-only (newest-wins), same rationale as ensureShimCurrent: never
689
+ // downgrade an alias stamped by a newer install sharing the shims dir.
690
+ const onDisk = readVersionedAliasSchemaVersion(agent, version);
691
+ if (onDisk === null || onDisk < VERSIONED_ALIAS_SCHEMA_VERSION) {
689
692
  createVersionedAlias(agent, version);
690
693
  return 'updated';
691
694
  }
@@ -1278,7 +1281,14 @@ export function ensureShimCurrent(agent) {
1278
1281
  createShim(agent);
1279
1282
  return 'created';
1280
1283
  }
1281
- if (!isShimCurrent(agent)) {
1284
+ // Upgrade-only (newest-wins): regenerate only when the on-disk shim is
1285
+ // unversioned/unreadable (null) or OLDER than this binary. Never downgrade a
1286
+ // shim stamped by a NEWER agents-cli install. Two installs at different
1287
+ // SHIM_SCHEMA_VERSION sharing ~/.agents/.cache/shims/ (e.g. a dev build on
1288
+ // PATH alongside a Hermes-bundled published copy) otherwise ping-pong —
1289
+ // rewriting every shim on each alternating launch and adding boot latency.
1290
+ const onDisk = readShimSchemaVersion(agent);
1291
+ if (onDisk === null || onDisk < SHIM_SCHEMA_VERSION) {
1282
1292
  createShim(agent);
1283
1293
  return 'updated';
1284
1294
  }
@@ -247,6 +247,24 @@ export declare function recordVersionResources(_agent: AgentId, _version: string
247
247
  * Pass all resource types you want to initialize in one call to batch the write.
248
248
  */
249
249
  export declare function ensureVersionResourcePatterns(agent: AgentId, version: string, updates: Partial<Record<Exclude<keyof VersionResources, 'rulesPreset'>, ResourcePattern[]>>): void;
250
+ /**
251
+ * Insert `<alias>:*` at the canonical position (after the system/user/other-extra
252
+ * includes, before `project:*`), unless the alias is already referenced — as an
253
+ * include (`alias:...`) or an exclude (`!alias:...`). Returns a new array when it
254
+ * changes, otherwise the same reference (so callers can detect no-ops cheaply).
255
+ */
256
+ export declare function withAlias(list: ResourcePattern[], alias: string): ResourcePattern[];
257
+ /** Strip every reference to `<alias>:...` / `!<alias>:...` from a selector list. */
258
+ export declare function withoutAlias(list: ResourcePattern[], alias: string): ResourcePattern[];
259
+ /**
260
+ * Backfill (add=true) or strip (add=false) an extra-repo alias across every
261
+ * already-installed version's selectors. New versions get the alias via
262
+ * `defaultPatterns()` at scaffold time; this keeps existing versions in sync
263
+ * when an extra repo is registered/enabled or removed. Only touches selector
264
+ * lists that are already set — an unset list is left for `defaultPatterns()`.
265
+ * Returns the number of (agent, version) pairs changed.
266
+ */
267
+ export declare function applyExtraAliasToVersions(alias: string, add: boolean): number;
250
268
  export declare function getVersionResources(agent: AgentId, version: string): VersionResources | null;
251
269
  /** Active rules preset for an agent@version. Defaults to "default" when unset. */
252
270
  export declare function getActiveRulesPreset(agent: AgentId, version: string): string;
package/dist/lib/state.js CHANGED
@@ -711,6 +711,79 @@ export function ensureVersionResourcePatterns(agent, version, updates) {
711
711
  if (changed)
712
712
  writeMeta(meta);
713
713
  }
714
+ /**
715
+ * Resource types that resolve across the extra-repo layer. Mirrors
716
+ * `defaultPatterns()`: extras feed commands/skills/hooks/subagents/plugins/
717
+ * workflows, but never permissions (`system:*`) or mcp (`user:*`).
718
+ */
719
+ const EXTRA_ELIGIBLE_TYPES = [
720
+ 'commands', 'skills', 'hooks', 'subagents', 'plugins', 'workflows',
721
+ ];
722
+ /**
723
+ * Insert `<alias>:*` at the canonical position (after the system/user/other-extra
724
+ * includes, before `project:*`), unless the alias is already referenced — as an
725
+ * include (`alias:...`) or an exclude (`!alias:...`). Returns a new array when it
726
+ * changes, otherwise the same reference (so callers can detect no-ops cheaply).
727
+ */
728
+ export function withAlias(list, alias) {
729
+ const prefix = `${alias}:`;
730
+ if (list.some(p => p === `${alias}:*` || p.startsWith(prefix) || p.startsWith(`!${prefix}`))) {
731
+ return list;
732
+ }
733
+ const next = [...list];
734
+ const projIdx = next.findIndex(p => p === 'project:*' || p.startsWith('project:'));
735
+ if (projIdx >= 0)
736
+ next.splice(projIdx, 0, `${alias}:*`);
737
+ else
738
+ next.push(`${alias}:*`);
739
+ return next;
740
+ }
741
+ /** Strip every reference to `<alias>:...` / `!<alias>:...` from a selector list. */
742
+ export function withoutAlias(list, alias) {
743
+ const prefix = `${alias}:`;
744
+ const next = list.filter(p => !(p.startsWith(prefix) || p.startsWith(`!${prefix}`)));
745
+ return next.length === list.length ? list : next;
746
+ }
747
+ /**
748
+ * Backfill (add=true) or strip (add=false) an extra-repo alias across every
749
+ * already-installed version's selectors. New versions get the alias via
750
+ * `defaultPatterns()` at scaffold time; this keeps existing versions in sync
751
+ * when an extra repo is registered/enabled or removed. Only touches selector
752
+ * lists that are already set — an unset list is left for `defaultPatterns()`.
753
+ * Returns the number of (agent, version) pairs changed.
754
+ */
755
+ export function applyExtraAliasToVersions(alias, add) {
756
+ const meta = readMeta();
757
+ if (!meta.versions)
758
+ return 0;
759
+ let changed = false;
760
+ let count = 0;
761
+ for (const versions of Object.values(meta.versions)) {
762
+ if (!versions)
763
+ continue;
764
+ for (const vr of Object.values(versions)) {
765
+ if (!vr)
766
+ continue;
767
+ let touched = false;
768
+ for (const type of EXTRA_ELIGIBLE_TYPES) {
769
+ const cur = vr[type];
770
+ if (!Array.isArray(cur) || cur.length === 0)
771
+ continue;
772
+ const next = add ? withAlias(cur, alias) : withoutAlias(cur, alias);
773
+ if (next !== cur) {
774
+ vr[type] = next;
775
+ touched = true;
776
+ changed = true;
777
+ }
778
+ }
779
+ if (touched)
780
+ count++;
781
+ }
782
+ }
783
+ if (changed)
784
+ writeMeta(meta);
785
+ return count;
786
+ }
714
787
  export function getVersionResources(agent, version) {
715
788
  const meta = readMeta();
716
789
  return meta.versions?.[agent]?.[version] || null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.20.14",
3
+ "version": "1.20.15",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",