@palmyr/cli 1.8.5 → 1.9.0
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.js +321 -9
- package/dist/cli.js.map +1 -1
- package/dist/sdk.d.ts +10 -1
- package/dist/sdk.js +21 -1
- package/dist/sdk.js.map +1 -1
- package/dist/telemetry.d.ts +49 -0
- package/dist/telemetry.js +150 -0
- package/dist/telemetry.js.map +1 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ import { render as inkRender } from 'ink';
|
|
|
17
17
|
import { ConfigScreen, Dashboard, DoctorScreen, DomainCheckScreen, DomainPricingScreen, ErrorScreen, HealthScreen, MenuScreen, PricingScreen, RecordsScreen, SetupScreen, StatusScreen, SuccessScreen, WalletCreateScreen, WalletStatusScreen, WalletListScreen } from './app.js';
|
|
18
18
|
import { Palmyr } from './sdk.js';
|
|
19
19
|
import { loadConfig, saveConfig, ensureDirs, log, addPhone, addDomain, addNote } from './config.js';
|
|
20
|
+
import { getState as getTelemetryState, setEnabled as setTelemetryEnabled, queuedCount as telemetryQueuedCount, appendEventSync as telemetryAppendEvent, flushQueue as telemetryFlushQueue } from './telemetry.js';
|
|
20
21
|
import { theme as t, icon, Spinner, warn, table, kv, section, setAgentMode as setUiAgentMode } from './ui.js';
|
|
21
22
|
import { existsSync, readFileSync } from 'fs';
|
|
22
23
|
import { homedir } from 'os';
|
|
@@ -776,9 +777,13 @@ const TWITTER_HELP = {
|
|
|
776
777
|
{ flag: '(price)', desc: 'Free — local TOTP generation' },
|
|
777
778
|
],
|
|
778
779
|
buy: [
|
|
779
|
-
{ flag: '(no args)', desc: 'Purchase the oldest ready X account from the supplier pool' },
|
|
780
|
-
{ flag: '
|
|
781
|
-
{ flag: '
|
|
780
|
+
{ flag: '(no args)', desc: 'Purchase the oldest ready X account from the supplier pool. Default for every filter below is random.' },
|
|
781
|
+
{ flag: '--country <CC>', desc: 'Filter to a specific country (ISO alpha-2: US, GB, DE, …). Run `pool-prices` first to see what is priced.' },
|
|
782
|
+
{ flag: '--source web|mobile', desc: 'Filter by registration source from X about-profile. Multiplies country price by source multiplier (default 1.0).' },
|
|
783
|
+
{ flag: '--max-renames N', desc: 'Cap username-change count. --max-renames 0 = never renamed. NULL on row means unknown → does not match.' },
|
|
784
|
+
{ flag: '--age 1y|2y|...', desc: 'Optional age category filter' },
|
|
785
|
+
{ flag: '(price)', desc: '$5 USDC default; country_price * source_multiplier when filters are passed.' },
|
|
786
|
+
{ flag: '(example)', desc: 'palmyr twitter buy --country US --source web --max-renames 0' },
|
|
782
787
|
],
|
|
783
788
|
login: [
|
|
784
789
|
{ flag: '<username>', desc: 'Force a fresh server-side session (browser runtime)' },
|
|
@@ -940,12 +945,62 @@ const TWITTER_HELP = {
|
|
|
940
945
|
'pool-add': [
|
|
941
946
|
{ flag: '--credentials-line "..."', desc: 'Single account creds (login:pw:email:email_pw[:2fa[:ct0:auth_token]])' },
|
|
942
947
|
{ flag: '--file path.txt', desc: 'Bulk: one credentials-line per row (# = comment)' },
|
|
943
|
-
{ flag: '--price <USDC>', desc: 'Required —
|
|
944
|
-
{ flag: '--country <CC>', desc: 'Optional
|
|
948
|
+
{ flag: '--price <USDC>', desc: 'Required — per-account sale_price_usdc (legacy fallback when no country price row exists)' },
|
|
949
|
+
{ flag: '--country <CC>', desc: 'Optional override; twitterapi.io detects country + source + rename count from about_profile at seed time. Admin wins on country mismatch (flagged in response).' },
|
|
945
950
|
{ flag: '--age 1y|2y|3y|...', desc: 'Optional age category metadata' },
|
|
946
951
|
{ flag: '(auth)', desc: 'Admin-signed call — requires PALMYR_ADMIN_KEY' },
|
|
947
952
|
{ flag: '(price)', desc: 'Free — server-side seeding by pool operator' },
|
|
948
953
|
],
|
|
954
|
+
'pool-prices': [
|
|
955
|
+
{ flag: '(no args)', desc: 'List per-country prices set by the pool admin (public, free).' },
|
|
956
|
+
{ flag: '(price)', desc: 'Free' },
|
|
957
|
+
],
|
|
958
|
+
'pool-set-price': [
|
|
959
|
+
{ flag: '--country <CC>', desc: 'ISO 3166-1 alpha-2 country code (US, GB, DE, …)' },
|
|
960
|
+
{ flag: '--price <USDC>', desc: 'USDC amount the `buy --country <CC>` route will charge' },
|
|
961
|
+
{ flag: '(auth)', desc: 'Admin-signed call — requires PALMYR_ADMIN_KEY' },
|
|
962
|
+
{ flag: '(price)', desc: 'Free' },
|
|
963
|
+
],
|
|
964
|
+
'pool-delete-price': [
|
|
965
|
+
{ flag: '--country <CC>', desc: 'Country code to remove pricing for' },
|
|
966
|
+
{ flag: '(auth)', desc: 'Admin-signed call — requires PALMYR_ADMIN_KEY' },
|
|
967
|
+
{ flag: '(price)', desc: 'Free' },
|
|
968
|
+
],
|
|
969
|
+
'pool-set-source-multiplier': [
|
|
970
|
+
{ flag: '--source web|mobile|<id>', desc: 'Source identifier (matches the `source` column populated by twitterapi.io)' },
|
|
971
|
+
{ flag: '--multiplier <number>', desc: 'Positive scaling factor applied on top of country price when --source is passed at buy time. 1.0 = no change.' },
|
|
972
|
+
{ flag: '(auth)', desc: 'Admin-signed call — requires PALMYR_ADMIN_KEY' },
|
|
973
|
+
{ flag: '(price)', desc: 'Free' },
|
|
974
|
+
],
|
|
975
|
+
'pool-delete-source-multiplier': [
|
|
976
|
+
{ flag: '--source <id>', desc: 'Source identifier to remove the multiplier for (buy still works, just reverts to multiplier=1.0)' },
|
|
977
|
+
{ flag: '(auth)', desc: 'Admin-signed call — requires PALMYR_ADMIN_KEY' },
|
|
978
|
+
{ flag: '(price)', desc: 'Free' },
|
|
979
|
+
],
|
|
980
|
+
dispute: [
|
|
981
|
+
{ flag: '<account_id>', desc: 'Account id returned by `twitter buy` (the 32-char hex)' },
|
|
982
|
+
{ flag: '--reason suspended|other', desc: 'Default "suspended" — triggers auto-verify via twitterapi.io' },
|
|
983
|
+
{ flag: '--evidence "..."', desc: 'Optional note shown to the admin if the dispute ends up in admin_review' },
|
|
984
|
+
{ flag: '(price)', desc: '$0.01 USDC ownership-proof. 7-day window from purchase.' },
|
|
985
|
+
{ flag: '(example)', desc: 'palmyr twitter dispute abcd1234… --reason suspended' },
|
|
986
|
+
],
|
|
987
|
+
disputes: [
|
|
988
|
+
{ flag: '<dispute_id>', desc: 'Look up the status of a previously filed dispute' },
|
|
989
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
990
|
+
],
|
|
991
|
+
'pool-disputes': [
|
|
992
|
+
{ flag: '(no args)', desc: 'List every dispute in the system (admin)' },
|
|
993
|
+
{ flag: '--status admin_review|pending|replaced|refunded|rejected', desc: 'Filter by status' },
|
|
994
|
+
{ flag: '(auth)', desc: 'Admin-signed call — requires PALMYR_ADMIN_KEY' },
|
|
995
|
+
{ flag: '(price)', desc: 'Free' },
|
|
996
|
+
],
|
|
997
|
+
'pool-resolve-dispute': [
|
|
998
|
+
{ flag: '<dispute_id>', desc: 'Id from `pool-disputes`' },
|
|
999
|
+
{ flag: '--action replace|refund|reject', desc: 'replace = grant same-country swap; refund = USDC back to payer; reject = decline' },
|
|
1000
|
+
{ flag: '--note "..."', desc: 'Optional admin note appended to the resolution' },
|
|
1001
|
+
{ flag: '(auth)', desc: 'Admin-signed call — requires PALMYR_ADMIN_KEY' },
|
|
1002
|
+
{ flag: '(price)', desc: 'Free' },
|
|
1003
|
+
],
|
|
949
1004
|
'pool-status': [
|
|
950
1005
|
{ flag: '(no args)', desc: 'Available / sold / reserved counts in the X account pool' },
|
|
951
1006
|
{ flag: '(auth)', desc: 'Admin-signed call — requires PALMYR_ADMIN_KEY' },
|
|
@@ -1133,6 +1188,7 @@ const TOP_LEVEL_COMMANDS = [
|
|
|
1133
1188
|
{ name: 'doctor', description: 'Verify system health (cred store, vault, API)' },
|
|
1134
1189
|
{ name: 'pricing', description: 'All service prices' },
|
|
1135
1190
|
{ name: 'health', description: 'API status + version check' },
|
|
1191
|
+
{ name: 'telemetry', description: 'on · off · status (opt-in anonymous usage stats)' },
|
|
1136
1192
|
];
|
|
1137
1193
|
// ─── Help ───
|
|
1138
1194
|
function help() {
|
|
@@ -1226,6 +1282,22 @@ async function main() {
|
|
|
1226
1282
|
const startTime = Date.now();
|
|
1227
1283
|
// No first-time banner — agent-first CLI should never pollute output.
|
|
1228
1284
|
const url = process.env.PALMYR_API || config.api;
|
|
1285
|
+
// Opt-in telemetry. If the user has explicitly enabled it, queue this run's
|
|
1286
|
+
// exit-code + duration on shutdown (sync — async work in 'exit' is dropped)
|
|
1287
|
+
// and fire-and-forget any previously-queued events to the API right now.
|
|
1288
|
+
// Both no-ops when telemetry is off. Never blocks the user's command —
|
|
1289
|
+
// the flush races their work and Node exits once both finish.
|
|
1290
|
+
process.on('exit', (code) => {
|
|
1291
|
+
telemetryAppendEvent({
|
|
1292
|
+
cmd: subcommand ? `${command} ${subcommand}` : command,
|
|
1293
|
+
exitCode: code,
|
|
1294
|
+
durationMs: Date.now() - startTime,
|
|
1295
|
+
cliVersion: VERSION,
|
|
1296
|
+
nodeVersion: process.version,
|
|
1297
|
+
platform: process.platform,
|
|
1298
|
+
});
|
|
1299
|
+
});
|
|
1300
|
+
void telemetryFlushQueue(url);
|
|
1229
1301
|
const token = flags.token || config.apiKey || process.env.PALMYR_TOKEN || process.env.PALMYR_API_KEY;
|
|
1230
1302
|
const passphrase = flags.passphrase || process.env.PALMYR_WALLET_PASSPHRASE;
|
|
1231
1303
|
const ao = new Palmyr(url, true, token, passphrase);
|
|
@@ -6202,10 +6274,27 @@ async function main() {
|
|
|
6202
6274
|
return print({ success: true, platform, username, op: subcommand, ...(data?.data || {}) });
|
|
6203
6275
|
}
|
|
6204
6276
|
case 'buy': {
|
|
6205
|
-
// Agents
|
|
6277
|
+
// Agents say "buy" with optional --country / --source / --max-renames.
|
|
6278
|
+
// Each filter is independent (default: random across that
|
|
6279
|
+
// dimension). Pricing = country_price * source_multiplier:
|
|
6280
|
+
// - --country US → country_prices.US (e.g. $8)
|
|
6281
|
+
// - --source web → multiplied by web's row in
|
|
6282
|
+
// source_multipliers (e.g. 1.2)
|
|
6283
|
+
// - --max-renames 0 → filter only, no price impact
|
|
6284
|
+
// Without --country, falls back to the legacy $5 flat rate.
|
|
6285
|
+
const country = (flags.country || '').trim().toUpperCase() || undefined;
|
|
6286
|
+
const ageCategory = flags.age || flags['age-category'] || undefined;
|
|
6287
|
+
const source = (flags.source || '').trim().toLowerCase() || undefined;
|
|
6288
|
+
const maxRenamesRaw = flags['max-renames'];
|
|
6289
|
+
const maxUsernameChanges = maxRenamesRaw === undefined || maxRenamesRaw === ''
|
|
6290
|
+
? undefined
|
|
6291
|
+
: Number(maxRenamesRaw);
|
|
6292
|
+
if (maxUsernameChanges !== undefined && (!Number.isFinite(maxUsernameChanges) || maxUsernameChanges < 0)) {
|
|
6293
|
+
err('--max-renames must be a non-negative integer (e.g. 0 = never renamed)');
|
|
6294
|
+
}
|
|
6206
6295
|
let data;
|
|
6207
6296
|
try {
|
|
6208
|
-
data = await ao.socialTwitterBuy();
|
|
6297
|
+
data = await ao.socialTwitterBuy(country, ageCategory, { source, maxUsernameChanges });
|
|
6209
6298
|
}
|
|
6210
6299
|
catch (e) {
|
|
6211
6300
|
err(`Buy failed: ${e.message}`, EXIT.GENERAL);
|
|
@@ -6217,10 +6306,15 @@ async function main() {
|
|
|
6217
6306
|
// Auto-import into the local vault + prime the session cache so
|
|
6218
6307
|
// the buyer can post immediately with the cookies the admin
|
|
6219
6308
|
// pre-seasoned at pool-add time.
|
|
6309
|
+
const filterTags = [
|
|
6310
|
+
country && `country=${country}`,
|
|
6311
|
+
source && `source=${source}`,
|
|
6312
|
+
maxUsernameChanges !== undefined && `max_renames=${maxUsernameChanges}`,
|
|
6313
|
+
].filter(Boolean).join(', ');
|
|
6220
6314
|
const summary = sv.importAccount(platform, account.username, account.credentials, {
|
|
6221
6315
|
source: 'pool',
|
|
6222
6316
|
proxy_session_id: account.proxy_session_id,
|
|
6223
|
-
notes: 'Bought from pool',
|
|
6317
|
+
notes: filterTags ? `Bought from pool (${filterTags})` : 'Bought from pool',
|
|
6224
6318
|
});
|
|
6225
6319
|
sv.saveSession(summary.id, platform, account.cookies || []);
|
|
6226
6320
|
sv.updateMeta(platform, summary.username, { last_action_at: new Date().toISOString() });
|
|
@@ -6228,8 +6322,177 @@ async function main() {
|
|
|
6228
6322
|
success: true,
|
|
6229
6323
|
platform,
|
|
6230
6324
|
username: summary.username,
|
|
6231
|
-
|
|
6325
|
+
country: account.country,
|
|
6326
|
+
source: account.source,
|
|
6327
|
+
username_change_count: account.username_change_count,
|
|
6328
|
+
account_based_in: account.account_based_in,
|
|
6329
|
+
account_id: account.id,
|
|
6330
|
+
hint: `Ready to post — try: palmyr twitter post ${summary.username} --body "gm". ` +
|
|
6331
|
+
`If the account is suspended within 7 days, run: palmyr twitter dispute ${account.id}`,
|
|
6332
|
+
});
|
|
6333
|
+
}
|
|
6334
|
+
case 'pool-prices': {
|
|
6335
|
+
// Public: which countries are priced and what they cost. Run
|
|
6336
|
+
// before `buy --country X` to confirm the country is available.
|
|
6337
|
+
let data;
|
|
6338
|
+
try {
|
|
6339
|
+
data = await ao.socialTwitterPoolPrices();
|
|
6340
|
+
}
|
|
6341
|
+
catch (e) {
|
|
6342
|
+
err(`pool-prices failed: ${e.message}`, EXIT.GENERAL);
|
|
6343
|
+
}
|
|
6344
|
+
return print(data);
|
|
6345
|
+
}
|
|
6346
|
+
case 'pool-set-price': {
|
|
6347
|
+
// Admin: set USDC price for a single country code. Idempotent.
|
|
6348
|
+
const { buildAdminHeaders } = await import('./admin-auth.js');
|
|
6349
|
+
const country = (flags.country || '').trim().toUpperCase();
|
|
6350
|
+
const price = flags.price !== undefined ? Number(flags.price) : NaN;
|
|
6351
|
+
if (!country)
|
|
6352
|
+
err('--country <CC> required (ISO 3166-1 alpha-2: US, GB, DE, …)');
|
|
6353
|
+
if (!Number.isFinite(price) || price <= 0)
|
|
6354
|
+
err('--price <USDC> required (positive number)');
|
|
6355
|
+
const path = `/social/twitter/pool/prices/${encodeURIComponent(country)}`;
|
|
6356
|
+
const headers = buildAdminHeaders('PUT', path);
|
|
6357
|
+
const res = await fetch(ao.api + path, {
|
|
6358
|
+
method: 'PUT',
|
|
6359
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
6360
|
+
body: JSON.stringify({ price_usdc: price }),
|
|
6361
|
+
});
|
|
6362
|
+
const data = await res.json();
|
|
6363
|
+
if (!res.ok || !data.success)
|
|
6364
|
+
err(`pool-set-price failed: ${data.error || `HTTP ${res.status}`}`, EXIT.GENERAL);
|
|
6365
|
+
return print(data);
|
|
6366
|
+
}
|
|
6367
|
+
case 'pool-delete-price': {
|
|
6368
|
+
// Admin: remove the row for a country. Subsequent `buy --country X`
|
|
6369
|
+
// will return 400 "Country not priced" until set again.
|
|
6370
|
+
const { buildAdminHeaders } = await import('./admin-auth.js');
|
|
6371
|
+
const country = (flags.country || '').trim().toUpperCase();
|
|
6372
|
+
if (!country)
|
|
6373
|
+
err('--country <CC> required');
|
|
6374
|
+
const path = `/social/twitter/pool/prices/${encodeURIComponent(country)}`;
|
|
6375
|
+
const headers = buildAdminHeaders('DELETE', path);
|
|
6376
|
+
const res = await fetch(ao.api + path, { method: 'DELETE', headers });
|
|
6377
|
+
const data = await res.json();
|
|
6378
|
+
if (!res.ok)
|
|
6379
|
+
err(`pool-delete-price failed: ${data.error || `HTTP ${res.status}`}`, EXIT.GENERAL);
|
|
6380
|
+
return print(data);
|
|
6381
|
+
}
|
|
6382
|
+
case 'pool-set-source-multiplier': {
|
|
6383
|
+
// Admin: scale the country price for buys filtered by a given
|
|
6384
|
+
// source ('web', 'mobile', …). e.g. mult=1.2 → web buys cost
|
|
6385
|
+
// 20% more than the base country price.
|
|
6386
|
+
const { buildAdminHeaders } = await import('./admin-auth.js');
|
|
6387
|
+
const source = (flags.source || '').trim().toLowerCase();
|
|
6388
|
+
const mult = flags.multiplier !== undefined ? Number(flags.multiplier) : NaN;
|
|
6389
|
+
if (!source)
|
|
6390
|
+
err('--source <name> required (e.g. web, mobile)');
|
|
6391
|
+
if (!Number.isFinite(mult) || mult <= 0)
|
|
6392
|
+
err('--multiplier <number> required (positive)');
|
|
6393
|
+
const path = `/social/twitter/pool/source-multipliers/${encodeURIComponent(source)}`;
|
|
6394
|
+
const headers = buildAdminHeaders('PUT', path);
|
|
6395
|
+
const res = await fetch(ao.api + path, {
|
|
6396
|
+
method: 'PUT',
|
|
6397
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
6398
|
+
body: JSON.stringify({ multiplier: mult }),
|
|
6232
6399
|
});
|
|
6400
|
+
const data = await res.json();
|
|
6401
|
+
if (!res.ok || !data.success)
|
|
6402
|
+
err(`pool-set-source-multiplier failed: ${data.error || `HTTP ${res.status}`}`, EXIT.GENERAL);
|
|
6403
|
+
return print(data);
|
|
6404
|
+
}
|
|
6405
|
+
case 'pool-delete-source-multiplier': {
|
|
6406
|
+
// Admin: drop the multiplier for a source. Subsequent `buy
|
|
6407
|
+
// --source X` reverts to using 1.0 (filter only, no price scaling).
|
|
6408
|
+
const { buildAdminHeaders } = await import('./admin-auth.js');
|
|
6409
|
+
const source = (flags.source || '').trim().toLowerCase();
|
|
6410
|
+
if (!source)
|
|
6411
|
+
err('--source <name> required');
|
|
6412
|
+
const path = `/social/twitter/pool/source-multipliers/${encodeURIComponent(source)}`;
|
|
6413
|
+
const headers = buildAdminHeaders('DELETE', path);
|
|
6414
|
+
const res = await fetch(ao.api + path, { method: 'DELETE', headers });
|
|
6415
|
+
const data = await res.json();
|
|
6416
|
+
if (!res.ok)
|
|
6417
|
+
err(`pool-delete-source-multiplier failed: ${data.error || `HTTP ${res.status}`}`, EXIT.GENERAL);
|
|
6418
|
+
return print(data);
|
|
6419
|
+
}
|
|
6420
|
+
case 'dispute': {
|
|
6421
|
+
// Buyer: file a dispute for a pool-bought account that got
|
|
6422
|
+
// suspended. Server auto-verifies via twitterapi.io and either
|
|
6423
|
+
// hands over a same-country replacement, refunds USDC, or queues
|
|
6424
|
+
// for admin review when the signal is ambiguous.
|
|
6425
|
+
const accountId = positional[0] || flags['account-id'] || flags.id;
|
|
6426
|
+
const reason = (flags.reason || 'suspended');
|
|
6427
|
+
const evidence = flags.evidence || flags.note || undefined;
|
|
6428
|
+
if (!accountId)
|
|
6429
|
+
err('<account_id> required (the id returned by `palmyr twitter buy`)');
|
|
6430
|
+
if (reason !== 'suspended' && reason !== 'other')
|
|
6431
|
+
err('--reason must be "suspended" or "other"');
|
|
6432
|
+
let data;
|
|
6433
|
+
try {
|
|
6434
|
+
data = await ao.socialTwitterDispute(accountId, { reason, evidence });
|
|
6435
|
+
}
|
|
6436
|
+
catch (e) {
|
|
6437
|
+
err(`Dispute failed: ${e.message}`, EXIT.GENERAL);
|
|
6438
|
+
}
|
|
6439
|
+
if (!data?.success)
|
|
6440
|
+
err(`Dispute failed: ${data?.error || 'unknown'}`, EXIT.GENERAL);
|
|
6441
|
+
return print(data);
|
|
6442
|
+
}
|
|
6443
|
+
case 'disputes': {
|
|
6444
|
+
// Buyer: list ONE specific dispute by id. Listing all your
|
|
6445
|
+
// disputes isn't supported by the buyer surface today — track
|
|
6446
|
+
// the id printed by `dispute` and call `disputes <id>`.
|
|
6447
|
+
const id = positional[0] || flags.id;
|
|
6448
|
+
if (!id)
|
|
6449
|
+
err('<dispute_id> required');
|
|
6450
|
+
let data;
|
|
6451
|
+
try {
|
|
6452
|
+
data = await ao.socialTwitterDisputeGet(id);
|
|
6453
|
+
}
|
|
6454
|
+
catch (e) {
|
|
6455
|
+
err(`Get dispute failed: ${e.message}`, EXIT.GENERAL);
|
|
6456
|
+
}
|
|
6457
|
+
return print(data);
|
|
6458
|
+
}
|
|
6459
|
+
case 'pool-disputes': {
|
|
6460
|
+
// Admin: list every dispute, optionally filter by status. The
|
|
6461
|
+
// `admin_review` queue is the human-decision backlog.
|
|
6462
|
+
const { buildAdminHeaders } = await import('./admin-auth.js');
|
|
6463
|
+
const status = flags.status || undefined;
|
|
6464
|
+
const path = '/social/twitter/pool/disputes' + (status ? `?status=${encodeURIComponent(status)}` : '');
|
|
6465
|
+
const headers = buildAdminHeaders('GET', path);
|
|
6466
|
+
const res = await fetch(ao.api + path, { headers });
|
|
6467
|
+
const data = await res.json();
|
|
6468
|
+
if (!res.ok)
|
|
6469
|
+
err(`pool-disputes failed: ${data.error || `HTTP ${res.status}`}`, EXIT.GENERAL);
|
|
6470
|
+
return print(data);
|
|
6471
|
+
}
|
|
6472
|
+
case 'pool-resolve-dispute': {
|
|
6473
|
+
// Admin: resolve an admin_review dispute. action ∈ replace |
|
|
6474
|
+
// refund | reject. `replace` needs same-country stock; `refund`
|
|
6475
|
+
// needs payment provenance on the row (auto-saved at buy time).
|
|
6476
|
+
const { buildAdminHeaders } = await import('./admin-auth.js');
|
|
6477
|
+
const id = positional[0] || flags.id;
|
|
6478
|
+
const action = flags.action;
|
|
6479
|
+
const note = flags.note || undefined;
|
|
6480
|
+
if (!id)
|
|
6481
|
+
err('<dispute_id> required');
|
|
6482
|
+
if (action !== 'replace' && action !== 'refund' && action !== 'reject') {
|
|
6483
|
+
err('--action must be "replace", "refund", or "reject"');
|
|
6484
|
+
}
|
|
6485
|
+
const path = `/social/twitter/pool/disputes/${encodeURIComponent(id)}/resolve`;
|
|
6486
|
+
const headers = buildAdminHeaders('POST', path);
|
|
6487
|
+
const res = await fetch(ao.api + path, {
|
|
6488
|
+
method: 'POST',
|
|
6489
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
6490
|
+
body: JSON.stringify({ action, ...(note ? { note } : {}) }),
|
|
6491
|
+
});
|
|
6492
|
+
const data = await res.json();
|
|
6493
|
+
if (!res.ok || !data.success)
|
|
6494
|
+
err(`pool-resolve-dispute failed: ${data.error || `HTTP ${res.status}`}`, EXIT.GENERAL);
|
|
6495
|
+
return print(data);
|
|
6233
6496
|
}
|
|
6234
6497
|
case 'pool-add': {
|
|
6235
6498
|
const { buildAdminHeaders } = await import('./admin-auth.js');
|
|
@@ -7080,6 +7343,55 @@ async function main() {
|
|
|
7080
7343
|
'The Palmyr server fires them at post_at without any client process.', EXIT.BAD_INPUT);
|
|
7081
7344
|
break;
|
|
7082
7345
|
}
|
|
7346
|
+
case 'telemetry': {
|
|
7347
|
+
// Off by default. Opt-in only. We never auto-enable, never prompt at
|
|
7348
|
+
// startup, never write to stdout outside this command. Captured fields
|
|
7349
|
+
// and storage location are documented in cli/telemetry.ts.
|
|
7350
|
+
const action = (subcommand || 'status').toLowerCase();
|
|
7351
|
+
if (action !== 'on' && action !== 'off' && action !== 'status') {
|
|
7352
|
+
err(`Unknown telemetry action: ${action}. Use: on | off | status`, EXIT.BAD_INPUT);
|
|
7353
|
+
}
|
|
7354
|
+
let state;
|
|
7355
|
+
if (action === 'on')
|
|
7356
|
+
state = setTelemetryEnabled(true);
|
|
7357
|
+
else if (action === 'off')
|
|
7358
|
+
state = setTelemetryEnabled(false);
|
|
7359
|
+
else
|
|
7360
|
+
state = getTelemetryState();
|
|
7361
|
+
const payload = {
|
|
7362
|
+
enabled: state.enabled,
|
|
7363
|
+
installId: state.installId || null,
|
|
7364
|
+
optedInAt: state.optedInAt || null,
|
|
7365
|
+
queuedEvents: telemetryQueuedCount(),
|
|
7366
|
+
captures: ['cmd', 'exitCode', 'durationMs', 'cliVersion', 'nodeVersion', 'platform'],
|
|
7367
|
+
neverCaptures: ['flag values', 'positional args', 'stdout/stderr', 'wallet addresses', 'phone numbers', 'any user input'],
|
|
7368
|
+
};
|
|
7369
|
+
if (AGENT_MODE) {
|
|
7370
|
+
print(payload);
|
|
7371
|
+
}
|
|
7372
|
+
else {
|
|
7373
|
+
const status = state.enabled ? `${t.success}on${t.reset}` : `${t.muted}off${t.reset}`;
|
|
7374
|
+
console.log(`Telemetry: ${status}`);
|
|
7375
|
+
if (state.installId)
|
|
7376
|
+
console.log(`Install ID: ${t.muted}${state.installId}${t.reset}`);
|
|
7377
|
+
if (state.optedInAt)
|
|
7378
|
+
console.log(`Opted in: ${state.optedInAt}`);
|
|
7379
|
+
if (payload.queuedEvents)
|
|
7380
|
+
console.log(`Queued: ${payload.queuedEvents} event(s) waiting to send`);
|
|
7381
|
+
console.log('');
|
|
7382
|
+
console.log(`Captures: ${payload.captures.join(', ')}`);
|
|
7383
|
+
console.log(`Never: flag values, positional args, stdout, user input`);
|
|
7384
|
+
if (!state.enabled) {
|
|
7385
|
+
console.log('');
|
|
7386
|
+
console.log(`Opt in: ${t.accent}palmyr telemetry on${t.reset}`);
|
|
7387
|
+
}
|
|
7388
|
+
else {
|
|
7389
|
+
console.log('');
|
|
7390
|
+
console.log(`Opt out: ${t.accent}palmyr telemetry off${t.reset} (drops any queued events)`);
|
|
7391
|
+
}
|
|
7392
|
+
}
|
|
7393
|
+
break;
|
|
7394
|
+
}
|
|
7083
7395
|
case 'config': {
|
|
7084
7396
|
const cfg = loadConfig();
|
|
7085
7397
|
const { homedir } = await import('os');
|