@skrr-ai/cli 0.1.104 → 0.1.106
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/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 +73 -3
- package/dist/commands/daemon/logs.js +304 -17
- 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/retry-schedule.js +11 -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 +28 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.js +50 -5
- 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 +28 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.js +50 -5
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +7261 -7056
- package/docs/v1/operations.md +1 -1
- package/oclif.manifest.json +116 -72
- package/package.json +1 -1
|
@@ -19,6 +19,7 @@ export interface DaemonLogsFlags {
|
|
|
19
19
|
stderr?: boolean;
|
|
20
20
|
path?: boolean;
|
|
21
21
|
profile?: string;
|
|
22
|
+
rotated?: boolean;
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
25
|
* Older daemons already kept logs in this profile-scoped location, even
|
|
@@ -116,6 +117,69 @@ export declare function resolveForwardedLogPath(flags: Pick<DaemonLogsFlags, 'st
|
|
|
116
117
|
* forwarding to the bug, so the CLI forwards only at or above this version.
|
|
117
118
|
*/
|
|
118
119
|
export declare const LIVE_LOG_RESOLUTION_RUNTIME_VERSION = "0.8.119";
|
|
120
|
+
/**
|
|
121
|
+
* The first runtime whose `logs` knows `--rotated` (OSK-16214). An older one
|
|
122
|
+
* rejects it as an unknown option, so the CLI never forwards the flag below
|
|
123
|
+
* this version: it asks the runtime which file it reads (`--path`) and reads
|
|
124
|
+
* that file and its rotated siblings itself.
|
|
125
|
+
*/
|
|
126
|
+
export declare const ROTATED_LOGS_RUNTIME_VERSION = "0.8.120";
|
|
127
|
+
/**
|
|
128
|
+
* The first runtime whose `logs --rotated --lines N` says where a window that
|
|
129
|
+
* cut older lines starts, and whose `logs --path` names the rotated siblings
|
|
130
|
+
* on stderr (OSK-16228). 0.8.120 knows `--rotated` but says neither, so for it
|
|
131
|
+
* the CLI reads the files itself, exactly as it does for an older runtime.
|
|
132
|
+
*/
|
|
133
|
+
export declare const ROTATED_WINDOW_NOTE_RUNTIME_VERSION = "0.8.121";
|
|
134
|
+
/**
|
|
135
|
+
* Rotated siblings of a log, OLDEST FIRST (OSK-16214). The daemon's logger
|
|
136
|
+
* renames `daemon.app.log` to `daemon.app.1.log`, `.1` to `.2` and so on at
|
|
137
|
+
* 10 MB (`daemon/src/logger.ts` `maybeRotate`), and `logs` never reached them —
|
|
138
|
+
* `--lines 5000 | grep <run id>` found nothing while `daemon.app.1.log` held the
|
|
139
|
+
* run. A copy of `daemon/src/rotated-log-files.ts`, for runtimes that predate
|
|
140
|
+
* `--rotated`. Never throws.
|
|
141
|
+
*/
|
|
142
|
+
export declare function rotatedLogSiblings(filePath: string, readdir?: (dir: string) => string[]): string[];
|
|
143
|
+
/** The sentence shown when rotated siblings hold older lines the view leaves out. */
|
|
144
|
+
export declare function describeRotatedSiblings(filePath: string, siblings: readonly string[]): string | null;
|
|
145
|
+
/**
|
|
146
|
+
* The last `maxLines` lines of one file, read backwards in chunks so the cost
|
|
147
|
+
* follows the lines asked for, not the file's size. Bytes are decoded once, so
|
|
148
|
+
* a multi-byte character split across a chunk boundary survives.
|
|
149
|
+
*/
|
|
150
|
+
export declare function readLastLines(filePath: string, maxLines: number): string[];
|
|
151
|
+
/**
|
|
152
|
+
* The last `maxLines` lines across `files` (OLDEST FIRST) as if they were one
|
|
153
|
+
* file. A file renamed away between the listing and the read is skipped.
|
|
154
|
+
*/
|
|
155
|
+
export declare function readLastLinesAcross(files: readonly string[], maxLines: number): string[];
|
|
156
|
+
/**
|
|
157
|
+
* Where a `--rotated --lines N` window began when it left older lines out
|
|
158
|
+
* (OSK-16228). `startLine` is 1-based within `file`; `totalLines` counts the
|
|
159
|
+
* whole set. A copy of `daemon/src/rotated-log-files.ts`, like the helpers above.
|
|
160
|
+
*/
|
|
161
|
+
export interface LogWindowCut {
|
|
162
|
+
file: string;
|
|
163
|
+
startLine: number;
|
|
164
|
+
totalLines: number;
|
|
165
|
+
}
|
|
166
|
+
/** Lines in a file, split as `readLastLines` splits them; read in chunks. */
|
|
167
|
+
export declare function countLines(filePath: string): number;
|
|
168
|
+
/**
|
|
169
|
+
* `readLastLinesAcross`, plus where the window began when it cut older lines.
|
|
170
|
+
* Without it, `--rotated --lines 20000 | grep <id>` for an id 90k lines back
|
|
171
|
+
* printed nothing and said nothing, which reads as "never logged". Lines are
|
|
172
|
+
* counted only once the window is full, and only from the file it stopped in
|
|
173
|
+
* back.
|
|
174
|
+
*/
|
|
175
|
+
export declare function readLogWindow(files: readonly string[], maxLines: number): {
|
|
176
|
+
lines: string[];
|
|
177
|
+
cut: LogWindowCut | null;
|
|
178
|
+
};
|
|
179
|
+
/** The stderr sentence for a window that left older lines out. */
|
|
180
|
+
export declare function describeLogWindowCut(cut: LogWindowCut, shown: number): string;
|
|
181
|
+
/** Every byte of `files`, in order, to `out` — streamed, honouring back-pressure. */
|
|
182
|
+
export declare function streamLogFiles(files: readonly string[], out: NodeJS.WritableStream): Promise<void>;
|
|
119
183
|
/** `x.y.z` from a version string, or null. A prerelease suffix is ignored. */
|
|
120
184
|
export declare function parseRuntimeVersion(text: string): [number, number, number] | null;
|
|
121
185
|
/** Whether `version` is at least `floor`; null when either is not a version. */
|
|
@@ -130,8 +194,11 @@ export declare function probeRuntimeVersion(capture?: typeof execRuntimeBinaryCa
|
|
|
130
194
|
*/
|
|
131
195
|
export declare function probeStatusLogPath(flags: Pick<DaemonLogsFlags, 'profile'>, capture?: typeof execRuntimeBinaryCapture): Promise<string | null>;
|
|
132
196
|
/** What `skrr daemon logs` does: forward to the runtime, or read a file itself. */
|
|
133
|
-
export type DaemonLogsPlan =
|
|
197
|
+
export type DaemonLogsPlan =
|
|
198
|
+
/** `runtimeVersion` decides whether a flag newer than 0.8.119 may be forwarded. */
|
|
199
|
+
{
|
|
134
200
|
kind: 'forward';
|
|
201
|
+
runtimeVersion: string | null;
|
|
135
202
|
}
|
|
136
203
|
/** `note` goes to stderr before any log content; never printed for `--path`. */
|
|
137
204
|
| {
|
|
@@ -171,13 +238,15 @@ export declare function readDaemonLogTail(filePath: string, maxLines: number): s
|
|
|
171
238
|
* or it parses successfully and is then silently dropped.
|
|
172
239
|
*/
|
|
173
240
|
export declare function buildDaemonLogsArgs(flags: DaemonLogsFlags): string[];
|
|
241
|
+
/** `--stderr` help, word for word the runtime's own (`daemon/src/index.ts`). */
|
|
242
|
+
export declare const 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";
|
|
174
243
|
export default class DaemonLogs extends Command {
|
|
175
244
|
static aliases: string[];
|
|
176
245
|
static description: string;
|
|
177
246
|
/**
|
|
178
|
-
* Mirrors the
|
|
247
|
+
* Mirrors the options `oversky logs` actually implements
|
|
179
248
|
* (`daemon/src/index.ts` — `-n/--lines`, `-f/--follow`, `--stderr`,
|
|
180
|
-
* `--path`). Typed flags are deliberate — they give `skrr daemon logs --help`
|
|
249
|
+
* `--path`, and since 0.8.120 `--rotated`). Typed flags are deliberate — they give `skrr daemon logs --help`
|
|
181
250
|
* something real to print, which raw `strict = false` passthrough never did.
|
|
182
251
|
* The trade is that anything NOT declared here becomes unreachable, so this
|
|
183
252
|
* list has to stay in step with the daemon command.
|
|
@@ -187,6 +256,7 @@ export default class DaemonLogs extends Command {
|
|
|
187
256
|
lines: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
188
257
|
stderr: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
189
258
|
path: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
259
|
+
rotated: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
190
260
|
profile: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
191
261
|
};
|
|
192
262
|
static examples: string[];
|
|
@@ -3,7 +3,7 @@ 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.LIVE_LOG_RESOLUTION_RUNTIME_VERSION = void 0;
|
|
6
|
+
exports.STDERR_FLAG_DESCRIPTION = exports.ROTATED_WINDOW_NOTE_RUNTIME_VERSION = exports.ROTATED_LOGS_RUNTIME_VERSION = exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION = void 0;
|
|
7
7
|
exports.describeLogAge = describeLogAge;
|
|
8
8
|
exports.resolveLegacyDaemonLogPath = resolveLegacyDaemonLogPath;
|
|
9
9
|
exports.resolveDaemonLogsProfile = resolveDaemonLogsProfile;
|
|
@@ -11,6 +11,14 @@ exports.installedServiceLogDestinations = installedServiceLogDestinations;
|
|
|
11
11
|
exports.installedServiceLogCandidates = installedServiceLogCandidates;
|
|
12
12
|
exports.chooseLegacyFallbackLog = chooseLegacyFallbackLog;
|
|
13
13
|
exports.resolveForwardedLogPath = resolveForwardedLogPath;
|
|
14
|
+
exports.rotatedLogSiblings = rotatedLogSiblings;
|
|
15
|
+
exports.describeRotatedSiblings = describeRotatedSiblings;
|
|
16
|
+
exports.readLastLines = readLastLines;
|
|
17
|
+
exports.readLastLinesAcross = readLastLinesAcross;
|
|
18
|
+
exports.countLines = countLines;
|
|
19
|
+
exports.readLogWindow = readLogWindow;
|
|
20
|
+
exports.describeLogWindowCut = describeLogWindowCut;
|
|
21
|
+
exports.streamLogFiles = streamLogFiles;
|
|
14
22
|
exports.parseRuntimeVersion = parseRuntimeVersion;
|
|
15
23
|
exports.runtimeVersionAtLeast = runtimeVersionAtLeast;
|
|
16
24
|
exports.probeRuntimeVersion = probeRuntimeVersion;
|
|
@@ -22,6 +30,7 @@ const core_1 = require("@oclif/core");
|
|
|
22
30
|
const node_fs_1 = require("node:fs");
|
|
23
31
|
const node_os_1 = require("node:os");
|
|
24
32
|
const node_path_1 = __importDefault(require("node:path"));
|
|
33
|
+
const node_events_1 = require("node:events");
|
|
25
34
|
const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
|
|
26
35
|
const MAX_FALLBACK_LOG_BYTES = 1024 * 1024;
|
|
27
36
|
const PROFILE_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
@@ -188,7 +197,9 @@ function chooseLegacyFallbackLog(args) {
|
|
|
188
197
|
*/
|
|
189
198
|
async function resolveForwardedLogPath(flags, capture = exec_runtime_binary_1.execRuntimeBinaryCapture) {
|
|
190
199
|
try {
|
|
191
|
-
|
|
200
|
+
// Only what decides the file: --rotated would list siblings first, and the
|
|
201
|
+
// last line is still the current file, but there is no reason to ask.
|
|
202
|
+
const out = await capture(buildDaemonLogsArgs({ stderr: flags.stderr, profile: flags.profile, path: true }), {
|
|
192
203
|
timeoutMs: 5_000,
|
|
193
204
|
});
|
|
194
205
|
if (out.code !== 0)
|
|
@@ -213,6 +224,197 @@ async function resolveForwardedLogPath(flags, capture = exec_runtime_binary_1.ex
|
|
|
213
224
|
* forwarding to the bug, so the CLI forwards only at or above this version.
|
|
214
225
|
*/
|
|
215
226
|
exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION = '0.8.119';
|
|
227
|
+
/**
|
|
228
|
+
* The first runtime whose `logs` knows `--rotated` (OSK-16214). An older one
|
|
229
|
+
* rejects it as an unknown option, so the CLI never forwards the flag below
|
|
230
|
+
* this version: it asks the runtime which file it reads (`--path`) and reads
|
|
231
|
+
* that file and its rotated siblings itself.
|
|
232
|
+
*/
|
|
233
|
+
exports.ROTATED_LOGS_RUNTIME_VERSION = '0.8.120';
|
|
234
|
+
/**
|
|
235
|
+
* The first runtime whose `logs --rotated --lines N` says where a window that
|
|
236
|
+
* cut older lines starts, and whose `logs --path` names the rotated siblings
|
|
237
|
+
* on stderr (OSK-16228). 0.8.120 knows `--rotated` but says neither, so for it
|
|
238
|
+
* the CLI reads the files itself, exactly as it does for an older runtime.
|
|
239
|
+
*/
|
|
240
|
+
exports.ROTATED_WINDOW_NOTE_RUNTIME_VERSION = '0.8.121';
|
|
241
|
+
/**
|
|
242
|
+
* Rotated siblings of a log, OLDEST FIRST (OSK-16214). The daemon's logger
|
|
243
|
+
* renames `daemon.app.log` to `daemon.app.1.log`, `.1` to `.2` and so on at
|
|
244
|
+
* 10 MB (`daemon/src/logger.ts` `maybeRotate`), and `logs` never reached them —
|
|
245
|
+
* `--lines 5000 | grep <run id>` found nothing while `daemon.app.1.log` held the
|
|
246
|
+
* run. A copy of `daemon/src/rotated-log-files.ts`, for runtimes that predate
|
|
247
|
+
* `--rotated`. Never throws.
|
|
248
|
+
*/
|
|
249
|
+
function rotatedLogSiblings(filePath, readdir = (dir) => (0, node_fs_1.readdirSync)(dir)) {
|
|
250
|
+
const dir = node_path_1.default.dirname(filePath);
|
|
251
|
+
const ext = node_path_1.default.extname(filePath);
|
|
252
|
+
const base = node_path_1.default.basename(filePath, ext);
|
|
253
|
+
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
254
|
+
const pattern = new RegExp(`^${escape(base)}\\.(\\d+)${escape(ext)}$`);
|
|
255
|
+
let names;
|
|
256
|
+
try {
|
|
257
|
+
names = readdir(dir);
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return [];
|
|
261
|
+
}
|
|
262
|
+
return names
|
|
263
|
+
.map((name) => ({ name, index: Number(pattern.exec(name)?.[1] ?? NaN) }))
|
|
264
|
+
.filter(({ index }) => Number.isSafeInteger(index) && index >= 1)
|
|
265
|
+
.sort((a, b) => b.index - a.index)
|
|
266
|
+
.map(({ name }) => node_path_1.default.join(dir, name));
|
|
267
|
+
}
|
|
268
|
+
/** The sentence shown when rotated siblings hold older lines the view leaves out. */
|
|
269
|
+
function describeRotatedSiblings(filePath, siblings) {
|
|
270
|
+
if (siblings.length === 0)
|
|
271
|
+
return null;
|
|
272
|
+
const names = siblings.map((s) => node_path_1.default.basename(s));
|
|
273
|
+
const listed = names.length === 1 ? names[0] : `${names.at(-1)} … ${names[0]}`;
|
|
274
|
+
return (`Older lines are in ${siblings.length} rotated file${siblings.length === 1 ? '' : 's'} ` +
|
|
275
|
+
`beside ${filePath} (${listed}). Add --rotated to read them too, oldest first.`);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* The last `maxLines` lines of one file, read backwards in chunks so the cost
|
|
279
|
+
* follows the lines asked for, not the file's size. Bytes are decoded once, so
|
|
280
|
+
* a multi-byte character split across a chunk boundary survives.
|
|
281
|
+
*/
|
|
282
|
+
function readLastLines(filePath, maxLines) {
|
|
283
|
+
if (maxLines <= 0)
|
|
284
|
+
return [];
|
|
285
|
+
const { size } = (0, node_fs_1.statSync)(filePath);
|
|
286
|
+
const descriptor = (0, node_fs_1.openSync)(filePath, 'r');
|
|
287
|
+
try {
|
|
288
|
+
let start = size;
|
|
289
|
+
const chunks = [];
|
|
290
|
+
let newlines = 0;
|
|
291
|
+
while (start > 0 && newlines <= maxLines) {
|
|
292
|
+
const length = Math.min(64 * 1024, start);
|
|
293
|
+
start -= length;
|
|
294
|
+
const buffer = Buffer.alloc(length);
|
|
295
|
+
(0, node_fs_1.readSync)(descriptor, buffer, 0, length, start);
|
|
296
|
+
chunks.unshift(buffer);
|
|
297
|
+
for (const byte of buffer)
|
|
298
|
+
if (byte === 0x0a)
|
|
299
|
+
newlines += 1;
|
|
300
|
+
}
|
|
301
|
+
const lines = Buffer.concat(chunks).toString('utf8').split('\n');
|
|
302
|
+
if (lines.at(-1) === '')
|
|
303
|
+
lines.pop();
|
|
304
|
+
if (start > 0)
|
|
305
|
+
lines.shift();
|
|
306
|
+
return lines.slice(-maxLines);
|
|
307
|
+
}
|
|
308
|
+
finally {
|
|
309
|
+
(0, node_fs_1.closeSync)(descriptor);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* The last `maxLines` lines across `files` (OLDEST FIRST) as if they were one
|
|
314
|
+
* file. A file renamed away between the listing and the read is skipped.
|
|
315
|
+
*/
|
|
316
|
+
function readLastLinesAcross(files, maxLines) {
|
|
317
|
+
return readLogWindow(files, maxLines).lines;
|
|
318
|
+
}
|
|
319
|
+
/** Lines in a file, split as `readLastLines` splits them; read in chunks. */
|
|
320
|
+
function countLines(filePath) {
|
|
321
|
+
const descriptor = (0, node_fs_1.openSync)(filePath, 'r');
|
|
322
|
+
try {
|
|
323
|
+
const buffer = Buffer.alloc(64 * 1024);
|
|
324
|
+
let count = 0;
|
|
325
|
+
let last = -1;
|
|
326
|
+
let position = 0;
|
|
327
|
+
for (;;) {
|
|
328
|
+
const read = (0, node_fs_1.readSync)(descriptor, buffer, 0, buffer.length, position);
|
|
329
|
+
if (read === 0)
|
|
330
|
+
break;
|
|
331
|
+
position += read;
|
|
332
|
+
for (let i = 0; i < read; i += 1)
|
|
333
|
+
if (buffer[i] === 0x0a)
|
|
334
|
+
count += 1;
|
|
335
|
+
last = buffer[read - 1];
|
|
336
|
+
}
|
|
337
|
+
return last === -1 || last === 0x0a ? count : count + 1;
|
|
338
|
+
}
|
|
339
|
+
finally {
|
|
340
|
+
(0, node_fs_1.closeSync)(descriptor);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* `readLastLinesAcross`, plus where the window began when it cut older lines.
|
|
345
|
+
* Without it, `--rotated --lines 20000 | grep <id>` for an id 90k lines back
|
|
346
|
+
* printed nothing and said nothing, which reads as "never logged". Lines are
|
|
347
|
+
* counted only once the window is full, and only from the file it stopped in
|
|
348
|
+
* back.
|
|
349
|
+
*/
|
|
350
|
+
function readLogWindow(files, maxLines) {
|
|
351
|
+
const collected = [];
|
|
352
|
+
let remaining = maxLines;
|
|
353
|
+
let boundary = null;
|
|
354
|
+
for (let i = files.length - 1; i >= 0 && remaining > 0; i -= 1) {
|
|
355
|
+
try {
|
|
356
|
+
const lines = readLastLines(files[i], remaining);
|
|
357
|
+
collected.unshift(lines);
|
|
358
|
+
remaining -= lines.length;
|
|
359
|
+
if (remaining <= 0)
|
|
360
|
+
boundary = { index: i, taken: lines.length };
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// Rotated away mid-read; the rest are still worth showing.
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const lines = collected.flat();
|
|
367
|
+
if (boundary === null)
|
|
368
|
+
return { lines, cut: null };
|
|
369
|
+
let boundaryLines;
|
|
370
|
+
try {
|
|
371
|
+
boundaryLines = countLines(files[boundary.index]);
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
return { lines, cut: null };
|
|
375
|
+
}
|
|
376
|
+
let olderLines = 0;
|
|
377
|
+
for (const file of files.slice(0, boundary.index)) {
|
|
378
|
+
try {
|
|
379
|
+
olderLines += countLines(file);
|
|
380
|
+
}
|
|
381
|
+
catch {
|
|
382
|
+
// Rotated away since the read; it is not in the window either way.
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const startLine = Math.max(1, boundaryLines - boundary.taken + 1);
|
|
386
|
+
if (startLine === 1 && olderLines === 0)
|
|
387
|
+
return { lines, cut: null };
|
|
388
|
+
return {
|
|
389
|
+
lines,
|
|
390
|
+
cut: {
|
|
391
|
+
file: files[boundary.index],
|
|
392
|
+
startLine,
|
|
393
|
+
totalLines: olderLines + (startLine - 1) + lines.length,
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
/** The stderr sentence for a window that left older lines out. */
|
|
398
|
+
function describeLogWindowCut(cut, shown) {
|
|
399
|
+
return (`Showing the last ${shown} of ${cut.totalLines} lines; the window starts at line ` +
|
|
400
|
+
`${cut.startLine} of ${node_path_1.default.basename(cut.file)}. Drop --lines to read the full history.`);
|
|
401
|
+
}
|
|
402
|
+
/** Every byte of `files`, in order, to `out` — streamed, honouring back-pressure. */
|
|
403
|
+
async function streamLogFiles(files, out) {
|
|
404
|
+
for (const file of files) {
|
|
405
|
+
try {
|
|
406
|
+
for await (const chunk of (0, node_fs_1.createReadStream)(file)) {
|
|
407
|
+
if (!out.write(chunk))
|
|
408
|
+
await (0, node_events_1.once)(out, 'drain');
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
if (err?.code === 'ENOENT')
|
|
413
|
+
continue;
|
|
414
|
+
throw err;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
216
418
|
/** `x.y.z` from a version string, or null. A prerelease suffix is ignored. */
|
|
217
419
|
function parseRuntimeVersion(text) {
|
|
218
420
|
const m = /(\d+)\.(\d+)\.(\d+)/.exec(text);
|
|
@@ -300,13 +502,13 @@ async function planDaemonLogs(flags, deps) {
|
|
|
300
502
|
}
|
|
301
503
|
// Help could not be read: nothing to go on, keep the old behaviour.
|
|
302
504
|
if (supports === null)
|
|
303
|
-
return { kind: 'forward' };
|
|
505
|
+
return { kind: 'forward', runtimeVersion: null };
|
|
304
506
|
const version = await deps.runtimeVersion();
|
|
305
507
|
const current = version
|
|
306
508
|
? runtimeVersionAtLeast(version, exports.LIVE_LOG_RESOLUTION_RUNTIME_VERSION)
|
|
307
509
|
: null;
|
|
308
510
|
if (current === true)
|
|
309
|
-
return { kind: 'forward' };
|
|
511
|
+
return { kind: 'forward', runtimeVersion: version };
|
|
310
512
|
const live = await deps.statusLogPath();
|
|
311
513
|
const predates = version !== null
|
|
312
514
|
? `The installed runtime (${version}) predates \`logs\` following the live daemon's ` +
|
|
@@ -315,12 +517,12 @@ async function planDaemonLogs(flags, deps) {
|
|
|
315
517
|
`${deps.profilePath} instead of the live log.`;
|
|
316
518
|
if (!live) {
|
|
317
519
|
if (current === null)
|
|
318
|
-
return { kind: 'forward' };
|
|
520
|
+
return { kind: 'forward', runtimeVersion: version };
|
|
319
521
|
return legacy(`${predates} Its \`status\` did not name the live log either.`);
|
|
320
522
|
}
|
|
321
523
|
// The live daemon writes the profile log itself: the runtime's answer is right.
|
|
322
524
|
if (sameLogFile(live, node_path_1.default.join(node_path_1.default.dirname(deps.profilePath), 'daemon.log'))) {
|
|
323
|
-
return { kind: 'forward' };
|
|
525
|
+
return { kind: 'forward', runtimeVersion: version };
|
|
324
526
|
}
|
|
325
527
|
const missingHint = 'The live daemon has not written it yet; `skrr daemon status` shows whether one is running.';
|
|
326
528
|
if (!flags.stderr) {
|
|
@@ -391,8 +593,13 @@ function buildDaemonLogsArgs(flags) {
|
|
|
391
593
|
...(flags.lines !== undefined ? ['--lines', String(flags.lines)] : []),
|
|
392
594
|
...(flags.stderr ? ['--stderr'] : []),
|
|
393
595
|
...(flags.path ? ['--path'] : []),
|
|
596
|
+
// Only ever reached for a runtime at ROTATED_LOGS_RUNTIME_VERSION or later;
|
|
597
|
+
// `run` reads the files itself for an older one.
|
|
598
|
+
...(flags.rotated ? ['--rotated'] : []),
|
|
394
599
|
];
|
|
395
600
|
}
|
|
601
|
+
/** `--stderr` help, word for word the runtime's own (`daemon/src/index.ts`). */
|
|
602
|
+
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";
|
|
396
603
|
class DaemonLogs extends core_1.Command {
|
|
397
604
|
// `skrr daemons <sub>` — the plural is advertised as an alias for the
|
|
398
605
|
// whole topic, and without a per-command alias only the bare topic
|
|
@@ -400,9 +607,9 @@ class DaemonLogs extends core_1.Command {
|
|
|
400
607
|
static aliases = ['daemons:logs'];
|
|
401
608
|
static description = 'Tail or print recent skrr daemon logs';
|
|
402
609
|
/**
|
|
403
|
-
* Mirrors the
|
|
610
|
+
* Mirrors the options `oversky logs` actually implements
|
|
404
611
|
* (`daemon/src/index.ts` — `-n/--lines`, `-f/--follow`, `--stderr`,
|
|
405
|
-
* `--path`). Typed flags are deliberate — they give `skrr daemon logs --help`
|
|
612
|
+
* `--path`, and since 0.8.120 `--rotated`). Typed flags are deliberate — they give `skrr daemon logs --help`
|
|
406
613
|
* something real to print, which raw `strict = false` passthrough never did.
|
|
407
614
|
* The trade is that anything NOT declared here becomes unreachable, so this
|
|
408
615
|
* list has to stay in step with the daemon command.
|
|
@@ -414,17 +621,26 @@ class DaemonLogs extends core_1.Command {
|
|
|
414
621
|
}),
|
|
415
622
|
lines: core_1.Flags.integer({
|
|
416
623
|
char: 'n',
|
|
417
|
-
description: 'Number of recent log lines to print',
|
|
624
|
+
description: 'Number of recent log lines to print (default 200; every line with --rotated)',
|
|
418
625
|
// `oversky logs` validates `>= 0`; keep the two in step so `-n 0` is not
|
|
419
626
|
// rejected here only to be accepted there.
|
|
420
627
|
min: 0,
|
|
421
628
|
}),
|
|
629
|
+
// OSK-16213: this said "daemon.err.log — every WARN and ERROR". Since
|
|
630
|
+
// OSK-15993 it is the process's raw stderr as the service redirects it
|
|
631
|
+
// (`planDaemonLogs` here, `resolveDaemonLogTarget` in the runtime), and a
|
|
632
|
+
// structured --log-file is what carries WARN and ERROR. The runtime's own
|
|
633
|
+
// `--stderr` help says the same words; logs.spec.ts compares the two.
|
|
422
634
|
stderr: core_1.Flags.boolean({
|
|
423
|
-
description:
|
|
635
|
+
description: exports.STDERR_FLAG_DESCRIPTION,
|
|
424
636
|
}),
|
|
425
637
|
path: core_1.Flags.boolean({
|
|
426
638
|
description: 'Print the resolved log file path and exit',
|
|
427
639
|
}),
|
|
640
|
+
// OSK-16214: the rotated files (`daemon.app.1.log` …) were unreachable.
|
|
641
|
+
rotated: core_1.Flags.boolean({
|
|
642
|
+
description: 'Include the rotated files (daemon.app.1.log …) before the current one, oldest first; with --path, list them all',
|
|
643
|
+
}),
|
|
428
644
|
// Every other daemon verb reaches a non-default profile with --profile; this
|
|
429
645
|
// one could only be steered by OVERSKY_PROFILE (OSK-10001).
|
|
430
646
|
profile: core_1.Flags.string({
|
|
@@ -438,6 +654,7 @@ class DaemonLogs extends core_1.Command {
|
|
|
438
654
|
'<%= config.bin %> daemon logs --lines 500',
|
|
439
655
|
'<%= config.bin %> daemon logs --stderr',
|
|
440
656
|
'<%= config.bin %> daemon logs --path',
|
|
657
|
+
'<%= config.bin %> daemon logs --rotated | grep arun_',
|
|
441
658
|
'<%= config.bin %> daemon logs --profile dogfood-laptop --lines 50',
|
|
442
659
|
];
|
|
443
660
|
async run() {
|
|
@@ -466,7 +683,7 @@ class DaemonLogs extends core_1.Command {
|
|
|
466
683
|
// with it — 0.8.112–0.8.118 read the profile log even when the live
|
|
467
684
|
// daemon writes elsewhere. `planDaemonLogs` forwards only to a runtime
|
|
468
685
|
// that resolves the live log itself.
|
|
469
|
-
|
|
686
|
+
let plan = await planDaemonLogs(flags, {
|
|
470
687
|
supportsLogs: () => (0, exec_runtime_binary_1.runtimeBinarySupportsSubcommand)('logs'),
|
|
471
688
|
runtimeVersion: () => probeRuntimeVersion(),
|
|
472
689
|
statusLogPath: () => probeStatusLogPath(flags),
|
|
@@ -481,22 +698,84 @@ class DaemonLogs extends core_1.Command {
|
|
|
481
698
|
}
|
|
482
699
|
},
|
|
483
700
|
});
|
|
701
|
+
// OSK-16214: `--rotated` is new in ROTATED_LOGS_RUNTIME_VERSION, and an
|
|
702
|
+
// older runtime rejects it as an unknown option. Its `--path` is still
|
|
703
|
+
// the live-aware answer, so read that file and its siblings here.
|
|
704
|
+
const runtimeKnowsRotated = plan.kind === 'forward' &&
|
|
705
|
+
plan.runtimeVersion !== null &&
|
|
706
|
+
runtimeVersionAtLeast(plan.runtimeVersion, exports.ROTATED_LOGS_RUNTIME_VERSION) === true;
|
|
707
|
+
// OSK-16228: a `--rotated --lines N` window that cuts older lines, and
|
|
708
|
+
// `--path` with rotated siblings beside it, are said on stderr only from
|
|
709
|
+
// ROTATED_WINDOW_NOTE_RUNTIME_VERSION. Below it, read the files here.
|
|
710
|
+
const runtimeSaysWindow = plan.kind === 'forward' &&
|
|
711
|
+
plan.runtimeVersion !== null &&
|
|
712
|
+
runtimeVersionAtLeast(plan.runtimeVersion, exports.ROTATED_WINDOW_NOTE_RUNTIME_VERSION) === true;
|
|
713
|
+
if (plan.kind === 'forward' && !runtimeSaysWindow && (flags.rotated || flags.path)) {
|
|
714
|
+
const resolved = await resolveForwardedLogPath(flags);
|
|
715
|
+
if (resolved) {
|
|
716
|
+
plan = {
|
|
717
|
+
kind: 'read',
|
|
718
|
+
path: resolved,
|
|
719
|
+
note: null,
|
|
720
|
+
missingHint: 'The live daemon has not written it yet; `skrr daemon status` shows whether one is running.',
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
else if (flags.rotated && !runtimeKnowsRotated) {
|
|
724
|
+
this.error(`The installed runtime${plan.runtimeVersion ? ` (${plan.runtimeVersion})` : ''} predates ` +
|
|
725
|
+
`--rotated (${exports.ROTATED_LOGS_RUNTIME_VERSION}) and did not name its log file, so its ` +
|
|
726
|
+
'rotated files cannot be found. Upgrade it (`skrr daemon update`), or list the ' +
|
|
727
|
+
'directory `skrr daemon logs --path` prints.', { exit: 1 });
|
|
728
|
+
}
|
|
729
|
+
// Otherwise forward: the runtime still answers, just without the note.
|
|
730
|
+
}
|
|
484
731
|
if (plan.kind === 'read') {
|
|
485
732
|
const logPath = plan.path;
|
|
486
|
-
|
|
733
|
+
const siblings = rotatedLogSiblings(logPath);
|
|
734
|
+
// `--path` prints the one path and nothing else on stdout — under
|
|
735
|
+
// --rotated, every file oldest first, the current one last. Without
|
|
736
|
+
// --rotated, stderr names the siblings a grep of that path would miss
|
|
737
|
+
// (OSK-16228).
|
|
487
738
|
if (flags.path) {
|
|
739
|
+
for (const file of flags.rotated ? siblings : [])
|
|
740
|
+
this.log(file);
|
|
488
741
|
this.log(logPath);
|
|
742
|
+
if (!flags.rotated) {
|
|
743
|
+
const hint = describeRotatedSiblings(logPath, siblings);
|
|
744
|
+
if (hint)
|
|
745
|
+
this.logToStderr(hint);
|
|
746
|
+
}
|
|
489
747
|
return;
|
|
490
748
|
}
|
|
491
749
|
if (plan.note)
|
|
492
750
|
this.logToStderr(plan.note);
|
|
751
|
+
if (flags.rotated && siblings.length === 0) {
|
|
752
|
+
this.logToStderr(`No rotated files beside ${logPath}.`);
|
|
753
|
+
}
|
|
754
|
+
else if (!flags.rotated) {
|
|
755
|
+
const hint = describeRotatedSiblings(logPath, siblings);
|
|
756
|
+
if (hint)
|
|
757
|
+
this.logToStderr(hint);
|
|
758
|
+
}
|
|
493
759
|
showAge(logPath);
|
|
494
760
|
if (!(0, node_fs_1.existsSync)(logPath)) {
|
|
495
761
|
this.error(`No daemon log at ${logPath}. ${plan.missingHint}`, { exit: 1 });
|
|
496
762
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
763
|
+
if (flags.rotated && flags.lines === undefined) {
|
|
764
|
+
// Every line of every file, streamed rather than held whole.
|
|
765
|
+
await streamLogFiles([...siblings, logPath], process.stdout);
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
const window = flags.rotated
|
|
769
|
+
? readLogWindow([...siblings, logPath], flags.lines ?? 200)
|
|
770
|
+
: null;
|
|
771
|
+
const lines = window ? window.lines : readDaemonLogTail(logPath, flags.lines ?? 200);
|
|
772
|
+
if (lines.length > 0)
|
|
773
|
+
this.log(lines.join('\n'));
|
|
774
|
+
// OSK-16228: after the content, so it is the last thing a terminal
|
|
775
|
+
// shows, and on stderr, so a grep over stdout is unchanged.
|
|
776
|
+
if (window?.cut)
|
|
777
|
+
this.logToStderr(describeLogWindowCut(window.cut, lines.length));
|
|
778
|
+
}
|
|
500
779
|
if (flags.follow) {
|
|
501
780
|
let position = (0, node_fs_1.statSync)(logPath).size;
|
|
502
781
|
(0, node_fs_1.watchFile)(logPath, { interval: 500 }, () => {
|
|
@@ -528,8 +807,16 @@ class DaemonLogs extends core_1.Command {
|
|
|
528
807
|
}
|
|
529
808
|
// The banner must describe the file the runtime will actually print —
|
|
530
809
|
// the live daemon's log (OSK-15993) — not the profile default.
|
|
531
|
-
if (!flags.path)
|
|
532
|
-
|
|
810
|
+
if (!flags.path) {
|
|
811
|
+
const forwardedPath = await resolveForwardedLogPath(flags);
|
|
812
|
+
// A runtime that knows --rotated says this itself; an older one cannot.
|
|
813
|
+
if (!runtimeKnowsRotated && forwardedPath) {
|
|
814
|
+
const hint = describeRotatedSiblings(forwardedPath, rotatedLogSiblings(forwardedPath));
|
|
815
|
+
if (hint)
|
|
816
|
+
this.logToStderr(hint);
|
|
817
|
+
}
|
|
818
|
+
showAge(forwardedPath);
|
|
819
|
+
}
|
|
533
820
|
const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(args);
|
|
534
821
|
this.exit(code);
|
|
535
822
|
}
|
|
@@ -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
|
}
|