@phnx-labs/agents-cli 1.22.50 → 1.22.52
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/CHANGELOG.md +219 -0
- package/dist/bootstrap.js +1 -1
- package/dist/commands/attach.js +7 -0
- package/dist/commands/browser.d.ts +10 -0
- package/dist/commands/browser.js +191 -54
- package/dist/commands/config.js +20 -0
- package/dist/commands/daemon.d.ts +2 -0
- package/dist/commands/daemon.js +8 -4
- package/dist/commands/detach.js +1 -1
- package/dist/commands/feedback.js +1 -1
- package/dist/commands/focus.d.ts +1 -10
- package/dist/commands/focus.js +14 -79
- package/dist/commands/go.d.ts +26 -0
- package/dist/commands/go.js +63 -5
- package/dist/commands/menubar.js +6 -4
- package/dist/commands/monitors.js +1 -1
- package/dist/commands/repo.js +31 -3
- package/dist/commands/sessions-resume.d.ts +1 -0
- package/dist/commands/sessions-resume.js +13 -2
- package/dist/commands/sessions-stop.js +1 -1
- package/dist/commands/sessions.d.ts +23 -13
- package/dist/commands/sessions.js +40 -20
- package/dist/commands/setup-browser.d.ts +5 -2
- package/dist/commands/setup-browser.js +14 -29
- package/dist/commands/setup-preferences.d.ts +22 -3
- package/dist/commands/setup-preferences.js +25 -8
- package/dist/commands/share.js +12 -8
- package/dist/commands/status.js +5 -0
- package/dist/commands/sync.js +58 -2
- package/dist/commands/tmux.d.ts +8 -1
- package/dist/commands/tmux.js +167 -17
- package/dist/commands/traces.js +32 -4
- package/dist/lib/browser/ipc.d.ts +44 -0
- package/dist/lib/browser/ipc.js +120 -8
- package/dist/lib/browser/profiles.d.ts +39 -17
- package/dist/lib/browser/profiles.js +51 -52
- package/dist/lib/browser/runtime-state.d.ts +4 -2
- package/dist/lib/browser/runtime-state.js +4 -2
- package/dist/lib/browser/service.js +4 -3
- package/dist/lib/channels/owner-forward.d.ts +88 -0
- package/dist/lib/channels/owner-forward.js +116 -0
- package/dist/lib/channels/owner-sink.js +7 -0
- package/dist/lib/claude-statusline.d.ts +9 -0
- package/dist/lib/claude-statusline.js +45 -4
- package/dist/lib/computer/ssh-tunnel.d.ts +7 -6
- package/dist/lib/computer/ssh-tunnel.js +13 -8
- package/dist/lib/config-keys.d.ts +4 -3
- package/dist/lib/config-keys.js +9 -2
- package/dist/lib/device-config.js +23 -0
- package/dist/lib/exec.d.ts +8 -0
- package/dist/lib/exec.js +7 -0
- package/dist/lib/factory/snapshot.d.ts +1 -1
- package/dist/lib/factory/snapshot.js +1 -1
- package/dist/lib/feed-broadcast.js +15 -1
- package/dist/lib/git.d.ts +93 -0
- package/dist/lib/git.js +232 -0
- package/dist/lib/helper-download.d.ts +12 -2
- package/dist/lib/helper-download.js +12 -2
- package/dist/lib/installations/migrate.js +4 -4
- package/dist/lib/menubar/install-menubar.d.ts +71 -8
- package/dist/lib/menubar/install-menubar.js +183 -24
- package/dist/lib/monitors/remote.d.ts +18 -1
- package/dist/lib/monitors/remote.js +15 -2
- package/dist/lib/notify.d.ts +7 -0
- package/dist/lib/notify.js +15 -1
- package/dist/lib/session/local-tmux-attach.d.ts +69 -0
- package/dist/lib/session/local-tmux-attach.js +164 -0
- package/dist/lib/session/remote-active.d.ts +8 -0
- package/dist/lib/session/remote-active.js +1 -0
- package/dist/lib/share/publish.d.ts +8 -11
- package/dist/lib/share/publish.js +16 -20
- package/dist/lib/share/worker-template.js +99 -12
- package/dist/lib/star-nudge.d.ts +2 -2
- package/dist/lib/star-nudge.js +2 -2
- package/dist/lib/state.d.ts +1 -1
- package/dist/lib/state.js +2 -2
- package/dist/lib/sync-status.d.ts +17 -0
- package/dist/lib/sync-status.js +21 -2
- package/dist/lib/tmux/index.d.ts +1 -1
- package/dist/lib/tmux/index.js +1 -1
- package/dist/lib/tmux/session.d.ts +10 -0
- package/dist/lib/tmux/session.js +29 -0
- package/dist/lib/traces/sync.d.ts +30 -0
- package/dist/lib/traces/sync.js +91 -10
- package/package.json +3 -3
package/dist/lib/git.js
CHANGED
|
@@ -829,6 +829,238 @@ export async function adoptRepo(source, targetDir) {
|
|
|
829
829
|
return { success: false, commit: '', backedUp: [], error: err.message };
|
|
830
830
|
}
|
|
831
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* Device-local record of the user config repo's remote URL, kept OUTSIDE the git
|
|
834
|
+
* tree so it survives a lost `.git` (`.history/` is gitignored runtime state).
|
|
835
|
+
* This is what lets `agents repo sync user` adopt-in-place a box that was healthy
|
|
836
|
+
* once and later lost its checkout, without the operator re-typing the URL.
|
|
837
|
+
*/
|
|
838
|
+
function userRepoRemoteRecordPath(dir) {
|
|
839
|
+
return path.join(dir, '.history', 'user-repo-remote.json');
|
|
840
|
+
}
|
|
841
|
+
/** Read an origin remote URL from a git dir, or null when there is none. */
|
|
842
|
+
export function readOriginUrl(dir) {
|
|
843
|
+
if (!isGitRepo(dir))
|
|
844
|
+
return null;
|
|
845
|
+
try {
|
|
846
|
+
const url = execFileSync('git', ['-C', dir, 'config', '--get', 'remote.origin.url'], {
|
|
847
|
+
encoding: 'utf-8',
|
|
848
|
+
}).trim();
|
|
849
|
+
return url || null;
|
|
850
|
+
}
|
|
851
|
+
catch {
|
|
852
|
+
return null;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Persist the user repo's remote URL to device-local runtime state so a future
|
|
857
|
+
* adopt-in-place can recover it after a `.git` loss. Best-effort — a write
|
|
858
|
+
* failure never blocks a sync.
|
|
859
|
+
*/
|
|
860
|
+
export function recordUserRepoRemote(dir, url) {
|
|
861
|
+
try {
|
|
862
|
+
const file = userRepoRemoteRecordPath(dir);
|
|
863
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
864
|
+
fs.writeFileSync(file, JSON.stringify({ url }, null, 2) + '\n', { mode: 0o600 });
|
|
865
|
+
}
|
|
866
|
+
catch {
|
|
867
|
+
/* runtime cache write is best-effort */
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Resolve the user config repo's remote URL WITHOUT hardcoding it, for the
|
|
872
|
+
* adopt-in-place self-heal. In priority order:
|
|
873
|
+
* 1. an existing `origin` remote on the dir (a partial repo that kept its
|
|
874
|
+
* `.git` but drifted) — the same source `agents repo sync` already reads;
|
|
875
|
+
* 2. the `AGENTS_USER_REPO_URL` env override (a fresh/never-cloned box);
|
|
876
|
+
* 3. the device-local record written by a prior healthy sync (a box that lost
|
|
877
|
+
* its `.git` but kept `.history/` runtime state).
|
|
878
|
+
* Returns null when none is known — the caller then guides the operator to
|
|
879
|
+
* `agents repo pull user <git-url>` instead of crashing.
|
|
880
|
+
*/
|
|
881
|
+
export function resolveUserRepoRemoteUrl(dir) {
|
|
882
|
+
const fromOrigin = readOriginUrl(dir);
|
|
883
|
+
if (fromOrigin)
|
|
884
|
+
return fromOrigin;
|
|
885
|
+
const fromEnv = process.env.AGENTS_USER_REPO_URL?.trim();
|
|
886
|
+
if (fromEnv)
|
|
887
|
+
return fromEnv;
|
|
888
|
+
try {
|
|
889
|
+
const raw = fs.readFileSync(userRepoRemoteRecordPath(dir), 'utf-8');
|
|
890
|
+
const url = JSON.parse(raw).url?.trim();
|
|
891
|
+
if (url)
|
|
892
|
+
return url;
|
|
893
|
+
}
|
|
894
|
+
catch {
|
|
895
|
+
/* no record yet */
|
|
896
|
+
}
|
|
897
|
+
return null;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
900
|
+
* Decide whether a local top-level `agents.yaml` is a stale install stub that
|
|
901
|
+
* should be restored from the committed copy, vs. a legitimately customized file
|
|
902
|
+
* that must be preserved.
|
|
903
|
+
*
|
|
904
|
+
* The stub a partial install leaves behind (createDefaultMeta + a few config
|
|
905
|
+
* writes) is strictly SHORTER than the committed config AND missing whole
|
|
906
|
+
* top-level blocks the committed one carries (`config:` / `hooks:` — the fleet
|
|
907
|
+
* browser hub and hook registrations). Device-specific settings live in
|
|
908
|
+
* `devices/<host>/agents.yaml`, never here, so restoring the top-level file is
|
|
909
|
+
* safe. A file that already carries those blocks (or is longer) is treated as a
|
|
910
|
+
* real local edit and left alone — it surfaces as a modified path instead.
|
|
911
|
+
*/
|
|
912
|
+
export function isStaleAgentsYamlStub(local, committed) {
|
|
913
|
+
if (local.trim() === committed.trim())
|
|
914
|
+
return false;
|
|
915
|
+
const shorter = local.split('\n').length < committed.split('\n').length;
|
|
916
|
+
const missingBlock = (/^config:/m.test(committed) && !/^config:/m.test(local)) ||
|
|
917
|
+
(/^hooks:/m.test(committed) && !/^hooks:/m.test(local));
|
|
918
|
+
return shorter && missingBlock;
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Adopt an EXISTING, non-git (or origin-less) `~/.agents` directory in place —
|
|
922
|
+
* git-back it against its remote WITHOUT re-cloning and WITHOUT destroying the
|
|
923
|
+
* runtime state it carries (`.cache` / `.history` / `scratch` / `.system`, all
|
|
924
|
+
* gitignored). The self-heal for a partial install (PHNX-3301): the current code
|
|
925
|
+
* hard-fails with "Not a git repo", and the only manual fix is a destructive
|
|
926
|
+
* re-clone that wipes that runtime state.
|
|
927
|
+
*
|
|
928
|
+
* Plumbing-only, so it never trips the fleet git-guard (no `reset` / `checkout
|
|
929
|
+
* <branch>` / `stash` / `git config`):
|
|
930
|
+
* 1. `git init` + point HEAD at `main`.
|
|
931
|
+
* 2. `git remote add origin <url>`.
|
|
932
|
+
* 3. `git fetch origin main`.
|
|
933
|
+
* 4. `git update-ref refs/heads/main origin/main`; set upstream to origin/main.
|
|
934
|
+
* 5. `git read-tree origin/main` — index = origin/main, working tree untouched.
|
|
935
|
+
* 6. Materialize only the tracked files MISSING from the working tree
|
|
936
|
+
* (`checkout-index` on that set) — existing local files are never overwritten.
|
|
937
|
+
* 7. Reconcile the top-level `agents.yaml`: restore it from origin/main only
|
|
938
|
+
* when the local copy is a stale stub ({@link isStaleAgentsYamlStub}).
|
|
939
|
+
*
|
|
940
|
+
* Idempotent: a second run finds the remote/refs already present and simply
|
|
941
|
+
* re-materializes nothing. Any tracked path with real local edits is returned in
|
|
942
|
+
* `localEdits` (surfaced, never clobbered).
|
|
943
|
+
*/
|
|
944
|
+
export async function adoptRepoInPlace(dir, remoteUrl) {
|
|
945
|
+
const empty = {
|
|
946
|
+
success: false,
|
|
947
|
+
commit: '',
|
|
948
|
+
materialized: 0,
|
|
949
|
+
reconciledAgentsYaml: false,
|
|
950
|
+
localEdits: [],
|
|
951
|
+
};
|
|
952
|
+
const trimmed = remoteUrl.trim();
|
|
953
|
+
try {
|
|
954
|
+
// The URL is always a resolved git remote (origin / env / record), never a
|
|
955
|
+
// `gh:` shorthand — so skip parseSource (which THROWS on ssh:// and rewrites
|
|
956
|
+
// git@github -> https, breaking SSH-key-only auth). assertSafeGitTransport
|
|
957
|
+
// still blocks the dangerous transports (ext::, file://, option injection)
|
|
958
|
+
// while permitting https / ssh / scp-style / a local bare repo.
|
|
959
|
+
assertSafeGitTransport(trimmed);
|
|
960
|
+
if (!fs.existsSync(dir)) {
|
|
961
|
+
return { ...empty, error: `Target directory does not exist: ${dir}` };
|
|
962
|
+
}
|
|
963
|
+
// Non-interactive git — fail fast on a missing credential instead of hanging
|
|
964
|
+
// on a prompt (same rationale as adoptRepo).
|
|
965
|
+
process.env.GIT_TERMINAL_PROMPT = '0';
|
|
966
|
+
const git = simpleGit(dir);
|
|
967
|
+
// 1. init + HEAD -> main (idempotent: init on an existing repo is a no-op).
|
|
968
|
+
// Set HEAD via symbolic-ref rather than `init -b main` so it works on git
|
|
969
|
+
// < 2.28, and lands on `main` even if the repo already initialized as
|
|
970
|
+
// `master`.
|
|
971
|
+
if (!isGitRepo(dir))
|
|
972
|
+
await git.init();
|
|
973
|
+
await git.raw(['symbolic-ref', 'HEAD', 'refs/heads/main']);
|
|
974
|
+
// 2. origin — add only when absent, so a re-run keeps the existing remote.
|
|
975
|
+
const remotes = await git.getRemotes(true);
|
|
976
|
+
if (!remotes.some((r) => r.name === 'origin')) {
|
|
977
|
+
await git.raw(['remote', 'add', 'origin', trimmed]);
|
|
978
|
+
}
|
|
979
|
+
// 3-4. fetch, plant the local main on origin/main, set upstream.
|
|
980
|
+
await git.raw(['fetch', 'origin', 'main']);
|
|
981
|
+
await git.raw(['update-ref', 'refs/heads/main', 'origin/main']);
|
|
982
|
+
await git.raw(['branch', '--set-upstream-to=origin/main', 'main']);
|
|
983
|
+
// 5. index = origin/main, working tree untouched.
|
|
984
|
+
await git.raw(['read-tree', 'origin/main']);
|
|
985
|
+
// 6. Materialize only the tracked files MISSING on disk. Passing the explicit
|
|
986
|
+
// missing set (never `checkout-index -a`) guarantees no existing local
|
|
987
|
+
// file — a stub agents.yaml, a modified rule — is overwritten. Chunked to
|
|
988
|
+
// stay under the argv limit on a cold box where most files are missing.
|
|
989
|
+
const tracked = (await git.raw(['ls-files', '-z'])).split('\0').filter(Boolean);
|
|
990
|
+
const missing = tracked.filter((rel) => !fs.existsSync(path.join(dir, rel)));
|
|
991
|
+
for (let i = 0; i < missing.length; i += 500) {
|
|
992
|
+
await git.raw(['checkout-index', '-f', '--', ...missing.slice(i, i + 500)]);
|
|
993
|
+
}
|
|
994
|
+
// 7. Reconcile a stale-stub top-level agents.yaml from origin/main. `restore`
|
|
995
|
+
// is plumbing the git-guard allows; it rewrites only this one path.
|
|
996
|
+
let reconciledAgentsYaml = false;
|
|
997
|
+
let agentsYamlBackup;
|
|
998
|
+
if (tracked.includes('agents.yaml')) {
|
|
999
|
+
const abs = path.join(dir, 'agents.yaml');
|
|
1000
|
+
const local = fs.existsSync(abs) ? fs.readFileSync(abs, 'utf-8') : '';
|
|
1001
|
+
const committed = await git.raw(['show', 'origin/main:agents.yaml']);
|
|
1002
|
+
if (isStaleAgentsYamlStub(local, committed)) {
|
|
1003
|
+
// The stub heuristic can't perfectly distinguish a partial-install stub
|
|
1004
|
+
// from a user who deliberately removed a whole block, so save the local
|
|
1005
|
+
// copy to gitignored runtime state BEFORE restoring — a false positive is
|
|
1006
|
+
// then recoverable and surfaced, never silent data loss.
|
|
1007
|
+
if (local) {
|
|
1008
|
+
agentsYamlBackup = path.join(dir, '.history', 'agents.yaml.pre-adopt.bak');
|
|
1009
|
+
fs.mkdirSync(path.dirname(agentsYamlBackup), { recursive: true });
|
|
1010
|
+
fs.writeFileSync(agentsYamlBackup, local);
|
|
1011
|
+
}
|
|
1012
|
+
await git.raw(['restore', '--source=origin/main', '--', 'agents.yaml']);
|
|
1013
|
+
reconciledAgentsYaml = true;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
// Surface — never silently keep — any tracked path whose local copy still
|
|
1017
|
+
// differs from origin/main after the reconcile (real un-gitignored edits).
|
|
1018
|
+
const dirty = (await git.raw(['status', '--porcelain', '--untracked-files=no']))
|
|
1019
|
+
.split('\n')
|
|
1020
|
+
.map((l) => l.slice(3).trim())
|
|
1021
|
+
.filter(Boolean);
|
|
1022
|
+
installGithooksSymlinks(dir);
|
|
1023
|
+
recordUserRepoRemote(dir, trimmed);
|
|
1024
|
+
const commit = (await git.raw(['rev-parse', '--short', 'HEAD'])).trim();
|
|
1025
|
+
return {
|
|
1026
|
+
success: true,
|
|
1027
|
+
commit,
|
|
1028
|
+
materialized: missing.length,
|
|
1029
|
+
reconciledAgentsYaml,
|
|
1030
|
+
...(agentsYamlBackup ? { agentsYamlBackup } : {}),
|
|
1031
|
+
localEdits: dirty,
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
catch (err) {
|
|
1035
|
+
return { ...empty, error: err.message };
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
/**
|
|
1039
|
+
* Self-heal entry point for the USER config repo: when `dir` is not a git repo
|
|
1040
|
+
* (or is a repo with no `origin`), resolve its remote URL and adopt it in place;
|
|
1041
|
+
* otherwise return null (nothing to adopt — the normal sync path runs). Returns a
|
|
1042
|
+
* failed result carrying `needsUrl` when the URL cannot be resolved, so the
|
|
1043
|
+
* caller can print the `agents repo pull user <git-url>` remediation instead of
|
|
1044
|
+
* the old "Not a git repo" crash.
|
|
1045
|
+
*/
|
|
1046
|
+
export async function adoptUserRepoIfNeeded(dir, opts = {}) {
|
|
1047
|
+
const hasOrigin = isGitRepo(dir) && readOriginUrl(dir) !== null;
|
|
1048
|
+
if (hasOrigin)
|
|
1049
|
+
return null;
|
|
1050
|
+
const url = opts.explicitUrl?.trim() || resolveUserRepoRemoteUrl(dir);
|
|
1051
|
+
if (!url) {
|
|
1052
|
+
return {
|
|
1053
|
+
success: false,
|
|
1054
|
+
commit: '',
|
|
1055
|
+
materialized: 0,
|
|
1056
|
+
reconciledAgentsYaml: false,
|
|
1057
|
+
localEdits: [],
|
|
1058
|
+
needsUrl: true,
|
|
1059
|
+
error: `${displayHomePath(dir)} is not a git repo and no remote URL is known.`,
|
|
1060
|
+
};
|
|
1061
|
+
}
|
|
1062
|
+
return adoptRepoInPlace(dir, url);
|
|
1063
|
+
}
|
|
832
1064
|
/**
|
|
833
1065
|
* Check if the repo's origin points to the system repo.
|
|
834
1066
|
*/
|
|
@@ -16,8 +16,18 @@
|
|
|
16
16
|
* lands for both helpers at once.
|
|
17
17
|
*/
|
|
18
18
|
import { type HelperName } from './helper-versions.js';
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* GitHub repo whose `<helper>/v<version>` releases carry the helper assets.
|
|
21
|
+
*
|
|
22
|
+
* This is the SLUG, which is not the npm package name: the package is still
|
|
23
|
+
* `@phnx-labs/agents-cli`, but the repository was renamed to `agi-cli`. The old
|
|
24
|
+
* slug kept working only because GitHub redirects a renamed repo, which is a
|
|
25
|
+
* poor thing to hang signed-binary delivery on — a redirect is one re-created
|
|
26
|
+
* repo away from resolving somewhere else. What actually protects the download
|
|
27
|
+
* is the sha256 + codesign + designated-requirement + Team ID verification
|
|
28
|
+
* below; this just stops relying on the redirect.
|
|
29
|
+
*/
|
|
30
|
+
export declare const HELPER_RELEASE_REPO = "phnx-labs/agi-cli";
|
|
21
31
|
/** Apple Developer ID Team every helper must be signed by ("Developer ID
|
|
22
32
|
* Application: Muqit Nawaz"). Defense in depth on top of `spctl` notarization. */
|
|
23
33
|
export declare const EXPECTED_TEAM_ID = "2HTP252L87";
|
|
@@ -23,8 +23,18 @@ import { pipeline } from 'node:stream/promises';
|
|
|
23
23
|
import { getCacheDir } from './state.js';
|
|
24
24
|
import { parseSha256Asset, sha256File } from './sha256-asset.js';
|
|
25
25
|
import { helperTag } from './helper-versions.js';
|
|
26
|
-
/**
|
|
27
|
-
|
|
26
|
+
/**
|
|
27
|
+
* GitHub repo whose `<helper>/v<version>` releases carry the helper assets.
|
|
28
|
+
*
|
|
29
|
+
* This is the SLUG, which is not the npm package name: the package is still
|
|
30
|
+
* `@phnx-labs/agents-cli`, but the repository was renamed to `agi-cli`. The old
|
|
31
|
+
* slug kept working only because GitHub redirects a renamed repo, which is a
|
|
32
|
+
* poor thing to hang signed-binary delivery on — a redirect is one re-created
|
|
33
|
+
* repo away from resolving somewhere else. What actually protects the download
|
|
34
|
+
* is the sha256 + codesign + designated-requirement + Team ID verification
|
|
35
|
+
* below; this just stops relying on the redirect.
|
|
36
|
+
*/
|
|
37
|
+
export const HELPER_RELEASE_REPO = 'phnx-labs/agi-cli';
|
|
28
38
|
/** Apple Developer ID Team every helper must be signed by ("Developer ID
|
|
29
39
|
* Application: Muqit Nawaz"). Defense in depth on top of `spctl` notarization. */
|
|
30
40
|
export const EXPECTED_TEAM_ID = '2HTP252L87';
|
|
@@ -298,7 +298,7 @@ function foldUserHooksYamlIntoAgentsYaml() {
|
|
|
298
298
|
meta.hooks = merged;
|
|
299
299
|
const header = `# agents-cli metadata
|
|
300
300
|
# Auto-generated - do not edit manually
|
|
301
|
-
# https://github.com/phnx-labs/
|
|
301
|
+
# https://github.com/phnx-labs/agi-cli
|
|
302
302
|
|
|
303
303
|
`;
|
|
304
304
|
try {
|
|
@@ -414,7 +414,7 @@ function foldBrowserProfilesIntoAgentsYaml() {
|
|
|
414
414
|
meta.browser = merged;
|
|
415
415
|
const header = `# agents-cli metadata
|
|
416
416
|
# Auto-generated - do not edit manually
|
|
417
|
-
# https://github.com/phnx-labs/
|
|
417
|
+
# https://github.com/phnx-labs/agi-cli
|
|
418
418
|
|
|
419
419
|
`;
|
|
420
420
|
try {
|
|
@@ -1641,7 +1641,7 @@ function migrateVersionResourcesToPatterns() {
|
|
|
1641
1641
|
}
|
|
1642
1642
|
}
|
|
1643
1643
|
if (changed) {
|
|
1644
|
-
const META_HEADER = '# agents-cli metadata\n# Auto-generated - do not edit manually\n# https://github.com/phnx-labs/
|
|
1644
|
+
const META_HEADER = '# agents-cli metadata\n# Auto-generated - do not edit manually\n# https://github.com/phnx-labs/agi-cli\n# yaml-language-server: $schema=https://raw.githubusercontent.com/phnx-labs/agi-cli/main/cli/schema/agents-yaml.schema.json\n\n';
|
|
1645
1645
|
fs.writeFileSync(metaFile, META_HEADER + yaml.stringify(meta), 'utf-8');
|
|
1646
1646
|
console.error('Migrated agents.yaml versions: entries to pattern format');
|
|
1647
1647
|
}
|
|
@@ -1668,7 +1668,7 @@ function migrateSplitDeviceLocalMeta() {
|
|
|
1668
1668
|
const agents = meta.agents;
|
|
1669
1669
|
const versions = meta.versions;
|
|
1670
1670
|
const hasLocal = (!!agents && Object.keys(agents).length > 0) || (!!versions && Object.keys(versions).length > 0);
|
|
1671
|
-
const HEADER = '# agents-cli metadata\n# Auto-generated - do not edit manually\n# https://github.com/phnx-labs/
|
|
1671
|
+
const HEADER = '# agents-cli metadata\n# Auto-generated - do not edit manually\n# https://github.com/phnx-labs/agi-cli\n\n';
|
|
1672
1672
|
// Only rewrite central when it actually carries machine-local fields — a
|
|
1673
1673
|
// machine whose agents.yaml is already portable-only is left untouched.
|
|
1674
1674
|
if (hasLocal) {
|
|
@@ -35,6 +35,26 @@ export declare const MENUBAR_HELPER_EXECUTABLE_NAME = "AGI Menu";
|
|
|
35
35
|
* hermetic test fork's own teardown boots out the operator's live helper.
|
|
36
36
|
*/
|
|
37
37
|
export declare function serviceLabel(): string;
|
|
38
|
+
/**
|
|
39
|
+
* What the installed helper IS — deliberately not the CLI's version.
|
|
40
|
+
*
|
|
41
|
+
* `source` matters because the two install paths have different notions of
|
|
42
|
+
* "changed": a release bundle is identified by its helper version, while a local
|
|
43
|
+
* dev build has none (menubar/scripts/build.sh hardcodes CFBundleShortVersionString),
|
|
44
|
+
* so it is identified by the source path + mtime it was copied from.
|
|
45
|
+
*/
|
|
46
|
+
/** Version label for a bundle that has none of its own (a local dev build). */
|
|
47
|
+
export declare const LOCAL_BUILD_LABEL = "local";
|
|
48
|
+
export type MenubarStamp = {
|
|
49
|
+
source: 'release';
|
|
50
|
+
helperVersion: string;
|
|
51
|
+
} | {
|
|
52
|
+
source: 'local';
|
|
53
|
+
sourceStamp: string;
|
|
54
|
+
} | {
|
|
55
|
+
source: 'legacy';
|
|
56
|
+
raw: string;
|
|
57
|
+
};
|
|
38
58
|
/**
|
|
39
59
|
* Absolute path to the installed menu-bar helper executable if it exists on
|
|
40
60
|
* disk, else null. The desktop notifier (notify-desktop.ts) routes daemon
|
|
@@ -136,16 +156,50 @@ export declare function enableMenubarService(opts?: {
|
|
|
136
156
|
clearOptOut?: boolean;
|
|
137
157
|
}): Promise<boolean>;
|
|
138
158
|
/**
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
159
|
+
* Render a stamp as a comparable/displayable version string.
|
|
160
|
+
*
|
|
161
|
+
* A local build has no version of its own, so it reports `local`; the ownership
|
|
162
|
+
* contest treats that as "not comparable" and falls through to its owner arm,
|
|
163
|
+
* which is the correct outcome — a dev build must never win a version contest
|
|
164
|
+
* against a release.
|
|
165
|
+
*/
|
|
166
|
+
export declare function stampVersionLabel(stamp: MenubarStamp | null): string | null;
|
|
167
|
+
/**
|
|
168
|
+
* Identify the bundle about to be installed, for the stamp.
|
|
169
|
+
*
|
|
170
|
+
* A bundle is a RELEASE iff it sits under the helper's own download cache —
|
|
171
|
+
* asked of `menubarHelperCacheDir`, not pattern-matched out of the path. A regex
|
|
172
|
+
* for `/v<x.y.z>/` gets this wrong in both directions: a checkout living under
|
|
173
|
+
* any directory that happens to contain a version-shaped segment reads as a
|
|
174
|
+
* release, and a release cache laid out differently reads as local. Either
|
|
175
|
+
* misclassification flips `source` between invocations, and a kind change is
|
|
176
|
+
* unconditionally stale — which is a reinstall loop.
|
|
177
|
+
*/
|
|
178
|
+
export declare function stampFor(resolvedSourceAppPath: string): MenubarStamp;
|
|
179
|
+
/**
|
|
180
|
+
* The helper version a path denotes, iff it is inside that version's cache dir.
|
|
181
|
+
* Returns null for anything else — including a version-shaped path that is not
|
|
182
|
+
* actually the cache.
|
|
183
|
+
*/
|
|
184
|
+
export declare function releaseVersionOfCachedBundle(appPath: string, cacheDirFor?: (v: string) => string): string | null;
|
|
185
|
+
/**
|
|
186
|
+
* Pure staleness decision (no I/O) so the truth table is unit-testable.
|
|
187
|
+
*
|
|
188
|
+
* This compares the HELPER axis, not the CLI's. It used to compare the installed
|
|
189
|
+
* stamp against `getCliVersion()`, which was wrong in both directions once the
|
|
190
|
+
* helpers gained their own version line: every CLI release made an unchanged
|
|
191
|
+
* helper look stale and reinstalled it (the #2109 restart storm), while a
|
|
192
|
+
* genuinely newer helper at the same CLI version never looked stale at all.
|
|
193
|
+
*
|
|
194
|
+
* Stale when: the executable is gone; nothing is stamped; the stamp predates the
|
|
195
|
+
* JSON format (`legacy` — re-stamped once); the install KIND changed
|
|
196
|
+
* (local <-> release), since a dev build and a release bundle are not
|
|
197
|
+
* interchangeable; a release install whose available helper version is newer;
|
|
198
|
+
* or a local install whose source path or mtime moved.
|
|
145
199
|
*/
|
|
146
200
|
export declare function isMenubarStale(opts: {
|
|
147
|
-
|
|
148
|
-
|
|
201
|
+
installed: MenubarStamp | null;
|
|
202
|
+
available: MenubarStamp;
|
|
149
203
|
execExists: boolean;
|
|
150
204
|
}): boolean;
|
|
151
205
|
/**
|
|
@@ -354,8 +408,12 @@ export interface MenubarStatus {
|
|
|
354
408
|
platform: string;
|
|
355
409
|
source: string | null;
|
|
356
410
|
installedApp: string | null;
|
|
411
|
+
/** The installed HELPER's version — not the CLI's. `local` for a dev build. */
|
|
357
412
|
installedVersion: string | null;
|
|
413
|
+
/** The helper version this install would put on disk right now. */
|
|
358
414
|
currentVersion: string;
|
|
415
|
+
/** The CLI's own version, reported separately so the two are never conflated. */
|
|
416
|
+
cliVersion: string;
|
|
359
417
|
stale: boolean;
|
|
360
418
|
serviceInstalled: boolean;
|
|
361
419
|
running: boolean;
|
|
@@ -370,8 +428,13 @@ export declare function getMenubarStatus(): MenubarStatus;
|
|
|
370
428
|
export interface MenubarDoctorReport {
|
|
371
429
|
platform: string;
|
|
372
430
|
installPath: string | null;
|
|
431
|
+
/** The installed HELPER's version — not the CLI's. `local` for a dev build. */
|
|
373
432
|
installedVersion: string | null;
|
|
433
|
+
/** The helper version this install would put on disk right now. */
|
|
374
434
|
currentVersion: string;
|
|
435
|
+
/** The CLI's own version, reported separately so the two are never conflated. */
|
|
436
|
+
cliVersion: string;
|
|
437
|
+
/** installed helper vs available helper. Never compares the CLI's version. */
|
|
375
438
|
versionMatches: boolean;
|
|
376
439
|
/** `unknown` on non-darwin or when nothing is installed to inspect. */
|
|
377
440
|
signingIdentity: 'developer-id' | 'ad-hoc' | 'unknown';
|