@postplus/cli 0.1.20 → 0.1.22
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/build/auth-lifecycle.js +13 -12
- package/build/auth-login.js +18 -6
- package/build/auth-session.js +35 -83
- package/build/auth-validate.js +9 -2
- package/build/auth.js +16 -23
- package/build/client-compatibility.js +69 -0
- package/build/doctor.js +8 -2
- package/build/index.js +2 -1
- package/build/local-state.js +24 -16
- package/build/skill-management.js +2 -0
- package/build/status.js +2 -0
- package/build/update-check.js +1 -9
- package/package.json +2 -1
package/build/auth-lifecycle.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { refreshRemoteAuthSession } from './auth-session.js';
|
|
2
2
|
import { clearAuthState, generateAuthStatusReport } from './auth.js';
|
|
3
|
+
import { buildPostPlusClientCompatibilityHeaders, formatPostPlusClientUpgradeError, } from './client-compatibility.js';
|
|
3
4
|
import { requireHostedBaseUrl } from './hosted-release.js';
|
|
4
|
-
import {
|
|
5
|
+
import { resolveCliSessionTokenState } from './local-state.js';
|
|
5
6
|
export async function refreshRemoteAuth() {
|
|
6
7
|
const refreshed = await refreshRemoteAuthSession();
|
|
7
8
|
return {
|
|
@@ -25,31 +26,31 @@ export function formatAuthRefreshReport(report) {
|
|
|
25
26
|
].join('\n');
|
|
26
27
|
}
|
|
27
28
|
export async function revokeRemoteAuth() {
|
|
28
|
-
const [apiBaseUrl,
|
|
29
|
+
const [apiBaseUrl, cliSessionTokenState] = await Promise.all([
|
|
29
30
|
requireHostedBaseUrl(),
|
|
30
|
-
|
|
31
|
-
resolveRefreshTokenState(),
|
|
31
|
+
resolveCliSessionTokenState(),
|
|
32
32
|
]);
|
|
33
|
-
if (!
|
|
34
|
-
throw new Error('Run `postplus auth login` before revoking PostPlus auth.');
|
|
35
|
-
}
|
|
36
|
-
if (!refreshTokenState.present || !refreshTokenState.value) {
|
|
33
|
+
if (!cliSessionTokenState.present || !cliSessionTokenState.value) {
|
|
37
34
|
throw new Error('Run `postplus auth login` before revoking PostPlus auth.');
|
|
38
35
|
}
|
|
36
|
+
const compatibilityHeaders = await buildPostPlusClientCompatibilityHeaders();
|
|
39
37
|
const response = await fetch(`${apiBaseUrl}/api/postplus-cli/auth/revoke`, {
|
|
40
38
|
method: 'POST',
|
|
41
39
|
headers: {
|
|
42
40
|
accept: 'application/json',
|
|
43
|
-
|
|
41
|
+
...compatibilityHeaders,
|
|
42
|
+
authorization: `Bearer ${cliSessionTokenState.value}`,
|
|
44
43
|
'content-type': 'application/json',
|
|
45
44
|
},
|
|
46
|
-
body: JSON.stringify({
|
|
47
|
-
refreshToken: refreshTokenState.value,
|
|
48
|
-
}),
|
|
45
|
+
body: JSON.stringify({}),
|
|
49
46
|
signal: AbortSignal.timeout(15000),
|
|
50
47
|
});
|
|
51
48
|
const payload = (await response.json());
|
|
52
49
|
if (!response.ok) {
|
|
50
|
+
if ('code' in payload &&
|
|
51
|
+
payload.code === 'postplus_client_upgrade_required') {
|
|
52
|
+
throw new Error(formatPostPlusClientUpgradeError(payload));
|
|
53
|
+
}
|
|
53
54
|
throw new Error('error' in payload && typeof payload.error === 'string'
|
|
54
55
|
? payload.error
|
|
55
56
|
: 'Failed to revoke remote PostPlus auth.');
|
package/build/auth-login.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { buildPostPlusClientCompatibilityHeaders, formatPostPlusClientUpgradeError, writeCurrentCliVersionToLocalConfig, } from './client-compatibility.js';
|
|
1
2
|
import { requireHostedBaseUrl } from './hosted-release.js';
|
|
2
3
|
import { setLocalSession } from './local-state.js';
|
|
3
4
|
export const CLI_AUTH_LOGIN_TIMEOUT_MS = 30 * 60 * 1000;
|
|
@@ -23,18 +24,18 @@ export async function loginWithCloudHandoff() {
|
|
|
23
24
|
requestId: started.requestId,
|
|
24
25
|
});
|
|
25
26
|
const validated = await validateCliSession({
|
|
26
|
-
accessToken: handoffPayload.accessToken,
|
|
27
27
|
apiBaseUrl: baseUrl,
|
|
28
|
+
cliSessionToken: handoffPayload.cliSessionToken,
|
|
28
29
|
});
|
|
29
30
|
await setLocalSession({
|
|
30
|
-
accessToken: handoffPayload.accessToken,
|
|
31
31
|
accountId: validated.accountId,
|
|
32
32
|
apiBaseUrl: baseUrl,
|
|
33
|
-
|
|
33
|
+
cliSessionToken: handoffPayload.cliSessionToken,
|
|
34
34
|
sessionExpiresAt: validated.sessionExpiresAt ?? handoffPayload.sessionExpiresAt ?? null,
|
|
35
35
|
userEmail: validated.userEmail,
|
|
36
36
|
userId: validated.userId,
|
|
37
37
|
});
|
|
38
|
+
await writeCurrentCliVersionToLocalConfig();
|
|
38
39
|
return {
|
|
39
40
|
accountId: validated.accountId,
|
|
40
41
|
apiBaseUrl: baseUrl,
|
|
@@ -44,10 +45,12 @@ export async function loginWithCloudHandoff() {
|
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
47
|
export async function startCloudAuthLogin(apiBaseUrl) {
|
|
48
|
+
const compatibilityHeaders = await buildPostPlusClientCompatibilityHeaders();
|
|
47
49
|
const response = await fetch(`${apiBaseUrl}/api/postplus-cli/auth/login/start`, {
|
|
48
50
|
method: 'POST',
|
|
49
51
|
headers: {
|
|
50
52
|
accept: 'application/json',
|
|
53
|
+
...compatibilityHeaders,
|
|
51
54
|
},
|
|
52
55
|
signal: AbortSignal.timeout(15000),
|
|
53
56
|
});
|
|
@@ -76,10 +79,12 @@ async function waitForCloudAuthLogin(input) {
|
|
|
76
79
|
throw new Error('Timed out waiting for the cloud sign-in handoff.');
|
|
77
80
|
}
|
|
78
81
|
export async function pollCloudAuthLogin(input) {
|
|
82
|
+
const compatibilityHeaders = await buildPostPlusClientCompatibilityHeaders();
|
|
79
83
|
const response = await fetch(`${input.apiBaseUrl}/api/postplus-cli/auth/login/poll`, {
|
|
80
84
|
method: 'POST',
|
|
81
85
|
headers: {
|
|
82
86
|
accept: 'application/json',
|
|
87
|
+
...compatibilityHeaders,
|
|
83
88
|
'content-type': 'application/json',
|
|
84
89
|
},
|
|
85
90
|
body: JSON.stringify({
|
|
@@ -101,11 +106,13 @@ export async function pollCloudAuthLogin(input) {
|
|
|
101
106
|
throw new Error('PostPlus CLI sign-in poll returned incomplete data.');
|
|
102
107
|
}
|
|
103
108
|
export async function validateCliSession(input) {
|
|
109
|
+
const compatibilityHeaders = await buildPostPlusClientCompatibilityHeaders();
|
|
104
110
|
const response = await fetch(`${input.apiBaseUrl}/api/postplus-cli/auth/whoami`, {
|
|
105
111
|
method: 'GET',
|
|
106
112
|
headers: {
|
|
107
113
|
accept: 'application/json',
|
|
108
|
-
|
|
114
|
+
...compatibilityHeaders,
|
|
115
|
+
authorization: `Bearer ${input.cliSessionToken}`,
|
|
109
116
|
},
|
|
110
117
|
signal: AbortSignal.timeout(15000),
|
|
111
118
|
});
|
|
@@ -123,6 +130,9 @@ export function formatCliSessionAuthError(payload) {
|
|
|
123
130
|
'Once the environment is ready, the CLI will automatically obtain and store its session.',
|
|
124
131
|
].join(' ');
|
|
125
132
|
}
|
|
133
|
+
if (payload.code === 'postplus_client_upgrade_required') {
|
|
134
|
+
return formatPostPlusClientUpgradeError(payload);
|
|
135
|
+
}
|
|
126
136
|
if (typeof payload.error === 'string' && payload.error.trim().length > 0) {
|
|
127
137
|
return payload.error;
|
|
128
138
|
}
|
|
@@ -140,8 +150,7 @@ function isCliAuthLoginStartSuccessPayload(payload) {
|
|
|
140
150
|
function isCliAuthLoginCompletedPayload(payload) {
|
|
141
151
|
return ('status' in payload &&
|
|
142
152
|
payload.status === 'completed' &&
|
|
143
|
-
typeof payload.
|
|
144
|
-
typeof payload.refreshToken === 'string' &&
|
|
153
|
+
typeof payload.cliSessionToken === 'string' &&
|
|
145
154
|
typeof payload.accountId === 'string' &&
|
|
146
155
|
typeof payload.userId === 'string');
|
|
147
156
|
}
|
|
@@ -149,6 +158,9 @@ function isCliAuthLoginPendingPayload(payload) {
|
|
|
149
158
|
return 'status' in payload && payload.status === 'pending';
|
|
150
159
|
}
|
|
151
160
|
function formatRemoteAuthLoginError(payload) {
|
|
161
|
+
if ('code' in payload && payload.code === 'postplus_client_upgrade_required') {
|
|
162
|
+
return formatPostPlusClientUpgradeError(payload);
|
|
163
|
+
}
|
|
152
164
|
return 'error' in payload &&
|
|
153
165
|
typeof payload.error === 'string' &&
|
|
154
166
|
payload.error.trim().length > 0
|
package/build/auth-session.js
CHANGED
|
@@ -1,137 +1,89 @@
|
|
|
1
|
+
import { buildPostPlusClientCompatibilityHeaders, formatPostPlusClientUpgradeError, writeCurrentCliVersionToLocalConfig, } from './client-compatibility.js';
|
|
1
2
|
import { requireHostedBaseUrl } from './hosted-release.js';
|
|
2
|
-
import {
|
|
3
|
-
export const AUTH_SESSION_REFRESH_LEEWAY_SECONDS = 60;
|
|
3
|
+
import { resolveCliSessionTokenState, setLocalSession, } from './local-state.js';
|
|
4
4
|
export async function resolveFreshRemoteAuth(options = {}) {
|
|
5
|
-
const [apiBaseUrl,
|
|
5
|
+
const [apiBaseUrl, cliSessionTokenState] = await Promise.all([
|
|
6
6
|
requireHostedBaseUrl(),
|
|
7
|
-
|
|
8
|
-
resolveRefreshTokenState(),
|
|
9
|
-
readLocalConfig(),
|
|
7
|
+
resolveCliSessionTokenState(),
|
|
10
8
|
]);
|
|
11
|
-
if (!
|
|
12
|
-
|
|
13
|
-
throw new Error('Run `postplus auth login` before validating PostPlus auth.');
|
|
14
|
-
}
|
|
15
|
-
return {
|
|
16
|
-
accessToken: accessTokenState.value,
|
|
17
|
-
apiBaseUrl,
|
|
18
|
-
refreshed: false,
|
|
19
|
-
source: 'config',
|
|
20
|
-
};
|
|
9
|
+
if (!cliSessionTokenState.present || !cliSessionTokenState.value) {
|
|
10
|
+
throw new Error('Run `postplus auth login` before using PostPlus auth.');
|
|
21
11
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
? decodedTokenExpiresAt
|
|
28
|
-
: typeof config?.sessionExpiresAt === 'number'
|
|
29
|
-
? config.sessionExpiresAt
|
|
30
|
-
: null;
|
|
31
|
-
const shouldRefresh = options.forceRefresh === true ||
|
|
32
|
-
!accessTokenState.present ||
|
|
33
|
-
!accessTokenState.value ||
|
|
34
|
-
isExpiringSoon(tokenExpiresAt);
|
|
35
|
-
if (!shouldRefresh) {
|
|
36
|
-
if (!existingAccessToken) {
|
|
37
|
-
throw new Error('Run `postplus auth login` before validating PostPlus auth.');
|
|
38
|
-
}
|
|
12
|
+
if (options.forceRefresh === true) {
|
|
13
|
+
const refreshed = await refreshRemoteAuthSession({
|
|
14
|
+
apiBaseUrl,
|
|
15
|
+
cliSessionToken: cliSessionTokenState.value,
|
|
16
|
+
});
|
|
39
17
|
return {
|
|
40
|
-
accessToken: existingAccessToken,
|
|
41
18
|
apiBaseUrl,
|
|
42
|
-
|
|
19
|
+
cliSessionToken: refreshed.cliSessionToken,
|
|
20
|
+
refreshed: true,
|
|
43
21
|
source: 'config',
|
|
44
22
|
};
|
|
45
23
|
}
|
|
46
|
-
const refreshed = await refreshRemoteAuthSession({
|
|
47
|
-
accessToken: accessTokenState.value,
|
|
48
|
-
apiBaseUrl,
|
|
49
|
-
refreshToken: refreshTokenState.value,
|
|
50
|
-
});
|
|
51
24
|
return {
|
|
52
|
-
accessToken: refreshed.accessToken,
|
|
53
25
|
apiBaseUrl,
|
|
54
|
-
|
|
26
|
+
cliSessionToken: cliSessionTokenState.value,
|
|
27
|
+
refreshed: false,
|
|
55
28
|
source: 'config',
|
|
56
29
|
};
|
|
57
30
|
}
|
|
58
31
|
export async function refreshRemoteAuthSession(input) {
|
|
59
|
-
const [apiBaseUrl,
|
|
32
|
+
const [apiBaseUrl, cliSessionTokenState] = await Promise.all([
|
|
60
33
|
input?.apiBaseUrl ?? requireHostedBaseUrl(),
|
|
61
|
-
input?.
|
|
62
|
-
input?.refreshToken === undefined ? resolveRefreshTokenState() : null,
|
|
34
|
+
input?.cliSessionToken === undefined ? resolveCliSessionTokenState() : null,
|
|
63
35
|
]);
|
|
64
|
-
const
|
|
65
|
-
?
|
|
66
|
-
: input.
|
|
67
|
-
|
|
68
|
-
? refreshTokenState?.value
|
|
69
|
-
: input.refreshToken;
|
|
70
|
-
if (!refreshToken) {
|
|
36
|
+
const cliSessionToken = input?.cliSessionToken === undefined
|
|
37
|
+
? cliSessionTokenState?.value
|
|
38
|
+
: input.cliSessionToken;
|
|
39
|
+
if (!cliSessionToken) {
|
|
71
40
|
throw new Error('Run `postplus auth login` before refreshing PostPlus auth.');
|
|
72
41
|
}
|
|
42
|
+
const compatibilityHeaders = await buildPostPlusClientCompatibilityHeaders();
|
|
73
43
|
const response = await fetch(`${apiBaseUrl}/api/postplus-cli/auth/refresh`, {
|
|
74
44
|
method: 'POST',
|
|
75
45
|
headers: {
|
|
76
46
|
accept: 'application/json',
|
|
77
|
-
...
|
|
47
|
+
...compatibilityHeaders,
|
|
48
|
+
authorization: `Bearer ${cliSessionToken}`,
|
|
78
49
|
'content-type': 'application/json',
|
|
79
50
|
},
|
|
80
|
-
body: JSON.stringify({
|
|
81
|
-
refreshToken,
|
|
82
|
-
}),
|
|
51
|
+
body: JSON.stringify({}),
|
|
83
52
|
signal: AbortSignal.timeout(15000),
|
|
84
53
|
});
|
|
85
54
|
const payload = (await response.json());
|
|
86
55
|
if (!response.ok) {
|
|
56
|
+
if ('code' in payload &&
|
|
57
|
+
payload.code === 'postplus_client_upgrade_required') {
|
|
58
|
+
throw new Error(formatPostPlusClientUpgradeError(payload));
|
|
59
|
+
}
|
|
87
60
|
throw new Error('error' in payload && typeof payload.error === 'string'
|
|
88
61
|
? payload.error
|
|
89
62
|
: 'Failed to refresh remote PostPlus auth.');
|
|
90
63
|
}
|
|
91
64
|
if (!isRemoteAuthRefreshSuccessPayload(payload)) {
|
|
92
|
-
throw new Error('PostPlus auth refresh returned incomplete session
|
|
65
|
+
throw new Error('PostPlus auth refresh returned incomplete session data.');
|
|
93
66
|
}
|
|
94
67
|
await setLocalSession({
|
|
95
|
-
accessToken: payload.accessToken,
|
|
96
68
|
accountId: payload.accountId,
|
|
97
69
|
apiBaseUrl,
|
|
98
|
-
|
|
70
|
+
cliSessionToken: payload.cliSessionToken,
|
|
99
71
|
sessionExpiresAt: payload.sessionExpiresAt,
|
|
100
72
|
userEmail: payload.userEmail,
|
|
101
73
|
userId: payload.userId,
|
|
102
74
|
});
|
|
75
|
+
await writeCurrentCliVersionToLocalConfig();
|
|
103
76
|
return {
|
|
104
77
|
...payload,
|
|
105
78
|
apiBaseUrl,
|
|
106
79
|
};
|
|
107
80
|
}
|
|
108
|
-
export function decodeAccessTokenExpiration(accessToken) {
|
|
109
|
-
try {
|
|
110
|
-
const [, payload] = accessToken.split('.');
|
|
111
|
-
if (!payload) {
|
|
112
|
-
return null;
|
|
113
|
-
}
|
|
114
|
-
const decoded = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'));
|
|
115
|
-
return typeof decoded.exp === 'number' ? decoded.exp : null;
|
|
116
|
-
}
|
|
117
|
-
catch {
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function isExpiringSoon(expiresAt) {
|
|
122
|
-
if (typeof expiresAt !== 'number' || !Number.isFinite(expiresAt)) {
|
|
123
|
-
return false;
|
|
124
|
-
}
|
|
125
|
-
const nowSeconds = Math.floor(Date.now() / 1_000);
|
|
126
|
-
return expiresAt - nowSeconds <= AUTH_SESSION_REFRESH_LEEWAY_SECONDS;
|
|
127
|
-
}
|
|
128
81
|
function isRemoteAuthRefreshSuccessPayload(payload) {
|
|
129
82
|
return (typeof payload === 'object' &&
|
|
130
83
|
payload !== null &&
|
|
131
|
-
typeof payload.
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
payload.refreshToken.trim().length > 0 &&
|
|
84
|
+
typeof payload.cliSessionToken ===
|
|
85
|
+
'string' &&
|
|
86
|
+
payload.cliSessionToken.trim().length > 0 &&
|
|
135
87
|
typeof payload.accountId === 'string' &&
|
|
136
88
|
typeof payload.userId === 'string');
|
|
137
89
|
}
|
package/build/auth-validate.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveFreshRemoteAuth } from './auth-session.js';
|
|
2
|
+
import { buildPostPlusClientCompatibilityHeaders, formatPostPlusClientUpgradeError, } from './client-compatibility.js';
|
|
2
3
|
export async function validateRemoteAuth() {
|
|
3
4
|
let auth = await resolveFreshRemoteAuth();
|
|
4
5
|
let response = await fetchWhoami(auth);
|
|
@@ -10,6 +11,10 @@ export async function validateRemoteAuth() {
|
|
|
10
11
|
}
|
|
11
12
|
const payload = (await response.json());
|
|
12
13
|
if (!response.ok) {
|
|
14
|
+
if ('code' in payload &&
|
|
15
|
+
payload.code === 'postplus_client_upgrade_required') {
|
|
16
|
+
throw new Error(formatPostPlusClientUpgradeError(payload));
|
|
17
|
+
}
|
|
13
18
|
throw new Error('error' in payload && typeof payload.error === 'string'
|
|
14
19
|
? payload.error
|
|
15
20
|
: 'Failed to validate remote PostPlus auth.');
|
|
@@ -36,12 +41,14 @@ export function formatAuthValidateReport(report) {
|
|
|
36
41
|
`Subscription: ${report.subscriptionStatus ?? 'unknown'}`,
|
|
37
42
|
].join('\n');
|
|
38
43
|
}
|
|
39
|
-
function fetchWhoami(input) {
|
|
44
|
+
async function fetchWhoami(input) {
|
|
45
|
+
const compatibilityHeaders = await buildPostPlusClientCompatibilityHeaders();
|
|
40
46
|
return fetch(`${input.apiBaseUrl}/api/postplus-cli/auth/whoami`, {
|
|
41
47
|
method: 'GET',
|
|
42
48
|
headers: {
|
|
43
49
|
accept: 'application/json',
|
|
44
|
-
|
|
50
|
+
...compatibilityHeaders,
|
|
51
|
+
authorization: `Bearer ${input.cliSessionToken}`,
|
|
45
52
|
},
|
|
46
53
|
signal: AbortSignal.timeout(15000),
|
|
47
54
|
});
|
package/build/auth.js
CHANGED
|
@@ -7,13 +7,11 @@ export async function generateAuthStatusReport() {
|
|
|
7
7
|
readLocalConfig(),
|
|
8
8
|
]);
|
|
9
9
|
return {
|
|
10
|
-
ok: sessionState.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
present: sessionState.accessToken.present,
|
|
16
|
-
maskedValue: maskSecret(sessionState.accessToken.value),
|
|
10
|
+
ok: sessionState.cliSessionToken.present && apiBaseUrlState.present,
|
|
11
|
+
cliSessionToken: {
|
|
12
|
+
source: sessionState.cliSessionToken.source,
|
|
13
|
+
present: sessionState.cliSessionToken.present,
|
|
14
|
+
maskedValue: maskSecret(sessionState.cliSessionToken.value),
|
|
17
15
|
},
|
|
18
16
|
apiBaseUrl: {
|
|
19
17
|
source: apiBaseUrlState.source,
|
|
@@ -24,29 +22,21 @@ export async function generateAuthStatusReport() {
|
|
|
24
22
|
path: getPostPlusConfigPath(),
|
|
25
23
|
exists: configExists,
|
|
26
24
|
accountId: config?.accountId?.trim() || null,
|
|
25
|
+
sessionExpiresAt: typeof config?.sessionExpiresAt === 'number'
|
|
26
|
+
? config.sessionExpiresAt
|
|
27
|
+
: null,
|
|
27
28
|
userEmail: typeof config?.userEmail === 'string' ? config.userEmail.trim() : null,
|
|
28
29
|
userId: config?.userId?.trim() || null,
|
|
29
30
|
},
|
|
30
|
-
refreshToken: {
|
|
31
|
-
source: sessionState.refreshToken.source,
|
|
32
|
-
present: sessionState.refreshToken.present,
|
|
33
|
-
maskedValue: maskSecret(sessionState.refreshToken.value),
|
|
34
|
-
},
|
|
35
31
|
};
|
|
36
32
|
}
|
|
37
33
|
export function formatAuthStatusReport(report) {
|
|
38
34
|
const lines = ['PostPlus CLI auth status', ''];
|
|
39
|
-
lines.push(report.
|
|
40
|
-
? `[PASS]
|
|
41
|
-
: '[FAIL]
|
|
42
|
-
lines.push(report.
|
|
43
|
-
? ` Value: ${report.
|
|
44
|
-
: ' Value: not configured');
|
|
45
|
-
lines.push(report.refreshToken.present
|
|
46
|
-
? `[PASS] Refresh token: present (${report.refreshToken.source})`
|
|
47
|
-
: '[FAIL] Refresh token: missing');
|
|
48
|
-
lines.push(report.refreshToken.maskedValue
|
|
49
|
-
? ` Value: ${report.refreshToken.maskedValue}`
|
|
35
|
+
lines.push(report.cliSessionToken.present
|
|
36
|
+
? `[PASS] CLI session token: present (${report.cliSessionToken.source})`
|
|
37
|
+
: '[FAIL] CLI session token: missing');
|
|
38
|
+
lines.push(report.cliSessionToken.maskedValue
|
|
39
|
+
? ` Value: ${report.cliSessionToken.maskedValue}`
|
|
50
40
|
: ' Value: not configured');
|
|
51
41
|
lines.push(report.apiBaseUrl.present
|
|
52
42
|
? `[PASS] PostPlus Cloud: configured (${report.apiBaseUrl.source})`
|
|
@@ -59,6 +49,9 @@ export function formatAuthStatusReport(report) {
|
|
|
59
49
|
: `[PASS] local config path: ${report.config.path}`);
|
|
60
50
|
lines.push(` Account: ${report.config.accountId ?? 'not bound'}`);
|
|
61
51
|
lines.push(` User: ${report.config.userEmail ?? report.config.userId ?? 'not bound'}`);
|
|
52
|
+
lines.push(` Expires: ${report.config.sessionExpiresAt
|
|
53
|
+
? new Date(report.config.sessionExpiresAt * 1000).toISOString()
|
|
54
|
+
: 'unknown'}`);
|
|
62
55
|
lines.push('', report.ok ? 'Auth status OK.' : 'Auth status incomplete.');
|
|
63
56
|
return lines.join('\n');
|
|
64
57
|
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { readLocalConfig, updateLocalConfig } from './local-state.js';
|
|
3
|
+
export const POSTPLUS_CLIENT_CONTRACT_VERSION = 1;
|
|
4
|
+
export const POSTPLUS_CLIENT_RUNTIME = 'postplus-cli';
|
|
5
|
+
export const POSTPLUS_CLIENT_COMPATIBILITY_HEADERS = {
|
|
6
|
+
cliVersion: 'x-postplus-cli-version',
|
|
7
|
+
contractVersion: 'x-postplus-client-contract-version',
|
|
8
|
+
runtime: 'x-postplus-client-runtime',
|
|
9
|
+
skillCatalogRevision: 'x-postplus-skill-catalog-revision',
|
|
10
|
+
skillName: 'x-postplus-skill-name',
|
|
11
|
+
};
|
|
12
|
+
export async function buildPostPlusClientCompatibilityHeaders(input = {}) {
|
|
13
|
+
const [cliVersion, config] = await Promise.all([
|
|
14
|
+
readCurrentCliVersion(),
|
|
15
|
+
readLocalConfig(),
|
|
16
|
+
]);
|
|
17
|
+
const headers = {
|
|
18
|
+
[POSTPLUS_CLIENT_COMPATIBILITY_HEADERS.cliVersion]: cliVersion,
|
|
19
|
+
[POSTPLUS_CLIENT_COMPATIBILITY_HEADERS.contractVersion]: String(POSTPLUS_CLIENT_CONTRACT_VERSION),
|
|
20
|
+
[POSTPLUS_CLIENT_COMPATIBILITY_HEADERS.runtime]: POSTPLUS_CLIENT_RUNTIME,
|
|
21
|
+
};
|
|
22
|
+
const skillCatalogRevision = config?.managedSkills?.revision?.trim();
|
|
23
|
+
const skillName = input.skillName?.trim();
|
|
24
|
+
if (skillCatalogRevision) {
|
|
25
|
+
headers[POSTPLUS_CLIENT_COMPATIBILITY_HEADERS.skillCatalogRevision] =
|
|
26
|
+
skillCatalogRevision;
|
|
27
|
+
}
|
|
28
|
+
if (skillName) {
|
|
29
|
+
headers[POSTPLUS_CLIENT_COMPATIBILITY_HEADERS.skillName] = skillName;
|
|
30
|
+
}
|
|
31
|
+
return headers;
|
|
32
|
+
}
|
|
33
|
+
export async function writeCurrentCliVersionToLocalConfig() {
|
|
34
|
+
const cliVersion = await readCurrentCliVersion();
|
|
35
|
+
await updateLocalConfig((current) => ({
|
|
36
|
+
...(current ?? {}),
|
|
37
|
+
cliVersion,
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
export async function readCurrentCliVersion() {
|
|
41
|
+
const packageJsonPath = new URL('../package.json', import.meta.url);
|
|
42
|
+
const raw = await readFile(packageJsonPath, 'utf8');
|
|
43
|
+
const parsed = JSON.parse(raw);
|
|
44
|
+
if (typeof parsed.version !== 'string' || !parsed.version.trim()) {
|
|
45
|
+
throw new Error('Could not read the current PostPlus CLI version.');
|
|
46
|
+
}
|
|
47
|
+
return parsed.version.trim();
|
|
48
|
+
}
|
|
49
|
+
export function formatPostPlusClientUpgradeError(payload) {
|
|
50
|
+
const record = payload && typeof payload === 'object' && !Array.isArray(payload)
|
|
51
|
+
? payload
|
|
52
|
+
: {};
|
|
53
|
+
const cliCommand = record.compatibility?.upgrade?.cli?.command ??
|
|
54
|
+
'npm install -g @postplus/cli';
|
|
55
|
+
const skillsCommand = record.compatibility?.upgrade?.skills?.command ?? 'postplus update';
|
|
56
|
+
const restart = record.compatibility?.upgrade?.restartAgentSession
|
|
57
|
+
? ' Then restart your agent session.'
|
|
58
|
+
: '';
|
|
59
|
+
return [
|
|
60
|
+
typeof record.error === 'string' && record.error.trim().length > 0
|
|
61
|
+
? record.error.trim()
|
|
62
|
+
: 'Your PostPlus CLI or PostPlus skills are out of date.',
|
|
63
|
+
`Update CLI: ${cliCommand}.`,
|
|
64
|
+
`Update skills: ${skillsCommand}.`,
|
|
65
|
+
restart.trim(),
|
|
66
|
+
]
|
|
67
|
+
.filter(Boolean)
|
|
68
|
+
.join(' ');
|
|
69
|
+
}
|
package/build/doctor.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveFreshRemoteAuth, } from './auth-session.js';
|
|
2
|
+
import { buildPostPlusClientCompatibilityHeaders, formatPostPlusClientUpgradeError, } from './client-compatibility.js';
|
|
2
3
|
import { resolveHostedBaseUrl } from './hosted-release.js';
|
|
3
4
|
import { formatLocalDependencyReport, generateLocalDependencyReport, } from './local-dependencies.js';
|
|
4
5
|
function createPass(id, label, detail, severity = 'required') {
|
|
@@ -184,15 +185,20 @@ function readReadinessCheckFailureLabel(value) {
|
|
|
184
185
|
: 'unknown check';
|
|
185
186
|
}
|
|
186
187
|
function readErrorMessage(payload, fallback) {
|
|
188
|
+
if (payload.code === 'postplus_client_upgrade_required') {
|
|
189
|
+
return formatPostPlusClientUpgradeError(payload);
|
|
190
|
+
}
|
|
187
191
|
return typeof payload.error === 'string' && payload.error.trim().length > 0
|
|
188
192
|
? payload.error
|
|
189
193
|
: fallback;
|
|
190
194
|
}
|
|
191
|
-
function requestWithAuth(input, path) {
|
|
195
|
+
async function requestWithAuth(input, path) {
|
|
196
|
+
const compatibilityHeaders = await buildPostPlusClientCompatibilityHeaders();
|
|
192
197
|
return fetch(`${input.apiBaseUrl}${path}`, {
|
|
193
198
|
headers: {
|
|
194
199
|
accept: 'application/json',
|
|
195
|
-
|
|
200
|
+
...compatibilityHeaders,
|
|
201
|
+
authorization: `Bearer ${input.cliSessionToken}`,
|
|
196
202
|
},
|
|
197
203
|
signal: AbortSignal.timeout(15000),
|
|
198
204
|
});
|
package/build/index.js
CHANGED
|
@@ -8,7 +8,8 @@ import { assertConfigFilePermissions } from './local-state.js';
|
|
|
8
8
|
import { POSTPLUS_SKILLS_INSTALL_COMMAND, loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
9
9
|
import { runPostPlusSkillUninstall, runPostPlusSkillUpdate, } from './skill-management.js';
|
|
10
10
|
import { formatStatusReport, generateStatusReport } from './status.js';
|
|
11
|
-
import { readCurrentCliVersion,
|
|
11
|
+
import { readCurrentCliVersion, } from './client-compatibility.js';
|
|
12
|
+
import { refreshUpdateCheckCache } from './update-check.js';
|
|
12
13
|
function printAuthHelp() {
|
|
13
14
|
process.stdout.write(`PostPlus CLI — auth commands
|
|
14
15
|
|
package/build/local-state.js
CHANGED
|
@@ -95,6 +95,7 @@ export async function clearLocalAuthState() {
|
|
|
95
95
|
delete next.accessToken;
|
|
96
96
|
delete next.accountId;
|
|
97
97
|
delete next.apiKey;
|
|
98
|
+
delete next.cliSessionToken;
|
|
98
99
|
delete next.machineId;
|
|
99
100
|
delete next.refreshToken;
|
|
100
101
|
delete next.sessionExpiresAt;
|
|
@@ -150,24 +151,19 @@ export async function setLocalApiBaseUrl(apiBaseUrl) {
|
|
|
150
151
|
}));
|
|
151
152
|
}
|
|
152
153
|
export async function setLocalSession(input) {
|
|
153
|
-
const
|
|
154
|
-
const refreshToken = input.refreshToken.trim();
|
|
154
|
+
const cliSessionToken = input.cliSessionToken.trim();
|
|
155
155
|
const apiBaseUrl = input.apiBaseUrl.trim().replace(/\/+$/, '');
|
|
156
|
-
if (
|
|
157
|
-
throw new Error('PostPlus CLI
|
|
158
|
-
}
|
|
159
|
-
if (refreshToken.length === 0) {
|
|
160
|
-
throw new Error('PostPlus CLI refresh token cannot be empty.');
|
|
156
|
+
if (cliSessionToken.length === 0) {
|
|
157
|
+
throw new Error('PostPlus CLI session token cannot be empty.');
|
|
161
158
|
}
|
|
162
159
|
if (apiBaseUrl.length === 0) {
|
|
163
160
|
throw new Error('POSTPLUS_API_BASE_URL cannot be empty.');
|
|
164
161
|
}
|
|
165
162
|
return updateLocalConfig((current) => ({
|
|
166
163
|
...omitLegacyAuthFields(current),
|
|
167
|
-
accessToken,
|
|
168
164
|
accountId: input.accountId,
|
|
169
165
|
apiBaseUrl,
|
|
170
|
-
|
|
166
|
+
cliSessionToken,
|
|
171
167
|
sessionExpiresAt: input.sessionExpiresAt,
|
|
172
168
|
userEmail: input.userEmail,
|
|
173
169
|
userId: input.userId,
|
|
@@ -182,6 +178,22 @@ export async function hasLocalConfigFile() {
|
|
|
182
178
|
return false;
|
|
183
179
|
}
|
|
184
180
|
}
|
|
181
|
+
export async function resolveCliSessionTokenState() {
|
|
182
|
+
const config = await readLocalConfig();
|
|
183
|
+
const configValue = config?.cliSessionToken?.trim();
|
|
184
|
+
if (configValue && configValue.length > 0) {
|
|
185
|
+
return {
|
|
186
|
+
source: 'config',
|
|
187
|
+
present: true,
|
|
188
|
+
value: configValue,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
source: 'missing',
|
|
193
|
+
present: false,
|
|
194
|
+
value: null,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
185
197
|
export async function resolveAccessTokenState() {
|
|
186
198
|
const config = await readLocalConfig();
|
|
187
199
|
const configValue = config?.accessToken?.trim();
|
|
@@ -215,13 +227,9 @@ export async function resolveRefreshTokenState() {
|
|
|
215
227
|
};
|
|
216
228
|
}
|
|
217
229
|
export async function resolveLocalSessionState() {
|
|
218
|
-
const
|
|
219
|
-
resolveAccessTokenState(),
|
|
220
|
-
resolveRefreshTokenState(),
|
|
221
|
-
]);
|
|
230
|
+
const cliSessionToken = await resolveCliSessionTokenState();
|
|
222
231
|
return {
|
|
223
|
-
|
|
224
|
-
refreshToken,
|
|
232
|
+
cliSessionToken,
|
|
225
233
|
};
|
|
226
234
|
}
|
|
227
235
|
export async function resolveApiBaseUrlState() {
|
|
@@ -258,7 +266,7 @@ export function maskSecret(value) {
|
|
|
258
266
|
return `${value.slice(0, 4)}…${value.slice(-4)}`;
|
|
259
267
|
}
|
|
260
268
|
function omitLegacyAuthFields(current) {
|
|
261
|
-
const { apiKey: _apiKey, machineId: _machineId, ...rest } = (current ?? {});
|
|
269
|
+
const { apiKey: _apiKey, accessToken: _accessToken, machineId: _machineId, refreshToken: _refreshToken, ...rest } = (current ?? {});
|
|
262
270
|
return rest;
|
|
263
271
|
}
|
|
264
272
|
function normalizeSkillNames(values) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { runCommand, runInteractiveCommand } from './command-runner.js';
|
|
2
|
+
import { writeCurrentCliVersionToLocalConfig } from './client-compatibility.js';
|
|
2
3
|
import { clearManagedSkillBaseline, readManagedSkillBaseline, writeManagedSkillBaseline, } from './local-state.js';
|
|
3
4
|
import { POSTPLUS_SKILLS_AGENT_TARGETS, POSTPLUS_SKILLS_INSTALL_COMMAND, POSTPLUS_SKILLS_REPO, loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
4
5
|
const NPX_SKILLS = ['-y', 'skills'];
|
|
@@ -26,6 +27,7 @@ export async function runPostPlusSkillUpdate(dependencies = {
|
|
|
26
27
|
revision: catalog.revision,
|
|
27
28
|
skillNames,
|
|
28
29
|
});
|
|
30
|
+
await writeCurrentCliVersionToLocalConfig();
|
|
29
31
|
return 0;
|
|
30
32
|
}
|
|
31
33
|
export async function runPostPlusSkillUninstall(dependencies = {
|
package/build/status.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { formatAuthStatusReport, generateAuthStatusReport, } from './auth.js';
|
|
2
|
+
import { writeCurrentCliVersionToLocalConfig } from './client-compatibility.js';
|
|
2
3
|
import { formatDoctorReport, generateDoctorReport, } from './doctor.js';
|
|
3
4
|
import { formatSkillInstallStatusReport, generateSkillInstallStatusReport, } from './skill-management.js';
|
|
4
5
|
import { formatUpdateStatusReport, generateUpdateStatusReport, } from './update-check.js';
|
|
@@ -6,6 +7,7 @@ export async function generateStatusReport() {
|
|
|
6
7
|
return generateStatusReportWithDependencies();
|
|
7
8
|
}
|
|
8
9
|
export async function generateStatusReportWithDependencies(dependencies = {}) {
|
|
10
|
+
await writeCurrentCliVersionToLocalConfig();
|
|
9
11
|
const generateAuthStatus = dependencies.generateAuthStatus ?? generateAuthStatusReport;
|
|
10
12
|
const generateDoctor = dependencies.generateDoctor ?? generateDoctorReport;
|
|
11
13
|
const generateSkillStatus = dependencies.generateSkillStatus ?? generateSkillInstallStatusReport;
|
package/build/update-check.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
|
+
import { readCurrentCliVersion } from './client-compatibility.js';
|
|
3
4
|
import { getPostPlusConfigDir, readManagedSkillBaseline, } from './local-state.js';
|
|
4
5
|
import { POSTPLUS_SKILLS_REPO, loadPublicSkillCatalog, } from './skill-catalog.js';
|
|
5
6
|
const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
@@ -125,15 +126,6 @@ function buildUpdateReport(input) {
|
|
|
125
126
|
warning: null,
|
|
126
127
|
};
|
|
127
128
|
}
|
|
128
|
-
export async function readCurrentCliVersion() {
|
|
129
|
-
const packageJsonPath = new URL('../package.json', import.meta.url);
|
|
130
|
-
const raw = await readFile(packageJsonPath, 'utf8');
|
|
131
|
-
const parsed = JSON.parse(raw);
|
|
132
|
-
if (typeof parsed.version !== 'string' || !parsed.version.trim()) {
|
|
133
|
-
throw new Error('Could not read the current PostPlus CLI version.');
|
|
134
|
-
}
|
|
135
|
-
return parsed.version.trim();
|
|
136
|
-
}
|
|
137
129
|
async function fetchLatestCliVersion(fetchFn) {
|
|
138
130
|
const response = await fetchFn(NPM_LATEST_URL, {
|
|
139
131
|
headers: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@postplus/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "PostPlus CLI for PostPlus Cloud auth, status, and diagnostics.",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"build/auth-session.js",
|
|
12
12
|
"build/auth-validate.js",
|
|
13
13
|
"build/auth.js",
|
|
14
|
+
"build/client-compatibility.js",
|
|
14
15
|
"build/command-runner.js",
|
|
15
16
|
"build/doctor.js",
|
|
16
17
|
"build/hosted-release.js",
|