@manyoars/oars 0.2.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +147 -28
- package/dist/api.js +1 -1
- package/dist/bin.js +6 -29
- package/dist/cli.d.ts +10 -4
- package/dist/cli.js +440 -91
- package/dist/commands/keys.d.ts +31 -0
- package/dist/commands/keys.js +65 -0
- package/dist/commands/login.d.ts +1 -1
- package/dist/commands/login.js +3 -3
- package/dist/errors.d.ts +13 -13
- package/dist/errors.js +28 -11
- package/dist/gate/checks.d.ts +68 -0
- package/dist/gate/checks.js +584 -0
- package/dist/gate/command.d.ts +14 -0
- package/dist/gate/command.js +61 -0
- package/dist/gate/hook-fill-main.d.ts +1 -0
- package/dist/gate/hook-fill-main.js +16 -0
- package/dist/gate/hook-fill.d.ts +38 -0
- package/dist/gate/hook-fill.js +94 -0
- package/dist/gate/hook-main.d.ts +1 -0
- package/dist/gate/hook-main.js +47 -0
- package/dist/gate/hook-process.d.ts +24 -0
- package/dist/gate/hook-process.js +112 -0
- package/dist/gate/hook-record.d.ts +3 -0
- package/dist/gate/hook-record.js +8 -0
- package/dist/gate/hook.d.ts +48 -0
- package/dist/gate/hook.js +259 -0
- package/dist/gate/install.d.ts +25 -0
- package/dist/gate/install.js +126 -0
- package/dist/gate/local-bin.d.ts +8 -0
- package/dist/gate/local-bin.js +36 -0
- package/dist/gate/notes.d.ts +19 -0
- package/dist/gate/notes.js +27 -0
- package/dist/gate/novelty.d.ts +35 -0
- package/dist/gate/novelty.js +99 -0
- package/dist/gate/policy.d.ts +25 -0
- package/dist/gate/policy.js +73 -0
- package/dist/gate/record.d.ts +120 -0
- package/dist/gate/record.js +125 -0
- package/dist/gate/registry-state.d.ts +10 -0
- package/dist/gate/registry-state.js +30 -0
- package/dist/gate/registry.d.ts +56 -0
- package/dist/gate/registry.js +94 -0
- package/dist/gate/replay.d.ts +20 -0
- package/dist/gate/replay.js +254 -0
- package/dist/gate/report.d.ts +50 -0
- package/dist/gate/report.js +89 -0
- package/dist/gate/scope.d.ts +7 -0
- package/dist/gate/scope.js +27 -0
- package/dist/gate/transcripts.d.ts +68 -0
- package/dist/gate/transcripts.js +232 -0
- package/dist/hook-bin.d.ts +1 -0
- package/dist/hook-bin.js +50 -0
- package/dist/issuer.d.ts +10 -6
- package/dist/issuer.js +19 -24
- package/dist/main.d.ts +1 -0
- package/dist/main.js +67 -0
- package/dist/oidc.js +1 -1
- package/dist/sessions/actions.d.ts +35 -0
- package/dist/sessions/actions.js +429 -0
- package/dist/sessions/allow.d.ts +23 -0
- package/dist/sessions/allow.js +71 -0
- package/dist/sessions/archive.d.ts +11 -0
- package/dist/sessions/archive.js +38 -0
- package/dist/sessions/audit.d.ts +42 -0
- package/dist/sessions/audit.js +104 -0
- package/dist/sessions/baseline.d.ts +62 -0
- package/dist/sessions/baseline.js +190 -0
- package/dist/sessions/call-shape.d.ts +19 -0
- package/dist/sessions/call-shape.js +85 -0
- package/dist/sessions/capture-runtime.d.ts +10 -0
- package/dist/sessions/capture-runtime.js +12 -0
- package/dist/sessions/cell-increments.d.ts +47 -0
- package/dist/sessions/cell-increments.js +243 -0
- package/dist/sessions/cells.d.ts +109 -0
- package/dist/sessions/cells.js +261 -0
- package/dist/sessions/codex.d.ts +53 -0
- package/dist/sessions/codex.js +745 -0
- package/dist/sessions/command.d.ts +127 -0
- package/dist/sessions/command.js +419 -0
- package/dist/sessions/copilot.d.ts +40 -0
- package/dist/sessions/copilot.js +432 -0
- package/dist/sessions/coverage.d.ts +19 -0
- package/dist/sessions/coverage.js +56 -0
- package/dist/sessions/cursor.d.ts +24 -0
- package/dist/sessions/cursor.js +324 -0
- package/dist/sessions/derive.d.ts +57 -0
- package/dist/sessions/derive.js +386 -0
- package/dist/sessions/exclusion.d.ts +87 -0
- package/dist/sessions/exclusion.js +361 -0
- package/dist/sessions/extract.d.ts +181 -0
- package/dist/sessions/extract.js +1806 -0
- package/dist/sessions/fold.d.ts +17 -0
- package/dist/sessions/fold.js +71 -0
- package/dist/sessions/frame.d.ts +24 -0
- package/dist/sessions/frame.js +95 -0
- package/dist/sessions/gemini.d.ts +37 -0
- package/dist/sessions/gemini.js +613 -0
- package/dist/sessions/gold-arms.d.ts +67 -0
- package/dist/sessions/gold-arms.js +172 -0
- package/dist/sessions/gold.d.ts +197 -0
- package/dist/sessions/gold.js +463 -0
- package/dist/sessions/graph.d.ts +12 -0
- package/dist/sessions/graph.js +34 -0
- package/dist/sessions/grok.d.ts +36 -0
- package/dist/sessions/grok.js +421 -0
- package/dist/sessions/hook-calls.d.ts +3 -0
- package/dist/sessions/hook-calls.js +47 -0
- package/dist/sessions/hook-index.d.ts +66 -0
- package/dist/sessions/hook-index.js +164 -0
- package/dist/sessions/hooks.d.ts +17 -0
- package/dist/sessions/hooks.js +281 -0
- package/dist/sessions/identity.d.ts +27 -0
- package/dist/sessions/identity.js +131 -0
- package/dist/sessions/install.d.ts +70 -0
- package/dist/sessions/install.js +246 -0
- package/dist/sessions/lineage.d.ts +19 -0
- package/dist/sessions/lineage.js +118 -0
- package/dist/sessions/mcp.d.ts +7 -0
- package/dist/sessions/mcp.js +157 -0
- package/dist/sessions/merkle.d.ts +7 -0
- package/dist/sessions/merkle.js +28 -0
- package/dist/sessions/motif.d.ts +44 -0
- package/dist/sessions/motif.js +59 -0
- package/dist/sessions/opencode.d.ts +33 -0
- package/dist/sessions/opencode.js +319 -0
- package/dist/sessions/otel.d.ts +60 -0
- package/dist/sessions/otel.js +494 -0
- package/dist/sessions/package-books.d.ts +48 -0
- package/dist/sessions/package-books.js +120 -0
- package/dist/sessions/package-resolve.d.ts +32 -0
- package/dist/sessions/package-resolve.js +185 -0
- package/dist/sessions/pending.d.ts +34 -0
- package/dist/sessions/pending.js +142 -0
- package/dist/sessions/pi.d.ts +36 -0
- package/dist/sessions/pi.js +353 -0
- package/dist/sessions/pool-books.d.ts +21 -0
- package/dist/sessions/pool-books.js +75 -0
- package/dist/sessions/pool-choices.d.ts +5 -0
- package/dist/sessions/pool-choices.js +11 -0
- package/dist/sessions/pool-command.d.ts +115 -0
- package/dist/sessions/pool-command.js +669 -0
- package/dist/sessions/pool-delta.d.ts +54 -0
- package/dist/sessions/pool-delta.js +190 -0
- package/dist/sessions/pool-nightly-deps.d.ts +3 -0
- package/dist/sessions/pool-nightly-deps.js +11 -0
- package/dist/sessions/pool-nightly.d.ts +10 -0
- package/dist/sessions/pool-nightly.js +85 -0
- package/dist/sessions/pool-outbox.d.ts +76 -0
- package/dist/sessions/pool-outbox.js +313 -0
- package/dist/sessions/pool-print.d.ts +26 -0
- package/dist/sessions/pool-print.js +68 -0
- package/dist/sessions/pool-raw.d.ts +27 -0
- package/dist/sessions/pool-raw.js +68 -0
- package/dist/sessions/pool-replica.d.ts +35 -0
- package/dist/sessions/pool-replica.js +91 -0
- package/dist/sessions/pool-send-lock.d.ts +20 -0
- package/dist/sessions/pool-send-lock.js +87 -0
- package/dist/sessions/pool-sent.d.ts +4 -0
- package/dist/sessions/pool-sent.js +26 -0
- package/dist/sessions/pool-totals.d.ts +10 -0
- package/dist/sessions/pool-totals.js +21 -0
- package/dist/sessions/pool-wire.d.ts +435 -0
- package/dist/sessions/pool-wire.js +1100 -0
- package/dist/sessions/pool.d.ts +32 -0
- package/dist/sessions/pool.js +164 -0
- package/dist/sessions/projection.d.ts +75 -0
- package/dist/sessions/projection.js +165 -0
- package/dist/sessions/public-vocabulary.d.ts +72 -0
- package/dist/sessions/public-vocabulary.js +295 -0
- package/dist/sessions/query.d.ts +123 -0
- package/dist/sessions/query.js +268 -0
- package/dist/sessions/read-adoption.d.ts +2 -0
- package/dist/sessions/read-adoption.js +27 -0
- package/dist/sessions/read-call-shape.d.ts +2 -0
- package/dist/sessions/read-call-shape.js +25 -0
- package/dist/sessions/read-change.d.ts +2 -0
- package/dist/sessions/read-change.js +30 -0
- package/dist/sessions/read-cost.d.ts +4 -0
- package/dist/sessions/read-cost.js +48 -0
- package/dist/sessions/read-day-move.d.ts +3 -0
- package/dist/sessions/read-day-move.js +65 -0
- package/dist/sessions/read-fit.d.ts +7 -0
- package/dist/sessions/read-fit.js +41 -0
- package/dist/sessions/read-interaction.d.ts +5 -0
- package/dist/sessions/read-interaction.js +62 -0
- package/dist/sessions/read-lead-lag.d.ts +15 -0
- package/dist/sessions/read-lead-lag.js +81 -0
- package/dist/sessions/read-live.d.ts +6 -0
- package/dist/sessions/read-live.js +38 -0
- package/dist/sessions/read-method.d.ts +2 -0
- package/dist/sessions/read-method.js +29 -0
- package/dist/sessions/read-next-step.d.ts +2 -0
- package/dist/sessions/read-next-step.js +35 -0
- package/dist/sessions/read-order.d.ts +54 -0
- package/dist/sessions/read-order.js +62 -0
- package/dist/sessions/read-readable.d.ts +8 -0
- package/dist/sessions/read-readable.js +24 -0
- package/dist/sessions/read-recovery-edge.d.ts +2 -0
- package/dist/sessions/read-recovery-edge.js +26 -0
- package/dist/sessions/read-recovery.d.ts +20 -0
- package/dist/sessions/read-recovery.js +97 -0
- package/dist/sessions/read-rows.d.ts +13 -0
- package/dist/sessions/read-rows.js +47 -0
- package/dist/sessions/read-substitution.d.ts +5 -0
- package/dist/sessions/read-substitution.js +51 -0
- package/dist/sessions/read-support.d.ts +48 -0
- package/dist/sessions/read-support.js +73 -0
- package/dist/sessions/read-tool-day.d.ts +2 -0
- package/dist/sessions/read-tool-day.js +26 -0
- package/dist/sessions/read-version-boundary.d.ts +31 -0
- package/dist/sessions/read-version-boundary.js +95 -0
- package/dist/sessions/read.d.ts +86 -0
- package/dist/sessions/read.js +87 -0
- package/dist/sessions/recall-bundle.d.ts +12 -0
- package/dist/sessions/recall-bundle.js +33 -0
- package/dist/sessions/recall-consequence.d.ts +45 -0
- package/dist/sessions/recall-consequence.js +179 -0
- package/dist/sessions/recall-frames.d.ts +18 -0
- package/dist/sessions/recall-frames.js +44 -0
- package/dist/sessions/recall-next.d.ts +145 -0
- package/dist/sessions/recall-next.js +241 -0
- package/dist/sessions/recall-object.d.ts +139 -0
- package/dist/sessions/recall-object.js +78 -0
- package/dist/sessions/recall-pool.d.ts +19 -0
- package/dist/sessions/recall-pool.js +96 -0
- package/dist/sessions/recall-push.d.ts +26 -0
- package/dist/sessions/recall-push.js +47 -0
- package/dist/sessions/recall-reads.d.ts +124 -0
- package/dist/sessions/recall-reads.js +417 -0
- package/dist/sessions/recall-render.d.ts +19 -0
- package/dist/sessions/recall-render.js +168 -0
- package/dist/sessions/recovery-calls.d.ts +21 -0
- package/dist/sessions/recovery-calls.js +25 -0
- package/dist/sessions/redact.d.ts +17 -0
- package/dist/sessions/redact.js +113 -0
- package/dist/sessions/repo-root.d.ts +10 -0
- package/dist/sessions/repo-root.js +101 -0
- package/dist/sessions/schema.d.ts +694 -0
- package/dist/sessions/schema.js +167 -0
- package/dist/sessions/session-shape.d.ts +76 -0
- package/dist/sessions/session-shape.js +355 -0
- package/dist/sessions/sources.d.ts +50 -0
- package/dist/sessions/sources.js +233 -0
- package/dist/sessions/steps.d.ts +105 -0
- package/dist/sessions/steps.js +250 -0
- package/dist/sessions/store.d.ts +309 -0
- package/dist/sessions/store.js +1498 -0
- package/dist/sessions/strata.d.ts +26 -0
- package/dist/sessions/strata.js +77 -0
- package/dist/sessions/survival.d.ts +27 -0
- package/dist/sessions/survival.js +230 -0
- package/dist/sessions/transcript-tail.d.ts +14 -0
- package/dist/sessions/transcript-tail.js +64 -0
- package/dist/sessions/verify.d.ts +53 -0
- package/dist/sessions/verify.js +286 -0
- package/dist/sessions/vocab-load.d.ts +26 -0
- package/dist/sessions/vocab-load.js +61 -0
- package/dist/sessions/vscode-copilot.d.ts +38 -0
- package/dist/sessions/vscode-copilot.js +494 -0
- package/dist/telemetry/command.d.ts +8 -0
- package/dist/telemetry/command.js +85 -0
- package/dist/telemetry/consent.d.ts +17 -0
- package/dist/telemetry/consent.js +63 -0
- package/dist/telemetry/crash.d.ts +8 -0
- package/dist/telemetry/crash.js +52 -0
- package/dist/telemetry/emit.d.ts +31 -0
- package/dist/telemetry/emit.js +42 -0
- package/dist/telemetry/fields.d.ts +16 -0
- package/dist/telemetry/fields.js +26 -0
- package/dist/telemetry/health.d.ts +21 -0
- package/dist/telemetry/health.js +108 -0
- package/dist/telemetry/hook-emit.d.ts +4 -0
- package/dist/telemetry/hook-emit.js +18 -0
- package/dist/telemetry/measures.d.ts +32 -0
- package/dist/telemetry/measures.js +128 -0
- package/dist/telemetry/observe.d.ts +13 -0
- package/dist/telemetry/observe.js +51 -0
- package/dist/telemetry/read-events.d.ts +9 -0
- package/dist/telemetry/read-events.js +17 -0
- package/dist/telemetry/record.d.ts +17 -0
- package/dist/telemetry/record.js +26 -0
- package/dist/telemetry/report.d.ts +77 -0
- package/dist/telemetry/report.js +188 -0
- package/dist/telemetry/schema.d.ts +40 -0
- package/dist/telemetry/schema.js +187 -0
- package/dist/telemetry/send-main.d.ts +1 -0
- package/dist/telemetry/send-main.js +11 -0
- package/dist/telemetry/send.d.ts +36 -0
- package/dist/telemetry/send.js +172 -0
- package/dist/telemetry/spool.d.ts +17 -0
- package/dist/telemetry/spool.js +70 -0
- package/dist/telemetry/wire.d.ts +25 -0
- package/dist/telemetry/wire.js +76 -0
- package/package.json +4 -5
- package/vocab/crates.json +1 -0
- package/vocab/go.json +1 -0
- package/vocab/harness.json +1 -0
- package/vocab/homebrew.json +1 -0
- package/vocab/manifest.json +1 -0
- package/vocab/maven.json +1 -0
- package/vocab/models.json +1 -0
- package/vocab/npm.json +1 -0
- package/vocab/programs.json +1 -0
- package/vocab/pypi.json +1 -0
- package/vocab/rubygems.json +1 -0
- package/dist/commands/deploy-input.d.ts +0 -38
- package/dist/commands/deploy-input.js +0 -150
- package/dist/commands/deploy.d.ts +0 -22
- package/dist/commands/deploy.js +0 -124
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.js +0 -18
- package/dist/commands/templates.d.ts +0 -11
- package/dist/commands/templates.js +0 -32
- package/dist/commands/test-charge.d.ts +0 -11
- package/dist/commands/test-charge.js +0 -34
- package/dist/journal.d.ts +0 -26
- package/dist/journal.js +0 -58
- package/parity/deploy-plans/paid-api.json +0 -34
- package/parity/deploy-plans/per-task-agent.json +0 -34
package/dist/cli.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { Command, CommanderError, InvalidArgumentError } from 'commander';
|
|
3
3
|
import { resolveApiUrl } from './api.js';
|
|
4
|
-
import {
|
|
4
|
+
import { keysCreate, keysList, keysRevoke, SCOPED_AUTHORITIES } from './commands/keys.js';
|
|
5
5
|
import { login } from './commands/login.js';
|
|
6
6
|
import { logout } from './commands/logout.js';
|
|
7
|
-
import { status } from './commands/status.js';
|
|
8
|
-
import { templates } from './commands/templates.js';
|
|
9
|
-
import { testCharge } from './commands/test-charge.js';
|
|
10
7
|
import { CliError, ExitCode } from './errors.js';
|
|
8
|
+
import { closedQueryOf, formsRefusal, RECALL_FAMILIES } from './sessions/recall-reads.js';
|
|
9
|
+
import { FAMILIES } from './sessions/read.js';
|
|
10
|
+
import { sessionsBooks, sessionsContext, sessionsDeposit, sessionsGoldArms, sessionsGoldSample, sessionsGoldScore, sessionsHookInstall, sessionsHookStatus, sessionsHookUninstall, sessionsMcp, sessionsLineage, sessionsMeasure, sessionsRead, sessionsRecall, sessionsRecallCounts, sessionsVerify } from './sessions/command.js';
|
|
11
|
+
import { gateInstall, gatePolicy, gateReplay, gateReport, gateUninstall } from './gate/command.js';
|
|
12
|
+
import { DEADLINE_MS, gateHook } from './gate/hook.js';
|
|
13
|
+
import { managerReader } from './gate/registry.js';
|
|
14
|
+
import { exclusionPaths, sessionsExclusions } from './sessions/exclusion.js';
|
|
15
|
+
import { poolCoverage, poolDeposit, poolFetch, poolLogin, poolOutbox, poolRaw } from './sessions/pool-command.js';
|
|
16
|
+
import { telemetryOff, telemetryOn, telemetryReport, telemetryShow, telemetryStatus } from './telemetry/command.js';
|
|
17
|
+
import { recordCrash } from './telemetry/crash.js';
|
|
18
|
+
import { emitOf } from './telemetry/emit.js';
|
|
19
|
+
import { finishTelemetry, observeCommand } from './telemetry/observe.js';
|
|
11
20
|
const flag = (options, name) => options[name] === true;
|
|
12
21
|
const text = (options, name) => {
|
|
13
22
|
const value = options[name];
|
|
@@ -28,50 +37,46 @@ const positiveInteger = (value) => {
|
|
|
28
37
|
throw new InvalidArgumentError('it must be a positive whole number.');
|
|
29
38
|
return Number(value);
|
|
30
39
|
};
|
|
40
|
+
/** An ISO time, normalized to the form session timestamps are stored in so the two compare as text. */
|
|
41
|
+
const isoTime = (value) => {
|
|
42
|
+
const parsed = Date.parse(value);
|
|
43
|
+
if (Number.isNaN(parsed))
|
|
44
|
+
throw new InvalidArgumentError('it must be an ISO time such as 2026-11-01T16:00:00Z.');
|
|
45
|
+
return new Date(parsed).toISOString();
|
|
46
|
+
};
|
|
31
47
|
const emit = (deps, json, outcome) => {
|
|
32
48
|
deps.io.stdout(`${json ? JSON.stringify(outcome.document) : outcome.text}\n`);
|
|
33
49
|
};
|
|
34
|
-
/** The last line of an exit 6: the one thing a person must do, by link when the platform sent one, then to rerun. Stripe onboarding is the step most runs wait on. */
|
|
35
|
-
const needsYouLine = (code, actionUrl) => {
|
|
36
|
-
const stripe = code === 'ACCOUNT_NOT_READY';
|
|
37
|
-
if (actionUrl !== undefined && actionUrl !== '') {
|
|
38
|
-
return `Needs you: ${stripe ? 'finish Stripe onboarding at' : 'open'} ${actionUrl}, then rerun this command.`;
|
|
39
|
-
}
|
|
40
|
-
return stripe
|
|
41
|
-
? 'Needs you: finish Stripe onboarding (Account, then Payout account, in the Many Oars web app), then rerun this command.'
|
|
42
|
-
: 'Needs you: a step only a person can do. Do it in the Many Oars web app, then rerun this command.';
|
|
43
|
-
};
|
|
44
50
|
/**
|
|
45
|
-
* `--json` prints `{ok: false, step, problem}` on stdout, plus
|
|
46
|
-
*
|
|
51
|
+
* `--json` prints `{ok: false, step, problem}` on stdout, plus the error's details; otherwise the problem's title, detail,
|
|
52
|
+
* and URL go to stderr.
|
|
47
53
|
*/
|
|
48
54
|
const printFailure = (deps, json, error) => {
|
|
49
|
-
const { partial } = error;
|
|
50
55
|
if (json) {
|
|
51
|
-
deps.io.stdout(`${JSON.stringify({ ok: false, step: error.step, problem: error.problem, ...
|
|
56
|
+
deps.io.stdout(`${JSON.stringify({ ok: false, step: error.step, problem: error.problem, ...error.details })}\n`);
|
|
52
57
|
return;
|
|
53
58
|
}
|
|
54
59
|
const { title, detail, actionUrl } = error.problem;
|
|
55
|
-
|
|
56
|
-
const needsYou = error.exitCode === ExitCode.HUMAN ? [needsYouLine(error.problem.code, actionUrl)] : [];
|
|
57
|
-
deps.io.stderr(`${[title ?? error.message, detail, ...(needsYou.length === 0 ? [actionUrl] : []), ...resume, ...needsYou].filter((line) => line !== undefined && line !== '').join('\n')}\n`);
|
|
58
|
-
if (partial?.sdkKey !== undefined)
|
|
59
|
-
deps.io.stdout(`SDK key (shown once; save it now): ${partial.sdkKey}\n`);
|
|
60
|
+
deps.io.stderr(`${[title ?? error.message, detail, actionUrl].filter((line) => line !== undefined && line !== '').join('\n')}\n`);
|
|
60
61
|
};
|
|
61
|
-
const
|
|
62
|
+
const sessionDeps = (deps) => ({ sessions: deps.sessions, install: deps.install, now: deps.now, telemetry: emitOf(deps) });
|
|
63
|
+
const buildProgram = (deps, observed) => {
|
|
62
64
|
const program = new Command('oars')
|
|
63
|
-
.description('
|
|
65
|
+
.description('Many Oars: sign in, manage API keys, and keep a local, searchable record of your own coding-agent sessions')
|
|
64
66
|
.version(deps.version)
|
|
65
67
|
.exitOverride()
|
|
66
68
|
.configureOutput({ writeOut: deps.io.stdout, writeErr: deps.io.stderr })
|
|
67
69
|
.option('--json', 'print one JSON document on stdout instead of text')
|
|
68
70
|
.option('--api-url <url>', 'the platform API (default: OARS_API_URL, else https://api.manyoars.com)')
|
|
69
|
-
.option('--idempotency-key <key>', "the base of
|
|
71
|
+
.option('--idempotency-key <key>', "the base of the idempotency key (default: a fresh UUID); rerun a failed command with the same key to replay it")
|
|
72
|
+
.hook('preAction', (_program, action) => {
|
|
73
|
+
observeCommand(observed, action);
|
|
74
|
+
});
|
|
70
75
|
program
|
|
71
76
|
.command('login')
|
|
72
77
|
.description('sign in with your browser (RFC 8628 device flow)')
|
|
73
78
|
.option('--no-browser', 'print the link and code without opening a browser')
|
|
74
|
-
.option('--issuer-url <url>', 'the Keycloak realm URL (default: OARS_ISSUER_URL, else Many Oars
|
|
79
|
+
.option('--issuer-url <url>', 'the Keycloak realm URL (default: OARS_ISSUER_URL, else the Many Oars issuer)')
|
|
75
80
|
.action(async (_options, command) => {
|
|
76
81
|
const options = command.optsWithGlobals();
|
|
77
82
|
const outcome = await login(deps, {
|
|
@@ -88,77 +93,411 @@ const buildProgram = (deps, argv) => {
|
|
|
88
93
|
const options = command.optsWithGlobals();
|
|
89
94
|
emit(deps, flag(options, 'json'), await logout(deps));
|
|
90
95
|
});
|
|
91
|
-
program
|
|
92
|
-
|
|
93
|
-
.
|
|
96
|
+
const keys = program.command('keys').description('create, list, and revoke your API keys; a key made with --workload works on that one workload only');
|
|
97
|
+
keys
|
|
98
|
+
.command('create')
|
|
99
|
+
.description('create an API key and print its secret once; --workload limits it to one workload you created')
|
|
100
|
+
.requiredOption('--name <name>', 'a name to recognize the key by')
|
|
101
|
+
.option('--workload <id>', 'limit the key to this workload (it may then carry only AGENT_OPERATE and AGENT_READ)')
|
|
102
|
+
.option('--authority <name>', 'an authority for the key (repeatable; default AGENT_OPERATE and AGENT_READ)', collect)
|
|
103
|
+
.option('--expires-in-days <n>', 'days until the key expires', positiveInteger)
|
|
94
104
|
.action(async (_options, command) => {
|
|
95
105
|
const options = command.optsWithGlobals();
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
program
|
|
99
|
-
.command('deploy')
|
|
100
|
-
.description('register a workload, create its prices and deploy it; stops at the first failed step (exit 9)')
|
|
101
|
-
.option('--template <id>', 'a starter template from the catalog (see: oars templates)')
|
|
102
|
-
.option('--image <ref>', 'an image pinned by digest (name@sha256:...)')
|
|
103
|
-
.option('--manifest <file>', 'a JSON workload manifest')
|
|
104
|
-
.option('--name <name>', 'the workload name (--image)')
|
|
105
|
-
.option('--port <n>', 'the service port (--image; default 8080)', positiveInteger)
|
|
106
|
-
.option('--public', 'expose the service publicly (--image)')
|
|
107
|
-
.option('--cpu-millicores <n>', 'CPU in millicores (--image; default 250)', positiveInteger)
|
|
108
|
-
.option('--memory-mib <n>', 'memory in MiB (--image; default 256)', positiveInteger)
|
|
109
|
-
.option('--always-on', 'keep the public service running instead of idling')
|
|
110
|
-
.option('--price <ref>', 'a starter price to create instead of all of them (--template; repeatable)', collect)
|
|
111
|
-
.option('--no-prices', "create none of the template's starter prices (--template)")
|
|
112
|
-
.option('--price-file <file>', 'a JSON array of prices to create (--image, --manifest)')
|
|
113
|
-
.option('--secret <NAME=value>', 'a secret for the workload (repeatable); prefer --secret-from-env', collect)
|
|
114
|
-
.option('--secret-from-env <NAME>', 'a secret read from the environment variable NAME (repeatable)', collect)
|
|
115
|
-
.option('--no-start', 'register and price the workload without deploying it')
|
|
116
|
-
.action(async (_options, command) => {
|
|
117
|
-
const options = command.optsWithGlobals();
|
|
118
|
-
const outcome = await deploy(deps, {
|
|
106
|
+
const authorities = list(options, 'authority');
|
|
107
|
+
const outcome = await keysCreate(deps, {
|
|
119
108
|
apiUrl: resolveApiUrl(deps, text(options, 'apiUrl')),
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
idempotencyKey: text(options, 'idempotencyKey'),
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
manifestFile: text(options, 'manifest'),
|
|
126
|
-
name: text(options, 'name'),
|
|
127
|
-
port: count(options, 'port'),
|
|
128
|
-
public: flag(options, 'public') ? true : undefined,
|
|
129
|
-
cpuMillicores: count(options, 'cpuMillicores'),
|
|
130
|
-
memoryMib: count(options, 'memoryMib'),
|
|
131
|
-
alwaysOn: flag(options, 'alwaysOn') ? true : undefined,
|
|
132
|
-
prices: list(options, 'price'),
|
|
133
|
-
noPrices: options.prices === false,
|
|
134
|
-
priceFile: text(options, 'priceFile'),
|
|
135
|
-
secrets: list(options, 'secret'),
|
|
136
|
-
secretsFromEnv: list(options, 'secretFromEnv'),
|
|
109
|
+
name: text(options, 'name') ?? '',
|
|
110
|
+
authorities: authorities.length === 0 ? SCOPED_AUTHORITIES : authorities,
|
|
111
|
+
idempotencyKey: text(options, 'idempotencyKey') ?? randomUUID(),
|
|
112
|
+
...(text(options, 'workload') === undefined ? {} : { workloadId: text(options, 'workload') }),
|
|
113
|
+
...(count(options, 'expiresInDays') === undefined ? {} : { expiresInDays: count(options, 'expiresInDays') }),
|
|
137
114
|
});
|
|
138
115
|
emit(deps, flag(options, 'json'), outcome);
|
|
139
116
|
});
|
|
140
|
-
|
|
117
|
+
keys
|
|
118
|
+
.command('list')
|
|
119
|
+
.description('list your API keys (the secrets are never shown again)')
|
|
120
|
+
.action(async (_options, command) => {
|
|
121
|
+
const options = command.optsWithGlobals();
|
|
122
|
+
emit(deps, flag(options, 'json'), await keysList(deps, { apiUrl: resolveApiUrl(deps, text(options, 'apiUrl')) }));
|
|
123
|
+
});
|
|
124
|
+
keys
|
|
125
|
+
.command('revoke')
|
|
126
|
+
.description('revoke an API key at once')
|
|
127
|
+
.argument('<keyId>', 'the key id that `keys list` prints')
|
|
128
|
+
.action(async (keyId, _options, command) => {
|
|
129
|
+
const options = command.optsWithGlobals();
|
|
130
|
+
emit(deps, flag(options, 'json'), await keysRevoke(deps, { apiUrl: resolveApiUrl(deps, text(options, 'apiUrl')), keyId }));
|
|
131
|
+
});
|
|
132
|
+
const sessions = program.command('sessions').description("deposit and recall your own coding-agent session logs, locally (these commands make no model call and no network call, except `gold arms`, which asks your own Claude subscription; telemetry is separate: oars telemetry)");
|
|
133
|
+
sessions
|
|
134
|
+
.command('deposit')
|
|
135
|
+
.description('read session logs into the local observation log, from every folder unless --allow names some or --deny keeps some out; idempotent. Subagent transcripts under each session (<project>/<session>/subagents/) are deposited with their parent, and each raw transcript is hard-linked or copied into the oars archive beside the store')
|
|
136
|
+
.option('--allow <folder>', 'a project folder to read: its path, or its name under ~/.claude/projects (repeatable); `cursor`, `codex`, `copilot`, `gemini`, `grok`, `opencode`, `pi`, `vscode-copilot`, and `otel` name the Cursor, Codex, Copilot CLI, Gemini CLI, Grok Build, OpenCode, Pi, VS Code Copilot Chat, and OpenTelemetry GenAI trace stores. A trailing slash names everything under a folder; * and ? are wildcards', collect)
|
|
137
|
+
.option('--allow-file <list>', 'a file of --allow entries, one per line (# starts a comment, and !<entry> keeps a folder out); repeatable, combines with --allow', collect)
|
|
138
|
+
.option('--deny <folder>', 'a folder never to read, in any form --allow takes (repeatable); saved, and it wins over every --allow until that exact entry is named with --allow', collect)
|
|
139
|
+
.option('--redact-domain <domain>', 'an organization domain to mask in everything written (repeatable)', collect)
|
|
140
|
+
.action(async (_options, command) => {
|
|
141
|
+
const options = command.optsWithGlobals();
|
|
142
|
+
emit(deps, flag(options, 'json'), await sessionsDeposit(sessionDeps(deps), { allow: list(options, 'allow'), allowFiles: list(options, 'allowFile'), deny: list(options, 'deny'), redactDomains: list(options, 'redactDomain') }));
|
|
143
|
+
});
|
|
144
|
+
sessions
|
|
145
|
+
.command('recall')
|
|
146
|
+
.description('look a tool error, a file, or a prompt class up in the store (--mcp serves the same query to a running agent). Note: an error pasted with a leading <tool_use_error> line does not match a stored signature, because the extractor keeps that markup line as the signature (reference-script behavior); paste the line below it')
|
|
147
|
+
.option('--error <text>', 'an error as printed; matched by its normalized signature')
|
|
148
|
+
.option('--path <file>', 'a file path or name that earlier sessions touched')
|
|
149
|
+
.option('--class <name>', 'a prompt class (edit-verify, edit-only, shell-only, research, orchestration, explore-only, mixed, no-tools)')
|
|
150
|
+
.option('--since <days>', 'only the last N days', positiveInteger)
|
|
151
|
+
.option('--pattern <id>', 'what happened from here: a read ID for a step so far (repeatable)', collect)
|
|
152
|
+
.option('--entity <name>', 'what is known about a public name: a package, a tool, an MCP server, an action, or a model')
|
|
153
|
+
.option('--family <name>', `one read family (${RECALL_FAMILIES.join(', ')}); only recovery and fit are narrowed by --class and --since`)
|
|
154
|
+
.option('--text <text>', 'look for text in the local record')
|
|
155
|
+
.option('--session <id>', 'the session asking, so what it did next can be read for this recall')
|
|
156
|
+
.option('--counts', 'print counts of recalls by mode, arm, and answer, with what followed, instead of answering (see --day)')
|
|
157
|
+
.option('--day <yyyy-mm-dd>', 'with --counts: only this UTC day')
|
|
158
|
+
.option('--mcp', 'serve one recall tool over MCP on stdio instead of answering once')
|
|
159
|
+
.option('--context', "print the repository's top recurring errors as a short block for injection into a session (see --cwd, --harness, --top, --max-chars)")
|
|
160
|
+
.option('--cwd <dir>', "with --context: the session's working directory (default: this one)")
|
|
161
|
+
.option('--harness <version>', 'with --context: only errors seen under this Claude Code version')
|
|
162
|
+
.option('--top <n>', 'with --context: how many signatures (default 5)', positiveInteger)
|
|
163
|
+
.option('--max-chars <n>', "with --context: the block's length limit (default 1200)", positiveInteger)
|
|
164
|
+
.action(async (_options, command) => {
|
|
165
|
+
const options = command.optsWithGlobals();
|
|
166
|
+
if (flag(options, 'counts')) {
|
|
167
|
+
emit(deps, flag(options, 'json'), await sessionsRecallCounts(sessionDeps(deps), text(options, 'day')));
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (flag(options, 'context')) {
|
|
171
|
+
emit(deps, flag(options, 'json'), await sessionsContext(sessionDeps(deps), {
|
|
172
|
+
cwd: text(options, 'cwd') ?? process.cwd(),
|
|
173
|
+
...(text(options, 'harness') === undefined ? {} : { harness: text(options, 'harness') }),
|
|
174
|
+
top: count(options, 'top') ?? 5,
|
|
175
|
+
maxChars: count(options, 'maxChars') ?? 1200,
|
|
176
|
+
}));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (flag(options, 'mcp')) {
|
|
180
|
+
await sessionsMcp(sessionDeps(deps));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const session = text(options, 'session') ?? null;
|
|
184
|
+
const conflict = formsRefusal([
|
|
185
|
+
...(list(options, 'pattern').length > 0 ? ['prefix'] : []),
|
|
186
|
+
...(text(options, 'entity') === undefined ? [] : ['entity']),
|
|
187
|
+
...(text(options, 'family') === undefined ? [] : ['family']),
|
|
188
|
+
...(text(options, 'text') === undefined ? [] : ['text']),
|
|
189
|
+
...(text(options, 'error') === undefined && text(options, 'path') === undefined ? [] : ['error or path']),
|
|
190
|
+
]);
|
|
191
|
+
if (conflict !== undefined)
|
|
192
|
+
throw new CliError(conflict, ExitCode.USAGE, 'usage');
|
|
193
|
+
const closed = closedQueryOf({ pattern: list(options, 'pattern'), entity: text(options, 'entity'), family: text(options, 'family'), text: text(options, 'text'), cls: text(options, 'class'), sinceDays: count(options, 'since') });
|
|
194
|
+
if (closed !== undefined) {
|
|
195
|
+
emit(deps, flag(options, 'json'), await sessionsRecall(sessionDeps(deps), closed, session));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const query = {
|
|
199
|
+
...(text(options, 'error') === undefined ? {} : { error: text(options, 'error') }),
|
|
200
|
+
...(text(options, 'path') === undefined ? {} : { path: text(options, 'path') }),
|
|
201
|
+
...(text(options, 'class') === undefined ? {} : { cls: text(options, 'class') }),
|
|
202
|
+
...(count(options, 'since') === undefined ? {} : { sinceDays: count(options, 'since') }),
|
|
203
|
+
};
|
|
204
|
+
emit(deps, flag(options, 'json'), await sessionsRecall(sessionDeps(deps), query, session));
|
|
205
|
+
});
|
|
206
|
+
sessions
|
|
207
|
+
.command('lineage <id>')
|
|
208
|
+
.description('walk a recall or read back through its evidence, one line per hop with its frame range and day; refuses evidence newer than the recall')
|
|
209
|
+
.action(async (id, _options, command) => {
|
|
210
|
+
emit(deps, flag(command.optsWithGlobals(), 'json'), await sessionsLineage(sessionDeps(deps), id));
|
|
211
|
+
});
|
|
212
|
+
sessions
|
|
213
|
+
.command('read')
|
|
214
|
+
.description('derived reads of your own sessions at every order, each row stating its order and clusters: rates at order 1, relations between them at order 2, one labeled experiment at order 3, and what this store cannot read')
|
|
215
|
+
.option('--family <name>', `only this family: ${FAMILIES.join(', ')}`)
|
|
216
|
+
.option('--slice <axis=value>', 'only reads holding this value on this axis, such as model=claude-opus-5-5 (repeatable; all must match)', collect)
|
|
217
|
+
.action(async (_options, command) => {
|
|
218
|
+
const options = command.optsWithGlobals();
|
|
219
|
+
emit(deps, flag(options, 'json'), await sessionsRead(sessionDeps(deps), { family: text(options, 'family'), slices: list(options, 'slice') }));
|
|
220
|
+
});
|
|
221
|
+
sessions
|
|
222
|
+
.command('books')
|
|
223
|
+
.description('tokens, wall time, turns, errors, verify-after-edit rate, and the outcome signals (verify passed, stops, redos, commits, and commit survival) per prompt class, model, harness, or pull; with --by package, the public-package counts a field report prints, exclusions applied')
|
|
224
|
+
.option('--by <key>', 'class, model, harness, pull, or package (default class)', (value) => {
|
|
225
|
+
if (value !== 'class' && value !== 'model' && value !== 'harness' && value !== 'pull' && value !== 'package')
|
|
226
|
+
throw new InvalidArgumentError('it must be class, model, harness, pull, or package.');
|
|
227
|
+
return value;
|
|
228
|
+
})
|
|
229
|
+
.option('--since <days>', 'only the last N days', positiveInteger)
|
|
230
|
+
.option('--repo <root>', "count only this repository's sessions (worktrees and subfolders included); without it, the whole store")
|
|
231
|
+
.action(async (_options, command) => {
|
|
232
|
+
const options = command.optsWithGlobals();
|
|
233
|
+
const picked = text(options, 'by');
|
|
234
|
+
const by = picked === 'model' || picked === 'harness' || picked === 'pull' || picked === 'package' ? picked : 'class';
|
|
235
|
+
const termsFile = deps.sessions === undefined ? undefined : exclusionPaths(deps.sessions.projectsRoot, deps.env).terms;
|
|
236
|
+
emit(deps, flag(options, 'json'), await sessionsBooks(sessionDeps(deps), { by, sinceDays: count(options, 'since'), repo: text(options, 'repo'), termsFile }));
|
|
237
|
+
});
|
|
238
|
+
sessions
|
|
239
|
+
.command('measure')
|
|
240
|
+
.description('the G1 signals for the last N sessions and the N before, and the pipeline step counters over the whole log')
|
|
241
|
+
.option('--last <n>', 'sessions per window (default 50)', positiveInteger)
|
|
242
|
+
.option('--since <iso>', 'pin the current window to the first N sessions started at or after this ISO time (the prior window is the N before it); without it the window is the last N and moves with every deposit', isoTime)
|
|
243
|
+
.option('--repo <root>', "count only this repository's sessions (worktrees and subfolders included); without it, the whole store")
|
|
244
|
+
.action(async (_options, command) => {
|
|
245
|
+
const options = command.optsWithGlobals();
|
|
246
|
+
emit(deps, flag(options, 'json'), await sessionsMeasure(sessionDeps(deps), { last: count(options, 'last') ?? 50, since: text(options, 'since'), repo: text(options, 'repo') }));
|
|
247
|
+
});
|
|
248
|
+
sessions
|
|
249
|
+
.command('verify')
|
|
250
|
+
.description('run the consistency invariants on the stored skeletons and capture records, report every session on its own, and audit each against its raw file (Merkle root, counts)')
|
|
251
|
+
.option('--session <id>', 'one session instead of all')
|
|
252
|
+
.option('--strict', 'exit non-zero (4) when any invariant has exceptions, a step is stuck, or the raw audit finds a root mismatch or counts that do not bind; by default FAIL lines still exit 0')
|
|
253
|
+
.action(async (_options, command) => {
|
|
254
|
+
const options = command.optsWithGlobals();
|
|
255
|
+
const { failed, report, ...outcome } = await sessionsVerify(sessionDeps(deps), { session: text(options, 'session') });
|
|
256
|
+
const json = flag(options, 'json');
|
|
257
|
+
if (flag(options, 'strict') && failed.length > 0) {
|
|
258
|
+
// One document under --json: the failure carries the invariants, so the success document is not printed first.
|
|
259
|
+
if (!json)
|
|
260
|
+
emit(deps, json, outcome);
|
|
261
|
+
throw new CliError(`${String(failed.length)} ${failed.length === 1 ? 'check' : 'checks'} failed: ${failed.join('; ')}`, ExitCode.INVALID, 'verify', undefined, json ? report : undefined);
|
|
262
|
+
}
|
|
263
|
+
emit(deps, json, outcome);
|
|
264
|
+
});
|
|
265
|
+
sessions
|
|
266
|
+
.command('exclusions')
|
|
267
|
+
.description('scan the local store and transcripts under the exclusion rule, write the excluded sessions to a local file, and print counts only (the term file is named by OARS_EXCLUSION_TERMS)')
|
|
268
|
+
.option('--out <file>', 'where to write the excluded sessions (default: session-commons/exclusion-list.jsonl beside the claude projects folder)')
|
|
269
|
+
.action(async (_options, command) => {
|
|
270
|
+
const options = command.optsWithGlobals();
|
|
271
|
+
if (deps.sessions === undefined)
|
|
272
|
+
throw new CliError('The session store is not configured in this run.', ExitCode.UNEXPECTED, 'unexpected');
|
|
273
|
+
emit(deps, flag(options, 'json'), await sessionsExclusions(deps.sessions, exclusionPaths(deps.sessions.projectsRoot, deps.env, text(options, 'out'))));
|
|
274
|
+
});
|
|
275
|
+
const gold = sessions.command('gold').description('a blind, human-judged sample of prompts, and every signal scored against those judgments');
|
|
276
|
+
gold
|
|
277
|
+
.command('sample')
|
|
278
|
+
.description('write about 200 prompts in batches to judge blind, across prompt classes and models, with the signals in a hidden sidecar; it never overwrites a file')
|
|
279
|
+
.option('--size <n>', 'prompts to draw (default 200)', positiveInteger)
|
|
280
|
+
.option('--batch <n>', 'prompts per batch (default 50)', positiveInteger)
|
|
281
|
+
.option('--seed <text>', "the draw's seed (default today's date)")
|
|
282
|
+
.option('--exclude-term <text>', 'also leave out any session that mentions this, in any case, beyond the local term file (OARS_EXCLUSION_TERMS) that always applies (repeatable)', collect)
|
|
283
|
+
.option('--out-dir <folder>', 'where to write (default <claude config>/session-commons/review)')
|
|
284
|
+
.action(async (_options, command) => {
|
|
285
|
+
const options = command.optsWithGlobals();
|
|
286
|
+
if (deps.sessions === undefined)
|
|
287
|
+
throw new CliError('The session store is not configured in this run.', ExitCode.UNEXPECTED, 'unexpected');
|
|
288
|
+
emit(deps, flag(options, 'json'), await sessionsGoldSample(sessionDeps(deps), {
|
|
289
|
+
termsFile: exclusionPaths(deps.sessions.projectsRoot, deps.env).terms,
|
|
290
|
+
size: count(options, 'size') ?? 200,
|
|
291
|
+
batch: count(options, 'batch') ?? 50,
|
|
292
|
+
seed: text(options, 'seed'),
|
|
293
|
+
excludeTerms: list(options, 'excludeTerm'),
|
|
294
|
+
outDir: text(options, 'outDir'),
|
|
295
|
+
}));
|
|
296
|
+
});
|
|
297
|
+
gold
|
|
298
|
+
.command('score')
|
|
299
|
+
.description('per signal and per model, the AUROC with its 95% interval and the base rate, from whatever is judged so far')
|
|
300
|
+
.argument('<file>', 'the gold file')
|
|
301
|
+
.action(async (file, _options, command) => {
|
|
302
|
+
const options = command.optsWithGlobals();
|
|
303
|
+
emit(deps, flag(options, 'json'), await sessionsGoldScore({ file }));
|
|
304
|
+
});
|
|
305
|
+
gold
|
|
306
|
+
.command('arms')
|
|
307
|
+
.description('fill the hidden sidecar: the closing claim, its Confidence line, and fresh single-turn readers on your Claude subscription, with fixed, hashed wording. It resumes, and never touches the gold file')
|
|
308
|
+
.argument('<file>', 'the gold file')
|
|
309
|
+
.action(async (file, _options, command) => {
|
|
310
|
+
const options = command.optsWithGlobals();
|
|
311
|
+
emit(deps, flag(options, 'json'), await sessionsGoldArms(sessionDeps(deps), { file }));
|
|
312
|
+
});
|
|
313
|
+
const hook = sessions.command('hook').description('install the recall hooks into a Claude Code settings file, and the nightly deposit task');
|
|
314
|
+
const scopeOption = (value) => {
|
|
315
|
+
if (value !== 'user' && value !== 'project' && value !== 'file')
|
|
316
|
+
throw new InvalidArgumentError('it must be user, project, or file.');
|
|
317
|
+
return value;
|
|
318
|
+
};
|
|
319
|
+
const scopeOf = (options) => {
|
|
320
|
+
const value = text(options, 'scope');
|
|
321
|
+
return value === 'user' || value === 'project' || value === 'file' ? value : undefined;
|
|
322
|
+
};
|
|
323
|
+
hook
|
|
324
|
+
.command('install')
|
|
325
|
+
.description('merge the SessionStart, UserPromptSubmit, PreToolUse, PostToolUseFailure, and SessionEnd hooks into a settings file (existing hooks are kept), raise cleanupPeriodDays to at least 365, and on Windows register the nightly task (which sends telemetry while telemetry is on, and pool batches only after you accept a pool covenant)')
|
|
326
|
+
.option('--scope <scope>', 'user (~/.claude/settings.json; the default), project (./.claude/settings.json), or file (needs --file; implied by --file)', scopeOption)
|
|
327
|
+
.option('--file <path>', 'the settings file for --scope file')
|
|
328
|
+
.option('--keep-retention', 'leave cleanupPeriodDays as it is (the harness deletes raw transcripts older than it)')
|
|
329
|
+
.option('--schedule <mode>', 'nightly: register a Windows task that runs the full deposit and the recurrence refresh daily (a plain install on Windows does this too); off: remove it', (value) => {
|
|
330
|
+
if (value !== 'nightly' && value !== 'off')
|
|
331
|
+
throw new InvalidArgumentError('it must be nightly or off.');
|
|
332
|
+
return value;
|
|
333
|
+
})
|
|
334
|
+
.action(async (_options, command) => {
|
|
335
|
+
const options = command.optsWithGlobals();
|
|
336
|
+
const schedule = text(options, 'schedule');
|
|
337
|
+
emit(deps, flag(options, 'json'), await sessionsHookInstall(sessionDeps(deps), {
|
|
338
|
+
scope: scopeOf(options),
|
|
339
|
+
file: text(options, 'file'),
|
|
340
|
+
keepRetention: flag(options, 'keepRetention'),
|
|
341
|
+
schedule: schedule === 'nightly' || schedule === 'off' ? schedule : undefined,
|
|
342
|
+
}));
|
|
343
|
+
});
|
|
344
|
+
hook
|
|
345
|
+
.command('uninstall')
|
|
346
|
+
.description('remove only the hooks `install` wrote from a settings file (cleanupPeriodDays stays; `install --schedule off` removes the task)')
|
|
347
|
+
.option('--scope <scope>', 'user (the default), project, or file (needs --file; implied by --file)', scopeOption)
|
|
348
|
+
.option('--file <path>', 'the settings file for --scope file')
|
|
349
|
+
.action(async (_options, command) => {
|
|
350
|
+
const options = command.optsWithGlobals();
|
|
351
|
+
emit(deps, flag(options, 'json'), await sessionsHookUninstall(sessionDeps(deps), { scope: scopeOf(options), file: text(options, 'file') }));
|
|
352
|
+
});
|
|
353
|
+
hook
|
|
141
354
|
.command('status')
|
|
142
|
-
.description('
|
|
143
|
-
.
|
|
144
|
-
.action(async (
|
|
355
|
+
.description('what is installed in the user and project settings (and --file), and whether the nightly task is registered')
|
|
356
|
+
.option('--file <path>', 'one more settings file to report')
|
|
357
|
+
.action(async (_options, command) => {
|
|
145
358
|
const options = command.optsWithGlobals();
|
|
146
|
-
emit(deps, flag(options, 'json'), await
|
|
359
|
+
emit(deps, flag(options, 'json'), await sessionsHookStatus(sessionDeps(deps), { file: text(options, 'file') }));
|
|
147
360
|
});
|
|
148
|
-
program
|
|
149
|
-
|
|
150
|
-
.
|
|
151
|
-
.
|
|
152
|
-
.option('--
|
|
153
|
-
.
|
|
361
|
+
const gate = program.command('gate').description('record the install, fetch-and-run, MCP, and skill picks your agents make, live through a hook or from logs already on disk, without judging them');
|
|
362
|
+
gate
|
|
363
|
+
.command('replay')
|
|
364
|
+
.description('find the crossings in your session logs and write them to crossings.jsonl in the sessions store; reads nothing outside the allowlist, and asks the package manager what the registry holds unless --no-registry-state')
|
|
365
|
+
.option('--allow <folder>', 'a project folder to read, as for `oars sessions deposit` (repeatable); default: the allowlist a deposit saved, else every folder', collect)
|
|
366
|
+
.option('--no-registry-state', 'do not ask the package manager what the registry holds for each package named (npm view, pip index versions; asked by default, as the live hook does, and the manager keeps its own cache)')
|
|
367
|
+
.action(async (_options, command) => {
|
|
154
368
|
const options = command.optsWithGlobals();
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
369
|
+
emit(deps, flag(options, 'json'), await gateReplay(sessionDeps(deps), { allow: list(options, 'allow'), ...(flag(options, 'registryState') ? { registry: managerReader() } : {}) }));
|
|
370
|
+
});
|
|
371
|
+
gate
|
|
372
|
+
.command('hook')
|
|
373
|
+
.description('the PreToolUse hook: reads the tool call on stdin, records any crossing in crossings.jsonl, and prints nothing unless the gate policy says to: a note as context, or a deny labeled with the fact for a name the registry does not hold; it never allows or changes a call (bin.js answers this command itself, before the rest of the CLI loads)')
|
|
374
|
+
.action(async () => {
|
|
375
|
+
const runtime = sessionDeps(deps).sessions;
|
|
376
|
+
// No store means no file: an empty store folder would put crossings.jsonl in the working folder.
|
|
377
|
+
if (runtime === undefined)
|
|
378
|
+
return;
|
|
379
|
+
let stdin = '';
|
|
380
|
+
for await (const chunk of runtime.stdin)
|
|
381
|
+
stdin += String(chunk);
|
|
382
|
+
const output = await gateHook(stdin, { storeDir: runtime.storeDir, now: deps.now, clock: () => performance.now(), deadlineMs: DEADLINE_MS });
|
|
383
|
+
if (output !== '')
|
|
384
|
+
deps.io.stdout(output);
|
|
385
|
+
});
|
|
386
|
+
gate
|
|
387
|
+
.command('install')
|
|
388
|
+
.description('wire the gate hook into Claude Code at user scope (PreToolUse for Bash, PowerShell, Skill, and MCP tools); rerunning changes nothing, and every other setting and hook is kept')
|
|
389
|
+
.action(async (_options, command) => {
|
|
390
|
+
emit(deps, flag(command.optsWithGlobals(), 'json'), await gateInstall(sessionDeps(deps)));
|
|
391
|
+
});
|
|
392
|
+
gate
|
|
393
|
+
.command('uninstall')
|
|
394
|
+
.description('remove only the gate hook `install` wrote from the user settings; crossings.jsonl stays')
|
|
395
|
+
.action(async (_options, command) => {
|
|
396
|
+
emit(deps, flag(command.optsWithGlobals(), 'json'), await gateUninstall(sessionDeps(deps)));
|
|
397
|
+
});
|
|
398
|
+
gate
|
|
399
|
+
.command('policy')
|
|
400
|
+
.description('print the gate policy in force: per class warn, block, or off, the day count for a young name, and the holdback share; read from gate-policy.json in the sessions store, whose absence or damage means the defaults')
|
|
401
|
+
.action(async (_options, command) => {
|
|
402
|
+
emit(deps, flag(command.optsWithGlobals(), 'json'), await gatePolicy(sessionDeps(deps)));
|
|
403
|
+
});
|
|
404
|
+
gate
|
|
405
|
+
.command('report')
|
|
406
|
+
.description('count the crossings in crossings.jsonl, by who recorded them, and say what replay cannot see')
|
|
407
|
+
.action(async (_options, command) => {
|
|
408
|
+
emit(deps, flag(command.optsWithGlobals(), 'json'), await gateReport(sessionDeps(deps)));
|
|
409
|
+
});
|
|
410
|
+
const pool = program.command('pool').description('sign in to a pool server, deposit session skeletons (a closed list of fields, each value redacted first) and, after a yes to the raw prompt, redacted raw copies, and fetch signed reads; local capture, recall, and reads need none of it');
|
|
411
|
+
const serverOption = '--server <url>';
|
|
412
|
+
const serverHelp = 'the pool server, https or plain http to localhost (default: OARS_POOL_URL, else https://pool.manyoars.com)';
|
|
413
|
+
pool
|
|
414
|
+
.command('login')
|
|
415
|
+
.description('sign in by GitHub or Google device flow, run here against the provider; prints each request before sending it, and the pool keeps a pseudonym; no harness token is read')
|
|
416
|
+
.requiredOption('--provider <name>', 'github or google')
|
|
417
|
+
.option(serverOption, serverHelp)
|
|
418
|
+
.action(async (_options, command) => {
|
|
419
|
+
const options = command.optsWithGlobals();
|
|
420
|
+
emit(deps, flag(options, 'json'), await poolLogin(deps, { server: text(options, 'server'), provider: text(options, 'provider') }));
|
|
421
|
+
});
|
|
422
|
+
pool
|
|
423
|
+
.command('deposit')
|
|
424
|
+
.description('write a batch to the outbox for each session that passes the folder allowlist and the exclusion check, and stop: nothing 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')
|
|
425
|
+
.option('--accept-covenant <version>', 'accept this version of the pool covenant before sending (with --send)')
|
|
426
|
+
.option('--send <hash>', 'the SHA-256 of a batch that oars pool outbox printed')
|
|
427
|
+
.option(serverOption, serverHelp)
|
|
428
|
+
.action(async (_options, command) => {
|
|
429
|
+
const options = command.optsWithGlobals();
|
|
430
|
+
emit(deps, flag(options, 'json'), await poolDeposit(deps, { server: text(options, 'server'), acceptCovenant: text(options, 'acceptCovenant'), send: text(options, 'send') }));
|
|
431
|
+
});
|
|
432
|
+
pool
|
|
433
|
+
.command('raw')
|
|
434
|
+
.description('the raw prompt: whether a redacted copy of each full transcript may leave besides its skeleton (--consent yes|no), and what one folder may send (--folder <folder> --mode all|skeleton|off); with no option, shows the prompt and your answer')
|
|
435
|
+
.option('--consent <answer>', 'yes or no; a no, or no answer, sends skeletons only')
|
|
436
|
+
.option('--folder <entry>', 'a folder, folder prefix, or glob, as --allow takes it')
|
|
437
|
+
.option('--mode <mode>', 'all, skeleton, or off for that folder: on by default after you accept; skeleton sends no raw; off sends nothing from that folder to the pool from now on')
|
|
438
|
+
.action(async (_options, command) => {
|
|
439
|
+
const options = command.optsWithGlobals();
|
|
440
|
+
emit(deps, flag(options, 'json'), await poolRaw(deps, { consent: text(options, 'consent'), folder: text(options, 'folder'), mode: text(options, 'mode') }));
|
|
441
|
+
});
|
|
442
|
+
pool
|
|
443
|
+
.command('coverage')
|
|
444
|
+
.description('state whom your deposits cover, kept on this machine and never looked up: --org-size solo, 2-10, 11-100, 101-1000, over-1000, or unknown; --repo-visibility public, private, or unknown. With no flag it prints what is set')
|
|
445
|
+
.option('--org-size <band>', 'how many people your organization has')
|
|
446
|
+
.option('--repo-visibility <visibility>', 'whether your repositories are public or private')
|
|
447
|
+
.action(async (_options, command) => {
|
|
448
|
+
const options = command.optsWithGlobals();
|
|
449
|
+
emit(deps, flag(options, 'json'), await poolCoverage(deps, { orgSize: text(options, 'orgSize'), repoVisibility: text(options, 'repoVisibility') }));
|
|
450
|
+
});
|
|
451
|
+
pool
|
|
452
|
+
.command('outbox')
|
|
453
|
+
.description('print every batch exactly as it would be sent, request line and headers included, with the credential as its size; prints each batch SHA-256 and records that it was shown')
|
|
454
|
+
.option('--explain', 'print each field of a batch with the rule that admits its values')
|
|
455
|
+
.option(serverOption, serverHelp)
|
|
456
|
+
.action(async (_options, command) => {
|
|
457
|
+
const options = command.optsWithGlobals();
|
|
458
|
+
emit(deps, flag(options, 'json'), await poolOutbox(deps, { server: text(options, 'server'), explain: flag(options, 'explain') }));
|
|
459
|
+
});
|
|
460
|
+
pool
|
|
461
|
+
.command('fetch')
|
|
462
|
+
.description('fetch the signed bundle for one harness version, verify it against the key pinned at login, and keep it locally')
|
|
463
|
+
.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)')
|
|
464
|
+
.requiredOption('--harness-version <version>', 'the public harness version whose bundle to fetch, or unknown for a session that recorded none (Cursor, Gemini, Grok Build, Pi, or a VS Code Copilot Chat session whose requests name no agent version; there is no bundle for otel)')
|
|
465
|
+
.option(serverOption, serverHelp)
|
|
466
|
+
.action(async (_options, command) => {
|
|
467
|
+
const options = command.optsWithGlobals();
|
|
468
|
+
emit(deps, flag(options, 'json'), await poolFetch(deps, { server: text(options, 'server'), harness: text(options, 'harness'), harnessVersion: text(options, 'harnessVersion') ?? '' }));
|
|
469
|
+
});
|
|
470
|
+
const telemetry = program.command('telemetry').description('what oars records about its own use, on this machine (docs/telemetry.md): status, on, off, show, and report');
|
|
471
|
+
const telemetryJson = (command) => flag(command.optsWithGlobals(), 'json');
|
|
472
|
+
telemetry
|
|
473
|
+
.command('status')
|
|
474
|
+
.description('whether telemetry is on, which switch decided it, whether the notice was shown, and the size of the spool')
|
|
475
|
+
.action((_options, command) => {
|
|
476
|
+
emit(deps, telemetryJson(command), telemetryStatus(deps));
|
|
477
|
+
});
|
|
478
|
+
telemetry
|
|
479
|
+
.command('on')
|
|
480
|
+
.description('turn telemetry on, and print what it records (OARS_TELEMETRY=0 and DO_NOT_TRACK=1 still win)')
|
|
481
|
+
.action((_options, command) => {
|
|
482
|
+
emit(deps, telemetryJson(command), telemetryOn(deps));
|
|
483
|
+
});
|
|
484
|
+
telemetry
|
|
485
|
+
.command('off')
|
|
486
|
+
.description('turn telemetry off: nothing more is recorded or sent')
|
|
487
|
+
.action((_options, command) => {
|
|
488
|
+
emit(deps, telemetryJson(command), telemetryOff(deps));
|
|
489
|
+
});
|
|
490
|
+
telemetry
|
|
491
|
+
.command('show')
|
|
492
|
+
.description('print the spool exactly as stored: every event telemetry has kept, oldest first')
|
|
493
|
+
.action((_options, command) => {
|
|
494
|
+
emit(deps, telemetryJson(command), telemetryShow(deps));
|
|
495
|
+
});
|
|
496
|
+
telemetry
|
|
497
|
+
.command('report')
|
|
498
|
+
.description('your own hook latency, hit rates, and store health over the last 7 and 30 days, from the spool, beside the fleet\'s when OARS_POOL_URL names a pool server that answers')
|
|
499
|
+
.action(async (_options, command) => {
|
|
500
|
+
emit(deps, telemetryJson(command), await telemetryReport(deps));
|
|
162
501
|
});
|
|
163
502
|
return program;
|
|
164
503
|
};
|
|
@@ -169,16 +508,18 @@ const asCliError = (error) => {
|
|
|
169
508
|
return new CliError(error.message.replace(/^error: /, ''), ExitCode.USAGE, 'usage');
|
|
170
509
|
return new CliError(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`, ExitCode.UNEXPECTED, 'unexpected');
|
|
171
510
|
};
|
|
172
|
-
|
|
173
|
-
export const run = async (argv, deps) => {
|
|
511
|
+
const execute = async (argv, deps, observed) => {
|
|
174
512
|
try {
|
|
175
|
-
await buildProgram(deps,
|
|
513
|
+
await buildProgram(deps, observed).parseAsync(argv, { from: 'user' });
|
|
176
514
|
return ExitCode.OK;
|
|
177
515
|
}
|
|
178
516
|
catch (error) {
|
|
179
517
|
// --help and --version end the parse through exitOverride with exit code 0; commander has already printed them.
|
|
180
518
|
if (error instanceof CommanderError && error.exitCode === 0)
|
|
181
519
|
return ExitCode.OK;
|
|
520
|
+
// A failure the program did not mean (not a CliError, not commander's own): its class and a hash of its stack are recorded, then it is handled as before.
|
|
521
|
+
if (!(error instanceof CliError) && !(error instanceof CommanderError))
|
|
522
|
+
recordCrash(deps, error);
|
|
182
523
|
const failure = asCliError(error);
|
|
183
524
|
// Commander wrote its own usage message to stderr; a CliError is ours to print.
|
|
184
525
|
if (!(error instanceof CommanderError) || argv.includes('--json'))
|
|
@@ -186,3 +527,11 @@ export const run = async (argv, deps) => {
|
|
|
186
527
|
return failure.exitCode;
|
|
187
528
|
}
|
|
188
529
|
};
|
|
530
|
+
/** Runs one invocation (`argv` without the node and script entries) and returns the exit code. Telemetry records it afterward and cannot change it. */
|
|
531
|
+
export const run = async (argv, deps) => {
|
|
532
|
+
const observed = { path: [], flags: [] };
|
|
533
|
+
const startedAt = deps.now();
|
|
534
|
+
const exit = await execute(argv, deps, observed);
|
|
535
|
+
finishTelemetry(deps, argv, observed, startedAt, exit);
|
|
536
|
+
return exit;
|
|
537
|
+
};
|