@crewx/cli 0.9.0-rc.9 → 0.9.0-rc.90

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.
Files changed (45) hide show
  1. package/dist/bootstrap/codex-writable-roots.d.ts +13 -0
  2. package/dist/bootstrap/codex-writable-roots.js +25 -0
  3. package/dist/bootstrap/crewx-cli.js +2 -1
  4. package/dist/builtin.js +1 -0
  5. package/dist/commands/agent.js +0 -58
  6. package/dist/commands/db.d.ts +1 -0
  7. package/dist/commands/db.js +191 -1
  8. package/dist/commands/doctor.d.ts +53 -0
  9. package/dist/commands/doctor.js +391 -21
  10. package/dist/commands/emit-trailer.d.ts +25 -0
  11. package/dist/commands/emit-trailer.js +33 -0
  12. package/dist/commands/execute.d.ts +6 -1
  13. package/dist/commands/execute.js +162 -11
  14. package/dist/commands/hook/command-marker.d.ts +2 -0
  15. package/dist/commands/hook/command-marker.js +5 -0
  16. package/dist/commands/hook/install.d.ts +0 -1
  17. package/dist/commands/hook/install.js +60 -63
  18. package/dist/commands/hook/status.js +3 -3
  19. package/dist/commands/hook/uninstall.js +2 -2
  20. package/dist/commands/init.js +22 -1
  21. package/dist/commands/log.js +4 -3
  22. package/dist/commands/parse-common-flags.d.ts +5 -1
  23. package/dist/commands/parse-common-flags.js +6 -2
  24. package/dist/commands/ps.js +7 -6
  25. package/dist/commands/publish.d.ts +1 -0
  26. package/dist/commands/publish.js +290 -0
  27. package/dist/commands/query.d.ts +3 -1
  28. package/dist/commands/query.js +78 -11
  29. package/dist/commands/registry.js +3 -1
  30. package/dist/commands/result.d.ts +7 -3
  31. package/dist/commands/result.js +41 -6
  32. package/dist/commands/shortcut.d.ts +1 -0
  33. package/dist/commands/shortcut.js +267 -0
  34. package/dist/commands/slack.js +2 -1
  35. package/dist/commands/write-output.d.ts +3 -0
  36. package/dist/commands/write-output.js +24 -0
  37. package/dist/logging.d.ts +1 -1
  38. package/dist/logging.js +3 -2
  39. package/dist/main.d.ts +3 -2
  40. package/dist/main.js +56 -11
  41. package/dist/utils/env-defaults.d.ts +2 -5
  42. package/dist/utils/env-defaults.js +10 -5
  43. package/dist/utils/sdk-compat.d.ts +24 -0
  44. package/dist/utils/sdk-compat.js +120 -0
  45. package/package.json +13 -11
@@ -34,9 +34,9 @@ function isAgyCommand(codingAgentCommand) {
34
34
  const base = firstToken.split('/').pop() ?? firstToken;
35
35
  return base.startsWith('agy');
36
36
  }
