akm-cli 0.9.0-rc.2 → 0.9.0-rc.4
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/CHANGELOG.md +40 -12
- package/README.md +7 -8
- package/SECURITY.md +5 -3
- package/dist/akm +44 -33
- package/dist/akm-migrate-storage +44 -33
- package/dist/assets/backends/schtasks-template.xml +2 -1
- package/dist/assets/improve-strategies/catchup.json +3 -1
- package/dist/assets/improve-strategies/consolidate.json +3 -1
- package/dist/assets/improve-strategies/frequent.json +3 -1
- package/dist/assets/improve-strategies/graph-refresh.json +3 -1
- package/dist/assets/improve-strategies/memory-focus.json +4 -1
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -0
- package/dist/assets/improve-strategies/quick.json +3 -1
- package/dist/assets/improve-strategies/recombine-only.json +2 -0
- package/dist/assets/improve-strategies/reflect-distill.json +1 -0
- package/dist/assets/improve-strategies/synthesize.json +2 -0
- package/dist/assets/tasks/core/backup.yml +2 -2
- package/dist/cli/config-migrate.js +554 -26
- package/dist/cli.js +28 -11
- package/dist/commands/backup-cli.js +6 -4
- package/dist/commands/config-cli.js +10 -3
- package/dist/commands/feedback-cli.js +24 -7
- package/dist/commands/health/checks.js +94 -15
- package/dist/commands/health/html-report.js +23 -2
- package/dist/commands/health/improve-metrics.js +45 -6
- package/dist/commands/health/md-report.js +10 -1
- package/dist/commands/health/surfaces.js +2 -1
- package/dist/commands/health/windows.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/improve/anti-collapse.js +3 -3
- package/dist/commands/improve/collapse-detector.js +5 -5
- package/dist/commands/improve/consolidate.js +123 -66
- package/dist/commands/improve/distill/promote-memory.js +8 -6
- package/dist/commands/improve/distill/quality-gate.js +1 -1
- package/dist/commands/improve/distill-guards.js +1 -1
- package/dist/commands/improve/distill-promotion-policy.js +32 -26
- package/dist/commands/improve/distill.js +52 -36
- package/dist/commands/improve/eligibility.js +9 -8
- package/dist/commands/improve/extract-prompt.js +2 -2
- package/dist/commands/improve/extract.js +43 -11
- package/dist/commands/improve/improve-auto-accept.js +14 -28
- package/dist/commands/improve/improve-cli.js +4 -2
- package/dist/commands/improve/improve-strategies.js +2 -1
- package/dist/commands/improve/improve.js +49 -8
- package/dist/commands/improve/loop-stages.js +11 -6
- package/dist/commands/improve/preparation.js +51 -17
- package/dist/commands/improve/procedural.js +10 -6
- package/dist/commands/improve/recombine.js +31 -7
- package/dist/commands/improve/reflect.js +24 -14
- package/dist/commands/improve/salience.js +5 -1
- package/dist/commands/improve/source-identity.js +43 -0
- package/dist/commands/migrate-cli.js +36 -0
- package/dist/commands/mv-cli.js +647 -258
- package/dist/commands/proposal/drain.js +16 -4
- package/dist/commands/proposal/proposal-cli.js +3 -2
- package/dist/commands/proposal/proposal.js +16 -55
- package/dist/commands/proposal/repository.js +664 -58
- package/dist/commands/read/curate.js +4 -2
- package/dist/commands/read/knowledge.js +4 -1
- package/dist/commands/read/search.js +6 -2
- package/dist/commands/read/show.js +8 -7
- package/dist/commands/sources/self-update.js +156 -112
- package/dist/commands/sources/sources-cli.js +7 -2
- package/dist/commands/tasks/default-tasks.js +5 -5
- package/dist/commands/tasks/tasks-cli.js +7 -3
- package/dist/commands/tasks/tasks.js +261 -68
- package/dist/core/common.js +36 -3
- package/dist/core/config/config-io.js +2 -2
- package/dist/core/config/config-schema.js +15 -3
- package/dist/core/config/config.js +14 -13
- package/dist/core/file-lock.js +2 -2
- package/dist/core/improve-result.js +87 -7
- package/dist/core/migration-backup.js +816 -171
- package/dist/core/migration-operation.js +44 -0
- package/dist/core/state/migrations.js +4 -7
- package/dist/core/state-db.js +23 -7
- package/dist/core/write-source.js +86 -18
- package/dist/indexer/db/db.js +109 -53
- package/dist/indexer/index-writer-lock.js +38 -37
- package/dist/indexer/index-written-assets.js +73 -56
- package/dist/indexer/indexer.js +5 -1
- package/dist/indexer/search/search-source.js +2 -2
- package/dist/indexer/usage/usage-events.js +8 -2
- package/dist/integrations/agent/engine-resolution.js +14 -7
- package/dist/scripts/migrate-storage.js +887 -992
- package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +494 -574
- package/dist/setup/detected-engines.js +7 -13
- package/dist/setup/engine-config.js +14 -4
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +1 -1
- package/dist/setup/steps/tasks.js +1 -1
- package/dist/sources/providers/git-stash.js +58 -21
- package/dist/sources/providers/git.js +1 -1
- package/dist/storage/engines/sqlite-migrations.js +138 -3
- package/dist/storage/repositories/events-repository.js +24 -0
- package/dist/storage/repositories/proposals-repository.js +14 -0
- package/dist/storage/repositories/task-history-repository.js +30 -3
- package/dist/tasks/backends/cron.js +71 -38
- package/dist/tasks/backends/exec-utils.js +55 -3
- package/dist/tasks/backends/launchd.js +241 -50
- package/dist/tasks/backends/schtasks.js +434 -59
- package/dist/tasks/command-executable.js +93 -0
- package/dist/tasks/embedded.js +2 -0
- package/dist/tasks/parser.js +142 -6
- package/dist/tasks/resolve-akm-bin.js +19 -4
- package/dist/tasks/runner.js +258 -93
- package/dist/tasks/schedule.js +108 -19
- package/dist/tasks/scheduler-invocation.js +86 -0
- package/dist/tasks/task-id.js +37 -0
- package/dist/workflows/db.js +24 -13
- package/dist/workflows/validator.js +24 -2
- package/docs/README.md +103 -0
- package/docs/migration/release-notes/0.9.0.md +28 -9
- package/docs/migration/v0.8-to-v0.9.md +186 -26
- package/package.json +2 -3
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
const ENV_OPTIONS_WITH_OPERAND = new Set(["--argv0", "--chdir", "--unset"]);
|
|
5
|
+
const ENV_OPTIONS_WITH_OPTIONAL_VALUE = new Set(["--block-signal", "--default-signal", "--ignore-signal"]);
|
|
6
|
+
const ENV_OPTIONS_WITHOUT_OPERAND = new Set(["--debug", "--ignore-environment", "--list-signal-handling"]);
|
|
7
|
+
/** Locate a command whose executable position clearly names AKM, including an explicit path. */
|
|
8
|
+
export function findAkmExecutableIndex(command) {
|
|
9
|
+
if (command.length === 0)
|
|
10
|
+
return undefined;
|
|
11
|
+
const executableIndex = isEnvExecutable(command[0]) ? findEnvCommandIndex(command) : 0;
|
|
12
|
+
if (executableIndex === undefined || !isAkmExecutable(command[executableIndex]))
|
|
13
|
+
return undefined;
|
|
14
|
+
return executableIndex;
|
|
15
|
+
}
|
|
16
|
+
/** Locate only PATH-selected AKM, which is safe to redirect to the current installation. */
|
|
17
|
+
export function findBareAkmExecutableIndex(command) {
|
|
18
|
+
const executableIndex = findAkmExecutableIndex(command);
|
|
19
|
+
if (executableIndex === undefined || !isBareAkm(command[executableIndex]))
|
|
20
|
+
return undefined;
|
|
21
|
+
return executableIndex;
|
|
22
|
+
}
|
|
23
|
+
function findEnvCommandIndex(command) {
|
|
24
|
+
let index = 1;
|
|
25
|
+
while (index < command.length) {
|
|
26
|
+
const part = command[index];
|
|
27
|
+
if (part === "--") {
|
|
28
|
+
index += 1;
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
if (isEnvironmentAssignment(part))
|
|
32
|
+
break;
|
|
33
|
+
if (!part.startsWith("-"))
|
|
34
|
+
break;
|
|
35
|
+
const operandCount = envOptionOperandCount(part);
|
|
36
|
+
if (operandCount === undefined || index + operandCount >= command.length)
|
|
37
|
+
return undefined;
|
|
38
|
+
index += operandCount + 1;
|
|
39
|
+
}
|
|
40
|
+
while (index < command.length && isEnvironmentAssignment(command[index]))
|
|
41
|
+
index += 1;
|
|
42
|
+
return index < command.length ? index : undefined;
|
|
43
|
+
}
|
|
44
|
+
function envOptionOperandCount(option) {
|
|
45
|
+
if (option === "-")
|
|
46
|
+
return 0;
|
|
47
|
+
if (ENV_OPTIONS_WITHOUT_OPERAND.has(option) || ENV_OPTIONS_WITH_OPTIONAL_VALUE.has(option))
|
|
48
|
+
return 0;
|
|
49
|
+
if ([...ENV_OPTIONS_WITH_OPTIONAL_VALUE].some((prefix) => option.startsWith(`${prefix}=`)))
|
|
50
|
+
return 0;
|
|
51
|
+
if (ENV_OPTIONS_WITH_OPERAND.has(option))
|
|
52
|
+
return 1;
|
|
53
|
+
if (["--argv0=", "--chdir=", "--unset="].some((prefix) => option.startsWith(prefix)))
|
|
54
|
+
return 0;
|
|
55
|
+
if (!option.startsWith("-") || option.startsWith("--"))
|
|
56
|
+
return undefined;
|
|
57
|
+
const shortOptions = option.slice(1);
|
|
58
|
+
for (let index = 0; index < shortOptions.length; index += 1) {
|
|
59
|
+
const flag = shortOptions[index];
|
|
60
|
+
if (flag === "i" || flag === "v")
|
|
61
|
+
continue;
|
|
62
|
+
if (flag === "S")
|
|
63
|
+
return undefined;
|
|
64
|
+
if (flag === "u" || flag === "C" || flag === "P" || flag === "a") {
|
|
65
|
+
return index === shortOptions.length - 1 ? 1 : 0;
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
return shortOptions.length > 0 ? 0 : undefined;
|
|
70
|
+
}
|
|
71
|
+
function isEnvironmentAssignment(value) {
|
|
72
|
+
return /^[A-Za-z_][A-Za-z0-9_]*=/.test(value);
|
|
73
|
+
}
|
|
74
|
+
function isEnvExecutable(value) {
|
|
75
|
+
if (!value)
|
|
76
|
+
return false;
|
|
77
|
+
if (value === "env")
|
|
78
|
+
return true;
|
|
79
|
+
if (!isAbsolutePath(value))
|
|
80
|
+
return false;
|
|
81
|
+
return /^env(?:\.exe)?$/i.test(value.split(/[\\/]/).at(-1) ?? "");
|
|
82
|
+
}
|
|
83
|
+
function isAbsolutePath(value) {
|
|
84
|
+
return value.startsWith("/") || value.startsWith("\\\\") || /^[A-Za-z]:[\\/]/.test(value);
|
|
85
|
+
}
|
|
86
|
+
function isBareAkm(value) {
|
|
87
|
+
return value !== undefined && /^akm(?:\.exe)?$/i.test(value);
|
|
88
|
+
}
|
|
89
|
+
function isAkmExecutable(value) {
|
|
90
|
+
if (!value)
|
|
91
|
+
return false;
|
|
92
|
+
return /^akm(?:\.exe)?$/i.test(value.split(/[\\/]/).at(-1) ?? "");
|
|
93
|
+
}
|
package/dist/tasks/embedded.js
CHANGED
|
@@ -58,12 +58,14 @@ export function listEmbeddedTasks() {
|
|
|
58
58
|
const command = typeof doc.command === "string" ? doc.command : "";
|
|
59
59
|
const schedule = typeof doc.schedule === "string" ? doc.schedule : "";
|
|
60
60
|
const description = typeof doc.description === "string" ? doc.description : "";
|
|
61
|
+
const enabled = doc.enabled !== false;
|
|
61
62
|
tasks.push({
|
|
62
63
|
id,
|
|
63
64
|
label: `core/${id}`,
|
|
64
65
|
command,
|
|
65
66
|
schedule,
|
|
66
67
|
description,
|
|
68
|
+
enabled,
|
|
67
69
|
yaml,
|
|
68
70
|
});
|
|
69
71
|
}
|
package/dist/tasks/parser.js
CHANGED
|
@@ -37,9 +37,12 @@ import path from "node:path";
|
|
|
37
37
|
import { parse as parseYaml } from "yaml";
|
|
38
38
|
import { UsageError } from "../core/errors.js";
|
|
39
39
|
import { formatExtraParamsIssue, validateExtraParams } from "../core/extra-params.js";
|
|
40
|
+
import { findBareAkmExecutableIndex } from "./command-executable.js";
|
|
40
41
|
import { TASK_SCHEMA_VERSION } from "./schema.js";
|
|
42
|
+
import { validateTaskId } from "./task-id.js";
|
|
41
43
|
export function parseTaskDocument(input) {
|
|
42
|
-
const { yaml, filePath
|
|
44
|
+
const { yaml, filePath } = input;
|
|
45
|
+
const id = validateTaskId(input.id);
|
|
43
46
|
let data;
|
|
44
47
|
try {
|
|
45
48
|
const parsed = parseYaml(yaml);
|
|
@@ -53,6 +56,9 @@ export function parseTaskDocument(input) {
|
|
|
53
56
|
throw err;
|
|
54
57
|
throw new UsageError(`Task "${id}" has invalid YAML: ${err instanceof Error ? err.message : String(err)}. File: ${filePath}`, "INVALID_FLAG_VALUE");
|
|
55
58
|
}
|
|
59
|
+
const legacy = data.version === undefined || data.version === 1;
|
|
60
|
+
if (legacy)
|
|
61
|
+
data = normalizeV1Task(data, filePath);
|
|
56
62
|
requireVersion(data, id, filePath);
|
|
57
63
|
rejectUnknownKeys(data, id, filePath);
|
|
58
64
|
const schedule = requireString(data.schedule, "schedule", filePath);
|
|
@@ -89,7 +95,8 @@ export function parseTaskDocument(input) {
|
|
|
89
95
|
}
|
|
90
96
|
else if (hasCommand) {
|
|
91
97
|
rejectTargetFields(data, ["timeoutMs"], id, filePath);
|
|
92
|
-
const
|
|
98
|
+
const parsedCommand = readCommand(data.command, filePath, id);
|
|
99
|
+
const cmd = legacy ? normalizeV1Command(parsedCommand) : parsedCommand;
|
|
93
100
|
target = { kind: "command", cmd };
|
|
94
101
|
}
|
|
95
102
|
else {
|
|
@@ -100,7 +107,7 @@ export function parseTaskDocument(input) {
|
|
|
100
107
|
}
|
|
101
108
|
const engine = optionalString(data.engine, "engine", filePath);
|
|
102
109
|
const model = optionalString(data.model, "model", filePath);
|
|
103
|
-
const timeoutMs = readTimeout(data.timeoutMs, filePath);
|
|
110
|
+
const timeoutMs = readTimeout(data.timeoutMs, filePath, legacy);
|
|
104
111
|
const llm = readLlmOverrides(data.llm, filePath);
|
|
105
112
|
target = {
|
|
106
113
|
kind: "prompt",
|
|
@@ -111,7 +118,7 @@ export function parseTaskDocument(input) {
|
|
|
111
118
|
...(llm ? { llm } : {}),
|
|
112
119
|
};
|
|
113
120
|
}
|
|
114
|
-
const timeoutMs = hasCommand ? readTimeout(data.timeoutMs, filePath) : undefined;
|
|
121
|
+
const timeoutMs = hasCommand ? readTimeout(data.timeoutMs, filePath, legacy) : undefined;
|
|
115
122
|
return {
|
|
116
123
|
version: TASK_SCHEMA_VERSION,
|
|
117
124
|
schemaVersion: TASK_SCHEMA_VERSION,
|
|
@@ -145,11 +152,138 @@ const TASK_KEYS = new Set([
|
|
|
145
152
|
"llm",
|
|
146
153
|
]);
|
|
147
154
|
const SHARED_KEYS = new Set(["version", "name", "description", "when_to_use", "tags", "schedule", "enabled"]);
|
|
155
|
+
/** Lower the complete permissive 0.8 task shape into the strict v2 reader shape. */
|
|
156
|
+
function normalizeV1Task(data, filePath) {
|
|
157
|
+
const normalized = { version: TASK_SCHEMA_VERSION };
|
|
158
|
+
for (const key of ["schedule", "workflow", "prompt", "command", "name", "description", "when_to_use"]) {
|
|
159
|
+
const value = data[key];
|
|
160
|
+
if (value === undefined || value === null)
|
|
161
|
+
continue;
|
|
162
|
+
if (key === "command" && Array.isArray(value)) {
|
|
163
|
+
normalized[key] = value.filter((part) => typeof part === "string" && part.trim().length > 0);
|
|
164
|
+
}
|
|
165
|
+
else if (key === "command") {
|
|
166
|
+
normalized[key] = value;
|
|
167
|
+
}
|
|
168
|
+
else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
|
|
169
|
+
normalized[key] = String(value).trim();
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
normalized[key] = value;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
normalized.enabled = data.enabled === undefined ? true : data.enabled === true;
|
|
176
|
+
if (typeof data.tags === "string") {
|
|
177
|
+
normalized.tags = data.tags
|
|
178
|
+
.split(/[\s,]+/)
|
|
179
|
+
.map((tag) => tag.trim())
|
|
180
|
+
.filter(Boolean);
|
|
181
|
+
}
|
|
182
|
+
else if (Array.isArray(data.tags)) {
|
|
183
|
+
normalized.tags = data.tags.filter((tag) => typeof tag === "string" && tag.trim().length > 0);
|
|
184
|
+
}
|
|
185
|
+
const hasWorkflow = "workflow" in data && data.workflow !== "" && data.workflow != null;
|
|
186
|
+
const hasPrompt = "prompt" in data && data.prompt !== "" && data.prompt != null;
|
|
187
|
+
if (hasWorkflow && data.params && typeof data.params === "object" && !Array.isArray(data.params)) {
|
|
188
|
+
normalized.params = data.params;
|
|
189
|
+
}
|
|
190
|
+
else if (hasWorkflow && typeof data.params === "string" && data.params.trim()) {
|
|
191
|
+
try {
|
|
192
|
+
const params = JSON.parse(data.params);
|
|
193
|
+
if (params && typeof params === "object" && !Array.isArray(params))
|
|
194
|
+
normalized.params = params;
|
|
195
|
+
else
|
|
196
|
+
normalized.params = data.params;
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
normalized.params = data.params;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (hasPrompt && data.profile !== undefined && data.profile !== null) {
|
|
203
|
+
const profile = legacyString(data.profile);
|
|
204
|
+
if (profile === undefined) {
|
|
205
|
+
throw new UsageError(`Key "profile" must be a string. File: ${filePath}`, "INVALID_FLAG_VALUE");
|
|
206
|
+
}
|
|
207
|
+
if (profile)
|
|
208
|
+
normalized.engine = profile;
|
|
209
|
+
}
|
|
210
|
+
if (!hasWorkflow && "timeoutMs" in data) {
|
|
211
|
+
const timeout = data.timeoutMs;
|
|
212
|
+
if (timeout === null || timeout === "null" || timeout === 0 || (typeof timeout === "number" && timeout < 0)) {
|
|
213
|
+
normalized.timeoutMs = null;
|
|
214
|
+
}
|
|
215
|
+
else if (typeof timeout === "number" && timeout > 0) {
|
|
216
|
+
normalized.timeoutMs = timeout;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return normalized;
|
|
220
|
+
}
|
|
221
|
+
function legacyString(value) {
|
|
222
|
+
if (value === undefined || value === null)
|
|
223
|
+
return undefined;
|
|
224
|
+
if (typeof value === "string")
|
|
225
|
+
return value.trim();
|
|
226
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
227
|
+
return String(value);
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
/** Rewrite only the removed AKM spelling; arbitrary command arguments remain untouched. */
|
|
231
|
+
function normalizeV1Command(cmd) {
|
|
232
|
+
const akmIndex = findBareAkmExecutableIndex(cmd);
|
|
233
|
+
if (akmIndex === undefined)
|
|
234
|
+
return cmd;
|
|
235
|
+
const improveIndex = findImproveSubcommandIndex(cmd, akmIndex);
|
|
236
|
+
if (improveIndex === undefined)
|
|
237
|
+
return cmd;
|
|
238
|
+
return cmd.map((part, index) => {
|
|
239
|
+
if (index <= improveIndex)
|
|
240
|
+
return part;
|
|
241
|
+
if (part === "--profile")
|
|
242
|
+
return "--strategy";
|
|
243
|
+
if (part.startsWith("--profile="))
|
|
244
|
+
return `--strategy=${part.slice("--profile=".length)}`;
|
|
245
|
+
return part;
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
const AKM_GLOBAL_BOOLEAN_OPTIONS = new Set([
|
|
249
|
+
"--quiet",
|
|
250
|
+
"-q",
|
|
251
|
+
"--verbose",
|
|
252
|
+
"--no-quiet",
|
|
253
|
+
"--no-verbose",
|
|
254
|
+
"--quiet=false",
|
|
255
|
+
"--verbose=false",
|
|
256
|
+
]);
|
|
257
|
+
const AKM_GLOBAL_VALUE_OPTIONS = new Set(["--format", "--output", "--detail", "--shape"]);
|
|
258
|
+
function findImproveSubcommandIndex(cmd, akmIndex) {
|
|
259
|
+
let index = akmIndex + 1;
|
|
260
|
+
while (index < cmd.length) {
|
|
261
|
+
const part = cmd[index];
|
|
262
|
+
if (part === "improve")
|
|
263
|
+
return index;
|
|
264
|
+
if (AKM_GLOBAL_BOOLEAN_OPTIONS.has(part)) {
|
|
265
|
+
index += 1;
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
if (AKM_GLOBAL_VALUE_OPTIONS.has(part)) {
|
|
269
|
+
if (index + 1 >= cmd.length)
|
|
270
|
+
return undefined;
|
|
271
|
+
index += 2;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if ([...AKM_GLOBAL_VALUE_OPTIONS].some((option) => part.startsWith(`${option}=`) && part.length > option.length + 1)) {
|
|
275
|
+
index += 1;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
148
282
|
function requireVersion(data, id, filePath) {
|
|
149
283
|
if (data.version === TASK_SCHEMA_VERSION)
|
|
150
284
|
return;
|
|
151
285
|
const actual = data.version === undefined ? "missing" : JSON.stringify(data.version);
|
|
152
|
-
throw new UsageError(`TASK_SCHEMA_VERSION_UNSUPPORTED: Task "${id}" uses task schema version ${actual}; version: 2 is required. File: ${filePath}`, "TASK_SCHEMA_VERSION_UNSUPPORTED", "
|
|
286
|
+
throw new UsageError(`TASK_SCHEMA_VERSION_UNSUPPORTED: Task "${id}" uses task schema version ${actual}; version: 2 is required. File: ${filePath}`, "TASK_SCHEMA_VERSION_UNSUPPORTED", "Use task schema version: 2.");
|
|
153
287
|
}
|
|
154
288
|
function rejectUnknownKeys(data, id, filePath) {
|
|
155
289
|
const unknown = Object.keys(data).filter((key) => !TASK_KEYS.has(key));
|
|
@@ -247,11 +381,13 @@ function readParams(value, filePath) {
|
|
|
247
381
|
}
|
|
248
382
|
throw new UsageError(`Key "params" must be a mapping. File: ${filePath}`, "INVALID_FLAG_VALUE");
|
|
249
383
|
}
|
|
250
|
-
function readTimeout(value, filePath) {
|
|
384
|
+
function readTimeout(value, filePath, legacy = false) {
|
|
251
385
|
if (value === undefined)
|
|
252
386
|
return undefined;
|
|
253
387
|
if (value === null)
|
|
254
388
|
return null;
|
|
389
|
+
if (legacy && typeof value === "number" && Number.isFinite(value) && value > 0)
|
|
390
|
+
return value;
|
|
255
391
|
if (typeof value === "number" && Number.isInteger(value) && value > 0)
|
|
256
392
|
return value;
|
|
257
393
|
throw new UsageError(`Key "timeoutMs" must be a positive integer or null. File: ${filePath}`, "INVALID_FLAG_VALUE");
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
* Resolution order:
|
|
11
11
|
*
|
|
12
12
|
* 1. `$AKM_BIN` (explicit override; takes precedence everywhere).
|
|
13
|
-
* 2. `process.execPath`
|
|
13
|
+
* 2. `process.execPath` alone for a Bun standalone executable.
|
|
14
|
+
* 3. `process.execPath` + the resolved CLI script — works when running
|
|
14
15
|
* from a development checkout (`bun /repo/src/cli.ts`) and from a
|
|
15
16
|
* compiled install (`bun /opt/akm/dist/cli.js`).
|
|
16
|
-
*
|
|
17
|
+
* 4. `which akm` / `where akm` — last resort when the binary is on PATH
|
|
17
18
|
* but neither override applies.
|
|
18
19
|
*
|
|
19
20
|
* Returns the argv array the scheduler should execute (e.g.
|
|
@@ -25,15 +26,25 @@ import fs from "node:fs";
|
|
|
25
26
|
import path from "node:path";
|
|
26
27
|
import { fileURLToPath } from "node:url";
|
|
27
28
|
import { ConfigError } from "../core/errors.js";
|
|
29
|
+
import { mainPath as runtimeMainPath } from "../runtime.js";
|
|
28
30
|
export function resolveAkmInvocation(options = {}) {
|
|
29
31
|
const env = options.env ?? process.env;
|
|
30
32
|
const override = env.AKM_BIN?.trim();
|
|
31
33
|
if (override) {
|
|
32
34
|
return { argv: [override], via: "AKM_BIN" };
|
|
33
35
|
}
|
|
36
|
+
const runtime = options.runtime ?? (process.versions.bun ? "bun" : "node");
|
|
37
|
+
const execPath = options.execPath ?? process.execPath;
|
|
38
|
+
const mainPath = options.mainPath ?? runtimeMainPath;
|
|
39
|
+
const isStandaloneMain = mainPath?.startsWith("/$bunfs/") || (mainPath !== undefined && /^[A-Za-z]:[\\/]~BUN[\\/]/i.test(mainPath));
|
|
40
|
+
if (runtime === "bun" && isStandaloneMain && execPath) {
|
|
41
|
+
return { argv: [execPath], via: "execPath" };
|
|
42
|
+
}
|
|
34
43
|
const cliPath = resolveCliEntry(options.cliEntryUrl ?? import.meta.url);
|
|
35
|
-
if (cliPath &&
|
|
36
|
-
|
|
44
|
+
if (cliPath && execPath) {
|
|
45
|
+
const entry = runtime === "node" ? resolveNodeCliEntry(cliPath) : cliPath;
|
|
46
|
+
if (entry)
|
|
47
|
+
return { argv: [execPath, entry], via: "execPath" };
|
|
37
48
|
}
|
|
38
49
|
const whichBin = findOnPath("akm", env);
|
|
39
50
|
if (whichBin) {
|
|
@@ -41,6 +52,10 @@ export function resolveAkmInvocation(options = {}) {
|
|
|
41
52
|
}
|
|
42
53
|
throw new ConfigError("Cannot resolve absolute path to the akm binary for scheduler registration.", "INVALID_CONFIG_FILE", "Set AKM_BIN to the absolute path of the akm binary, or ensure `akm` is on PATH.");
|
|
43
54
|
}
|
|
55
|
+
function resolveNodeCliEntry(cliPath) {
|
|
56
|
+
const wrapper = path.join(path.dirname(cliPath), "cli-node.mjs");
|
|
57
|
+
return fs.existsSync(wrapper) ? wrapper : undefined;
|
|
58
|
+
}
|
|
44
59
|
/**
|
|
45
60
|
* From the URL of a module inside `src/tasks/` figure out the CLI entry.
|
|
46
61
|
*
|