clixad 0.0.1-beta.15 → 0.0.1-beta.17
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/README.md +4 -0
- package/dist/clixad.mjs +407 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,10 @@ npm install -g clixad
|
|
|
19
19
|
|
|
20
20
|
Requires Node.js >= 20.
|
|
21
21
|
|
|
22
|
+
## Community
|
|
23
|
+
|
|
24
|
+
Discord: <https://discord.gg/pwxPM3QyF> — bug reports and feature requests are read there.
|
|
25
|
+
|
|
22
26
|
## What it does
|
|
23
27
|
|
|
24
28
|
It reads, searches and edits files and runs shell commands in the directory you start it in, asking
|
package/dist/clixad.mjs
CHANGED
|
@@ -101,6 +101,52 @@ function grantRangeLabel(range) {
|
|
|
101
101
|
const n = (v) => v.toLocaleString("en-US");
|
|
102
102
|
return low === high ? `${n(low)} credits on completion` : `${n(low)}\u2013${n(high)} credits on completion`;
|
|
103
103
|
}
|
|
104
|
+
function streakLabel(streak) {
|
|
105
|
+
if (streak.length <= 0 || streak.day <= 0) return null;
|
|
106
|
+
const n = (x) => x.toLocaleString("en-US");
|
|
107
|
+
const where = `day ${streak.day} of ${streak.length}`;
|
|
108
|
+
if (!streak.claimed) return `daily streak: ${where} \xB7 today's already counted`;
|
|
109
|
+
const tomorrow = streak.schedule[Math.min(streak.day, streak.length - 1)];
|
|
110
|
+
const next = tomorrow === void 0 ? "" : ` \xB7 tomorrow ${n(tomorrow)}`;
|
|
111
|
+
return `daily streak: ${where} \xB7 +${n(streak.credits)} credits${next}`;
|
|
112
|
+
}
|
|
113
|
+
function referLines(info) {
|
|
114
|
+
const n = (x) => x.toLocaleString("en-US");
|
|
115
|
+
const lines = [
|
|
116
|
+
`your code: ${info.code}`,
|
|
117
|
+
`share it: clixad login --invite ${info.code}`,
|
|
118
|
+
`you both get ${n(info.inviterCredits)} credits once they complete their first offer`
|
|
119
|
+
];
|
|
120
|
+
if (info.invited === 0) {
|
|
121
|
+
lines.push("nobody has signed up with it yet");
|
|
122
|
+
return lines;
|
|
123
|
+
}
|
|
124
|
+
const waiting = info.invited - info.credited;
|
|
125
|
+
lines.push(
|
|
126
|
+
`${n(info.invited)} signed up \xB7 ${n(info.credited)} completed an offer \xB7 ${n(info.creditsEarned)} credits earned`
|
|
127
|
+
);
|
|
128
|
+
if (waiting > 0) {
|
|
129
|
+
lines.push(`${n(waiting)} haven't completed an offer yet \u2014 that pays nothing until they do`);
|
|
130
|
+
}
|
|
131
|
+
if (info.remaining === 0 && info.maxCredited > 0) {
|
|
132
|
+
lines.push(`you've been paid for the maximum of ${n(info.maxCredited)} referrals`);
|
|
133
|
+
}
|
|
134
|
+
return lines;
|
|
135
|
+
}
|
|
136
|
+
function redeemMessage(result) {
|
|
137
|
+
switch (result.status) {
|
|
138
|
+
case "redeemed":
|
|
139
|
+
return { ok: true, text: `+${result.credited.toLocaleString("en-US")} credits` };
|
|
140
|
+
case "used":
|
|
141
|
+
return { ok: false, text: "that code has already been used" };
|
|
142
|
+
case "expired":
|
|
143
|
+
return { ok: false, text: "that code has expired" };
|
|
144
|
+
case "wrong_account":
|
|
145
|
+
return { ok: false, text: "that code belongs to a different account \u2014 check who you're signed in as" };
|
|
146
|
+
default:
|
|
147
|
+
return { ok: false, text: "that isn't a code we recognise \u2014 check it for typos" };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
104
150
|
function ledgerReasonLabel(reason) {
|
|
105
151
|
return LEDGER_REASONS[reason] ?? reason.replace(/_/g, " ");
|
|
106
152
|
}
|
|
@@ -195,6 +241,10 @@ var init_client = __esm({
|
|
|
195
241
|
CHAT_ENCODED_CONTENT_TYPE = "application/vnd.clixad.chat+gzip";
|
|
196
242
|
LEDGER_REASONS = {
|
|
197
243
|
signup_bonus: "signup bonus",
|
|
244
|
+
login_streak: "daily streak",
|
|
245
|
+
feedback_bonus: "thanks for the feedback",
|
|
246
|
+
referral_bonus: "referral bonus",
|
|
247
|
+
redeem_code: "code redeemed",
|
|
198
248
|
ad_reward: "offer reward",
|
|
199
249
|
ad_screenout_bonus: "screenout bonus (didn't qualify)",
|
|
200
250
|
ad_sponsor_impression: "sponsored line shown",
|
|
@@ -323,16 +373,154 @@ var init_client = __esm({
|
|
|
323
373
|
return void 0;
|
|
324
374
|
}
|
|
325
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* Claim today's login-streak grant, or find out that today is already claimed.
|
|
378
|
+
*
|
|
379
|
+
* Safe to call from anywhere and as often as you like: the gateway keys it on
|
|
380
|
+
* `(account, UTC day)`, so the second call of the day credits nothing and
|
|
381
|
+
* answers with the same day number. That is what lets every surface which
|
|
382
|
+
* might be somebody's first contact today — a login, the REPL opening,
|
|
383
|
+
* `wallet` — claim without coordinating.
|
|
384
|
+
*
|
|
385
|
+
* **Never throws.** Like `sponsorConfig`, every caller is decorating something
|
|
386
|
+
* that already works: a gateway too old to have the route, a cold start, a
|
|
387
|
+
* dropped connection and an expired token all mean the same thing here, which
|
|
388
|
+
* is "say nothing about a streak". A missed grant is claimed by the next
|
|
389
|
+
* command; a session that died over one would be a real loss.
|
|
390
|
+
*/
|
|
391
|
+
async claimStreak(signal) {
|
|
392
|
+
try {
|
|
393
|
+
const res = await fetch(`${this.config.gatewayUrl}/v1/streak/claim`, {
|
|
394
|
+
method: "POST",
|
|
395
|
+
headers: this.headers(),
|
|
396
|
+
...signal ? { signal } : {}
|
|
397
|
+
});
|
|
398
|
+
if (!res.ok) return null;
|
|
399
|
+
const body = await res.json();
|
|
400
|
+
if (typeof body.day !== "number" || typeof body.credits !== "number") return null;
|
|
401
|
+
return {
|
|
402
|
+
day: body.day,
|
|
403
|
+
credits: body.credits,
|
|
404
|
+
claimed: body.claimed === true,
|
|
405
|
+
balance: typeof body.balance === "number" ? body.balance : 0,
|
|
406
|
+
schedule: Array.isArray(body.schedule) ? body.schedule : [],
|
|
407
|
+
length: typeof body.length === "number" ? body.length : 0
|
|
408
|
+
};
|
|
409
|
+
} catch {
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Send one rating, and answer what it was paid.
|
|
415
|
+
*
|
|
416
|
+
* **Never throws**, for `claimStreak`'s reason and one of its own: this is
|
|
417
|
+
* called from a prompt that has already interrupted somebody, and the worst
|
|
418
|
+
* possible outcome is that answering it takes the session down. A gateway too
|
|
419
|
+
* old for the route, a cold start and an expired token all mean the same thing
|
|
420
|
+
* — the rating is lost, which costs the user nothing and us one data point.
|
|
421
|
+
*
|
|
422
|
+
* `null` for "nothing to say"; an outcome with `credited: 0` for "recorded,
|
|
423
|
+
* unpaid", which is what the daily ceiling looks like from here and is worth
|
|
424
|
+
* telling the user apart from a grant.
|
|
425
|
+
*/
|
|
426
|
+
async sendFeedback(report, signal) {
|
|
427
|
+
try {
|
|
428
|
+
const res = await fetch(`${this.config.gatewayUrl}/v1/feedback`, {
|
|
429
|
+
method: "POST",
|
|
430
|
+
headers: this.headers(),
|
|
431
|
+
...signal ? { signal } : {},
|
|
432
|
+
body: JSON.stringify({
|
|
433
|
+
session_id: report.session,
|
|
434
|
+
turn: report.turn,
|
|
435
|
+
rating: report.rating,
|
|
436
|
+
model: report.model,
|
|
437
|
+
prompt: report.prompt,
|
|
438
|
+
response: report.response,
|
|
439
|
+
...report.steps === void 0 ? {} : { steps: report.steps }
|
|
440
|
+
})
|
|
441
|
+
});
|
|
442
|
+
if (!res.ok) return null;
|
|
443
|
+
const body = await res.json();
|
|
444
|
+
return {
|
|
445
|
+
recorded: body.recorded === true,
|
|
446
|
+
credited: typeof body.credited === "number" ? body.credited : 0,
|
|
447
|
+
balance: typeof body.balance === "number" ? body.balance : 0
|
|
448
|
+
};
|
|
449
|
+
} catch {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* This account's referral code and standing, minting the code if it has none.
|
|
455
|
+
*
|
|
456
|
+
* **This one throws**, unlike `claimStreak` and `sendFeedback`. Those two
|
|
457
|
+
* decorate something that already works and a failure costs a line of chrome;
|
|
458
|
+
* this *is* the command — `clixad refer` with nothing to print is a command
|
|
459
|
+
* that did not run, and saying so beats printing an empty summary.
|
|
460
|
+
*/
|
|
461
|
+
async referral(signal) {
|
|
462
|
+
const res = await fetch(`${this.config.gatewayUrl}/v1/referrals`, {
|
|
463
|
+
headers: this.headers(),
|
|
464
|
+
...signal ? { signal } : {}
|
|
465
|
+
});
|
|
466
|
+
if (res.status === 401) throw await authFailure(res, "referrals");
|
|
467
|
+
if (!res.ok) throw new Error(await httpFailure(res, "referrals"));
|
|
468
|
+
const body = await res.json();
|
|
469
|
+
const num = (value) => typeof value === "number" ? value : 0;
|
|
470
|
+
if (typeof body.code !== "string" || !body.code) {
|
|
471
|
+
throw new Error("the gateway did not return a referral code");
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
code: body.code,
|
|
475
|
+
invited: num(body.invited),
|
|
476
|
+
credited: num(body.credited),
|
|
477
|
+
creditsEarned: num(body.credits_earned),
|
|
478
|
+
remaining: num(body.remaining),
|
|
479
|
+
maxCredited: num(body.max_credited),
|
|
480
|
+
inviterCredits: num(body.inviter_credits),
|
|
481
|
+
inviteeCredits: num(body.invitee_credits)
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Spend a redeem code.
|
|
486
|
+
*
|
|
487
|
+
* **Throws**, like `referral` and unlike `claimStreak`: this is the command
|
|
488
|
+
* itself, and somebody who typed a code and got silence has been told nothing.
|
|
489
|
+
* A refusal is not a throw though — an expired or already-spent code answers
|
|
490
|
+
* 200 with a status, because that is an answer rather than a failure.
|
|
491
|
+
*/
|
|
492
|
+
async redeem(code, signal) {
|
|
493
|
+
const res = await fetch(`${this.config.gatewayUrl}/v1/redeem`, {
|
|
494
|
+
method: "POST",
|
|
495
|
+
headers: this.headers(),
|
|
496
|
+
...signal ? { signal } : {},
|
|
497
|
+
body: JSON.stringify({ code })
|
|
498
|
+
});
|
|
499
|
+
if (res.status === 401) throw await authFailure(res, "redeem");
|
|
500
|
+
if (!res.ok) throw new Error(await httpFailure(res, "redeem"));
|
|
501
|
+
const body = await res.json();
|
|
502
|
+
return {
|
|
503
|
+
status: body.status ?? "unknown",
|
|
504
|
+
credited: typeof body.credited === "number" ? body.credited : 0,
|
|
505
|
+
balance: typeof body.balance === "number" ? body.balance : 0
|
|
506
|
+
};
|
|
507
|
+
}
|
|
326
508
|
async creditPacks() {
|
|
327
509
|
const res = await fetch(`${this.config.gatewayUrl}/v1/billing/packs`);
|
|
328
510
|
if (!res.ok) throw new Error(`packs failed: ${res.status}`);
|
|
329
511
|
return res.json();
|
|
330
512
|
}
|
|
331
|
-
|
|
513
|
+
/**
|
|
514
|
+
* `consent` carries the two declarations § 356 (5) BGB requires before we may
|
|
515
|
+
* deliver digital content inside the withdrawal period. It is not optional:
|
|
516
|
+
* the gateway refuses a checkout without it, because starting a purchase we
|
|
517
|
+
* could not deliver instantly is worse than not starting one.
|
|
518
|
+
*/
|
|
519
|
+
async checkout(pack, consent) {
|
|
332
520
|
const res = await fetch(`${this.config.gatewayUrl}/v1/billing/checkout`, {
|
|
333
521
|
method: "POST",
|
|
334
522
|
headers: this.headers(),
|
|
335
|
-
body: JSON.stringify({ pack })
|
|
523
|
+
body: JSON.stringify({ pack, consent })
|
|
336
524
|
});
|
|
337
525
|
if (res.status === 401) throw await authFailure(res, "checkout");
|
|
338
526
|
if (res.status === 501) throw new Error("Buying credits isn't enabled (Stripe not configured on the gateway).");
|
|
@@ -819,12 +1007,18 @@ function sleep(ms, signal) {
|
|
|
819
1007
|
signal.addEventListener("abort", done, { once: true });
|
|
820
1008
|
});
|
|
821
1009
|
}
|
|
1010
|
+
var TERMS_URL, PRIVACY_URL, TERMS_NOTICE;
|
|
822
1011
|
var init_login = __esm({
|
|
823
1012
|
"src/login.ts"() {
|
|
824
1013
|
"use strict";
|
|
825
1014
|
init_client();
|
|
826
1015
|
init_browser();
|
|
827
1016
|
init_config();
|
|
1017
|
+
TERMS_URL = "https://clixad.io/terms.html";
|
|
1018
|
+
PRIVACY_URL = "https://clixad.io/privacy.html";
|
|
1019
|
+
TERMS_NOTICE = `By creating an account you confirm that you are at least 18 and that you agree to
|
|
1020
|
+
the Terms \u2014 ${TERMS_URL}
|
|
1021
|
+
and the Privacy Policy \u2014 ${PRIVACY_URL}`;
|
|
828
1022
|
}
|
|
829
1023
|
});
|
|
830
1024
|
|
|
@@ -4452,6 +4646,15 @@ var init_commands = __esm({
|
|
|
4452
4646
|
// forms, surveys and app trials, and describing the top-up as a video costs
|
|
4453
4647
|
// the user the one expectation that makes a screenout make sense.
|
|
4454
4648
|
{ name: "earn", desc: "open the offer wall to earn credits" },
|
|
4649
|
+
// Deliberately *no* `args`, for /login's reason: an arg hint makes enter
|
|
4650
|
+
// complete the command instead of running it, and printing the code is the
|
|
4651
|
+
// whole command.
|
|
4652
|
+
{ name: "refer", desc: "your invite code, and what it has earned" },
|
|
4653
|
+
// This one *does* carry an `args` hint, unlike /refer and /login: the code is
|
|
4654
|
+
// the whole command, and `/redeem` on its own has nothing to do — so enter
|
|
4655
|
+
// completing to "/redeem " is the helpful outcome rather than the surprising
|
|
4656
|
+
// one.
|
|
4657
|
+
{ name: "redeem", args: "<code>", desc: "spend a code you were given" },
|
|
4455
4658
|
// Reachable from inside the REPL because that is where the 401 is printed.
|
|
4456
4659
|
// "Run `clixad login` first" is not a runnable instruction at this prompt —
|
|
4457
4660
|
// anything without a leading slash is a prompt, so it went to the model and
|
|
@@ -4926,7 +5129,7 @@ function useTerminalSize() {
|
|
|
4926
5129
|
}, [stdout]);
|
|
4927
5130
|
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
4928
5131
|
}
|
|
4929
|
-
function App({ client, config, wallet, session, initialTask, sponsorServe }) {
|
|
5132
|
+
function App({ client, config, wallet, streak, session, initialTask, sponsorServe }) {
|
|
4930
5133
|
const { exit } = useApp();
|
|
4931
5134
|
const { stdout, write: writeToStdout } = useStdout();
|
|
4932
5135
|
const idRef = useRef(1);
|
|
@@ -5001,6 +5204,8 @@ function App({ client, config, wallet, session, initialTask, sponsorServe }) {
|
|
|
5001
5204
|
const pendingTaskRef = useRef(null);
|
|
5002
5205
|
const mcpRef = useRef(McpHub.empty());
|
|
5003
5206
|
const todosRef = useRef(new TodoList());
|
|
5207
|
+
const lastTurnRef = useRef(null);
|
|
5208
|
+
const turnCountRef = useRef(config.turns ?? 0);
|
|
5004
5209
|
const deltaBufRef = useRef("");
|
|
5005
5210
|
const deltaTimerRef = useRef(null);
|
|
5006
5211
|
const sponsorRef = useRef(sponsorSource());
|
|
@@ -5080,6 +5285,8 @@ function App({ client, config, wallet, session, initialTask, sponsorServe }) {
|
|
|
5080
5285
|
client.onNotice = (text2) => push({ kind: "notice", tone: "warn", text: text2 });
|
|
5081
5286
|
const files = contextRef.current.files;
|
|
5082
5287
|
if (files.length) push({ kind: "notice", text: ` context: ${files.join(", ")}` });
|
|
5288
|
+
const streakLine = streak && streak.claimed ? streakLabel(streak) : null;
|
|
5289
|
+
if (streakLine) push({ kind: "notice", tone: "good", text: ` ${streakLine}` });
|
|
5083
5290
|
if (session?.messages.length) {
|
|
5084
5291
|
push({ kind: "notice", text: ` resumed session ${session.id} (${session.messages.length} messages)` });
|
|
5085
5292
|
}
|
|
@@ -5364,6 +5571,16 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5364
5571
|
text: result.content,
|
|
5365
5572
|
meta: `[${result.creditsCharged} credits \xB7 balance ${result.balance.toLocaleString("en-US")}]`
|
|
5366
5573
|
});
|
|
5574
|
+
lastTurnRef.current = {
|
|
5575
|
+
turn: turnCountRef.current,
|
|
5576
|
+
model,
|
|
5577
|
+
prompt: task,
|
|
5578
|
+
response: result.content,
|
|
5579
|
+
steps: result.steps
|
|
5580
|
+
};
|
|
5581
|
+
turnCountRef.current += 1;
|
|
5582
|
+
config.turns = turnCountRef.current;
|
|
5583
|
+
saveConfig(config);
|
|
5367
5584
|
}
|
|
5368
5585
|
} catch (err) {
|
|
5369
5586
|
if (err instanceof PaywallError) {
|
|
@@ -5434,6 +5651,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5434
5651
|
const runLogin = useCallback(
|
|
5435
5652
|
async (arg) => {
|
|
5436
5653
|
const args = parseLoginArgs(arg.split(/\s+/).filter(Boolean));
|
|
5654
|
+
push({ kind: "notice", text: TERMS_NOTICE });
|
|
5437
5655
|
await runBusy(SIGNING_IN, async (signal) => {
|
|
5438
5656
|
const outcome = await performLogin(
|
|
5439
5657
|
client,
|
|
@@ -5458,6 +5676,10 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5458
5676
|
tone: "good",
|
|
5459
5677
|
text: ` signed in as ${account.login ?? account.email ?? "you"} \xB7 balance ${account.balance.toLocaleString("en-US")} credits` + (account.created ? " (signup bonus)" : "")
|
|
5460
5678
|
});
|
|
5679
|
+
const claimed = await client.claimStreak(signal);
|
|
5680
|
+
const line2 = claimed?.claimed ? streakLabel(claimed) : null;
|
|
5681
|
+
if (claimed?.claimed) setBalance(claimed.balance);
|
|
5682
|
+
if (line2) push({ kind: "notice", tone: "good", text: ` ${line2}` });
|
|
5461
5683
|
return;
|
|
5462
5684
|
}
|
|
5463
5685
|
case "closed":
|
|
@@ -5549,6 +5771,7 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5549
5771
|
expandedRef.current = /* @__PURE__ */ new Set();
|
|
5550
5772
|
heightCacheRef.current = /* @__PURE__ */ new Map();
|
|
5551
5773
|
todosRef.current = new TodoList();
|
|
5774
|
+
lastTurnRef.current = null;
|
|
5552
5775
|
sessionRef.current = { id: newSessionId(), started: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5553
5776
|
writeToStdout("\x1B[2J\x1B[3J\x1B[H");
|
|
5554
5777
|
push({ kind: "notice", text: " cleared \u2014 new session, empty context" });
|
|
@@ -5635,16 +5858,19 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5635
5858
|
return;
|
|
5636
5859
|
case "wallet":
|
|
5637
5860
|
await runBusy(LOADING, async (signal) => {
|
|
5861
|
+
const streakNow = await client.claimStreak(signal);
|
|
5638
5862
|
const w = await client.wallet(signal);
|
|
5639
5863
|
setBalance(w.balance);
|
|
5640
5864
|
const reversals = w.ledger.filter((e) => e.reason === "ad_reversal");
|
|
5641
5865
|
const reversed = reversals.reduce((sum, e) => sum + Math.abs(e.delta), 0);
|
|
5642
5866
|
const screenouts = w.screenouts_today ?? 0;
|
|
5643
5867
|
const screenoutCredits = w.screenout_credits_today ?? 0;
|
|
5868
|
+
const streakLine = streakNow && streakLabel(streakNow);
|
|
5644
5869
|
push({
|
|
5645
5870
|
kind: "notice",
|
|
5646
5871
|
text: ` balance ${w.balance.toLocaleString("en-US")} credits \xB7 ${w.ads_today} offers today \xB7 $${w.earned_usd_today.toFixed(2)}/$${w.max_reward_usd_per_day.toFixed(2)} earned` + (screenouts ? `
|
|
5647
|
-
plus ${screenoutCredits.toLocaleString("en-US")} credits from ${screenouts} screenout bonus${screenouts === 1 ? "" : "es"} \u2014 attempts that didn't qualify, which CPX pays a little for anyway.` : "") + (
|
|
5872
|
+
plus ${screenoutCredits.toLocaleString("en-US")} credits from ${screenouts} screenout bonus${screenouts === 1 ? "" : "es"} \u2014 attempts that didn't qualify, which CPX pays a little for anyway.` : "") + (streakLine ? `
|
|
5873
|
+
${streakLine}` : "") + (reversals.length ? `
|
|
5648
5874
|
recently: ${reversed.toLocaleString("en-US")} credits from ${reversals.length} offer${reversals.length === 1 ? "" : "s"} were reversed by the provider. Run \`clixad wallet\` for the full ledger.` : "")
|
|
5649
5875
|
});
|
|
5650
5876
|
});
|
|
@@ -5752,6 +5978,32 @@ ${NO_SEARCH_INSTRUCTION}`),
|
|
|
5752
5978
|
case "earn":
|
|
5753
5979
|
await runAdWall();
|
|
5754
5980
|
return;
|
|
5981
|
+
// Through `runBusy` like every other gateway call: the free instance can
|
|
5982
|
+
// cold-start for the better part of a minute, and the box says "esc to
|
|
5983
|
+
// stop" whenever it is locked.
|
|
5984
|
+
case "refer":
|
|
5985
|
+
await runBusy(LOADING, async (signal) => {
|
|
5986
|
+
const info = await client.referral(signal);
|
|
5987
|
+
push({ kind: "notice", text: referLines(info).map((line3) => ` ${line3}`).join("\n") });
|
|
5988
|
+
});
|
|
5989
|
+
return;
|
|
5990
|
+
case "redeem": {
|
|
5991
|
+
if (!arg) {
|
|
5992
|
+
push({ kind: "notice", tone: "warn", text: " usage: /redeem <code>" });
|
|
5993
|
+
return;
|
|
5994
|
+
}
|
|
5995
|
+
await runBusy(LOADING, async (signal) => {
|
|
5996
|
+
const result = await client.redeem(arg, signal);
|
|
5997
|
+
const { ok, text: text2 } = redeemMessage(result);
|
|
5998
|
+
if (ok) setBalance(result.balance);
|
|
5999
|
+
push({
|
|
6000
|
+
kind: "notice",
|
|
6001
|
+
tone: ok ? "good" : "warn",
|
|
6002
|
+
text: ok ? ` ${text2} \xB7 balance ${result.balance.toLocaleString("en-US")}` : ` ${text2}`
|
|
6003
|
+
});
|
|
6004
|
+
});
|
|
6005
|
+
return;
|
|
6006
|
+
}
|
|
5755
6007
|
case "login":
|
|
5756
6008
|
await runLogin(arg);
|
|
5757
6009
|
return;
|
|
@@ -5819,14 +6071,58 @@ ${output}` }
|
|
|
5819
6071
|
},
|
|
5820
6072
|
[push, root, runCommand, runShell, runTurn2]
|
|
5821
6073
|
);
|
|
6074
|
+
const askFeedback = useCallback(async () => {
|
|
6075
|
+
const turn = lastTurnRef.current;
|
|
6076
|
+
if (!turn) return;
|
|
6077
|
+
lastTurnRef.current = null;
|
|
6078
|
+
const amount = wallet?.rewards?.feedback_credits;
|
|
6079
|
+
const pay = amount ? `Both answers pay ${amount.toLocaleString("en-US")} credits` : "Both answers pay the same";
|
|
6080
|
+
const choice = await new Promise((resolve2) => {
|
|
6081
|
+
setPickerSel(0);
|
|
6082
|
+
setPicker({
|
|
6083
|
+
title: "Was that last answer any good?",
|
|
6084
|
+
subtitle: `${pay} \xB7 we keep that exchange to test against \xB7 esc skips`,
|
|
6085
|
+
items: [
|
|
6086
|
+
{ value: "up", label: "yes \u2014 that worked", hint: "" },
|
|
6087
|
+
{ value: "down", label: "no \u2014 that missed", hint: "" },
|
|
6088
|
+
{ value: "skip", label: "skip", hint: "nothing is sent, nothing is stored" }
|
|
6089
|
+
],
|
|
6090
|
+
onPick: (item) => resolve2(item.value),
|
|
6091
|
+
// Esc and "skip" are the same outcome and must stay that way: a skip
|
|
6092
|
+
// that cost something is a prompt people stop reading.
|
|
6093
|
+
onCancel: () => resolve2(null)
|
|
6094
|
+
});
|
|
6095
|
+
});
|
|
6096
|
+
if (choice !== "up" && choice !== "down") return;
|
|
6097
|
+
await runBusy(SENDING_FEEDBACK, async (signal) => {
|
|
6098
|
+
const outcome = await client.sendFeedback(
|
|
6099
|
+
{ ...turn, session: sessionRef.current.id, rating: choice },
|
|
6100
|
+
signal
|
|
6101
|
+
);
|
|
6102
|
+
if (!outcome) return;
|
|
6103
|
+
if (outcome.credited > 0) setBalance(outcome.balance);
|
|
6104
|
+
push({
|
|
6105
|
+
kind: "notice",
|
|
6106
|
+
tone: "good",
|
|
6107
|
+
text: outcome.credited > 0 ? ` thanks \u2014 +${outcome.credited.toLocaleString("en-US")} credits` : (
|
|
6108
|
+
// Recorded and unpaid: the daily ceiling, or a turn already rated.
|
|
6109
|
+
// Worth saying, because the offer of credits was made a moment ago.
|
|
6110
|
+
" thanks \u2014 noted (today's feedback credits are already in)"
|
|
6111
|
+
)
|
|
6112
|
+
});
|
|
6113
|
+
});
|
|
6114
|
+
}, [client, push, runBusy, wallet]);
|
|
5822
6115
|
const runSerially = useCallback(
|
|
5823
6116
|
async (line2) => {
|
|
5824
6117
|
await dispatchLine(line2);
|
|
5825
6118
|
for (let next = dequeue(); next !== void 0; next = dequeue()) {
|
|
5826
6119
|
await dispatchLine(next);
|
|
5827
6120
|
}
|
|
6121
|
+
if (lastTurnRef.current && turnCountRef.current % FEEDBACK_TURN_INTERVAL === 0) {
|
|
6122
|
+
await askFeedback();
|
|
6123
|
+
}
|
|
5828
6124
|
},
|
|
5829
|
-
[dequeue, dispatchLine]
|
|
6125
|
+
[askFeedback, dequeue, dispatchLine]
|
|
5830
6126
|
);
|
|
5831
6127
|
const submit = useCallback(
|
|
5832
6128
|
async (raw) => {
|
|
@@ -6326,7 +6622,7 @@ function tailLines(text, max) {
|
|
|
6326
6622
|
const lines = text.split("\n");
|
|
6327
6623
|
return lines.length <= max ? text : lines.slice(-max).join("\n");
|
|
6328
6624
|
}
|
|
6329
|
-
var WORKING, WAITING_FOR_REWARD, LOADING, COMPACTING, SIGNING_IN, RUNNING, NOT_SIGNED_IN, LIVE_OUTPUT_LINES, COMMITTED_OUTPUT_LINES, DELTA_FLUSH_MS, MAX_INPUT_ROWS, MAX_PICKER_ROWS, MAX_QUEUE_ROWS, QUEUE_MARKER, PICKER_CURRENT, MIN_PICKER_LABEL_COLS, PICKER_CHROME, PICKER_CHROME_COMPACT, ASK_CHROME, PLAN_CHROME, ASK_HINT, PLAN_HINT, EXPAND_MAX_LINES, FALLBACK_ROWS, FALLBACK_COLS, CONTEXT_NOTICE_AT, KEEP_ALWAYS;
|
|
6625
|
+
var WORKING, WAITING_FOR_REWARD, LOADING, COMPACTING, SIGNING_IN, RUNNING, SENDING_FEEDBACK, FEEDBACK_TURN_INTERVAL, NOT_SIGNED_IN, LIVE_OUTPUT_LINES, COMMITTED_OUTPUT_LINES, DELTA_FLUSH_MS, MAX_INPUT_ROWS, MAX_PICKER_ROWS, MAX_QUEUE_ROWS, QUEUE_MARKER, PICKER_CURRENT, MIN_PICKER_LABEL_COLS, PICKER_CHROME, PICKER_CHROME_COMPACT, ASK_CHROME, PLAN_CHROME, ASK_HINT, PLAN_HINT, EXPAND_MAX_LINES, FALLBACK_ROWS, FALLBACK_COLS, CONTEXT_NOTICE_AT, KEEP_ALWAYS;
|
|
6330
6626
|
var init_app = __esm({
|
|
6331
6627
|
"src/tui/app.tsx"() {
|
|
6332
6628
|
"use strict";
|
|
@@ -6363,6 +6659,8 @@ var init_app = __esm({
|
|
|
6363
6659
|
COMPACTING = "compacting\u2026";
|
|
6364
6660
|
SIGNING_IN = "signing in\u2026";
|
|
6365
6661
|
RUNNING = "running\u2026";
|
|
6662
|
+
SENDING_FEEDBACK = "sending\u2026";
|
|
6663
|
+
FEEDBACK_TURN_INTERVAL = 50;
|
|
6366
6664
|
NOT_SIGNED_IN = " Not signed in, or the stored token isn't valid for this gateway.\n Run /login to sign in.";
|
|
6367
6665
|
LIVE_OUTPUT_LINES = 5;
|
|
6368
6666
|
COMMITTED_OUTPUT_LINES = 4;
|
|
@@ -6434,6 +6732,7 @@ init_kimi();
|
|
|
6434
6732
|
init_banner();
|
|
6435
6733
|
init_browser();
|
|
6436
6734
|
init_color();
|
|
6735
|
+
import { createInterface } from "node:readline/promises";
|
|
6437
6736
|
|
|
6438
6737
|
// src/title.ts
|
|
6439
6738
|
var APP_TITLE = "Clixad";
|
|
@@ -6485,6 +6784,10 @@ async function main() {
|
|
|
6485
6784
|
return showWallet(client);
|
|
6486
6785
|
case "earn":
|
|
6487
6786
|
return earn(client, config);
|
|
6787
|
+
case "refer":
|
|
6788
|
+
return refer(client, config);
|
|
6789
|
+
case "redeem":
|
|
6790
|
+
return redeem(client, config, rest[0]);
|
|
6488
6791
|
case "buy":
|
|
6489
6792
|
return buyCmd(client, config, rest[0]);
|
|
6490
6793
|
case "ask":
|
|
@@ -6526,6 +6829,7 @@ async function main() {
|
|
|
6526
6829
|
}
|
|
6527
6830
|
async function login(client, config, args) {
|
|
6528
6831
|
const parsed = parseLoginArgs(args);
|
|
6832
|
+
for (const line2 of TERMS_NOTICE.split("\n")) console.log(c.dim(` ${line2}`));
|
|
6529
6833
|
const outcome = await performLogin(client, config, parsed, {
|
|
6530
6834
|
verify(start) {
|
|
6531
6835
|
console.log(`
|
|
@@ -6546,6 +6850,7 @@ async function login(client, config, args) {
|
|
|
6546
6850
|
\u2713 logged in as ${who}`) + suffix);
|
|
6547
6851
|
const bonus = account.created ? c.dim(account.signupBonus === void 0 ? " (signup bonus)" : ` (signup bonus ${credits(account.signupBonus)})`) : "";
|
|
6548
6852
|
console.log(` balance: ${c.bold(credits(account.balance))} credits${bonus}`);
|
|
6853
|
+
await showStreak(client);
|
|
6549
6854
|
console.log(c.dim(` token stored in ${configPath()}`));
|
|
6550
6855
|
return;
|
|
6551
6856
|
}
|
|
@@ -6630,9 +6935,12 @@ async function setModel(client, config, model) {
|
|
|
6630
6935
|
console.log(c.green(`\u2713 model set to ${model}`));
|
|
6631
6936
|
}
|
|
6632
6937
|
async function showWallet(client) {
|
|
6938
|
+
const streak = await client.claimStreak();
|
|
6633
6939
|
const w = await client.wallet();
|
|
6634
6940
|
console.log(`balance: ${c.bold(w.balance.toLocaleString("en-US"))} credits`);
|
|
6635
6941
|
console.log(`${earnedToday(w)} \xB7 offers pay ${grantRangeLabel(w.grant_per_ad_range)}`);
|
|
6942
|
+
const line2 = streak && streakLabel(streak);
|
|
6943
|
+
if (line2) console.log(streak.claimed ? c.green(line2) : c.dim(line2));
|
|
6636
6944
|
if (w.ledger.length) {
|
|
6637
6945
|
console.log(c.dim("recent:"));
|
|
6638
6946
|
const label = (e) => ledgerReasonLabel(e.reason);
|
|
@@ -6679,6 +6987,40 @@ async function earn(client, config) {
|
|
|
6679
6987
|
);
|
|
6680
6988
|
console.log(c.yellow(" pays nothing and is normal. Start another, or check `clixad wallet` later."));
|
|
6681
6989
|
}
|
|
6990
|
+
async function refer(client, config) {
|
|
6991
|
+
if (!config.token) return console.log(c.yellow("Not logged in. Run `clixad login` first."));
|
|
6992
|
+
let info;
|
|
6993
|
+
try {
|
|
6994
|
+
info = await client.referral();
|
|
6995
|
+
} catch (err) {
|
|
6996
|
+
console.error(c.red(err.message));
|
|
6997
|
+
process.exitCode = 1;
|
|
6998
|
+
return;
|
|
6999
|
+
}
|
|
7000
|
+
const [code, share, deal, ...rest] = referLines(info);
|
|
7001
|
+
console.log(`
|
|
7002
|
+
${c.bold(code)}`);
|
|
7003
|
+
console.log(` ${c.cyan(share)}`);
|
|
7004
|
+
console.log(c.dim(` ${deal}`));
|
|
7005
|
+
for (const line2 of rest) console.log(c.dim(` ${line2}`));
|
|
7006
|
+
console.log("");
|
|
7007
|
+
}
|
|
7008
|
+
async function redeem(client, config, code) {
|
|
7009
|
+
if (!config.token) return console.log(c.yellow("Not logged in. Run `clixad login` first."));
|
|
7010
|
+
if (!code) return console.error(c.red("usage: clixad redeem <code>"));
|
|
7011
|
+
let result;
|
|
7012
|
+
try {
|
|
7013
|
+
result = await client.redeem(code);
|
|
7014
|
+
} catch (err) {
|
|
7015
|
+
console.error(c.red(err.message));
|
|
7016
|
+
process.exitCode = 1;
|
|
7017
|
+
return;
|
|
7018
|
+
}
|
|
7019
|
+
const { ok, text } = redeemMessage(result);
|
|
7020
|
+
if (!ok) return console.log(c.yellow(` ${text}`));
|
|
7021
|
+
console.log(c.green(`
|
|
7022
|
+
\u2713 ${text}`) + ` \xB7 balance ${c.bold(credits(result.balance))}`);
|
|
7023
|
+
}
|
|
6682
7024
|
async function buyCmd(client, config, pack) {
|
|
6683
7025
|
if (!config.token) return console.log(c.yellow("Not logged in. Run `clixad login` first."));
|
|
6684
7026
|
const { enabled, packs } = await client.creditPacks();
|
|
@@ -6693,9 +7035,20 @@ async function buyCmd(client, config, pack) {
|
|
|
6693
7035
|
console.log(c.dim("\n buy one with: clixad buy <id>"));
|
|
6694
7036
|
return;
|
|
6695
7037
|
}
|
|
7038
|
+
const chosen = packs.find((x) => x.id === pack);
|
|
7039
|
+
if (!chosen) {
|
|
7040
|
+
console.log(c.red(` unknown pack: ${pack}`));
|
|
7041
|
+
return console.log(c.dim(` try one of: ${packs.map((x) => x.id).join(", ")}`));
|
|
7042
|
+
}
|
|
7043
|
+
const consented = await confirmWithdrawalWaiver(chosen);
|
|
7044
|
+
if (!consented) return;
|
|
6696
7045
|
let checkout;
|
|
6697
7046
|
try {
|
|
6698
|
-
checkout = await client.checkout(pack
|
|
7047
|
+
checkout = await client.checkout(pack, {
|
|
7048
|
+
requestImmediatePerformance: true,
|
|
7049
|
+
acknowledgeLossOfWithdrawal: true,
|
|
7050
|
+
at: (/* @__PURE__ */ new Date()).toISOString()
|
|
7051
|
+
});
|
|
6699
7052
|
} catch (err) {
|
|
6700
7053
|
return console.log(c.red(err.message));
|
|
6701
7054
|
}
|
|
@@ -6718,6 +7071,39 @@ async function buyCmd(client, config, pack) {
|
|
|
6718
7071
|
}
|
|
6719
7072
|
console.log(c.yellow("\n No credits yet \u2014 the webhook may be delayed. Check `clixad wallet`."));
|
|
6720
7073
|
}
|
|
7074
|
+
async function confirmWithdrawalWaiver(pack) {
|
|
7075
|
+
if (!process.stdin.isTTY) {
|
|
7076
|
+
console.log(c.yellow("\n Buying credits needs an interactive terminal."));
|
|
7077
|
+
console.log(
|
|
7078
|
+
c.dim(" We have to record your confirmation before charging you, and a pipe has nobody to ask.")
|
|
7079
|
+
);
|
|
7080
|
+
return false;
|
|
7081
|
+
}
|
|
7082
|
+
const price = "$" + pack.price_usd.toFixed(2);
|
|
7083
|
+
console.log(`
|
|
7084
|
+
${c.bold(pack.label)} \u2014 ${credits(pack.credits)} credits for ${c.bold(price)}`);
|
|
7085
|
+
console.log(c.dim(` One purchase. No subscription, nothing to cancel. Terms: ${TERMS_URL}`));
|
|
7086
|
+
console.log("\n Before you pay, please confirm:");
|
|
7087
|
+
console.log(" \xB7 You request that we deliver the credits immediately, before the");
|
|
7088
|
+
console.log(" 14-day withdrawal period has expired.");
|
|
7089
|
+
console.log(" \xB7 You acknowledge that your right of withdrawal lapses once they");
|
|
7090
|
+
console.log(" have been delivered.");
|
|
7091
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
7092
|
+
let answer = "";
|
|
7093
|
+
try {
|
|
7094
|
+
answer = (await rl.question(`
|
|
7095
|
+
Type ${c.bold("yes")} to continue: `)).trim().toLowerCase();
|
|
7096
|
+
} catch {
|
|
7097
|
+
answer = "";
|
|
7098
|
+
} finally {
|
|
7099
|
+
rl.close();
|
|
7100
|
+
}
|
|
7101
|
+
if (answer !== "yes" && answer !== "y") {
|
|
7102
|
+
console.log(c.dim("\n Cancelled. You have not been charged."));
|
|
7103
|
+
return false;
|
|
7104
|
+
}
|
|
7105
|
+
return true;
|
|
7106
|
+
}
|
|
6721
7107
|
async function ask(client, config, prompt) {
|
|
6722
7108
|
if (!prompt) return console.error(c.red('usage: clixad ask "your prompt"'));
|
|
6723
7109
|
const messages = [{ role: "user", content: prompt }];
|
|
@@ -6845,10 +7231,11 @@ async function repl(client, config, opts = {}) {
|
|
|
6845
7231
|
session = loadSession(opts.resume);
|
|
6846
7232
|
if (!session) return console.log(c.red(`no such session: ${opts.resume}`));
|
|
6847
7233
|
}
|
|
7234
|
+
const streak = await client.claimStreak();
|
|
6848
7235
|
const wallet = await safeWallet(client);
|
|
6849
7236
|
clearScreen();
|
|
6850
7237
|
const { startTui: startTui2 } = await Promise.resolve().then(() => (init_tui(), tui_exports));
|
|
6851
|
-
await startTui2({ client, config, wallet, session, initialTask: opts.task });
|
|
7238
|
+
await startTui2({ client, config, wallet, streak, session, initialTask: opts.task });
|
|
6852
7239
|
}
|
|
6853
7240
|
async function runTurn(client, config, messages) {
|
|
6854
7241
|
try {
|
|
@@ -6875,6 +7262,13 @@ async function runTurn(client, config, messages) {
|
|
|
6875
7262
|
return void 0;
|
|
6876
7263
|
}
|
|
6877
7264
|
}
|
|
7265
|
+
async function showStreak(client) {
|
|
7266
|
+
const streak = await client.claimStreak();
|
|
7267
|
+
const line2 = streak && streakLabel(streak);
|
|
7268
|
+
if (!line2) return streak;
|
|
7269
|
+
console.log(streak.claimed ? c.green(` ${line2}`) : c.dim(` ${line2}`));
|
|
7270
|
+
return streak;
|
|
7271
|
+
}
|
|
6878
7272
|
async function safeWallet(client) {
|
|
6879
7273
|
try {
|
|
6880
7274
|
return await client.wallet();
|
|
@@ -6892,6 +7286,8 @@ function printHelp() {
|
|
|
6892
7286
|
${c.cyan("model")} <id> set default model
|
|
6893
7287
|
${c.cyan("wallet")} balance, ads today, ledger
|
|
6894
7288
|
${c.cyan("earn")} open the ad wall to earn credits
|
|
7289
|
+
${c.cyan("refer")} your invite code, and what it has earned
|
|
7290
|
+
${c.cyan("redeem")} <code> spend a code you were given
|
|
6895
7291
|
${c.cyan("buy")} [pack] list credit packs / buy one via Stripe
|
|
6896
7292
|
${c.cyan("ask")} "<prompt>" one-shot completion (no tools)
|
|
6897
7293
|
${c.cyan("agent")} "<task>" open the REPL with a task already running
|
|
@@ -6901,7 +7297,9 @@ function printHelp() {
|
|
|
6901
7297
|
${c.cyan("--resume")} [id] list saved sessions, or resume one
|
|
6902
7298
|
${c.cyan("--version")} print the version and exit
|
|
6903
7299
|
(no command) interactive coding REPL ${c.dim("(signs you in if needed)")}
|
|
6904
|
-
"<task>" same as ${c.cyan("agent")} \u2014 anything that is not a command is a task
|
|
7300
|
+
"<task>" same as ${c.cyan("agent")} \u2014 anything that is not a command is a task
|
|
7301
|
+
|
|
7302
|
+
${c.dim("Discord: https://discord.gg/pwxPM3QyF \u2014 bugs and feature requests are read there")}`);
|
|
6905
7303
|
}
|
|
6906
7304
|
main().catch((err) => {
|
|
6907
7305
|
console.error(c.red(err.message));
|