@manyoars/oars 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +79 -29
- package/dist/bin.js +3 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +206 -20
- 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 +212 -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 +577 -133
- 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 +172 -12
- package/dist/sessions/pool-wire.js +284 -30
- 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.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 +2 -2
- package/dist/sessions/redact.js +116 -14
- 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 +23 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Recall } from '../sessions/recall-object.js';
|
|
2
|
+
import type { CiWord, SurfaceWord } from './words.js';
|
|
2
3
|
/** The harness every hook oars installs runs under (Claude Code); one spelling for the session hooks and the gate hook, so the two emitters cannot drift. */
|
|
3
4
|
export declare const HOOK_HARNESS = "claude-code";
|
|
4
5
|
/** The harness a hook ran under, as the closed list names it, else `other`. */
|
|
@@ -14,3 +15,13 @@ export declare const frameWords: (harness: string | undefined, harnessVersion: s
|
|
|
14
15
|
};
|
|
15
16
|
/** A recall as counts: how it was asked, which families it served, how many reads and evidence rows, the size of the answer, and its arm. Never the query text, a name, or the rendered answer. */
|
|
16
17
|
export declare const recallFields: (recall: Recall, families: readonly string[], legacy?: boolean) => object;
|
|
18
|
+
/** Whether this run is in CI, as a closed word read from the environment's variable names. Only the name decides the word: the value is tested and dropped, never returned. */
|
|
19
|
+
export declare const ciWord: (env: NodeJS.ProcessEnv) => CiWord;
|
|
20
|
+
/**
|
|
21
|
+
* How this copy arrived, as a closed word, from the folder oars runs in and `npm_config_user_agent` and from nothing else. A harness store is read first (a plugin can carry its own
|
|
22
|
+
* `node_modules`), then a package runner, then a global install; a folder that fits none reads `unknown`, never a guess. The agent only confirms a runner: a package manager that started the
|
|
23
|
+
* process says its name there, and a folder called `dlx` with no such agent is not a runner.
|
|
24
|
+
*/
|
|
25
|
+
export declare const surfaceWord: (installPath: string, userAgent: string | undefined) => SurfaceWord;
|
|
26
|
+
/** The folder this module runs from: inside the package wherever it was installed (a symlinked bin resolves to its real folder). */
|
|
27
|
+
export declare const installFolder: () => string;
|
package/dist/telemetry/fields.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HARNESSES, OTHER } from '../sessions/pool-wire.js';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
2
3
|
import { modelWord, versionWord } from './schema.js';
|
|
3
4
|
/** The harness every hook oars installs runs under (Claude Code); one spelling for the session hooks and the gate hook, so the two emitters cannot drift. */
|
|
4
5
|
export const HOOK_HARNESS = 'claude-code';
|
|
@@ -24,3 +25,58 @@ export const recallFields = (recall, families, legacy = false) => ({
|
|
|
24
25
|
tokens: recall.answer.tokens,
|
|
25
26
|
arm: recall.arm,
|
|
26
27
|
});
|
|
28
|
+
/** A provider's own variable and the word it is read as. Order is the precedence: most providers also set `CI`, which on its own reads `other`. */
|
|
29
|
+
const CI_PROVIDERS = [
|
|
30
|
+
['GITHUB_ACTIONS', 'github-actions'],
|
|
31
|
+
['GITLAB_CI', 'gitlab-ci'],
|
|
32
|
+
['CIRCLECI', 'circleci'],
|
|
33
|
+
['TRAVIS', 'travis-ci'],
|
|
34
|
+
['JENKINS_URL', 'jenkins'],
|
|
35
|
+
['BUILDKITE', 'buildkite'],
|
|
36
|
+
['TF_BUILD', 'azure-pipelines'],
|
|
37
|
+
['BITBUCKET_BUILD_NUMBER', 'bitbucket'],
|
|
38
|
+
['TEAMCITY_VERSION', 'teamcity'],
|
|
39
|
+
['APPVEYOR', 'appveyor'],
|
|
40
|
+
['CODEBUILD_BUILD_ID', 'codebuild'],
|
|
41
|
+
];
|
|
42
|
+
/** A variable counts when it holds anything but the words a shell uses for no (an empty value, `0`, `false`, `no`, `off`). */
|
|
43
|
+
const NOT_SET = new Set(['', '0', 'false', 'no', 'off']);
|
|
44
|
+
const isSet = (value) => value !== undefined && !NOT_SET.has(value.toLowerCase());
|
|
45
|
+
/** Whether this run is in CI, as a closed word read from the environment's variable names. Only the name decides the word: the value is tested and dropped, never returned. */
|
|
46
|
+
export const ciWord = (env) => {
|
|
47
|
+
for (const [variable, word] of CI_PROVIDERS)
|
|
48
|
+
if (isSet(env[variable]))
|
|
49
|
+
return word;
|
|
50
|
+
return isSet(env.CI) ? 'other' : 'none';
|
|
51
|
+
};
|
|
52
|
+
/** The harness stores a plugin or extension lands in, matched on the folder names the harness uses under its home. */
|
|
53
|
+
const HARNESS_STORES = [
|
|
54
|
+
['/.claude/plugins/', 'claude-plugin'],
|
|
55
|
+
['/.codex/plugins/', 'codex-plugin'],
|
|
56
|
+
['/.gemini/extensions/', 'gemini-extension'],
|
|
57
|
+
];
|
|
58
|
+
/** Where a package manager keeps a global install: npm and Node's own prefix (`lib/node_modules`, Windows `npm/node_modules`), pnpm, yarn, and bun. */
|
|
59
|
+
const GLOBAL_FOLDERS = ['/lib/node_modules/', '/npm/node_modules/', '/pnpm/global/', '/yarn/global/node_modules/', '/.bun/install/global/'];
|
|
60
|
+
/** npm's own folder for a package run once (`npx`), which no other tool uses. */
|
|
61
|
+
const NPX_FOLDER = '/_npx/';
|
|
62
|
+
/** The folders pnpm (`dlx`) and bun (`bunx-<id>-<package>`) run a package from: names common enough that only a package manager's own agent makes them count. */
|
|
63
|
+
const RUNNER_FOLDERS = /\/(dlx|bunx-[^/]*)\//;
|
|
64
|
+
const PACKAGE_MANAGER_AGENT = /^(npm|pnpm|yarn|bun)\//;
|
|
65
|
+
/**
|
|
66
|
+
* How this copy arrived, as a closed word, from the folder oars runs in and `npm_config_user_agent` and from nothing else. A harness store is read first (a plugin can carry its own
|
|
67
|
+
* `node_modules`), then a package runner, then a global install; a folder that fits none reads `unknown`, never a guess. The agent only confirms a runner: a package manager that started the
|
|
68
|
+
* process says its name there, and a folder called `dlx` with no such agent is not a runner.
|
|
69
|
+
*/
|
|
70
|
+
export const surfaceWord = (installPath, userAgent) => {
|
|
71
|
+
const path = `/${installPath.split(String.fromCharCode(92)).join('/').toLowerCase()}`;
|
|
72
|
+
const store = HARNESS_STORES.find(([folder]) => path.includes(folder));
|
|
73
|
+
if (store !== undefined)
|
|
74
|
+
return store[1];
|
|
75
|
+
if (path.includes(NPX_FOLDER))
|
|
76
|
+
return 'npx';
|
|
77
|
+
if (RUNNER_FOLDERS.test(path) && userAgent !== undefined && PACKAGE_MANAGER_AGENT.test(userAgent))
|
|
78
|
+
return 'npx';
|
|
79
|
+
return GLOBAL_FOLDERS.some((folder) => path.includes(folder)) ? 'npm-global' : 'unknown';
|
|
80
|
+
};
|
|
81
|
+
/** The folder this module runs from: inside the package wherever it was installed (a symlinked bin resolves to its real folder). */
|
|
82
|
+
export const installFolder = () => fileURLToPath(import.meta.url);
|
|
@@ -15,6 +15,19 @@ export declare const WINDOWS: readonly number[];
|
|
|
15
15
|
export declare const HOOK_NAMES: readonly string[];
|
|
16
16
|
export declare const OTHER = "other";
|
|
17
17
|
export declare const UNRECORDED = "unrecorded";
|
|
18
|
+
/**
|
|
19
|
+
* The cell of a `first-run` event: one yes (the first run was in CI) or no under the oars version, in a metric named for the listing surface (`first-run.npx`), so the pool's rollup holds each install's
|
|
20
|
+
* first-run day, its surface, and whether it ran in CI. `measuresOf` leaves it out on purpose: a rate over one event is no measure of the program, so the user's own report has no use for it, and the pool's fleet read skips it for the same reason. A word off
|
|
21
|
+
* the closed lists reads as `unknown` or not-CI, and the install is still counted.
|
|
22
|
+
*/
|
|
23
|
+
export declare const firstRunMeasures: (event: unknown) => Measure[];
|
|
24
|
+
/**
|
|
25
|
+
* The cell of a `covenant-shown` event: one yes under the oars version, in a metric named for the covenant version the CLI told the operator to read (`covenant-shown.2026-11-10`), so the pool's rollup counts shows by version and
|
|
26
|
+
* day. Raw batches are swept at 90 days and a cell added later cannot count older shows, so the cell is kept from the first event. `measuresOf` leaves it out and the fleet read skips it (`isRecordOnlyMetric`): a count of shows is
|
|
27
|
+
* for the operator's query, not a measure of the program. A version that is not a word reads `other`, and a missing one `unrecorded`; the show is still counted.
|
|
28
|
+
*/
|
|
29
|
+
export declare const covenantShownMeasures: (event: unknown) => Measure[];
|
|
30
|
+
export declare const isRecordOnlyMetric: (metric: string) => boolean;
|
|
18
31
|
/** What one event measures; an event of a type nothing measures, or that is not an object, measures nothing. */
|
|
19
32
|
export declare const measuresOf: (event: unknown) => Measure[];
|
|
20
33
|
/** Every cell is named by these three together. */
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// A measure is one observation in a cell: a latency in milliseconds, or a yes/no that rates are made from. The cell is (metric, dimension, value): `hook.pre-tool.latency` at harness
|
|
3
3
|
// `claude-code`, version `2.1.5`. The reader is tolerant on purpose: the pool reads batches kept whole from any client, so a value that does not fit is skipped, never trusted.
|
|
4
4
|
import { isHarness } from '../sessions/pool-wire.js';
|
|
5
|
+
import { CI_WORDS, SURFACE_WORDS } from './words.js';
|
|
5
6
|
/** The windows the report shows and the pool serves, in days. */
|
|
6
7
|
export const WINDOWS = [7, 30];
|
|
7
8
|
const DAY_MS = 86_400_000;
|
|
@@ -73,6 +74,35 @@ const storeMeasures = (event, data) => {
|
|
|
73
74
|
found.push(rate('store.verifyFailure', 'cli', version, failed > 0));
|
|
74
75
|
return found;
|
|
75
76
|
};
|
|
77
|
+
/**
|
|
78
|
+
* The cell of a `first-run` event: one yes (the first run was in CI) or no under the oars version, in a metric named for the listing surface (`first-run.npx`), so the pool's rollup holds each install's
|
|
79
|
+
* first-run day, its surface, and whether it ran in CI. `measuresOf` leaves it out on purpose: a rate over one event is no measure of the program, so the user's own report has no use for it, and the pool's fleet read skips it for the same reason. A word off
|
|
80
|
+
* the closed lists reads as `unknown` or not-CI, and the install is still counted.
|
|
81
|
+
*/
|
|
82
|
+
export const firstRunMeasures = (event) => {
|
|
83
|
+
if (fieldOf(event, 'type') !== 'first-run')
|
|
84
|
+
return [];
|
|
85
|
+
const data = fieldOf(event, 'data');
|
|
86
|
+
const surface = fieldOf(data, 'surface');
|
|
87
|
+
const ci = fieldOf(data, 'ci');
|
|
88
|
+
const known = typeof surface === 'string' && SURFACE_WORDS.some((word) => word === surface) ? surface : 'unknown';
|
|
89
|
+
const inCi = typeof ci === 'string' && ci !== 'none' && CI_WORDS.some((word) => word === ci);
|
|
90
|
+
return [rate(`first-run.${known}`, 'cli', valueOf(fieldOf(event, 'cli')), inCi)];
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* The cell of a `covenant-shown` event: one yes under the oars version, in a metric named for the covenant version the CLI told the operator to read (`covenant-shown.2026-11-10`), so the pool's rollup counts shows by version and
|
|
94
|
+
* day. Raw batches are swept at 90 days and a cell added later cannot count older shows, so the cell is kept from the first event. `measuresOf` leaves it out and the fleet read skips it (`isRecordOnlyMetric`): a count of shows is
|
|
95
|
+
* for the operator's query, not a measure of the program. A version that is not a word reads `other`, and a missing one `unrecorded`; the show is still counted.
|
|
96
|
+
*/
|
|
97
|
+
export const covenantShownMeasures = (event) => {
|
|
98
|
+
if (fieldOf(event, 'type') !== 'covenant-shown')
|
|
99
|
+
return [];
|
|
100
|
+
const version = valueOf(fieldOf(fieldOf(event, 'data'), 'version'));
|
|
101
|
+
return [rate(`covenant-shown.${version}`, 'cli', valueOf(fieldOf(event, 'cli')), true)];
|
|
102
|
+
};
|
|
103
|
+
/** The metrics the rollup records for the operator's queries and the fleet read never serves: a cell of one event or one count per install measures nothing across installs. */
|
|
104
|
+
const RECORD_ONLY_PREFIXES = ['first-run.', 'covenant-shown.'];
|
|
105
|
+
export const isRecordOnlyMetric = (metric) => RECORD_ONLY_PREFIXES.some((prefix) => metric.startsWith(prefix));
|
|
76
106
|
/** What one event measures; an event of a type nothing measures, or that is not an object, measures nothing. */
|
|
77
107
|
export const measuresOf = (event) => {
|
|
78
108
|
const type = fieldOf(event, 'type');
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import type { RunDeps } from '../cli.js';
|
|
3
3
|
import type { ExitCode } from '../errors.js';
|
|
4
|
+
import type { CardLineArm } from './schema.js';
|
|
5
|
+
import { type TelemetryRuntime } from './record.js';
|
|
4
6
|
export interface Observed {
|
|
5
7
|
/** The command path, such as `['pool', 'fetch']`; empty when no command ran (`--help`, `--version`, a usage error). */
|
|
6
8
|
path: string[];
|
|
7
9
|
/** The long names (as commander keys them) of the options given on the command line. */
|
|
8
10
|
flags: string[];
|
|
11
|
+
/** What the weekly card line did on this command (`sessions/card-line.ts`); absent when it was never asked. */
|
|
12
|
+
cardLine?: CardLineArm;
|
|
9
13
|
}
|
|
10
14
|
/** Called by commander just before an action runs. */
|
|
11
15
|
export declare const observeCommand: (observed: Observed, action: Command) => void;
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
export declare const isHook: (argv: string[], observed: Observed) => boolean;
|
|
17
|
+
/**
|
|
18
|
+
* 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.
|
|
19
|
+
* Never throws: `oars telemetry on`, which shows the notice itself, calls it outside `finishTelemetry`'s guard.
|
|
20
|
+
*/
|
|
21
|
+
export declare const recordFirstRun: (deps: RunDeps, runtime: TelemetryRuntime, installPath?: string) => void;
|
|
22
|
+
/**
|
|
23
|
+
* 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,
|
|
24
|
+
* and it is read inside the guard because it is needed on a first run only.
|
|
25
|
+
*/
|
|
26
|
+
export declare const finishTelemetry: (deps: RunDeps, argv: string[], observed: Observed, startedAt: number, exit: ExitCode, installPath?: string) => void;
|
|
@@ -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.1",
|
|
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",
|