@manyoars/oars 0.5.4 → 0.5.5
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 -4
- package/dist/cli.js +18 -7
- package/dist/club.d.ts +2 -0
- package/dist/club.js +10 -4
- package/dist/sessions/archive.js +1 -1
- package/dist/sessions/card-command.d.ts +2 -2
- package/dist/sessions/card-command.js +7 -5
- package/dist/sessions/card-line.d.ts +6 -2
- package/dist/sessions/card-line.js +6 -3
- package/dist/sessions/cell-increments.d.ts +7 -0
- package/dist/sessions/cell-increments.js +25 -10
- package/dist/sessions/cells.d.ts +7 -0
- package/dist/sessions/cells.js +118 -107
- package/dist/sessions/crew-mark.d.ts +6 -0
- package/dist/sessions/crew-mark.js +22 -0
- package/dist/sessions/hook-index.d.ts +2 -1
- package/dist/sessions/hook-index.js +2 -2
- package/dist/sessions/pool-command.d.ts +22 -4
- package/dist/sessions/pool-command.js +516 -82
- package/dist/sessions/pool-delta.d.ts +5 -0
- package/dist/sessions/pool-delta.js +32 -12
- package/dist/sessions/pool-fold.d.ts +22 -0
- package/dist/sessions/pool-fold.js +65 -0
- package/dist/sessions/pool-outbox.d.ts +9 -0
- package/dist/sessions/pool-outbox.js +33 -12
- package/dist/sessions/pool.d.ts +10 -5
- package/dist/sessions/pool.js +16 -15
- package/dist/sessions/read-call-shape.js +3 -2
- package/dist/sessions/read-folds.d.ts +21 -0
- package/dist/sessions/read-folds.js +37 -0
- package/dist/sessions/read-recovery.d.ts +1 -1
- package/dist/sessions/read-recovery.js +6 -5
- package/dist/sessions/read-support.d.ts +4 -1
- package/dist/sessions/recall-consequence.d.ts +2 -1
- package/dist/sessions/recall-consequence.js +1 -2
- package/dist/sessions/recall-reads.d.ts +23 -1
- package/dist/sessions/recall-reads.js +91 -63
- package/dist/sessions/reversion.d.ts +41 -2
- package/dist/sessions/reversion.js +17 -10
- package/dist/sessions/store.d.ts +55 -2
- package/dist/sessions/store.js +312 -61
- package/dist/telemetry/measures.d.ts +5 -0
- package/dist/telemetry/measures.js +16 -2
- package/dist/telemetry/schema.js +3 -1
- package/dist/telemetry/words.d.ts +6 -0
- package/dist/telemetry/words.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,7 +82,7 @@ oars card --month # the last 30 days as a picture: oars-card-mont
|
|
|
82
82
|
oars card --year --out me.png # the last 365 days, saved as me.png and me.svg
|
|
83
83
|
```
|
|
84
84
|
|
|
85
|
-
`oars card` makes a share-ready card of your own week (the default, the last 7 days), month, or year across every harness, read from the local session store only. It shows your sessions and active days, the harnesses and models you used, tokens by model, tool calls, failed calls and how many were followed by a different next step, how often an edit was verified, commits and how many survived, and cost only where the harness recorded one. A count under the same floor `oars sessions books` uses prints `too few`. The depositor row reads `ashore: run oars pool
|
|
85
|
+
`oars card` makes a share-ready card of your own week (the default, the last 7 days), month, or year across every harness, read from the local session store only. It shows your sessions and active days, the harnesses and models you used, tokens by model, tool calls, failed calls and how many were followed by a different next step, how often an edit was verified, commits and how many survived, and cost only where the harness recorded one. A count under the same floor `oars sessions books` uses prints `too few`. The depositor row reads `ashore: run oars pool join` until you sign in; signed in, it reads the number of weeks in which this machine sent a deposit, from the local sent record, and says it is this machine's.
|
|
86
86
|
|
|
87
87
|
Once a week, the first command you run in a terminal on or after Friday prints one line to stderr, `Your week: N sessions, M strokes. \`oars card --text\` for the table.`, never with `--json`, from a hook, from the MCP server, from a recall, from `oars sessions try`, or from `oars card` itself, and only when the week has a session. About one install in ten never sees it; `card-line.json` in the session store folder records the week and whether it was `shown`, `held`, or `not_due`, and `card-line-id` holds the id the holdback hashes, copied once from the telemetry install id when there is one, else random, so the arm never changes.
|
|
88
88
|
|
|
@@ -207,12 +207,12 @@ Replay is local and read-only on your transcripts; it writes `crossings.jsonl` a
|
|
|
207
207
|
|
|
208
208
|
### pool
|
|
209
209
|
|
|
210
|
-
`oars pool` uses the Many Oars pool, `https://pool.manyoars.com`, which is live and open
|
|
210
|
+
`oars pool` uses the Many Oars pool, `https://pool.manyoars.com`, which is live and open by covenant (a deposit needs the covenant read and accepted first), or a pool server you name with `--server <url>` or `OARS_POOL_URL`. Every other command works without it.
|
|
211
211
|
|
|
212
|
-
- `oars pool join --provider github|google --harness-version <version> [--harness <name>]` is the first way in:
|
|
212
|
+
- `oars pool join [--provider github|google] [--plan] [--accept-covenant <version>] [--in-eu-eea-uk <yes|no>] [--harness-version <version> [--harness <name>]]` is the first way in, from an empty store to a first send in one run. Its steps, in order: `signin` (`login`), `region` (at a terminal, for an account that has not accepted, "Are you in the EU, the EEA, or the UK?" is asked plainly and the answer goes with the acceptance; `--in-eu-eea-uk` answers it), `terms` (your exclusion term file, made empty for you to edit when it is missing, since oars adds no term for you; the run waits for Enter at a terminal and stops with the file's path off one), `batch` (`deposit`), `outbox` (`outbox --explain`, printed before anything is sent), `covenant` (the screen `deposit --send` shows; you type its phrase, and no flag or variable does it for you: off a terminal the run stops with `covenant_not_accepted`, and `--accept-covenant` only names the version expected), and `send` (`deposit --send <hash>` for each batch not yet sent). With `--harness-version` it then fetches that bundle, as `fetch` does. If a step stops, it prints that step's own error and names the command to run again; running `oars pool join` again skips what already stands (a held sign-in, terms in the file, a prepared batch, an accepted covenant, a batch already sent). `oars pool join --plan --json` prints `{ok, plan, server, steps: [{step, state, needs_person}]}` for the seven steps and `raw`, with `state` `done` or `pending` and `needs_person` true for `covenant` and `raw`, the two a person does themselves, and for `region` and `terms` while they are pending and no `--in-eu-eea-uk` flag or term in the file answers them; it reads the sign-in, the term file, the outbox, and (when signed in) the covenant, and signs in to nothing, writes nothing, and sends nothing. Each step leaves one `join-step` telemetry event (docs/telemetry.md). The commands below do the same step by step.
|
|
213
213
|
- `oars pool login --provider github|google` asks the pool to start the provider's device flow, prints the user code and link, and polls the pool until you approve (a failed poll is retried with a longer wait until the code expires; the output names the exclusion-terms file when there is none, and the first `pool deposit` needs it); with `--verbose`, every request prints before it is sent. The pool holds the OAuth client and its secret, so the CLI needs no client ID or secret, and the provider's tokens never reach this machine.
|
|
214
214
|
- `oars pool logout` revokes this machine's credential at the pool, then removes the local sign-in. If the pool cannot be reached it still removes the local sign-in and says the credential was not revoked there.
|
|
215
|
-
- `oars pool deposit` writes a batch of session skeletons (a closed list of fields, each value redacted first; a host is hashed by the pool on arrival) to an outbox for each session that passes your folder allowlist and the exclusion check and whose last line is over 6 hours old, and stops: no session data is sent. `--send <hash>` sends one batch that `outbox` printed and that is unchanged on disk, once this account has accepted the pool covenant (a person types the phrase the covenant screen states, at a terminal; `--accept-covenant <version>` only names the version expected and accepts nothing; the screen then asks "Are you in the EU, the EEA, or the UK? (yes/no)" and sends the answer, and `--in-eu-eea-uk <yes|no>` answers it without asking; while the pool is closed to those regions a yes, or a request from them, is refused with `region_closed` and nothing is accepted); before that it reads the covenant, sends no batch, and exits 3 with `covenant_not_accepted`. A hash that was never shown, or a batch that changed, is refused before any request. The detached sender is the other sender: after the pool says your account accepted its covenant, it sends new batches, and raw copies only if you said yes to the raw prompt (`oars pool raw --consent yes`, answered by typing the phrase `I consent to sending raw copies` at a terminal; a yes also tells the pool, and `--consent no` tells it to stop accepting raw copies, whatever a later request states (a machine that held a yes and is signed out exits nonzero: the pool was not told), and the pool's nightly job then erases the raw copies it already holds, starting that night; a large account can take more than one night). With no sign-in, no acceptance, or an unreachable server it sends nothing.
|
|
215
|
+
- `oars pool deposit` writes a batch of session skeletons (a closed list of fields, each value redacted first; a host is hashed by the pool on arrival) to an outbox for each session that passes your folder allowlist and the exclusion check and whose last line is over 6 hours old, and stops: no session data is sent. `--send <hash>` sends one batch that `outbox` printed and that is unchanged on disk, once this account has accepted the pool covenant (a person types the phrase the covenant screen states, at a terminal; `--accept-covenant <version>` only names the version expected and accepts nothing; the screen then asks "Are you in the EU, the EEA, or the UK? (yes/no)" and sends the answer, and `--in-eu-eea-uk <yes|no>` answers it without asking; while the pool is closed to those regions a yes, or a request from them, is refused with `region_closed` and nothing is accepted); before that it reads the covenant, sends no batch, and exits 3 with `covenant_not_accepted`. A hash that was never shown, or a batch that changed, is refused before any request. `--send-all` sends, in order and one batch at a time, every batch that `outbox` printed and that is unchanged, under the same covenant rule, except that a never-accepted account is refused, on a terminal too, with the `--send <hash>` command that shows the screen (it cannot be combined with `--send`, `--accept-covenant`, or `--in-eu-eea-uk`); a batch never printed or changed is skipped and counted, never sent. A batch already in `sent.log` is not sent again, so a run that stops (the first failed batch ends it, after up to three tries on a 429 or a 5xx, with the sent, skipped, failed, and left counts and a non-zero exit) is continued by running the same command again. It prints one progress line per 100 batches and a final count line, never a session's content. The detached sender is the other sender: after the pool says your account accepted its covenant, it sends new batches, and raw copies only if you said yes to the raw prompt (`oars pool raw --consent yes`, answered by typing the phrase `I consent to sending raw copies` at a terminal; a yes also tells the pool, and `--consent no` tells it to stop accepting raw copies, whatever a later request states (a machine that held a yes and is signed out exits nonzero: the pool was not told), and the pool's nightly job then erases the raw copies it already holds, starting that night; a large account can take more than one night). With no sign-in, no acceptance, or an unreachable server it sends nothing.
|
|
216
216
|
- `deposit`, `outbox`, `export`, and `fetch` need `oars pool login` first; without it they exit 3 and name the server.
|
|
217
217
|
- `oars pool coverage [--org-size <band>] [--repo-visibility public|private|unknown]` sets the two coverage bands only you can state, read locally and never from the network; an unset band goes on the wire as `unknown`.
|
|
218
218
|
- `oars pool export --out <folder> [--raw]` saves a copy of what the pool holds about your account (GDPR Articles 15(3) and 20), as JSON, one file a page, readable by you only. It covers your account row (with your raw consent record), when your sign-ins end (never the credential), your covenant acceptances, your machines (without their keys, with the day of each one's last deposit and its key days), your repository keys, your deposit hours, your deposits as they now read (with the machine, host class, and launcher each came from, and the host class and launcher the batch claimed), your raw ledger, your staged deposit parts, your erasure audit rows, and your telemetry days. `--raw` also saves each raw transcript you deposited that has not been erased, checked against its hash. It needs `oars pool login`, writes nothing to the pool, prints what it saved, and exits nonzero naming any page or object that failed; a rerun overwrites the same files. Totals the pool derives from many accounts are not yours to export and are not in it.
|
package/dist/cli.js
CHANGED
|
@@ -19,7 +19,7 @@ import { githubVisibility, poolEnroll, poolMachineRevoke, poolMachines } from '.
|
|
|
19
19
|
import { runNetGit } from './sessions/machine-hold.js';
|
|
20
20
|
import { holdPull } from './sessions/machine-pull.js';
|
|
21
21
|
import { runGit } from './sessions/survival.js';
|
|
22
|
-
import { poolCoverage, poolDeposit, poolExport, poolFetch, poolJoin, poolLogin, poolLogout, poolOutbox, poolProve, poolRaw, poolRemove } from './sessions/pool-command.js';
|
|
22
|
+
import { poolCoverage, poolDeposit, poolExport, poolFetch, poolInvite, poolJoin, poolLogin, poolLogout, poolOutbox, poolProve, poolRaw, poolRemove } from './sessions/pool-command.js';
|
|
23
23
|
import { telemetryOff, telemetryOn, telemetryReport, telemetryShow, telemetryStatus } from './telemetry/command.js';
|
|
24
24
|
import { cardLine } from './sessions/card-line.js';
|
|
25
25
|
import { recordCrash } from './telemetry/crash.js';
|
|
@@ -504,18 +504,20 @@ const buildProgram = (deps, observed) => {
|
|
|
504
504
|
const serverHelp = 'the pool server, https or plain http to localhost (default: OARS_POOL_URL, else https://pool.manyoars.com)';
|
|
505
505
|
pool
|
|
506
506
|
.command('join')
|
|
507
|
-
.description('
|
|
507
|
+
.description('your first deposit in one command: sign in, answer the region question, name the terms to keep out, see the batch, type the covenant phrase yourself, and send. Run it again after a stop to continue at the first step not done')
|
|
508
|
+
.option('--plan', 'print each step and whether it stands (needs_person marks the covenant phrase and the raw prompt, and the region question and term file while nothing answers them), and do nothing else: no sign-in, no file, no send')
|
|
508
509
|
.option('--provider <name>', 'github or google, for the sign-in step (not needed once signed in)')
|
|
509
510
|
.option('--verbose', 'print each sign-in request, headers and body, to stderr before it is sent (a credential shows as its size)')
|
|
510
511
|
.option('--accept-covenant <version>', 'name the covenant version you expect, as deposit --send takes it: on a terminal the screen is shown and you type its phrase yourself, and off a terminal this is refused')
|
|
511
512
|
.option('--in-eu-eea-uk <yes|no>', 'answer "Are you in the EU, the EEA, or the UK?" when you accept, as deposit --send takes it')
|
|
512
513
|
.option('--harness <name>', 'the harness whose bundle to fetch (default: the one your local store holds the most sessions of, claude-code when none leads)')
|
|
513
|
-
.
|
|
514
|
+
.option('--invite <code>', 'an invite code a founding or standing member gave you (oars pool invite makes one): used once, right after sign-in, and a run repeated after it skips it')
|
|
515
|
+
.option('--harness-version <version>', 'with this, fetch the public bundle for that harness version after the send, as fetch takes it; without it nothing is fetched')
|
|
514
516
|
.option(serverOption, serverHelp)
|
|
515
517
|
.action(async (_options, command) => {
|
|
516
518
|
const options = command.optsWithGlobals();
|
|
517
519
|
const json = flag(options, 'json');
|
|
518
|
-
emit(deps, json, await poolJoin(deps, { server: text(options, 'server'), provider: text(options, 'provider'), verbose: flag(options, 'verbose'), acceptCovenant: text(options, 'acceptCovenant'), inEuEeaUk: text(options, 'inEuEeaUk'), harness: text(options, 'harness'), harnessVersion: text(options, 'harnessVersion')
|
|
520
|
+
emit(deps, json, await poolJoin(deps, { server: text(options, 'server'), provider: text(options, 'provider'), verbose: flag(options, 'verbose'), acceptCovenant: text(options, 'acceptCovenant'), inEuEeaUk: text(options, 'inEuEeaUk'), harness: text(options, 'harness'), harnessVersion: text(options, 'harnessVersion'), invite: text(options, 'invite'), json, plan: flag(options, 'plan') }));
|
|
519
521
|
});
|
|
520
522
|
pool
|
|
521
523
|
.command('logout')
|
|
@@ -581,14 +583,15 @@ const buildProgram = (deps, observed) => {
|
|
|
581
583
|
});
|
|
582
584
|
pool
|
|
583
585
|
.command('deposit')
|
|
584
|
-
.description('write a batch to the outbox for each session that passes the folder allowlist and the exclusion check, and stop: no session data is sent, and only counts print. --send <hash> sends one batch that oars pool outbox printed and that is unchanged, once this account has accepted the pool covenant')
|
|
586
|
+
.description('write a batch to the outbox for each session that passes the folder allowlist and the exclusion check, and stop: no session data is sent, and only counts print. --send <hash> sends one batch that oars pool outbox printed and that is unchanged, and --send-all sends every such batch, once this account has accepted the pool covenant')
|
|
585
587
|
.option('--accept-covenant <version>', 'name the covenant version you expect, with --send: on a terminal the screen is shown and you type its phrase yourself, and off a terminal this is refused')
|
|
586
588
|
.option('--in-eu-eea-uk <yes|no>', 'answer "Are you in the EU, the EEA, or the UK?" when you accept, with --send; without it a person is asked, with or without --accept-covenant. While the pool is closed to those regions, yes is refused')
|
|
587
589
|
.option('--send <hash>', 'the SHA-256 of a batch that oars pool outbox printed')
|
|
590
|
+
.option('--send-all', 'send, in order, every batch that oars pool outbox printed and that is unchanged, one at a time, under the same covenant rule as --send, except that a never-accepted account is refused, on a terminal too, with the --send <hash> command that shows the screen; a batch never printed or changed is skipped and counted. A batch that fails (after up to three tries on 429 and 5xx) stops the run with the counts and a non-zero exit; run it again to send what is left, since a batch already sent is never sent twice. Not with --send')
|
|
588
591
|
.option(serverOption, serverHelp)
|
|
589
592
|
.action(async (_options, command) => {
|
|
590
593
|
const options = command.optsWithGlobals();
|
|
591
|
-
emit(deps, flag(options, 'json'), await poolDeposit(deps, { server: text(options, 'server'), acceptCovenant: text(options, 'acceptCovenant'), inEuEeaUk: text(options, 'inEuEeaUk'), send: text(options, 'send') }));
|
|
594
|
+
emit(deps, flag(options, 'json'), await poolDeposit(deps, { server: text(options, 'server'), acceptCovenant: text(options, 'acceptCovenant'), inEuEeaUk: text(options, 'inEuEeaUk'), send: text(options, 'send'), sendAll: flag(options, 'sendAll') }));
|
|
592
595
|
});
|
|
593
596
|
pool
|
|
594
597
|
.command('raw')
|
|
@@ -628,6 +631,14 @@ const buildProgram = (deps, observed) => {
|
|
|
628
631
|
const options = command.optsWithGlobals();
|
|
629
632
|
emit(deps, flag(options, 'json'), await poolRemove(deps, hash, { server: text(options, 'server'), restore: flag(options, 'restore') }));
|
|
630
633
|
});
|
|
634
|
+
pool
|
|
635
|
+
.command('invite')
|
|
636
|
+
.description('make one invite code to vouch for a person and print it once with the line they run to join (oars pool join --invite): a founding or standing account holds three open codes at most; needs oars pool login')
|
|
637
|
+
.option(serverOption, serverHelp)
|
|
638
|
+
.action(async (_options, command) => {
|
|
639
|
+
const options = command.optsWithGlobals();
|
|
640
|
+
emit(deps, flag(options, 'json'), await poolInvite(deps, { server: text(options, 'server') }));
|
|
641
|
+
});
|
|
631
642
|
pool
|
|
632
643
|
.command('prove')
|
|
633
644
|
.description('prove you control your pool account with your sign-in, and print the account and the time the pool stored, to send to the operator who asked for proof before acting on your request (GDPR Article 12(6)); for a closed account, run login instead')
|
|
@@ -770,7 +781,7 @@ const isSessionsTry = (observed) => observed.path.join(' ') === 'sessions try';
|
|
|
770
781
|
/** The weekly card line (`sessions/card-line.ts`): stderr only, never an error, never a change to the exit code. */
|
|
771
782
|
const weeklyLine = (argv, deps, observed) => deps.sessions === undefined
|
|
772
783
|
? Promise.resolve('not_due')
|
|
773
|
-
: cardLine({ storeDir: deps.sessions.storeDir, telemetryDir: deps.telemetry?.dir, now: deps.now(), tty: deps.sessions.stderr?.isTTY === true, argv, path: observed.path, flags: observed.flags, stderr: deps.io.stderr });
|
|
784
|
+
: cardLine({ storeDir: deps.sessions.storeDir, telemetryDir: deps.telemetry?.dir, now: deps.now(), tty: deps.sessions.stderr?.isTTY === true, argv, path: observed.path, flags: observed.flags, stderr: deps.io.stderr, env: deps.env });
|
|
774
785
|
/** Runs one invocation (`argv` without the node and script entries) and returns the exit code. Telemetry records it afterward and cannot change it. */
|
|
775
786
|
export const run = async (argv, deps) => {
|
|
776
787
|
const observed = { path: [], flags: [] };
|
package/dist/club.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export declare const clubJoin: (deps: RunDeps, code: string, options: Options &
|
|
|
12
12
|
}) => Promise<Outcome>;
|
|
13
13
|
/** `oars club leave <club>`: the row leaves every view at once. */
|
|
14
14
|
export declare const clubLeave: (deps: RunDeps, club: string, options: Options) => Promise<Outcome>;
|
|
15
|
+
/** What the club shows a member with no counting week, and where to go from it. */
|
|
16
|
+
export declare const ASHORE_CLUB_LINE = "ashore: run oars pool join to start rowing.";
|
|
15
17
|
/** `oars club show <club>`: the board, to its members only. */
|
|
16
18
|
export declare const clubShow: (deps: RunDeps, club: string, options: Options) => Promise<Outcome>;
|
|
17
19
|
/** `oars board`: the open board. It is public, so it needs no sign-in. */
|
package/dist/club.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// `oars club` and `oars board` (t1091): the commands over the pool's board routes (docs/pool.md, "Boards"). A club shares counts with the people who hold its code; the open board is public. Joining is a person at a terminal who reads the pool's consent text and types yes, the way the covenant is accepted: a flag or a script on stdin is not that person.
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { CliError, exitCodeForProblem, ExitCode } from './errors.js';
|
|
4
|
+
import { crewMarkText } from './sessions/crew-mark.js';
|
|
4
5
|
import { ask, isTerminal, lineFrom, poolServer, runtimeOf } from './sessions/pool-command.js';
|
|
5
6
|
import { isObject, loadSignIns, numberIn, textIn } from './sessions/pool-replica.js';
|
|
6
7
|
const CREATE = 'club-create';
|
|
@@ -101,16 +102,21 @@ const rowsOf = (body) => {
|
|
|
101
102
|
for (const member of Array.isArray(members) ? members : []) {
|
|
102
103
|
if (!isObject(member))
|
|
103
104
|
continue;
|
|
104
|
-
rows.push({ place: numberIn(member, 'place') ?? rows.length + 1, nickname: textIn(member, 'nickname') ?? '', ashore: Reflect.get(member, 'ashore') === true, weeksRowing: numberIn(member, 'weeksRowing') ?? 0, activeDays: numberIn(member, 'activeDays') ?? 0, sessions: numberIn(member, 'sessions') ?? 0, strokesThisWeek: numberIn(member, 'strokesThisWeek') ?? 0 });
|
|
105
|
+
rows.push({ place: numberIn(member, 'place') ?? rows.length + 1, nickname: textIn(member, 'nickname') ?? '', ashore: Reflect.get(member, 'ashore') === true, crewNumber: numberIn(member, 'crewNumber'), weeksRowing: numberIn(member, 'weeksRowing') ?? 0, activeDays: numberIn(member, 'activeDays') ?? 0, sessions: numberIn(member, 'sessions') ?? 0, strokesThisWeek: numberIn(member, 'strokesThisWeek') ?? 0 });
|
|
105
106
|
}
|
|
106
107
|
return rows;
|
|
107
108
|
};
|
|
109
|
+
/** What the club shows a member with no counting week, and where to go from it. */
|
|
110
|
+
export const ASHORE_CLUB_LINE = 'ashore: run oars pool join to start rowing.';
|
|
108
111
|
/** The board as lines, in the pool's order: a member with no counting week is "ashore" and has no numbers. */
|
|
109
112
|
const tableOf = (rows) => {
|
|
110
|
-
|
|
111
|
-
const
|
|
113
|
+
// The crew column appears only when a row carries a number, and it holds that row's own mark: nothing here counts the crew.
|
|
114
|
+
const crewed = rows.some((row) => row.crewNumber !== undefined);
|
|
115
|
+
const head = ['#', 'nickname', ...(crewed ? ['crew'] : []), 'weeks rowing', 'active days', 'sessions', 'strokes this week'];
|
|
116
|
+
const body = rows.map((row) => [String(row.place), row.nickname, ...(crewed ? [row.crewNumber === undefined ? '' : crewMarkText(row.crewNumber)] : []), ...(row.ashore ? ['ashore', '', '', ''] : [String(row.weeksRowing), String(row.activeDays), String(row.sessions), String(row.strokesThisWeek)])]);
|
|
112
117
|
const widths = head.map((title, at) => Math.max(title.length, ...body.map((line) => (line[at] ?? '').length)));
|
|
113
|
-
|
|
118
|
+
const table = [head, ...body].map((line) => line.map((cell, at) => cell.padEnd(widths[at] ?? 0)).join(' ').trimEnd()).join('\n');
|
|
119
|
+
return rows.some((row) => row.ashore) ? `${table}\n${ASHORE_CLUB_LINE}` : table;
|
|
114
120
|
};
|
|
115
121
|
/** `oars club show <club>`: the board, to its members only. */
|
|
116
122
|
export const clubShow = async (deps, club, options) => {
|
package/dist/sessions/archive.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// oars' own raw archive (
|
|
1
|
+
// oars' own raw archive (owner ruling 2026-11-01, retention point 4c): every deposited transcript is hard-linked under `<store>/raw/`, so the
|
|
2
2
|
// raw behind the log outlives the harness's cleanup without a second copy on disk while both exist. Across volumes, where a hard link
|
|
3
3
|
// cannot reach, it is copied. Local only: nothing here is ever shared.
|
|
4
4
|
import { copyFile, link, mkdir, rm, stat } from 'node:fs/promises';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { Outcome } from '../cli.js';
|
|
2
2
|
import { type CardPeriod } from './card.js';
|
|
3
3
|
import type { SessionsDeps } from './command.js';
|
|
4
|
-
export declare const ASHORE = "ashore: run oars pool
|
|
4
|
+
export declare const ASHORE = "ashore: run oars pool join";
|
|
5
5
|
/**
|
|
6
|
-
* The depositor row. Signed out it points at `oars pool
|
|
6
|
+
* The depositor row. Signed out it points at `oars pool join`, the one command from nothing to a first deposit; signed in it counts the weeks (Monday-start, UTC) in which this machine's sent record holds a deposit,
|
|
7
7
|
* labeled as this machine's because the record is local. It reads two local files and makes no network call; a sign-in file or pool address that cannot be read is
|
|
8
8
|
* signed out, since a share card must not fail on it.
|
|
9
9
|
*/
|
|
@@ -10,23 +10,24 @@ import { poolServer } from './pool-command.js';
|
|
|
10
10
|
import { outboxFolder } from './pool-outbox.js';
|
|
11
11
|
import { loadSignIns } from './pool-replica.js';
|
|
12
12
|
import { sentLog } from './pool-sent.js';
|
|
13
|
+
import { crewMarkText, crewNumberOf } from './crew-mark.js';
|
|
13
14
|
import { loadStore } from './store.js';
|
|
14
15
|
import { readSurvival } from './survival.js';
|
|
15
|
-
export const ASHORE = 'ashore: run oars pool
|
|
16
|
+
export const ASHORE = 'ashore: run oars pool join';
|
|
16
17
|
const DAY = 86_400_000;
|
|
17
18
|
const WEEK = 7 * DAY;
|
|
18
19
|
/** 1970-01-01 was a Thursday, so this is the Monday before it: weeks counted from here start on a Monday, UTC. */
|
|
19
20
|
const FIRST_MONDAY = -3 * DAY;
|
|
20
21
|
/**
|
|
21
|
-
* The depositor row. Signed out it points at `oars pool
|
|
22
|
+
* The depositor row. Signed out it points at `oars pool join`, the one command from nothing to a first deposit; signed in it counts the weeks (Monday-start, UTC) in which this machine's sent record holds a deposit,
|
|
22
23
|
* labeled as this machine's because the record is local. It reads two local files and makes no network call; a sign-in file or pool address that cannot be read is
|
|
23
24
|
* signed out, since a share card must not fail on it.
|
|
24
25
|
*/
|
|
25
26
|
export const depositorLine = async (storeDir, env, now) => {
|
|
26
27
|
try {
|
|
27
28
|
const server = poolServer(env, 'card');
|
|
28
|
-
const signedIn = (await loadSignIns(storeDir, 'card')).
|
|
29
|
-
if (
|
|
29
|
+
const signedIn = (await loadSignIns(storeDir, 'card')).find((entry) => entry.server === server && entry.expiresAt > now);
|
|
30
|
+
if (signedIn === undefined)
|
|
30
31
|
return ASHORE;
|
|
31
32
|
const weeks = new Set();
|
|
32
33
|
for (const at of (await sentLog(outboxFolder(storeDir, server))).values()) {
|
|
@@ -34,7 +35,8 @@ export const depositorLine = async (storeDir, env, now) => {
|
|
|
34
35
|
if (!Number.isNaN(time))
|
|
35
36
|
weeks.add(Math.floor((time - FIRST_MONDAY) / WEEK));
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
const crew = await crewNumberOf(storeDir, server, signedIn.account);
|
|
39
|
+
return `weeks rowing (this machine): ${String(weeks.size)}${crew === undefined ? '' : ` · ${crewMarkText(crew)}`}`;
|
|
38
40
|
}
|
|
39
41
|
catch (error) {
|
|
40
42
|
if (error instanceof CliError)
|
|
@@ -5,8 +5,10 @@ export declare const HELD_PERCENT = 10;
|
|
|
5
5
|
export declare const isoWeekOf: (now: number) => string;
|
|
6
6
|
/** Friday, Saturday, or Sunday local time: the ISO week's last three days, so a first command on or after Friday 00:00 is in the week it counts. */
|
|
7
7
|
export declare const fridayOrLater: (now: number) => boolean;
|
|
8
|
-
/** The
|
|
9
|
-
export declare const
|
|
8
|
+
/** The line a person with no pool sign-in also reads: the one command that takes them from nothing to a first deposit. */
|
|
9
|
+
export declare const ASHORE_LINE = "Ashore: `oars pool join` takes you to your first deposit.";
|
|
10
|
+
/** The week's text. Singular at one; ashore, the join line follows. Nothing else (no install, star, or link words). */
|
|
11
|
+
export declare const cardLineText: (sessions: number, strokes: number, ashore?: boolean) => string;
|
|
10
12
|
/** Stable per install: the first four bytes of a SHA-256 of the id, taken mod 100, under the holdback percent. */
|
|
11
13
|
export declare const isHeld: (id: string) => boolean;
|
|
12
14
|
export interface CardLineInput {
|
|
@@ -20,6 +22,8 @@ export interface CardLineInput {
|
|
|
20
22
|
path: readonly string[];
|
|
21
23
|
flags: readonly string[];
|
|
22
24
|
stderr: (text: string) => void;
|
|
25
|
+
/** The run's environment, for the pool address the sign-in is looked up under; absent reads the process's. */
|
|
26
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
23
27
|
}
|
|
24
28
|
/** A hook, the MCP server, a recall, `sessions try`, `--json`, or `oars card` itself: the line never goes there. */
|
|
25
29
|
export declare const quiet: (input: Pick<CardLineInput, "argv" | "path" | "flags">) => boolean;
|
|
@@ -6,6 +6,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
6
6
|
import { join } from 'node:path';
|
|
7
7
|
import { isHook } from '../telemetry/observe.js';
|
|
8
8
|
import { cardOf } from './card.js';
|
|
9
|
+
import { ASHORE, depositorLine } from './card-command.js';
|
|
9
10
|
import { mkdirOwnerOnly, writeOwnerOnly } from './owner-only.js';
|
|
10
11
|
import { loadStore } from './store.js';
|
|
11
12
|
import { readSurvival } from './survival.js';
|
|
@@ -28,8 +29,10 @@ export const fridayOrLater = (now) => {
|
|
|
28
29
|
const day = new Date(now).getDay();
|
|
29
30
|
return day === 0 || day >= FRIDAY;
|
|
30
31
|
};
|
|
31
|
-
/** The
|
|
32
|
-
export const
|
|
32
|
+
/** The line a person with no pool sign-in also reads: the one command that takes them from nothing to a first deposit. */
|
|
33
|
+
export const ASHORE_LINE = 'Ashore: `oars pool join` takes you to your first deposit.';
|
|
34
|
+
/** The week's text. Singular at one; ashore, the join line follows. Nothing else (no install, star, or link words). */
|
|
35
|
+
export const cardLineText = (sessions, strokes, ashore = false) => `Your week: ${String(sessions)} ${sessions === 1 ? 'session' : 'sessions'}, ${String(strokes)} ${strokes === 1 ? 'stroke' : 'strokes'}. \`oars card --text\` for the table.${ashore ? ` ${ASHORE_LINE}` : ''}\n`;
|
|
33
36
|
const readTrimmed = async (file) => {
|
|
34
37
|
try {
|
|
35
38
|
return (await readFile(file, 'utf8')).trim();
|
|
@@ -96,7 +99,7 @@ export const cardLine = async (input) => {
|
|
|
96
99
|
const arm = held ? 'held' : 'shown';
|
|
97
100
|
await mark(input.storeDir, week, arm);
|
|
98
101
|
if (!held)
|
|
99
|
-
input.stderr(cardLineText(card.sessions, card.strokes));
|
|
102
|
+
input.stderr(cardLineText(card.sessions, card.strokes, (await depositorLine(input.storeDir, input.env ?? process.env, input.now)) === ASHORE));
|
|
100
103
|
return arm;
|
|
101
104
|
}
|
|
102
105
|
catch {
|
|
@@ -36,6 +36,13 @@ export interface Increments {
|
|
|
36
36
|
motifs: Motif[];
|
|
37
37
|
strata: Map<string, StratumRow>;
|
|
38
38
|
}
|
|
39
|
+
/** What `cellsOf` does to a store, as calls: `add` one run at a time, then `finish` once. */
|
|
40
|
+
export interface CellsFold {
|
|
41
|
+
add: (store: Store) => void;
|
|
42
|
+
finish: () => CellSet;
|
|
43
|
+
}
|
|
44
|
+
/** `cellsOf` as a fold: each `add` floors one run and tallies it, whole and marginal, and `finish` merges the two. Runs come in the whole store's row order. */
|
|
45
|
+
export declare const cellsFold: (graph?: RecallGraph) => CellsFold;
|
|
39
46
|
/**
|
|
40
47
|
* Rule 5: exact tokens and durations never reach `index`. A cell counts a bucketed value as the bucket's lowest value, so a derivation reads only what a deposit carries, and a
|
|
41
48
|
* summed window is a sum of those. A value already at a bucket's floor stays there, which is why a skeleton's store and a local store of the same session derive the same cells.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// increments with `incrementsOf`, and a local read derives its cells with `cellsOf` over the store it holds, projected the way a deposit projects it (`projectedStore`). Both end in the
|
|
3
3
|
// one `cellsOf`, so a session gives the same cells at the pool and on the machine. This file reads no file system: the pool's Worker bundles it.
|
|
4
4
|
import { ARG_CLASSES, CHANGE_KINDS } from './call-shape.js';
|
|
5
|
-
import { bucketFields,
|
|
5
|
+
import { bucketFields, cellFold, ENVIRONMENT_BUCKET, EXPOSURE_CODES, exposureOfCell, isEnvironmentCell } from './cells.js';
|
|
6
6
|
import { bucketOf, floorOf, OTHER, OPEN_GATE, projectedSteps, wireChange, wireFeatures, wireKeys, wireValues } from './pool-wire.js';
|
|
7
7
|
import { NO_RECALLS } from './recall-consequence.js';
|
|
8
8
|
import { USAGE_FIELDS, harnessOf } from './schema.js';
|
|
@@ -40,21 +40,36 @@ const marginalSteps = (step) => {
|
|
|
40
40
|
};
|
|
41
41
|
/** The tools of a store as one-step calls, one per flag and per pull: read at window length 1, each is one marginal cell. */
|
|
42
42
|
const marginalStore = (store) => ({ ...store, tools: store.tools.flatMap((tool) => (tool.steps ?? []).flatMap(marginalSteps).map((marginal) => ({ ...tool, steps: [marginal] }))) });
|
|
43
|
+
/** `cellsOf` as a fold: each `add` floors one run and tallies it, whole and marginal, and `finish` merges the two. Runs come in the whole store's row order. */
|
|
44
|
+
export const cellsFold = (graph = NO_RECALLS) => {
|
|
45
|
+
const whole = cellFold(graph);
|
|
46
|
+
const marginal = cellFold(graph);
|
|
47
|
+
const add = (store) => {
|
|
48
|
+
const floored = flooredStore(store);
|
|
49
|
+
whole.add({ ...floored, tools: floored.tools.map((tool) => (tool.steps === undefined ? tool : { ...tool, steps: tool.steps.map(withoutLists) })) });
|
|
50
|
+
// Only the one-step named motifs of the marginal pass that hold a shell identity (a recall step the graph places there has none, and `whole` already counts it) are kept at the finish: its prompts, its shape motifs, and its longer windows repeat or splice what `whole` holds.
|
|
51
|
+
marginal.add(marginalStore(floored));
|
|
52
|
+
};
|
|
53
|
+
const finish = () => {
|
|
54
|
+
const wholeSet = whole.finish();
|
|
55
|
+
const marginalSet = marginal.finish();
|
|
56
|
+
const named = new Set(marginalSet.motifs.filter(({ level, steps }) => level === 'named' && steps.length === 1 && steps[0]?.identity !== null).map(({ motifId }) => motifId));
|
|
57
|
+
const motifs = new Map([...wholeSet.motifs, ...marginalSet.motifs.filter(({ motifId }) => named.has(motifId))].map((motif) => [motif.motifId, motif]));
|
|
58
|
+
const order = (x, y) => (x < y ? -1 : x > y ? 1 : 0);
|
|
59
|
+
const cells = [...wholeSet.cells, ...marginalSet.cells.filter(({ motifId }) => named.has(motifId))].sort((x, y) => order(x.motifId, y.motifId) || order(x.frameBucket, y.frameBucket) || order(x.grain, y.grain) || order(x.day, y.day) || EXPOSURE_CODES[exposureOfCell(x)] - EXPOSURE_CODES[exposureOfCell(y)]);
|
|
60
|
+
return { cells, motifs: [...motifs.values()].sort((x, y) => order(x.motifId, y.motifId)) };
|
|
61
|
+
};
|
|
62
|
+
return { add, finish };
|
|
63
|
+
};
|
|
43
64
|
/**
|
|
44
65
|
* Rule 5: exact tokens and durations never reach `index`. A cell counts a bucketed value as the bucket's lowest value, so a derivation reads only what a deposit carries, and a
|
|
45
66
|
* summed window is a sum of those. A value already at a bucket's floor stays there, which is why a skeleton's store and a local store of the same session derive the same cells.
|
|
46
67
|
* Rules 1 and 2: a window names a shell step by its program and subcommand alone, and each flag and each pull of it adds a one-step cell of its own.
|
|
47
68
|
*/
|
|
48
69
|
export const cellsOf = (store, graph = NO_RECALLS) => {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const marginal = deriveCells(marginalStore(floored), graph);
|
|
53
|
-
const named = new Set(marginal.motifs.filter(({ level, steps }) => level === 'named' && steps.length === 1 && steps[0]?.identity !== null).map(({ motifId }) => motifId));
|
|
54
|
-
const motifs = new Map([...whole.motifs, ...marginal.motifs.filter(({ motifId }) => named.has(motifId))].map((motif) => [motif.motifId, motif]));
|
|
55
|
-
const order = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
56
|
-
const cells = [...whole.cells, ...marginal.cells.filter(({ motifId }) => named.has(motifId))].sort((a, b) => order(a.motifId, b.motifId) || order(a.frameBucket, b.frameBucket) || order(a.grain, b.grain) || order(a.day, b.day) || EXPOSURE_CODES[exposureOfCell(a)] - EXPOSURE_CODES[exposureOfCell(b)]);
|
|
57
|
-
return { cells, motifs: [...motifs.values()].sort((a, b) => order(a.motifId, b.motifId)) };
|
|
70
|
+
const fold = cellsFold(graph);
|
|
71
|
+
fold.add(store);
|
|
72
|
+
return fold.finish();
|
|
58
73
|
};
|
|
59
74
|
/**
|
|
60
75
|
* A local store with each shell step read as a deposit reads it (`projectedSteps`), so its named motifs are the ones the pool holds, and each call's key names as the wire sends them (`wireKeys`), so its shape cells are too.
|
package/dist/sessions/cells.d.ts
CHANGED
|
@@ -134,6 +134,13 @@ export interface CellSet {
|
|
|
134
134
|
cells: Cell[];
|
|
135
135
|
motifs: Motif[];
|
|
136
136
|
}
|
|
137
|
+
/** The tally half of `deriveCells`: `add` one run of sessions, then `finish`. */
|
|
138
|
+
export interface CellFold {
|
|
139
|
+
add: (store: Store) => void;
|
|
140
|
+
finish: () => CellSet;
|
|
141
|
+
}
|
|
142
|
+
/** `deriveCells` as a fold: each `add` tallies one run of sessions into the same tallies, and `finish` sorts them. The runs must come in the order the whole store holds its rows, so sums of floats match. */
|
|
143
|
+
export declare const cellFold: (graph?: RecallGraph) => CellFold;
|
|
137
144
|
export declare const deriveCells: (store: Store, graph?: RecallGraph) => CellSet;
|
|
138
145
|
export declare const serializeCells: (cells: readonly Cell[]) => string;
|
|
139
146
|
/** What every read of one derive pass reads, and the snapshot it answers from. */
|