@skrr-ai/cli 0.1.103 → 0.1.105
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/base-command.js +6 -2
- package/dist/commands/agenda/trigger/list.js +2 -0
- package/dist/commands/agents/chat.js +4 -1
- package/dist/commands/agents/operating-plan.js +5 -2
- package/dist/commands/agents/triggers/create.js +3 -1
- package/dist/commands/agents/triggers/fire.js +2 -0
- package/dist/commands/agents/triggers/replay.js +2 -0
- package/dist/commands/agents/triggers/update.js +12 -4
- package/dist/commands/commitments/action-history.js +7 -2
- package/dist/commands/commitments/cadence-decisions.js +6 -1
- package/dist/commands/commitments/cadence-requests/decide.d.ts +1 -1
- package/dist/commands/commitments/cadence-requests/decide.js +1 -1
- package/dist/commands/commitments/create.js +2 -0
- package/dist/commands/commitments/doctor.js +7 -1
- package/dist/commands/commitments/observations.js +7 -1
- package/dist/commands/commitments/operating-review.d.ts +15 -0
- package/dist/commands/commitments/operating-review.js +23 -2
- package/dist/commands/commitments/preflight.d.ts +12 -0
- package/dist/commands/commitments/preflight.js +23 -9
- package/dist/commands/commitments/reflection.js +6 -1
- package/dist/commands/commitments/show.js +3 -1
- package/dist/commands/commitments/trigger-status.js +2 -22
- package/dist/commands/commitments/wake-capabilities.d.ts +13 -0
- package/dist/commands/commitments/wake-capabilities.js +39 -3
- package/dist/commands/daemon/logs.d.ts +109 -4
- package/dist/commands/daemon/logs.js +397 -43
- package/dist/commands/followups/act.js +13 -1
- package/dist/commands/followups/checkin.js +11 -2
- package/dist/commands/followups/reschedule.js +13 -1
- package/dist/commands/proposals/discard.d.ts +1 -1
- package/dist/commands/proposals/keep.d.ts +1 -1
- package/dist/commands/proposals/list.d.ts +7 -0
- package/dist/commands/proposals/list.js +31 -5
- package/dist/commands/proposals/show.d.ts +2 -1
- package/dist/commands/proposals/show.js +31 -4
- package/dist/commands/proposals/snooze.d.ts +1 -1
- package/dist/commands/requests/create.js +8 -3
- package/dist/commands/spaces/workflows/create.js +3 -3
- package/dist/commands/tasks/complete.js +4 -0
- package/dist/commands/tasks/create.d.ts +2 -0
- package/dist/commands/tasks/create.js +17 -3
- package/dist/commands/tasks/deliverable/add.d.ts +2 -0
- package/dist/commands/tasks/deliverable/add.js +22 -7
- package/dist/commands/tasks/deliverable/list.d.ts +13 -0
- package/dist/commands/tasks/deliverable/list.js +34 -1
- package/dist/commands/tasks/economics-compare.js +7 -1
- package/dist/commands/tasks/expectations.d.ts +10 -0
- package/dist/commands/tasks/expectations.js +31 -3
- package/dist/commands/tasks/list.d.ts +9 -0
- package/dist/commands/tasks/list.js +40 -5
- package/dist/commands/tasks/report.js +1 -1
- package/dist/commands/tasks/result/show.d.ts +7 -0
- package/dist/commands/tasks/result/show.js +32 -1
- package/dist/commands/tasks/result/submit.js +1 -1
- package/dist/commands/tasks/show.d.ts +6 -0
- package/dist/commands/tasks/show.js +23 -0
- package/dist/commands/tasks/update.d.ts +3 -0
- package/dist/commands/tasks/update.js +23 -3
- package/dist/commands/triggers/audit.js +6 -2
- package/dist/commands/triggers/create.js +3 -1
- package/dist/commands/triggers/fire.d.ts +1 -0
- package/dist/commands/triggers/fire.js +16 -1
- package/dist/commands/triggers/preflight.js +6 -5
- package/dist/commands/triggers/replay.js +2 -0
- package/dist/commands/triggers/update.js +12 -4
- package/dist/commands/workspaces/stewardship.js +5 -1
- package/dist/lib/agentic-stream.d.ts +9 -0
- package/dist/lib/agentic-stream.js +38 -18
- package/dist/lib/commitment-event-wakes.d.ts +8 -0
- package/dist/lib/commitment-event-wakes.js +39 -0
- package/dist/lib/commitment-ledgers.d.ts +9 -0
- package/dist/lib/commitment-ledgers.js +126 -0
- package/dist/lib/commitments.d.ts +102 -2
- package/dist/lib/commitments.js +208 -8
- package/dist/lib/delegated-cli.js +13 -2
- package/dist/lib/followups.d.ts +6 -0
- package/dist/lib/followups.js +12 -0
- package/dist/lib/message-content.d.ts +6 -0
- package/dist/lib/message-content.js +24 -0
- package/dist/lib/operating-plan-loops.d.ts +8 -0
- package/dist/lib/operating-plan-loops.js +32 -0
- package/dist/lib/preflight-copy.d.ts +27 -0
- package/dist/lib/preflight-copy.js +89 -0
- package/dist/lib/proposal-decision-command.d.ts +1 -1
- package/dist/lib/proposal-decision-command.js +27 -3
- package/dist/lib/proposals.d.ts +17 -0
- package/dist/lib/proposals.js +41 -0
- package/dist/lib/stewardship-output.js +57 -12
- package/dist/lib/task-economics-line.d.ts +30 -0
- package/dist/lib/task-economics-line.js +49 -0
- package/dist/lib/task-economics.d.ts +15 -0
- package/dist/lib/task-economics.js +73 -8
- package/dist/lib/task-recurrence-outcome.d.ts +19 -0
- package/dist/lib/task-recurrence-outcome.js +30 -0
- package/dist/lib/triggers.d.ts +42 -0
- package/dist/lib/triggers.js +136 -2
- package/dist/lib/widgetTerminalText.d.ts +8 -0
- package/dist/lib/widgetTerminalText.js +116 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +27 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.d.ts +84 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.js +222 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +26 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.d.ts +84 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.js +216 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +7628 -7414
- package/docs/v1/operations.md +1 -1
- package/oclif.manifest.json +117 -73
- package/package.json +1 -1
|
@@ -3,18 +3,31 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.STDERR_FLAG_DESCRIPTION = exports.ROTATED_LOGS_RUNTIME_VERSION = exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION = void 0;
|
|
6
7
|
exports.describeLogAge = describeLogAge;
|
|
7
8
|
exports.resolveLegacyDaemonLogPath = resolveLegacyDaemonLogPath;
|
|
8
9
|
exports.resolveDaemonLogsProfile = resolveDaemonLogsProfile;
|
|
10
|
+
exports.installedServiceLogDestinations = installedServiceLogDestinations;
|
|
9
11
|
exports.installedServiceLogCandidates = installedServiceLogCandidates;
|
|
10
12
|
exports.chooseLegacyFallbackLog = chooseLegacyFallbackLog;
|
|
11
13
|
exports.resolveForwardedLogPath = resolveForwardedLogPath;
|
|
14
|
+
exports.rotatedLogSiblings = rotatedLogSiblings;
|
|
15
|
+
exports.describeRotatedSiblings = describeRotatedSiblings;
|
|
16
|
+
exports.readLastLines = readLastLines;
|
|
17
|
+
exports.readLastLinesAcross = readLastLinesAcross;
|
|
18
|
+
exports.streamLogFiles = streamLogFiles;
|
|
19
|
+
exports.parseRuntimeVersion = parseRuntimeVersion;
|
|
20
|
+
exports.runtimeVersionAtLeast = runtimeVersionAtLeast;
|
|
21
|
+
exports.probeRuntimeVersion = probeRuntimeVersion;
|
|
22
|
+
exports.probeStatusLogPath = probeStatusLogPath;
|
|
23
|
+
exports.planDaemonLogs = planDaemonLogs;
|
|
12
24
|
exports.readDaemonLogTail = readDaemonLogTail;
|
|
13
25
|
exports.buildDaemonLogsArgs = buildDaemonLogsArgs;
|
|
14
26
|
const core_1 = require("@oclif/core");
|
|
15
27
|
const node_fs_1 = require("node:fs");
|
|
16
28
|
const node_os_1 = require("node:os");
|
|
17
29
|
const node_path_1 = __importDefault(require("node:path"));
|
|
30
|
+
const node_events_1 = require("node:events");
|
|
18
31
|
const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
|
|
19
32
|
const MAX_FALLBACK_LOG_BYTES = 1024 * 1024;
|
|
20
33
|
const PROFILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
@@ -93,22 +106,22 @@ function resolveDaemonLogsProfile(flags, options = {}) {
|
|
|
93
106
|
return profile ?? 'default';
|
|
94
107
|
}
|
|
95
108
|
/**
|
|
96
|
-
*
|
|
97
|
-
*
|
|
109
|
+
* Read the installed launchd definition's log destinations (OSK-15993). The
|
|
110
|
+
* desktop supervisor starts the daemon with
|
|
98
111
|
* `--log-file ~/Library/Logs/Skrr/daemon.app.log` and redirects stderr to
|
|
99
112
|
* `daemon.stderr.log`, so the profile's `logs/` directory can hold a file
|
|
100
113
|
* nobody has written for a week. Read-only: the plist is parsed, never loaded.
|
|
101
114
|
*/
|
|
102
|
-
function
|
|
115
|
+
function installedServiceLogDestinations(profile, options = {}) {
|
|
103
116
|
if ((options.platform ?? process.platform) !== 'darwin')
|
|
104
|
-
return
|
|
117
|
+
return null;
|
|
105
118
|
const plist = node_path_1.default.join(options.home ?? (0, node_os_1.homedir)(), 'Library', 'LaunchAgents', `ai.skrr.daemon.${profile}.plist`);
|
|
106
119
|
let xml;
|
|
107
120
|
try {
|
|
108
121
|
xml = (0, node_fs_1.readFileSync)(plist, 'utf8');
|
|
109
122
|
}
|
|
110
123
|
catch {
|
|
111
|
-
return
|
|
124
|
+
return null;
|
|
112
125
|
}
|
|
113
126
|
const unescape = (v) => v
|
|
114
127
|
.replace(/</g, '<')
|
|
@@ -120,22 +133,28 @@ function installedServiceLogCandidates(profile, stderr, options = {}) {
|
|
|
120
133
|
const m = new RegExp(`<key>\\s*${key}\\s*</key>\\s*<string>([\\s\\S]*?)</string>`).exec(xml);
|
|
121
134
|
return m?.[1]?.trim() ? unescape(m[1].trim()) : null;
|
|
122
135
|
};
|
|
123
|
-
|
|
124
|
-
const errPath = stringFor('StandardErrorPath');
|
|
125
|
-
return errPath ? [errPath] : [];
|
|
126
|
-
}
|
|
127
|
-
const out = [];
|
|
136
|
+
let logFile = null;
|
|
128
137
|
const array = /<key>\s*ProgramArguments\s*<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(xml);
|
|
129
138
|
if (array?.[1]) {
|
|
130
139
|
const args = [...array[1].matchAll(/<string>([\s\S]*?)<\/string>/g)].map((m) => unescape(m[1].trim()));
|
|
131
140
|
const at = args.lastIndexOf('--log-file');
|
|
132
141
|
if (at >= 0 && args[at + 1] && !args[at + 1].startsWith('-'))
|
|
133
|
-
|
|
142
|
+
logFile = args[at + 1];
|
|
134
143
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
144
|
+
return {
|
|
145
|
+
logFile,
|
|
146
|
+
stdoutPath: stringFor('StandardOutPath'),
|
|
147
|
+
stderrPath: stringFor('StandardErrorPath'),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/** The installed service's log files for the main log, or for `--stderr`. */
|
|
151
|
+
function installedServiceLogCandidates(profile, stderr, options = {}) {
|
|
152
|
+
const dest = installedServiceLogDestinations(profile, options);
|
|
153
|
+
if (!dest)
|
|
154
|
+
return [];
|
|
155
|
+
if (stderr)
|
|
156
|
+
return dest.stderrPath ? [dest.stderrPath] : [];
|
|
157
|
+
return [dest.logFile, dest.stdoutPath].filter((p) => Boolean(p));
|
|
139
158
|
}
|
|
140
159
|
/**
|
|
141
160
|
* Pick the file the legacy fallback reads. An old daemon cannot say which file
|
|
@@ -164,7 +183,7 @@ function chooseLegacyFallbackLog(args) {
|
|
|
164
183
|
(profileMtime === null
|
|
165
184
|
? `the profile log ${args.profilePath}, which does not exist.`
|
|
166
185
|
: `the profile log ${args.profilePath}, which has not been written since.`) +
|
|
167
|
-
'
|
|
186
|
+
` ${args.reason ?? 'This daemon predates `logs`, so it cannot name its live log itself.'}`,
|
|
168
187
|
};
|
|
169
188
|
}
|
|
170
189
|
/**
|
|
@@ -175,7 +194,9 @@ function chooseLegacyFallbackLog(args) {
|
|
|
175
194
|
*/
|
|
176
195
|
async function resolveForwardedLogPath(flags, capture = exec_runtime_binary_1.execRuntimeBinaryCapture) {
|
|
177
196
|
try {
|
|
178
|
-
|
|
197
|
+
// Only what decides the file: --rotated would list siblings first, and the
|
|
198
|
+
// last line is still the current file, but there is no reason to ask.
|
|
199
|
+
const out = await capture(buildDaemonLogsArgs({ stderr: flags.stderr, profile: flags.profile, path: true }), {
|
|
179
200
|
timeoutMs: 5_000,
|
|
180
201
|
});
|
|
181
202
|
if (out.code !== 0)
|
|
@@ -191,6 +212,266 @@ async function resolveForwardedLogPath(flags, capture = exec_runtime_binary_1.ex
|
|
|
191
212
|
return null;
|
|
192
213
|
}
|
|
193
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* The first runtime whose own `logs` reads the LIVE daemon's `--log-file`
|
|
217
|
+
* (OSK-15993, `daemon/src/daemon-log-target.ts`). 0.8.112 through 0.8.118 have
|
|
218
|
+
* a `logs` subcommand, so the subcommand probe says "supported", but it reads
|
|
219
|
+
* the profile's `logs/daemon.log` unconditionally — with a desktop-managed
|
|
220
|
+
* daemon, a file nobody has written for a week. Forwarding to one of those is
|
|
221
|
+
* forwarding to the bug, so the CLI forwards only at or above this version.
|
|
222
|
+
*/
|
|
223
|
+
exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION = '0.8.119';
|
|
224
|
+
/**
|
|
225
|
+
* The first runtime whose `logs` knows `--rotated` (OSK-16214). An older one
|
|
226
|
+
* rejects it as an unknown option, so the CLI never forwards the flag below
|
|
227
|
+
* this version: it asks the runtime which file it reads (`--path`) and reads
|
|
228
|
+
* that file and its rotated siblings itself.
|
|
229
|
+
*/
|
|
230
|
+
exports.ROTATED_LOGS_RUNTIME_VERSION = '0.8.120';
|
|
231
|
+
/**
|
|
232
|
+
* Rotated siblings of a log, OLDEST FIRST (OSK-16214). The daemon's logger
|
|
233
|
+
* renames `daemon.app.log` to `daemon.app.1.log`, `.1` to `.2` and so on at
|
|
234
|
+
* 10 MB (`daemon/src/logger.ts` `maybeRotate`), and `logs` never reached them —
|
|
235
|
+
* `--lines 5000 | grep <run id>` found nothing while `daemon.app.1.log` held the
|
|
236
|
+
* run. A copy of `daemon/src/rotated-log-files.ts`, for runtimes that predate
|
|
237
|
+
* `--rotated`. Never throws.
|
|
238
|
+
*/
|
|
239
|
+
function rotatedLogSiblings(filePath, readdir = (dir) => (0, node_fs_1.readdirSync)(dir)) {
|
|
240
|
+
const dir = node_path_1.default.dirname(filePath);
|
|
241
|
+
const ext = node_path_1.default.extname(filePath);
|
|
242
|
+
const base = node_path_1.default.basename(filePath, ext);
|
|
243
|
+
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
244
|
+
const pattern = new RegExp(`^${escape(base)}\\.(\\d+)${escape(ext)}$`);
|
|
245
|
+
let names;
|
|
246
|
+
try {
|
|
247
|
+
names = readdir(dir);
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
return [];
|
|
251
|
+
}
|
|
252
|
+
return names
|
|
253
|
+
.map((name) => ({ name, index: Number(pattern.exec(name)?.[1] ?? NaN) }))
|
|
254
|
+
.filter(({ index }) => Number.isSafeInteger(index) && index >= 1)
|
|
255
|
+
.sort((a, b) => b.index - a.index)
|
|
256
|
+
.map(({ name }) => node_path_1.default.join(dir, name));
|
|
257
|
+
}
|
|
258
|
+
/** The sentence shown when rotated siblings hold older lines the view leaves out. */
|
|
259
|
+
function describeRotatedSiblings(filePath, siblings) {
|
|
260
|
+
if (siblings.length === 0)
|
|
261
|
+
return null;
|
|
262
|
+
const names = siblings.map((s) => node_path_1.default.basename(s));
|
|
263
|
+
const listed = names.length === 1 ? names[0] : `${names.at(-1)} … ${names[0]}`;
|
|
264
|
+
return (`Older lines are in ${siblings.length} rotated file${siblings.length === 1 ? '' : 's'} ` +
|
|
265
|
+
`beside ${filePath} (${listed}). Add --rotated to read them too, oldest first.`);
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* The last `maxLines` lines of one file, read backwards in chunks so the cost
|
|
269
|
+
* follows the lines asked for, not the file's size. Bytes are decoded once, so
|
|
270
|
+
* a multi-byte character split across a chunk boundary survives.
|
|
271
|
+
*/
|
|
272
|
+
function readLastLines(filePath, maxLines) {
|
|
273
|
+
if (maxLines <= 0)
|
|
274
|
+
return [];
|
|
275
|
+
const { size } = (0, node_fs_1.statSync)(filePath);
|
|
276
|
+
const descriptor = (0, node_fs_1.openSync)(filePath, 'r');
|
|
277
|
+
try {
|
|
278
|
+
let start = size;
|
|
279
|
+
const chunks = [];
|
|
280
|
+
let newlines = 0;
|
|
281
|
+
while (start > 0 && newlines <= maxLines) {
|
|
282
|
+
const length = Math.min(64 * 1024, start);
|
|
283
|
+
start -= length;
|
|
284
|
+
const buffer = Buffer.alloc(length);
|
|
285
|
+
(0, node_fs_1.readSync)(descriptor, buffer, 0, length, start);
|
|
286
|
+
chunks.unshift(buffer);
|
|
287
|
+
for (const byte of buffer)
|
|
288
|
+
if (byte === 0x0a)
|
|
289
|
+
newlines += 1;
|
|
290
|
+
}
|
|
291
|
+
const lines = Buffer.concat(chunks).toString('utf8').split('\n');
|
|
292
|
+
if (lines.at(-1) === '')
|
|
293
|
+
lines.pop();
|
|
294
|
+
if (start > 0)
|
|
295
|
+
lines.shift();
|
|
296
|
+
return lines.slice(-maxLines);
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
(0, node_fs_1.closeSync)(descriptor);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* The last `maxLines` lines across `files` (OLDEST FIRST) as if they were one
|
|
304
|
+
* file. A file renamed away between the listing and the read is skipped.
|
|
305
|
+
*/
|
|
306
|
+
function readLastLinesAcross(files, maxLines) {
|
|
307
|
+
const collected = [];
|
|
308
|
+
let remaining = maxLines;
|
|
309
|
+
for (let i = files.length - 1; i >= 0 && remaining > 0; i -= 1) {
|
|
310
|
+
try {
|
|
311
|
+
const lines = readLastLines(files[i], remaining);
|
|
312
|
+
collected.unshift(lines);
|
|
313
|
+
remaining -= lines.length;
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
// Rotated away mid-read; the rest are still worth showing.
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return collected.flat();
|
|
320
|
+
}
|
|
321
|
+
/** Every byte of `files`, in order, to `out` — streamed, honouring back-pressure. */
|
|
322
|
+
async function streamLogFiles(files, out) {
|
|
323
|
+
for (const file of files) {
|
|
324
|
+
try {
|
|
325
|
+
for await (const chunk of (0, node_fs_1.createReadStream)(file)) {
|
|
326
|
+
if (!out.write(chunk))
|
|
327
|
+
await (0, node_events_1.once)(out, 'drain');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
if (err?.code === 'ENOENT')
|
|
332
|
+
continue;
|
|
333
|
+
throw err;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/** `x.y.z` from a version string, or null. A prerelease suffix is ignored. */
|
|
338
|
+
function parseRuntimeVersion(text) {
|
|
339
|
+
const m = /(\d+)\.(\d+)\.(\d+)/.exec(text);
|
|
340
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
341
|
+
}
|
|
342
|
+
/** Whether `version` is at least `floor`; null when either is not a version. */
|
|
343
|
+
function runtimeVersionAtLeast(version, floor) {
|
|
344
|
+
const a = parseRuntimeVersion(version);
|
|
345
|
+
const b = parseRuntimeVersion(floor);
|
|
346
|
+
if (!a || !b)
|
|
347
|
+
return null;
|
|
348
|
+
for (let i = 0; i < 3; i += 1) {
|
|
349
|
+
if (a[i] !== b[i])
|
|
350
|
+
return a[i] > b[i];
|
|
351
|
+
}
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
/** The installed runtime's `--version`, or null when it cannot say in time. */
|
|
355
|
+
async function probeRuntimeVersion(capture = exec_runtime_binary_1.execRuntimeBinaryCapture) {
|
|
356
|
+
try {
|
|
357
|
+
const out = await capture(['--version'], { timeoutMs: 5_000 });
|
|
358
|
+
if (out.code !== 0)
|
|
359
|
+
return null;
|
|
360
|
+
const parsed = parseRuntimeVersion(out.stdout);
|
|
361
|
+
return parsed ? parsed.join('.') : null;
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* The live daemon's log as the runtime's `status --json` reports it
|
|
369
|
+
* (`service.logs`). Status has named the live `--log-file` since before `logs`
|
|
370
|
+
* did, so on 0.8.117 status is right while logs is wrong. Null when status
|
|
371
|
+
* cannot answer, or answers with anything but an absolute path.
|
|
372
|
+
*/
|
|
373
|
+
async function probeStatusLogPath(flags, capture = exec_runtime_binary_1.execRuntimeBinaryCapture) {
|
|
374
|
+
try {
|
|
375
|
+
const out = await capture([...(flags.profile ? ['--profile', flags.profile] : []), 'status', '--json'], { timeoutMs: 15_000 });
|
|
376
|
+
// `status` exits non-zero when it reports problems; the JSON is still the answer.
|
|
377
|
+
const start = out.stdout.indexOf('{');
|
|
378
|
+
if (start < 0)
|
|
379
|
+
return null;
|
|
380
|
+
const parsed = JSON.parse(out.stdout.slice(start));
|
|
381
|
+
const logs = parsed?.service?.logs;
|
|
382
|
+
return typeof logs === 'string' && node_path_1.default.isAbsolute(logs) ? logs : null;
|
|
383
|
+
}
|
|
384
|
+
catch {
|
|
385
|
+
return null;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
function sameLogFile(a, b) {
|
|
389
|
+
return Boolean(a && b && node_path_1.default.resolve(a) === node_path_1.default.resolve(b));
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Decide which runtime, if any, `skrr daemon logs` may trust (OSK-15993).
|
|
393
|
+
*
|
|
394
|
+
* - No `logs` subcommand: the legacy fallback, as before.
|
|
395
|
+
* - A runtime at or above {@link LIVE_LOG_RESOLUTION_RUNTIME_VERSION}: forward.
|
|
396
|
+
* - An older runtime, or one whose version cannot be read: ask its `status`
|
|
397
|
+
* which file the live daemon writes and read that file here. When status
|
|
398
|
+
* cannot answer, an older runtime gets the legacy fallback (the newest of the
|
|
399
|
+
* profile log and the installed service's logs); an unknown one is forwarded.
|
|
400
|
+
*/
|
|
401
|
+
async function planDaemonLogs(flags, deps) {
|
|
402
|
+
const legacy = (reason) => {
|
|
403
|
+
const chosen = chooseLegacyFallbackLog({
|
|
404
|
+
profilePath: deps.profilePath,
|
|
405
|
+
serviceCandidates: (flags.stderr
|
|
406
|
+
? [deps.service?.stderrPath]
|
|
407
|
+
: [deps.service?.logFile, deps.service?.stdoutPath]).filter((p) => Boolean(p)),
|
|
408
|
+
mtimeOf: deps.mtimeOf,
|
|
409
|
+
reason,
|
|
410
|
+
});
|
|
411
|
+
return {
|
|
412
|
+
kind: 'read',
|
|
413
|
+
path: chosen.path,
|
|
414
|
+
note: chosen.note,
|
|
415
|
+
missingHint: 'The installed daemon predates `oversky logs`; start it or upgrade it, then retry.',
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
const supports = await deps.supportsLogs();
|
|
419
|
+
if (supports === false) {
|
|
420
|
+
return legacy('This daemon predates `logs`, so it cannot name its live log itself.');
|
|
421
|
+
}
|
|
422
|
+
// Help could not be read: nothing to go on, keep the old behaviour.
|
|
423
|
+
if (supports === null)
|
|
424
|
+
return { kind: 'forward', runtimeVersion: null };
|
|
425
|
+
const version = await deps.runtimeVersion();
|
|
426
|
+
const current = version
|
|
427
|
+
? runtimeVersionAtLeast(version, exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION)
|
|
428
|
+
: null;
|
|
429
|
+
if (current === true)
|
|
430
|
+
return { kind: 'forward', runtimeVersion: version };
|
|
431
|
+
const live = await deps.statusLogPath();
|
|
432
|
+
const predates = version !== null
|
|
433
|
+
? `The installed runtime (${version}) predates \`logs\` following the live daemon's ` +
|
|
434
|
+
`--log-file (${exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION}), so it would read ${deps.profilePath}.`
|
|
435
|
+
: `The installed runtime did not report its version, so its \`logs\` may read ` +
|
|
436
|
+
`${deps.profilePath} instead of the live log.`;
|
|
437
|
+
if (!live) {
|
|
438
|
+
if (current === null)
|
|
439
|
+
return { kind: 'forward', runtimeVersion: version };
|
|
440
|
+
return legacy(`${predates} Its \`status\` did not name the live log either.`);
|
|
441
|
+
}
|
|
442
|
+
// The live daemon writes the profile log itself: the runtime's answer is right.
|
|
443
|
+
if (sameLogFile(live, node_path_1.default.join(node_path_1.default.dirname(deps.profilePath), 'daemon.log'))) {
|
|
444
|
+
return { kind: 'forward', runtimeVersion: version };
|
|
445
|
+
}
|
|
446
|
+
const missingHint = 'The live daemon has not written it yet; `skrr daemon status` shows whether one is running.';
|
|
447
|
+
if (!flags.stderr) {
|
|
448
|
+
return {
|
|
449
|
+
kind: 'read',
|
|
450
|
+
path: live,
|
|
451
|
+
note: `Showing ${live}, the live daemon's log as \`skrrd status\` reports it. ${predates}`,
|
|
452
|
+
missingHint,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
// --stderr: trust the installed definition's redirect only when it is the
|
|
456
|
+
// one that launched this daemon (its --log-file is the live one) — the same
|
|
457
|
+
// rule the runtime's own resolution applies.
|
|
458
|
+
if (deps.service?.stderrPath && sameLogFile(deps.service.logFile, live)) {
|
|
459
|
+
return {
|
|
460
|
+
kind: 'read',
|
|
461
|
+
path: deps.service.stderrPath,
|
|
462
|
+
note: `Showing ${deps.service.stderrPath}, the live daemon's stderr as its service definition ` +
|
|
463
|
+
`redirects it; its full log, WARN and ERROR included, is ${live}. ${predates}`,
|
|
464
|
+
missingHint,
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
return {
|
|
468
|
+
kind: 'read',
|
|
469
|
+
path: live,
|
|
470
|
+
note: `Showing ${live}, the live daemon's log as \`skrrd status\` reports it; it carries every ` +
|
|
471
|
+
`WARN and ERROR line. ${predates}`,
|
|
472
|
+
missingHint,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
194
475
|
/** Read a bounded trailing window, so an unrotated legacy err log is safe. */
|
|
195
476
|
function readDaemonLogTail(filePath, maxLines) {
|
|
196
477
|
if (maxLines === 0)
|
|
@@ -231,8 +512,13 @@ function buildDaemonLogsArgs(flags) {
|
|
|
231
512
|
...(flags.lines !== undefined ? ['--lines', String(flags.lines)] : []),
|
|
232
513
|
...(flags.stderr ? ['--stderr'] : []),
|
|
233
514
|
...(flags.path ? ['--path'] : []),
|
|
515
|
+
// Only ever reached for a runtime at ROTATED_LOGS_RUNTIME_VERSION or later;
|
|
516
|
+
// `run` reads the files itself for an older one.
|
|
517
|
+
...(flags.rotated ? ['--rotated'] : []),
|
|
234
518
|
];
|
|
235
519
|
}
|
|
520
|
+
/** `--stderr` help, word for word the runtime's own (`daemon/src/index.ts`). */
|
|
521
|
+
exports.STDERR_FLAG_DESCRIPTION = "Read the daemon's raw stderr instead of its log: the file its installed service redirects stderr to, or the profile's daemon.err.log. A daemon started with --log-file by anything else has no known stderr file, so its live log (which carries every WARN and ERROR) is shown instead, with a note";
|
|
236
522
|
class DaemonLogs extends core_1.Command {
|
|
237
523
|
// `skrr daemons <sub>` — the plural is advertised as an alias for the
|
|
238
524
|
// whole topic, and without a per-command alias only the bare topic
|
|
@@ -240,9 +526,9 @@ class DaemonLogs extends core_1.Command {
|
|
|
240
526
|
static aliases = ['daemons:logs'];
|
|
241
527
|
static description = 'Tail or print recent skrr daemon logs';
|
|
242
528
|
/**
|
|
243
|
-
* Mirrors the
|
|
529
|
+
* Mirrors the options `oversky logs` actually implements
|
|
244
530
|
* (`daemon/src/index.ts` — `-n/--lines`, `-f/--follow`, `--stderr`,
|
|
245
|
-
* `--path`). Typed flags are deliberate — they give `skrr daemon logs --help`
|
|
531
|
+
* `--path`, and since 0.8.120 `--rotated`). Typed flags are deliberate — they give `skrr daemon logs --help`
|
|
246
532
|
* something real to print, which raw `strict = false` passthrough never did.
|
|
247
533
|
* The trade is that anything NOT declared here becomes unreachable, so this
|
|
248
534
|
* list has to stay in step with the daemon command.
|
|
@@ -254,17 +540,26 @@ class DaemonLogs extends core_1.Command {
|
|
|
254
540
|
}),
|
|
255
541
|
lines: core_1.Flags.integer({
|
|
256
542
|
char: 'n',
|
|
257
|
-
description: 'Number of recent log lines to print',
|
|
543
|
+
description: 'Number of recent log lines to print (default 200; every line with --rotated)',
|
|
258
544
|
// `oversky logs` validates `>= 0`; keep the two in step so `-n 0` is not
|
|
259
545
|
// rejected here only to be accepted there.
|
|
260
546
|
min: 0,
|
|
261
547
|
}),
|
|
548
|
+
// OSK-16213: this said "daemon.err.log — every WARN and ERROR". Since
|
|
549
|
+
// OSK-15993 it is the process's raw stderr as the service redirects it
|
|
550
|
+
// (`planDaemonLogs` here, `resolveDaemonLogTarget` in the runtime), and a
|
|
551
|
+
// structured --log-file is what carries WARN and ERROR. The runtime's own
|
|
552
|
+
// `--stderr` help says the same words; logs.spec.ts compares the two.
|
|
262
553
|
stderr: core_1.Flags.boolean({
|
|
263
|
-
description:
|
|
554
|
+
description: exports.STDERR_FLAG_DESCRIPTION,
|
|
264
555
|
}),
|
|
265
556
|
path: core_1.Flags.boolean({
|
|
266
557
|
description: 'Print the resolved log file path and exit',
|
|
267
558
|
}),
|
|
559
|
+
// OSK-16214: the rotated files (`daemon.app.1.log` …) were unreachable.
|
|
560
|
+
rotated: core_1.Flags.boolean({
|
|
561
|
+
description: 'Include the rotated files (daemon.app.1.log …) before the current one, oldest first; with --path, list them all',
|
|
562
|
+
}),
|
|
268
563
|
// Every other daemon verb reaches a non-default profile with --profile; this
|
|
269
564
|
// one could only be steered by OVERSKY_PROFILE (OSK-10001).
|
|
270
565
|
profile: core_1.Flags.string({
|
|
@@ -278,6 +573,7 @@ class DaemonLogs extends core_1.Command {
|
|
|
278
573
|
'<%= config.bin %> daemon logs --lines 500',
|
|
279
574
|
'<%= config.bin %> daemon logs --stderr',
|
|
280
575
|
'<%= config.bin %> daemon logs --path',
|
|
576
|
+
'<%= config.bin %> daemon logs --rotated | grep arun_',
|
|
281
577
|
'<%= config.bin %> daemon logs --profile dogfood-laptop --lines 50',
|
|
282
578
|
];
|
|
283
579
|
async run() {
|
|
@@ -301,33 +597,83 @@ class DaemonLogs extends core_1.Command {
|
|
|
301
597
|
// `oversky 0.2.x` has profiles and writes the same files, but has no
|
|
302
598
|
// `logs` subcommand. Probe top-level help first so forwarding does not
|
|
303
599
|
// print an unrelated "unknown option" error before the useful output.
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
600
|
+
//
|
|
601
|
+
// OSK-15993: a runtime that HAS `logs` is not necessarily one to trust
|
|
602
|
+
// with it — 0.8.112–0.8.118 read the profile log even when the live
|
|
603
|
+
// daemon writes elsewhere. `planDaemonLogs` forwards only to a runtime
|
|
604
|
+
// that resolves the live log itself.
|
|
605
|
+
let plan = await planDaemonLogs(flags, {
|
|
606
|
+
supportsLogs: () => (0, exec_runtime_binary_1.runtimeBinarySupportsSubcommand)('logs'),
|
|
607
|
+
runtimeVersion: () => probeRuntimeVersion(),
|
|
608
|
+
statusLogPath: () => probeStatusLogPath(flags),
|
|
609
|
+
profilePath: resolveLegacyDaemonLogPath(flags),
|
|
610
|
+
service: installedServiceLogDestinations(resolveDaemonLogsProfile(flags)),
|
|
611
|
+
mtimeOf: (filePath) => {
|
|
612
|
+
try {
|
|
613
|
+
return (0, node_fs_1.statSync)(filePath).mtimeMs;
|
|
614
|
+
}
|
|
615
|
+
catch {
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
// OSK-16214: `--rotated` is new in ROTATED_LOGS_RUNTIME_VERSION, and an
|
|
621
|
+
// older runtime rejects it as an unknown option. Its `--path` is still
|
|
622
|
+
// the live-aware answer, so read that file and its siblings here.
|
|
623
|
+
const runtimeKnowsRotated = plan.kind === 'forward' &&
|
|
624
|
+
plan.runtimeVersion !== null &&
|
|
625
|
+
runtimeVersionAtLeast(plan.runtimeVersion, exports.ROTATED_LOGS_RUNTIME_VERSION) === true;
|
|
626
|
+
if (plan.kind === 'forward' && flags.rotated && !runtimeKnowsRotated) {
|
|
627
|
+
const resolved = await resolveForwardedLogPath(flags);
|
|
628
|
+
if (!resolved) {
|
|
629
|
+
this.error(`The installed runtime${plan.runtimeVersion ? ` (${plan.runtimeVersion})` : ''} predates ` +
|
|
630
|
+
`--rotated (${exports.ROTATED_LOGS_RUNTIME_VERSION}) and did not name its log file, so its ` +
|
|
631
|
+
'rotated files cannot be found. Upgrade it (`skrr daemon update`), or list the ' +
|
|
632
|
+
'directory `skrr daemon logs --path` prints.', { exit: 1 });
|
|
633
|
+
}
|
|
634
|
+
plan = {
|
|
635
|
+
kind: 'read',
|
|
636
|
+
path: resolved,
|
|
637
|
+
note: null,
|
|
638
|
+
missingHint: 'The live daemon has not written it yet; `skrr daemon status` shows whether one is running.',
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
if (plan.kind === 'read') {
|
|
642
|
+
const logPath = plan.path;
|
|
643
|
+
const siblings = rotatedLogSiblings(logPath);
|
|
644
|
+
// `--path` prints the one path and nothing else, on either stream —
|
|
645
|
+
// under --rotated, every file oldest first, the current one last.
|
|
320
646
|
if (flags.path) {
|
|
647
|
+
for (const file of flags.rotated ? siblings : [])
|
|
648
|
+
this.log(file);
|
|
321
649
|
this.log(logPath);
|
|
322
650
|
return;
|
|
323
651
|
}
|
|
652
|
+
if (plan.note)
|
|
653
|
+
this.logToStderr(plan.note);
|
|
654
|
+
if (flags.rotated && siblings.length === 0) {
|
|
655
|
+
this.logToStderr(`No rotated files beside ${logPath}.`);
|
|
656
|
+
}
|
|
657
|
+
else if (!flags.rotated) {
|
|
658
|
+
const hint = describeRotatedSiblings(logPath, siblings);
|
|
659
|
+
if (hint)
|
|
660
|
+
this.logToStderr(hint);
|
|
661
|
+
}
|
|
324
662
|
showAge(logPath);
|
|
325
663
|
if (!(0, node_fs_1.existsSync)(logPath)) {
|
|
326
|
-
this.error(`No daemon log at ${logPath}.
|
|
664
|
+
this.error(`No daemon log at ${logPath}. ${plan.missingHint}`, { exit: 1 });
|
|
665
|
+
}
|
|
666
|
+
if (flags.rotated && flags.lines === undefined) {
|
|
667
|
+
// Every line of every file, streamed rather than held whole.
|
|
668
|
+
await streamLogFiles([...siblings, logPath], process.stdout);
|
|
669
|
+
}
|
|
670
|
+
else {
|
|
671
|
+
const lines = flags.rotated
|
|
672
|
+
? readLastLinesAcross([...siblings, logPath], flags.lines ?? 200)
|
|
673
|
+
: readDaemonLogTail(logPath, flags.lines ?? 200);
|
|
674
|
+
if (lines.length > 0)
|
|
675
|
+
this.log(lines.join('\n'));
|
|
327
676
|
}
|
|
328
|
-
const lines = readDaemonLogTail(logPath, flags.lines ?? 200);
|
|
329
|
-
if (lines.length > 0)
|
|
330
|
-
this.log(lines.join('\n'));
|
|
331
677
|
if (flags.follow) {
|
|
332
678
|
let position = (0, node_fs_1.statSync)(logPath).size;
|
|
333
679
|
(0, node_fs_1.watchFile)(logPath, { interval: 500 }, () => {
|
|
@@ -359,8 +705,16 @@ class DaemonLogs extends core_1.Command {
|
|
|
359
705
|
}
|
|
360
706
|
// The banner must describe the file the runtime will actually print —
|
|
361
707
|
// the live daemon's log (OSK-15993) — not the profile default.
|
|
362
|
-
if (!flags.path)
|
|
363
|
-
|
|
708
|
+
if (!flags.path) {
|
|
709
|
+
const forwardedPath = await resolveForwardedLogPath(flags);
|
|
710
|
+
// A runtime that knows --rotated says this itself; an older one cannot.
|
|
711
|
+
if (!runtimeKnowsRotated && forwardedPath) {
|
|
712
|
+
const hint = describeRotatedSiblings(forwardedPath, rotatedLogSiblings(forwardedPath));
|
|
713
|
+
if (hint)
|
|
714
|
+
this.logToStderr(hint);
|
|
715
|
+
}
|
|
716
|
+
showAge(forwardedPath);
|
|
717
|
+
}
|
|
364
718
|
const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(args);
|
|
365
719
|
this.exit(code);
|
|
366
720
|
}
|
|
@@ -23,7 +23,10 @@ class FollowupsAct extends base_command_1.BaseCommand {
|
|
|
23
23
|
required: true,
|
|
24
24
|
}),
|
|
25
25
|
at: core_1.Flags.string({ description: 'ISO-8601 instant to act at', exclusive: ['in'] }),
|
|
26
|
-
in: core_1.Flags.string({
|
|
26
|
+
in: core_1.Flags.string({
|
|
27
|
+
description: 'Duration from now (2h, 3d), between 1m and 30d',
|
|
28
|
+
exclusive: ['at'],
|
|
29
|
+
}),
|
|
27
30
|
agent: core_1.Flags.string({ description: 'Agent ID (default: bound session agent)' }),
|
|
28
31
|
endpoint: core_1.Flags.string({ description: 'Delivery endpoint id (EndpointRegistry)' }),
|
|
29
32
|
'endpoint-target': core_1.Flags.string({ description: 'Channel target for channel endpoints' }),
|
|
@@ -42,6 +45,15 @@ class FollowupsAct extends base_command_1.BaseCommand {
|
|
|
42
45
|
retryable: false,
|
|
43
46
|
});
|
|
44
47
|
}
|
|
48
|
+
const problem = (0, followups_1.whenFlagProblem)(flags.at ? 'at' : 'in', at);
|
|
49
|
+
if (problem) {
|
|
50
|
+
this.failWithCliError({
|
|
51
|
+
message: problem,
|
|
52
|
+
code: 'FOLLOWUP_INVALID_FIELD',
|
|
53
|
+
exit: 2,
|
|
54
|
+
retryable: false,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
45
57
|
const action = (0, triggers_1.parseJsonObject)(flags.action, '--action');
|
|
46
58
|
if (typeof action?.builtinType !== 'string' || !action.builtinType) {
|
|
47
59
|
this.failWithCliError({
|
|
@@ -23,7 +23,7 @@ class FollowupsCheckin extends base_command_1.BaseCommand {
|
|
|
23
23
|
subject: core_1.Flags.string({ description: followups_1.FOLLOWUP_SUBJECT_FLAG_HELP, required: true }),
|
|
24
24
|
why: core_1.Flags.string({ description: 'Why this wake exists, in one line', required: true }),
|
|
25
25
|
in: core_1.Flags.string({
|
|
26
|
-
description: 'Duration from now (90m, 2h) — the once form',
|
|
26
|
+
description: 'Duration from now (90m, 2h), between 1m and 30d — the once form',
|
|
27
27
|
exclusive: ['frequency'],
|
|
28
28
|
}),
|
|
29
29
|
frequency: core_1.Flags.string({
|
|
@@ -64,6 +64,15 @@ class FollowupsCheckin extends base_command_1.BaseCommand {
|
|
|
64
64
|
retryable: false,
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
|
+
const problem = (0, followups_1.whenFlagProblem)('in', flags.in);
|
|
68
|
+
if (problem) {
|
|
69
|
+
this.failWithCliError({
|
|
70
|
+
message: problem,
|
|
71
|
+
code: 'FOLLOWUP_INVALID_FIELD',
|
|
72
|
+
exit: 2,
|
|
73
|
+
retryable: false,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
67
76
|
let agent;
|
|
68
77
|
try {
|
|
69
78
|
agent = (0, followups_1.resolveFollowUpAgent)(flags.agent);
|
|
@@ -106,7 +115,7 @@ class FollowupsCheckin extends base_command_1.BaseCommand {
|
|
|
106
115
|
if (response.dropped && producer === 'user_explicit') {
|
|
107
116
|
this.failWithCliError({
|
|
108
117
|
message: budgetRefusalMessage(agent.id, response.budget),
|
|
109
|
-
code: '
|
|
118
|
+
code: 'FOLLOWUP_BUDGET_EXCEEDED',
|
|
110
119
|
exit: 1,
|
|
111
120
|
retryable: false,
|
|
112
121
|
details: { budget: response.budget ?? null, response },
|
|
@@ -15,7 +15,10 @@ class FollowupsReschedule extends base_command_1.BaseCommand {
|
|
|
15
15
|
};
|
|
16
16
|
static flags = {
|
|
17
17
|
at: core_1.Flags.string({ description: 'New instant (ISO-8601)', exclusive: ['in'] }),
|
|
18
|
-
in: core_1.Flags.string({
|
|
18
|
+
in: core_1.Flags.string({
|
|
19
|
+
description: 'New offset from now (90m, 2h); between 1m and 30d',
|
|
20
|
+
exclusive: ['at'],
|
|
21
|
+
}),
|
|
19
22
|
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
20
23
|
};
|
|
21
24
|
async run() {
|
|
@@ -30,6 +33,15 @@ class FollowupsReschedule extends base_command_1.BaseCommand {
|
|
|
30
33
|
retryable: false,
|
|
31
34
|
});
|
|
32
35
|
}
|
|
36
|
+
const problem = (0, followups_1.whenFlagProblem)(flags.at ? 'at' : 'in', at);
|
|
37
|
+
if (problem) {
|
|
38
|
+
this.failWithCliError({
|
|
39
|
+
message: problem,
|
|
40
|
+
code: 'FOLLOWUP_INVALID_FIELD',
|
|
41
|
+
exit: 2,
|
|
42
|
+
retryable: false,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
33
45
|
let result;
|
|
34
46
|
try {
|
|
35
47
|
result = (await followups_1.followupsApi.reschedule(args.id, { at }));
|
|
@@ -3,7 +3,7 @@ export default class ProposalsDiscard extends ProposalDecisionCommand {
|
|
|
3
3
|
static description: string;
|
|
4
4
|
static args: {
|
|
5
5
|
kind: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
-
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
id: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
7
|
};
|
|
8
8
|
static decision: "discard";
|
|
9
9
|
}
|