37
- function getLastActive(task) {
37
+ function getLastActive(task, taskRepo) {
38
38
  try {
39
- const entries = task.logs ? JSON.parse(task.logs) : [];
39
+ const entries = taskRepo.readLogsTail(task.id, 1)?.entries ?? [];
40
40
  if (Array.isArray(entries) && entries.length > 0) {
41
41
  const lastEntry = entries[entries.length - 1];
42
42
  const ts = lastEntry?.timestamp;
@@ -59,9 +59,9 @@ function getLastActive(task) {
59
59
  }
60
60
  return { display: '—', isoTimestamp: null, logCapable: true };
61
61
  }
62
- function taskToRow(task) {
62
+ function taskToRow(task, taskRepo) {
63
63
  const elapsed = formatElapsed(Date.now() - new Date(task.started_at).getTime());
64
- const lastActive = getLastActive(task);
64
+ const lastActive = getLastActive(task, taskRepo);
65
65
  return [
66
66
  task.id,
67
67
  task.agent_id ?? '—',
@@ -83,6 +83,7 @@ function renderTable(headers, rows) {
83
83
  }
84
84
  async function handlePs(args) {
85
85
  const repo = new repository_1.TaskRepository();
86
+ repo.reapRunningWorkflowTasks();
86
87
  const tasks = repo.getRunningTasks();
87
88
  if (tasks.length === 0) {
88
89
  console.log('No running tasks.');
@@ -90,7 +91,7 @@ async function handlePs(args) {
90
91
  }
91
92
  if (args.includes('--json')) {
92
93
  const withLastActive = tasks.map((task) => {
93
- const lastActive = getLastActive(task);
94
+ const lastActive = getLastActive(task, repo);
94
95
  return {
95
96
  ...task,
96
97
  last_active_at: lastActive.isoTimestamp,
@@ -101,7 +102,7 @@ async function handlePs(args) {
101
102
  return;
102
103
  }
103
104
  const headers = ['TASK ID', 'AGENT', 'PID', 'ELAPSED', 'MODE', 'LAST ACTIVE'];
104
- const rows = tasks.map(taskToRow);
105
+ const rows = tasks.map((task) => taskToRow(task, repo));
105
106
  renderTable(headers, rows);
106
107
  console.log(`\n ${tasks.length} running task(s)`);
107
108
  }
@@ -0,0 +1 @@
1
+ export declare function handlePublish(args: string[]): Promise<void>;
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handlePublish = handlePublish;
37
+ /**
38
+ * crewx publish handler — thin CLI wrapper over @crewx/sdk/publish.
39
+ *
40
+ * Usage:
41
+ * crewx publish [dir] [--dry-run] [--version <semver>] [--json] [--upload]
42
+ *
43
+ * All scanning, exclusion-rule judgment, secret detection, hashing, and
44
+ * tar.gz packing lives in @crewx/sdk/publish (planPublish / packTemplate).
45
+ * Submit+upload (WI-SHR-20260806-013) lives in the same package
46
+ * (uploadToMarketplace) for the same reason — see that module's header.
47
+ * This command only parses argv, forwards options, asks the SDK SessionManager
48
+ * for a valid account bearer when upload is requested, and renders the result.
49
+ */
50
+ const fs = __importStar(require("fs"));
51
+ const account_1 = require("@crewx/sdk/account");
52
+ const publish_1 = require("@crewx/sdk/publish");
53
+ const parse_common_flags_1 = require("./parse-common-flags");
54
+ function parsePublishFlags(args) {
55
+ const flags = { dryRun: false, json: false, help: false, upload: false };
56
+ for (let i = 0; i < args.length; i++) {
57
+ const arg = args[i];
58
+ if (arg === '--help' || arg === '-h') {
59
+ flags.help = true;
60
+ continue;
61
+ }
62
+ if (arg === '--dry-run') {
63
+ flags.dryRun = true;
64
+ continue;
65
+ }
66
+ if (arg === '--json') {
67
+ flags.json = true;
68
+ continue;
69
+ }
70
+ if (arg === '--upload') {
71
+ flags.upload = true;
72
+ continue;
73
+ }
74
+ if (arg === '--version') {
75
+ const value = args[i + 1];
76
+ if (value === undefined)
77
+ throw new parse_common_flags_1.UnknownOptionError('--version requires a value');
78
+ flags.version = value;
79
+ i++;
80
+ continue;
81
+ }
82
+ if (arg.startsWith('--version=')) {
83
+ flags.version = arg.slice('--version='.length);
84
+ continue;
85
+ }
86
+ if (arg.startsWith('-')) {
87
+ throw new parse_common_flags_1.UnknownOptionError(`Unknown option: ${arg}`);
88
+ }
89
+ if (flags.dir === undefined) {
90
+ flags.dir = arg;
91
+ continue;
92
+ }
93
+ throw new parse_common_flags_1.UnknownOptionError(`Unknown option: ${arg}`);
94
+ }
95
+ return flags;
96
+ }
97
+ function resolveWorkspaceDir(flags) {
98
+ return flags.dir ?? process.env['CREWX_WORKSPACE'] ?? process.cwd();
99
+ }
100
+ function printJson(payload) {
101
+ console.log(JSON.stringify(payload));
102
+ }
103
+ /** Prints the error and exits 1. Never returns (matches `process.exit`'s `never` type). */
104
+ function failWithError(err, json) {
105
+ const message = err instanceof Error ? err.message : String(err);
106
+ if (json) {
107
+ printJson({ success: false, error: message });
108
+ }
109
+ else {
110
+ console.error(`✗ ${message}`);
111
+ }
112
+ process.exit(1);
113
+ }
114
+ function printHelp() {
115
+ console.log(`
116
+ crewx publish — package a workspace as a distributable template archive
117
+
118
+ Usage:
119
+ crewx publish [dir] [--dry-run] [--version <semver>] [--json] [--upload]
120
+
121
+ Arguments:
122
+ dir Workspace to publish (default: $CREWX_WORKSPACE or cwd)
123
+
124
+ Options:
125
+ --dry-run Scan + build manifest only; do not write a .tgz
126
+ --version <ver> Override manifest version (semver, e.g. 1.0.0)
127
+ --json Print machine-readable JSON to stdout
128
+ --upload Submit + upload the packed archive to marketplace
129
+ (requires CREWX_MARKETPLACE_URL and a prior login;
130
+ ignored when combined with --dry-run)
131
+ --help, -h Show this help
132
+
133
+ Notes:
134
+ Without --upload, this command only scans the workspace, applies
135
+ exclusion rules, checks for secrets, and packs a local
136
+ .crewx/publish/<name>-<version>.tgz archive.
137
+ `.trim());
138
+ }
139
+ /** CREWX_MARKETPLACE_URL — same env var name as the server's MARKETPLACE_DISABLED check, never a second name. */
140
+ function requireMarketplaceUrl() {
141
+ const url = process.env['CREWX_MARKETPLACE_URL'];
142
+ if (!url?.trim()) {
143
+ throw new Error('publish: [E_ENV_MISSING] CREWX_MARKETPLACE_URL is not set — this environment variable is required for upload');
144
+ }
145
+ return url.trim();
146
+ }
147
+ function isMarketplaceUnauthorized(err) {
148
+ return err instanceof Error
149
+ && err.message.includes('publish: [E_AUTH_EXPIRED]')
150
+ && /HTTP 401\b/.test(err.message);
151
+ }
152
+ async function uploadWithSessionRefresh(sessionManager, options, accessToken) {
153
+ try {
154
+ return await (0, publish_1.uploadToMarketplace)({ ...options, accessToken });
155
+ }
156
+ catch (err) {
157
+ if (!isMarketplaceUnauthorized(err))
158
+ throw err;
159
+ const refreshedSession = await sessionManager.refresh();
160
+ return (0, publish_1.uploadToMarketplace)({ ...options, accessToken: refreshedSession.access_token });
161
+ }
162
+ }
163
+ async function handlePublish(args) {
164
+ const flags = parsePublishFlags(args);
165
+ if (flags.help) {
166
+ printHelp();
167
+ return;
168
+ }
169
+ const dir = resolveWorkspaceDir(flags);
170
+ process.stderr.write(`[publish] scanning ${dir}...\n`);
171
+ let plan;
172
+ try {
173
+ plan = await (0, publish_1.planPublish)(dir, { version: flags.version });
174
+ }
175
+ catch (err) {
176
+ failWithError(err, flags.json);
177
+ }
178
+ if (plan.secretFindings.length > 0) {
179
+ if (flags.json) {
180
+ printJson({ success: false, error: 'secret findings detected', findings: plan.secretFindings });
181
+ }
182
+ else {
183
+ console.error('✗ secret findings detected — publish aborted');
184
+ for (const f of plan.secretFindings) {
185
+ console.error(` ${f.path}:${f.line} (${f.rule})`);
186
+ }
187
+ }
188
+ process.exit(1);
189
+ }
190
+ if (flags.dryRun) {
191
+ if (flags.json) {
192
+ printJson({
193
+ success: true,
194
+ dryRun: true,
195
+ workspace: dir,
196
+ manifest: plan.manifest,
197
+ included: plan.included,
198
+ excluded: plan.excluded,
199
+ });
200
+ }
201
+ else {
202
+ console.log('✓ dry-run — no archive written');
203
+ console.log(` workspace: ${dir}`);
204
+ console.log(` name: ${plan.manifest.name} version: ${plan.manifest.version}`);
205
+ console.log(` included: ${plan.included.length} files`);
206
+ console.log(` excluded: ${plan.excluded.length} entries`);
207
+ for (const e of plan.excluded) {
208
+ console.log(` ${e.path} (${e.rule})`);
209
+ }
210
+ }
211
+ return;
212
+ }
213
+ // Resolved before packing (not after) so a doomed --upload run (no
214
+ // marketplace url / no session) never writes a stray .tgz to the
215
+ // workspace — WI-SHR-20260806-013 AC-U5.
216
+ let marketplaceUrl = '';
217
+ let accessToken = '';
218
+ let sessionManager;
219
+ if (flags.upload) {
220
+ try {
221
+ marketplaceUrl = requireMarketplaceUrl();
222
+ sessionManager = new account_1.SessionManager({ authClient: new account_1.AuthClient() });
223
+ accessToken = await sessionManager.getAccessToken();
224
+ }
225
+ catch (err) {
226
+ if (err instanceof Error && !err.message.startsWith('publish: [')) {
227
+ failWithError(new Error(`publish: [E_AUTH_EXPIRED] Authentication is missing or expired — please log in again — ${err.message}`), flags.json);
228
+ }
229
+ failWithError(err, flags.json);
230
+ }
231
+ }
232
+ process.stderr.write(`[publish] packing ${plan.manifest.name}@${plan.manifest.version}...\n`);
233
+ let packed;
234
+ try {
235
+ packed = await (0, publish_1.packTemplate)(dir, { version: flags.version });
236
+ }
237
+ catch (err) {
238
+ failWithError(err, flags.json);
239
+ }
240
+ const fileCount = packed.manifest.files.length;
241
+ const totalBytes = fs.statSync(packed.tgzPath).size;
242
+ let uploadResult;
243
+ if (flags.upload) {
244
+ process.stderr.write(`[publish] uploading ${packed.manifest.name}@${packed.manifest.version} to ${marketplaceUrl}...\n`);
245
+ try {
246
+ if (!sessionManager)
247
+ throw new Error('publish: [E_AUTH_EXPIRED] Account session is unavailable');
248
+ uploadResult = await uploadWithSessionRefresh(sessionManager, {
249
+ tgzPath: packed.tgzPath,
250
+ manifest: packed.manifest,
251
+ baseUrl: marketplaceUrl,
252
+ }, accessToken);
253
+ }
254
+ catch (err) {
255
+ failWithError(err, flags.json);
256
+ }
257
+ process.stderr.write(`[publish] submit -> ${uploadResult.submitOutcome}\n`);
258
+ }
259
+ if (flags.json) {
260
+ printJson({
261
+ success: true,
262
+ tgzPath: packed.tgzPath,
263
+ fileCount,
264
+ totalBytes,
265
+ manifest: packed.manifest,
266
+ ...(uploadResult
267
+ ? {
268
+ slug: uploadResult.slug,
269
+ version: uploadResult.version,
270
+ artifactChecksum: uploadResult.artifactChecksum,
271
+ artifactSizeBytes: uploadResult.artifactSizeBytes,
272
+ submitOutcome: uploadResult.submitOutcome,
273
+ versionUpdate: uploadResult.versionUpdate,
274
+ serverVersionBefore: uploadResult.serverVersionBefore,
275
+ }
276
+ : {}),
277
+ });
278
+ }
279
+ else {
280
+ console.log(`✓ packed: ${packed.tgzPath}`);
281
+ console.log(` files: ${fileCount} bytes: ${totalBytes}`);
282
+ if (uploadResult) {
283
+ console.log(`✓ uploaded: ${uploadResult.slug}@${uploadResult.version} (${uploadResult.submitOutcome})`);
284
+ console.log(` artifactChecksum: ${uploadResult.artifactChecksum} artifactSizeBytes: ${uploadResult.artifactSizeBytes}`);
285
+ }
286
+ }
287
+ if (!flags.upload) {
288
+ console.error('⚠ To upload, use --upload — check the generated file path');
289
+ }
290
+ }
@@ -5,6 +5,7 @@
5
5
  * Flags:
6
6
  * --thread <name> Conversation thread name
7
7
  * --provider <cli/xxx> Provider override
8
+ * --model <name> Model override (e.g. claude-sonnet-5)
8
9
  * --metadata <json> Extra metadata JSON (double-quoted object). Propagated to events/hooks/tracing.
9
10
  * e.g. --metadata='{"workflow_id":"wf-1"}'
10
11
  * --verbose Debug output mode (default: raw agent response only)
@@ -18,10 +19,11 @@
18
19
  * e.g. cat brief.md | crewx q "@agent label"
19
20
  * Stdin is ignored when running in an interactive TTY.
20
21
  */
22
+ import { type DelegationEmitState } from './emit-trailer';
21
23
  /**
22
24
  * Handle `crewx query <agentRef> <message>` command.
23
25
  *
24
26
  * Default output: raw agent response only (stdout).
25
27
  * --verbose: debug info written to stderr, response to stdout.
26
28
  */
27
- export declare function handleQuery(args: string[]): Promise<void>;
29
+ export declare function handleQuery(args: string[], command?: string, emitState?: DelegationEmitState): Promise<void>;
@@ -6,6 +6,7 @@
6
6
  * Flags:
7
7
  * --thread <name> Conversation thread name
8
8
  * --provider <cli/xxx> Provider override
9
+ * --model <name> Model override (e.g. claude-sonnet-5)
9
10
  * --metadata <json> Extra metadata JSON (double-quoted object). Propagated to events/hooks/tracing.
10
11
  * e.g. --metadata='{"workflow_id":"wf-1"}'
11
12
  * --verbose Debug output mode (default: raw agent response only)
@@ -27,19 +28,38 @@ const parse_common_flags_1 = require("./parse-common-flags");
27
28
  const resolve_prompt_1 = require("./resolve-prompt");
28
29
  const crewx_cli_1 = require("../bootstrap/crewx-cli");
29
30
  const inherited_trace_1 = require("../utils/inherited-trace");
31
+ const write_output_1 = require("./write-output");
32
+ const emit_trailer_1 = require("./emit-trailer");
30
33
  /**
31
34
  * Handle `crewx query <agentRef> <message>` command.
32
35
  *
33
36
  * Default output: raw agent response only (stdout).
34
37
  * --verbose: debug info written to stderr, response to stdout.
35
38
  */
36
- async function handleQuery(args) {
37
- const { thread, provider, metadata, verbose, config, outputFormat, effort, promptFile, overdrive, vars, rest } = (0, parse_common_flags_1.parseCommonFlags)(args);
38
- const { agentRef: parsedAgentRef, message } = (0, parse_agent_message_1.parseAgentMessage)(rest);
39
- // No @mention → default to @crewx agent (matches cli-bak behaviour)
39
+ async function handleQuery(args, command = 'query', emitState = (0, emit_trailer_1.createDelegationEmitState)()) {
40
+ let parsedFlags;
41
+ try {
42
+ parsedFlags = (0, parse_common_flags_1.parseCommonFlags)(args);
43
+ }
44
+ catch (err) {
45
+ (0, emit_trailer_1.emitDelegationResult)({ command, isError: true, state: emitState });
46
+ throw err;
47
+ }
48
+ const { thread, provider, model, metadata, verbose, config, outputFormat, effort, promptFile, overdrive, vars, out, rest } = parsedFlags;
49
+ const inheritedTrace = (0, inherited_trace_1.readInheritedTrace)();
50
+ let parsedAgentRef;
51
+ let message;
52
+ let finalMessage;
53
+ try {
54
+ ({ agentRef: parsedAgentRef, message } = (0, parse_agent_message_1.parseAgentMessage)(rest));
55
+ // Resolve final prompt: argv | stdin pipe | --prompt-file (or combination)
56
+ finalMessage = await (0, resolve_prompt_1.resolvePrompt)(message, promptFile);
57
+ }
58
+ catch (err) {
59
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
60
+ throw err;
61
+ }
40
62
  const agentRef = parsedAgentRef || '@crewx';
41
- // Resolve final prompt: argv | stdin pipe | --prompt-file (or combination)
42
- const finalMessage = await (0, resolve_prompt_1.resolvePrompt)(message, promptFile);
43
63
  if (!finalMessage) {
44
64
  console.error('Usage: crewx query [@agent] <message> [options]');
45
65
  console.error(' crewx q [@agent] <message> [options]');
@@ -55,6 +75,7 @@ async function handleQuery(args) {
55
75
  console.error('Options:');
56
76
  console.error(' --thread <name> Conversation thread name');
57
77
  console.error(' --provider <cli/xxx> Provider override');
78
+ console.error(' --model <name> Model override (e.g. claude-sonnet-5)');
58
79
  console.error(' --metadata <json> Extra metadata (JSON object, double-quoted).');
59
80
  console.error(' Propagated to events/hooks/tracing.');
60
81
  console.error(' Invalid JSON aborts with exit code 2.');
@@ -62,16 +83,26 @@ async function handleQuery(args) {
62
83
  console.error(' --verbose Debug output mode');
63
84
  console.error(' --config/-c <path> Config file path');
64
85
  console.error(' --output-format <fmt> Output format (json|text|stream-json)');
86
+ console.error(' --out/-o <path> Save result to file (stdout suppressed)');
65
87
  console.error(' --effort <level> Model effort (high|medium|low)');
66
88
  console.error(' -f/--prompt-file <path> Read prompt body from file');
67
89
  console.error(' --var key=value Template variable (repeatable)');
68
90
  console.error(' --overdrive Activate overdrive (boost) profile for this request');
91
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
69
92
  process.exit(1);
93
+ return;
70
94
  }
71
95
  const configPath = config ?? process.env.CREWX_CONFIG ?? 'crewx.yaml';
72
96
  // Only show exec audit span JSON in verbose mode
73
97
  (0, sdk_1.setAuditVerbose)(verbose);
74
- const crewx = await (0, crewx_cli_1.createCliCrewx)(configPath);
98
+ let crewx;
99
+ try {
100
+ crewx = await (0, crewx_cli_1.createCliCrewx)(configPath);
101
+ }
102
+ catch (err) {
103
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
104
+ throw err;
105
+ }
75
106
  // file:// remote agent delegation is handled transparently inside Crewx.query/execute.
76
107
  if (verbose) {
77
108
  process.stderr.write(`📋 Task: ${finalMessage}\n`);
@@ -80,6 +111,8 @@ async function handleQuery(args) {
80
111
  process.stderr.write(`🔗 Thread: ${thread}\n`);
81
112
  if (provider)
82
113
  process.stderr.write(`🔌 Provider: ${provider}\n`);
114
+ if (model)
115
+ process.stderr.write(`🧠 Model: ${model}\n`);
83
116
  if (outputFormat)
84
117
  process.stderr.write(`📄 Output-format: ${outputFormat}\n`);
85
118
  if (effort)
@@ -95,22 +128,34 @@ async function handleQuery(args) {
95
128
  catch (err) {
96
129
  const msg = err instanceof Error ? err.message : String(err);
97
130
  process.stderr.write(`Error: ${msg}\n`);
131
+ (0, emit_trailer_1.emitDelegationResult)({ command, trace: inheritedTrace, isError: true, state: emitState });
98
132
  process.exit(2);
133
+ return;
99
134
  }
135
+ // A trace with a rootTraceId but no parentTaskId means the id was pre-assigned
136
+ // to *this* task itself. Keep query and execute on the same contract so the
137
+ // reserved trace id remains the single source of truth for this task row.
138
+ const selfTaskId = inheritedTrace && !inheritedTrace.parentTaskId
139
+ ? (inheritedTrace.rootTraceId || undefined)
140
+ : undefined;
100
141
  let exitCode = 0;
142
+ let result;
101
143
  try {
102
- const result = await crewx.query(agentRef, finalMessage, {
144
+ result = await crewx.query(agentRef, finalMessage, {
103
145
  provider,
146
+ model,
104
147
  effort: effort || undefined,
105
148
  overdrive: overdrive || undefined,
106
149
  threadId: thread,
150
+ taskId: selfTaskId,
107
151
  metadata: Object.keys(parsedMetadata).length > 0 ? parsedMetadata : undefined,
108
152
  vars: Object.keys(vars).length > 0 ? vars : undefined,
109
- trace: (0, inherited_trace_1.readInheritedTrace)(),
153
+ trace: inheritedTrace,
110
154
  });
111
155
  if (!result.ok) {
112
156
  const errMsg = result.error?.message ?? 'Query failed';
113
157
  console.error(errMsg);
158
+ (0, write_output_1.appendError)(out, errMsg);
114
159
  exitCode = 1;
115
160
  }
116
161
  else {
@@ -123,7 +168,7 @@ async function handleQuery(args) {
123
168
  process.stderr.write('\n📄 Response:\n');
124
169
  process.stderr.write('─'.repeat(40) + '\n');
125
170
  }
126
- console.log(result.data);
171
+ (0, write_output_1.writeResult)(out, result.data);
127
172
  if (verbose) {
128
173
  process.stderr.write('\n✅ Query completed successfully\n');
129
174
  }
@@ -132,11 +177,33 @@ async function handleQuery(args) {
132
177
  catch (err) {
133
178
  const errMsg = err instanceof Error ? err.message : String(err);
134
179
  console.error(`Error: ${errMsg}`);
180
+ (0, write_output_1.appendError)(out, `Error: ${errMsg}`);
135
181
  exitCode = 1;
136
182
  }
137
183
  finally {
138
- await crewx.close();
184
+ try {
185
+ await crewx.close();
186
+ }
187
+ catch (err) {
188
+ (0, emit_trailer_1.emitDelegationResult)({
189
+ command,
190
+ trace: inheritedTrace,
191
+ taskId: result?.meta.taskId,
192
+ agentId: result?.meta.agentId,
193
+ isError: true,
194
+ state: emitState,
195
+ });
196
+ throw err;
197
+ }
139
198
  }
199
+ (0, emit_trailer_1.emitDelegationResult)({
200
+ command,
201
+ trace: inheritedTrace,
202
+ taskId: result?.meta.taskId,
203
+ agentId: result?.meta.agentId,
204
+ isError: exitCode !== 0 || result?.ok === false,
205
+ state: emitState,
206
+ });
140
207
  if (exitCode !== 0)
141
208
  process.exit(exitCode);
142
209
  }
@@ -17,13 +17,15 @@ exports.KNOWN_COMMANDS = new Set([
17
17
  'doctor', 'init',
18
18
  'db',
19
19
  'help',
20
+ 'shortcut',
21
+ 'publish',
20
22
  'slack', 'slack:files',
21
23
  'hook', 'hook-dispatch',
22
24
  ]);
23
25
  /** Built-in tool commands routed via handleBuiltin(). */
24
26
  exports.BUILTIN_COMMAND_NAMES = new Set([
25
27
  'memory', 'search', 'doc', 'wbs', 'cron', 'workflow', 'skill', 'dreaming',
26
- 'wi', 'chromex',
28
+ 'wi', 'chromex', 'notify',
27
29
  ]);
28
30
  /** Commands not yet migrated from cli-bak — show a migration message. */
29
31
  exports.NOT_YET_MIGRATED = new Set([
@@ -3,8 +3,12 @@
3
3
  * Retrieves the result of a completed task by its ID.
4
4
  *
5
5
  * Usage:
6
- * crewx result <task-id> Print raw result
7
- * crewx result <task-id> --json Print full task record as JSON
8
- * crewx result List recent tasks (latest 10)
6
+ * crewx result <task-id> Print raw result
7
+ * crewx result <task-id> --json Print full task record as JSON
8
+ * crewx result <task-id> --wait=N Poll (1s interval) up to N seconds for
9
+ * the task to leave 'running'. Exit 124 on
10
+ * timeout. --wait=0 behaves like no --wait
11
+ * (single immediate check).
12
+ * crewx result List recent tasks (latest 10)
9
13
  */
10
14
  export declare function handleResult(args: string[]): Promise<void>;
@@ -4,13 +4,19 @@
4
4
  * Retrieves the result of a completed task by its ID.
5
5
  *
6
6
  * Usage:
7
- * crewx result <task-id> Print raw result
8
- * crewx result <task-id> --json Print full task record as JSON
9
- * crewx result List recent tasks (latest 10)
7
+ * crewx result <task-id> Print raw result
8
+ * crewx result <task-id> --json Print full task record as JSON
9
+ * crewx result <task-id> --wait=N Poll (1s interval) up to N seconds for
10
+ * the task to leave 'running'. Exit 124 on
11
+ * timeout. --wait=0 behaves like no --wait
12
+ * (single immediate check).
13
+ * crewx result List recent tasks (latest 10)
10
14
  */
11
15
  Object.defineProperty(exports, "__esModule", { value: true });
12
16
  exports.handleResult = handleResult;
17
+ const sdk_1 = require("@crewx/sdk");
13
18
  const repository_1 = require("@crewx/sdk/repository");
19
+ const POLL_INTERVAL_MS = 1000;
14
20
  function statusIcon(status) {
15
21
  switch (status) {
16
22
  case 'running': return '⏳';
@@ -19,8 +25,20 @@ function statusIcon(status) {
19
25
  default: return '❓';
20
26
  }
21
27
  }
28
+ function sleep(ms) {
29
+ return new Promise((resolve) => setTimeout(resolve, ms));
30
+ }
31
+ /** Parses `--wait=N` (seconds). Returns undefined when the flag is absent. */
32
+ function parseWaitSeconds(args) {
33
+ const arg = args.find(a => a.startsWith('--wait='));
34
+ if (arg === undefined)
35
+ return undefined;
36
+ const n = Number(arg.slice('--wait='.length));
37
+ return Number.isFinite(n) && n >= 0 ? n : 0;
38
+ }
22
39
  async function handleResult(args) {
23
40
  const jsonMode = args.includes('--json');
41
+ const waitSeconds = parseWaitSeconds(args);
24
42
  const taskId = args.find(a => !a.startsWith('--'));
25
43
  const repo = new repository_1.TaskRepository();
26
44
  if (!taskId) {
@@ -36,21 +54,38 @@ async function handleResult(args) {
36
54
  const icon = statusIcon(task.status);
37
55
  console.log(`${idx + 1}. ${icon} ${task.id}`);
38
56
  console.log(` Agent: ${task.agent_id ?? '—'} Mode: ${task.mode ?? '—'}`);
39
- console.log(` Started: ${new Date(task.started_at).toLocaleString()}`);
57
+ console.log(` Started: ${(0, sdk_1.formatDisplayTimestamp)(new Date(task.started_at))}`);
40
58
  if (task.completed_at) {
41
- console.log(` Completed: ${new Date(task.completed_at).toLocaleString()}`);
59
+ console.log(` Completed: ${(0, sdk_1.formatDisplayTimestamp)(new Date(task.completed_at))}`);
42
60
  }
43
61
  console.log('');
44
62
  });
45
63
  console.log('Tip: Run `crewx result <task-id>` to see full output.');
46
64
  return;
47
65
  }
48
- const task = repo.getTask(taskId);
66
+ let task = repo.getTask(taskId);
49
67
  if (!task) {
50
68
  console.error(`Error: Task not found: ${taskId}`);
51
69
  process.exit(1);
52
70
  return;
53
71
  }
72
+ if (waitSeconds !== undefined && waitSeconds > 0 && task.status === 'running') {
73
+ const deadline = Date.now() + waitSeconds * 1000;
74
+ while (task && task.status === 'running') {
75
+ if (Date.now() >= deadline) {
76
+ console.error(`Task ${taskId} did not complete within --wait=${waitSeconds}s (status: running).`);
77
+ process.exit(124);
78
+ return;
79
+ }
80
+ await sleep(Math.min(POLL_INTERVAL_MS, Math.max(0, deadline - Date.now())));
81
+ task = repo.getTask(taskId);
82
+ if (!task) {
83
+ console.error(`Error: Task not found: ${taskId}`);
84
+ process.exit(1);
85
+ return;
86
+ }
87
+ }
88
+ }
54
89
  if (jsonMode) {
55
90
  console.log(JSON.stringify(task, null, 2));
56
91
  return;
@@ -0,0 +1 @@
1
+ export declare function handleShortcut(args: string[]): Promise<void>;