ugly-app 0.1.418 → 0.1.419
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/dist/cli/doctor.d.ts +11 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +38 -49
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/probeAuth.d.ts +44 -0
- package/dist/cli/probeAuth.d.ts.map +1 -0
- package/dist/cli/probeAuth.js +250 -0
- package/dist/cli/probeAuth.js.map +1 -0
- package/dist/cli/version.d.ts +1 -1
- package/dist/cli/version.js +1 -1
- package/package.json +1 -1
- package/src/cli/doctor.ts +48 -49
- package/src/cli/probeAuth.ts +312 -0
- package/src/cli/version.ts +1 -1
- package/templates/.claude/skills/bot-swarm/SKILL.md +15 -1
package/dist/cli/doctor.d.ts
CHANGED
|
@@ -8,5 +8,15 @@
|
|
|
8
8
|
* lines and a trailing "next steps" section so an agent can parse it
|
|
9
9
|
* by grep if it needs to.
|
|
10
10
|
*/
|
|
11
|
-
export
|
|
11
|
+
export interface RunDoctorOptions {
|
|
12
|
+
/** Override for tests. Defaults to process.cwd(). */
|
|
13
|
+
cwd?: string;
|
|
14
|
+
/** Override for tests. Defaults to os.homedir(). */
|
|
15
|
+
homeDir?: string;
|
|
16
|
+
/** Skip the live DataProxy / migration probes. Defaults to false. */
|
|
17
|
+
skipLiveChecks?: boolean;
|
|
18
|
+
/** Sink for output lines. Defaults to console.log. */
|
|
19
|
+
print?: (line: string) => void;
|
|
20
|
+
}
|
|
21
|
+
export declare function runDoctor(options?: RunDoctorOptions): Promise<void>;
|
|
12
22
|
//# sourceMappingURL=doctor.d.ts.map
|
package/dist/cli/doctor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAUH,MAAM,WAAW,gBAAgB;IAC/B,qDAAqD;IACrD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oDAAoD;IACpD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,sDAAsD;IACtD,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,wBAAsB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0I7E"}
|
package/dist/cli/doctor.js
CHANGED
|
@@ -9,11 +9,15 @@
|
|
|
9
9
|
* by grep if it needs to.
|
|
10
10
|
*/
|
|
11
11
|
import fs from 'fs';
|
|
12
|
+
import os from 'os';
|
|
12
13
|
import path from 'path';
|
|
13
14
|
import { readUglyAppConfig } from './uglyappConfig.js';
|
|
14
|
-
import {
|
|
15
|
-
export async function runDoctor() {
|
|
16
|
-
const cwd = process.cwd();
|
|
15
|
+
import { probeAuth } from './probeAuth.js';
|
|
16
|
+
export async function runDoctor(options = {}) {
|
|
17
|
+
const cwd = options.cwd ?? process.cwd();
|
|
18
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
19
|
+
const print = options.print ?? ((s) => console.log(s));
|
|
20
|
+
const skipLive = options.skipLiveChecks ?? false;
|
|
17
21
|
const lines = [];
|
|
18
22
|
const nextSteps = [];
|
|
19
23
|
// 1. .uglyapp
|
|
@@ -33,44 +37,19 @@ export async function runDoctor() {
|
|
|
33
37
|
detail: `projectId=${config.projectId} localPort=${config.localPort}${config.domain ? ` domain=${config.domain}` : ''}`,
|
|
34
38
|
});
|
|
35
39
|
}
|
|
36
|
-
// 2. Auth tokens
|
|
40
|
+
// 2. Auth tokens — global user identity + per-project cached tokens.
|
|
41
|
+
// probeAuth() is the single source of truth so test code can exercise
|
|
42
|
+
// the same logic without spinning up a DataProxy.
|
|
37
43
|
const isLocal = process.env['UGLY_BOT_LOCAL'] === '1';
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
49
|
-
lines.push({
|
|
50
|
-
status: isLocal ? 'info' : 'warn',
|
|
51
|
-
label: 'auth (cloud)',
|
|
52
|
-
detail: 'no project token for ugly.bot cloud',
|
|
53
|
-
});
|
|
54
|
-
if (!isLocal)
|
|
55
|
-
nextSteps.push('Run `npx ugly-app login` to mint a project token');
|
|
56
|
-
}
|
|
57
|
-
if (localToken) {
|
|
58
|
-
lines.push({
|
|
59
|
-
status: 'ok',
|
|
60
|
-
label: 'auth (local)',
|
|
61
|
-
detail: `local project token present (${localToken.slice(0, 8)}…)`,
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
lines.push({
|
|
66
|
-
status: isLocal ? 'warn' : 'info',
|
|
67
|
-
label: 'auth (local)',
|
|
68
|
-
detail: 'no local ugly.bot token',
|
|
69
|
-
});
|
|
70
|
-
if (isLocal)
|
|
71
|
-
nextSteps.push('Run `UGLY_BOT_LOCAL=1 npx ugly-app login` to mint a local token');
|
|
72
|
-
}
|
|
73
|
-
}
|
|
44
|
+
const authProbe = probeAuth({
|
|
45
|
+
homeDir,
|
|
46
|
+
projectId: config?.projectId ?? null,
|
|
47
|
+
isLocal,
|
|
48
|
+
});
|
|
49
|
+
for (const l of authProbe.lines)
|
|
50
|
+
lines.push(l);
|
|
51
|
+
for (const s of authProbe.nextSteps)
|
|
52
|
+
nextSteps.push(s);
|
|
74
53
|
// 3. DataProxy / env inheritance (pre-ensureInfra snapshot)
|
|
75
54
|
const envUrl = process.env['DATA_PROXY_URL'];
|
|
76
55
|
const envToken = process.env['DATA_PROXY_TOKEN'];
|
|
@@ -117,7 +96,7 @@ export async function runDoctor() {
|
|
|
117
96
|
// 5. Live DataProxy connection check (only when .uglyapp + some token).
|
|
118
97
|
// Gated on config presence so doctor is fast in a half-configured app
|
|
119
98
|
// instead of blocking on a tunnel handshake that can't succeed.
|
|
120
|
-
if (config) {
|
|
99
|
+
if (config && !skipLive) {
|
|
121
100
|
const connectCheck = await safeConnectCheck(cwd);
|
|
122
101
|
lines.push({
|
|
123
102
|
status: connectCheck.ok ? 'ok' : 'error',
|
|
@@ -140,8 +119,8 @@ export async function runDoctor() {
|
|
|
140
119
|
}
|
|
141
120
|
}
|
|
142
121
|
// Print the report
|
|
143
|
-
|
|
144
|
-
|
|
122
|
+
print('ugly-app doctor — child app diagnostics');
|
|
123
|
+
print('═'.repeat(50));
|
|
145
124
|
for (const l of lines) {
|
|
146
125
|
const icon = l.status === 'ok'
|
|
147
126
|
? '✓'
|
|
@@ -150,17 +129,27 @@ export async function runDoctor() {
|
|
|
150
129
|
: l.status === 'error'
|
|
151
130
|
? '✗'
|
|
152
131
|
: '·';
|
|
153
|
-
|
|
132
|
+
print(`${icon} ${l.label.padEnd(18)} ${l.detail}`);
|
|
133
|
+
}
|
|
134
|
+
// Agent-ready summary — a single grep-friendly line so headless
|
|
135
|
+
// tooling (the bot-swarm skill, evals, CI) can decide whether to
|
|
136
|
+
// proceed without parsing the human-formatted lines above.
|
|
137
|
+
print('');
|
|
138
|
+
if (authProbe.agentReady) {
|
|
139
|
+
print('agent-ready: yes');
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
print(`agent-ready: no — ${authProbe.agentBlockedReason ?? 'auth incomplete'}`);
|
|
154
143
|
}
|
|
155
144
|
if (nextSteps.length > 0) {
|
|
156
|
-
|
|
157
|
-
|
|
145
|
+
print('');
|
|
146
|
+
print('Next steps:');
|
|
158
147
|
for (const s of nextSteps)
|
|
159
|
-
|
|
148
|
+
print(` • ${s}`);
|
|
160
149
|
}
|
|
161
150
|
else {
|
|
162
|
-
|
|
163
|
-
|
|
151
|
+
print('');
|
|
152
|
+
print('Next steps: nothing obvious — env looks healthy.');
|
|
164
153
|
}
|
|
165
154
|
}
|
|
166
155
|
function safeReadConfig(cwd) {
|
package/dist/cli/doctor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type AuthStatus = 'ok' | 'warn' | 'error' | 'info';
|
|
2
|
+
export interface AuthLine {
|
|
3
|
+
status: AuthStatus;
|
|
4
|
+
label: string;
|
|
5
|
+
detail: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ProbeAuthInput {
|
|
8
|
+
homeDir: string;
|
|
9
|
+
projectId: string | null;
|
|
10
|
+
/** True when running against localhost ugly.bot (UGLY_BOT_LOCAL=1). */
|
|
11
|
+
isLocal: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface ProbeAuthResult {
|
|
14
|
+
/** Human-readable lines, in the order they should be printed. */
|
|
15
|
+
lines: AuthLine[];
|
|
16
|
+
/** Hints to show under "Next steps:". */
|
|
17
|
+
nextSteps: string[];
|
|
18
|
+
/**
|
|
19
|
+
* True when the on-disk state is sufficient for a headless agent to
|
|
20
|
+
* proceed: at minimum a non-expired global user token exists, OR a
|
|
21
|
+
* non-expired project token of the right kind (cloud/local) exists.
|
|
22
|
+
* Project tokens are auto-minted from the global user token on first
|
|
23
|
+
* run, so the global user token is the real prerequisite.
|
|
24
|
+
*/
|
|
25
|
+
agentReady: boolean;
|
|
26
|
+
/** When agentReady is false, a one-line reason. */
|
|
27
|
+
agentBlockedReason?: string;
|
|
28
|
+
/**
|
|
29
|
+
* The token an agent should put in `UGLY_BOT_TOKEN`, if one is
|
|
30
|
+
* available. Prefers the project token (matches what the framework
|
|
31
|
+
* uses at runtime); falls back to the global user token.
|
|
32
|
+
*/
|
|
33
|
+
agentToken?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Inspects the JWT exp claim without verifying the signature. Returns:
|
|
37
|
+
* - 'valid' : exp present and at least 60s in the future
|
|
38
|
+
* - 'expired' : exp present and in the past (or within 60s)
|
|
39
|
+
* - 'unknown' : token doesn't have a parseable exp — treat as valid
|
|
40
|
+
* since some service tokens are intentionally non-expiring
|
|
41
|
+
*/
|
|
42
|
+
export declare function inspectJwt(token: string): 'valid' | 'expired' | 'unknown';
|
|
43
|
+
export declare function probeAuth(input: ProbeAuthInput): ProbeAuthResult;
|
|
44
|
+
//# sourceMappingURL=probeAuth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probeAuth.d.ts","sourceRoot":"","sources":["../../src/cli/probeAuth.ts"],"names":[],"mappings":"AAoBA,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAE1D,MAAM,WAAW,QAAQ;IACvB,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,uEAAuE;IACvE,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,yCAAyC;IACzC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB;;;;;;OAMG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB,mDAAmD;IACnD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAyBD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAYzE;AAyCD,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,eAAe,CA0KhE"}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure auth-state probe used by `ugly-app doctor` (and by tests).
|
|
3
|
+
*
|
|
4
|
+
* Reads the on-disk auth files under a given home directory and returns
|
|
5
|
+
* a structured report. No side effects, no DataProxy, no network.
|
|
6
|
+
*
|
|
7
|
+
* ~/.ugly-bot/auth.json — global user identity ({ token, userId, serverUrl })
|
|
8
|
+
* ~/.ugly-bot/<projectId>.json — cloud project token ({ token })
|
|
9
|
+
* ~/.ugly-bot/<projectId>.local.json — local project token ({ token })
|
|
10
|
+
*
|
|
11
|
+
* The doctor previously only checked the project-level files and
|
|
12
|
+
* reported "no project token for cloud" as a warning even when a valid
|
|
13
|
+
* global user identity existed (in which case `dev` / `auth:create-bot`
|
|
14
|
+
* will mint a project token automatically on first run). Headless
|
|
15
|
+
* agents reading the report concluded auth was broken and got stuck on
|
|
16
|
+
* `npx ugly-app login`, which needs a browser.
|
|
17
|
+
*/
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
function readJson(filePath) {
|
|
21
|
+
try {
|
|
22
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
23
|
+
try {
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
if (parsed && typeof parsed === 'object') {
|
|
26
|
+
return { data: parsed, malformed: false };
|
|
27
|
+
}
|
|
28
|
+
return { data: null, malformed: true };
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return { data: null, malformed: true };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return { data: null, malformed: false };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Inspects the JWT exp claim without verifying the signature. Returns:
|
|
40
|
+
* - 'valid' : exp present and at least 60s in the future
|
|
41
|
+
* - 'expired' : exp present and in the past (or within 60s)
|
|
42
|
+
* - 'unknown' : token doesn't have a parseable exp — treat as valid
|
|
43
|
+
* since some service tokens are intentionally non-expiring
|
|
44
|
+
*/
|
|
45
|
+
export function inspectJwt(token) {
|
|
46
|
+
const parts = token.split('.');
|
|
47
|
+
if (parts.length !== 3)
|
|
48
|
+
return 'unknown';
|
|
49
|
+
try {
|
|
50
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf-8'));
|
|
51
|
+
if (typeof payload.exp !== 'number')
|
|
52
|
+
return 'unknown';
|
|
53
|
+
return Date.now() / 1000 < payload.exp - 60 ? 'valid' : 'expired';
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return 'unknown';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function readGlobalAuth(homeDir) {
|
|
60
|
+
const file = path.join(homeDir, '.ugly-bot', 'auth.json');
|
|
61
|
+
const { data, malformed } = readJson(file);
|
|
62
|
+
if (!data)
|
|
63
|
+
return { auth: null, malformed };
|
|
64
|
+
if (typeof data.token !== 'string' ||
|
|
65
|
+
typeof data.userId !== 'string' ||
|
|
66
|
+
typeof data.serverUrl !== 'string') {
|
|
67
|
+
return { auth: null, malformed: true };
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
auth: { token: data.token, userId: data.userId, serverUrl: data.serverUrl },
|
|
71
|
+
malformed: false,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function readProjectToken(homeDir, projectId, mode) {
|
|
75
|
+
const suffix = mode === 'local' ? '.local.json' : '.json';
|
|
76
|
+
const file = path.join(homeDir, '.ugly-bot', `${projectId}${suffix}`);
|
|
77
|
+
const { data, malformed } = readJson(file);
|
|
78
|
+
if (!data)
|
|
79
|
+
return { token: null, malformed };
|
|
80
|
+
if (typeof data.token !== 'string')
|
|
81
|
+
return { token: null, malformed: true };
|
|
82
|
+
return { token: data.token, malformed: false };
|
|
83
|
+
}
|
|
84
|
+
export function probeAuth(input) {
|
|
85
|
+
const { homeDir, projectId, isLocal } = input;
|
|
86
|
+
const lines = [];
|
|
87
|
+
const nextSteps = [];
|
|
88
|
+
// 1. Global user identity (~/.ugly-bot/auth.json).
|
|
89
|
+
const { auth: globalAuth, malformed: globalMalformed } = readGlobalAuth(homeDir);
|
|
90
|
+
let globalValid = false;
|
|
91
|
+
if (globalAuth) {
|
|
92
|
+
const exp = inspectJwt(globalAuth.token);
|
|
93
|
+
if (exp === 'expired') {
|
|
94
|
+
lines.push({
|
|
95
|
+
status: 'warn',
|
|
96
|
+
label: 'auth (user)',
|
|
97
|
+
detail: `token expired (userId=${globalAuth.userId})`,
|
|
98
|
+
});
|
|
99
|
+
nextSteps.push('Run `npx ugly-app login` to refresh your global user token');
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
globalValid = true;
|
|
103
|
+
lines.push({
|
|
104
|
+
status: 'ok',
|
|
105
|
+
label: 'auth (user)',
|
|
106
|
+
detail: `signed in as ${globalAuth.userId} (${globalAuth.serverUrl})`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (globalMalformed) {
|
|
111
|
+
lines.push({
|
|
112
|
+
status: 'error',
|
|
113
|
+
label: 'auth (user)',
|
|
114
|
+
detail: '~/.ugly-bot/auth.json is malformed or missing required fields',
|
|
115
|
+
});
|
|
116
|
+
nextSteps.push('Delete ~/.ugly-bot/auth.json and re-run `npx ugly-app login`');
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
lines.push({
|
|
120
|
+
status: 'error',
|
|
121
|
+
label: 'auth (user)',
|
|
122
|
+
detail: 'not signed in (no ~/.ugly-bot/auth.json)',
|
|
123
|
+
});
|
|
124
|
+
nextSteps.push('Run `npx ugly-app login` once on the host (interactive, opens a browser) — required even for headless agents');
|
|
125
|
+
}
|
|
126
|
+
// 2. Project tokens. Without a .uglyapp we have no projectId to look up.
|
|
127
|
+
let cloudToken = null;
|
|
128
|
+
let cloudExpired = false;
|
|
129
|
+
let localToken = null;
|
|
130
|
+
let localExpired = false;
|
|
131
|
+
if (projectId) {
|
|
132
|
+
const cloud = readProjectToken(homeDir, projectId, 'cloud');
|
|
133
|
+
const local = readProjectToken(homeDir, projectId, 'local');
|
|
134
|
+
if (cloud.token) {
|
|
135
|
+
const exp = inspectJwt(cloud.token);
|
|
136
|
+
if (exp === 'expired') {
|
|
137
|
+
cloudExpired = true;
|
|
138
|
+
lines.push({
|
|
139
|
+
status: 'warn',
|
|
140
|
+
label: 'auth (cloud)',
|
|
141
|
+
detail: `cached project token expired (${cloud.token.slice(0, 8)}…)`,
|
|
142
|
+
});
|
|
143
|
+
if (!isLocal && globalValid) {
|
|
144
|
+
nextSteps.push('Stale cloud project token — `ugly-app dev` will re-mint it from your user token');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
cloudToken = cloud.token;
|
|
149
|
+
lines.push({
|
|
150
|
+
status: 'ok',
|
|
151
|
+
label: 'auth (cloud)',
|
|
152
|
+
detail: `project token present (${cloud.token.slice(0, 8)}…)`,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
else if (cloud.malformed) {
|
|
157
|
+
lines.push({
|
|
158
|
+
status: 'warn',
|
|
159
|
+
label: 'auth (cloud)',
|
|
160
|
+
detail: `~/.ugly-bot/${projectId}.json is malformed`,
|
|
161
|
+
});
|
|
162
|
+
nextSteps.push(`Delete ~/.ugly-bot/${projectId}.json and re-run \`npx ugly-app dev\` to re-mint`);
|
|
163
|
+
}
|
|
164
|
+
else if (globalValid && !isLocal) {
|
|
165
|
+
// Common case: never run before. Don't scare the agent — it's auto-minted.
|
|
166
|
+
lines.push({
|
|
167
|
+
status: 'info',
|
|
168
|
+
label: 'auth (cloud)',
|
|
169
|
+
detail: 'no cached project token — will be minted from user token on first run',
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
lines.push({
|
|
174
|
+
status: isLocal ? 'info' : 'warn',
|
|
175
|
+
label: 'auth (cloud)',
|
|
176
|
+
detail: 'no project token for ugly.bot cloud',
|
|
177
|
+
});
|
|
178
|
+
if (!isLocal && !globalValid) {
|
|
179
|
+
// The "log in first" hint already came from the user line; don't duplicate.
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (local.token) {
|
|
183
|
+
const exp = inspectJwt(local.token);
|
|
184
|
+
if (exp === 'expired') {
|
|
185
|
+
localExpired = true;
|
|
186
|
+
lines.push({
|
|
187
|
+
status: isLocal ? 'warn' : 'info',
|
|
188
|
+
label: 'auth (local)',
|
|
189
|
+
detail: `cached local token expired (${local.token.slice(0, 8)}…)`,
|
|
190
|
+
});
|
|
191
|
+
if (isLocal) {
|
|
192
|
+
nextSteps.push('Stale local project token — `UGLY_BOT_LOCAL=1 npx ugly-app dev` will re-mint it');
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
localToken = local.token;
|
|
197
|
+
lines.push({
|
|
198
|
+
status: 'ok',
|
|
199
|
+
label: 'auth (local)',
|
|
200
|
+
detail: `local project token present (${local.token.slice(0, 8)}…)`,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else if (local.malformed) {
|
|
205
|
+
lines.push({
|
|
206
|
+
status: 'warn',
|
|
207
|
+
label: 'auth (local)',
|
|
208
|
+
detail: `~/.ugly-bot/${projectId}.local.json is malformed`,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
lines.push({
|
|
213
|
+
status: isLocal ? (globalValid ? 'info' : 'warn') : 'info',
|
|
214
|
+
label: 'auth (local)',
|
|
215
|
+
detail: isLocal && globalValid
|
|
216
|
+
? 'no cached local project token — will be minted on first local run'
|
|
217
|
+
: 'no local ugly.bot token',
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// 3. Agent-ready summary.
|
|
222
|
+
// For headless tooling, the global user token is what unblocks
|
|
223
|
+
// everything: project tokens are derived. If we're in local mode we
|
|
224
|
+
// still want the global token because the local project token is
|
|
225
|
+
// minted from it.
|
|
226
|
+
let agentReady = false;
|
|
227
|
+
let agentBlockedReason;
|
|
228
|
+
let agentToken;
|
|
229
|
+
const liveCloud = cloudToken && !cloudExpired ? cloudToken : null;
|
|
230
|
+
const liveLocal = localToken && !localExpired ? localToken : null;
|
|
231
|
+
if (globalValid) {
|
|
232
|
+
agentReady = true;
|
|
233
|
+
agentToken = isLocal ? (liveLocal ?? globalAuth?.token) : (liveCloud ?? globalAuth?.token);
|
|
234
|
+
}
|
|
235
|
+
else if (liveCloud || liveLocal) {
|
|
236
|
+
// Edge case: project tokens cached but global identity gone. The
|
|
237
|
+
// project tokens still work for `auth:create-bot` etc., so don't
|
|
238
|
+
// block, but flag it.
|
|
239
|
+
agentReady = true;
|
|
240
|
+
agentToken = isLocal ? (liveLocal ?? liveCloud ?? undefined) : (liveCloud ?? liveLocal ?? undefined);
|
|
241
|
+
}
|
|
242
|
+
else {
|
|
243
|
+
agentReady = false;
|
|
244
|
+
agentBlockedReason = globalAuth
|
|
245
|
+
? 'global user token expired — run `npx ugly-app login`'
|
|
246
|
+
: 'not signed in — run `npx ugly-app login` on the host';
|
|
247
|
+
}
|
|
248
|
+
return { lines, nextSteps, agentReady, agentBlockedReason, agentToken };
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=probeAuth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"probeAuth.js","sourceRoot":"","sources":["../../src/cli/probeAuth.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.1.
|
|
1
|
+
export declare const CLI_VERSION = "0.1.419";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/cli/version.js
CHANGED
package/package.json
CHANGED
package/src/cli/doctor.ts
CHANGED
|
@@ -10,18 +10,29 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import fs from 'fs';
|
|
13
|
+
import os from 'os';
|
|
13
14
|
import path from 'path';
|
|
14
15
|
import { readUglyAppConfig } from './uglyappConfig.js';
|
|
15
|
-
import {
|
|
16
|
+
import { probeAuth, type AuthLine } from './probeAuth.js';
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
type DoctorLine = AuthLine;
|
|
19
|
+
|
|
20
|
+
export interface RunDoctorOptions {
|
|
21
|
+
/** Override for tests. Defaults to process.cwd(). */
|
|
22
|
+
cwd?: string;
|
|
23
|
+
/** Override for tests. Defaults to os.homedir(). */
|
|
24
|
+
homeDir?: string;
|
|
25
|
+
/** Skip the live DataProxy / migration probes. Defaults to false. */
|
|
26
|
+
skipLiveChecks?: boolean;
|
|
27
|
+
/** Sink for output lines. Defaults to console.log. */
|
|
28
|
+
print?: (line: string) => void;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
|
-
export async function runDoctor(): Promise<void> {
|
|
24
|
-
const cwd = process.cwd();
|
|
31
|
+
export async function runDoctor(options: RunDoctorOptions = {}): Promise<void> {
|
|
32
|
+
const cwd = options.cwd ?? process.cwd();
|
|
33
|
+
const homeDir = options.homeDir ?? os.homedir();
|
|
34
|
+
const print = options.print ?? ((s: string) => console.log(s));
|
|
35
|
+
const skipLive = options.skipLiveChecks ?? false;
|
|
25
36
|
const lines: DoctorLine[] = [];
|
|
26
37
|
const nextSteps: string[] = [];
|
|
27
38
|
|
|
@@ -42,40 +53,17 @@ export async function runDoctor(): Promise<void> {
|
|
|
42
53
|
});
|
|
43
54
|
}
|
|
44
55
|
|
|
45
|
-
// 2. Auth tokens
|
|
56
|
+
// 2. Auth tokens — global user identity + per-project cached tokens.
|
|
57
|
+
// probeAuth() is the single source of truth so test code can exercise
|
|
58
|
+
// the same logic without spinning up a DataProxy.
|
|
46
59
|
const isLocal = process.env['UGLY_BOT_LOCAL'] === '1';
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
detail: `project token present (${cloudToken.slice(0, 8)}…)`,
|
|
55
|
-
});
|
|
56
|
-
} else {
|
|
57
|
-
lines.push({
|
|
58
|
-
status: isLocal ? 'info' : 'warn',
|
|
59
|
-
label: 'auth (cloud)',
|
|
60
|
-
detail: 'no project token for ugly.bot cloud',
|
|
61
|
-
});
|
|
62
|
-
if (!isLocal) nextSteps.push('Run `npx ugly-app login` to mint a project token');
|
|
63
|
-
}
|
|
64
|
-
if (localToken) {
|
|
65
|
-
lines.push({
|
|
66
|
-
status: 'ok',
|
|
67
|
-
label: 'auth (local)',
|
|
68
|
-
detail: `local project token present (${localToken.slice(0, 8)}…)`,
|
|
69
|
-
});
|
|
70
|
-
} else {
|
|
71
|
-
lines.push({
|
|
72
|
-
status: isLocal ? 'warn' : 'info',
|
|
73
|
-
label: 'auth (local)',
|
|
74
|
-
detail: 'no local ugly.bot token',
|
|
75
|
-
});
|
|
76
|
-
if (isLocal) nextSteps.push('Run `UGLY_BOT_LOCAL=1 npx ugly-app login` to mint a local token');
|
|
77
|
-
}
|
|
78
|
-
}
|
|
60
|
+
const authProbe = probeAuth({
|
|
61
|
+
homeDir,
|
|
62
|
+
projectId: config?.projectId ?? null,
|
|
63
|
+
isLocal,
|
|
64
|
+
});
|
|
65
|
+
for (const l of authProbe.lines) lines.push(l);
|
|
66
|
+
for (const s of authProbe.nextSteps) nextSteps.push(s);
|
|
79
67
|
|
|
80
68
|
// 3. DataProxy / env inheritance (pre-ensureInfra snapshot)
|
|
81
69
|
const envUrl = process.env['DATA_PROXY_URL'];
|
|
@@ -122,7 +110,7 @@ export async function runDoctor(): Promise<void> {
|
|
|
122
110
|
// 5. Live DataProxy connection check (only when .uglyapp + some token).
|
|
123
111
|
// Gated on config presence so doctor is fast in a half-configured app
|
|
124
112
|
// instead of blocking on a tunnel handshake that can't succeed.
|
|
125
|
-
if (config) {
|
|
113
|
+
if (config && !skipLive) {
|
|
126
114
|
const connectCheck = await safeConnectCheck(cwd);
|
|
127
115
|
lines.push({
|
|
128
116
|
status: connectCheck.ok ? 'ok' : 'error',
|
|
@@ -146,8 +134,8 @@ export async function runDoctor(): Promise<void> {
|
|
|
146
134
|
}
|
|
147
135
|
|
|
148
136
|
// Print the report
|
|
149
|
-
|
|
150
|
-
|
|
137
|
+
print('ugly-app doctor — child app diagnostics');
|
|
138
|
+
print('═'.repeat(50));
|
|
151
139
|
for (const l of lines) {
|
|
152
140
|
const icon =
|
|
153
141
|
l.status === 'ok'
|
|
@@ -157,15 +145,26 @@ export async function runDoctor(): Promise<void> {
|
|
|
157
145
|
: l.status === 'error'
|
|
158
146
|
? '✗'
|
|
159
147
|
: '·';
|
|
160
|
-
|
|
148
|
+
print(`${icon} ${l.label.padEnd(18)} ${l.detail}`);
|
|
161
149
|
}
|
|
150
|
+
|
|
151
|
+
// Agent-ready summary — a single grep-friendly line so headless
|
|
152
|
+
// tooling (the bot-swarm skill, evals, CI) can decide whether to
|
|
153
|
+
// proceed without parsing the human-formatted lines above.
|
|
154
|
+
print('');
|
|
155
|
+
if (authProbe.agentReady) {
|
|
156
|
+
print('agent-ready: yes');
|
|
157
|
+
} else {
|
|
158
|
+
print(`agent-ready: no — ${authProbe.agentBlockedReason ?? 'auth incomplete'}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
162
161
|
if (nextSteps.length > 0) {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
for (const s of nextSteps)
|
|
162
|
+
print('');
|
|
163
|
+
print('Next steps:');
|
|
164
|
+
for (const s of nextSteps) print(` • ${s}`);
|
|
166
165
|
} else {
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
print('');
|
|
167
|
+
print('Next steps: nothing obvious — env looks healthy.');
|
|
169
168
|
}
|
|
170
169
|
}
|
|
171
170
|
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure auth-state probe used by `ugly-app doctor` (and by tests).
|
|
3
|
+
*
|
|
4
|
+
* Reads the on-disk auth files under a given home directory and returns
|
|
5
|
+
* a structured report. No side effects, no DataProxy, no network.
|
|
6
|
+
*
|
|
7
|
+
* ~/.ugly-bot/auth.json — global user identity ({ token, userId, serverUrl })
|
|
8
|
+
* ~/.ugly-bot/<projectId>.json — cloud project token ({ token })
|
|
9
|
+
* ~/.ugly-bot/<projectId>.local.json — local project token ({ token })
|
|
10
|
+
*
|
|
11
|
+
* The doctor previously only checked the project-level files and
|
|
12
|
+
* reported "no project token for cloud" as a warning even when a valid
|
|
13
|
+
* global user identity existed (in which case `dev` / `auth:create-bot`
|
|
14
|
+
* will mint a project token automatically on first run). Headless
|
|
15
|
+
* agents reading the report concluded auth was broken and got stuck on
|
|
16
|
+
* `npx ugly-app login`, which needs a browser.
|
|
17
|
+
*/
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
|
|
21
|
+
export type AuthStatus = 'ok' | 'warn' | 'error' | 'info';
|
|
22
|
+
|
|
23
|
+
export interface AuthLine {
|
|
24
|
+
status: AuthStatus;
|
|
25
|
+
label: string;
|
|
26
|
+
detail: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ProbeAuthInput {
|
|
30
|
+
homeDir: string;
|
|
31
|
+
projectId: string | null;
|
|
32
|
+
/** True when running against localhost ugly.bot (UGLY_BOT_LOCAL=1). */
|
|
33
|
+
isLocal: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ProbeAuthResult {
|
|
37
|
+
/** Human-readable lines, in the order they should be printed. */
|
|
38
|
+
lines: AuthLine[];
|
|
39
|
+
/** Hints to show under "Next steps:". */
|
|
40
|
+
nextSteps: string[];
|
|
41
|
+
/**
|
|
42
|
+
* True when the on-disk state is sufficient for a headless agent to
|
|
43
|
+
* proceed: at minimum a non-expired global user token exists, OR a
|
|
44
|
+
* non-expired project token of the right kind (cloud/local) exists.
|
|
45
|
+
* Project tokens are auto-minted from the global user token on first
|
|
46
|
+
* run, so the global user token is the real prerequisite.
|
|
47
|
+
*/
|
|
48
|
+
agentReady: boolean;
|
|
49
|
+
/** When agentReady is false, a one-line reason. */
|
|
50
|
+
agentBlockedReason?: string;
|
|
51
|
+
/**
|
|
52
|
+
* The token an agent should put in `UGLY_BOT_TOKEN`, if one is
|
|
53
|
+
* available. Prefers the project token (matches what the framework
|
|
54
|
+
* uses at runtime); falls back to the global user token.
|
|
55
|
+
*/
|
|
56
|
+
agentToken?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface JsonReadResult {
|
|
60
|
+
data: Record<string, unknown> | null;
|
|
61
|
+
/** Set when the file exists but couldn't be parsed — distinct from "missing". */
|
|
62
|
+
malformed: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readJson(filePath: string): JsonReadResult {
|
|
66
|
+
try {
|
|
67
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
68
|
+
try {
|
|
69
|
+
const parsed = JSON.parse(raw) as Record<string, unknown>;
|
|
70
|
+
if (parsed && typeof parsed === 'object') {
|
|
71
|
+
return { data: parsed, malformed: false };
|
|
72
|
+
}
|
|
73
|
+
return { data: null, malformed: true };
|
|
74
|
+
} catch {
|
|
75
|
+
return { data: null, malformed: true };
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
return { data: null, malformed: false };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Inspects the JWT exp claim without verifying the signature. Returns:
|
|
84
|
+
* - 'valid' : exp present and at least 60s in the future
|
|
85
|
+
* - 'expired' : exp present and in the past (or within 60s)
|
|
86
|
+
* - 'unknown' : token doesn't have a parseable exp — treat as valid
|
|
87
|
+
* since some service tokens are intentionally non-expiring
|
|
88
|
+
*/
|
|
89
|
+
export function inspectJwt(token: string): 'valid' | 'expired' | 'unknown' {
|
|
90
|
+
const parts = token.split('.');
|
|
91
|
+
if (parts.length !== 3) return 'unknown';
|
|
92
|
+
try {
|
|
93
|
+
const payload = JSON.parse(
|
|
94
|
+
Buffer.from(parts[1], 'base64url').toString('utf-8'),
|
|
95
|
+
) as { exp?: number };
|
|
96
|
+
if (typeof payload.exp !== 'number') return 'unknown';
|
|
97
|
+
return Date.now() / 1000 < payload.exp - 60 ? 'valid' : 'expired';
|
|
98
|
+
} catch {
|
|
99
|
+
return 'unknown';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
interface GlobalAuth {
|
|
104
|
+
token: string;
|
|
105
|
+
userId: string;
|
|
106
|
+
serverUrl: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function readGlobalAuth(homeDir: string): {
|
|
110
|
+
auth: GlobalAuth | null;
|
|
111
|
+
malformed: boolean;
|
|
112
|
+
} {
|
|
113
|
+
const file = path.join(homeDir, '.ugly-bot', 'auth.json');
|
|
114
|
+
const { data, malformed } = readJson(file);
|
|
115
|
+
if (!data) return { auth: null, malformed };
|
|
116
|
+
if (
|
|
117
|
+
typeof data.token !== 'string' ||
|
|
118
|
+
typeof data.userId !== 'string' ||
|
|
119
|
+
typeof data.serverUrl !== 'string'
|
|
120
|
+
) {
|
|
121
|
+
return { auth: null, malformed: true };
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
auth: { token: data.token, userId: data.userId, serverUrl: data.serverUrl },
|
|
125
|
+
malformed: false,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function readProjectToken(
|
|
130
|
+
homeDir: string,
|
|
131
|
+
projectId: string,
|
|
132
|
+
mode: 'cloud' | 'local',
|
|
133
|
+
): { token: string | null; malformed: boolean } {
|
|
134
|
+
const suffix = mode === 'local' ? '.local.json' : '.json';
|
|
135
|
+
const file = path.join(homeDir, '.ugly-bot', `${projectId}${suffix}`);
|
|
136
|
+
const { data, malformed } = readJson(file);
|
|
137
|
+
if (!data) return { token: null, malformed };
|
|
138
|
+
if (typeof data.token !== 'string') return { token: null, malformed: true };
|
|
139
|
+
return { token: data.token, malformed: false };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function probeAuth(input: ProbeAuthInput): ProbeAuthResult {
|
|
143
|
+
const { homeDir, projectId, isLocal } = input;
|
|
144
|
+
const lines: AuthLine[] = [];
|
|
145
|
+
const nextSteps: string[] = [];
|
|
146
|
+
|
|
147
|
+
// 1. Global user identity (~/.ugly-bot/auth.json).
|
|
148
|
+
const { auth: globalAuth, malformed: globalMalformed } = readGlobalAuth(homeDir);
|
|
149
|
+
let globalValid = false;
|
|
150
|
+
if (globalAuth) {
|
|
151
|
+
const exp = inspectJwt(globalAuth.token);
|
|
152
|
+
if (exp === 'expired') {
|
|
153
|
+
lines.push({
|
|
154
|
+
status: 'warn',
|
|
155
|
+
label: 'auth (user)',
|
|
156
|
+
detail: `token expired (userId=${globalAuth.userId})`,
|
|
157
|
+
});
|
|
158
|
+
nextSteps.push('Run `npx ugly-app login` to refresh your global user token');
|
|
159
|
+
} else {
|
|
160
|
+
globalValid = true;
|
|
161
|
+
lines.push({
|
|
162
|
+
status: 'ok',
|
|
163
|
+
label: 'auth (user)',
|
|
164
|
+
detail: `signed in as ${globalAuth.userId} (${globalAuth.serverUrl})`,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
} else if (globalMalformed) {
|
|
168
|
+
lines.push({
|
|
169
|
+
status: 'error',
|
|
170
|
+
label: 'auth (user)',
|
|
171
|
+
detail: '~/.ugly-bot/auth.json is malformed or missing required fields',
|
|
172
|
+
});
|
|
173
|
+
nextSteps.push(
|
|
174
|
+
'Delete ~/.ugly-bot/auth.json and re-run `npx ugly-app login`',
|
|
175
|
+
);
|
|
176
|
+
} else {
|
|
177
|
+
lines.push({
|
|
178
|
+
status: 'error',
|
|
179
|
+
label: 'auth (user)',
|
|
180
|
+
detail: 'not signed in (no ~/.ugly-bot/auth.json)',
|
|
181
|
+
});
|
|
182
|
+
nextSteps.push(
|
|
183
|
+
'Run `npx ugly-app login` once on the host (interactive, opens a browser) — required even for headless agents',
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// 2. Project tokens. Without a .uglyapp we have no projectId to look up.
|
|
188
|
+
let cloudToken: string | null = null;
|
|
189
|
+
let cloudExpired = false;
|
|
190
|
+
let localToken: string | null = null;
|
|
191
|
+
let localExpired = false;
|
|
192
|
+
|
|
193
|
+
if (projectId) {
|
|
194
|
+
const cloud = readProjectToken(homeDir, projectId, 'cloud');
|
|
195
|
+
const local = readProjectToken(homeDir, projectId, 'local');
|
|
196
|
+
|
|
197
|
+
if (cloud.token) {
|
|
198
|
+
const exp = inspectJwt(cloud.token);
|
|
199
|
+
if (exp === 'expired') {
|
|
200
|
+
cloudExpired = true;
|
|
201
|
+
lines.push({
|
|
202
|
+
status: 'warn',
|
|
203
|
+
label: 'auth (cloud)',
|
|
204
|
+
detail: `cached project token expired (${cloud.token.slice(0, 8)}…)`,
|
|
205
|
+
});
|
|
206
|
+
if (!isLocal && globalValid) {
|
|
207
|
+
nextSteps.push(
|
|
208
|
+
'Stale cloud project token — `ugly-app dev` will re-mint it from your user token',
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
cloudToken = cloud.token;
|
|
213
|
+
lines.push({
|
|
214
|
+
status: 'ok',
|
|
215
|
+
label: 'auth (cloud)',
|
|
216
|
+
detail: `project token present (${cloud.token.slice(0, 8)}…)`,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
} else if (cloud.malformed) {
|
|
220
|
+
lines.push({
|
|
221
|
+
status: 'warn',
|
|
222
|
+
label: 'auth (cloud)',
|
|
223
|
+
detail: `~/.ugly-bot/${projectId}.json is malformed`,
|
|
224
|
+
});
|
|
225
|
+
nextSteps.push(`Delete ~/.ugly-bot/${projectId}.json and re-run \`npx ugly-app dev\` to re-mint`);
|
|
226
|
+
} else if (globalValid && !isLocal) {
|
|
227
|
+
// Common case: never run before. Don't scare the agent — it's auto-minted.
|
|
228
|
+
lines.push({
|
|
229
|
+
status: 'info',
|
|
230
|
+
label: 'auth (cloud)',
|
|
231
|
+
detail: 'no cached project token — will be minted from user token on first run',
|
|
232
|
+
});
|
|
233
|
+
} else {
|
|
234
|
+
lines.push({
|
|
235
|
+
status: isLocal ? 'info' : 'warn',
|
|
236
|
+
label: 'auth (cloud)',
|
|
237
|
+
detail: 'no project token for ugly.bot cloud',
|
|
238
|
+
});
|
|
239
|
+
if (!isLocal && !globalValid) {
|
|
240
|
+
// The "log in first" hint already came from the user line; don't duplicate.
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (local.token) {
|
|
245
|
+
const exp = inspectJwt(local.token);
|
|
246
|
+
if (exp === 'expired') {
|
|
247
|
+
localExpired = true;
|
|
248
|
+
lines.push({
|
|
249
|
+
status: isLocal ? 'warn' : 'info',
|
|
250
|
+
label: 'auth (local)',
|
|
251
|
+
detail: `cached local token expired (${local.token.slice(0, 8)}…)`,
|
|
252
|
+
});
|
|
253
|
+
if (isLocal) {
|
|
254
|
+
nextSteps.push(
|
|
255
|
+
'Stale local project token — `UGLY_BOT_LOCAL=1 npx ugly-app dev` will re-mint it',
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
localToken = local.token;
|
|
260
|
+
lines.push({
|
|
261
|
+
status: 'ok',
|
|
262
|
+
label: 'auth (local)',
|
|
263
|
+
detail: `local project token present (${local.token.slice(0, 8)}…)`,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
} else if (local.malformed) {
|
|
267
|
+
lines.push({
|
|
268
|
+
status: 'warn',
|
|
269
|
+
label: 'auth (local)',
|
|
270
|
+
detail: `~/.ugly-bot/${projectId}.local.json is malformed`,
|
|
271
|
+
});
|
|
272
|
+
} else {
|
|
273
|
+
lines.push({
|
|
274
|
+
status: isLocal ? (globalValid ? 'info' : 'warn') : 'info',
|
|
275
|
+
label: 'auth (local)',
|
|
276
|
+
detail: isLocal && globalValid
|
|
277
|
+
? 'no cached local project token — will be minted on first local run'
|
|
278
|
+
: 'no local ugly.bot token',
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// 3. Agent-ready summary.
|
|
284
|
+
// For headless tooling, the global user token is what unblocks
|
|
285
|
+
// everything: project tokens are derived. If we're in local mode we
|
|
286
|
+
// still want the global token because the local project token is
|
|
287
|
+
// minted from it.
|
|
288
|
+
let agentReady = false;
|
|
289
|
+
let agentBlockedReason: string | undefined;
|
|
290
|
+
let agentToken: string | undefined;
|
|
291
|
+
|
|
292
|
+
const liveCloud = cloudToken && !cloudExpired ? cloudToken : null;
|
|
293
|
+
const liveLocal = localToken && !localExpired ? localToken : null;
|
|
294
|
+
|
|
295
|
+
if (globalValid) {
|
|
296
|
+
agentReady = true;
|
|
297
|
+
agentToken = isLocal ? (liveLocal ?? globalAuth?.token) : (liveCloud ?? globalAuth?.token);
|
|
298
|
+
} else if (liveCloud || liveLocal) {
|
|
299
|
+
// Edge case: project tokens cached but global identity gone. The
|
|
300
|
+
// project tokens still work for `auth:create-bot` etc., so don't
|
|
301
|
+
// block, but flag it.
|
|
302
|
+
agentReady = true;
|
|
303
|
+
agentToken = isLocal ? (liveLocal ?? liveCloud ?? undefined) : (liveCloud ?? liveLocal ?? undefined);
|
|
304
|
+
} else {
|
|
305
|
+
agentReady = false;
|
|
306
|
+
agentBlockedReason = globalAuth
|
|
307
|
+
? 'global user token expired — run `npx ugly-app login`'
|
|
308
|
+
: 'not signed in — run `npx ugly-app login` on the host';
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return { lines, nextSteps, agentReady, agentBlockedReason, agentToken };
|
|
312
|
+
}
|
package/src/cli/version.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by prebuild — do not edit manually
|
|
2
|
-
export const CLI_VERSION = "0.1.
|
|
2
|
+
export const CLI_VERSION = "0.1.419";
|
|
@@ -38,7 +38,21 @@ Run `npx ugly-app url` to get the local server URL. Use this value everywhere (b
|
|
|
38
38
|
|
|
39
39
|
- The dev server must be running **without** `--watch` (see below)
|
|
40
40
|
|
|
41
|
-
-
|
|
41
|
+
- Auth must be set up. **Always run `npx ugly-app doctor` first** to verify. The bottom of its output prints a single line:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
agent-ready: yes
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
If it says `agent-ready: no — ...`, follow the reason verbatim. The most common cause is no global user token on the host — the user must run `npx ugly-app login` once interactively (it opens a browser and is **not** runnable from within an unattended agent). Do **not** retry `ugly-app login` from inside the cycle; it will deadlock waiting for browser auth.
|
|
48
|
+
|
|
49
|
+
Once `agent-ready: yes`, source the token for downstream `auth:create-bot` / `feedback:submit` calls from `~/.ugly-bot/auth.json`:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
export UGLY_BOT_TOKEN="$(node -e 'console.log(JSON.parse(require("fs").readFileSync(require("os").homedir()+"/.ugly-bot/auth.json","utf8")).token)')"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Project-scoped tokens under `~/.ugly-bot/<projectId>.json` are minted automatically by `ugly-app dev` from the global token — do not try to mint them manually.
|
|
42
56
|
|
|
43
57
|
- Playwright must be installed (`npx playwright install chromium`)
|
|
44
58
|
|