@crewx/cli 0.9.0-rc.10 → 0.9.0-rc.101
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/bootstrap/codex-writable-roots.d.ts +13 -0
- package/dist/bootstrap/codex-writable-roots.js +25 -0
- package/dist/bootstrap/crewx-cli.js +7 -2
- package/dist/builtin.js +1 -0
- package/dist/commands/agent.js +0 -58
- package/dist/commands/cal.d.ts +15 -0
- package/dist/commands/cal.js +44 -0
- package/dist/commands/db.d.ts +2 -0
- package/dist/commands/db.js +381 -1
- package/dist/commands/detached.d.ts +65 -0
- package/dist/commands/detached.js +507 -0
- package/dist/commands/doctor.d.ts +53 -0
- package/dist/commands/doctor.js +391 -21
- package/dist/commands/emit-trailer.d.ts +25 -0
- package/dist/commands/emit-trailer.js +33 -0
- package/dist/commands/execute.d.ts +7 -4
- package/dist/commands/execute.js +118 -17
- package/dist/commands/hook/command-marker.d.ts +2 -0
- package/dist/commands/hook/command-marker.js +5 -0
- package/dist/commands/hook/install.d.ts +0 -1
- package/dist/commands/hook/install.js +60 -63
- package/dist/commands/hook/status.js +3 -3
- package/dist/commands/hook/uninstall.js +2 -2
- package/dist/commands/init.js +22 -1
- package/dist/commands/kill.d.ts +2 -8
- package/dist/commands/kill.js +6 -47
- package/dist/commands/log.js +4 -3
- package/dist/commands/parse-common-flags.d.ts +5 -1
- package/dist/commands/parse-common-flags.js +6 -2
- package/dist/commands/ps.d.ts +2 -2
- package/dist/commands/ps.js +25 -15
- package/dist/commands/publish.d.ts +1 -0
- package/dist/commands/publish.js +303 -0
- package/dist/commands/query.d.ts +5 -1
- package/dist/commands/query.js +110 -11
- package/dist/commands/registry.js +4 -2
- package/dist/commands/result.d.ts +8 -3
- package/dist/commands/result.js +139 -7
- package/dist/commands/shortcut.d.ts +1 -0
- package/dist/commands/shortcut.js +267 -0
- package/dist/commands/slack.js +2 -1
- package/dist/commands/stop.d.ts +8 -0
- package/dist/commands/stop.js +119 -0
- package/dist/commands/write-output.d.ts +3 -0
- package/dist/commands/write-output.js +24 -0
- package/dist/logging.d.ts +1 -1
- package/dist/logging.js +3 -2
- package/dist/main.d.ts +3 -2
- package/dist/main.js +66 -16
- package/dist/utils/env-defaults.d.ts +2 -5
- package/dist/utils/env-defaults.js +10 -5
- package/dist/utils/inherited-trace.d.ts +24 -0
- package/dist/utils/inherited-trace.js +40 -0
- package/dist/utils/sdk-compat.d.ts +24 -0
- package/dist/utils/sdk-compat.js +120 -0
- package/package.json +14 -11
|
@@ -33,6 +33,19 @@ export declare function escapeTomlBasicString(value: string): string;
|
|
|
33
33
|
* Works for both `codex exec` and `codex exec resume` (config-override form).
|
|
34
34
|
*/
|
|
35
35
|
export declare function buildCodexWritableRootArgs(crewxHome: string): string[];
|
|
36
|
+
/**
|
|
37
|
+
* Create an `AdditionalArgsProvider` that skips Codex's git repository trust
|
|
38
|
+
* check for a resolved workspace.
|
|
39
|
+
*
|
|
40
|
+
* The workspace is resolved by the product layer, so an unavailable workspace
|
|
41
|
+
* must not fall back to the host cwd. The flag is independent of execution
|
|
42
|
+
* mode because it controls repository trust rather than filesystem access.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createCodexSkipGitRepoCheckProvider(workspaceRoot: string | undefined): AdditionalArgsProvider;
|
|
45
|
+
/**
|
|
46
|
+
* Compose product-layer argument providers while preserving their order.
|
|
47
|
+
*/
|
|
48
|
+
export declare function composeAdditionalArgsProviders(...providers: AdditionalArgsProvider[]): AdditionalArgsProvider;
|
|
36
49
|
/**
|
|
37
50
|
* Create an `AdditionalArgsProvider` (SDK extension point, WI-20260703-001)
|
|
38
51
|
* that injects CrewX home as a Codex writable root for `workspace-write`
|
|
@@ -20,6 +20,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
20
20
|
exports.resolveCrewxHome = resolveCrewxHome;
|
|
21
21
|
exports.escapeTomlBasicString = escapeTomlBasicString;
|
|
22
22
|
exports.buildCodexWritableRootArgs = buildCodexWritableRootArgs;
|
|
23
|
+
exports.createCodexSkipGitRepoCheckProvider = createCodexSkipGitRepoCheckProvider;
|
|
24
|
+
exports.composeAdditionalArgsProviders = composeAdditionalArgsProviders;
|
|
23
25
|
exports.createCodexWritableRootsProvider = createCodexWritableRootsProvider;
|
|
24
26
|
const os_1 = require("os");
|
|
25
27
|
const path_1 = require("path");
|
|
@@ -68,6 +70,29 @@ function buildCodexWritableRootArgs(crewxHome) {
|
|
|
68
70
|
function isCodexProvider(ctx) {
|
|
69
71
|
return ctx.providerId === 'codex' || ctx.providerStr === 'cli/codex';
|
|
70
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Create an `AdditionalArgsProvider` that skips Codex's git repository trust
|
|
75
|
+
* check for a resolved workspace.
|
|
76
|
+
*
|
|
77
|
+
* The workspace is resolved by the product layer, so an unavailable workspace
|
|
78
|
+
* must not fall back to the host cwd. The flag is independent of execution
|
|
79
|
+
* mode because it controls repository trust rather than filesystem access.
|
|
80
|
+
*/
|
|
81
|
+
function createCodexSkipGitRepoCheckProvider(workspaceRoot) {
|
|
82
|
+
return (ctx) => {
|
|
83
|
+
if (!isCodexProvider(ctx))
|
|
84
|
+
return [];
|
|
85
|
+
if (!workspaceRoot)
|
|
86
|
+
return [];
|
|
87
|
+
return ['--skip-git-repo-check'];
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Compose product-layer argument providers while preserving their order.
|
|
92
|
+
*/
|
|
93
|
+
function composeAdditionalArgsProviders(...providers) {
|
|
94
|
+
return (ctx) => providers.flatMap((provider) => provider(ctx));
|
|
95
|
+
}
|
|
71
96
|
/**
|
|
72
97
|
* Create an `AdditionalArgsProvider` (SDK extension point, WI-20260703-001)
|
|
73
98
|
* that injects CrewX home as a Codex writable root for `workspace-write`
|
|
@@ -9,6 +9,7 @@ const plugins_1 = require("@crewx/sdk/plugins");
|
|
|
9
9
|
const repository_1 = require("@crewx/sdk/repository");
|
|
10
10
|
const register_builtin_tools_1 = require("../register-builtin-tools");
|
|
11
11
|
const version_1 = require("../utils/version");
|
|
12
|
+
const inherited_trace_1 = require("../utils/inherited-trace");
|
|
12
13
|
const codex_writable_roots_1 = require("./codex-writable-roots");
|
|
13
14
|
/**
|
|
14
15
|
* Build a Crewx instance with CLI-standard plugins (FileLogger + SqliteTracing)
|
|
@@ -40,6 +41,7 @@ async function createCliCrewx(configPath = process.env.CREWX_CONFIG ?? 'crewx.ya
|
|
|
40
41
|
else {
|
|
41
42
|
yamlPath = undefined;
|
|
42
43
|
}
|
|
44
|
+
const skipRoot = yamlPath !== undefined ? (0, path_1.dirname)(absConfigPath) : undefined;
|
|
43
45
|
// Run drizzle migrations once at bootstrap — plugin relies on this guarantee.
|
|
44
46
|
const dbDir = (0, path_1.join)((0, os_1.homedir)(), '.crewx');
|
|
45
47
|
(0, fs_1.mkdirSync)(dbDir, { recursive: true });
|
|
@@ -53,11 +55,14 @@ async function createCliCrewx(configPath = process.env.CREWX_CONFIG ?? 'crewx.ya
|
|
|
53
55
|
}
|
|
54
56
|
const crewx = await sdk_1.Crewx.loadYaml(yamlPath, {
|
|
55
57
|
remoteFactory: createCliCrewx,
|
|
56
|
-
additionalArgsProvider: (0, codex_writable_roots_1.createCodexWritableRootsProvider)(),
|
|
58
|
+
additionalArgsProvider: (0, codex_writable_roots_1.composeAdditionalArgsProviders)((0, codex_writable_roots_1.createCodexWritableRootsProvider)(), (0, codex_writable_roots_1.createCodexSkipGitRepoCheckProvider)(skipRoot)),
|
|
57
59
|
});
|
|
58
60
|
(0, register_builtin_tools_1.registerBuiltinToolsIfNeeded)(crewx);
|
|
59
61
|
await crewx.use(new plugins_1.ConversationPlugin());
|
|
60
62
|
await crewx.use(new plugins_1.FileLoggerPlugin({ version: version_1.CLI_VERSION, workspaceRoot }));
|
|
61
|
-
await crewx.use(new plugins_1.SqliteTracingPlugin({
|
|
63
|
+
await crewx.use(new plugins_1.SqliteTracingPlugin({
|
|
64
|
+
version: version_1.CLI_VERSION,
|
|
65
|
+
runnerIdentity: (0, inherited_trace_1.consumeInheritedRunnerIdentity)(),
|
|
66
|
+
}));
|
|
62
67
|
return crewx;
|
|
63
68
|
}
|
package/dist/builtin.js
CHANGED
|
@@ -55,6 +55,7 @@ const BUILTIN_MAP = {
|
|
|
55
55
|
dreaming: () => Promise.resolve().then(() => __importStar(require('@crewx/dreaming/cli'))),
|
|
56
56
|
wi: () => Promise.resolve().then(() => __importStar(require('@crewx/wi/cli'))),
|
|
57
57
|
chromex: () => Promise.resolve().then(() => __importStar(require('@crewx/chromex/cli'))),
|
|
58
|
+
notify: () => Promise.resolve().then(() => __importStar(require('@crewx/notify/cli'))),
|
|
58
59
|
};
|
|
59
60
|
exports.BUILTIN_COMMANDS = new Set(Object.keys(BUILTIN_MAP));
|
|
60
61
|
// Load skill-tracer for observability (graceful degradation if unavailable)
|
package/dist/commands/agent.js
CHANGED
|
@@ -3,46 +3,11 @@
|
|
|
3
3
|
* crewx agent handler.
|
|
4
4
|
* Dispatches `crewx agent ls` and `crewx agent prompt` subcommands.
|
|
5
5
|
*/
|
|
6
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
-
if (k2 === undefined) k2 = k;
|
|
8
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
-
}
|
|
12
|
-
Object.defineProperty(o, k2, desc);
|
|
13
|
-
}) : (function(o, m, k, k2) {
|
|
14
|
-
if (k2 === undefined) k2 = k;
|
|
15
|
-
o[k2] = m[k];
|
|
16
|
-
}));
|
|
17
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
-
}) : function(o, v) {
|
|
20
|
-
o["default"] = v;
|
|
21
|
-
});
|
|
22
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
-
var ownKeys = function(o) {
|
|
24
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
-
var ar = [];
|
|
26
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
-
return ar;
|
|
28
|
-
};
|
|
29
|
-
return ownKeys(o);
|
|
30
|
-
};
|
|
31
|
-
return function (mod) {
|
|
32
|
-
if (mod && mod.__esModule) return mod;
|
|
33
|
-
var result = {};
|
|
34
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
-
__setModuleDefault(result, mod);
|
|
36
|
-
return result;
|
|
37
|
-
};
|
|
38
|
-
})();
|
|
39
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
7
|
exports.handleAgent = handleAgent;
|
|
41
|
-
const path = __importStar(require("path"));
|
|
42
8
|
const fs_1 = require("fs");
|
|
43
9
|
const crewx_cli_1 = require("../bootstrap/crewx-cli");
|
|
44
10
|
const sdk_1 = require("@crewx/sdk");
|
|
45
|
-
const skill_1 = require("@crewx/skill");
|
|
46
11
|
/**
|
|
47
12
|
* Parse a flag from args array.
|
|
48
13
|
* Supports both `--flag=value` and `--flag value` forms.
|
|
@@ -135,11 +100,9 @@ async function handleAgentPrompt(crewx, args) {
|
|
|
135
100
|
process.exit(1);
|
|
136
101
|
}
|
|
137
102
|
try {
|
|
138
|
-
const skills = loadAgentSkills();
|
|
139
103
|
const rendered = await crewx.renderAgentPromptFull(agentIdRaw, {
|
|
140
104
|
env: process.env,
|
|
141
105
|
session: { mode: 'query', platform: 'cli' },
|
|
142
|
-
skills,
|
|
143
106
|
});
|
|
144
107
|
const displayId = agentIdRaw.startsWith('@') ? agentIdRaw.slice(1) : agentIdRaw;
|
|
145
108
|
console.log(`\n🤖 **Rendered Prompt for Agent: ${displayId}**\n`);
|
|
@@ -197,27 +160,6 @@ async function handleAgent(args) {
|
|
|
197
160
|
process.exit(1);
|
|
198
161
|
}
|
|
199
162
|
}
|
|
200
|
-
/**
|
|
201
|
-
* Discover available skills and convert to SkillEntry format for template rendering.
|
|
202
|
-
* Searches skills/ and node_modules/@crewx directories.
|
|
203
|
-
*/
|
|
204
|
-
function loadAgentSkills() {
|
|
205
|
-
try {
|
|
206
|
-
const engine = new skill_1.SkillEngine(process.cwd());
|
|
207
|
-
const discovered = engine.discover();
|
|
208
|
-
return discovered.map(s => ({
|
|
209
|
-
metadata: {
|
|
210
|
-
name: s.name,
|
|
211
|
-
version: s.version ?? '0.0.0',
|
|
212
|
-
description: s.description ?? '',
|
|
213
|
-
},
|
|
214
|
-
filePath: s.skillMdPath ?? path.join(s.dir, 'SKILL.md'),
|
|
215
|
-
}));
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
return [];
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
163
|
function printAgentHelp() {
|
|
222
164
|
console.log(`
|
|
223
165
|
CrewX Agent Management
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ActionContext } from '@crewx/knowledge-core';
|
|
2
|
+
/** Suggested wait rungs in seconds. 540 is the safe caller-harness cap. */
|
|
3
|
+
export declare const WAIT_LADDER_SECONDS: readonly [60, 300, 540];
|
|
4
|
+
export interface SuggestWaitSecondsOptions {
|
|
5
|
+
startedAt?: string | number | Date | null;
|
|
6
|
+
lastWaitSeconds?: number;
|
|
7
|
+
now?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Select the next safe result wait based on task runtime without stepping
|
|
11
|
+
* down from the wait value the caller just used.
|
|
12
|
+
*/
|
|
13
|
+
export declare function suggestWaitSeconds(options: SuggestWaitSecondsOptions): number;
|
|
14
|
+
/** Write a human-only CAL footer without contaminating command stdout. */
|
|
15
|
+
export declare function writeCalFooter(context: ActionContext): void;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WAIT_LADDER_SECONDS = void 0;
|
|
4
|
+
exports.suggestWaitSeconds = suggestWaitSeconds;
|
|
5
|
+
exports.writeCalFooter = writeCalFooter;
|
|
6
|
+
const knowledge_core_1 = require("@crewx/knowledge-core");
|
|
7
|
+
/** Suggested wait rungs in seconds. 540 is the safe caller-harness cap. */
|
|
8
|
+
exports.WAIT_LADDER_SECONDS = [60, 300, 540];
|
|
9
|
+
function toTimestamp(value) {
|
|
10
|
+
if (value instanceof Date) {
|
|
11
|
+
return Number.isFinite(value.getTime()) ? value.getTime() : undefined;
|
|
12
|
+
}
|
|
13
|
+
if (typeof value === 'number') {
|
|
14
|
+
return Number.isFinite(value) ? value : undefined;
|
|
15
|
+
}
|
|
16
|
+
if (typeof value === 'string') {
|
|
17
|
+
const timestamp = Date.parse(value);
|
|
18
|
+
return Number.isFinite(timestamp) ? timestamp : undefined;
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Select the next safe result wait based on task runtime without stepping
|
|
24
|
+
* down from the wait value the caller just used.
|
|
25
|
+
*/
|
|
26
|
+
function suggestWaitSeconds(options) {
|
|
27
|
+
const now = Number.isFinite(options.now) ? options.now : Date.now();
|
|
28
|
+
const startedAt = toTimestamp(options.startedAt);
|
|
29
|
+
const elapsedSeconds = startedAt === undefined
|
|
30
|
+
? 0
|
|
31
|
+
: Math.max(0, (now - startedAt) / 1000);
|
|
32
|
+
const rung = exports.WAIT_LADDER_SECONDS.find(seconds => seconds > elapsedSeconds)
|
|
33
|
+
?? exports.WAIT_LADDER_SECONDS[exports.WAIT_LADDER_SECONDS.length - 1];
|
|
34
|
+
const lastWaitSeconds = Number.isFinite(options.lastWaitSeconds)
|
|
35
|
+
? Math.max(0, options.lastWaitSeconds)
|
|
36
|
+
: 0;
|
|
37
|
+
return Math.min(exports.WAIT_LADDER_SECONDS[exports.WAIT_LADDER_SECONDS.length - 1], Math.max(rung, lastWaitSeconds));
|
|
38
|
+
}
|
|
39
|
+
/** Write a human-only CAL footer without contaminating command stdout. */
|
|
40
|
+
function writeCalFooter(context) {
|
|
41
|
+
const block = (0, knowledge_core_1.formatActions)((0, knowledge_core_1.generateNextActions)(context));
|
|
42
|
+
if (block)
|
|
43
|
+
process.stderr.write(`${block}\n`);
|
|
44
|
+
}
|
package/dist/commands/db.d.ts
CHANGED
|
@@ -6,4 +6,6 @@
|
|
|
6
6
|
* crewx db push --force Apply without confirmation + reset migration history
|
|
7
7
|
* crewx db push --dry-run Show preview only, no changes
|
|
8
8
|
*/
|
|
9
|
+
export declare const TASK_LOG_MIGRATION_HELP = "Usage:\n crewx db migrate-task-logs --dry-run [--db PATH]\n crewx db migrate-task-logs --apply [--db PATH] [--batch-tasks N]\n crewx db migrate-task-logs --verify [--db PATH]\n\nModes:\n --dry-run Read and validate legacy blobs without writing or creating a backup.\n --apply Create a consistent SQLite backup, then migrate one task per transaction.\n --verify Check source invariants, event sequences, counts, and orphan rows.\n\nApply policy:\n Only blob tasks with status success, failed, or completed are migrated.\n pending, running, paused, and unknown statuses are deferred for a later run.\n There is no --force or --include-active option. Apply requires the estimated\n database/event growth plus a 10 GiB free-space reserve.\n\nExit codes:\n 0 Completed with no malformed rows or invariant failures.\n 1 Safe tasks completed but a row/invariant failed, or apply was blocked.\n 2 Invalid command-line arguments.\n\nThe command never runs automatically at server startup and never runs VACUUM.";
|
|
10
|
+
export declare const USAGE_BACKFILL_HELP = "Usage:\n crewx db backfill-usage [--dry-run|--apply|--verify] [--db PATH] [--backup PATH]\n crewx db backfill-usage --restore SNAPSHOT [--db PATH]\n\nModes:\n --dry-run Show provider/reason recovery plans without changing the database (default).\n --apply Create a row-level undo snapshot, then account one task per transaction.\n --verify Recheck remaining targets and usageAccounting markers without changing the database.\n --restore Restore rows from SNAPSHOT when their current values still match the snapshot after-state.\n\nApply policy:\n Only success, failed, and completed tasks with zero recorded usage are considered.\n Workflow/MCP anchors and plugin/mock rows are excluded. Rows completed within 24 hours\n are not marked unavailable when their logs do not contain usage.\n\nExit codes:\n 0 Completed successfully.\n 1 Apply was blocked or one or more task updates failed.\n 2 Invalid command-line arguments.";
|
|
9
11
|
export declare function handleDb(args: string[]): Promise<void>;
|
package/dist/commands/db.js
CHANGED
|
@@ -11,12 +11,56 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
11
11
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
12
|
};
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.USAGE_BACKFILL_HELP = exports.TASK_LOG_MIGRATION_HELP = void 0;
|
|
14
15
|
exports.handleDb = handleDb;
|
|
15
16
|
const path_1 = __importDefault(require("path"));
|
|
16
17
|
const os_1 = __importDefault(require("os"));
|
|
17
18
|
const readline_1 = __importDefault(require("readline"));
|
|
18
19
|
const repository_1 = require("@crewx/sdk/repository");
|
|
19
20
|
const repository_2 = require("@crewx/sdk/repository");
|
|
21
|
+
const repository_3 = require("@crewx/sdk/repository");
|
|
22
|
+
const repository_4 = require("@crewx/sdk/repository");
|
|
23
|
+
exports.TASK_LOG_MIGRATION_HELP = `Usage:
|
|
24
|
+
crewx db migrate-task-logs --dry-run [--db PATH]
|
|
25
|
+
crewx db migrate-task-logs --apply [--db PATH] [--batch-tasks N]
|
|
26
|
+
crewx db migrate-task-logs --verify [--db PATH]
|
|
27
|
+
|
|
28
|
+
Modes:
|
|
29
|
+
--dry-run Read and validate legacy blobs without writing or creating a backup.
|
|
30
|
+
--apply Create a consistent SQLite backup, then migrate one task per transaction.
|
|
31
|
+
--verify Check source invariants, event sequences, counts, and orphan rows.
|
|
32
|
+
|
|
33
|
+
Apply policy:
|
|
34
|
+
Only blob tasks with status success, failed, or completed are migrated.
|
|
35
|
+
pending, running, paused, and unknown statuses are deferred for a later run.
|
|
36
|
+
There is no --force or --include-active option. Apply requires the estimated
|
|
37
|
+
database/event growth plus a 10 GiB free-space reserve.
|
|
38
|
+
|
|
39
|
+
Exit codes:
|
|
40
|
+
0 Completed with no malformed rows or invariant failures.
|
|
41
|
+
1 Safe tasks completed but a row/invariant failed, or apply was blocked.
|
|
42
|
+
2 Invalid command-line arguments.
|
|
43
|
+
|
|
44
|
+
The command never runs automatically at server startup and never runs VACUUM.`;
|
|
45
|
+
exports.USAGE_BACKFILL_HELP = `Usage:
|
|
46
|
+
crewx db backfill-usage [--dry-run|--apply|--verify] [--db PATH] [--backup PATH]
|
|
47
|
+
crewx db backfill-usage --restore SNAPSHOT [--db PATH]
|
|
48
|
+
|
|
49
|
+
Modes:
|
|
50
|
+
--dry-run Show provider/reason recovery plans without changing the database (default).
|
|
51
|
+
--apply Create a row-level undo snapshot, then account one task per transaction.
|
|
52
|
+
--verify Recheck remaining targets and usageAccounting markers without changing the database.
|
|
53
|
+
--restore Restore rows from SNAPSHOT when their current values still match the snapshot after-state.
|
|
54
|
+
|
|
55
|
+
Apply policy:
|
|
56
|
+
Only success, failed, and completed tasks with zero recorded usage are considered.
|
|
57
|
+
Workflow/MCP anchors and plugin/mock rows are excluded. Rows completed within 24 hours
|
|
58
|
+
are not marked unavailable when their logs do not contain usage.
|
|
59
|
+
|
|
60
|
+
Exit codes:
|
|
61
|
+
0 Completed successfully.
|
|
62
|
+
1 Apply was blocked or one or more task updates failed.
|
|
63
|
+
2 Invalid command-line arguments.`;
|
|
20
64
|
function defaultDbPath() {
|
|
21
65
|
return path_1.default.join(os_1.default.homedir(), '.crewx', 'crewx.db');
|
|
22
66
|
}
|
|
@@ -57,6 +101,12 @@ function formatPreview(result, dbPath) {
|
|
|
57
101
|
function hasChanges(result) {
|
|
58
102
|
return result.created.length > 0 || result.altered.length > 0;
|
|
59
103
|
}
|
|
104
|
+
function formatStatusCounts(counts) {
|
|
105
|
+
const entries = Object.entries(counts ?? {});
|
|
106
|
+
return entries.length > 0
|
|
107
|
+
? entries.map(([status, count]) => `${status}=${count}`).join(', ')
|
|
108
|
+
: '(none)';
|
|
109
|
+
}
|
|
60
110
|
function prompt(question) {
|
|
61
111
|
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
62
112
|
return new Promise((resolve) => {
|
|
@@ -68,13 +118,343 @@ function prompt(question) {
|
|
|
68
118
|
}
|
|
69
119
|
async function handleDb(args) {
|
|
70
120
|
const subcommand = args[0];
|
|
121
|
+
if (subcommand === 'migrate-task-logs') {
|
|
122
|
+
await handleTaskLogMigration(args.slice(1));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (subcommand === 'backfill-usage') {
|
|
126
|
+
await handleUsageBackfill(args.slice(1));
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (subcommand === '--help' || subcommand === '-h') {
|
|
130
|
+
console.log('Usage: crewx db push [--force] [--dry-run]');
|
|
131
|
+
console.log(exports.TASK_LOG_MIGRATION_HELP);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
71
134
|
if (!subcommand || subcommand === 'push') {
|
|
72
135
|
await handleDbPush(args.slice(subcommand === 'push' ? 1 : 0));
|
|
73
136
|
return;
|
|
74
137
|
}
|
|
75
138
|
console.error(`Unknown db subcommand: ${subcommand}`);
|
|
76
139
|
console.error('Usage: crewx db push [--force] [--dry-run]');
|
|
77
|
-
|
|
140
|
+
console.error(exports.TASK_LOG_MIGRATION_HELP);
|
|
141
|
+
process.exitCode = 2;
|
|
142
|
+
}
|
|
143
|
+
class TaskLogMigrationUsageError extends Error {
|
|
144
|
+
}
|
|
145
|
+
class UsageBackfillUsageError extends Error {
|
|
146
|
+
}
|
|
147
|
+
function parseTaskLogMigrationArgs(args) {
|
|
148
|
+
let mode;
|
|
149
|
+
let dbPath;
|
|
150
|
+
let batchTasks;
|
|
151
|
+
const setMode = (next) => {
|
|
152
|
+
if (mode)
|
|
153
|
+
throw new TaskLogMigrationUsageError('Choose exactly one of --dry-run, --apply, or --verify.');
|
|
154
|
+
mode = next;
|
|
155
|
+
};
|
|
156
|
+
const requireValue = (args, index, flag) => {
|
|
157
|
+
const value = args[index + 1];
|
|
158
|
+
if (!value || value.startsWith('--'))
|
|
159
|
+
throw new TaskLogMigrationUsageError(`${flag} requires a value.`);
|
|
160
|
+
return value;
|
|
161
|
+
};
|
|
162
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
163
|
+
const arg = args[index];
|
|
164
|
+
if (arg === '--help' || arg === '-h') {
|
|
165
|
+
throw new TaskLogMigrationUsageError(exports.TASK_LOG_MIGRATION_HELP);
|
|
166
|
+
}
|
|
167
|
+
if (arg === '--dry-run') {
|
|
168
|
+
setMode('dry-run');
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (arg === '--apply') {
|
|
172
|
+
setMode('apply');
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (arg === '--verify') {
|
|
176
|
+
setMode('verify');
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (arg === '--db') {
|
|
180
|
+
dbPath = requireValue(args, index, '--db');
|
|
181
|
+
index += 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (arg.startsWith('--db=')) {
|
|
185
|
+
dbPath = arg.slice('--db='.length);
|
|
186
|
+
if (!dbPath)
|
|
187
|
+
throw new TaskLogMigrationUsageError('--db requires a value.');
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (arg === '--batch-tasks') {
|
|
191
|
+
const value = requireValue(args, index, '--batch-tasks');
|
|
192
|
+
const parsed = Number(value);
|
|
193
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1) {
|
|
194
|
+
throw new TaskLogMigrationUsageError('--batch-tasks must be a positive integer.');
|
|
195
|
+
}
|
|
196
|
+
batchTasks = parsed;
|
|
197
|
+
index += 1;
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (arg.startsWith('--batch-tasks=')) {
|
|
201
|
+
const value = arg.slice('--batch-tasks='.length);
|
|
202
|
+
const parsed = Number(value);
|
|
203
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1) {
|
|
204
|
+
throw new TaskLogMigrationUsageError('--batch-tasks must be a positive integer.');
|
|
205
|
+
}
|
|
206
|
+
batchTasks = parsed;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
throw new TaskLogMigrationUsageError(`Unknown option: ${arg}`);
|
|
210
|
+
}
|
|
211
|
+
if (!mode)
|
|
212
|
+
throw new TaskLogMigrationUsageError('Choose one of --dry-run, --apply, or --verify.');
|
|
213
|
+
if (batchTasks !== undefined && mode !== 'apply') {
|
|
214
|
+
throw new TaskLogMigrationUsageError('--batch-tasks is only valid with --apply.');
|
|
215
|
+
}
|
|
216
|
+
return { mode, dbPath, batchTasks };
|
|
217
|
+
}
|
|
218
|
+
function formatMigrationReport(report) {
|
|
219
|
+
const modeLabel = report.mode === 'dry-run' ? 'Dry-run' : report.mode === 'apply' ? 'Apply' : 'Verify';
|
|
220
|
+
const lines = [
|
|
221
|
+
`[crewx] Task-log migration — ${modeLabel}`,
|
|
222
|
+
` Database: ${report.dbPath}`,
|
|
223
|
+
` Tasks scanned: ${report.taskCount}`,
|
|
224
|
+
` Blob candidates: ${report.candidateTaskCount}`,
|
|
225
|
+
` Eligible terminal blobs: ${report.eligibleTaskCount}`,
|
|
226
|
+
` Status counts: ${formatStatusCounts(report.statusCounts)}`,
|
|
227
|
+
` Deferred blob tasks: ${report.deferredTaskCount}`,
|
|
228
|
+
` Deferred statuses: ${formatStatusCounts(report.deferredStatusCounts)}`,
|
|
229
|
+
` Entries: ${report.entryCount}`,
|
|
230
|
+
` Source bytes: ${report.sourceBytes}`,
|
|
231
|
+
` Estimated free-space need: ${report.estimatedFreeSpaceBytes}`,
|
|
232
|
+
` Required free space (with reserve): ${report.requiredFreeSpaceBytes}`,
|
|
233
|
+
` Available free space: ${report.availableFreeSpaceBytes}`,
|
|
234
|
+
` Free-space gate: ${report.freeSpaceGatePassed ? 'PASS' : 'FAIL'}`,
|
|
235
|
+
` Migrated: ${report.migratedTasks} task(s), ${report.migratedEntries} entr${report.migratedEntries === 1 ? 'y' : 'ies'}`,
|
|
236
|
+
` Skipped event-source tasks: ${report.skippedTasks}`,
|
|
237
|
+
` Failed tasks: ${report.failedTasks}`,
|
|
238
|
+
` Logical source bytes removed: ${report.logicalSourceBytesRemoved}`,
|
|
239
|
+
];
|
|
240
|
+
if (report.remainingTaskCount !== undefined)
|
|
241
|
+
lines.push(` Remaining blob tasks: ${report.remainingTaskCount}`);
|
|
242
|
+
if (report.backupPath) {
|
|
243
|
+
lines.push(` Backup: ${report.backupPath}`);
|
|
244
|
+
if (report.backupMethod)
|
|
245
|
+
lines.push(` Backup method: ${report.backupMethod}`);
|
|
246
|
+
}
|
|
247
|
+
if (report.verification) {
|
|
248
|
+
lines.push(` Event rows checked: ${report.verification.eventCount}`);
|
|
249
|
+
lines.push(` Verification issues: ${report.verification.issues.length}`);
|
|
250
|
+
}
|
|
251
|
+
if (report.blockedReason)
|
|
252
|
+
lines.push(` Blocked: ${report.blockedReason}`);
|
|
253
|
+
for (const warning of report.warnings ?? [])
|
|
254
|
+
lines.push(` Warning: ${warning}`);
|
|
255
|
+
return lines.join('\n');
|
|
256
|
+
}
|
|
257
|
+
async function handleTaskLogMigration(args) {
|
|
258
|
+
let parsed;
|
|
259
|
+
try {
|
|
260
|
+
parsed = parseTaskLogMigrationArgs(args);
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
264
|
+
if (message === exports.TASK_LOG_MIGRATION_HELP) {
|
|
265
|
+
console.log(message);
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
console.error(message);
|
|
269
|
+
console.error(exports.TASK_LOG_MIGRATION_HELP);
|
|
270
|
+
process.exitCode = 2;
|
|
271
|
+
}
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
try {
|
|
275
|
+
const report = await (0, repository_3.runTaskLogMigration)(parsed);
|
|
276
|
+
console.log(formatMigrationReport(report));
|
|
277
|
+
if (!report.ok) {
|
|
278
|
+
if (report.blockedReason)
|
|
279
|
+
console.error(report.blockedReason);
|
|
280
|
+
const failures = report.failures ?? report.malformedRows ?? [];
|
|
281
|
+
for (const failure of failures) {
|
|
282
|
+
console.error(` ${failure.taskId}: ${failure.reason}`);
|
|
283
|
+
}
|
|
284
|
+
process.exitCode = 1;
|
|
285
|
+
}
|
|
286
|
+
else {
|
|
287
|
+
process.exitCode = 0;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
292
|
+
process.exitCode = 1;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
function parseUsageBackfillArgs(args) {
|
|
296
|
+
let mode;
|
|
297
|
+
let dbPath;
|
|
298
|
+
let backupPath;
|
|
299
|
+
let restorePath;
|
|
300
|
+
const setMode = (next) => {
|
|
301
|
+
if (mode)
|
|
302
|
+
throw new UsageBackfillUsageError('Choose exactly one of --dry-run, --apply, --verify, or --restore.');
|
|
303
|
+
mode = next;
|
|
304
|
+
};
|
|
305
|
+
const requireValue = (index, flag) => {
|
|
306
|
+
const value = args[index + 1];
|
|
307
|
+
if (!value || value.startsWith('--'))
|
|
308
|
+
throw new UsageBackfillUsageError(`${flag} requires a value.`);
|
|
309
|
+
return value;
|
|
310
|
+
};
|
|
311
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
312
|
+
const arg = args[index];
|
|
313
|
+
if (arg === '--help' || arg === '-h')
|
|
314
|
+
throw new UsageBackfillUsageError(exports.USAGE_BACKFILL_HELP);
|
|
315
|
+
if (arg === '--dry-run') {
|
|
316
|
+
setMode('dry-run');
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
if (arg === '--apply') {
|
|
320
|
+
setMode('apply');
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
if (arg === '--verify') {
|
|
324
|
+
setMode('verify');
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (arg === '--restore') {
|
|
328
|
+
setMode('restore');
|
|
329
|
+
restorePath = requireValue(index, '--restore');
|
|
330
|
+
index += 1;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (arg.startsWith('--restore=')) {
|
|
334
|
+
setMode('restore');
|
|
335
|
+
restorePath = arg.slice('--restore='.length);
|
|
336
|
+
if (!restorePath)
|
|
337
|
+
throw new UsageBackfillUsageError('--restore requires a value.');
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (arg === '--db') {
|
|
341
|
+
dbPath = requireValue(index, '--db');
|
|
342
|
+
index += 1;
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
345
|
+
if (arg.startsWith('--db=')) {
|
|
346
|
+
dbPath = arg.slice('--db='.length);
|
|
347
|
+
if (!dbPath)
|
|
348
|
+
throw new UsageBackfillUsageError('--db requires a value.');
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (arg === '--backup') {
|
|
352
|
+
backupPath = requireValue(index, '--backup');
|
|
353
|
+
index += 1;
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
if (arg.startsWith('--backup=')) {
|
|
357
|
+
backupPath = arg.slice('--backup='.length);
|
|
358
|
+
if (!backupPath)
|
|
359
|
+
throw new UsageBackfillUsageError('--backup requires a value.');
|
|
360
|
+
continue;
|
|
361
|
+
}
|
|
362
|
+
throw new UsageBackfillUsageError(`Unknown option: ${arg}`);
|
|
363
|
+
}
|
|
364
|
+
const selectedMode = mode ?? 'dry-run';
|
|
365
|
+
if (backupPath && selectedMode !== 'apply') {
|
|
366
|
+
throw new UsageBackfillUsageError('--backup is only valid with --apply.');
|
|
367
|
+
}
|
|
368
|
+
if (restorePath && selectedMode !== 'restore') {
|
|
369
|
+
throw new UsageBackfillUsageError('--restore is only valid with restore mode.');
|
|
370
|
+
}
|
|
371
|
+
return { mode: selectedMode, dbPath, backupPath, restorePath };
|
|
372
|
+
}
|
|
373
|
+
function formatUsageBackfillReport(report) {
|
|
374
|
+
const modeLabel = report.mode === 'dry-run'
|
|
375
|
+
? 'Dry-run'
|
|
376
|
+
: report.mode === 'apply'
|
|
377
|
+
? 'Apply'
|
|
378
|
+
: report.mode === 'restore'
|
|
379
|
+
? 'Restore'
|
|
380
|
+
: 'Verify';
|
|
381
|
+
const lines = [
|
|
382
|
+
`[crewx] Usage backfill — ${modeLabel}`,
|
|
383
|
+
` Database: ${report.dbPath}`,
|
|
384
|
+
` Tasks scanned: ${report.scannedTasks}`,
|
|
385
|
+
` Terminal tasks: ${report.terminalTasks}`,
|
|
386
|
+
` Raw target tasks: ${report.rawTargetTasks}`,
|
|
387
|
+
` Candidate tasks: ${report.candidateTasks}`,
|
|
388
|
+
` Recovered: ${report.recoveredTasks}`,
|
|
389
|
+
` Unavailable: ${report.unavailableTasks}`,
|
|
390
|
+
` Deferred (within 24h): ${report.deferredTasks}`,
|
|
391
|
+
` Changed: ${report.changedTasks}`,
|
|
392
|
+
` Would change: ${report.wouldChangeTasks}`,
|
|
393
|
+
` Restored: ${report.restored}`,
|
|
394
|
+
` Skipped conflicts: ${report.skippedConflict}`,
|
|
395
|
+
` Remaining target tasks: ${report.remainingTargetTasks}`,
|
|
396
|
+
` Tasks with usageAccounting: ${report.accountingTasks}`,
|
|
397
|
+
' Provider distribution:',
|
|
398
|
+
];
|
|
399
|
+
const providers = Object.entries(report.providerDistribution);
|
|
400
|
+
if (providers.length === 0) {
|
|
401
|
+
lines.push(' (none)');
|
|
402
|
+
}
|
|
403
|
+
else {
|
|
404
|
+
for (const [provider, stats] of providers) {
|
|
405
|
+
lines.push(` ${provider}: candidates=${stats.candidates}, recovered=${stats.recovered}, unavailable=${stats.unavailable}, deferred=${stats.deferred}`);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
lines.push(' Reason counts:');
|
|
409
|
+
for (const [reason, count] of Object.entries(report.reasonCounts))
|
|
410
|
+
lines.push(` ${reason}: ${count}`);
|
|
411
|
+
lines.push(` Estimated free-space need: ${report.estimatedFreeSpaceBytes}`);
|
|
412
|
+
lines.push(` Required free space (with reserve): ${report.requiredFreeSpaceBytes}`);
|
|
413
|
+
lines.push(` Available free space: ${report.availableFreeSpaceBytes}`);
|
|
414
|
+
lines.push(` Free-space gate: ${report.freeSpaceGatePassed ? 'PASS' : 'FAIL'}`);
|
|
415
|
+
const snapshotPath = report.snapshotPath ?? report.backupPath;
|
|
416
|
+
if (snapshotPath) {
|
|
417
|
+
lines.push(` Undo snapshot: ${snapshotPath}`);
|
|
418
|
+
if (report.backupMethod)
|
|
419
|
+
lines.push(` Snapshot method: ${report.backupMethod}`);
|
|
420
|
+
}
|
|
421
|
+
if (report.blockedReason)
|
|
422
|
+
lines.push(` Blocked: ${report.blockedReason}`);
|
|
423
|
+
for (const conflict of report.conflictSamples) {
|
|
424
|
+
lines.push(` Conflict sample: ${conflict.taskId}: ${conflict.fields.join(', ')}`);
|
|
425
|
+
}
|
|
426
|
+
for (const failure of report.failures)
|
|
427
|
+
lines.push(` Failure: ${failure.taskId}: ${failure.reason}`);
|
|
428
|
+
for (const warning of report.warnings)
|
|
429
|
+
lines.push(` Warning: ${warning}`);
|
|
430
|
+
return lines.join('\n');
|
|
431
|
+
}
|
|
432
|
+
async function handleUsageBackfill(args) {
|
|
433
|
+
let parsed;
|
|
434
|
+
try {
|
|
435
|
+
parsed = parseUsageBackfillArgs(args);
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
439
|
+
if (message === exports.USAGE_BACKFILL_HELP) {
|
|
440
|
+
console.log(message);
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
console.error(message);
|
|
444
|
+
console.error(exports.USAGE_BACKFILL_HELP);
|
|
445
|
+
process.exitCode = 2;
|
|
446
|
+
}
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
try {
|
|
450
|
+
const report = await (0, repository_4.runUsageBackfill)(parsed);
|
|
451
|
+
console.log(formatUsageBackfillReport(report));
|
|
452
|
+
process.exitCode = report.ok ? 0 : 1;
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
456
|
+
process.exitCode = 1;
|
|
457
|
+
}
|
|
78
458
|
}
|
|
79
459
|
async function handleDbPush(args) {
|
|
80
460
|
const force = args.includes('--force');
|