@open-cr-agent/cli 0.5.0 → 0.6.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/dist/cloud/account-salt.d.ts +6 -0
- package/dist/cloud/account-salt.js +33 -0
- package/dist/{review → cloud}/account-settings.js +5 -4
- package/dist/cloud/browser.d.ts +17 -0
- package/dist/cloud/browser.js +37 -0
- package/dist/cloud/client.d.ts +93 -0
- package/dist/cloud/client.js +221 -0
- package/dist/cloud/credentials.d.ts +18 -0
- package/dist/cloud/credentials.js +52 -0
- package/dist/cloud/deps.d.ts +14 -0
- package/dist/cloud/deps.js +34 -0
- package/dist/cloud/file-lock.d.ts +14 -0
- package/dist/cloud/file-lock.js +77 -0
- package/dist/cloud/findings.d.ts +8 -0
- package/dist/{review/cloud-findings.js → cloud/findings.js} +10 -32
- package/dist/cloud/gateway-token.d.ts +18 -0
- package/dist/cloud/gateway-token.js +63 -0
- package/dist/cloud/memory.d.ts +8 -0
- package/dist/cloud/memory.js +68 -0
- package/dist/cloud/private-file.d.ts +8 -0
- package/dist/cloud/private-file.js +46 -0
- package/dist/{review/cloud-providers.d.ts → cloud/providers.d.ts} +12 -6
- package/dist/cloud/providers.js +132 -0
- package/dist/cloud/review.d.ts +28 -0
- package/dist/cloud/review.js +76 -0
- package/dist/cloud/settings.d.ts +22 -0
- package/dist/cloud/settings.js +64 -0
- package/dist/cloud/upload.d.ts +20 -0
- package/dist/{review/cloud-upload.js → cloud/upload.js} +74 -71
- package/dist/commands/login.d.ts +5 -0
- package/dist/commands/login.js +164 -0
- package/dist/commands/memory.d.ts +3 -0
- package/dist/{memory.js → commands/memory.js} +21 -20
- package/dist/commands/metrics.d.ts +45 -0
- package/dist/{metrics.js → commands/metrics.js} +32 -45
- package/dist/{plugins-command.d.ts → commands/plugins.d.ts} +3 -9
- package/dist/{plugins-command.js → commands/plugins.js} +29 -53
- package/dist/{review → commands/review}/args.d.ts +7 -10
- package/dist/{review → commands/review}/args.js +11 -16
- package/dist/commands/review/deliver.d.ts +7 -0
- package/dist/commands/review/deliver.js +56 -0
- package/dist/commands/review/deps.d.ts +18 -0
- package/dist/commands/review/deps.js +2 -0
- package/dist/commands/review/execute-run.d.ts +13 -0
- package/dist/commands/review/execute-run.js +97 -0
- package/dist/commands/review/exit-code.d.ts +4 -0
- package/dist/commands/review/exit-code.js +29 -0
- package/dist/commands/review/plan-prices.d.ts +3 -0
- package/dist/commands/review/plan-prices.js +18 -0
- package/dist/{review → commands/review}/plan-render.js +22 -2
- package/dist/commands/review/plan-run.d.ts +5 -0
- package/dist/commands/review/plan-run.js +38 -0
- package/dist/commands/review/plan-schema.d.ts +97 -0
- package/dist/commands/review/plan-schema.js +34 -0
- package/dist/{review → commands/review}/progress.d.ts +3 -5
- package/dist/{review → commands/review}/progress.js +3 -3
- package/dist/{review → commands/review}/provenance.d.ts +4 -2
- package/dist/{review → commands/review}/provenance.js +2 -3
- package/dist/{review → commands/review}/render.js +1 -1
- package/dist/commands/review/resolve-run.d.ts +29 -0
- package/dist/commands/review/resolve-run.js +125 -0
- package/dist/commands/review/review-options.d.ts +6 -0
- package/dist/commands/review/review-options.js +35 -0
- package/dist/{review → commands/review}/runtimes.js +1 -1
- package/dist/{review → commands/review}/sarif-input.d.ts +0 -1
- package/dist/{review → commands/review}/sarif-input.js +5 -5
- package/dist/commands/review/sarif.d.ts +96 -0
- package/dist/{review → commands/review}/sarif.js +29 -7
- package/dist/commands/review/settings-sources.d.ts +17 -0
- package/dist/commands/review/settings-sources.js +30 -0
- package/dist/commands/review/target.d.ts +38 -0
- package/dist/commands/review/target.js +126 -0
- package/dist/commands/review.d.ts +7 -0
- package/dist/commands/review.js +34 -0
- package/dist/config/cli-config.d.ts +41 -0
- package/dist/config/cli-config.js +199 -0
- package/dist/{review/remote-config.d.ts → config/remote.d.ts} +3 -3
- package/dist/{review/remote-config.js → config/remote.js} +5 -23
- package/dist/{review/config.d.ts → config/schema.d.ts} +8 -31
- package/dist/config/schema.js +138 -0
- package/dist/config/settings.d.ts +37 -0
- package/dist/config/settings.js +133 -0
- package/dist/config/user-dir.d.ts +3 -0
- package/dist/config/user-dir.js +10 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +10 -1
- package/dist/internal.d.ts +6 -0
- package/dist/internal.js +7 -0
- package/dist/io/exit.d.ts +9 -0
- package/dist/io/exit.js +12 -0
- package/dist/io/output.d.ts +4 -0
- package/dist/io/output.js +2 -0
- package/dist/io/usage-error.d.ts +5 -0
- package/dist/io/usage-error.js +8 -0
- package/dist/main.js +2 -2
- package/dist/{review/account-plugins.d.ts → plugins/account.d.ts} +2 -2
- package/dist/{review/account-plugins.js → plugins/account.js} +7 -7
- package/dist/{review/plugins.d.ts → plugins/load.d.ts} +2 -2
- package/dist/{review/plugins.js → plugins/load.js} +6 -5
- package/dist/plugins/npm.d.ts +8 -0
- package/dist/plugins/npm.js +24 -0
- package/dist/{plugin-store.d.ts → plugins/store.d.ts} +1 -1
- package/dist/{plugin-store.js → plugins/store.js} +5 -4
- package/dist/repository-id.d.ts +12 -0
- package/dist/repository-id.js +72 -0
- package/dist/run.d.ts +2 -2
- package/dist/run.js +15 -12
- package/dist/session/store.d.ts +9 -0
- package/dist/session/store.js +31 -0
- package/package.json +17 -7
- package/dist/account-salt.d.ts +0 -7
- package/dist/account-salt.js +0 -43
- package/dist/cloud.d.ts +0 -34
- package/dist/cloud.js +0 -272
- package/dist/memory.d.ts +0 -4
- package/dist/metrics.d.ts +0 -47
- package/dist/review/cloud-findings.d.ts +0 -31
- package/dist/review/cloud-memory.d.ts +0 -6
- package/dist/review/cloud-memory.js +0 -58
- package/dist/review/cloud-providers.js +0 -60
- package/dist/review/cloud-review.d.ts +0 -19
- package/dist/review/cloud-review.js +0 -51
- package/dist/review/cloud-settings.d.ts +0 -16
- package/dist/review/cloud-settings.js +0 -118
- package/dist/review/cloud-upload.d.ts +0 -60
- package/dist/review/command.d.ts +0 -33
- package/dist/review/command.js +0 -293
- package/dist/review/config.js +0 -304
- package/dist/review/sarif.d.ts +0 -4
- package/dist/review/settings-sources.d.ts +0 -11
- package/dist/review/settings-sources.js +0 -58
- package/dist/review/target.d.ts +0 -18
- package/dist/review/target.js +0 -210
- /package/dist/{review → cloud}/account-settings.d.ts +0 -0
- /package/dist/{review → commands/review}/plan-render.d.ts +0 -0
- /package/dist/{review → commands/review}/render.d.ts +0 -0
- /package/dist/{review → commands/review}/runtimes.d.ts +0 -0
- /package/dist/{review → io}/terminal.d.ts +0 -0
- /package/dist/{review → io}/terminal.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { OcraError } from "@open-cr-agent/core";
|
|
2
|
+
import { MODEL_TIERS, OcraError } from "@open-cr-agent/core";
|
|
3
3
|
import { repoRuleSchema } from "@open-cr-agent/core/internal";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
const MAX_BYTES = 256 * 1024;
|
|
@@ -7,11 +7,11 @@ const TIMEOUT_MS = 10_000;
|
|
|
7
7
|
// What a shared configuration may set: settings, rules and model providers
|
|
8
8
|
// (a company gateway, for example), never plugins or a runtime, because it
|
|
9
9
|
// is fetched from outside the repository.
|
|
10
|
-
|
|
10
|
+
const remoteConfigSchema = z
|
|
11
11
|
.object({
|
|
12
12
|
$schema: z.string().optional(),
|
|
13
|
-
models: z.record(z.enum(
|
|
14
|
-
effort: z.partialRecord(z.enum(
|
|
13
|
+
models: z.record(z.enum(MODEL_TIERS), z.unknown()).optional(),
|
|
14
|
+
effort: z.partialRecord(z.enum(MODEL_TIERS), z.unknown()).optional(),
|
|
15
15
|
concurrency: z.unknown().optional(),
|
|
16
16
|
taskTimeoutMinutes: z.unknown().optional(),
|
|
17
17
|
runTimeoutMinutes: z.unknown().optional(),
|
|
@@ -81,22 +81,4 @@ async function readLimited(response, limit, name) {
|
|
|
81
81
|
}
|
|
82
82
|
return Buffer.concat(chunks).toString("utf8");
|
|
83
83
|
}
|
|
84
|
-
|
|
85
|
-
export function mergeConfig(remote, local) {
|
|
86
|
-
const { rules: _rules, ...settings } = remote;
|
|
87
|
-
const merged = { ...settings, ...local };
|
|
88
|
-
for (const key of ["models", "effort", "reviewers", "roles", "github", "providers"]) {
|
|
89
|
-
const a = settings[key];
|
|
90
|
-
const b = local[key];
|
|
91
|
-
if (a !== undefined || b !== undefined)
|
|
92
|
-
merged[key] = { ...(a ?? {}), ...(b ?? {}) };
|
|
93
|
-
}
|
|
94
|
-
for (const key of ["include", "exclude"]) {
|
|
95
|
-
const a = settings[key] ?? [];
|
|
96
|
-
const b = local[key] ?? [];
|
|
97
|
-
if (a.length + b.length > 0)
|
|
98
|
-
merged[key] = [...a, ...b];
|
|
99
|
-
}
|
|
100
|
-
return merged;
|
|
101
|
-
}
|
|
102
|
-
//# sourceMappingURL=remote-config.js.map
|
|
84
|
+
//# sourceMappingURL=remote.js.map
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import { type CustomProvider, type ModelChains, OcraError, type ReviewerOverrides, type RoleSettings, type SourcedRule, type TierEfforts } from "@open-cr-agent/core";
|
|
2
1
|
import { z } from "zod";
|
|
3
|
-
export declare const
|
|
2
|
+
export declare const effort: z.ZodEnum<{
|
|
3
|
+
high: "high";
|
|
4
|
+
low: "low";
|
|
5
|
+
medium: "medium";
|
|
6
|
+
minimal: "minimal";
|
|
7
|
+
none: "none";
|
|
8
|
+
}>;
|
|
4
9
|
export declare const DEFAULT_RUNTIME = "opencode";
|
|
5
10
|
export declare const configSchema: z.ZodObject<{
|
|
6
11
|
$schema: z.ZodOptional<z.ZodString>;
|
|
@@ -103,32 +108,4 @@ export declare const configSchema: z.ZodObject<{
|
|
|
103
108
|
}, z.core.$strict>;
|
|
104
109
|
export declare const CONFIG_SCHEMA_ID = "https://ocra.majincheng.com/schema/config.v1.json";
|
|
105
110
|
export declare function configJsonSchema(): Record<string, unknown>;
|
|
106
|
-
|
|
107
|
-
runtime: string;
|
|
108
|
-
runtimeSet: boolean;
|
|
109
|
-
models: ModelChains;
|
|
110
|
-
effort: TierEfforts;
|
|
111
|
-
providers: Record<string, CustomProvider>;
|
|
112
|
-
rules: SourcedRule[];
|
|
113
|
-
accountSettings?: {
|
|
114
|
-
version: string | null;
|
|
115
|
-
};
|
|
116
|
-
};
|
|
117
|
-
export interface LoadOptions {
|
|
118
|
-
repository: boolean;
|
|
119
|
-
read?: (path: string) => Promise<string | undefined>;
|
|
120
|
-
file?: string;
|
|
121
|
-
fetch?: typeof fetch;
|
|
122
|
-
warn?: (message: string) => void;
|
|
123
|
-
}
|
|
124
|
-
export declare class ConfigError extends OcraError {
|
|
125
|
-
constructor(message: string, options?: {
|
|
126
|
-
cause?: unknown;
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
export declare function loadConfig(root: string, env: Readonly<Record<string, string | undefined>>, options?: LoadOptions): Promise<CliConfig>;
|
|
130
|
-
export declare function agentChains(config: {
|
|
131
|
-
reviewers: ReviewerOverrides;
|
|
132
|
-
roles: RoleSettings;
|
|
133
|
-
}): Record<string, readonly string[]>;
|
|
134
|
-
//# sourceMappingURL=config.d.ts.map
|
|
111
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { EFFORT_LEVELS } from "@open-cr-agent/core";
|
|
2
|
+
import { AGENT_ROLES, MAX_TIMER_MS, RISK_TIERS } from "@open-cr-agent/core/internal";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
// What .ocra/config.json may say. Every layer of settings (settings.ts) is
|
|
5
|
+
// checked against it.
|
|
6
|
+
// How much a model reasons before answering (ADR-0025); unset leaves the
|
|
7
|
+
// provider's default.
|
|
8
|
+
export const effort = z.enum(EFFORT_LEVELS);
|
|
9
|
+
// The longest timeout a timer keeps, about 24.8 days; a longer one would fire at once.
|
|
10
|
+
const timeoutMinutes = z
|
|
11
|
+
.number()
|
|
12
|
+
.positive()
|
|
13
|
+
.max(Math.floor(MAX_TIMER_MS / 60_000))
|
|
14
|
+
.optional();
|
|
15
|
+
const modelChain = z
|
|
16
|
+
.union([z.string().min(1), z.array(z.string().min(1)).min(1)])
|
|
17
|
+
.transform((value) => (typeof value === "string" ? [value] : value));
|
|
18
|
+
// Where review code is sent: over https, or plain http only to this machine.
|
|
19
|
+
// OpenCode replaces {env:NAME} and {file:path} anywhere in its configuration,
|
|
20
|
+
// so an address with braces could carry a variable or a file (the checkout's
|
|
21
|
+
// .git/config holds its token) to the endpoint.
|
|
22
|
+
const endpoint = z
|
|
23
|
+
.url()
|
|
24
|
+
.refine((value) => {
|
|
25
|
+
const url = new URL(value);
|
|
26
|
+
if (url.protocol === "https:")
|
|
27
|
+
return true;
|
|
28
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
29
|
+
return (url.protocol === "http:" &&
|
|
30
|
+
(host === "localhost" || host === "::1" || /^127\.\d+\.\d+\.\d+$/.test(host)));
|
|
31
|
+
}, "must be an https URL, or http on this machine (localhost, 127.0.0.1, ::1)")
|
|
32
|
+
.refine((value) => !/[{}]/.test(value), "must not contain { or }");
|
|
33
|
+
// The platforms' and clouds' credentials never go to a model endpoint; the
|
|
34
|
+
// same prefixes are never passed to the runtime by prefix (server-env.ts).
|
|
35
|
+
const PLATFORM_TOKENS = /^(GITHUB_|GH_|GITLAB_|CI_|ACTIONS_|RUNNER_|AWS_|AZURE_|NPM_|SSH_|OCRA_)/;
|
|
36
|
+
const price = z.number().min(0).max(10_000);
|
|
37
|
+
const providerSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
type: z.literal("openai-compatible"),
|
|
40
|
+
baseUrl: endpoint,
|
|
41
|
+
apiKeyEnv: z
|
|
42
|
+
.string()
|
|
43
|
+
.regex(/^[A-Za-z_][A-Za-z0-9_]*$/, "must be an environment variable name")
|
|
44
|
+
.refine((name) => !PLATFORM_TOKENS.test(name), "must not name a platform or cloud credential")
|
|
45
|
+
.optional(),
|
|
46
|
+
// Prices in US dollars per million tokens; 0 means unpriced.
|
|
47
|
+
models: z
|
|
48
|
+
.record(z.string().regex(/^[\w./:@-]{1,200}$/), z.object({ input: price, output: price, cachedInput: price.optional() }).strict())
|
|
49
|
+
.refine((models) => Object.keys(models).length > 0, "must list at least one model"),
|
|
50
|
+
// How the endpoint takes a reasoning effort: "openai" sends
|
|
51
|
+
// reasoning_effort (the default), "openrouter" sends reasoning.effort.
|
|
52
|
+
effort: z.enum(["openai", "openrouter"]).optional(),
|
|
53
|
+
})
|
|
54
|
+
.strict();
|
|
55
|
+
// The runtimes ocra ships; a plugin may register another name, so the
|
|
56
|
+
// schema lists these for editors and still accepts any other string.
|
|
57
|
+
const BUILTIN_RUNTIMES = ["opencode", "direct"];
|
|
58
|
+
export const DEFAULT_RUNTIME = "opencode";
|
|
59
|
+
export const configSchema = z
|
|
60
|
+
.object({
|
|
61
|
+
$schema: z.string().optional(),
|
|
62
|
+
models: z
|
|
63
|
+
.object({ top: modelChain, standard: modelChain, light: modelChain })
|
|
64
|
+
.partial()
|
|
65
|
+
.strict()
|
|
66
|
+
.default({}),
|
|
67
|
+
effort: z
|
|
68
|
+
.object({ top: effort, standard: effort, light: effort })
|
|
69
|
+
.partial()
|
|
70
|
+
.strict()
|
|
71
|
+
.default({}),
|
|
72
|
+
concurrency: z.number().int().min(1).max(32).optional(),
|
|
73
|
+
taskTimeoutMinutes: timeoutMinutes,
|
|
74
|
+
runTimeoutMinutes: timeoutMinutes,
|
|
75
|
+
verify: z.boolean().optional(),
|
|
76
|
+
judge: z.boolean().optional(),
|
|
77
|
+
maxCostUsd: z.number().positive().optional(),
|
|
78
|
+
maxTasks: z.number().int().min(1).max(1_000).optional(),
|
|
79
|
+
// Unset: each provider's default. A seed makes sampling repeatable only
|
|
80
|
+
// where the provider supports it.
|
|
81
|
+
sampling: z
|
|
82
|
+
.object({
|
|
83
|
+
temperature: z.number().min(0).max(2),
|
|
84
|
+
seed: z
|
|
85
|
+
.number()
|
|
86
|
+
.int()
|
|
87
|
+
.min(0)
|
|
88
|
+
.max(2 ** 31 - 1),
|
|
89
|
+
})
|
|
90
|
+
.partial()
|
|
91
|
+
.strict()
|
|
92
|
+
.optional(),
|
|
93
|
+
github: z
|
|
94
|
+
.object({ requestChanges: z.boolean(), botLogin: z.string().min(1) })
|
|
95
|
+
.partial()
|
|
96
|
+
.strict()
|
|
97
|
+
.default({}),
|
|
98
|
+
include: z.array(z.string().min(1)).default([]),
|
|
99
|
+
exclude: z.array(z.string().min(1)).default([]),
|
|
100
|
+
// Unset means "opencode"; loadConfig fills it in and records whether the
|
|
101
|
+
// file chose, so account defaults (ADR-0025) apply only where it did not.
|
|
102
|
+
runtime: z.enum(BUILTIN_RUNTIMES).or(z.string().min(1)).optional(),
|
|
103
|
+
plugins: z.array(z.string().min(1)).default([]),
|
|
104
|
+
reviewers: z
|
|
105
|
+
.record(z.string(), z
|
|
106
|
+
.object({
|
|
107
|
+
enabled: z.boolean(),
|
|
108
|
+
minTier: z.enum(RISK_TIERS),
|
|
109
|
+
// The reviewer's own chain, for its review tasks and plan call.
|
|
110
|
+
models: modelChain,
|
|
111
|
+
effort,
|
|
112
|
+
})
|
|
113
|
+
.partial()
|
|
114
|
+
.strict())
|
|
115
|
+
.default({}),
|
|
116
|
+
roles: z
|
|
117
|
+
.partialRecord(z.enum(AGENT_ROLES), z.object({ models: modelChain, effort }).partial().strict())
|
|
118
|
+
.default({}),
|
|
119
|
+
pluginSettings: z.record(z.string(), z.unknown()).default({}),
|
|
120
|
+
providers: z.record(z.string().regex(/^[a-z][a-z0-9-]{0,39}$/), providerSchema).default({}),
|
|
121
|
+
extends: z.string().min(1).optional(),
|
|
122
|
+
})
|
|
123
|
+
.strict();
|
|
124
|
+
export const CONFIG_SCHEMA_ID = "https://ocra.majincheng.com/schema/config.v1.json";
|
|
125
|
+
// JSON Schema (draft 2020-12) of .ocra/config.json, for editors: what a
|
|
126
|
+
// file may say, so defaults are optional and model chains take either form.
|
|
127
|
+
export function configJsonSchema() {
|
|
128
|
+
const generated = z.toJSONSchema(configSchema, { target: "draft-2020-12", io: "input" });
|
|
129
|
+
const { $schema, ...rest } = generated;
|
|
130
|
+
return {
|
|
131
|
+
$schema,
|
|
132
|
+
$id: CONFIG_SCHEMA_ID,
|
|
133
|
+
title: "ocra configuration",
|
|
134
|
+
description: "The .ocra/config.json file of a repository, or a file passed with --config. Unknown keys are rejected.",
|
|
135
|
+
...rest,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type SourcedRule } from "@open-cr-agent/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { configSchema } from "./schema.js";
|
|
4
|
+
type FileSettings = z.output<typeof configSchema>;
|
|
5
|
+
export type Settings = FileSettings & {
|
|
6
|
+
rules: SourcedRule[];
|
|
7
|
+
ultra: boolean;
|
|
8
|
+
};
|
|
9
|
+
type SettingSource = "shared" | "file" | "env" | "account" | "flag";
|
|
10
|
+
export type LayerSettings = {
|
|
11
|
+
[K in keyof Settings]?: Settings[K];
|
|
12
|
+
};
|
|
13
|
+
export interface SettingsLayer {
|
|
14
|
+
source: SettingSource;
|
|
15
|
+
settings: LayerSettings;
|
|
16
|
+
under?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export type SettingSources = Record<string, SettingSource[]>;
|
|
19
|
+
export declare const layerSchema: z.ZodType<Omit<LayerSettings, "rules" | "ultra">>;
|
|
20
|
+
/** The layers, earliest first, over ocra's defaults, and where each setting came from. */
|
|
21
|
+
export declare function resolveSettings(layers: readonly SettingsLayer[]): {
|
|
22
|
+
settings: Settings;
|
|
23
|
+
sources: SettingSources;
|
|
24
|
+
};
|
|
25
|
+
export interface ListedSetting {
|
|
26
|
+
key: string;
|
|
27
|
+
value?: unknown;
|
|
28
|
+
sources: SettingSource[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The settings --plan lists, in its order, with their sources. A setting
|
|
32
|
+
* only a command-line flag set is the flag's, not a setting, and is left
|
|
33
|
+
* out.
|
|
34
|
+
*/
|
|
35
|
+
export declare function listSettings(settings: Settings, sources: SettingSources): ListedSetting[];
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { isDeepStrictEqual } from "node:util";
|
|
2
|
+
import { MODEL_TIERS, OcraError } from "@open-cr-agent/core";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { configSchema } from "./schema.js";
|
|
5
|
+
const ALL = ["shared", "file", "env", "account"];
|
|
6
|
+
const NOT_ENV = ["shared", "file", "account"];
|
|
7
|
+
const ALWAYS = { list: "always" };
|
|
8
|
+
// How each setting merges, which layers may set it, and how --plan lists
|
|
9
|
+
// it, in --plan's order. A shared configuration is fetched from outside the
|
|
10
|
+
// repository, so it never names plugins or a runtime; the account never
|
|
11
|
+
// moves code or keys (providers, extends) or decides whose comments are
|
|
12
|
+
// trusted (github). The layers' parsers refuse these with a warning; here
|
|
13
|
+
// they are a broken invariant.
|
|
14
|
+
const MERGE = {
|
|
15
|
+
runtime: { merge: "replace", from: ["file", "account"], plan: ALWAYS },
|
|
16
|
+
models: { merge: "entries", from: ALL, plan: { list: "always", entries: MODEL_TIERS } },
|
|
17
|
+
effort: { merge: "entries", from: ALL, plan: { list: "always", entries: MODEL_TIERS } },
|
|
18
|
+
reviewers: { merge: "entries", from: NOT_ENV, plan: { list: "set" } },
|
|
19
|
+
roles: { merge: "entries", from: NOT_ENV, plan: { list: "set" } },
|
|
20
|
+
concurrency: { merge: "replace", from: NOT_ENV, plan: ALWAYS },
|
|
21
|
+
taskTimeoutMinutes: { merge: "replace", from: NOT_ENV, plan: ALWAYS },
|
|
22
|
+
runTimeoutMinutes: { merge: "replace", from: NOT_ENV, plan: ALWAYS },
|
|
23
|
+
maxCostUsd: { merge: "replace", from: NOT_ENV, plan: ALWAYS },
|
|
24
|
+
maxTasks: { merge: "replace", from: NOT_ENV, plan: ALWAYS },
|
|
25
|
+
verify: { merge: "replace", from: NOT_ENV, plan: ALWAYS },
|
|
26
|
+
judge: { merge: "replace", from: NOT_ENV, plan: ALWAYS },
|
|
27
|
+
sampling: { merge: "replace", from: ["file", "account"], plan: ALWAYS },
|
|
28
|
+
include: { merge: "concat", from: NOT_ENV, plan: ALWAYS },
|
|
29
|
+
exclude: { merge: "concat", from: NOT_ENV, plan: ALWAYS },
|
|
30
|
+
rules: {
|
|
31
|
+
merge: "concat",
|
|
32
|
+
from: ["shared", "account"],
|
|
33
|
+
// Where each rule applies and whose it is; the rule's text can be long.
|
|
34
|
+
plan: {
|
|
35
|
+
list: "always",
|
|
36
|
+
show: (rules) => rules.map(({ path, source }) => ({ path, source })),
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
ultra: { merge: "replace", from: ["account", "flag"], plan: { list: "set" } },
|
|
40
|
+
$schema: { merge: "replace", from: ["shared", "file"] },
|
|
41
|
+
github: { merge: "entries", from: ["shared", "file"] },
|
|
42
|
+
plugins: { merge: "replace", from: ["file"] },
|
|
43
|
+
pluginSettings: { merge: "replace", from: ["file"] },
|
|
44
|
+
providers: { merge: "entries", from: ["shared", "file"] },
|
|
45
|
+
extends: { merge: "replace", from: ["file"] },
|
|
46
|
+
};
|
|
47
|
+
// The configuration file's schema without its defaults, so a layer holds
|
|
48
|
+
// only what it sets; unknown keys are refused as in the file.
|
|
49
|
+
export const layerSchema = z
|
|
50
|
+
.object(Object.fromEntries(Object.entries(configSchema.shape).map(([key, schema]) => [
|
|
51
|
+
key,
|
|
52
|
+
(schema instanceof z.ZodDefault ? schema.unwrap() : schema).optional(),
|
|
53
|
+
])))
|
|
54
|
+
.strict();
|
|
55
|
+
/** The layers, earliest first, over ocra's defaults, and where each setting came from. */
|
|
56
|
+
export function resolveSettings(layers) {
|
|
57
|
+
const merged = { ...configSchema.parse({}), rules: [], ultra: false };
|
|
58
|
+
const sources = {};
|
|
59
|
+
const record = (name, source) => {
|
|
60
|
+
const from = sources[name] ?? [];
|
|
61
|
+
if (!from.includes(source))
|
|
62
|
+
sources[name] = [...from, source];
|
|
63
|
+
};
|
|
64
|
+
for (const { source, settings, under = false } of layers) {
|
|
65
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
66
|
+
if (value === undefined)
|
|
67
|
+
continue;
|
|
68
|
+
const rule = MERGE[key];
|
|
69
|
+
if (!rule.from.includes(source)) {
|
|
70
|
+
throw new OcraError("INTERNAL", `the ${source} settings may not set ${key}`);
|
|
71
|
+
}
|
|
72
|
+
if (rule.merge === "replace") {
|
|
73
|
+
if (under && sources[key])
|
|
74
|
+
continue;
|
|
75
|
+
merged[key] = value;
|
|
76
|
+
sources[key] = [source];
|
|
77
|
+
}
|
|
78
|
+
else if (rule.merge === "entries") {
|
|
79
|
+
const entries = { ...merged[key] };
|
|
80
|
+
for (const [entry, setting] of Object.entries(value)) {
|
|
81
|
+
const name = `${key}.${entry}`;
|
|
82
|
+
if (setting === undefined || (under && sources[name]))
|
|
83
|
+
continue;
|
|
84
|
+
entries[entry] = setting;
|
|
85
|
+
sources[name] = [source];
|
|
86
|
+
}
|
|
87
|
+
merged[key] = entries;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
const current = merged[key];
|
|
91
|
+
const added = under
|
|
92
|
+
? value.filter((v) => !current.some((c) => isDeepStrictEqual(c, v)))
|
|
93
|
+
: value;
|
|
94
|
+
if (added.length === 0)
|
|
95
|
+
continue;
|
|
96
|
+
merged[key] = [...current, ...added];
|
|
97
|
+
record(key, source);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return { settings: merged, sources };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The settings --plan lists, in its order, with their sources. A setting
|
|
105
|
+
* only a command-line flag set is the flag's, not a setting, and is left
|
|
106
|
+
* out.
|
|
107
|
+
*/
|
|
108
|
+
export function listSettings(settings, sources) {
|
|
109
|
+
const listed = [];
|
|
110
|
+
for (const [key, rule] of Object.entries(MERGE)) {
|
|
111
|
+
const plan = rule.plan;
|
|
112
|
+
if (!plan)
|
|
113
|
+
continue;
|
|
114
|
+
const value = settings[key];
|
|
115
|
+
const add = (name, shown, always) => {
|
|
116
|
+
const from = (sources[name] ?? []).filter((source) => source !== "flag");
|
|
117
|
+
if (from.length === 0 && !always)
|
|
118
|
+
return;
|
|
119
|
+
listed.push(from.length === 0 ? { key: name, sources: [] } : { key: name, value: shown, sources: from });
|
|
120
|
+
};
|
|
121
|
+
if (rule.merge === "entries") {
|
|
122
|
+
const map = value;
|
|
123
|
+
for (const entry of plan.entries ?? Object.keys(map)) {
|
|
124
|
+
add(`${key}.${entry}`, map[entry], plan.list === "always");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
add(key, plan.show ? plan.show(value) : value, plan.list === "always");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return listed;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=settings.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** ~/.config/ocra ($XDG_CONFIG_HOME/ocra), or %APPDATA%\ocra on Windows: this machine's own. */
|
|
4
|
+
export function ocraConfigDir(env) {
|
|
5
|
+
const base = process.platform === "win32"
|
|
6
|
+
? (env.APPDATA ?? join(homedir(), "AppData", "Roaming"))
|
|
7
|
+
: (env.XDG_CONFIG_HOME ?? join(homedir(), ".config"));
|
|
8
|
+
return join(base, "ocra");
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=user-dir.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export
|
|
1
|
+
export declare function run(argv: readonly string[]): Promise<number>;
|
|
2
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
// The public API of @open-cr-agent/cli, a contract under the 0.x rule
|
|
2
|
+
// (manual: Embedding, Stability); etc/cli.api.md records it. What the
|
|
3
|
+
// other workspace packages need beyond this comes from
|
|
4
|
+
// "@open-cr-agent/cli/internal", which is not a contract.
|
|
5
|
+
import { defaultDeps, run as runWith } from "./run.js";
|
|
6
|
+
// The `ocra` command with its arguments (no node or script path), writing
|
|
7
|
+
// to stdout and stderr; resolves to the exit code.
|
|
8
|
+
export function run(argv) {
|
|
9
|
+
return runWith([...argv], process.stdout, process.stderr, defaultDeps());
|
|
10
|
+
}
|
|
2
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type { ReviewDeps } from "./commands/review/deps.js";
|
|
2
|
+
export { type CliPlanOutput, cliPlanOutputSchema, planJsonSchema, } from "./commands/review/plan-schema.js";
|
|
3
|
+
export { configJsonSchema } from "./config/schema.js";
|
|
4
|
+
export type { Output } from "./io/output.js";
|
|
5
|
+
export { run } from "./run.js";
|
|
6
|
+
//# sourceMappingURL=internal.d.ts.map
|
package/dist/internal.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// "@open-cr-agent/cli/internal": what ocra's own packages share beyond the
|
|
2
|
+
// public API (index.ts), such as the command with its dependencies
|
|
3
|
+
// injected. Not a contract: any release may change or remove any of it.
|
|
4
|
+
export { cliPlanOutputSchema, planJsonSchema, } from "./commands/review/plan-schema.js";
|
|
5
|
+
export { configJsonSchema } from "./config/schema.js";
|
|
6
|
+
export { run } from "./run.js";
|
|
7
|
+
//# sourceMappingURL=internal.js.map
|
package/dist/io/exit.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Exit codes are a contract (docs/manual, "Exit codes"): every command
|
|
2
|
+
// returns one of these, never a bare number.
|
|
3
|
+
export const EXIT = {
|
|
4
|
+
ok: 0,
|
|
5
|
+
blocking: 1,
|
|
6
|
+
// ocra login, logout and whoami: no usable ocra Cloud session.
|
|
7
|
+
notSignedIn: 1,
|
|
8
|
+
error: 2,
|
|
9
|
+
incomplete: 3,
|
|
10
|
+
interrupted: 130,
|
|
11
|
+
};
|
|
12
|
+
//# sourceMappingURL=exit.js.map
|
package/dist/main.js
CHANGED
|
@@ -10,7 +10,7 @@ export declare function parseAccountPlugins(body: unknown, warn: (message: strin
|
|
|
10
10
|
* never with --no-repo-config); a package the configuration already lists
|
|
11
11
|
* loads as the configuration's, and a plugin whose name is taken is skipped.
|
|
12
12
|
*/
|
|
13
|
-
export declare function
|
|
13
|
+
export declare function loadAccountPlugins(account: AccountPlugins, options: {
|
|
14
14
|
allowed: boolean;
|
|
15
15
|
configured: readonly string[];
|
|
16
16
|
taken: readonly OcraPlugin[];
|
|
@@ -20,4 +20,4 @@ export declare function accountPlugins(account: AccountPlugins, options: {
|
|
|
20
20
|
plugins: OcraPlugin[];
|
|
21
21
|
settings: Record<string, unknown>;
|
|
22
22
|
}>;
|
|
23
|
-
//# sourceMappingURL=account
|
|
23
|
+
//# sourceMappingURL=account.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { loadAllowedPlugins } from "./load.js";
|
|
2
|
+
import { isPackageName } from "./store.js";
|
|
3
3
|
const MAX_PLUGINS = 50;
|
|
4
4
|
export function parseAccountPlugins(body, warn) {
|
|
5
5
|
const settings = record(record(body).settings);
|
|
@@ -22,7 +22,7 @@ export function parseAccountPlugins(body, warn) {
|
|
|
22
22
|
* never with --no-repo-config); a package the configuration already lists
|
|
23
23
|
* loads as the configuration's, and a plugin whose name is taken is skipped.
|
|
24
24
|
*/
|
|
25
|
-
export async function
|
|
25
|
+
export async function loadAccountPlugins(account, options) {
|
|
26
26
|
const names = account.plugins.filter((name) => !options.configured.includes(name));
|
|
27
27
|
if (names.length === 0)
|
|
28
28
|
return { plugins: [], settings: {} };
|
|
@@ -33,15 +33,15 @@ export async function accountPlugins(account, options) {
|
|
|
33
33
|
const taken = new Set(options.taken.map((p) => p.name));
|
|
34
34
|
const plugins = [];
|
|
35
35
|
const settings = {};
|
|
36
|
-
for (const { name, plugin } of await
|
|
36
|
+
for (const { name, plugin } of await loadAllowedPlugins(names, options.dir, options.warn)) {
|
|
37
37
|
if (taken.has(plugin.name)) {
|
|
38
38
|
options.warn(`skipping plugin ${name} from your ocra Cloud settings: a plugin named "${plugin.name}" is already loaded`);
|
|
39
39
|
continue;
|
|
40
40
|
}
|
|
41
41
|
taken.add(plugin.name);
|
|
42
42
|
plugins.push(plugin);
|
|
43
|
-
if (Object.hasOwn(account.pluginSettings,
|
|
44
|
-
settings[plugin.name] = account.pluginSettings[
|
|
43
|
+
if (Object.hasOwn(account.pluginSettings, name)) {
|
|
44
|
+
settings[plugin.name] = account.pluginSettings[name];
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
return { plugins, settings };
|
|
@@ -51,4 +51,4 @@ function record(value) {
|
|
|
51
51
|
? value
|
|
52
52
|
: {};
|
|
53
53
|
}
|
|
54
|
-
//# sourceMappingURL=account
|
|
54
|
+
//# sourceMappingURL=account.js.map
|
|
@@ -6,8 +6,8 @@ export declare function loadExternalPlugins(specifiers: readonly string[], root:
|
|
|
6
6
|
* when it was allowed. Anything else is skipped with a warning; an account
|
|
7
7
|
* plugin never fails the review.
|
|
8
8
|
*/
|
|
9
|
-
export declare function
|
|
9
|
+
export declare function loadAllowedPlugins(names: readonly string[], dir: string, warn: (message: string) => void): Promise<{
|
|
10
10
|
name: string;
|
|
11
11
|
plugin: OcraPlugin;
|
|
12
12
|
}[]>;
|
|
13
|
-
//# sourceMappingURL=
|
|
13
|
+
//# sourceMappingURL=load.d.ts.map
|
|
@@ -2,8 +2,9 @@ import { realpath } from "node:fs/promises";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { isAbsolute, join, resolve, sep } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
|
-
import {
|
|
6
|
-
import { ConfigError } from "
|
|
5
|
+
import { errorMessage } from "@open-cr-agent/core";
|
|
6
|
+
import { ConfigError } from "../config/cli-config.js";
|
|
7
|
+
import { installedEntry, readAllowed } from "./store.js";
|
|
7
8
|
// Plugins listed in the repository config execute code, so they are resolved
|
|
8
9
|
// only from the repository's own dependencies or relative paths inside it.
|
|
9
10
|
export async function loadExternalPlugins(specifiers, root) {
|
|
@@ -37,7 +38,7 @@ function isPlugin(value) {
|
|
|
37
38
|
* when it was allowed. Anything else is skipped with a warning; an account
|
|
38
39
|
* plugin never fails the review.
|
|
39
40
|
*/
|
|
40
|
-
export async function
|
|
41
|
+
export async function loadAllowedPlugins(names, dir, warn) {
|
|
41
42
|
const allowed = new Map((await readAllowed(dir)).map((p) => [p.name, p]));
|
|
42
43
|
const missing = names.filter((name) => !allowed.has(name));
|
|
43
44
|
if (missing.length > 0) {
|
|
@@ -60,7 +61,7 @@ export async function loadAccountPlugins(names, dir, warn) {
|
|
|
60
61
|
loaded.push({ name, plugin });
|
|
61
62
|
}
|
|
62
63
|
catch (error) {
|
|
63
|
-
warn(`skipping plugin ${name} from your ocra Cloud settings: ${error
|
|
64
|
+
warn(`skipping plugin ${name} from your ocra Cloud settings: ${errorMessage(error)}`);
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
return loaded;
|
|
@@ -74,4 +75,4 @@ async function allowedEntry(dir, name) {
|
|
|
74
75
|
throw new Error(`it resolves outside ${dir}`);
|
|
75
76
|
return entry;
|
|
76
77
|
}
|
|
77
|
-
//# sourceMappingURL=
|
|
78
|
+
//# sourceMappingURL=load.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs npm with these arguments in `cwd`, never through a shell; resolves
|
|
3
|
+
* with its standard output. Every call runs in the plugin directory, so the
|
|
4
|
+
* npm configuration of whatever project ocra was started in does not apply.
|
|
5
|
+
*/
|
|
6
|
+
export type NpmRunner = (args: readonly string[], cwd: string) => Promise<string>;
|
|
7
|
+
export declare function defaultNpm(env?: NodeJS.ProcessEnv): NpmRunner;
|
|
8
|
+
//# sourceMappingURL=npm.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { ConfigError } from "../config/cli-config.js";
|
|
4
|
+
import { forTerminal } from "../io/terminal.js";
|
|
5
|
+
// npm on Windows is npm.cmd, which execFile cannot run without a shell; run
|
|
6
|
+
// the npm that ships with this Node directly instead.
|
|
7
|
+
export function defaultNpm(env = process.env) {
|
|
8
|
+
const [file, prefix] = process.platform === "win32"
|
|
9
|
+
? [
|
|
10
|
+
process.execPath,
|
|
11
|
+
[join(dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js")],
|
|
12
|
+
]
|
|
13
|
+
: ["npm", []];
|
|
14
|
+
return (args, cwd) => new Promise((resolve, reject) => {
|
|
15
|
+
execFile(file, [...prefix, ...args], { cwd, env, timeout: 5 * 60_000, maxBuffer: 16 * 1024 * 1024, shell: false }, (error, stdout, stderr) => {
|
|
16
|
+
if (error) {
|
|
17
|
+
reject(new ConfigError(`npm ${args[0]} failed: ${forTerminal(String(stderr).trim().split("\n").slice(-3).join(" ") || error.message)}`));
|
|
18
|
+
}
|
|
19
|
+
else
|
|
20
|
+
resolve(String(stdout));
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=npm.js.map
|