@wayai/cli 0.3.40 → 0.3.41
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/index.js +16893 -310
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
- package/dist/commands/admin.d.ts +0 -1
- package/dist/commands/admin.js +0 -913
- package/dist/commands/admin.js.map +0 -1
- package/dist/commands/analytics.d.ts +0 -34
- package/dist/commands/analytics.js +0 -605
- package/dist/commands/analytics.js.map +0 -1
- package/dist/commands/conversations.d.ts +0 -14
- package/dist/commands/conversations.js +0 -292
- package/dist/commands/conversations.js.map +0 -1
- package/dist/commands/create-credential.d.ts +0 -20
- package/dist/commands/create-credential.js +0 -146
- package/dist/commands/create-credential.js.map +0 -1
- package/dist/commands/delete-history.d.ts +0 -15
- package/dist/commands/delete-history.js +0 -56
- package/dist/commands/delete-history.js.map +0 -1
- package/dist/commands/eval-capture.d.ts +0 -1
- package/dist/commands/eval-capture.js +0 -135
- package/dist/commands/eval-capture.js.map +0 -1
- package/dist/commands/eval-results.d.ts +0 -10
- package/dist/commands/eval-results.js +0 -168
- package/dist/commands/eval-results.js.map +0 -1
- package/dist/commands/eval.d.ts +0 -1
- package/dist/commands/eval.js +0 -49
- package/dist/commands/eval.js.map +0 -1
- package/dist/commands/evals.d.ts +0 -21
- package/dist/commands/evals.js +0 -120
- package/dist/commands/evals.js.map +0 -1
- package/dist/commands/init.d.ts +0 -16
- package/dist/commands/init.js +0 -191
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/list.d.ts +0 -13
- package/dist/commands/list.js +0 -70
- package/dist/commands/list.js.map +0 -1
- package/dist/commands/login.d.ts +0 -8
- package/dist/commands/login.js +0 -145
- package/dist/commands/login.js.map +0 -1
- package/dist/commands/logout.d.ts +0 -1
- package/dist/commands/logout.js +0 -19
- package/dist/commands/logout.js.map +0 -1
- package/dist/commands/migrate.d.ts +0 -12
- package/dist/commands/migrate.js +0 -106
- package/dist/commands/migrate.js.map +0 -1
- package/dist/commands/observability.d.ts +0 -14
- package/dist/commands/observability.js +0 -124
- package/dist/commands/observability.js.map +0 -1
- package/dist/commands/org.d.ts +0 -19
- package/dist/commands/org.js +0 -186
- package/dist/commands/org.js.map +0 -1
- package/dist/commands/pull.d.ts +0 -31
- package/dist/commands/pull.js +0 -210
- package/dist/commands/pull.js.map +0 -1
- package/dist/commands/push.d.ts +0 -25
- package/dist/commands/push.js +0 -472
- package/dist/commands/push.js.map +0 -1
- package/dist/commands/report-create.d.ts +0 -18
- package/dist/commands/report-create.js +0 -143
- package/dist/commands/report-create.js.map +0 -1
- package/dist/commands/report.d.ts +0 -16
- package/dist/commands/report.js +0 -252
- package/dist/commands/report.js.map +0 -1
- package/dist/commands/run-eval.d.ts +0 -15
- package/dist/commands/run-eval.js +0 -187
- package/dist/commands/run-eval.js.map +0 -1
- package/dist/commands/send-message.d.ts +0 -11
- package/dist/commands/send-message.js +0 -63
- package/dist/commands/send-message.js.map +0 -1
- package/dist/commands/status.d.ts +0 -17
- package/dist/commands/status.js +0 -205
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/sync-mcp.d.ts +0 -16
- package/dist/commands/sync-mcp.js +0 -51
- package/dist/commands/sync-mcp.js.map +0 -1
- package/dist/commands/sync-skills.d.ts +0 -10
- package/dist/commands/sync-skills.js +0 -49
- package/dist/commands/sync-skills.js.map +0 -1
- package/dist/commands/unlock.d.ts +0 -1
- package/dist/commands/unlock.js +0 -13
- package/dist/commands/unlock.js.map +0 -1
- package/dist/commands/update-credential.d.ts +0 -29
- package/dist/commands/update-credential.js +0 -156
- package/dist/commands/update-credential.js.map +0 -1
- package/dist/commands/update.d.ts +0 -6
- package/dist/commands/update.js +0 -36
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/use.d.ts +0 -1
- package/dist/commands/use.js +0 -56
- package/dist/commands/use.js.map +0 -1
- package/dist/commands/whoami.d.ts +0 -11
- package/dist/commands/whoami.js +0 -30
- package/dist/commands/whoami.js.map +0 -1
- package/dist/index.d.ts +0 -28
- package/dist/lib/analytics-constants.d.ts +0 -6
- package/dist/lib/analytics-constants.js +0 -14
- package/dist/lib/analytics-constants.js.map +0 -1
- package/dist/lib/api-client.d.ts +0 -568
- package/dist/lib/api-client.js +0 -445
- package/dist/lib/api-client.js.map +0 -1
- package/dist/lib/auth.d.ts +0 -50
- package/dist/lib/auth.js +0 -230
- package/dist/lib/auth.js.map +0 -1
- package/dist/lib/config.d.ts +0 -34
- package/dist/lib/config.js +0 -69
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/credential-utils.d.ts +0 -36
- package/dist/lib/credential-utils.js +0 -88
- package/dist/lib/credential-utils.js.map +0 -1
- package/dist/lib/diff-display.d.ts +0 -8
- package/dist/lib/diff-display.js +0 -37
- package/dist/lib/diff-display.js.map +0 -1
- package/dist/lib/eval-format.d.ts +0 -29
- package/dist/lib/eval-format.js +0 -44
- package/dist/lib/eval-format.js.map +0 -1
- package/dist/lib/hub-lock.d.ts +0 -7
- package/dist/lib/hub-lock.js +0 -116
- package/dist/lib/hub-lock.js.map +0 -1
- package/dist/lib/layout.d.ts +0 -79
- package/dist/lib/layout.js +0 -133
- package/dist/lib/layout.js.map +0 -1
- package/dist/lib/org-workspace.d.ts +0 -22
- package/dist/lib/org-workspace.js +0 -106
- package/dist/lib/org-workspace.js.map +0 -1
- package/dist/lib/parser.d.ts +0 -16
- package/dist/lib/parser.js +0 -306
- package/dist/lib/parser.js.map +0 -1
- package/dist/lib/repo-config.d.ts +0 -23
- package/dist/lib/repo-config.js +0 -91
- package/dist/lib/repo-config.js.map +0 -1
- package/dist/lib/report-edit-args.d.ts +0 -27
- package/dist/lib/report-edit-args.js +0 -56
- package/dist/lib/report-edit-args.js.map +0 -1
- package/dist/lib/report-inbox.d.ts +0 -19
- package/dist/lib/report-inbox.js +0 -73
- package/dist/lib/report-inbox.js.map +0 -1
- package/dist/lib/resource-files.d.ts +0 -29
- package/dist/lib/resource-files.js +0 -156
- package/dist/lib/resource-files.js.map +0 -1
- package/dist/lib/sentry.d.ts +0 -38
- package/dist/lib/sentry.js +0 -168
- package/dist/lib/sentry.js.map +0 -1
- package/dist/lib/skill-version.d.ts +0 -9
- package/dist/lib/skill-version.js +0 -53
- package/dist/lib/skill-version.js.map +0 -1
- package/dist/lib/sql-output.d.ts +0 -17
- package/dist/lib/sql-output.js +0 -48
- package/dist/lib/sql-output.js.map +0 -1
- package/dist/lib/token-store.d.ts +0 -37
- package/dist/lib/token-store.js +0 -354
- package/dist/lib/token-store.js.map +0 -1
- package/dist/lib/types.d.ts +0 -18
- package/dist/lib/types.js +0 -5
- package/dist/lib/types.js.map +0 -1
- package/dist/lib/utils.d.ts +0 -97
- package/dist/lib/utils.js +0 -288
- package/dist/lib/utils.js.map +0 -1
- package/dist/lib/version-cache.d.ts +0 -10
- package/dist/lib/version-cache.js +0 -34
- package/dist/lib/version-cache.js.map +0 -1
- package/dist/lib/workspace.d.ts +0 -112
- package/dist/lib/workspace.js +0 -387
- package/dist/lib/workspace.js.map +0 -1
- package/dist/lib/yaml-writer.d.ts +0 -18
- package/dist/lib/yaml-writer.js +0 -283
- package/dist/lib/yaml-writer.js.map +0 -1
package/dist/commands/admin.js
DELETED
|
@@ -1,913 +0,0 @@
|
|
|
1
|
-
// Platform-admin DO read-only inspection — audits to Loki as admin_debug_read; 403 non-admin; 60/hour.
|
|
2
|
-
import { requireAuth } from '../lib/auth.js';
|
|
3
|
-
import { ApiClient, ApiError } from '../lib/api-client.js';
|
|
4
|
-
import { runDebugObservability } from './observability.js';
|
|
5
|
-
import { parseReportEditArgs } from '../lib/report-edit-args.js';
|
|
6
|
-
// Runtime list is duplicated (not imported from shared) because the CLI is
|
|
7
|
-
// npm-published and @wayai/shared is a workspace devDependency — pulling in
|
|
8
|
-
// the full shared bundle for five strings isn't worth it, and a type-only
|
|
9
|
-
// import keeps the type side as the single source of truth.
|
|
10
|
-
const VALID_TYPES = ['hub', 'org', 'conversation', 'user', 'platform'];
|
|
11
|
-
// Same rationale as VALID_TYPES — kept as `satisfies` so the type side stays canonical.
|
|
12
|
-
// Drift between this and the backend allowlist is caught by admin.test.ts (cross-file invariant).
|
|
13
|
-
const VALID_ANALYTICS_TABLES = [
|
|
14
|
-
'conversation',
|
|
15
|
-
'message',
|
|
16
|
-
'billing_event',
|
|
17
|
-
'schedule_event',
|
|
18
|
-
];
|
|
19
|
-
export async function adminCommand(args) {
|
|
20
|
-
const [group, ...afterGroup] = args;
|
|
21
|
-
if (!group) {
|
|
22
|
-
printHelp();
|
|
23
|
-
process.exit(1);
|
|
24
|
-
}
|
|
25
|
-
if (group === 'archive') {
|
|
26
|
-
// `archive` has no sub — all remaining args are positional/flags.
|
|
27
|
-
const { positional, flagArgs } = splitArgs(afterGroup);
|
|
28
|
-
await runArchiveRead(positional, flagArgs);
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
if (group === 'observability') {
|
|
32
|
-
const { positional, flagArgs } = splitArgs(afterGroup);
|
|
33
|
-
await runObservabilityRead(positional, flagArgs);
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
const [sub, ...rest] = afterGroup;
|
|
37
|
-
const { positional, flagArgs } = splitArgs(rest);
|
|
38
|
-
if (group === 'do') {
|
|
39
|
-
if (!sub) {
|
|
40
|
-
printHelp();
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
switch (sub) {
|
|
44
|
-
case 'tables':
|
|
45
|
-
await runDoTables(positional);
|
|
46
|
-
return;
|
|
47
|
-
case 'read':
|
|
48
|
-
await runDoRead(positional, flagArgs);
|
|
49
|
-
return;
|
|
50
|
-
default:
|
|
51
|
-
printHelp();
|
|
52
|
-
process.exit(1);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
if (group === 'analytics') {
|
|
56
|
-
if (!sub) {
|
|
57
|
-
printHelp();
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
switch (sub) {
|
|
61
|
-
case 'tables':
|
|
62
|
-
await runAnalyticsTables();
|
|
63
|
-
return;
|
|
64
|
-
case 'read':
|
|
65
|
-
await runAnalyticsRead(positional, flagArgs);
|
|
66
|
-
return;
|
|
67
|
-
default:
|
|
68
|
-
printHelp();
|
|
69
|
-
process.exit(1);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (group === 'report') {
|
|
73
|
-
if (!sub) {
|
|
74
|
-
printHelp();
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
switch (sub) {
|
|
78
|
-
case 'list':
|
|
79
|
-
await runReportList(flagArgs);
|
|
80
|
-
return;
|
|
81
|
-
case 'show':
|
|
82
|
-
await runReportShow(positional);
|
|
83
|
-
return;
|
|
84
|
-
case 'group':
|
|
85
|
-
await runReportGroup(positional);
|
|
86
|
-
return;
|
|
87
|
-
case 'ungroup':
|
|
88
|
-
await runReportUngroup(positional);
|
|
89
|
-
return;
|
|
90
|
-
case 'escalate':
|
|
91
|
-
await runReportEscalate(positional, flagArgs);
|
|
92
|
-
return;
|
|
93
|
-
case 'shipped':
|
|
94
|
-
await runReportShipped(positional, flagArgs);
|
|
95
|
-
return;
|
|
96
|
-
case 'addressed':
|
|
97
|
-
await runReportAddressed(positional);
|
|
98
|
-
return;
|
|
99
|
-
case 'dismiss':
|
|
100
|
-
await runReportDismiss(positional, flagArgs);
|
|
101
|
-
return;
|
|
102
|
-
case 'hold':
|
|
103
|
-
await runReportHold(positional, flagArgs);
|
|
104
|
-
return;
|
|
105
|
-
case 'update':
|
|
106
|
-
await runReportUpdate(positional, flagArgs);
|
|
107
|
-
return;
|
|
108
|
-
default:
|
|
109
|
-
printHelp();
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
if (group === 'notice') {
|
|
114
|
-
if (!sub) {
|
|
115
|
-
printHelp();
|
|
116
|
-
process.exit(1);
|
|
117
|
-
}
|
|
118
|
-
switch (sub) {
|
|
119
|
-
case 'create':
|
|
120
|
-
await runNoticeCreate(flagArgs);
|
|
121
|
-
return;
|
|
122
|
-
case 'list':
|
|
123
|
-
await runNoticeList(flagArgs);
|
|
124
|
-
return;
|
|
125
|
-
case 'resolve':
|
|
126
|
-
await runNoticeResolve(positional);
|
|
127
|
-
return;
|
|
128
|
-
default:
|
|
129
|
-
printHelp();
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
printHelp();
|
|
134
|
-
process.exit(1);
|
|
135
|
-
}
|
|
136
|
-
function splitArgs(args) {
|
|
137
|
-
const positional = [];
|
|
138
|
-
const flagArgs = [];
|
|
139
|
-
for (const a of args) {
|
|
140
|
-
if (a.startsWith('--') || flagArgs.length > 0) {
|
|
141
|
-
flagArgs.push(a);
|
|
142
|
-
}
|
|
143
|
-
else {
|
|
144
|
-
positional.push(a);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return { positional, flagArgs };
|
|
148
|
-
}
|
|
149
|
-
// ---------------------------------------------------------------------------
|
|
150
|
-
// Subcommands — DO
|
|
151
|
-
// ---------------------------------------------------------------------------
|
|
152
|
-
async function runDoTables(positional) {
|
|
153
|
-
if (positional.length < 2) {
|
|
154
|
-
console.error('wayai admin do tables <type> <id>');
|
|
155
|
-
process.exit(1);
|
|
156
|
-
}
|
|
157
|
-
const type = validateType(positional[0]);
|
|
158
|
-
const id = positional[1];
|
|
159
|
-
const { config, accessToken } = await requireAuth();
|
|
160
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
161
|
-
let response;
|
|
162
|
-
try {
|
|
163
|
-
response = await client.debugListTables(type, id);
|
|
164
|
-
}
|
|
165
|
-
catch (err) {
|
|
166
|
-
exitOnApiError(err);
|
|
167
|
-
throw err; // unreachable
|
|
168
|
-
}
|
|
169
|
-
const { tables } = response.data;
|
|
170
|
-
if (tables.length === 0) {
|
|
171
|
-
console.log('(no allowlisted tables found on this DO)');
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
for (const t of tables)
|
|
175
|
-
console.log(t);
|
|
176
|
-
}
|
|
177
|
-
async function runDoRead(positional, flagArgs) {
|
|
178
|
-
if (positional.length < 3) {
|
|
179
|
-
console.error('wayai admin do read <type> <id> <table> [--limit N] [--id X] [--json]');
|
|
180
|
-
process.exit(1);
|
|
181
|
-
}
|
|
182
|
-
const type = validateType(positional[0]);
|
|
183
|
-
const id = positional[1];
|
|
184
|
-
const table = positional[2];
|
|
185
|
-
const { limit, filterId, jsonOutput } = parseFlags(flagArgs, ['--limit', '--id', '--json']);
|
|
186
|
-
const { config, accessToken } = await requireAuth();
|
|
187
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
188
|
-
let response;
|
|
189
|
-
try {
|
|
190
|
-
response = await client.debugReadTable(type, id, table, { limit, id: filterId });
|
|
191
|
-
}
|
|
192
|
-
catch (err) {
|
|
193
|
-
exitOnApiError(err);
|
|
194
|
-
throw err;
|
|
195
|
-
}
|
|
196
|
-
printDebugRowsResult(response.data, jsonOutput);
|
|
197
|
-
}
|
|
198
|
-
// ---------------------------------------------------------------------------
|
|
199
|
-
// Subcommands — Analytics (ClickHouse)
|
|
200
|
-
// ---------------------------------------------------------------------------
|
|
201
|
-
async function runAnalyticsTables() {
|
|
202
|
-
const { config, accessToken } = await requireAuth();
|
|
203
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
204
|
-
let response;
|
|
205
|
-
try {
|
|
206
|
-
response = await client.debugListAnalyticsTables();
|
|
207
|
-
}
|
|
208
|
-
catch (err) {
|
|
209
|
-
exitOnApiError(err);
|
|
210
|
-
throw err;
|
|
211
|
-
}
|
|
212
|
-
const { tables } = response.data;
|
|
213
|
-
if (tables.length === 0) {
|
|
214
|
-
console.log('(no analytics tables available)');
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
for (const t of tables)
|
|
218
|
-
console.log(t);
|
|
219
|
-
}
|
|
220
|
-
async function runAnalyticsRead(positional, flagArgs) {
|
|
221
|
-
if (positional.length < 1) {
|
|
222
|
-
console.error('wayai admin analytics read <table> [--conversation-id X] [--hub-id Y] [--message-id Z] [--limit N] [--json]');
|
|
223
|
-
process.exit(1);
|
|
224
|
-
}
|
|
225
|
-
const table = validateAnalyticsTable(positional[0]);
|
|
226
|
-
const flags = parseFlags(flagArgs, ['--limit', '--json', '--conversation-id', '--hub-id', '--message-id']);
|
|
227
|
-
const { config, accessToken } = await requireAuth();
|
|
228
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
229
|
-
let response;
|
|
230
|
-
try {
|
|
231
|
-
response = await client.debugReadAnalyticsTable(table, {
|
|
232
|
-
conversationId: flags.conversationId,
|
|
233
|
-
hubId: flags.hubId,
|
|
234
|
-
messageId: flags.messageId,
|
|
235
|
-
limit: flags.limit,
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
catch (err) {
|
|
239
|
-
exitOnApiError(err);
|
|
240
|
-
throw err;
|
|
241
|
-
}
|
|
242
|
-
printDebugRowsResult(response.data, flags.jsonOutput);
|
|
243
|
-
}
|
|
244
|
-
// ---------------------------------------------------------------------------
|
|
245
|
-
// Subcommands — Archive (R2)
|
|
246
|
-
// ---------------------------------------------------------------------------
|
|
247
|
-
async function runArchiveRead(positional, flagArgs) {
|
|
248
|
-
if (positional.length < 2) {
|
|
249
|
-
console.error('wayai admin archive <hub_id> <conversation_id> [--message-id X] [--json]');
|
|
250
|
-
process.exit(1);
|
|
251
|
-
}
|
|
252
|
-
const hubId = positional[0];
|
|
253
|
-
const conversationId = positional[1];
|
|
254
|
-
const flags = parseFlags(flagArgs, ['--json', '--message-id']);
|
|
255
|
-
const { config, accessToken } = await requireAuth();
|
|
256
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
257
|
-
let response;
|
|
258
|
-
try {
|
|
259
|
-
response = await client.debugReadArchive(hubId, conversationId, {
|
|
260
|
-
messageId: flags.messageId,
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
catch (err) {
|
|
264
|
-
// Archive-specific 404 copy: an admin reading an archive expects "purged or never written"
|
|
265
|
-
// rather than a generic "not found." Only this subcommand has the context to say that.
|
|
266
|
-
if (err instanceof ApiError && err.status === 404) {
|
|
267
|
-
console.error('Archive not found. The conversation may not be ended yet, or the R2 blob may have been purged.');
|
|
268
|
-
process.exit(1);
|
|
269
|
-
}
|
|
270
|
-
exitOnApiError(err);
|
|
271
|
-
throw err;
|
|
272
|
-
}
|
|
273
|
-
const data = response.data;
|
|
274
|
-
if (flags.jsonOutput) {
|
|
275
|
-
console.log(JSON.stringify(data, null, 2));
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
// Default pretty-print: archive metadata + a one-line message digest.
|
|
279
|
-
// Admins who need full content pass --json.
|
|
280
|
-
const archive = data.archive;
|
|
281
|
-
console.log(`hub_id: ${data.hub_id}`);
|
|
282
|
-
console.log(`conversation_id:${data.conversation_id}`);
|
|
283
|
-
if (data.message_filtered)
|
|
284
|
-
console.log('(messages filtered by --message-id)');
|
|
285
|
-
const messages = Array.isArray(archive.messages) ? archive.messages : [];
|
|
286
|
-
const files = Array.isArray(archive.files) ? archive.files.length : 0;
|
|
287
|
-
console.log(`messages: ${messages.length}`);
|
|
288
|
-
console.log(`files: ${files}`);
|
|
289
|
-
if (messages.length > 0) {
|
|
290
|
-
console.log('');
|
|
291
|
-
console.log('messages (id | sender | created_at | text):');
|
|
292
|
-
for (const m of messages) {
|
|
293
|
-
const id = String(m.message_id ?? '');
|
|
294
|
-
const sender = String(m.sender_type ?? '');
|
|
295
|
-
const ts = String(m.created_at ?? '');
|
|
296
|
-
const text = truncate(String(m.message_text ?? ''), 80);
|
|
297
|
-
console.log(` ${id} | ${sender} | ${ts} | ${text}`);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
// ---------------------------------------------------------------------------
|
|
302
|
-
// Subcommands — Observability (R2)
|
|
303
|
-
// ---------------------------------------------------------------------------
|
|
304
|
-
async function runObservabilityRead(positional, flagArgs) {
|
|
305
|
-
if (positional.length < 2) {
|
|
306
|
-
console.error('wayai admin observability <hub_id> <conversation_id> [--message-id X] [--json]');
|
|
307
|
-
process.exit(1);
|
|
308
|
-
}
|
|
309
|
-
const hubId = positional[0];
|
|
310
|
-
const conversationId = positional[1];
|
|
311
|
-
const flags = parseFlags(flagArgs, ['--json', '--message-id']);
|
|
312
|
-
const { config, accessToken } = await requireAuth();
|
|
313
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
314
|
-
try {
|
|
315
|
-
await runDebugObservability(client, hubId, conversationId, {
|
|
316
|
-
messageId: flags.messageId,
|
|
317
|
-
jsonOutput: flags.jsonOutput,
|
|
318
|
-
});
|
|
319
|
-
}
|
|
320
|
-
catch (err) {
|
|
321
|
-
if (err instanceof ApiError && err.status === 404) {
|
|
322
|
-
console.error('Observability data not found. Either the conversation has no LLM turns yet, or R2 entries were purged.');
|
|
323
|
-
process.exit(1);
|
|
324
|
-
}
|
|
325
|
-
exitOnApiError(err);
|
|
326
|
-
throw err;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
// ---------------------------------------------------------------------------
|
|
330
|
-
// Helpers
|
|
331
|
-
// ---------------------------------------------------------------------------
|
|
332
|
-
function validateType(raw) {
|
|
333
|
-
if (!raw || !VALID_TYPES.includes(raw)) {
|
|
334
|
-
console.error(`type must be one of: ${VALID_TYPES.join(', ')}`);
|
|
335
|
-
process.exit(1);
|
|
336
|
-
}
|
|
337
|
-
return raw;
|
|
338
|
-
}
|
|
339
|
-
function validateAnalyticsTable(raw) {
|
|
340
|
-
if (!raw || !VALID_ANALYTICS_TABLES.includes(raw)) {
|
|
341
|
-
console.error(`table must be one of: ${VALID_ANALYTICS_TABLES.join(', ')}`);
|
|
342
|
-
process.exit(1);
|
|
343
|
-
}
|
|
344
|
-
return raw;
|
|
345
|
-
}
|
|
346
|
-
function parseFlags(flagArgs, allowed) {
|
|
347
|
-
const out = { jsonOutput: false };
|
|
348
|
-
for (let i = 0; i < flagArgs.length; i++) {
|
|
349
|
-
const arg = flagArgs[i];
|
|
350
|
-
if (!allowed.includes(arg)) {
|
|
351
|
-
console.error(`Unknown flag: ${arg}`);
|
|
352
|
-
process.exit(1);
|
|
353
|
-
}
|
|
354
|
-
if (arg === '--json') {
|
|
355
|
-
out.jsonOutput = true;
|
|
356
|
-
continue;
|
|
357
|
-
}
|
|
358
|
-
const v = flagArgs[++i];
|
|
359
|
-
if (!v) {
|
|
360
|
-
console.error(`${arg} requires a value`);
|
|
361
|
-
process.exit(1);
|
|
362
|
-
}
|
|
363
|
-
if (arg === '--limit') {
|
|
364
|
-
const n = Number.parseInt(v, 10);
|
|
365
|
-
if (!Number.isFinite(n)) {
|
|
366
|
-
console.error('--limit must be an integer');
|
|
367
|
-
process.exit(1);
|
|
368
|
-
}
|
|
369
|
-
if (n < 1) {
|
|
370
|
-
console.error('--limit must be >= 1');
|
|
371
|
-
process.exit(1);
|
|
372
|
-
}
|
|
373
|
-
out.limit = n;
|
|
374
|
-
}
|
|
375
|
-
else if (arg === '--id') {
|
|
376
|
-
out.filterId = v;
|
|
377
|
-
}
|
|
378
|
-
else if (arg === '--conversation-id') {
|
|
379
|
-
out.conversationId = v;
|
|
380
|
-
}
|
|
381
|
-
else if (arg === '--hub-id') {
|
|
382
|
-
out.hubId = v;
|
|
383
|
-
}
|
|
384
|
-
else if (arg === '--message-id') {
|
|
385
|
-
out.messageId = v;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return out;
|
|
389
|
-
}
|
|
390
|
-
function exitOnApiError(err) {
|
|
391
|
-
if (err instanceof ApiError) {
|
|
392
|
-
if (err.status === 401) {
|
|
393
|
-
console.error('Authentication failed. Run `wayai login` again.');
|
|
394
|
-
process.exit(1);
|
|
395
|
-
}
|
|
396
|
-
if (err.status === 403) {
|
|
397
|
-
console.error('Access denied. `wayai admin` requires platform-admin access.');
|
|
398
|
-
console.error('Run `wayai whoami` to check your identity and platform-admin status.');
|
|
399
|
-
process.exit(1);
|
|
400
|
-
}
|
|
401
|
-
if (err.status === 404) {
|
|
402
|
-
console.error('Not found.');
|
|
403
|
-
process.exit(1);
|
|
404
|
-
}
|
|
405
|
-
if (err.status === 429) {
|
|
406
|
-
console.error('Rate limited. `wayai admin` is capped at 60 reads/hour per user.');
|
|
407
|
-
process.exit(1);
|
|
408
|
-
}
|
|
409
|
-
if (err.status === 503) {
|
|
410
|
-
console.error('Admin debug reads are currently disabled (platform kill switch).');
|
|
411
|
-
process.exit(1);
|
|
412
|
-
}
|
|
413
|
-
if (err.status === 400) {
|
|
414
|
-
console.error(`Bad request: ${err.body || err.message}`);
|
|
415
|
-
process.exit(1);
|
|
416
|
-
}
|
|
417
|
-
console.error(err.message);
|
|
418
|
-
process.exit(1);
|
|
419
|
-
}
|
|
420
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
421
|
-
process.exit(1);
|
|
422
|
-
}
|
|
423
|
-
/**
|
|
424
|
-
* Render the standard debug rows result: --json passes through the raw payload;
|
|
425
|
-
* default mode prints the table + footer with row count, truncation marker, and
|
|
426
|
-
* the redacted-columns summary.
|
|
427
|
-
*/
|
|
428
|
-
function printDebugRowsResult(data, jsonOutput) {
|
|
429
|
-
if (jsonOutput) {
|
|
430
|
-
console.log(JSON.stringify(data, null, 2));
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
if (data.rows.length === 0) {
|
|
434
|
-
console.log('(no rows)');
|
|
435
|
-
if (data.redacted_columns.length > 0) {
|
|
436
|
-
console.log(`\nredacted columns: ${data.redacted_columns.join(', ')}`);
|
|
437
|
-
}
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
printRowsTable(data.rows);
|
|
441
|
-
console.log('');
|
|
442
|
-
console.log(`rows: ${data.rows.length}${data.truncated ? ` (truncated to ${data.limit})` : ''}`);
|
|
443
|
-
if (data.redacted_columns.length > 0) {
|
|
444
|
-
console.log(`redacted columns: ${data.redacted_columns.join(', ')}`);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Pretty-print rows as a columnar text table. Columns are the union of keys
|
|
449
|
-
* across all rows, ordered by first appearance. Values are coerced to strings
|
|
450
|
-
* and truncated at a per-column width so a single row with a huge blob doesn't
|
|
451
|
-
* blow up the whole table.
|
|
452
|
-
*/
|
|
453
|
-
function printRowsTable(rows) {
|
|
454
|
-
const COL_MAX = 80;
|
|
455
|
-
const cols = [];
|
|
456
|
-
for (const row of rows)
|
|
457
|
-
for (const k of Object.keys(row))
|
|
458
|
-
if (!cols.includes(k))
|
|
459
|
-
cols.push(k);
|
|
460
|
-
const widths = {};
|
|
461
|
-
for (const c of cols)
|
|
462
|
-
widths[c] = Math.min(COL_MAX, Math.max(c.length, ...rows.map((r) => formatValue(r[c]).length)));
|
|
463
|
-
const header = cols.map((c) => c.padEnd(widths[c])).join(' | ');
|
|
464
|
-
const divider = cols.map((c) => '-'.repeat(widths[c])).join('-+-');
|
|
465
|
-
console.log(header);
|
|
466
|
-
console.log(divider);
|
|
467
|
-
for (const row of rows) {
|
|
468
|
-
console.log(cols.map((c) => truncate(formatValue(row[c]), widths[c]).padEnd(widths[c])).join(' | '));
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
function formatValue(v) {
|
|
472
|
-
if (v === null || v === undefined)
|
|
473
|
-
return 'null';
|
|
474
|
-
if (typeof v === 'string')
|
|
475
|
-
return v;
|
|
476
|
-
return JSON.stringify(v);
|
|
477
|
-
}
|
|
478
|
-
function truncate(s, n) {
|
|
479
|
-
return s.length > n ? s.slice(0, Math.max(1, n - 1)) + '…' : s;
|
|
480
|
-
}
|
|
481
|
-
// ---------------------------------------------------------------------------
|
|
482
|
-
// Subcommands — Report (platform_report triage queue)
|
|
483
|
-
//
|
|
484
|
-
// Consumed by Claude Code Desktop's 15-min triage routine and by humans
|
|
485
|
-
// running ad-hoc triage. Mutations (group/escalate/addressed/dismiss) drive
|
|
486
|
-
// the state machine in PlatformDO, which is the sole owner of email side
|
|
487
|
-
// effects (escalation, resolution, dismissal — `cli` (human) rows only).
|
|
488
|
-
// ---------------------------------------------------------------------------
|
|
489
|
-
function parseReportFlags(flagArgs) {
|
|
490
|
-
const out = {};
|
|
491
|
-
for (let i = 0; i < flagArgs.length; i++) {
|
|
492
|
-
const flag = flagArgs[i];
|
|
493
|
-
if (!flag.startsWith('--'))
|
|
494
|
-
continue;
|
|
495
|
-
const key = flag.slice(2);
|
|
496
|
-
const next = flagArgs[i + 1];
|
|
497
|
-
if (next === undefined || next.startsWith('--')) {
|
|
498
|
-
out[key] = 'true';
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
out[key] = next;
|
|
502
|
-
i++;
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
return out;
|
|
506
|
-
}
|
|
507
|
-
async function runReportList(flagArgs) {
|
|
508
|
-
const flags = parseReportFlags(flagArgs);
|
|
509
|
-
const { config, accessToken } = await requireAuth();
|
|
510
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
511
|
-
try {
|
|
512
|
-
const result = await client.listAdminReports({
|
|
513
|
-
status: flags.status,
|
|
514
|
-
source: flags.source,
|
|
515
|
-
limit: flags.limit ? Number(flags.limit) : undefined,
|
|
516
|
-
offset: flags.offset ? Number(flags.offset) : undefined,
|
|
517
|
-
since: flags.since,
|
|
518
|
-
});
|
|
519
|
-
if (flags.json === 'true') {
|
|
520
|
-
console.log(JSON.stringify(result, null, 2));
|
|
521
|
-
return;
|
|
522
|
-
}
|
|
523
|
-
console.log(`Reports: ${result.data.length}/${result.total}`);
|
|
524
|
-
for (const r of result.data) {
|
|
525
|
-
const age = Math.floor((Date.now() - r.created_at) / 1000 / 60);
|
|
526
|
-
const meta = [
|
|
527
|
-
r.source,
|
|
528
|
-
r.status,
|
|
529
|
-
// Surface a broken Sentry reference (set at escalation) — these never auto-resolve.
|
|
530
|
-
r.sentry_ref_status && r.sentry_ref_status !== 'valid' ? `sentry_ref:${r.sentry_ref_status}` : null,
|
|
531
|
-
r.gh_issue_url ? `gh:${r.gh_issue_url}` : null,
|
|
532
|
-
]
|
|
533
|
-
.filter(Boolean)
|
|
534
|
-
.join(' · ');
|
|
535
|
-
console.log(` ${r.report_id} [${meta}] ${age}m ago`);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
catch (err) {
|
|
539
|
-
exitOnApiError(err);
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
async function runReportShow(positional) {
|
|
543
|
-
const reportId = positional[0];
|
|
544
|
-
if (!reportId) {
|
|
545
|
-
console.error('wayai admin report show <report_id>');
|
|
546
|
-
process.exit(1);
|
|
547
|
-
}
|
|
548
|
-
const { config, accessToken } = await requireAuth();
|
|
549
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
550
|
-
try {
|
|
551
|
-
const result = await client.getAdminReport(reportId);
|
|
552
|
-
console.log(JSON.stringify(result.data, null, 2));
|
|
553
|
-
}
|
|
554
|
-
catch (err) {
|
|
555
|
-
exitOnApiError(err);
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
async function runReportGroup(positional) {
|
|
559
|
-
const [canonicalId, ...memberIds] = positional;
|
|
560
|
-
if (!canonicalId || memberIds.length === 0) {
|
|
561
|
-
console.error('wayai admin report group <canonical_id> <member_id> [<member_id> ...]');
|
|
562
|
-
process.exit(1);
|
|
563
|
-
}
|
|
564
|
-
const { config, accessToken } = await requireAuth();
|
|
565
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
566
|
-
try {
|
|
567
|
-
const result = await client.groupAdminReports(canonicalId, memberIds);
|
|
568
|
-
console.log(`Grouped ${result.grouped} member(s) under ${result.canonical_id}`);
|
|
569
|
-
for (const s of result.skipped) {
|
|
570
|
-
const reason = s.reason === 'different_gh_issue'
|
|
571
|
-
? `linked to a different GitHub issue (${s.detail})`
|
|
572
|
-
: s.reason === 'grouped_elsewhere'
|
|
573
|
-
? `already grouped under ${s.detail}`
|
|
574
|
-
: s.reason === 'terminal'
|
|
575
|
-
? `already ${s.detail} (terminal)`
|
|
576
|
-
: 'not found';
|
|
577
|
-
console.log(` skipped ${s.report_id} — ${reason}`);
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
catch (err) {
|
|
581
|
-
exitOnApiError(err);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
async function runReportUngroup(positional) {
|
|
585
|
-
const memberId = positional[0];
|
|
586
|
-
if (!memberId) {
|
|
587
|
-
console.error('wayai admin report ungroup <member_id>');
|
|
588
|
-
process.exit(1);
|
|
589
|
-
}
|
|
590
|
-
const { config, accessToken } = await requireAuth();
|
|
591
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
592
|
-
try {
|
|
593
|
-
const result = await client.ungroupAdminReport(memberId);
|
|
594
|
-
console.log(`Detached ${result.report.report_id} → ${result.report.status}`);
|
|
595
|
-
}
|
|
596
|
-
catch (err) {
|
|
597
|
-
exitOnApiError(err);
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
async function runReportEscalate(positional, flagArgs) {
|
|
601
|
-
const reportId = positional[0];
|
|
602
|
-
const flags = parseReportFlags(flagArgs);
|
|
603
|
-
const ghUrl = flags.gh;
|
|
604
|
-
if (!reportId || !ghUrl) {
|
|
605
|
-
console.error('wayai admin report escalate <report_id> --gh <github_issue_url> [--verdict <text>]');
|
|
606
|
-
process.exit(1);
|
|
607
|
-
}
|
|
608
|
-
const { config, accessToken } = await requireAuth();
|
|
609
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
610
|
-
try {
|
|
611
|
-
const result = await client.transitionAdminReport(reportId, {
|
|
612
|
-
target: 'escalated',
|
|
613
|
-
gh_issue_url: ghUrl,
|
|
614
|
-
ai_verdict: flags.verdict,
|
|
615
|
-
});
|
|
616
|
-
console.log(`Report ${result.report.report_id} → ${result.report.status}`);
|
|
617
|
-
// The backend cross-checks a sentry-sourced report's captured reference
|
|
618
|
-
// against the live Sentry issue at escalation. A broken ref still escalates
|
|
619
|
-
// (the bug may be real) but won't auto-resolve on close — flag it loudly.
|
|
620
|
-
const refStatus = result.report.sentry_ref_status;
|
|
621
|
-
if (refStatus === 'not_found') {
|
|
622
|
-
console.log(` ⚠ Sentry reference not_found — the cited issue does not exist on Sentry. Auto-resolve on close is skipped; verify the reference.`);
|
|
623
|
-
}
|
|
624
|
-
else if (refStatus === 'mismatch') {
|
|
625
|
-
console.log(` ⚠ Sentry reference mismatch — issue_id resolves but its short_id ≠ the captured one. Auto-resolve on close is skipped.`);
|
|
626
|
-
}
|
|
627
|
-
else if (refStatus === 'unverified') {
|
|
628
|
-
console.log(` ℹ Sentry reference unverified — could not reach Sentry (no token / API error). Auto-resolve on close is skipped.`);
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
catch (err) {
|
|
632
|
-
exitOnApiError(err);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
async function runReportShipped(positional, flagArgs) {
|
|
636
|
-
const reportId = positional[0];
|
|
637
|
-
const flags = parseReportFlags(flagArgs);
|
|
638
|
-
if (!reportId) {
|
|
639
|
-
console.error('wayai admin report shipped <report_id> [--note <what shipped, for the reporter>]');
|
|
640
|
-
process.exit(1);
|
|
641
|
-
}
|
|
642
|
-
const { config, accessToken } = await requireAuth();
|
|
643
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
644
|
-
try {
|
|
645
|
-
// `--note` is a value-flag; if omitted, parseReportFlags leaves it undefined and the
|
|
646
|
-
// backend records a default. Don't pass the literal "true" from a value-less `--note`.
|
|
647
|
-
const note = flags.note && flags.note !== 'true' ? flags.note : undefined;
|
|
648
|
-
const result = await client.transitionAdminReport(reportId, { target: 'shipped', note });
|
|
649
|
-
console.log(`Report ${result.report.report_id} → ${result.report.status} (awaiting reporter verification)`);
|
|
650
|
-
}
|
|
651
|
-
catch (err) {
|
|
652
|
-
exitOnApiError(err);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
async function runReportAddressed(positional) {
|
|
656
|
-
const reportId = positional[0];
|
|
657
|
-
if (!reportId) {
|
|
658
|
-
console.error('wayai admin report addressed <report_id>');
|
|
659
|
-
process.exit(1);
|
|
660
|
-
}
|
|
661
|
-
const { config, accessToken } = await requireAuth();
|
|
662
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
663
|
-
try {
|
|
664
|
-
const result = await client.transitionAdminReport(reportId, { target: 'addressed' });
|
|
665
|
-
console.log(`Report ${result.report.report_id} → ${result.report.status}`);
|
|
666
|
-
if (result.cascaded_report_ids.length > 0) {
|
|
667
|
-
console.log(`Cascaded ${result.cascaded_report_ids.length} grouped member(s).`);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
catch (err) {
|
|
671
|
-
exitOnApiError(err);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
async function runReportDismiss(positional, flagArgs) {
|
|
675
|
-
const reportId = positional[0];
|
|
676
|
-
const flags = parseReportFlags(flagArgs);
|
|
677
|
-
const reason = flags.reason;
|
|
678
|
-
if (!reportId || !reason) {
|
|
679
|
-
console.error('wayai admin report dismiss <report_id> --reason <text> [--verdict <text>] [--final]');
|
|
680
|
-
process.exit(1);
|
|
681
|
-
}
|
|
682
|
-
const { config, accessToken } = await requireAuth();
|
|
683
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
684
|
-
try {
|
|
685
|
-
// `--final` makes the dismissal non-contestable (the reporter can't dispute it). Use it to
|
|
686
|
-
// settle a clearly-wrong reporter without waiting for the contest cap.
|
|
687
|
-
const result = await client.transitionAdminReport(reportId, {
|
|
688
|
-
target: 'dismissed',
|
|
689
|
-
dismissal_reason: reason,
|
|
690
|
-
ai_verdict: flags.verdict,
|
|
691
|
-
final: flags.final === 'true',
|
|
692
|
-
});
|
|
693
|
-
console.log(`Report ${result.report.report_id} → ${result.report.status}${flags.final === 'true' ? ' (final — non-contestable)' : ''}`);
|
|
694
|
-
}
|
|
695
|
-
catch (err) {
|
|
696
|
-
exitOnApiError(err);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
async function runReportHold(positional, flagArgs) {
|
|
700
|
-
const reportId = positional[0];
|
|
701
|
-
const flags = parseReportFlags(flagArgs);
|
|
702
|
-
const reason = flags.reason;
|
|
703
|
-
// Reason is mandatory: a hold with no recorded rationale defeats the whole point (an
|
|
704
|
-
// auditable, dateable record of *why* this row is waiting another cycle).
|
|
705
|
-
if (!reportId || !reason || reason === 'true') {
|
|
706
|
-
console.error('wayai admin report hold <report_id> --reason <text>');
|
|
707
|
-
console.error('Defers a pending report one triage cycle (records a note + bumps hold_count). Silent — no email.');
|
|
708
|
-
process.exit(1);
|
|
709
|
-
}
|
|
710
|
-
const { config, accessToken } = await requireAuth();
|
|
711
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
712
|
-
try {
|
|
713
|
-
const result = await client.holdAdminReport(reportId, reason);
|
|
714
|
-
console.log(`Report ${result.report.report_id} held (cycle ${result.report.hold_count}) — remains ${result.report.status}.`);
|
|
715
|
-
}
|
|
716
|
-
catch (err) {
|
|
717
|
-
exitOnApiError(err);
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
async function runReportUpdate(positional, flagArgs) {
|
|
721
|
-
const reportId = positional[0];
|
|
722
|
-
// Use the dedicated content-flag parser (not parseReportFlags): it errors on a
|
|
723
|
-
// value-less flag and keeps an explicit "" as a clear, so a malformed
|
|
724
|
-
// `--error` can't silently persist the literal "true" into a content field.
|
|
725
|
-
const { patch, error } = parseReportEditArgs(flagArgs);
|
|
726
|
-
if (error) {
|
|
727
|
-
console.error(error);
|
|
728
|
-
process.exit(1);
|
|
729
|
-
}
|
|
730
|
-
if (!reportId || Object.keys(patch).length === 0) {
|
|
731
|
-
console.error('wayai admin report update <report_id> [--title <t>] [--description <d>] [--error <e>] [--steps <s>] [--context <c>]');
|
|
732
|
-
console.error('At least one field to change is required. Pass "" to clear error/steps/context.');
|
|
733
|
-
process.exit(1);
|
|
734
|
-
}
|
|
735
|
-
const { config, accessToken } = await requireAuth();
|
|
736
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
737
|
-
try {
|
|
738
|
-
const result = await client.updateAdminReport(reportId, patch);
|
|
739
|
-
console.log(`Report ${result.report_id} content updated (status: ${result.status}).`);
|
|
740
|
-
if (result.status === 'escalated') {
|
|
741
|
-
console.log('Note: this edit is queue-local — the linked GitHub issue body is NOT updated.');
|
|
742
|
-
}
|
|
743
|
-
console.log(JSON.stringify(result.raw_payload, null, 2));
|
|
744
|
-
}
|
|
745
|
-
catch (err) {
|
|
746
|
-
exitOnApiError(err);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
// ---------------------------------------------------------------------------
|
|
750
|
-
// Subcommands — Notices (platform broadcasts, feed 2)
|
|
751
|
-
// ---------------------------------------------------------------------------
|
|
752
|
-
const VALID_NOTICE_SEVERITIES = ['critical', 'warn', 'info'];
|
|
753
|
-
async function runNoticeCreate(flagArgs) {
|
|
754
|
-
const flags = parseReportFlags(flagArgs);
|
|
755
|
-
const title = flags.title;
|
|
756
|
-
if (!title || title === 'true') {
|
|
757
|
-
console.error('wayai admin notice create --title <text> [--severity critical|warn|info] [--scope <scope>] [--body <text>] [--link <url>] [--starts <ISO>] [--ends <ISO>]');
|
|
758
|
-
console.error('Authors a platform broadcast (feed 2). Severity defaults to info, scope to global.');
|
|
759
|
-
process.exit(1);
|
|
760
|
-
}
|
|
761
|
-
const severity = (flags.severity ?? 'info');
|
|
762
|
-
if (!VALID_NOTICE_SEVERITIES.includes(severity)) {
|
|
763
|
-
console.error(`--severity must be one of: ${VALID_NOTICE_SEVERITIES.join(', ')}`);
|
|
764
|
-
process.exit(1);
|
|
765
|
-
}
|
|
766
|
-
// Guard the value-less-flag sentinel (parseReportFlags maps a bare `--scope` to 'true')
|
|
767
|
-
// so it fails locally with a clear message instead of as a backend regex 400.
|
|
768
|
-
if (flags.scope === 'true') {
|
|
769
|
-
console.error('--scope requires a value (e.g. global, provider:gemini, service:analytics)');
|
|
770
|
-
process.exit(1);
|
|
771
|
-
}
|
|
772
|
-
const body = {
|
|
773
|
-
severity,
|
|
774
|
-
scope: flags.scope ?? 'global',
|
|
775
|
-
title,
|
|
776
|
-
...(flags.body && flags.body !== 'true' ? { body: flags.body } : {}),
|
|
777
|
-
...(flags.link && flags.link !== 'true' ? { link: flags.link } : {}),
|
|
778
|
-
...(flags.starts && flags.starts !== 'true' ? { starts_at: flags.starts } : {}),
|
|
779
|
-
...(flags.ends && flags.ends !== 'true' ? { ends_at: flags.ends } : {}),
|
|
780
|
-
};
|
|
781
|
-
const { config, accessToken } = await requireAuth();
|
|
782
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
783
|
-
try {
|
|
784
|
-
const result = await client.createAdminNotice(body);
|
|
785
|
-
const n = result.data;
|
|
786
|
-
console.log(`Created notice ${n.notice_id} — [${n.severity} · ${n.scope}] ${n.title}`);
|
|
787
|
-
}
|
|
788
|
-
catch (err) {
|
|
789
|
-
exitOnApiError(err);
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
async function runNoticeList(flagArgs) {
|
|
793
|
-
const flags = parseReportFlags(flagArgs);
|
|
794
|
-
const { config, accessToken } = await requireAuth();
|
|
795
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
796
|
-
try {
|
|
797
|
-
const result = await client.listAdminNotices({
|
|
798
|
-
status: flags.status,
|
|
799
|
-
limit: flags.limit ? Number(flags.limit) : undefined,
|
|
800
|
-
offset: flags.offset ? Number(flags.offset) : undefined,
|
|
801
|
-
});
|
|
802
|
-
if (flags.json === 'true') {
|
|
803
|
-
console.log(JSON.stringify(result, null, 2));
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
console.log(`Notices: ${result.data.length}/${result.total}`);
|
|
807
|
-
for (const n of result.data) {
|
|
808
|
-
const meta = [n.severity, n.scope, n.status, n.ends_at ? `ends:${n.ends_at}` : null]
|
|
809
|
-
.filter(Boolean)
|
|
810
|
-
.join(' · ');
|
|
811
|
-
console.log(` ${n.notice_id} [${meta}] ${n.title}`);
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
catch (err) {
|
|
815
|
-
exitOnApiError(err);
|
|
816
|
-
}
|
|
817
|
-
}
|
|
818
|
-
async function runNoticeResolve(positional) {
|
|
819
|
-
const noticeId = positional[0];
|
|
820
|
-
if (!noticeId) {
|
|
821
|
-
console.error('wayai admin notice resolve <notice_id>');
|
|
822
|
-
process.exit(1);
|
|
823
|
-
}
|
|
824
|
-
const { config, accessToken } = await requireAuth();
|
|
825
|
-
const client = new ApiClient({ apiUrl: config.api_url, accessToken });
|
|
826
|
-
try {
|
|
827
|
-
const result = await client.resolveAdminNotice(noticeId);
|
|
828
|
-
console.log(`Notice ${result.data.notice_id} resolved.`);
|
|
829
|
-
}
|
|
830
|
-
catch (err) {
|
|
831
|
-
exitOnApiError(err);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
function printHelp() {
|
|
835
|
-
console.error(`
|
|
836
|
-
wayai admin — platform-admin diagnostic commands
|
|
837
|
-
|
|
838
|
-
Usage:
|
|
839
|
-
wayai admin do tables <type> <id>
|
|
840
|
-
wayai admin do read <type> <id> <table> [--limit N] [--id X] [--json]
|
|
841
|
-
|
|
842
|
-
wayai admin analytics tables
|
|
843
|
-
wayai admin analytics read <table> [--conversation-id X] [--hub-id Y] [--message-id Z] [--limit N] [--json]
|
|
844
|
-
|
|
845
|
-
wayai admin archive <hub_id> <conversation_id> [--message-id X] [--json]
|
|
846
|
-
|
|
847
|
-
wayai admin observability <hub_id> <conversation_id> [--message-id X] [--json]
|
|
848
|
-
|
|
849
|
-
wayai admin report list [--status STATUS] [--source SOURCE] [--limit N] [--offset N] [--since ISO] [--json]
|
|
850
|
-
wayai admin report show <report_id>
|
|
851
|
-
wayai admin report group <canonical_id> <member_id> [<member_id> ...]
|
|
852
|
-
wayai admin report ungroup <member_id>
|
|
853
|
-
wayai admin report escalate <report_id> --gh <github_issue_url> [--verdict <text>]
|
|
854
|
-
wayai admin report shipped <report_id> [--note <what shipped, for the reporter>]
|
|
855
|
-
wayai admin report addressed <report_id>
|
|
856
|
-
wayai admin report dismiss <report_id> --reason <text> [--verdict <text>] [--final]
|
|
857
|
-
wayai admin report hold <report_id> --reason <text>
|
|
858
|
-
wayai admin report update <report_id> [--title <t>] [--description <d>] [--error <e>] [--steps <s>] [--context <c>]
|
|
859
|
-
|
|
860
|
-
wayai admin notice create --title <text> [--severity critical|warn|info] [--scope <scope>] [--body <text>] [--link <url>] [--starts <ISO>] [--ends <ISO>]
|
|
861
|
-
wayai admin notice list [--status active|resolved] [--limit N] [--offset N] [--json]
|
|
862
|
-
wayai admin notice resolve <notice_id>
|
|
863
|
-
|
|
864
|
-
Sources:
|
|
865
|
-
do Live DO SQLite (existing conversation, hub config, etc.).
|
|
866
|
-
analytics ClickHouse projections (conversation, message, billing_event, schedule_event).
|
|
867
|
-
archive R2 archive blob — canonical content for ended conversations after DO cleanup.
|
|
868
|
-
observability R2 LLM observability — per-message OTel GenAI records (prompts, completions,
|
|
869
|
-
tool calls, tokens, latency). Default lists all records for the conversation;
|
|
870
|
-
--message-id drills into one. Use this admin variant when the caller has no
|
|
871
|
-
hub access; otherwise prefer \`wayai conversations <id> observability\`.
|
|
872
|
-
report platform_report triage queue. \`list/show\` are reads; \`group/ungroup/
|
|
873
|
-
escalate/addressed/dismiss\` drive the state machine (which owns email
|
|
874
|
-
fanout for cli (human) rows). \`ungroup\` detaches a grouped member back
|
|
875
|
-
to pending (inverse of \`group\`). \`hold\` defers a pending row one cycle
|
|
876
|
-
(records an internal note + bumps hold_count) without transitioning it —
|
|
877
|
-
silent for every source. \`update\` amends content fields
|
|
878
|
-
(title/description/error/steps/context) while pending/escalated — the
|
|
879
|
-
dedup fingerprint stays frozen and no email fires. Agent/Sentry rows
|
|
880
|
-
never trigger emails.
|
|
881
|
-
notice platform_notice broadcasts (Status & Notices feed 2) — operator-authored
|
|
882
|
-
"Gemini degraded" / "Analytics delayed" notices. \`create\` authors one
|
|
883
|
-
(severity defaults to info, scope to global); \`list\` reads them
|
|
884
|
-
(--status active|resolved); \`resolve\` ends an active notice.
|
|
885
|
-
|
|
886
|
-
Types (do): ${VALID_TYPES.join(' | ')}
|
|
887
|
-
Tables (analytics): ${VALID_ANALYTICS_TABLES.join(' | ')}
|
|
888
|
-
|
|
889
|
-
Entity id forms (do):
|
|
890
|
-
<uuid> Domain entity id (hub_id, org_id, conversation_id, user_id) —
|
|
891
|
-
hashed via idFromName, same path as every other route.
|
|
892
|
-
hex:<64-hex> Raw CF DO hex id — resolved via idFromString. Use when you
|
|
893
|
-
only have the CF-level id (e.g. from orphan-audit logs, CF
|
|
894
|
-
traces, zombie instances with no registry entry).
|
|
895
|
-
platform Singleton — literal string, hex: form not supported.
|
|
896
|
-
|
|
897
|
-
Examples:
|
|
898
|
-
wayai admin do tables hub 018fab...
|
|
899
|
-
wayai admin do read conversation <conv-id> message --limit 5 --json
|
|
900
|
-
wayai admin analytics read message --conversation-id <conv-id>
|
|
901
|
-
wayai admin analytics read conversation --conversation-id <conv-id>
|
|
902
|
-
wayai admin archive <hub-id> <conv-id>
|
|
903
|
-
wayai admin archive <hub-id> <conv-id> --message-id <msg-id> --json
|
|
904
|
-
wayai admin observability <hub-id> <conv-id>
|
|
905
|
-
wayai admin observability <hub-id> <conv-id> --message-id <msg-id> --json
|
|
906
|
-
|
|
907
|
-
Audit + rate limit:
|
|
908
|
-
Every successful read is logged as admin_debug_read (queryable in Grafana
|
|
909
|
-
Loki — info-level events are NOT sent to Sentry). Capped at 60 reads/hour/user
|
|
910
|
-
across all sources. Platform-admin grant required.
|
|
911
|
-
`.trim());
|
|
912
|
-
}
|
|
913
|
-
//# sourceMappingURL=admin.js.map
|