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