@ucsandman/legcli 0.13.1 → 0.14.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/CHANGELOG.md +16 -0
- package/LICENSE +2 -0
- package/README.md +12 -10
- package/bin/leg.mjs +4 -3
- package/docs/DECISIONS.md +2 -1
- package/docs/ERRORS.md +22 -0
- package/package.json +2 -2
- package/src/board/board.js +1 -1
- package/src/board/floor.js +1 -1
- package/src/license.mjs +38 -13
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.0 (2026-09-18)
|
|
4
|
+
|
|
5
|
+
The 14-day trial is back.
|
|
6
|
+
|
|
7
|
+
- **Free for 14 days, no key, no card.** The first `leg <agent>` on a machine
|
|
8
|
+
starts a 14-day trial with every gate open, Team's `leg share` included, and
|
|
9
|
+
records it in `$LEG_HOME/trial.json`. `leg license status` shows the days
|
|
10
|
+
left without starting the clock. After the trial, or with a refused key once
|
|
11
|
+
it is over, a session exits 4 with the price and the 30-day guarantee, which
|
|
12
|
+
stays as the second net after buying. Deleting the file resets the clock,
|
|
13
|
+
which is the known limit of an offline trial and is accepted. The license
|
|
14
|
+
agreement gains the trial clause; the site, README, support page and
|
|
15
|
+
`llms.txt` say "free for 14 days" where they said "no trial".
|
|
16
|
+
- **The README stops calling the repository private.** It has been public at
|
|
17
|
+
github.com/ucsandman/legcli; two sentences still said otherwise.
|
|
18
|
+
|
|
3
19
|
## 0.13.1 (2026-09-18)
|
|
4
20
|
|
|
5
21
|
A one-line fix in the terminal and the marketing site brought up to what 0.13.0 does.
|
package/LICENSE
CHANGED
|
@@ -9,6 +9,8 @@ install or use Leg.
|
|
|
9
9
|
|
|
10
10
|
1. License. When you buy a license we grant you a non-exclusive,
|
|
11
11
|
non-transferable license to install and use Leg as follows.
|
|
12
|
+
Trial: before buying, one person may install and use Leg to evaluate it
|
|
13
|
+
for 14 days from its first use on a machine, on the Team terms below.
|
|
12
14
|
Personal: one named person, on any number of machines that person uses.
|
|
13
15
|
Team: one named person per seat purchased. A Team license also covers use
|
|
14
16
|
of the shared board (leg share) by the people holding seats. A Personal
|
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ files and regions). `leg claude --model opus` is
|
|
|
23
23
|
1. **A board.** Opened once in your browser, reused after that. Every Leg
|
|
24
24
|
session in every terminal is a row on it, and the row's register reads
|
|
25
25
|
status, repo on branch, uncommitted and unpushed counts, agent and model,
|
|
26
|
-
and how long it has been quiet: `waiting on you
|
|
26
|
+
and how long it has been quiet: `waiting on you leg on main dirty 3
|
|
27
27
|
ahead 2 claude/fable`. Under that come the prompt, the one thing worth
|
|
28
28
|
knowing, the files it is touching and the usage bucket that will stop it.
|
|
29
29
|
Capacity is one strip at the top rather than a region, with the login
|
|
@@ -138,8 +138,8 @@ agent name passes straight through (`leg codex -m gpt-5.3-codex-spark`,
|
|
|
138
138
|
through unchanged, and your settings file is never edited: Leg's hooks ride
|
|
139
139
|
in a separate per-session `--settings` file.
|
|
140
140
|
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
Leg is commercial, source-available software, and the source is on
|
|
142
|
+
[GitHub](https://github.com/ucsandman/legcli): every `.mjs` file that runs is in the package you just installed, at
|
|
143
143
|
`$(npm root -g)/legcli/src`, and the license lets you read it and modify
|
|
144
144
|
your own copy. There is nothing compiled, minified or bundled to see through.
|
|
145
145
|
|
|
@@ -697,8 +697,8 @@ drift. CI waits for the Ubuntu and Windows test matrix, validates both
|
|
|
697
697
|
when that version is missing and newer than the stable `latest`. Existing
|
|
698
698
|
versions skip cleanly; older, prerelease, lockstep, and registry-error cases
|
|
699
699
|
fail the job. The npm trusted publisher is bound to `ucsandman/legcli` and
|
|
700
|
-
`.github/workflows/ci.yml` (bind `leg-agents` the same way).
|
|
701
|
-
|
|
700
|
+
`.github/workflows/ci.yml` (bind `leg-agents` the same way). Publication
|
|
701
|
+
uses `--provenance=false`.
|
|
702
702
|
|
|
703
703
|
## Privacy and attribution
|
|
704
704
|
|
|
@@ -714,17 +714,19 @@ Leg is commercial software under the [Leg License Agreement](LICENSE).
|
|
|
714
714
|
It ships as readable JavaScript so you can see what it does on your machine,
|
|
715
715
|
and you may modify it for your own use, but not redistribute it or work
|
|
716
716
|
around the license check. Versions 0.2.0 and 0.3.0 were published under MIT
|
|
717
|
-
and remain available. The version in this source tree is 0.
|
|
717
|
+
and remain available. The version in this source tree is 0.14.0; see
|
|
718
718
|
[npm](https://www.npmjs.com/package/legcli) for published versions and
|
|
719
719
|
[CHANGELOG.md](CHANGELOG.md) for release notes.
|
|
720
720
|
|
|
721
|
-
|
|
721
|
+
The first 14 days are a **free trial**: install it, type `leg claude`, and
|
|
722
|
+
everything in both plans is on, with no key and no card. The clock starts on
|
|
723
|
+
your first session and `leg license status` shows what is left. After that
|
|
724
|
+
it needs a license: **Personal, $79 once**, one human on any number of
|
|
722
725
|
machines, every release for 12 months and the version you have keeps working
|
|
723
726
|
after that; **Team, $12 per seat per month**, Personal plus `leg share` for
|
|
724
727
|
more than one human on the board. Buy at the site, then
|
|
725
|
-
`leg license activate <key>`. There is
|
|
726
|
-
|
|
727
|
-
and ask for a refund if it does not earn its place. A key is a signed token
|
|
728
|
+
`leg license activate <key>`. There is also a **30-day money-back
|
|
729
|
+
guarantee** after buying, no reason required. A key is a signed token
|
|
728
730
|
checked offline with the public key in `src/license.mjs`; only a Team key
|
|
729
731
|
renewal talks to the site. The bare agent CLIs are never affected by any of
|
|
730
732
|
this; only what Leg adds is licensed.
|
package/bin/leg.mjs
CHANGED
|
@@ -493,10 +493,11 @@ async function main() {
|
|
|
493
493
|
// The paid gate. Keys verify offline against the public key in
|
|
494
494
|
// src/license.mjs; nothing here talks to the network except refresh.
|
|
495
495
|
if (!cmd || cmd === 'status') {
|
|
496
|
-
|
|
496
|
+
// looking does not start the trial clock; the first session does
|
|
497
|
+
const ent = entitlement({ startTrial: false })
|
|
497
498
|
out(describeLicense(ent))
|
|
498
499
|
if (ent.source === 'license') out(`stored at ${licensePath()}`)
|
|
499
|
-
if (!ent.ok) out(`Buy: ${BUY_URL} then: leg license activate <key>`)
|
|
500
|
+
if (!ent.ok || ent.source === 'trial') out(`Buy: ${BUY_URL} then: leg license activate <key>`)
|
|
500
501
|
return
|
|
501
502
|
}
|
|
502
503
|
if (cmd === 'activate') {
|
|
@@ -508,7 +509,7 @@ async function main() {
|
|
|
508
509
|
} catch (err) { die(2, err.message) }
|
|
509
510
|
return
|
|
510
511
|
}
|
|
511
|
-
if (cmd === 'deactivate') return out(deactivateLicense() ? `removed ${licensePath()}; Leg
|
|
512
|
+
if (cmd === 'deactivate') return out(deactivateLicense() ? `removed ${licensePath()}; Leg is back on the trial if it has days left, otherwise it needs a key` : 'no license was stored')
|
|
512
513
|
if (cmd === 'refresh') {
|
|
513
514
|
try { const p = await refreshLicense(); out(`renewed ${p.plan} license ${p.id}, valid through ${p.expires}`) } catch (err) { die(2, err.message) }
|
|
514
515
|
return
|
package/docs/DECISIONS.md
CHANGED
|
@@ -100,7 +100,7 @@ The repository stays private (see the 2026-09-11 license decision). That left ev
|
|
|
100
100
|
## 2026-09-11: Leg is a commercial product; the site stays on the free Vercel address
|
|
101
101
|
|
|
102
102
|
- **License.** Wes: "if we're trying to sell this thing it shouldn't be open source and MIT." The repo stays private and the package ships under the Leg License Agreement (LICENSE): commercial, source readable in the package for inspection and own-use modification, no redistribution, no working around the license check. The FSL option from the pricing research was dropped for the same reason. Versions 0.2.0 and 0.3.0 remain available under MIT.
|
|
103
|
-
- **Pricing.** Personal $79 once with 12 months of releases (Sublime shape); Team $12 per seat per month (adds `leg share`); no trial, and a 30-day money-back guarantee as the risk reversal instead (2026-09-15: a trial suits daily-habit products, and Leg's value is bursty, it pays off in the moment a limit lands, which a fortnight of evaluation does not reliably contain; a buyer already paying for two or three agent subscriptions is not price-sensitive at $79, they are trust-sensitive, and a trial does not answer trust). Keys are Ed25519 tokens signed with a private key that lives only in the seller's `.env` and the site's Vercel env; the public key is in `src/license.mjs`. A Personal key is a window over `RELEASE_DATE`, so every release bumps that constant.
|
|
103
|
+
- **Pricing.** Personal $79 once with 12 months of releases (Sublime shape); Team $12 per seat per month (adds `leg share`); ~~no trial, and a 30-day money-back guarantee as the risk reversal instead~~ superseded 2026-09-18 (2026-09-15: a trial suits daily-habit products, and Leg's value is bursty, it pays off in the moment a limit lands, which a fortnight of evaluation does not reliably contain; a buyer already paying for two or three agent subscriptions is not price-sensitive at $79, they are trust-sensitive, and a trial does not answer trust). 2026-09-18, Wes, ahead of the Hacker News post: the 14-day trial from 0.4.0 is back, started by the first session and recorded in `$LEG_HOME/trial.json` with every gate open, and the 30-day guarantee stays as the second net after buying. A launch audience installs to look, and "buy first" on a repo they found ten minutes ago is a wall the guarantee does not lower. Keys are Ed25519 tokens signed with a private key that lives only in the seller's `.env` and the site's Vercel env; the public key is in `src/license.mjs`. A Personal key is a window over `RELEASE_DATE`, so every release bumps that constant.
|
|
104
104
|
- **Checkout.** Stripe payment links (live) with automatic tax, `site/api/key` and `site/api/webhook` on Vercel functions, Resend from `legcli@practicalsystems.io`. `scripts/stripe-setup.mjs` is idempotent per site origin; a test-mode purchase was run end to end on 2026-09-11 (checkout, thanks page, key activated in the CLI, webhook 200 twice).
|
|
105
105
|
- **Domain.** A `legcli.com` purchase ($11.25) was started and cancelled at Wes's "just deploy it to a free vercel site"; nothing was bought. The site is https://legcli.com/.
|
|
106
106
|
- **Not a lawyer.** The license text was drafted in-session; a review before the first sale outside the US is Wes's call.
|
|
@@ -116,6 +116,7 @@ The repository stays private (see the 2026-09-11 license decision). That left ev
|
|
|
116
116
|
- **Root Directory had to change first.** It was `.`, which is correct when deploying from inside `site/` but would have published the repo root on a git build: no `index.html`, and `api/key` and `api/webhook` gone, so a purchase in flight would not have received its key.
|
|
117
117
|
- ~~**Not every push.** `site/vercel.json` carries `ignoreCommand: git diff --quiet HEAD^ HEAD .`, which Vercel maps to the Ignored Build Step. A commit that touches nothing under `site/` cancels the build. The command failing (a shallow clone with no `HEAD^`) exits non-zero, which builds, so the failure mode is a redundant deploy rather than a missed one.~~ **Superseded 2026-09-15: the failure mode was a MISSED deploy.** `HEAD^ HEAD` compares one commit, not the push. Fast-forwarding five commits to main where only the first touched `site/` left the tip with no `site/` change, so Vercel cancelled the build and the redesigned site never went out while the repo said it had. The `ignoreCommand` is removed: every push to `main` deploys the site. Vercel's own "Skip deployments when there are no changes to the root directory" toggle is the safe version of this idea, because it compares against the last deployment rather than against `HEAD^`.
|
|
118
118
|
- **Agent sessions cannot do this part.** `vercel --prod` and `vercel git connect` are both denied by the harness classifier as production deploys, and the CLI auth token cannot be read. Wes ran both.
|
|
119
|
+
- **2026-09-18: the git deploy did not fire, and the manual fallback has two traps.** The push of 0.13.1 (`19667ba`) produced no `vercel[bot]` deployment on GitHub (commit status stayed `pending`, no statuses), while the pushes an hour earlier had; the account had an overdue invoice at the time, which is the likeliest cause, and the git integration should be re-checked in the Vercel dashboard after a rename (the GitHub repository was renamed since the connection was made; `git remote -v` has the current name and the project still says `baton`). The fallback that works, run from the **repo root**, not `site/`: a root `.vercel/project.json` copied from `site/.vercel/` (gitignored), a root `.vercelignore` of `*`, `!site`, `!site/**`, `site/.vercel`, `site/.env*`, then `vercel --prod --yes --archive=tgz`. Trap one: `vercel --prod --yes` from inside `site/` uploads `site/` as the root, Vercel applies Root Directory `site` on top of it, and the deployment errors with an empty message. Trap two: that same `--yes` auto-linked the repo root to a **new** Vercel project named `leg` (empty, no domain); it is junk and `vercel project rm leg` removes it. Trap three: with no `.vercelignore` the CLI uploads the untracked probe debris too and refuses at 15,000 files.
|
|
119
120
|
|
|
120
121
|
|
|
121
122
|
## 2026-09-15: the board is a dark product surface, and DESIGN.md was the defect
|
package/docs/ERRORS.md
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
What broke, why, and what fixed it. One entry per failure, newest first. A first
|
|
4
4
|
occurrence has to be written down or a repeat is never countable.
|
|
5
5
|
|
|
6
|
+
## 2026-09-18: 0.14.0 pushed, CI green everywhere, and npm still served 0.13.1
|
|
7
|
+
|
|
8
|
+
**Fixed by putting the `repository` block back into the lockfile root
|
|
9
|
+
(`packages[""]`) and pushing again.** `scripts/npm-publish-gate.mjs` requires
|
|
10
|
+
`package-lock.json`'s root `repository.url` to equal the trusted-publisher
|
|
11
|
+
binding, and npm 10.9 (the local install) does not write that block; npm 11
|
|
12
|
+
does, which is where the 0.13.1 lockfile got it. `npm version 0.14.0` on npm
|
|
13
|
+
10 rewrote the lockfile without it, the test matrix and the site deploy passed,
|
|
14
|
+
and only the `publish-npm` job failed, on the gate, before publishing anything.
|
|
15
|
+
The lesson: a lockfile touched by a different npm major is a release change,
|
|
16
|
+
and "CI green" after a push means the run, not the publish; `npm view
|
|
17
|
+
@ucsandman/legcli version` is the check.
|
|
18
|
+
|
|
19
|
+
## 2026-09-18: `vercel --prod --yes` from `site/` failed the deploy and created a stray Vercel project
|
|
20
|
+
|
|
21
|
+
**Fixed by deploying from the repo root with a root `.vercelignore` and
|
|
22
|
+
`--archive=tgz`; the recipe is in DECISIONS.md under "the site deploys itself
|
|
23
|
+
from git".** The `--yes` also auto-created an empty Vercel project named `leg`
|
|
24
|
+
that still needs `vercel project rm leg`. The lesson: on a project with a Root
|
|
25
|
+
Directory set, the CLI must run from the repo root, and `--yes` is a consent to
|
|
26
|
+
create projects, not only to skip a confirmation.
|
|
27
|
+
|
|
6
28
|
## 2026-09-18: the terminal's opening `next:` line named the same agent twice and no model
|
|
7
29
|
|
|
8
30
|
**Fixed in `src/attach.mjs`: the line maps the chain through `rungLabel`, so it
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ucsandman/legcli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Usage-limit monitor and automatic handoff for Claude Code, Codex, agy and Grok. Type leg claude|codex|agy|grok and get the same interactive agent with a board alongside, auto-approve on by default, usage tracking per agent and account, a live context handoff bundle, and at the limit the next agent continuing in the same terminal. $79 once, 30-day money-back guarantee.",
|
|
3
|
+
"version": "0.14.0",
|
|
4
|
+
"description": "Usage-limit monitor and automatic handoff for Claude Code, Codex, agy and Grok. Type leg claude|codex|agy|grok and get the same interactive agent with a board alongside, auto-approve on by default, usage tracking per agent and account, a live context handoff bundle, and at the limit the next agent continuing in the same terminal. 14-day free trial, then $79 once, 30-day money-back guarantee.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"homepage": "https://legcli.com",
|
package/src/board/board.js
CHANGED
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
// release lands on disk under a board that was started before it: the page
|
|
69
69
|
// then draws controls the process has no routes for (an empty agent select,
|
|
70
70
|
// no buckets). test/files-version.test.mjs pins this to package.json.
|
|
71
|
-
const FILES_VERSION = '0.
|
|
71
|
+
const FILES_VERSION = '0.14.0'
|
|
72
72
|
const TIMELINE_CAP = 12
|
|
73
73
|
// mirrors LOOPBACK in src/auth.mjs; state.bind is "<host>:<port>" and an IPv6
|
|
74
74
|
// host arrives bracketed
|
package/src/board/floor.js
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
// name. test/board-updates.test.mjs pins `state.stopped` in this file by
|
|
51
51
|
// source text, which is why the module object keeps the name.
|
|
52
52
|
// pinned to package.json by test/files-version.test.mjs; see board.js FILES_VERSION
|
|
53
|
-
const FILES_VERSION = '0.
|
|
53
|
+
const FILES_VERSION = '0.14.0'
|
|
54
54
|
const state = { es: null, retryMs: 1000, timers: [], stopped: false, sseRequest: 0, floorRequest: 0, trunkRequest: 0, headRequest: 0, cardsRequest: 0, lastReadingAt: null, bind: (typeof location !== 'undefined' && location.host) || '127.0.0.1:4747', pendingFloor: null, pendingCards: null, cards: new Map(), blockers: new Map(), doneOpen: false, ringId: null, scheduler: {}, trunkOff: false }
|
|
55
55
|
|
|
56
56
|
// What the entry row reads on every render: the terminals (for the repo it
|
package/src/license.mjs
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
// team per-seat subscription; the key carries an expiry a few days past
|
|
9
9
|
// the billing period, `leg license refresh` fetches a renewed one
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
11
|
+
// Before any key there is a 14-day trial, started the first time a session
|
|
12
|
+
// asks and recorded under $LEG_HOME/trial.json with every gate open. Deleting
|
|
13
|
+
// that file resets the clock; that is a known and accepted limit of an offline
|
|
14
|
+
// trial. Past the trial, or with a refused key, Leg needs a license; the risk
|
|
15
|
+
// reversal after buying is a 30-day money-back guarantee. Key shape: LEG-<base64url payload>.<base64url signature> (legacy
|
|
15
16
|
// BATON- prefix still accepted) where the signature is Ed25519 over the payload
|
|
16
17
|
// bytes exactly as encoded.
|
|
17
18
|
import { createPublicKey, createPrivateKey, verify as cryptoVerify, sign as cryptoSign, createHash } from 'node:crypto'
|
|
@@ -29,11 +30,13 @@ export const PUBLIC_KEY_B64 = 'MCowBQYDK2VwAyEAIpVQymHHJAkIrZHv0u4o0bgfFmtW3Crm7
|
|
|
29
30
|
const ACTIVE_PUBLIC_KEY = process.env.LEG_PUBLIC_KEY_B64 || process.env.BATON_PUBLIC_KEY_B64 || PUBLIC_KEY_B64
|
|
30
31
|
// The date this release was cut. A personal key activates when this is on or
|
|
31
32
|
// before its updates_until. Bumped with every published version.
|
|
32
|
-
export const RELEASE_DATE = '2026-09-
|
|
33
|
+
export const RELEASE_DATE = '2026-09-18'
|
|
34
|
+
export const TRIAL_DAYS = 14
|
|
33
35
|
export const GUARANTEE_DAYS = 30
|
|
34
36
|
export const SITE = process.env.LEG_SITE || process.env.BATON_SITE || 'https://legcli.com'
|
|
35
37
|
export const BUY_URL = `${SITE}/#pricing`
|
|
36
38
|
export const PLANS = {
|
|
39
|
+
trial: { label: 'Trial', gates: ['run', 'share'] },
|
|
37
40
|
personal: { label: 'Personal', gates: ['run'] },
|
|
38
41
|
team: { label: 'Team', gates: ['run', 'share'] },
|
|
39
42
|
}
|
|
@@ -86,6 +89,7 @@ export function verifyLicense(key, { publicKeyB64 = ACTIVE_PUBLIC_KEY, today = i
|
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
export function licensePath() { return join(home(), 'license.json') }
|
|
92
|
+
export function trialPath() { return join(home(), 'trial.json') }
|
|
89
93
|
|
|
90
94
|
function readJson(f) { try { return JSON.parse(readFileSync(f, 'utf8')) } catch { return null } }
|
|
91
95
|
function writeJson(f, obj) {
|
|
@@ -114,14 +118,30 @@ export function deactivate() {
|
|
|
114
118
|
return had
|
|
115
119
|
}
|
|
116
120
|
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
export function
|
|
121
|
+
// The trial starts the first time something asks with start on (a session;
|
|
122
|
+
// `leg license status` asks with it off, so looking does not start the clock).
|
|
123
|
+
export function trial({ today = isoToday(), start = true } = {}) {
|
|
124
|
+
let t = readJson(trialPath())
|
|
125
|
+
if (!t?.started && start) { t = { started: today }; writeJson(trialPath(), t) }
|
|
126
|
+
if (!t?.started) return { started: null, daysLeft: TRIAL_DAYS, expired: false }
|
|
127
|
+
const used = Math.floor((Date.parse(today) - Date.parse(t.started)) / 86400000)
|
|
128
|
+
const daysLeft = Math.max(0, TRIAL_DAYS - used)
|
|
129
|
+
return { started: t.started, daysLeft, expired: used >= TRIAL_DAYS || used < 0 }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// What this machine may do right now. A valid key wins; otherwise the trial;
|
|
133
|
+
// otherwise nothing, with the reason the stored key (if any) was refused.
|
|
134
|
+
export function entitlement({ today = isoToday(), releaseDate = RELEASE_DATE, startTrial = true } = {}) {
|
|
120
135
|
const lic = readLicense()
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
136
|
+
let refused = null
|
|
137
|
+
if (lic) {
|
|
138
|
+
const v = verifyLicense(lic.key, { today, releaseDate })
|
|
139
|
+
if (v.ok) return { ok: true, plan: v.payload.plan, seats: v.payload.seats ?? 1, payload: v.payload, source: 'license' }
|
|
140
|
+
refused = v.reason
|
|
141
|
+
}
|
|
142
|
+
const t = trial({ today, start: startTrial })
|
|
143
|
+
if (!t.expired) return { ok: true, plan: 'trial', started: t.started, daysLeft: t.daysLeft, source: 'trial', refused }
|
|
144
|
+
return { ok: false, plan: 'none', reason: refused ?? 'trial-expired', refused }
|
|
125
145
|
}
|
|
126
146
|
|
|
127
147
|
export function allows(ent, gate) {
|
|
@@ -136,13 +156,18 @@ export function explain(reason, payload) {
|
|
|
136
156
|
case 'unknown-plan': return `the key names a plan this version does not know (${payload?.plan})`
|
|
137
157
|
case 'personal-updates-ended': return `this Personal key covers releases up to ${payload?.updates_until}; this release is dated ${RELEASE_DATE}. Keep the version you have, or renew at ${BUY_URL}`
|
|
138
158
|
case 'team-expired': return `this Team key expired on ${payload?.expires}; run "leg license refresh" (the subscription renews it) or see ${BUY_URL}`
|
|
139
|
-
case '
|
|
159
|
+
case 'trial-expired': return `the ${TRIAL_DAYS}-day trial has ended; Leg needs a license key. Buy one at ${BUY_URL} (${GUARANTEE_DAYS}-day money-back guarantee), then: leg license activate <key>`
|
|
140
160
|
default: return String(reason)
|
|
141
161
|
}
|
|
142
162
|
}
|
|
143
163
|
|
|
144
164
|
export function describe(ent) {
|
|
145
165
|
if (!ent.ok) return `no license: ${explain(ent.reason)}`
|
|
166
|
+
if (ent.plan === 'trial') {
|
|
167
|
+
const note = ent.refused ? ` (stored key refused: ${ent.refused})` : ''
|
|
168
|
+
if (!ent.started) return `${TRIAL_DAYS}-day trial, not started; it starts with your first leg <agent>${note}`
|
|
169
|
+
return `${TRIAL_DAYS}-day trial, ${ent.daysLeft} day${ent.daysLeft === 1 ? '' : 's'} left; buy at ${BUY_URL}${note}`
|
|
170
|
+
}
|
|
146
171
|
const p = ent.payload
|
|
147
172
|
const until = p.plan === 'personal' ? `updates through ${p.updates_until}` : `renews; valid through ${p.expires}`
|
|
148
173
|
return `${PLANS[p.plan].label} license ${p.id}${p.seats > 1 ? `, ${p.seats} seats` : ''}, ${until}`
|