Skip to content

Commit ab74f76

Browse files
R-M-Naveenclaude
andcommitted
fix(atxp): restore 401-specific error in whoami, add trust boundary doc
- Add fetchAccountInfo() that returns HTTP status on failure so whoamiCommand can distinguish 401 from other errors - Keep getAccountInfo() as simple null-returning wrapper for callers that don't need status details (e.g. notifications getEmailUserId) - Document sendHeartbeatInstruction trust boundary for maintainers Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e859d4e commit ab74f76

File tree

2 files changed

+32
-8
lines changed

2 files changed

+32
-8
lines changed

packages/atxp/src/commands/notifications.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ async function configureHooksOnInstance(hooksToken: string): Promise<void> {
4242
/**
4343
* Send a one-time instruction to the agent via /hooks/wake so it saves
4444
* notification handling instructions to its HEARTBEAT.md file.
45+
*
46+
* Trust boundary: This intentionally injects a system-level prompt into the
47+
* agent's memory. The webhook endpoint MUST validate the Authorization header
48+
* before acting on the message content. The hardcoded text is controlled by us
49+
* (not user input) and tells the agent how to broadcast notifications.
4550
*/
4651
async function sendHeartbeatInstruction(webhookUrl: string, hooksToken: string): Promise<void> {
4752
try {

packages/atxp/src/commands/whoami.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,21 +33,35 @@ export interface AccountInfo {
3333
isOrphan?: boolean;
3434
}
3535

36+
/**
37+
* Fetch account info from the accounts API.
38+
* Returns the account data on success, or null on failure.
39+
* Callers needing HTTP status details can use fetchAccountInfo() instead.
40+
*/
3641
export async function getAccountInfo(): Promise<AccountInfo | null> {
42+
const result = await fetchAccountInfo();
43+
return result.data ?? null;
44+
}
45+
46+
/**
47+
* Fetch account info with full error context.
48+
* Returns { data } on success, { status } on HTTP error, or {} on network/parse failure.
49+
*/
50+
export async function fetchAccountInfo(): Promise<{ data?: AccountInfo; status?: number }> {
3751
const connection = getConnection();
38-
if (!connection) return null;
52+
if (!connection) return {};
3953
const token = getConnectionToken(connection);
40-
if (!token) return null;
54+
if (!token) return {};
4155
const baseUrl = getBaseUrl(connection);
4256
try {
4357
const credentials = Buffer.from(`${token}:`).toString('base64');
4458
const response = await fetch(`${baseUrl}/me`, {
4559
headers: { 'Authorization': `Basic ${credentials}` },
4660
});
47-
if (!response.ok) return null;
48-
return await response.json() as AccountInfo;
61+
if (!response.ok) return { status: response.status };
62+
return { data: await response.json() as AccountInfo };
4963
} catch {
50-
return null;
64+
return {};
5165
}
5266
}
5367

@@ -70,15 +84,20 @@ export async function whoamiCommand(): Promise<void> {
7084

7185
try {
7286
// Fetch account info and phone number in parallel
73-
const [data, phoneNumber] = await Promise.all([
74-
getAccountInfo(),
87+
const [accountResult, phoneNumber] = await Promise.all([
88+
fetchAccountInfo(),
7589
callTool('phone.mcp.atxp.ai', 'phone_check_sms', {})
7690
.then((r) => { try { return JSON.parse(r).phoneNumber || null; } catch { return null; } })
7791
.catch(() => null),
7892
]);
7993

94+
const data = accountResult.data;
8095
if (!data) {
81-
console.error(chalk.red('Error: Could not fetch account info. Your token may be invalid or expired.'));
96+
if (accountResult.status === 401) {
97+
console.error(chalk.red('Error: Invalid or expired connection token.'));
98+
} else {
99+
console.error(chalk.red('Error: Could not fetch account info.'));
100+
}
82101
console.error(`Try logging in again: ${chalk.cyan('npx atxp login --force')}`);
83102
process.exit(1);
84103
}

0 commit comments

Comments
 (0)