@oxygen-agent/cli 1.1010.721 → 1.1010.905
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 +1 -1
- package/dist/auto-update.d.ts +129 -0
- package/dist/auto-update.js +392 -0
- package/dist/command-manifest.js +14 -0
- package/dist/credentials.d.ts +2 -0
- package/dist/credentials.js +6 -3
- package/dist/functions-commands.js +1 -1
- package/dist/http-client.js +28 -4
- package/dist/index.js +583 -145
- package/dist/run-wait.d.ts +3 -1
- package/dist/run-wait.js +19 -5
- package/dist/streamed-file-import.d.ts +58 -0
- package/dist/streamed-file-import.js +115 -0
- package/dist/update.d.ts +29 -0
- package/dist/update.js +62 -16
- package/dist/workflow-plan-limit-notices.d.ts +8 -0
- package/dist/workflow-plan-limit-notices.js +28 -0
- package/node_modules/@oxygen/cli-ugc/dist/commands.js +3 -3
- package/node_modules/@oxygen/shared/dist/billing-anchors.d.ts +17 -0
- package/node_modules/@oxygen/shared/dist/billing-anchors.js +27 -0
- package/node_modules/@oxygen/shared/dist/billing.d.ts +191 -35
- package/node_modules/@oxygen/shared/dist/billing.js +333 -42
- package/node_modules/@oxygen/shared/dist/capability-discovery.js +55 -5
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.d.ts +2 -2
- package/node_modules/@oxygen/shared/dist/copilot-skills.generated.js +2 -2
- package/node_modules/@oxygen/shared/dist/cost-estimate-view.d.ts +50 -0
- package/node_modules/@oxygen/shared/dist/cost-estimate-view.js +90 -0
- package/node_modules/@oxygen/shared/dist/cost-estimate.d.ts +167 -0
- package/node_modules/@oxygen/shared/dist/cost-estimate.js +361 -0
- package/node_modules/@oxygen/shared/dist/credit-gate.d.ts +26 -0
- package/node_modules/@oxygen/shared/dist/credit-gate.js +65 -0
- package/node_modules/@oxygen/shared/dist/email-deliverability-policy.d.ts +51 -0
- package/node_modules/@oxygen/shared/dist/email-deliverability-policy.js +101 -0
- package/node_modules/@oxygen/shared/dist/email-hard-bounce.d.ts +3 -1
- package/node_modules/@oxygen/shared/dist/email-hard-bounce.js +3 -3
- package/node_modules/@oxygen/shared/dist/error-redaction.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/error-redaction.js +1 -1
- package/node_modules/@oxygen/shared/dist/feature-gates.d.ts +4 -0
- package/node_modules/@oxygen/shared/dist/feature-gates.js +5 -0
- package/node_modules/@oxygen/shared/dist/file-import.d.ts +13 -1
- package/node_modules/@oxygen/shared/dist/file-import.js +33 -6
- package/node_modules/@oxygen/shared/dist/hosted-ai.d.ts +73 -3
- package/node_modules/@oxygen/shared/dist/hosted-ai.js +246 -24
- package/node_modules/@oxygen/shared/dist/import-limits.d.ts +25 -1
- package/node_modules/@oxygen/shared/dist/import-limits.js +35 -2
- package/node_modules/@oxygen/shared/dist/index.d.ts +2 -22
- package/node_modules/@oxygen/shared/dist/index.js +2 -42
- package/node_modules/@oxygen/shared/dist/object-storage.d.ts +9 -0
- package/node_modules/@oxygen/shared/dist/object-storage.js +17 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.d.ts +41 -0
- package/node_modules/@oxygen/shared/dist/operational-telemetry.js +55 -0
- package/node_modules/@oxygen/shared/dist/plan-band.d.ts +117 -1
- package/node_modules/@oxygen/shared/dist/plan-band.js +175 -10
- package/node_modules/@oxygen/shared/dist/plan-capabilities.d.ts +77 -7
- package/node_modules/@oxygen/shared/dist/plan-capabilities.js +87 -7
- package/node_modules/@oxygen/shared/dist/plan-limits-view.d.ts +219 -0
- package/node_modules/@oxygen/shared/dist/plan-limits-view.js +330 -0
- package/node_modules/@oxygen/shared/dist/plan-limits.d.ts +204 -6
- package/node_modules/@oxygen/shared/dist/plan-limits.js +197 -15
- package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +80 -36
- package/node_modules/@oxygen/shared/dist/pricing-sheet.js +80 -31
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.d.ts +38 -20
- package/node_modules/@oxygen/shared/dist/pricing-snapshot.generated.js +47 -34
- package/node_modules/@oxygen/shared/dist/provider-http-error.d.ts +10 -0
- package/node_modules/@oxygen/shared/dist/provider-http-error.js +27 -0
- package/node_modules/@oxygen/shared/dist/repricing.d.ts +127 -0
- package/node_modules/@oxygen/shared/dist/repricing.js +407 -6
- package/node_modules/@oxygen/shared/dist/semver.d.ts +21 -0
- package/node_modules/@oxygen/shared/dist/semver.js +41 -0
- package/node_modules/@oxygen/shared/dist/sending-limits.d.ts +5 -7
- package/node_modules/@oxygen/shared/dist/sending-limits.js +10 -16
- package/node_modules/@oxygen/shared/dist/sending-seats.d.ts +18 -15
- package/node_modules/@oxygen/shared/dist/sending-seats.js +22 -17
- package/node_modules/@oxygen/shared/dist/spend-safety.d.ts +57 -8
- package/node_modules/@oxygen/shared/dist/spend-safety.js +64 -11
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.d.ts +15 -7
- package/node_modules/@oxygen/shared/dist/stripe-price-catalog.js +18 -5
- package/node_modules/@oxygen/shared/dist/table-capacity.d.ts +34 -5
- package/node_modules/@oxygen/shared/dist/table-capacity.js +25 -8
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.d.ts +6 -0
- package/node_modules/@oxygen/shared/dist/telemetry-export-observer.js +13 -5
- package/node_modules/@oxygen/shared/dist/telemetry-resource.d.ts +40 -0
- package/node_modules/@oxygen/shared/dist/telemetry-resource.js +35 -0
- package/node_modules/@oxygen/shared/dist/telemetry.js +5 -0
- package/node_modules/@oxygen/shared/dist/ugc.d.ts +15 -0
- package/node_modules/@oxygen/shared/dist/ugc.js +29 -0
- package/node_modules/@oxygen/shared/dist/version.d.ts +1 -3
- package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
- package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
- package/node_modules/@oxygen/shared/dist/version.js +14 -27
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +5 -0
- package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +5 -0
- package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +3 -3
- package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +15 -1
- package/node_modules/@oxygen/workflows/dist/graph/types.js +15 -1
- package/node_modules/@oxygen/workflows/dist/index.d.ts +45 -0
- package/node_modules/@oxygen/workflows/dist/index.js +152 -2
- package/node_modules/@oxygen/workflows/dist/usage-estimate.d.ts +10 -1
- package/node_modules/@oxygen/workflows/dist/usage-estimate.js +33 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
2
|
+
import { type CliEndpoint } from "./runtime.js";
|
|
3
|
+
import { type UpdateOptions } from "./update.js";
|
|
4
|
+
export declare const AUTO_UPDATE_DISABLE_ENV = "OXYGEN_NO_AUTO_UPDATE";
|
|
5
|
+
/** Set on the re-run after a floor-driven update; stops a second attempt. */
|
|
6
|
+
export declare const AUTO_UPDATED_FROM_ENV = "OXYGEN_CLI_AUTO_UPDATED";
|
|
7
|
+
export declare const BACKGROUND_CHECK_INTERVAL_MS: number;
|
|
8
|
+
export type AutoUpdateResult = {
|
|
9
|
+
at: string;
|
|
10
|
+
from: string;
|
|
11
|
+
to: string | null;
|
|
12
|
+
ok: boolean;
|
|
13
|
+
trigger: "background" | "floor";
|
|
14
|
+
error?: string;
|
|
15
|
+
};
|
|
16
|
+
export type AutoUpdateState = {
|
|
17
|
+
auto_update?: "on" | "off";
|
|
18
|
+
last_check_at?: string;
|
|
19
|
+
latest_seen?: string;
|
|
20
|
+
last_check_error?: string;
|
|
21
|
+
last_result?: AutoUpdateResult;
|
|
22
|
+
notice_pending?: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type AutoUpdateIneligibleReason = "ci" | "disabled_by_env" | "disabled_by_setting" | "dev_binary" | "not_global_npm_install" | "non_production_endpoint";
|
|
25
|
+
export type AutoUpdateEligibility = {
|
|
26
|
+
eligible: true;
|
|
27
|
+
} | {
|
|
28
|
+
eligible: false;
|
|
29
|
+
reason: AutoUpdateIneligibleReason;
|
|
30
|
+
};
|
|
31
|
+
type SpawnSyncFn = (command: string, args: string[], options: {
|
|
32
|
+
stdio: "inherit" | "ignore";
|
|
33
|
+
env: NodeJS.ProcessEnv;
|
|
34
|
+
windowsHide: boolean;
|
|
35
|
+
timeout?: number;
|
|
36
|
+
}) => {
|
|
37
|
+
status: number | null;
|
|
38
|
+
error?: Error;
|
|
39
|
+
};
|
|
40
|
+
type SpawnDetachedFn = (command: string, args: string[], env: NodeJS.ProcessEnv) => void;
|
|
41
|
+
export type AutoUpdateRuntime = {
|
|
42
|
+
env?: NodeJS.ProcessEnv;
|
|
43
|
+
argv?: readonly string[];
|
|
44
|
+
execPath?: string;
|
|
45
|
+
modulePath?: string;
|
|
46
|
+
now?: () => Date;
|
|
47
|
+
fetch?: typeof fetch;
|
|
48
|
+
update?: (options: UpdateOptions) => Promise<{
|
|
49
|
+
updated: boolean;
|
|
50
|
+
}>;
|
|
51
|
+
spawnSync?: SpawnSyncFn;
|
|
52
|
+
spawnDetached?: SpawnDetachedFn;
|
|
53
|
+
/** Reads the version npm left on disk; defaults to the running package's package.json. */
|
|
54
|
+
readInstalledVersion?: () => string | null;
|
|
55
|
+
stderr?: (text: string) => void;
|
|
56
|
+
exit?: (code: number) => never;
|
|
57
|
+
};
|
|
58
|
+
export declare function recordMutatingRequestAnswered(): void;
|
|
59
|
+
/** Call before reading piped stdin: a re-run could not read it a second time. */
|
|
60
|
+
export declare function markStdinConsumed(): void;
|
|
61
|
+
/** Test-only: reset the per-process guards. */
|
|
62
|
+
export declare function resetAutoUpdateProcessGuards(): void;
|
|
63
|
+
export declare function autoUpdateStatePath(env?: NodeJS.ProcessEnv): string;
|
|
64
|
+
export declare function readAutoUpdateState(env?: NodeJS.ProcessEnv): AutoUpdateState;
|
|
65
|
+
/** Best-effort: an unwritable config directory must never fail a command. */
|
|
66
|
+
export declare function writeAutoUpdateState(state: AutoUpdateState, env?: NodeJS.ProcessEnv): boolean;
|
|
67
|
+
export declare function autoUpdateEligibility(runtime?: AutoUpdateRuntime & {
|
|
68
|
+
endpoint?: CliEndpoint;
|
|
69
|
+
state?: AutoUpdateState;
|
|
70
|
+
}): AutoUpdateEligibility;
|
|
71
|
+
/**
|
|
72
|
+
* The reactive path. Returns the error to throw (annotated with why no update
|
|
73
|
+
* happened), or — after a successful update — re-runs the command in the new
|
|
74
|
+
* binary and exits with its status.
|
|
75
|
+
*/
|
|
76
|
+
export declare function recoverFromCliFloorRejection(error: OxygenError, endpoint: CliEndpoint, runtime?: AutoUpdateRuntime): Promise<OxygenError>;
|
|
77
|
+
/**
|
|
78
|
+
* The proactive path, called once at CLI start. Never blocks: it reads a small
|
|
79
|
+
* state file, prints any pending notice to stderr, and — at most once a day —
|
|
80
|
+
* spawns a detached `update --background` worker.
|
|
81
|
+
*/
|
|
82
|
+
export declare function maybeScheduleBackgroundUpdate(runtime?: AutoUpdateRuntime): void;
|
|
83
|
+
export declare function fetchLatestCliVersion(fetchImpl?: typeof fetch): Promise<string>;
|
|
84
|
+
export type BackgroundUpdateOutcome = {
|
|
85
|
+
action: "skipped";
|
|
86
|
+
reason: AutoUpdateIneligibleReason;
|
|
87
|
+
} | {
|
|
88
|
+
action: "check_failed";
|
|
89
|
+
error: string;
|
|
90
|
+
} | {
|
|
91
|
+
action: "up_to_date";
|
|
92
|
+
latest: string;
|
|
93
|
+
} | {
|
|
94
|
+
action: "updated";
|
|
95
|
+
latest: string;
|
|
96
|
+
} | {
|
|
97
|
+
action: "update_failed";
|
|
98
|
+
latest: string;
|
|
99
|
+
error: string;
|
|
100
|
+
};
|
|
101
|
+
/** The detached worker behind `oxygen update --background`. Prints nothing. */
|
|
102
|
+
export declare function runBackgroundUpdate(runtime?: AutoUpdateRuntime): Promise<BackgroundUpdateOutcome>;
|
|
103
|
+
export type AutoUpdateStatus = {
|
|
104
|
+
current_version: string;
|
|
105
|
+
latest_version: string | null;
|
|
106
|
+
latest_error?: string;
|
|
107
|
+
update_available: boolean | null;
|
|
108
|
+
/** Present for --enable-auto/--disable-auto: a dry run reports what would change and writes nothing. */
|
|
109
|
+
dry_run?: boolean;
|
|
110
|
+
setting_changed?: boolean;
|
|
111
|
+
auto_update: {
|
|
112
|
+
setting: "on" | "off";
|
|
113
|
+
active: boolean;
|
|
114
|
+
reason: AutoUpdateIneligibleReason | null;
|
|
115
|
+
reason_text: string | null;
|
|
116
|
+
/** The step that works here: how to turn updates on, or how to update by hand. Null while active. */
|
|
117
|
+
next_step: string | null;
|
|
118
|
+
disable: string;
|
|
119
|
+
last_check_at: string | null;
|
|
120
|
+
last_result: AutoUpdateResult | null;
|
|
121
|
+
};
|
|
122
|
+
state_path: string;
|
|
123
|
+
};
|
|
124
|
+
/** `oxygen update --status`, `--enable-auto`, `--disable-auto`. Checks npm live. */
|
|
125
|
+
export declare function readAutoUpdateStatus(runtime?: AutoUpdateRuntime & {
|
|
126
|
+
setting?: "on" | "off";
|
|
127
|
+
dryRun?: boolean;
|
|
128
|
+
}): Promise<AutoUpdateStatus>;
|
|
129
|
+
export {};
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { OxygenError } from "@oxygen/shared/cli-result";
|
|
6
|
+
import { isVersionGreater, parseSemver } from "@oxygen/shared/semver";
|
|
7
|
+
import { OXYGEN_VERSION } from "@oxygen/shared/version";
|
|
8
|
+
import { resolveConfigDir } from "./credentials.js";
|
|
9
|
+
import { resolveCliBinaryName, resolveCliUpdateGuidance } from "./runtime.js";
|
|
10
|
+
import { detectCliInstall, readInstalledCliVersion, updateCli } from "./update.js";
|
|
11
|
+
// The npm `oxygen` CLI keeps itself current in two ways:
|
|
12
|
+
//
|
|
13
|
+
// 1. Floor rejection (reactive). When the API refuses this CLI with
|
|
14
|
+
// `cli_update_required`, the CLI installs the newest release and re-runs the
|
|
15
|
+
// exact command once. The API refuses before doing any work, so the re-run
|
|
16
|
+
// cannot repeat a side effect — and the client refuses to re-run once any
|
|
17
|
+
// mutating request of its own has been answered in this process.
|
|
18
|
+
// 2. Background (proactive). At most once a day a detached child checks npm and
|
|
19
|
+
// installs a newer release; the command that spawned it never waits, and the
|
|
20
|
+
// next run prints one stderr line saying what happened.
|
|
21
|
+
//
|
|
22
|
+
// Neither ever runs for `oxygen-dev`, under CI, for a CLI that is not a global
|
|
23
|
+
// npm install (npx, a source checkout), or once turned off with
|
|
24
|
+
// `oxygen update --disable-auto` / OXYGEN_NO_AUTO_UPDATE=1.
|
|
25
|
+
export const AUTO_UPDATE_DISABLE_ENV = "OXYGEN_NO_AUTO_UPDATE";
|
|
26
|
+
/** Set on the re-run after a floor-driven update; stops a second attempt. */
|
|
27
|
+
export const AUTO_UPDATED_FROM_ENV = "OXYGEN_CLI_AUTO_UPDATED";
|
|
28
|
+
export const BACKGROUND_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
29
|
+
const CLI_PACKAGE_NAME = "@oxygen-agent/cli";
|
|
30
|
+
const NPM_DIST_TAGS_URL = "https://registry.npmjs.org/-/package/@oxygen-agent%2Fcli/dist-tags";
|
|
31
|
+
const NPM_CHECK_TIMEOUT_MS = 10_000;
|
|
32
|
+
const SKILLS_REFRESH_TIMEOUT_MS = 120_000;
|
|
33
|
+
// Why automatic updates cannot run, and the one step that works instead. Both
|
|
34
|
+
// ship in the `--status` envelope: an agent reads JSON, so a remedy printed only
|
|
35
|
+
// in the terminal text never reaches it.
|
|
36
|
+
const AUTO_UPDATE_REASON_TEXT = {
|
|
37
|
+
ci: "off because CI is set",
|
|
38
|
+
disabled_by_env: "off because OXYGEN_NO_AUTO_UPDATE is set",
|
|
39
|
+
disabled_by_setting: "off (turned off with --disable-auto)",
|
|
40
|
+
dev_binary: "not available for oxygen-dev; rebuild it from the dev branch",
|
|
41
|
+
not_global_npm_install: "not available: this CLI is not a global npm install (npx or a project's node_modules)",
|
|
42
|
+
non_production_endpoint: "not available against a non-production API",
|
|
43
|
+
};
|
|
44
|
+
const AUTO_UPDATE_NEXT_STEP = {
|
|
45
|
+
ci: "Update deliberately in CI: oxygen update --package @oxygen-agent/cli@<version>",
|
|
46
|
+
disabled_by_env: "Unset OXYGEN_NO_AUTO_UPDATE to turn automatic updates back on",
|
|
47
|
+
disabled_by_setting: "oxygen update --enable-auto",
|
|
48
|
+
dev_binary: "npm run install-dev-cli (from the latest dev checkout)",
|
|
49
|
+
not_global_npm_install: "Install globally to get automatic updates: npm install -g @oxygen-agent/cli (or update in place: npm install @oxygen-agent/cli@latest)",
|
|
50
|
+
non_production_endpoint: "oxygen update",
|
|
51
|
+
};
|
|
52
|
+
// A command that already sent a mutating request must never be re-run whole:
|
|
53
|
+
// the per-route floors can refuse a LATER request after an earlier one wrote.
|
|
54
|
+
let mutatingRequestAnswered = false;
|
|
55
|
+
let stdinConsumed = false;
|
|
56
|
+
export function recordMutatingRequestAnswered() {
|
|
57
|
+
mutatingRequestAnswered = true;
|
|
58
|
+
}
|
|
59
|
+
/** Call before reading piped stdin: a re-run could not read it a second time. */
|
|
60
|
+
export function markStdinConsumed() {
|
|
61
|
+
stdinConsumed = true;
|
|
62
|
+
}
|
|
63
|
+
/** Test-only: reset the per-process guards. */
|
|
64
|
+
export function resetAutoUpdateProcessGuards() {
|
|
65
|
+
mutatingRequestAnswered = false;
|
|
66
|
+
stdinConsumed = false;
|
|
67
|
+
}
|
|
68
|
+
export function autoUpdateStatePath(env = process.env) {
|
|
69
|
+
return join(resolveConfigDir(env), "update-state.json");
|
|
70
|
+
}
|
|
71
|
+
export function readAutoUpdateState(env = process.env) {
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(readFileSync(autoUpdateStatePath(env), "utf8"));
|
|
74
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** Best-effort: an unwritable config directory must never fail a command. */
|
|
81
|
+
export function writeAutoUpdateState(state, env = process.env) {
|
|
82
|
+
try {
|
|
83
|
+
const path = autoUpdateStatePath(env);
|
|
84
|
+
mkdirSync(resolveConfigDir(env), { recursive: true });
|
|
85
|
+
const temp = `${path}.${process.pid}.tmp`;
|
|
86
|
+
writeFileSync(temp, `${JSON.stringify(state, null, 2)}\n`, { mode: 0o600 });
|
|
87
|
+
renameSync(temp, path);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function isTruthyEnv(value) {
|
|
95
|
+
const normalized = value?.trim().toLowerCase();
|
|
96
|
+
return Boolean(normalized && normalized !== "0" && normalized !== "false");
|
|
97
|
+
}
|
|
98
|
+
export function autoUpdateEligibility(runtime = {}) {
|
|
99
|
+
const env = runtime.env ?? process.env;
|
|
100
|
+
const argv = runtime.argv ?? process.argv;
|
|
101
|
+
if (isTruthyEnv(env[AUTO_UPDATE_DISABLE_ENV]))
|
|
102
|
+
return { eligible: false, reason: "disabled_by_env" };
|
|
103
|
+
if (isTruthyEnv(env.CI))
|
|
104
|
+
return { eligible: false, reason: "ci" };
|
|
105
|
+
const state = runtime.state ?? readAutoUpdateState(env);
|
|
106
|
+
if (state.auto_update === "off")
|
|
107
|
+
return { eligible: false, reason: "disabled_by_setting" };
|
|
108
|
+
if (resolveCliBinaryName(env, argv) === "oxygen-dev")
|
|
109
|
+
return { eligible: false, reason: "dev_binary" };
|
|
110
|
+
const modulePath = runtime.modulePath ?? fileURLToPath(import.meta.url);
|
|
111
|
+
if (detectCliInstall(modulePath).kind !== "global")
|
|
112
|
+
return { eligible: false, reason: "not_global_npm_install" };
|
|
113
|
+
// Pointed at a non-production API, npm can only reinstall the version that
|
|
114
|
+
// API's floor is refusing; the endpoint is the fault, not the binary.
|
|
115
|
+
if (runtime.endpoint && resolveCliUpdateGuidance(runtime.endpoint, env, argv).channel !== "npm") {
|
|
116
|
+
return { eligible: false, reason: "non_production_endpoint" };
|
|
117
|
+
}
|
|
118
|
+
return { eligible: true };
|
|
119
|
+
}
|
|
120
|
+
function withAutoUpdateDetails(error, autoUpdate) {
|
|
121
|
+
const details = error.details && typeof error.details === "object" && !Array.isArray(error.details)
|
|
122
|
+
? { ...error.details, auto_update: autoUpdate }
|
|
123
|
+
: { auto_update: autoUpdate };
|
|
124
|
+
return new OxygenError(error.code, error.message, { details, exitCode: error.exitCode });
|
|
125
|
+
}
|
|
126
|
+
// npm can exit 0 having written somewhere the running binary never loads, so an
|
|
127
|
+
// automatic update trusts only the version it can read back from its own package.
|
|
128
|
+
function readInstalledVersion(runtime) {
|
|
129
|
+
if (runtime.readInstalledVersion)
|
|
130
|
+
return runtime.readInstalledVersion();
|
|
131
|
+
const install = detectCliInstall(runtime.modulePath ?? fileURLToPath(import.meta.url));
|
|
132
|
+
return install.kind === "global" ? readInstalledCliVersion(install.packageDir) : null;
|
|
133
|
+
}
|
|
134
|
+
function readMinimumCliVersion(error) {
|
|
135
|
+
const details = error.details;
|
|
136
|
+
if (!details || typeof details !== "object")
|
|
137
|
+
return null;
|
|
138
|
+
const value = details.minimum_cli_version;
|
|
139
|
+
return typeof value === "string" ? value : null;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The reactive path. Returns the error to throw (annotated with why no update
|
|
143
|
+
* happened), or — after a successful update — re-runs the command in the new
|
|
144
|
+
* binary and exits with its status.
|
|
145
|
+
*/
|
|
146
|
+
export async function recoverFromCliFloorRejection(error, endpoint, runtime = {}) {
|
|
147
|
+
if (error.code !== "cli_update_required")
|
|
148
|
+
return error;
|
|
149
|
+
const env = runtime.env ?? process.env;
|
|
150
|
+
const argv = runtime.argv ?? process.argv;
|
|
151
|
+
const stderr = runtime.stderr ?? ((text) => { process.stderr.write(text); });
|
|
152
|
+
if (env[AUTO_UPDATED_FROM_ENV]) {
|
|
153
|
+
return withAutoUpdateDetails(error, { attempted: false, reason: "already_updated_once", updated_from: env[AUTO_UPDATED_FROM_ENV] });
|
|
154
|
+
}
|
|
155
|
+
const eligibility = autoUpdateEligibility({ ...runtime, endpoint });
|
|
156
|
+
if (!eligibility.eligible)
|
|
157
|
+
return withAutoUpdateDetails(error, { attempted: false, reason: eligibility.reason });
|
|
158
|
+
if (mutatingRequestAnswered)
|
|
159
|
+
return withAutoUpdateDetails(error, { attempted: false, reason: "command_already_wrote" });
|
|
160
|
+
if (stdinConsumed)
|
|
161
|
+
return withAutoUpdateDetails(error, { attempted: false, reason: "stdin_consumed" });
|
|
162
|
+
const minimum = readMinimumCliVersion(error);
|
|
163
|
+
stderr(`[oxygen] CLI ${OXYGEN_VERSION} is older than this API accepts${minimum ? ` (minimum ${minimum})` : ""}; updating automatically...\n`);
|
|
164
|
+
const now = runtime.now ?? (() => new Date());
|
|
165
|
+
const update = runtime.update ?? ((options) => updateCli(options, { env }));
|
|
166
|
+
const state = readAutoUpdateState(env);
|
|
167
|
+
const fail = (reason, extra = {}) => {
|
|
168
|
+
writeAutoUpdateState({
|
|
169
|
+
...state,
|
|
170
|
+
last_result: { at: now().toISOString(), from: OXYGEN_VERSION, to: null, ok: false, trigger: "floor", error: reason },
|
|
171
|
+
}, env);
|
|
172
|
+
stderr("[oxygen] Automatic update failed. Run `oxygen update` to update manually.\n");
|
|
173
|
+
return withAutoUpdateDetails(error, { attempted: true, ok: false, reason, ...extra });
|
|
174
|
+
};
|
|
175
|
+
try {
|
|
176
|
+
await update({ json: true, skipSkillsInstall: true });
|
|
177
|
+
}
|
|
178
|
+
catch (updateError) {
|
|
179
|
+
const stderrText = updateError instanceof OxygenError && updateError.details && typeof updateError.details === "object"
|
|
180
|
+
? updateError.details.stderr
|
|
181
|
+
: undefined;
|
|
182
|
+
return fail(updateError instanceof Error ? updateError.message : String(updateError), typeof stderrText === "string" ? { npm_stderr: stderrText.slice(0, 2000) } : {});
|
|
183
|
+
}
|
|
184
|
+
const installed = readInstalledVersion(runtime);
|
|
185
|
+
if (!installed || !isVersionGreater(installed, OXYGEN_VERSION)) {
|
|
186
|
+
return fail(`npm finished but this CLI's own install still reports ${installed ?? "no version"}`);
|
|
187
|
+
}
|
|
188
|
+
writeAutoUpdateState({
|
|
189
|
+
...state,
|
|
190
|
+
last_result: { at: now().toISOString(), from: OXYGEN_VERSION, to: installed, ok: true, trigger: "floor" },
|
|
191
|
+
}, env);
|
|
192
|
+
const execPath = runtime.execPath ?? process.execPath;
|
|
193
|
+
const entry = argv[1];
|
|
194
|
+
if (!entry)
|
|
195
|
+
return withAutoUpdateDetails(error, { attempted: true, ok: true, reason: "rerun_unavailable" });
|
|
196
|
+
refreshSkillsInBackground({ ...runtime, env, argv, execPath });
|
|
197
|
+
stderr("[oxygen] Updated. Re-running your command with the new version...\n");
|
|
198
|
+
const run = runtime.spawnSync ?? defaultSpawnSync;
|
|
199
|
+
const child = run(execPath, [entry, ...argv.slice(2)], {
|
|
200
|
+
stdio: "inherit",
|
|
201
|
+
env: { ...env, [AUTO_UPDATED_FROM_ENV]: OXYGEN_VERSION },
|
|
202
|
+
windowsHide: true,
|
|
203
|
+
});
|
|
204
|
+
if (child.error) {
|
|
205
|
+
return withAutoUpdateDetails(error, { attempted: true, ok: true, reason: "rerun_failed", rerun_error: child.error.message });
|
|
206
|
+
}
|
|
207
|
+
const exit = runtime.exit ?? ((code) => process.exit(code));
|
|
208
|
+
return exit(child.status ?? 1);
|
|
209
|
+
}
|
|
210
|
+
/** Refresh the product skills from the NEW binary, detached and best-effort. */
|
|
211
|
+
function refreshSkillsInBackground(runtime) {
|
|
212
|
+
const entry = runtime.argv[1];
|
|
213
|
+
if (!entry)
|
|
214
|
+
return;
|
|
215
|
+
try {
|
|
216
|
+
(runtime.spawnDetached ?? defaultSpawnDetached)(runtime.execPath, [entry, "update", "--refresh-skills"], runtime.env);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Skills refresh is a convenience; `oxygen skills install` remains the manual path.
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
function defaultSpawnSync(...args) {
|
|
223
|
+
const [command, commandArgs, options] = args;
|
|
224
|
+
const result = spawnSync(command, commandArgs, options);
|
|
225
|
+
return { status: result.status, ...(result.error ? { error: result.error } : {}) };
|
|
226
|
+
}
|
|
227
|
+
function defaultSpawnDetached(command, args, env) {
|
|
228
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore", env, windowsHide: true });
|
|
229
|
+
child.on("error", () => undefined);
|
|
230
|
+
child.unref();
|
|
231
|
+
}
|
|
232
|
+
function formatNotice(result) {
|
|
233
|
+
if (result.ok) {
|
|
234
|
+
return `[oxygen] Updated automatically from ${result.from}${result.to ? ` to ${result.to}` : ""}. `
|
|
235
|
+
+ "Turn this off with `oxygen update --disable-auto`.\n";
|
|
236
|
+
}
|
|
237
|
+
return `[oxygen] Automatic update${result.to ? ` to ${result.to}` : ""} failed (${result.error ?? "unknown error"}). `
|
|
238
|
+
+ "Run `oxygen update` to update manually.\n";
|
|
239
|
+
}
|
|
240
|
+
function isUpdateCommand(args) {
|
|
241
|
+
const first = args.find((arg) => !arg.startsWith("-"));
|
|
242
|
+
return first === "update";
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* The proactive path, called once at CLI start. Never blocks: it reads a small
|
|
246
|
+
* state file, prints any pending notice to stderr, and — at most once a day —
|
|
247
|
+
* spawns a detached `update --background` worker.
|
|
248
|
+
*/
|
|
249
|
+
export function maybeScheduleBackgroundUpdate(runtime = {}) {
|
|
250
|
+
try {
|
|
251
|
+
const env = runtime.env ?? process.env;
|
|
252
|
+
const argv = runtime.argv ?? process.argv;
|
|
253
|
+
const args = argv.slice(2);
|
|
254
|
+
if (isUpdateCommand(args) || env[AUTO_UPDATED_FROM_ENV])
|
|
255
|
+
return;
|
|
256
|
+
const now = runtime.now ?? (() => new Date());
|
|
257
|
+
let state = readAutoUpdateState(env);
|
|
258
|
+
if (state.notice_pending && state.last_result) {
|
|
259
|
+
(runtime.stderr ?? ((text) => { process.stderr.write(text); }))(formatNotice(state.last_result));
|
|
260
|
+
state = { ...state, notice_pending: false };
|
|
261
|
+
writeAutoUpdateState(state, env);
|
|
262
|
+
}
|
|
263
|
+
if (!autoUpdateEligibility({ ...runtime, env, argv, state }).eligible)
|
|
264
|
+
return;
|
|
265
|
+
const lastCheck = state.last_check_at ? Date.parse(state.last_check_at) : Number.NaN;
|
|
266
|
+
if (Number.isFinite(lastCheck) && now().getTime() - lastCheck < BACKGROUND_CHECK_INTERVAL_MS)
|
|
267
|
+
return;
|
|
268
|
+
// Claim the slot before spawning so concurrent commands don't stampede npm.
|
|
269
|
+
if (!writeAutoUpdateState({ ...state, last_check_at: now().toISOString() }, env))
|
|
270
|
+
return;
|
|
271
|
+
const entry = argv[1];
|
|
272
|
+
if (!entry)
|
|
273
|
+
return;
|
|
274
|
+
(runtime.spawnDetached ?? defaultSpawnDetached)(runtime.execPath ?? process.execPath, [entry, "update", "--background"], env);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// Keeping the CLI current must never break the command the user ran.
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
export async function fetchLatestCliVersion(fetchImpl = fetch) {
|
|
281
|
+
const response = await fetchImpl(NPM_DIST_TAGS_URL, {
|
|
282
|
+
headers: { Accept: "application/json" },
|
|
283
|
+
signal: AbortSignal.timeout(NPM_CHECK_TIMEOUT_MS),
|
|
284
|
+
});
|
|
285
|
+
if (!response.ok)
|
|
286
|
+
throw new Error(`npm registry answered HTTP ${response.status}`);
|
|
287
|
+
const body = await response.json();
|
|
288
|
+
const latest = typeof body.latest === "string" ? body.latest.trim() : "";
|
|
289
|
+
if (!parseSemver(latest))
|
|
290
|
+
throw new Error("npm registry returned no usable latest version");
|
|
291
|
+
return latest;
|
|
292
|
+
}
|
|
293
|
+
/** The detached worker behind `oxygen update --background`. Prints nothing. */
|
|
294
|
+
export async function runBackgroundUpdate(runtime = {}) {
|
|
295
|
+
const env = runtime.env ?? process.env;
|
|
296
|
+
const argv = runtime.argv ?? process.argv;
|
|
297
|
+
const now = runtime.now ?? (() => new Date());
|
|
298
|
+
const state = readAutoUpdateState(env);
|
|
299
|
+
const eligibility = autoUpdateEligibility({ ...runtime, env, argv, state });
|
|
300
|
+
if (!eligibility.eligible)
|
|
301
|
+
return { action: "skipped", reason: eligibility.reason };
|
|
302
|
+
let latest;
|
|
303
|
+
try {
|
|
304
|
+
latest = await fetchLatestCliVersion(runtime.fetch ?? fetch);
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
308
|
+
writeAutoUpdateState({ ...readAutoUpdateState(env), last_check_error: message }, env);
|
|
309
|
+
return { action: "check_failed", error: message };
|
|
310
|
+
}
|
|
311
|
+
const checked = { ...readAutoUpdateState(env), latest_seen: latest };
|
|
312
|
+
delete checked.last_check_error;
|
|
313
|
+
if (!isVersionGreater(latest, OXYGEN_VERSION)) {
|
|
314
|
+
writeAutoUpdateState(checked, env);
|
|
315
|
+
return { action: "up_to_date", latest };
|
|
316
|
+
}
|
|
317
|
+
const update = runtime.update ?? ((options) => updateCli(options, { env }));
|
|
318
|
+
try {
|
|
319
|
+
await update({ json: true, skipSkillsInstall: true, package: `${CLI_PACKAGE_NAME}@${latest}` });
|
|
320
|
+
const installed = readInstalledVersion(runtime);
|
|
321
|
+
if (installed !== latest) {
|
|
322
|
+
throw new Error(`npm finished but this CLI's own install reports ${installed ?? "no version"}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch (error) {
|
|
326
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
327
|
+
writeAutoUpdateState({
|
|
328
|
+
...checked,
|
|
329
|
+
notice_pending: true,
|
|
330
|
+
last_result: { at: now().toISOString(), from: OXYGEN_VERSION, to: latest, ok: false, trigger: "background", error: message },
|
|
331
|
+
}, env);
|
|
332
|
+
return { action: "update_failed", latest, error: message };
|
|
333
|
+
}
|
|
334
|
+
writeAutoUpdateState({
|
|
335
|
+
...checked,
|
|
336
|
+
notice_pending: true,
|
|
337
|
+
last_result: { at: now().toISOString(), from: OXYGEN_VERSION, to: latest, ok: true, trigger: "background" },
|
|
338
|
+
}, env);
|
|
339
|
+
const entry = argv[1];
|
|
340
|
+
if (entry) {
|
|
341
|
+
(runtime.spawnSync ?? defaultSpawnSync)(runtime.execPath ?? process.execPath, [entry, "update", "--refresh-skills"], {
|
|
342
|
+
stdio: "ignore",
|
|
343
|
+
env,
|
|
344
|
+
windowsHide: true,
|
|
345
|
+
timeout: SKILLS_REFRESH_TIMEOUT_MS,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
return { action: "updated", latest };
|
|
349
|
+
}
|
|
350
|
+
/** `oxygen update --status`, `--enable-auto`, `--disable-auto`. Checks npm live. */
|
|
351
|
+
export async function readAutoUpdateStatus(runtime = {}) {
|
|
352
|
+
const env = runtime.env ?? process.env;
|
|
353
|
+
let state = readAutoUpdateState(env);
|
|
354
|
+
let settingChanged;
|
|
355
|
+
if (runtime.setting) {
|
|
356
|
+
settingChanged = (state.auto_update === "off" ? "off" : "on") !== runtime.setting;
|
|
357
|
+
state = { ...state, auto_update: runtime.setting };
|
|
358
|
+
if (!runtime.dryRun && !writeAutoUpdateState(state, env)) {
|
|
359
|
+
throw new OxygenError("config_write_failed", `Could not save the auto-update setting to ${autoUpdateStatePath(env)}.`, {
|
|
360
|
+
details: { state_path: autoUpdateStatePath(env) },
|
|
361
|
+
exitCode: 1,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const eligibility = autoUpdateEligibility({ ...runtime, env, state });
|
|
366
|
+
let latest = null;
|
|
367
|
+
let latestError;
|
|
368
|
+
try {
|
|
369
|
+
latest = await fetchLatestCliVersion(runtime.fetch ?? fetch);
|
|
370
|
+
}
|
|
371
|
+
catch (error) {
|
|
372
|
+
latestError = error instanceof Error ? error.message : String(error);
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
current_version: OXYGEN_VERSION,
|
|
376
|
+
latest_version: latest,
|
|
377
|
+
...(latestError ? { latest_error: latestError } : {}),
|
|
378
|
+
update_available: latest ? isVersionGreater(latest, OXYGEN_VERSION) : null,
|
|
379
|
+
...(runtime.setting ? { dry_run: runtime.dryRun === true, setting_changed: settingChanged === true } : {}),
|
|
380
|
+
auto_update: {
|
|
381
|
+
setting: state.auto_update === "off" ? "off" : "on",
|
|
382
|
+
active: eligibility.eligible,
|
|
383
|
+
reason: eligibility.eligible ? null : eligibility.reason,
|
|
384
|
+
reason_text: eligibility.eligible ? null : AUTO_UPDATE_REASON_TEXT[eligibility.reason],
|
|
385
|
+
next_step: eligibility.eligible ? null : AUTO_UPDATE_NEXT_STEP[eligibility.reason],
|
|
386
|
+
disable: "oxygen update --disable-auto (or set OXYGEN_NO_AUTO_UPDATE=1)",
|
|
387
|
+
last_check_at: state.last_check_at ?? null,
|
|
388
|
+
last_result: state.last_result ?? null,
|
|
389
|
+
},
|
|
390
|
+
state_path: autoUpdateStatePath(env),
|
|
391
|
+
};
|
|
392
|
+
}
|
package/dist/command-manifest.js
CHANGED
|
@@ -89,6 +89,9 @@ const MUTATING_VERBS = new Set([
|
|
|
89
89
|
"unarchive", "unpublish", "unshare",
|
|
90
90
|
"unbind", "unsubscribe", "update", "upload", "upsert", "use", "warm-send", "warmup",
|
|
91
91
|
"withdraw", "write",
|
|
92
|
+
// `voice numbers release` returns a number to the carrier: irreversible,
|
|
93
|
+
// someone else can take it within minutes. It is the only `release` leaf.
|
|
94
|
+
"release",
|
|
92
95
|
]);
|
|
93
96
|
// Exact commands that must not inherit a read-only discovery label. Warmup
|
|
94
97
|
// status persists state and can pause the provider; reconnect replaces its
|
|
@@ -103,6 +106,8 @@ const MUTATING_COMMANDS = new Set([
|
|
|
103
106
|
// whose own subcommands (list/set/enable/disable/backfill, catalog/entries)
|
|
104
107
|
// already carry their own correctly-classified verbs.
|
|
105
108
|
"viewers enrichment",
|
|
109
|
+
// A noun leaf that switches a bought domain's yearly paid renewal on or off.
|
|
110
|
+
"domains renewal",
|
|
106
111
|
]);
|
|
107
112
|
// `--approved` usually identifies a paid or externally mutating execution
|
|
108
113
|
// mode, but a small number of approved operations are explicitly
|
|
@@ -126,6 +131,10 @@ const ZERO_CREDIT_APPROVAL_COMMANDS = new Set([
|
|
|
126
131
|
// flag neither authorizes nor caps later inference; only `copilot send` can
|
|
127
132
|
// trigger the billed model loop.
|
|
128
133
|
"copilot start",
|
|
134
|
+
// Releasing a phone number STOPS its monthly charge; it never buys anything.
|
|
135
|
+
"voice numbers release",
|
|
136
|
+
// Both previews without --approved; approval cancels or archives, never charges.
|
|
137
|
+
"domains delete",
|
|
129
138
|
"mailboxes delete",
|
|
130
139
|
// Previews without --approved; with it, removes a program no creator ever
|
|
131
140
|
// joined. No provider call, no credits.
|
|
@@ -171,6 +180,7 @@ const PREVIEW_BY_DEFAULT_COMMANDS = new Set([
|
|
|
171
180
|
// an agent that previewing the play arms it, and the preview would go unrun.
|
|
172
181
|
"linkedin intent autoenroll",
|
|
173
182
|
"mailboxes emailguard auto-enroll",
|
|
183
|
+
"domains delete",
|
|
174
184
|
"mailboxes delete",
|
|
175
185
|
"sequences recover-capacity",
|
|
176
186
|
"support admin done",
|
|
@@ -178,6 +188,10 @@ const PREVIEW_BY_DEFAULT_COMMANDS = new Set([
|
|
|
178
188
|
// A bare call reads the Plain workspace and prints the plan; --apply writes.
|
|
179
189
|
"support admin setup",
|
|
180
190
|
"verify email",
|
|
191
|
+
// Both fenced by --approved: a bare call prices (buy) or describes (release)
|
|
192
|
+
// what would change and writes nothing.
|
|
193
|
+
"voice numbers buy",
|
|
194
|
+
"voice numbers release",
|
|
181
195
|
"workflows webhooks rotate",
|
|
182
196
|
]);
|
|
183
197
|
export function buildCommandManifest(program, binaryName) {
|
package/dist/credentials.d.ts
CHANGED
|
@@ -77,4 +77,6 @@ export declare function pickProfileNameForUserSession(userId: string, candidateS
|
|
|
77
77
|
export declare function switchCredentialProfile(profile: string, env?: NodeJS.ProcessEnv): Promise<CredentialProfile>;
|
|
78
78
|
export declare function updateActiveOrganizationForProfile(profile: string, organization: StoredOrganization, env?: NodeJS.ProcessEnv): Promise<CredentialProfile>;
|
|
79
79
|
export declare function normalizeApiUrl(value: string): string;
|
|
80
|
+
/** The CLI's per-user config directory; `OXYGEN_CONFIG_DIR` overrides it. */
|
|
81
|
+
export declare function resolveConfigDir(env?: NodeJS.ProcessEnv): string;
|
|
80
82
|
export {};
|
package/dist/credentials.js
CHANGED
|
@@ -411,12 +411,15 @@ function credentialStore(env) {
|
|
|
411
411
|
return "credential-manager";
|
|
412
412
|
return "file";
|
|
413
413
|
}
|
|
414
|
-
|
|
415
|
-
|
|
414
|
+
/** The CLI's per-user config directory; `OXYGEN_CONFIG_DIR` overrides it. */
|
|
415
|
+
export function resolveConfigDir(env = process.env) {
|
|
416
|
+
return env.OXYGEN_CONFIG_DIR?.trim()
|
|
416
417
|
|| (process.platform === "win32"
|
|
417
418
|
? join(env.APPDATA || join(homedir(), "AppData", "Roaming"), "oxygen")
|
|
418
419
|
: join(homedir(), ".config", "oxygen"));
|
|
419
|
-
|
|
420
|
+
}
|
|
421
|
+
function credentialsPath(env) {
|
|
422
|
+
return join(resolveConfigDir(env), "credentials.json");
|
|
420
423
|
}
|
|
421
424
|
function readFileCredentialPayload(env) {
|
|
422
425
|
const path = credentialsPath(env);
|
|
@@ -47,7 +47,7 @@ export function registerFunctionsCommands(program, handle) {
|
|
|
47
47
|
functions.command("rename <function> <name>").description("Rename a Function while preserving its identity, callers and published execution.")
|
|
48
48
|
.option("--json", "Print a JSON envelope.")
|
|
49
49
|
.action((ref, name, options) => handle("functions rename", options, () => requestOxygen("/api/cli/functions", { method: "POST", body: { operation: "rename", function: ref, display_name: name } })));
|
|
50
|
-
functions.command("delete <function>").description("Preview removal from the Function library, then remove it with --yes; preserves backing tables, rows and run history.
|
|
50
|
+
functions.command("delete <function>").description("Preview removal from the Function library, then remove it with --yes; preserves backing tables, rows and run history. The preview names every column that calls it: those stop running, keeping the values they already wrote. Only the Function's own active runs block deletion.")
|
|
51
51
|
.addHelpText("after", "\nDeleting your own Function is final: there is no restore, unlike `tables delete`. To reuse the logic, `tables duplicate` the backing Table and draft a new Function from the copy.\n")
|
|
52
52
|
.option("--yes", "Confirm removal of this Function after reviewing its callers.")
|
|
53
53
|
.option("--json", "Print a JSON envelope.")
|