@phnx-labs/agents-cli 1.22.77 → 1.22.78
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 +3 -1
- package/dist/commands/auth.js +4 -1
- package/dist/lib/actor.d.ts +17 -0
- package/dist/lib/actor.js +26 -2
- package/dist/lib/identity/index.d.ts +10 -8
- package/dist/lib/identity/index.js +15 -11
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 1.22.
|
|
3
|
+
## 1.22.78
|
|
4
|
+
|
|
5
|
+
- **The launch env carries the human's Phoenix ID profile picture.** A resolved human actor now gets `AGENTS_ACTOR_AVATAR` (https URL) when the Phoenix ID session signed in on the device belongs to the same email, so tools an agent runs — `artifacts render`, shares — can show the person instead of initials. A shared box signed in as someone else lends nothing to whoever SSH-ed in, and a child spawn inherits the value like the rest of `AGENTS_ACTOR*`. `agents auth whoami` now also refreshes the persisted picture from `/api/v1/auth/me`, which Phoenix ID returns as of phnx-labs/phoenix-id#7. Source: `cli/src/lib/actor.ts`, `cli/src/commands/auth.ts`.
|
|
4
6
|
|
|
5
7
|
- **`agents repo pull` / `agents sync` no longer wedge on a CLI-regenerated `~/.agents/agents.yaml` (PHNX-3968).** The shared central config was rewritten by config commands without being committed, so between the daemon's publish ticks the tree sat dirty on `agents.yaml`; an incoming peer commit that also touched it made the pull refuse with *"incoming changes touch uncommitted paths: agents.yaml"*, forcing manual autostash gymnastics on the fleet. Central config edits are now committed synchronously by the command that makes them (after the meta lock releases, skipped for the daemon whose own publish tick owns its commits), so the tree is clean at rest and the trip cannot occur. A genuinely dirty-and-differing central file now *refuses* the pull (data-safe, self-heals on the next commit) rather than silently taking the remote copy. The user-repo `CHANGELOG.md` (a duplicate of the shipped one) is de-tracked via `.git/info/exclude` so it stops churning the tree. Source: `cli/src/lib/state.ts`, `cli/src/lib/git.ts`, `cli/src/lib/browser/registry.ts`, `cli/src/lib/project-resources.ts`.
|
|
6
8
|
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import { PhoenixApiError, clearSession, createSpace, createSpaceInvite, fetchWhoAmI, listSpaceMembers, listSpaces, pollDeviceToken, readSession, removeSpaceMember, resolveMemberFromList, resolveSpaceFromList, slugify, startDeviceAuthorization, updateSpaceMemberRole, writeSession, } from '../lib/identity/index.js';
|
|
2
|
+
import { PhoenixApiError, clearSession, createSpace, createSpaceInvite, fetchWhoAmI, listSpaceMembers, listSpaces, pollDeviceToken, readSession, refreshSessionAvatar, removeSpaceMember, resolveMemberFromList, resolveSpaceFromList, slugify, startDeviceAuthorization, updateSpaceMemberRole, writeSession, } from '../lib/identity/index.js';
|
|
3
3
|
import { setHelpSections } from '../lib/help.js';
|
|
4
4
|
import { runOrDie } from '../lib/format.js';
|
|
5
5
|
import { registerMintCommand } from './auth-mint.js';
|
|
@@ -60,6 +60,9 @@ async function whoami(json) {
|
|
|
60
60
|
}
|
|
61
61
|
try {
|
|
62
62
|
const me = await fetchWhoAmI();
|
|
63
|
+
// Keep the persisted profile picture current: the actor env and share
|
|
64
|
+
// attribution read it from the session file, never from the network.
|
|
65
|
+
await refreshSessionAvatar(me);
|
|
63
66
|
if (json) {
|
|
64
67
|
console.log(JSON.stringify({ signedIn: true, ...me }, null, 2));
|
|
65
68
|
return;
|
package/dist/lib/actor.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PhoenixSession } from './identity/client.js';
|
|
1
2
|
import type { ActorConfig } from './types.js';
|
|
2
3
|
export type ActorKind = 'human' | 'agent';
|
|
3
4
|
export interface ResolvedActor {
|
|
@@ -20,6 +21,13 @@ export interface ResolvedActor {
|
|
|
20
21
|
* happens to be signed into tailscale with.
|
|
21
22
|
*/
|
|
22
23
|
phoenixId?: string;
|
|
24
|
+
/**
|
|
25
|
+
* The human's hosted profile picture (https URL) from the Phoenix ID session
|
|
26
|
+
* signed in on this device, attached only when that session belongs to the
|
|
27
|
+
* same email as the resolved actor. Rides the env as `AGENTS_ACTOR_AVATAR` so
|
|
28
|
+
* tools the agent runs (artifacts, shares) can show the person, not initials.
|
|
29
|
+
*/
|
|
30
|
+
avatarUrl?: string;
|
|
23
31
|
}
|
|
24
32
|
/** Result of `tailscale whois --json <ip>` we care about. */
|
|
25
33
|
export interface WhoisIdentity {
|
|
@@ -34,6 +42,13 @@ export interface WhoisIdentity {
|
|
|
34
42
|
* from the tailnet DisplayName + login email.
|
|
35
43
|
*/
|
|
36
44
|
export declare function actorFromIdentity(who: WhoisIdentity | undefined, host: string, actors: Record<string, ActorConfig>): ResolvedActor;
|
|
45
|
+
/**
|
|
46
|
+
* The Phoenix ID profile picture for a resolved human, when the session signed
|
|
47
|
+
* in on this device is that same person (matched on email, case-insensitive).
|
|
48
|
+
* A shared box signed in as someone else must not lend its picture to whoever
|
|
49
|
+
* SSH-ed in, so a mismatch yields nothing. Pure: the session is injected.
|
|
50
|
+
*/
|
|
51
|
+
export declare function actorAvatar(actor: ResolvedActor, session: PhoenixSession | null): string | undefined;
|
|
37
52
|
/**
|
|
38
53
|
* Injectable tailscale resolvers, so tests can drive the SSH-whois and
|
|
39
54
|
* local-self branches deterministically without a real tailscale on the box
|
|
@@ -43,6 +58,8 @@ export declare function actorFromIdentity(who: WhoisIdentity | undefined, host:
|
|
|
43
58
|
export interface ActorResolvers {
|
|
44
59
|
whois: (ip: string) => WhoisIdentity | undefined;
|
|
45
60
|
self: () => WhoisIdentity | undefined;
|
|
61
|
+
/** The Phoenix ID session on this device (a local file read, no network). */
|
|
62
|
+
session: () => PhoenixSession | null;
|
|
46
63
|
}
|
|
47
64
|
/**
|
|
48
65
|
* Compute the actor for a given environment. The only impurity is the tailscale
|
package/dist/lib/actor.js
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import { spawnSync } from 'child_process';
|
|
26
26
|
import { machineId } from './machine-id.js';
|
|
27
27
|
import { parseSshConnection } from './session/provenance.js';
|
|
28
|
+
import { readSession } from './identity/client.js';
|
|
28
29
|
import { readMeta } from './state.js';
|
|
29
30
|
/** Hard cap on the whois shell-out — it runs on the spawn hot path. */
|
|
30
31
|
const WHOIS_TIMEOUT_MS = 2000;
|
|
@@ -145,9 +146,28 @@ function inheritedActor(env) {
|
|
|
145
146
|
email: env.AGENTS_ACTOR_EMAIL || undefined,
|
|
146
147
|
github: env.AGENTS_ACTOR_GITHUB || undefined,
|
|
147
148
|
phoenixId: env.AGENTS_ACTOR_PHOENIX_ID || undefined,
|
|
149
|
+
avatarUrl: httpsUrl(env.AGENTS_ACTOR_AVATAR),
|
|
148
150
|
};
|
|
149
151
|
}
|
|
150
|
-
|
|
152
|
+
/** An avatar is only ever an https URL; anything else is not an avatar. */
|
|
153
|
+
function httpsUrl(value) {
|
|
154
|
+
const trimmed = value?.trim();
|
|
155
|
+
return trimmed && /^https:\/\/\S+$/i.test(trimmed) ? trimmed : undefined;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* The Phoenix ID profile picture for a resolved human, when the session signed
|
|
159
|
+
* in on this device is that same person (matched on email, case-insensitive).
|
|
160
|
+
* A shared box signed in as someone else must not lend its picture to whoever
|
|
161
|
+
* SSH-ed in, so a mismatch yields nothing. Pure: the session is injected.
|
|
162
|
+
*/
|
|
163
|
+
export function actorAvatar(actor, session) {
|
|
164
|
+
if (actor.kind !== 'human' || !actor.email || !session?.email)
|
|
165
|
+
return undefined;
|
|
166
|
+
if (actor.email.trim().toLowerCase() !== session.email.trim().toLowerCase())
|
|
167
|
+
return undefined;
|
|
168
|
+
return httpsUrl(session.avatarUrl);
|
|
169
|
+
}
|
|
170
|
+
const defaultResolvers = { whois: tailscaleWhois, self: tailscaleSelf, session: readSession };
|
|
151
171
|
/**
|
|
152
172
|
* Compute the actor for a given environment. The only impurity is the tailscale
|
|
153
173
|
* shell-out (injectable via `resolvers`), so tests drive every branch explicitly.
|
|
@@ -171,7 +191,9 @@ export function computeActor(env = process.env, resolvers = defaultResolvers) {
|
|
|
171
191
|
// UNRESOLVED rather than being misattributed to the box's owner.
|
|
172
192
|
if (!who && !sshRaw)
|
|
173
193
|
who = resolvers.self();
|
|
174
|
-
|
|
194
|
+
const actor = actorFromIdentity(who, machineId(), readActors());
|
|
195
|
+
const avatarUrl = actorAvatar(actor, resolvers.session());
|
|
196
|
+
return avatarUrl ? { ...actor, avatarUrl } : actor;
|
|
175
197
|
}
|
|
176
198
|
let cached;
|
|
177
199
|
let resolverOverride;
|
|
@@ -220,6 +242,8 @@ export function actorEnv(actor) {
|
|
|
220
242
|
env.AGENTS_ACTOR_GITHUB = actor.github;
|
|
221
243
|
if (actor.phoenixId)
|
|
222
244
|
env.AGENTS_ACTOR_PHOENIX_ID = actor.phoenixId;
|
|
245
|
+
if (actor.avatarUrl)
|
|
246
|
+
env.AGENTS_ACTOR_AVATAR = actor.avatarUrl;
|
|
223
247
|
if (actor.kind === 'human' && actor.name && actor.email) {
|
|
224
248
|
env.GIT_AUTHOR_NAME = actor.name;
|
|
225
249
|
env.GIT_AUTHOR_EMAIL = actor.email;
|
|
@@ -48,15 +48,17 @@ export declare function startDeviceAuthorization(): Promise<DeviceAuthorization>
|
|
|
48
48
|
export declare function pollDeviceToken(deviceCode: string): Promise<DevicePoll>;
|
|
49
49
|
export declare function fetchWhoAmI(token?: string): Promise<WhoAmI>;
|
|
50
50
|
/**
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
51
|
+
* Keep the session's hosted avatar current (PHNX-3547): merge the `avatar_url`
|
|
52
|
+
* Phoenix ID reports on `/api/v1/auth/me` into the persisted session, so the
|
|
53
|
+
* actor env and share attribution can stamp the profile image instead of only
|
|
54
|
+
* a Gravatar hash. Pass `known` when the caller already fetched `/auth/me`
|
|
55
|
+
* (whoami) — then a changed picture is written too. Without it, a session that
|
|
56
|
+
* already carries a picture is left alone rather than spending a network round
|
|
57
|
+
* trip to re-check it (the share publish path). A no-op when signed out or when
|
|
58
|
+
* the server exposes none; network/server failures are swallowed (the Gravatar
|
|
59
|
+
* fallback covers attribution either way).
|
|
58
60
|
*/
|
|
59
|
-
export declare function refreshSessionAvatar(): Promise<void>;
|
|
61
|
+
export declare function refreshSessionAvatar(known?: WhoAmI): Promise<void>;
|
|
60
62
|
export interface SpaceSummary {
|
|
61
63
|
id: string;
|
|
62
64
|
slug: string;
|
|
@@ -39,22 +39,26 @@ export function fetchWhoAmI(token) {
|
|
|
39
39
|
return phoenixRequest('GET', '/api/v1/auth/me', { token });
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
42
|
+
* Keep the session's hosted avatar current (PHNX-3547): merge the `avatar_url`
|
|
43
|
+
* Phoenix ID reports on `/api/v1/auth/me` into the persisted session, so the
|
|
44
|
+
* actor env and share attribution can stamp the profile image instead of only
|
|
45
|
+
* a Gravatar hash. Pass `known` when the caller already fetched `/auth/me`
|
|
46
|
+
* (whoami) — then a changed picture is written too. Without it, a session that
|
|
47
|
+
* already carries a picture is left alone rather than spending a network round
|
|
48
|
+
* trip to re-check it (the share publish path). A no-op when signed out or when
|
|
49
|
+
* the server exposes none; network/server failures are swallowed (the Gravatar
|
|
50
|
+
* fallback covers attribution either way).
|
|
49
51
|
*/
|
|
50
|
-
export async function refreshSessionAvatar() {
|
|
52
|
+
export async function refreshSessionAvatar(known) {
|
|
51
53
|
const session = readSession();
|
|
52
|
-
if (!session
|
|
54
|
+
if (!session)
|
|
55
|
+
return;
|
|
56
|
+
if (!known && session.avatarUrl)
|
|
53
57
|
return;
|
|
54
58
|
try {
|
|
55
|
-
const me = await fetchWhoAmI();
|
|
59
|
+
const me = known ?? (await fetchWhoAmI());
|
|
56
60
|
const hosted = me.avatar_url?.trim();
|
|
57
|
-
if (hosted && /^https:\/\//i.test(hosted)) {
|
|
61
|
+
if (hosted && /^https:\/\//i.test(hosted) && hosted !== session.avatarUrl) {
|
|
58
62
|
writeSession({ ...session, avatarUrl: hosted });
|
|
59
63
|
}
|
|
60
64
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.22.
|
|
3
|
+
"version": "1.22.78",
|
|
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",
|