@manyoars/oars 0.4.1 → 0.5.2
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 +79 -29
- package/dist/bin.js +3 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +207 -21
- package/dist/club.d.ts +19 -0
- package/dist/club.js +132 -0
- package/dist/gate/hook-fill-main.js +1 -1
- package/dist/gate/hook-fill.d.ts +1 -1
- package/dist/gate/hook-fill.js +1 -1
- package/dist/gate/hook-main.js +7 -5
- package/dist/gate/hook.d.ts +12 -1
- package/dist/gate/hook.js +38 -7
- package/dist/hook-bin.js +23 -4
- package/dist/main.js +5 -0
- package/dist/node-floor.d.ts +10 -0
- package/dist/node-floor.js +23 -0
- package/dist/oars-hook.d.ts +2 -0
- package/dist/oars-hook.js +7 -0
- package/dist/sessions/allow.d.ts +1 -0
- package/dist/sessions/allow.js +1 -1
- package/dist/sessions/call-shape.d.ts +111 -0
- package/dist/sessions/call-shape.js +464 -0
- package/dist/sessions/candidates.d.ts +65 -0
- package/dist/sessions/candidates.js +236 -0
- package/dist/sessions/card-command.d.ts +24 -0
- package/dist/sessions/card-command.js +71 -0
- package/dist/sessions/card-font.d.ts +6 -0
- package/dist/sessions/card-font.js +36 -0
- package/dist/sessions/card-line.d.ts +30 -0
- package/dist/sessions/card-line.js +105 -0
- package/dist/sessions/card-render.d.ts +15 -0
- package/dist/sessions/card-render.js +146 -0
- package/dist/sessions/card.d.ts +59 -0
- package/dist/sessions/card.js +107 -0
- package/dist/sessions/cell-increments.d.ts +14 -3
- package/dist/sessions/cell-increments.js +79 -12
- package/dist/sessions/cells.d.ts +42 -4
- package/dist/sessions/cells.js +72 -18
- package/dist/sessions/codex-hook-bin.d.ts +1 -0
- package/dist/sessions/codex-hook-bin.js +24 -0
- package/dist/sessions/codex-hook-install.d.ts +21 -0
- package/dist/sessions/codex-hook-install.js +47 -0
- package/dist/sessions/codex-hook-view.d.ts +20 -0
- package/dist/sessions/codex-hook-view.js +20 -0
- package/dist/sessions/codex-hook.d.ts +77 -0
- package/dist/sessions/codex-hook.js +255 -0
- package/dist/sessions/codex.d.ts +9 -0
- package/dist/sessions/codex.js +104 -18
- package/dist/sessions/command.d.ts +17 -2
- package/dist/sessions/command.js +145 -33
- package/dist/sessions/copilot.d.ts +3 -0
- package/dist/sessions/copilot.js +12 -4
- package/dist/sessions/derive.js +2 -1
- package/dist/sessions/empty-state.d.ts +17 -0
- package/dist/sessions/empty-state.js +38 -0
- package/dist/sessions/exclusion.js +48 -3
- package/dist/sessions/extract.d.ts +16 -3
- package/dist/sessions/extract.js +281 -24
- package/dist/sessions/gemini.d.ts +2 -0
- package/dist/sessions/gemini.js +29 -2
- package/dist/sessions/harness-files.d.ts +27 -0
- package/dist/sessions/harness-files.js +109 -0
- package/dist/sessions/head-only.d.ts +7 -0
- package/dist/sessions/head-only.js +21 -0
- package/dist/sessions/hook-calls.js +4 -3
- package/dist/sessions/hooks.d.ts +1 -1
- package/dist/sessions/hooks.js +6 -2
- package/dist/sessions/identity.d.ts +6 -0
- package/dist/sessions/injected.d.ts +13 -0
- package/dist/sessions/injected.js +260 -0
- package/dist/sessions/install.d.ts +31 -20
- package/dist/sessions/install.js +80 -82
- package/dist/sessions/load-status.d.ts +41 -0
- package/dist/sessions/load-status.js +156 -0
- package/dist/sessions/machine-child-deps.d.ts +5 -0
- package/dist/sessions/machine-child-deps.js +83 -0
- package/dist/sessions/machine-child.d.ts +43 -0
- package/dist/sessions/machine-child.js +120 -0
- package/dist/sessions/machine-credential.d.ts +10 -0
- package/dist/sessions/machine-credential.js +20 -0
- package/dist/sessions/machine-enroll.d.ts +47 -0
- package/dist/sessions/machine-enroll.js +230 -0
- package/dist/sessions/machine-hold.d.ts +49 -0
- package/dist/sessions/machine-hold.js +88 -0
- package/dist/sessions/machine-ids.d.ts +6 -0
- package/dist/sessions/machine-ids.js +9 -0
- package/dist/sessions/machine-notice.d.ts +21 -0
- package/dist/sessions/machine-notice.js +89 -0
- package/dist/sessions/machine-pull.d.ts +40 -0
- package/dist/sessions/machine-pull.js +220 -0
- package/dist/sessions/machine-repo.d.ts +7 -0
- package/dist/sessions/machine-repo.js +30 -0
- package/dist/sessions/machine-scan-cache.d.ts +12 -0
- package/dist/sessions/machine-scan-cache.js +47 -0
- package/dist/sessions/machine-send.d.ts +12 -0
- package/dist/sessions/machine-send.js +156 -0
- package/dist/sessions/machine-stop.d.ts +22 -0
- package/dist/sessions/machine-stop.js +53 -0
- package/dist/sessions/machine.d.ts +41 -0
- package/dist/sessions/machine.js +89 -0
- package/dist/sessions/opencode.d.ts +2 -0
- package/dist/sessions/opencode.js +6 -0
- package/dist/sessions/otel.d.ts +21 -0
- package/dist/sessions/otel.js +109 -38
- package/dist/sessions/owner-only.d.ts +7 -0
- package/dist/sessions/owner-only.js +34 -0
- package/dist/sessions/pi.d.ts +13 -1
- package/dist/sessions/pi.js +27 -13
- package/dist/sessions/pool-books.d.ts +1 -1
- package/dist/sessions/pool-books.js +1 -1
- package/dist/sessions/pool-choices.d.ts +4 -2
- package/dist/sessions/pool-choices.js +4 -2
- package/dist/sessions/pool-command.d.ts +46 -6
- package/dist/sessions/pool-command.js +613 -136
- package/dist/sessions/pool-delta.d.ts +7 -0
- package/dist/sessions/pool-delta.js +28 -5
- package/dist/sessions/pool-due.d.ts +43 -0
- package/dist/sessions/pool-due.js +113 -0
- package/dist/sessions/pool-nightly.d.ts +9 -1
- package/dist/sessions/pool-nightly.js +65 -21
- package/dist/sessions/pool-outbox.d.ts +51 -5
- package/dist/sessions/pool-outbox.js +217 -24
- package/dist/sessions/pool-print.d.ts +1 -1
- package/dist/sessions/pool-raw.js +4 -3
- package/dist/sessions/pool-replica.d.ts +4 -4
- package/dist/sessions/pool-replica.js +26 -7
- package/dist/sessions/pool-send-lock.js +1 -1
- package/dist/sessions/pool-sent.d.ts +2 -0
- package/dist/sessions/pool-sent.js +21 -4
- package/dist/sessions/pool-wire.d.ts +176 -15
- package/dist/sessions/pool-wire.js +297 -33
- package/dist/sessions/pool.js +4 -2
- package/dist/sessions/public-vocabulary.d.ts +5 -0
- package/dist/sessions/public-vocabulary.js +16 -0
- package/dist/sessions/read-call-shape.js +12 -6
- package/dist/sessions/read-fit.d.ts +5 -0
- package/dist/sessions/read-fit.js +23 -2
- package/dist/sessions/read-interaction.js +1 -1
- package/dist/sessions/read-lead-lag.js +1 -1
- package/dist/sessions/read-readable.js +1 -1
- package/dist/sessions/read.d.ts +1 -1
- package/dist/sessions/recall-consequence.d.ts +2 -2
- package/dist/sessions/recall-consequence.js +14 -9
- package/dist/sessions/recall-object.d.ts +9 -2
- package/dist/sessions/receipt.d.ts +8 -0
- package/dist/sessions/receipt.js +78 -0
- package/dist/sessions/recovery-calls.d.ts +3 -2
- package/dist/sessions/recovery-calls.js +9 -3
- package/dist/sessions/redact.d.ts +11 -2
- package/dist/sessions/redact.js +201 -23
- package/dist/sessions/repo-root.d.ts +7 -0
- package/dist/sessions/repo-root.js +17 -13
- package/dist/sessions/reversion.d.ts +8 -0
- package/dist/sessions/reversion.js +114 -0
- package/dist/sessions/schema.d.ts +78 -4
- package/dist/sessions/schema.js +31 -1
- package/dist/sessions/session-shape.d.ts +2 -1
- package/dist/sessions/session-shape.js +15 -0
- package/dist/sessions/sources.d.ts +9 -0
- package/dist/sessions/sources.js +16 -5
- package/dist/sessions/steps.js +3 -2
- package/dist/sessions/store.d.ts +41 -2
- package/dist/sessions/store.js +68 -16
- package/dist/sessions/try-demo.d.ts +8 -0
- package/dist/sessions/try-demo.js +90 -0
- package/dist/sessions/try.d.ts +13 -0
- package/dist/sessions/try.js +50 -0
- package/dist/sessions/vscode-copilot.d.ts +4 -0
- package/dist/sessions/vscode-copilot.js +25 -18
- package/dist/telemetry/command.js +12 -6
- package/dist/telemetry/consent.d.ts +9 -3
- package/dist/telemetry/consent.js +25 -9
- package/dist/telemetry/fields.d.ts +11 -0
- package/dist/telemetry/fields.js +56 -0
- package/dist/telemetry/measures.d.ts +13 -0
- package/dist/telemetry/measures.js +30 -0
- package/dist/telemetry/observe.d.ts +15 -2
- package/dist/telemetry/observe.js +34 -7
- package/dist/telemetry/region.d.ts +6 -0
- package/dist/telemetry/region.js +28 -0
- package/dist/telemetry/report.js +1 -1
- package/dist/telemetry/schema.d.ts +3 -0
- package/dist/telemetry/schema.js +11 -1
- package/dist/telemetry/send.d.ts +5 -2
- package/dist/telemetry/send.js +15 -7
- package/dist/telemetry/words.d.ts +6 -0
- package/dist/telemetry/words.js +6 -0
- package/package.json +24 -5
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { consentOf, markNoticeShown, NOTICE } from './consent.js';
|
|
1
|
+
import { consentOf, everShownNotice, markNoticeShown, NOTICE } from './consent.js';
|
|
2
2
|
import { recordEvent } from './record.js';
|
|
3
3
|
import { startDailySend } from './send.js';
|
|
4
|
+
import { ciWord, installFolder, surfaceWord } from './fields.js';
|
|
4
5
|
import { errorClassOf } from './schema.js';
|
|
5
6
|
const pathOf = (command) => {
|
|
6
7
|
const names = [];
|
|
@@ -18,12 +19,29 @@ export const observeCommand = (observed, action) => {
|
|
|
18
19
|
const given = action.optsWithGlobals();
|
|
19
20
|
observed.flags = Object.keys(given).filter((key) => action.getOptionValueSourceWithGlobals(key) === 'cli');
|
|
20
21
|
};
|
|
21
|
-
const isHook = (argv, observed) => {
|
|
22
|
+
export const isHook = (argv, observed) => {
|
|
22
23
|
const gateHook = (words) => words[0] === 'gate' && words[1] === 'hook';
|
|
23
24
|
return gateHook(observed.path) || gateHook(argv);
|
|
24
25
|
};
|
|
25
|
-
/**
|
|
26
|
-
|
|
26
|
+
/**
|
|
27
|
+
* The `first-run` event, from the one place that shows the notice. `installPath` is where this copy runs from; only a test names it, and it is read here because it is needed on a first run only.
|
|
28
|
+
* Never throws: `oars telemetry on`, which shows the notice itself, calls it outside `finishTelemetry`'s guard.
|
|
29
|
+
*/
|
|
30
|
+
export const recordFirstRun = (deps, runtime, installPath) => {
|
|
31
|
+
try {
|
|
32
|
+
recordEvent({ ...runtime, version: deps.version, now: deps.now }, 'first-run', { ci: ciWord(deps.env), surface: surfaceWord(installPath ?? installFolder(), deps.env.npm_config_user_agent) });
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// Telemetry is never allowed to change what a command does.
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/** `--version` and `--help` end the parse before any action runs (so the path is empty); they stay a plain answer and leave the notice for the next command. */
|
|
39
|
+
const isInfoOnly = (argv, observed) => observed.path.length === 0 && argv.some((word) => ['--version', '-V', '--help', '-h'].includes(word));
|
|
40
|
+
/**
|
|
41
|
+
* Prints the notice on the first run that is not a hook, then records the event. Never throws and never changes what the command did. `installPath` is where this copy runs from; only a test names it,
|
|
42
|
+
* and it is read inside the guard because it is needed on a first run only.
|
|
43
|
+
*/
|
|
44
|
+
export const finishTelemetry = (deps, argv, observed, startedAt, exit, installPath) => {
|
|
27
45
|
try {
|
|
28
46
|
const runtime = deps.telemetry;
|
|
29
47
|
if (runtime === undefined)
|
|
@@ -31,17 +49,26 @@ export const finishTelemetry = (deps, argv, observed, startedAt, exit) => {
|
|
|
31
49
|
const consent = consentOf(deps.env, runtime.dir);
|
|
32
50
|
if (!consent.enabled)
|
|
33
51
|
return;
|
|
34
|
-
if (!consent.noticeShown && !isHook(argv, observed) &&
|
|
35
|
-
|
|
52
|
+
if (!consent.noticeShown && !isHook(argv, observed) && !isInfoOnly(argv, observed)) {
|
|
53
|
+
const seenBefore = everShownNotice(runtime.dir);
|
|
54
|
+
if (markNoticeShown(runtime.dir, deps.now)) {
|
|
55
|
+
deps.io.stderr(`${NOTICE}\n`);
|
|
56
|
+
// Once per install: an install that saw an older notice sees this one but records no second first-run; nothing is recorded or sent from a folder that could not claim it.
|
|
57
|
+
if (!seenBefore)
|
|
58
|
+
recordFirstRun(deps, runtime, installPath);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
36
61
|
const errorClass = errorClassOf(exit);
|
|
37
62
|
recordEvent({ ...runtime, version: deps.version, now: deps.now }, 'command', {
|
|
38
63
|
path: observed.path,
|
|
39
64
|
flags: observed.flags,
|
|
40
65
|
durationMs: Math.max(0, deps.now() - startedAt),
|
|
41
66
|
exitCode: exit,
|
|
67
|
+
ci: ciWord(deps.env),
|
|
68
|
+
...(observed.cardLine === undefined ? {} : { cardLine: observed.cardLine }),
|
|
42
69
|
...(errorClass === undefined ? {} : { errorClass }),
|
|
43
70
|
});
|
|
44
|
-
// Never from a hook: a hook has a time budget, and the send is for the
|
|
71
|
+
// Never from a hook: a hook has a time budget, and the send is for the detached sender and the commands a person runs.
|
|
45
72
|
if (!isHook(argv, observed))
|
|
46
73
|
startDailySend(runtime, deps.env, deps.now());
|
|
47
74
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** IANA zones of the EU, the EEA (Iceland, Liechtenstein, Norway), and the UK, with the outermost regions that sit outside `Europe/*` and `Europe/Mariehamn` (Åland). */
|
|
2
|
+
export declare const CLOSED_ZONES: ReadonlySet<string>;
|
|
3
|
+
/** True when the zone is on the closed list or cannot be read (absent or blank). */
|
|
4
|
+
export declare const isClosedZone: (zone: string | undefined) => boolean;
|
|
5
|
+
/** The machine's resolved IANA zone, or `undefined` when the runtime cannot say. */
|
|
6
|
+
export declare const machineZone: () => string | undefined;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Where telemetry is off: a machine whose time zone is in the EU, the EEA, or the UK records and sends nothing, with no prompt (P97). The zone is the machine's own fact, so no network call
|
|
2
|
+
// is made. An unresolvable zone counts as closed: the rule fails toward sending nothing.
|
|
3
|
+
/** IANA zones of the EU, the EEA (Iceland, Liechtenstein, Norway), and the UK, with the outermost regions that sit outside `Europe/*` and `Europe/Mariehamn` (Åland). */
|
|
4
|
+
export const CLOSED_ZONES = new Set([
|
|
5
|
+
// EU member states
|
|
6
|
+
'Europe/Vienna', 'Europe/Brussels', 'Europe/Sofia', 'Europe/Zagreb', 'Asia/Nicosia', 'Asia/Famagusta', 'Europe/Nicosia', 'Europe/Prague', 'Europe/Copenhagen',
|
|
7
|
+
'Europe/Tallinn', 'Europe/Helsinki', 'Europe/Mariehamn', 'Europe/Paris', 'Europe/Berlin', 'Europe/Busingen', 'Europe/Athens', 'Europe/Budapest', 'Europe/Dublin',
|
|
8
|
+
'Europe/Rome', 'Europe/Riga', 'Europe/Vilnius', 'Europe/Luxembourg', 'Europe/Malta', 'Europe/Amsterdam', 'Europe/Warsaw', 'Europe/Lisbon', 'Europe/Bucharest',
|
|
9
|
+
'Europe/Bratislava', 'Europe/Ljubljana', 'Europe/Madrid', 'Europe/Stockholm',
|
|
10
|
+
// EEA outside the EU, and the UK (with its legacy aliases). Europe/Jersey, Europe/Guernsey, and Europe/Isle_of_Man (Crown dependencies) and
|
|
11
|
+
// America/St_Barthelemy (outside the EU since 2012) are kept on purpose, to fail closed.
|
|
12
|
+
'Atlantic/Reykjavik', 'Iceland', 'Europe/Vaduz', 'Europe/Oslo', 'Arctic/Longyearbyen', 'Atlantic/Jan_Mayen', 'WET', 'CET', 'MET', 'EET', 'Europe/London', 'Europe/Belfast', 'Europe/Jersey', 'Europe/Guernsey', 'Europe/Isle_of_Man',
|
|
13
|
+
'GB', 'GB-Eire', 'Eire', 'Portugal', 'Poland',
|
|
14
|
+
// Outermost regions and island zones of member states
|
|
15
|
+
'Atlantic/Canary', 'Atlantic/Madeira', 'Atlantic/Azores', 'Africa/Ceuta', 'America/Martinique', 'America/Guadeloupe', 'America/Cayenne', 'Indian/Reunion', 'Indian/Mayotte',
|
|
16
|
+
'America/St_Barthelemy', 'America/Marigot',
|
|
17
|
+
]);
|
|
18
|
+
/** True when the zone is on the closed list or cannot be read (absent or blank). */
|
|
19
|
+
export const isClosedZone = (zone) => zone === undefined || zone.trim() === '' || zone.trim() === 'Etc/Unknown' || CLOSED_ZONES.has(zone.trim());
|
|
20
|
+
/** The machine's resolved IANA zone, or `undefined` when the runtime cannot say. */
|
|
21
|
+
export const machineZone = () => {
|
|
22
|
+
try {
|
|
23
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
};
|
package/dist/telemetry/report.js
CHANGED
|
@@ -167,7 +167,7 @@ export const textOf = (report, dir) => {
|
|
|
167
167
|
const floor = fleet.status === 'answered' ? fleet.floor : 0;
|
|
168
168
|
const out = [`Your telemetry, from the spool in ${dir}.`];
|
|
169
169
|
if (report.windows.every((window) => window.cells.length === 0) && report.store === undefined)
|
|
170
|
-
out.push('Nothing is measured in the spool yet: the hooks and
|
|
170
|
+
out.push('Nothing is measured in the spool yet: the hooks and the detached sender record as they run.');
|
|
171
171
|
for (const window of report.windows) {
|
|
172
172
|
if (window.cells.length === 0)
|
|
173
173
|
continue;
|
|
@@ -18,6 +18,9 @@ export declare const HOOK_OUTCOMES: readonly ["hit", "miss", "held", "error"];
|
|
|
18
18
|
export declare const RECALL_FORMS: readonly ["pattern", "entity", "question", "text", "legacy"];
|
|
19
19
|
export declare const RECALL_MODES: readonly ["pull", "push"];
|
|
20
20
|
export declare const RECALL_ARMS: readonly ["shown", "held"];
|
|
21
|
+
/** What the weekly card line did on a command: printed, held back by the holdback, or not due. */
|
|
22
|
+
export declare const CARD_LINE_ARMS: readonly ["shown", "held", "not_due"];
|
|
23
|
+
export type CardLineArm = (typeof CARD_LINE_ARMS)[number];
|
|
21
24
|
/** The read families a recall can serve, as the program names them. */
|
|
22
25
|
export declare const RECALL_FAMILY_NAMES: readonly ["recovery", "fit", "cost", "change", "live", "adoption", "method", "next-step", "tool-day", "version-boundary", "substitution", "interaction", "day-move", "entity"];
|
|
23
26
|
export declare const FETCH_HARNESSES: readonly ["claude-code", "cursor", "codex", "copilot", "gemini", "grok", "opencode", "pi", "vscode-copilot", "otel", "other"];
|
package/dist/telemetry/schema.js
CHANGED
|
@@ -6,6 +6,7 @@ import { ExitCode } from '../errors.js';
|
|
|
6
6
|
import { FIELD_ROWS } from '../sessions/pool-outbox.js';
|
|
7
7
|
import { OTHER, HARNESSES } from '../sessions/pool-wire.js';
|
|
8
8
|
import { LITERAL, publicModel, publicVersion } from '../sessions/public-vocabulary.js';
|
|
9
|
+
import { CI_WORDS, SURFACE_WORDS } from './words.js';
|
|
9
10
|
/** Bumped when an event type or field changes meaning; every spooled event carries it. */
|
|
10
11
|
export const SCHEMA_VERSION = 1;
|
|
11
12
|
/** A command or option name as the program defines it: letters, digits, and hyphens. No space, slash, dot, colon, or quote. */
|
|
@@ -36,6 +37,9 @@ const hex = (digits) => (value) => typeof value === 'string' && new RegExp(`^[0-
|
|
|
36
37
|
/** A problem code as a server names it: lowercase words joined with underscores. No space, slash, dot, colon, or quote, so no path, URL, or sentence fits. */
|
|
37
38
|
const CODE = /^[a-z][a-z0-9_]{0,39}$/;
|
|
38
39
|
const code = (value) => (typeof value === 'string' && CODE.test(value) ? value : undefined);
|
|
40
|
+
/** A covenant version as the pool names it: a short word of letters, digits, dots, underscores, and hyphens. A text off that shape (the pool is any server the operator names) is dropped, not trimmed. */
|
|
41
|
+
const COVENANT_VERSION = /^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/;
|
|
42
|
+
const covenantVersion = (value) => (typeof value === 'string' && COVENANT_VERSION.test(value) ? value : undefined);
|
|
39
43
|
const rules = (fields) => new Map(Object.entries(fields));
|
|
40
44
|
const DAY_MS = 86_400_000;
|
|
41
45
|
/** What went wrong, as a closed word chosen from the exit code; the error's own text never comes here. */
|
|
@@ -48,6 +52,8 @@ export const HOOK_OUTCOMES = ['hit', 'miss', 'held', 'error'];
|
|
|
48
52
|
export const RECALL_FORMS = ['pattern', 'entity', 'question', 'text', 'legacy'];
|
|
49
53
|
export const RECALL_MODES = ['pull', 'push'];
|
|
50
54
|
export const RECALL_ARMS = ['shown', 'held'];
|
|
55
|
+
/** What the weekly card line did on a command: printed, held back by the holdback, or not due. */
|
|
56
|
+
export const CARD_LINE_ARMS = ['shown', 'held', 'not_due'];
|
|
51
57
|
/** The read families a recall can serve, as the program names them. */
|
|
52
58
|
export const RECALL_FAMILY_NAMES = ['recovery', 'fit', 'cost', 'change', 'live', 'adoption', 'method', 'next-step', 'tool-day', 'version-boundary', 'substitution', 'interaction', 'day-move', 'entity'];
|
|
53
59
|
export const FETCH_HARNESSES = ['claude-code', 'cursor', 'codex', 'copilot', 'gemini', 'grok', 'opencode', 'pi', 'vscode-copilot', 'otel', 'other'];
|
|
@@ -63,7 +69,7 @@ const WIRE_HARNESSES = [...HARNESSES, OTHER];
|
|
|
63
69
|
const WIRE_LENGTH = WIRE_NAMES.size;
|
|
64
70
|
/** One line per event type. */
|
|
65
71
|
const EVENTS = new Map([
|
|
66
|
-
['command', rules({ path: tokens(4), flags: tokens(40), durationMs: whole(DAY_MS), exitCode: whole(255), errorClass: oneOf(ERROR_CLASSES) })],
|
|
72
|
+
['command', rules({ path: tokens(4), flags: tokens(40), durationMs: whole(DAY_MS), exitCode: whole(255), errorClass: oneOf(ERROR_CLASSES), ci: oneOf(CI_WORDS), cardLine: oneOf(CARD_LINE_ARMS) })],
|
|
67
73
|
['hook', rules({ name: oneOf(HOOK_NAMES), latencyMs: whole(DAY_MS), outcome: oneOf(HOOK_OUTCOMES), bytes: whole(BYTES), harness: oneOf(WIRE_HARNESSES), harnessVersion: MODEL_OR_VERSION(versionWord), model: MODEL_OR_VERSION(modelWord) })],
|
|
68
74
|
[
|
|
69
75
|
'recall',
|
|
@@ -128,6 +134,10 @@ const EVENTS = new Map([
|
|
|
128
134
|
],
|
|
129
135
|
// One send of one batch: whether the pool took it, the HTTP status, and the code of its refusal.
|
|
130
136
|
['wire-send', rules({ sent: whole(1), status: whole(599), code, bytes: whole(HUGE) })],
|
|
137
|
+
// The install's first run, sent once where the first-run notice is shown (docs/telemetry.md).
|
|
138
|
+
['first-run', rules({ ci: oneOf(CI_WORDS), surface: oneOf(SURFACE_WORDS) })],
|
|
139
|
+
// The CLI told the operator which covenant to read and accept: the version and nothing else.
|
|
140
|
+
['covenant-shown', rules({ version: covenantVersion })],
|
|
131
141
|
]);
|
|
132
142
|
export const EVENT_TYPES = [...EVENTS.keys()];
|
|
133
143
|
/** The field names a type admits, so a test can feed every one. */
|
package/dist/telemetry/send.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export interface SendDeps {
|
|
|
14
14
|
timeoutMs?: number;
|
|
15
15
|
}
|
|
16
16
|
export interface SendResult {
|
|
17
|
-
outcome: 'sent' | 'failed' | 'empty' | 'not-allowed' | 'no-server';
|
|
17
|
+
outcome: 'sent' | 'failed' | 'empty' | 'not-allowed' | 'no-server' | 'not-due';
|
|
18
18
|
/** Events the server took (and that were cleared from the spool). */
|
|
19
19
|
sent: number;
|
|
20
20
|
/** Requests made, including one that failed. */
|
|
@@ -25,7 +25,10 @@ export declare const writeMark: (dir: string, now: number) => void;
|
|
|
25
25
|
export declare const dueForSend: (dir: string, now: number) => boolean;
|
|
26
26
|
/** Sends what the spool holds. Never throws. */
|
|
27
27
|
export declare const sendSpool: (deps: SendDeps) => Promise<SendResult>;
|
|
28
|
-
/**
|
|
28
|
+
/**
|
|
29
|
+
* The detached sender's telemetry send, in the telemetry folder under the config folder. The sender runs up to about hourly, but telemetry goes at most once a
|
|
30
|
+
* day, on the same mark a command's daily send uses; events recorded by the runs in between wait in the spool for the next day's batch.
|
|
31
|
+
*/
|
|
29
32
|
export declare const sendNightly: (env: NodeJS.ProcessEnv, platform: NodeJS.Platform, home: string, now: () => number) => Promise<SendResult>;
|
|
30
33
|
/**
|
|
31
34
|
* Called after an interactive command: starts one detached send, at most once a day, when sending is allowed. The mark is written first, so two commands at
|
package/dist/telemetry/send.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// The sender: the only code that sends telemetry. It runs from the nightly
|
|
1
|
+
// The sender: the only code that sends telemetry. It runs from the detached sender (hook-bin.js nightly), and at most once a day from an interactive command as a detached process; never on a hook path, and
|
|
2
2
|
// nothing waits for it. Before any byte leaves it calls `telemetrySendAllowed` (telemetry on, notice shown) and sends to the pool server in `OARS_POOL_URL`, else the Many Oars pool, the same default `oars pool`
|
|
3
3
|
// reads and with the same rules (https, or http to this machine).
|
|
4
4
|
//
|
|
@@ -9,7 +9,7 @@ import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
import { fileURLToPath } from 'node:url';
|
|
11
11
|
import { defaultConfigDir } from '../credentials.js';
|
|
12
|
-
import { telemetrySendAllowed } from './consent.js';
|
|
12
|
+
import { shownNoticeVersion, telemetrySendAllowed } from './consent.js';
|
|
13
13
|
import { installId, readSpool, spoolPaths } from './spool.js';
|
|
14
14
|
/** The pool route takes at most this many events (Analytics Engine's per-request write limit). */
|
|
15
15
|
export const BATCH_EVENTS = 250;
|
|
@@ -41,12 +41,12 @@ const eventOf = (line) => {
|
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
43
|
/** True only for a 2xx. A redirect is not followed: a pool never redirects a POST, and a 3xx from anything else is not the pool. */
|
|
44
|
-
const post = async (url, install, events, deps) => {
|
|
44
|
+
const post = async (url, install, notice, events, deps) => {
|
|
45
45
|
try {
|
|
46
46
|
const response = await (deps.fetch ?? fetch)(url, {
|
|
47
47
|
method: 'POST',
|
|
48
48
|
headers: { 'content-type': 'application/json' },
|
|
49
|
-
body: JSON.stringify({ install, events }),
|
|
49
|
+
body: JSON.stringify({ install, notice, events }),
|
|
50
50
|
redirect: 'manual',
|
|
51
51
|
signal: AbortSignal.timeout(deps.timeoutMs ?? SEND_TIMEOUT_MS),
|
|
52
52
|
});
|
|
@@ -129,7 +129,7 @@ export const sendSpool = async (deps) => {
|
|
|
129
129
|
for (let at = 0; at < events.length && !failed; at += BATCH_EVENTS) {
|
|
130
130
|
const batch = events.slice(at, at + BATCH_EVENTS);
|
|
131
131
|
tally.requests += 1;
|
|
132
|
-
if (await post(`${server}/v1/telemetry`, install, batch.map(({ event }) => event), deps)) {
|
|
132
|
+
if (await post(`${server}/v1/telemetry`, install, shownNoticeVersion(deps.dir), batch.map(({ event }) => event), deps)) {
|
|
133
133
|
tally.sent += batch.length;
|
|
134
134
|
sent.push(...batch.map(({ line }) => line));
|
|
135
135
|
}
|
|
@@ -145,8 +145,16 @@ export const sendSpool = async (deps) => {
|
|
|
145
145
|
return finish('failed');
|
|
146
146
|
}
|
|
147
147
|
};
|
|
148
|
-
/**
|
|
149
|
-
|
|
148
|
+
/**
|
|
149
|
+
* The detached sender's telemetry send, in the telemetry folder under the config folder. The sender runs up to about hourly, but telemetry goes at most once a
|
|
150
|
+
* day, on the same mark a command's daily send uses; events recorded by the runs in between wait in the spool for the next day's batch.
|
|
151
|
+
*/
|
|
152
|
+
export const sendNightly = async (env, platform, home, now) => {
|
|
153
|
+
const dir = join(defaultConfigDir(env, platform, home), 'oars', 'telemetry');
|
|
154
|
+
if (!dueForSend(dir, now()))
|
|
155
|
+
return { outcome: 'not-due', sent: 0, requests: 0 };
|
|
156
|
+
return sendSpool({ env, dir, now });
|
|
157
|
+
};
|
|
150
158
|
/**
|
|
151
159
|
* Called after an interactive command: starts one detached send, at most once a day, when sending is allowed. The mark is written first, so two commands at
|
|
152
160
|
* once start one. Nothing here throws or waits, and the run has no way to start a send unless `main.ts` gave it one.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Whether a run was in CI and by which provider: the word is chosen from the provider's own variable (`ciWord` in `fields.ts`), so the variable's value never reaches the spool. `other` is a bare `CI`. */
|
|
2
|
+
export declare const CI_WORDS: readonly ["none", "github-actions", "gitlab-ci", "circleci", "travis-ci", "jenkins", "buildkite", "azure-pipelines", "bitbucket", "teamcity", "appveyor", "codebuild", "other"];
|
|
3
|
+
export type CiWord = (typeof CI_WORDS)[number];
|
|
4
|
+
/** How this copy of oars arrived, from the folder it runs in and the package manager that started it (`surfaceWord` in `fields.ts`); the path itself never reaches the spool. A folder it cannot place is `unknown`. */
|
|
5
|
+
export declare const SURFACE_WORDS: readonly ["npm-global", "npx", "claude-plugin", "codex-plugin", "gemini-extension", "unknown"];
|
|
6
|
+
export type SurfaceWord = (typeof SURFACE_WORDS)[number];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The two closed word lists the pool's rollup reads (`measures.ts`) beside the schema that checks them (`schema.ts`). A leaf on purpose: it imports nothing, so a Worker that imports it takes no
|
|
2
|
+
// file-system module along (`schema.ts` reaches `node:fs/promises` through the session outbox).
|
|
3
|
+
/** Whether a run was in CI and by which provider: the word is chosen from the provider's own variable (`ciWord` in `fields.ts`), so the variable's value never reaches the spool. `other` is a bare `CI`. */
|
|
4
|
+
export const CI_WORDS = ['none', 'github-actions', 'gitlab-ci', 'circleci', 'travis-ci', 'jenkins', 'buildkite', 'azure-pipelines', 'bitbucket', 'teamcity', 'appveyor', 'codebuild', 'other'];
|
|
5
|
+
/** How this copy of oars arrived, from the folder it runs in and the package manager that started it (`surfaceWord` in `fields.ts`); the path itself never reaches the spool. A folder it cannot place is `unknown`. */
|
|
6
|
+
export const SURFACE_WORDS = ['npm-global', 'npx', 'claude-plugin', 'codex-plugin', 'gemini-extension', 'unknown'];
|
package/package.json
CHANGED
|
@@ -1,7 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manyoars/oars",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.5.2",
|
|
4
|
+
"mcpName": "io.github.manyoars/oars",
|
|
5
|
+
"description": "A local, searchable record of your coding agent sessions. Reads Claude Code, Cursor, Codex, Copilot CLI, Gemini CLI, and more, and keeps the record on your machine.",
|
|
6
|
+
"homepage": "https://manyoars.com/",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"claude-code",
|
|
9
|
+
"codex",
|
|
10
|
+
"cursor",
|
|
11
|
+
"gemini-cli",
|
|
12
|
+
"coding-agents",
|
|
13
|
+
"agent-memory",
|
|
14
|
+
"session-history",
|
|
15
|
+
"mcp",
|
|
16
|
+
"mcp-server",
|
|
17
|
+
"claude-code-plugin",
|
|
18
|
+
"codex-plugin",
|
|
19
|
+
"gemini-cli-extension",
|
|
20
|
+
"coding-agent",
|
|
21
|
+
"cli"
|
|
22
|
+
],
|
|
5
23
|
"license": "Apache-2.0",
|
|
6
24
|
"repository": {
|
|
7
25
|
"type": "git",
|
|
@@ -10,10 +28,11 @@
|
|
|
10
28
|
},
|
|
11
29
|
"type": "module",
|
|
12
30
|
"engines": {
|
|
13
|
-
"node": ">=
|
|
31
|
+
"node": ">=22.23.4"
|
|
14
32
|
},
|
|
15
33
|
"bin": {
|
|
16
|
-
"oars": "./dist/bin.js"
|
|
34
|
+
"oars": "./dist/bin.js",
|
|
35
|
+
"oars-hook": "./dist/oars-hook.js"
|
|
17
36
|
},
|
|
18
37
|
"files": [
|
|
19
38
|
"dist",
|
|
@@ -30,7 +49,7 @@
|
|
|
30
49
|
"test:live": "vitest run --config vitest.live.config.ts"
|
|
31
50
|
},
|
|
32
51
|
"dependencies": {
|
|
33
|
-
"@manyoars/oars-client": "^0.1.
|
|
52
|
+
"@manyoars/oars-client": "^0.1.2",
|
|
34
53
|
"commander": "^15.0.0",
|
|
35
54
|
"openid-client": "^6.8.8"
|
|
36
55
|
},
|