@skrr-ai/cli 0.1.102 → 0.1.103
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 +24 -0
- package/dist/commands/agent/message/send.js +6 -2
- package/dist/commands/agents/triggers/create.js +7 -17
- package/dist/commands/agents/triggers/update.js +41 -28
- package/dist/commands/commitments/preflight.js +18 -1
- package/dist/commands/commitments/roster.d.ts +15 -0
- package/dist/commands/commitments/roster.js +39 -0
- package/dist/commands/commitments/show.js +1 -1
- package/dist/commands/commitments/status.d.ts +17 -0
- package/dist/commands/commitments/status.js +41 -0
- package/dist/commands/commitments/update.js +12 -0
- package/dist/commands/daemon/logs.d.ts +39 -1
- package/dist/commands/daemon/logs.js +140 -13
- package/dist/commands/followups/checkin.js +46 -3
- package/dist/commands/tasks/approvals/resolve.d.ts +12 -0
- package/dist/commands/tasks/approvals/resolve.js +21 -5
- package/dist/commands/triggers/create.js +8 -17
- package/dist/commands/triggers/fire.js +3 -0
- package/dist/commands/triggers/update.js +41 -27
- package/dist/commands/widgets/check.d.ts +39 -0
- package/dist/commands/widgets/check.js +179 -0
- package/dist/lib/agent-mailbox.d.ts +0 -1
- package/dist/lib/agent-mailbox.js +0 -1
- package/dist/lib/commitment-loop-status.d.ts +16 -0
- package/dist/lib/commitment-loop-status.js +164 -0
- package/dist/lib/commitments.d.ts +28 -1
- package/dist/lib/commitments.js +184 -5
- package/dist/lib/delegated-cli.d.ts +23 -1
- package/dist/lib/delegated-cli.js +61 -4
- package/dist/lib/first-party-harness.d.ts +1 -1
- package/dist/lib/first-party-harness.js +1 -1
- package/dist/lib/session-task-write.d.ts +13 -0
- package/dist/lib/session-task-write.js +63 -3
- package/dist/lib/triggers.d.ts +26 -0
- package/dist/lib/triggers.js +134 -1
- package/dist/lib/widgetCheck.d.ts +123 -0
- package/dist/lib/widgetCheck.js +998 -0
- package/dist/lib/widgetStaticScan.d.ts +111 -0
- package/dist/lib/widgetStaticScan.js +833 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +9 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +12 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +9 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +12 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +11902 -8696
- package/oclif.manifest.json +218 -5
- package/package.json +2 -2
|
@@ -5,8 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.describeLogAge = describeLogAge;
|
|
7
7
|
exports.resolveLegacyDaemonLogPath = resolveLegacyDaemonLogPath;
|
|
8
|
+
exports.resolveDaemonLogsProfile = resolveDaemonLogsProfile;
|
|
9
|
+
exports.installedServiceLogCandidates = installedServiceLogCandidates;
|
|
10
|
+
exports.chooseLegacyFallbackLog = chooseLegacyFallbackLog;
|
|
11
|
+
exports.resolveForwardedLogPath = resolveForwardedLogPath;
|
|
8
12
|
exports.readDaemonLogTail = readDaemonLogTail;
|
|
9
|
-
exports.
|
|
13
|
+
exports.buildDaemonLogsArgs = buildDaemonLogsArgs;
|
|
10
14
|
const core_1 = require("@oclif/core");
|
|
11
15
|
const node_fs_1 = require("node:fs");
|
|
12
16
|
const node_os_1 = require("node:os");
|
|
@@ -62,6 +66,11 @@ function describeLogAge(mtimeMs, now = Date.now()) {
|
|
|
62
66
|
};
|
|
63
67
|
}
|
|
64
68
|
function resolveLegacyDaemonLogPath(flags, options = {}) {
|
|
69
|
+
const home = options.home ?? (0, node_os_1.homedir)();
|
|
70
|
+
return node_path_1.default.join(home, '.skrr', 'profiles', resolveDaemonLogsProfile(flags, options), 'logs', flags.stderr ? 'daemon.err.log' : 'daemon.log');
|
|
71
|
+
}
|
|
72
|
+
/** The profile `logs` addresses: --profile, then the profile environment variable, then the pin. */
|
|
73
|
+
function resolveDaemonLogsProfile(flags, options = {}) {
|
|
65
74
|
const home = options.home ?? (0, node_os_1.homedir)();
|
|
66
75
|
const env = options.env ?? process.env;
|
|
67
76
|
// An explicit --profile outranks the environment, as it does everywhere else.
|
|
@@ -81,7 +90,106 @@ function resolveLegacyDaemonLogPath(flags, options = {}) {
|
|
|
81
90
|
// An absent/corrupt sticky selector must match the daemon's default.
|
|
82
91
|
}
|
|
83
92
|
}
|
|
84
|
-
return
|
|
93
|
+
return profile ?? 'default';
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Log files the installed launchd definition for this profile sends output to
|
|
97
|
+
* (OSK-15993). The desktop supervisor starts the daemon with
|
|
98
|
+
* `--log-file ~/Library/Logs/Skrr/daemon.app.log` and redirects stderr to
|
|
99
|
+
* `daemon.stderr.log`, so the profile's `logs/` directory can hold a file
|
|
100
|
+
* nobody has written for a week. Read-only: the plist is parsed, never loaded.
|
|
101
|
+
*/
|
|
102
|
+
function installedServiceLogCandidates(profile, stderr, options = {}) {
|
|
103
|
+
if ((options.platform ?? process.platform) !== 'darwin')
|
|
104
|
+
return [];
|
|
105
|
+
const plist = node_path_1.default.join(options.home ?? (0, node_os_1.homedir)(), 'Library', 'LaunchAgents', `ai.skrr.daemon.${profile}.plist`);
|
|
106
|
+
let xml;
|
|
107
|
+
try {
|
|
108
|
+
xml = (0, node_fs_1.readFileSync)(plist, 'utf8');
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
const unescape = (v) => v
|
|
114
|
+
.replace(/</g, '<')
|
|
115
|
+
.replace(/>/g, '>')
|
|
116
|
+
.replace(/"/g, '"')
|
|
117
|
+
.replace(/'/g, "'")
|
|
118
|
+
.replace(/&/g, '&');
|
|
119
|
+
const stringFor = (key) => {
|
|
120
|
+
const m = new RegExp(`<key>\\s*${key}\\s*</key>\\s*<string>([\\s\\S]*?)</string>`).exec(xml);
|
|
121
|
+
return m?.[1]?.trim() ? unescape(m[1].trim()) : null;
|
|
122
|
+
};
|
|
123
|
+
if (stderr) {
|
|
124
|
+
const errPath = stringFor('StandardErrorPath');
|
|
125
|
+
return errPath ? [errPath] : [];
|
|
126
|
+
}
|
|
127
|
+
const out = [];
|
|
128
|
+
const array = /<key>\s*ProgramArguments\s*<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(xml);
|
|
129
|
+
if (array?.[1]) {
|
|
130
|
+
const args = [...array[1].matchAll(/<string>([\s\S]*?)<\/string>/g)].map((m) => unescape(m[1].trim()));
|
|
131
|
+
const at = args.lastIndexOf('--log-file');
|
|
132
|
+
if (at >= 0 && args[at + 1] && !args[at + 1].startsWith('-'))
|
|
133
|
+
out.push(args[at + 1]);
|
|
134
|
+
}
|
|
135
|
+
const stdoutPath = stringFor('StandardOutPath');
|
|
136
|
+
if (stdoutPath)
|
|
137
|
+
out.push(stdoutPath);
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Pick the file the legacy fallback reads. An old daemon cannot say which file
|
|
142
|
+
* it writes, so the most recently written of the profile log and the installed
|
|
143
|
+
* service's logs is the best evidence of the live one — and whenever that is
|
|
144
|
+
* not the profile log, the choice is said out loud rather than made silently.
|
|
145
|
+
*/
|
|
146
|
+
function chooseLegacyFallbackLog(args) {
|
|
147
|
+
const profileMtime = args.mtimeOf(args.profilePath);
|
|
148
|
+
let best = null;
|
|
149
|
+
for (const file of args.serviceCandidates) {
|
|
150
|
+
if (node_path_1.default.resolve(file) === node_path_1.default.resolve(args.profilePath))
|
|
151
|
+
continue;
|
|
152
|
+
const mtime = args.mtimeOf(file);
|
|
153
|
+
if (mtime === null)
|
|
154
|
+
continue;
|
|
155
|
+
if (!best || mtime > best.mtime)
|
|
156
|
+
best = { file, mtime };
|
|
157
|
+
}
|
|
158
|
+
if (!best || (profileMtime !== null && profileMtime >= best.mtime)) {
|
|
159
|
+
return { path: args.profilePath, note: null };
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
path: best.file,
|
|
163
|
+
note: `Showing ${best.file}, the installed service's log: it was written more recently than ` +
|
|
164
|
+
(profileMtime === null
|
|
165
|
+
? `the profile log ${args.profilePath}, which does not exist.`
|
|
166
|
+
: `the profile log ${args.profilePath}, which has not been written since.`) +
|
|
167
|
+
' This daemon predates `logs`, so it cannot name its live log itself.',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Ask the installed runtime which file `logs` will read — the same live-aware
|
|
172
|
+
* answer it uses itself — so the age banner describes the file actually shown.
|
|
173
|
+
* Null when the runtime cannot answer in time; the banner is then skipped
|
|
174
|
+
* rather than printed about a file that may not be the one shown.
|
|
175
|
+
*/
|
|
176
|
+
async function resolveForwardedLogPath(flags, capture = exec_runtime_binary_1.execRuntimeBinaryCapture) {
|
|
177
|
+
try {
|
|
178
|
+
const out = await capture(buildDaemonLogsArgs({ ...flags, path: true }), {
|
|
179
|
+
timeoutMs: 5_000,
|
|
180
|
+
});
|
|
181
|
+
if (out.code !== 0)
|
|
182
|
+
return null;
|
|
183
|
+
const lines = out.stdout
|
|
184
|
+
.split('\n')
|
|
185
|
+
.map((l) => l.trim())
|
|
186
|
+
.filter(Boolean);
|
|
187
|
+
const last = lines.at(-1);
|
|
188
|
+
return last && node_path_1.default.isAbsolute(last) ? last : null;
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
85
193
|
}
|
|
86
194
|
/** Read a bounded trailing window, so an unrotated legacy err log is safe. */
|
|
87
195
|
function readDaemonLogTail(filePath, maxLines) {
|
|
@@ -114,7 +222,7 @@ function readDaemonLogTail(filePath, maxLines) {
|
|
|
114
222
|
* `completeTaskDurably`. Every flag declared on the command must appear here,
|
|
115
223
|
* or it parses successfully and is then silently dropped.
|
|
116
224
|
*/
|
|
117
|
-
function
|
|
225
|
+
function buildDaemonLogsArgs(flags) {
|
|
118
226
|
return [
|
|
119
227
|
// The runtime's own global option, placed before the subcommand.
|
|
120
228
|
...(flags.profile ? ['--profile', flags.profile] : []),
|
|
@@ -174,31 +282,46 @@ class DaemonLogs extends core_1.Command {
|
|
|
174
282
|
];
|
|
175
283
|
async run() {
|
|
176
284
|
const { flags } = await this.parse(DaemonLogs);
|
|
177
|
-
const args =
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
285
|
+
const args = buildDaemonLogsArgs(flags);
|
|
286
|
+
const showAge = (filePath) => {
|
|
287
|
+
// Before ANY log content, on stderr so it never pollutes a pipe. `--path`
|
|
288
|
+
// is exempt: its whole contract is one path on stdout.
|
|
289
|
+
if (flags.path || !filePath)
|
|
290
|
+
return;
|
|
181
291
|
try {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
const { line } = describeLogAge((0, node_fs_1.statSync)(candidate).mtimeMs);
|
|
185
|
-
this.logToStderr(line);
|
|
292
|
+
if ((0, node_fs_1.existsSync)(filePath)) {
|
|
293
|
+
this.logToStderr(`${filePath}: ${describeLogAge((0, node_fs_1.statSync)(filePath).mtimeMs).line}`);
|
|
186
294
|
}
|
|
187
295
|
}
|
|
188
296
|
catch {
|
|
189
297
|
// Never let the banner stop the logs from being shown.
|
|
190
298
|
}
|
|
191
|
-
}
|
|
299
|
+
};
|
|
192
300
|
try {
|
|
193
301
|
// `oversky 0.2.x` has profiles and writes the same files, but has no
|
|
194
302
|
// `logs` subcommand. Probe top-level help first so forwarding does not
|
|
195
303
|
// print an unrelated "unknown option" error before the useful output.
|
|
196
304
|
if ((await (0, exec_runtime_binary_1.runtimeBinarySupportsSubcommand)('logs')) === false) {
|
|
197
|
-
const
|
|
305
|
+
const chosen = chooseLegacyFallbackLog({
|
|
306
|
+
profilePath: resolveLegacyDaemonLogPath(flags),
|
|
307
|
+
serviceCandidates: installedServiceLogCandidates(resolveDaemonLogsProfile(flags), flags.stderr === true),
|
|
308
|
+
mtimeOf: (filePath) => {
|
|
309
|
+
try {
|
|
310
|
+
return (0, node_fs_1.statSync)(filePath).mtimeMs;
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
const logPath = chosen.path;
|
|
318
|
+
if (chosen.note)
|
|
319
|
+
this.logToStderr(chosen.note);
|
|
198
320
|
if (flags.path) {
|
|
199
321
|
this.log(logPath);
|
|
200
322
|
return;
|
|
201
323
|
}
|
|
324
|
+
showAge(logPath);
|
|
202
325
|
if (!(0, node_fs_1.existsSync)(logPath)) {
|
|
203
326
|
this.error(`No daemon log at ${logPath}. The installed daemon predates \`oversky logs\`; start it or upgrade it, then retry.`, { exit: 1 });
|
|
204
327
|
}
|
|
@@ -234,6 +357,10 @@ class DaemonLogs extends core_1.Command {
|
|
|
234
357
|
}
|
|
235
358
|
return;
|
|
236
359
|
}
|
|
360
|
+
// The banner must describe the file the runtime will actually print —
|
|
361
|
+
// the live daemon's log (OSK-15993) — not the profile default.
|
|
362
|
+
if (!flags.path)
|
|
363
|
+
showAge(await resolveForwardedLogPath(flags));
|
|
237
364
|
const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(args);
|
|
238
365
|
this.exit(code);
|
|
239
366
|
}
|
|
@@ -99,18 +99,32 @@ class FollowupsCheckin extends base_command_1.BaseCommand {
|
|
|
99
99
|
this.handleApiError(err);
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
|
+
const producer = agent.boundToSession ? 'agent_in_turn' : 'user_explicit';
|
|
103
|
+
// An explicit request from a person that arms nothing is a refusal, and a script reading the
|
|
104
|
+
// exit code must see it as one (OSK-15413). A check-in the AGENT chose to make is different:
|
|
105
|
+
// there, staying quiet when the budget is spent is the designed outcome.
|
|
106
|
+
if (response.dropped && producer === 'user_explicit') {
|
|
107
|
+
this.failWithCliError({
|
|
108
|
+
message: budgetRefusalMessage(agent.id, response.budget),
|
|
109
|
+
code: 'FOLLOWUP_BUDGET_EXHAUSTED',
|
|
110
|
+
exit: 1,
|
|
111
|
+
retryable: false,
|
|
112
|
+
details: { budget: response.budget ?? null, response },
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
102
116
|
if (flags.json) {
|
|
103
117
|
this.log(JSON.stringify(response, null, 2));
|
|
104
118
|
return;
|
|
105
119
|
}
|
|
106
120
|
const row = response.followup;
|
|
107
121
|
if (response.reused) {
|
|
108
|
-
this.log(
|
|
122
|
+
this.log(reusedMessage(row, subject, flags.why));
|
|
109
123
|
return;
|
|
110
124
|
}
|
|
111
125
|
if (response.dropped) {
|
|
112
|
-
// silent_drop is the check-in budget's contract: the answer to
|
|
113
|
-
// greeting too many" is quiet, not an error the caller can retry.
|
|
126
|
+
// silent_drop is the check-in budget's contract for an agent's own check-in: the answer to
|
|
127
|
+
// "one greeting too many" is quiet, not an error the caller can retry.
|
|
114
128
|
this.log('Dropped by the daily check-in budget — staying quiet is the correct outcome.');
|
|
115
129
|
return;
|
|
116
130
|
}
|
|
@@ -118,3 +132,32 @@ class FollowupsCheckin extends base_command_1.BaseCommand {
|
|
|
118
132
|
}
|
|
119
133
|
}
|
|
120
134
|
exports.default = FollowupsCheckin;
|
|
135
|
+
/**
|
|
136
|
+
* What a reused answer means for the person who asked: a second check-in on a subject that already
|
|
137
|
+
* has one arms nothing and changes nothing, so the note and time they typed were NOT applied. Say
|
|
138
|
+
* so, show what is armed, and name the two ways forward (OSK-15411). Exit stays 0: the follow-up
|
|
139
|
+
* they wanted to exist does exist.
|
|
140
|
+
*/
|
|
141
|
+
function reusedMessage(row, subject, requestedWhy) {
|
|
142
|
+
const lines = [
|
|
143
|
+
`Already armed: ${row?.id} — one pending check-in per subject (${subject.type}:${subject.externalKey}), and this is it.`,
|
|
144
|
+
'Nothing in this request was applied: it armed no new check-in and changed neither the note nor the time.',
|
|
145
|
+
];
|
|
146
|
+
if (row) {
|
|
147
|
+
const sameNote = row.why === requestedWhy;
|
|
148
|
+
lines.push(`Armed: ${row.why ? `"${row.why}"` : '(no note)'}, next ${row.nextRunAt ?? 'when its schedule says'}${sameNote ? ' (the same note you gave)' : ''}.`);
|
|
149
|
+
}
|
|
150
|
+
lines.push(`To change the time: skrr followups reschedule ${row?.id ?? '<id>'} --at <instant> or --in <offset>.`, "To change the note: cancel it and create it again (a cancelled check-in still counts against today's check-in budget).");
|
|
151
|
+
return lines.join('\n');
|
|
152
|
+
}
|
|
153
|
+
/** The refusal for an explicit check-in the daily budget will not take, with the number and the way to raise it. */
|
|
154
|
+
function budgetRefusalMessage(agentId, budget) {
|
|
155
|
+
const { used, limit } = (budget && typeof budget === 'object' ? budget : {});
|
|
156
|
+
const spent = typeof used === 'number' && typeof limit === 'number'
|
|
157
|
+
? `${agentId} has used ${used} of its ${limit} check-ins today`
|
|
158
|
+
: `${agentId} has used its daily check-in budget`;
|
|
159
|
+
return [
|
|
160
|
+
`No check-in was armed: ${spent}. The day resets at 00:00 UTC, and a cancelled check-in still counts.`,
|
|
161
|
+
`Raise the limit with: skrr agents triggers policy ${agentId} --followup-budget checkin=<count>`,
|
|
162
|
+
].join(' ');
|
|
163
|
+
}
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
import { type TaskApprovalResolveParams } from '@skrr-ai/data-provider';
|
|
1
2
|
import { BaseCommand } from '../../../base-command';
|
|
3
|
+
/**
|
|
4
|
+
* The request body for resolving an approval. The server's field is `comment`
|
|
5
|
+
* (`TaskApprovalResolveParams`); `--reason` is only the flag's name. It was sent
|
|
6
|
+
* as `reason` through an `as unknown as` cast, so the type checker could not see
|
|
7
|
+
* the mismatch and the server's non-strict schema dropped the key: the command
|
|
8
|
+
* succeeded and the reason was stored nowhere (OSK-15362).
|
|
9
|
+
*/
|
|
10
|
+
export declare function approvalResolutionBody(flags: {
|
|
11
|
+
status: 'approved' | 'rejected';
|
|
12
|
+
reason?: string;
|
|
13
|
+
}): TaskApprovalResolveParams;
|
|
2
14
|
export default class TasksApprovalsResolve extends BaseCommand {
|
|
3
15
|
static description: string;
|
|
4
16
|
static examples: string[];
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.approvalResolutionBody = approvalResolutionBody;
|
|
3
4
|
const core_1 = require("@oclif/core");
|
|
4
5
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
6
|
const base_command_1 = require("../../../base-command");
|
|
7
|
+
/**
|
|
8
|
+
* The request body for resolving an approval. The server's field is `comment`
|
|
9
|
+
* (`TaskApprovalResolveParams`); `--reason` is only the flag's name. It was sent
|
|
10
|
+
* as `reason` through an `as unknown as` cast, so the type checker could not see
|
|
11
|
+
* the mismatch and the server's non-strict schema dropped the key: the command
|
|
12
|
+
* succeeded and the reason was stored nowhere (OSK-15362).
|
|
13
|
+
*/
|
|
14
|
+
function approvalResolutionBody(flags) {
|
|
15
|
+
const body = { status: flags.status };
|
|
16
|
+
if (flags.reason)
|
|
17
|
+
body.comment = flags.reason;
|
|
18
|
+
return body;
|
|
19
|
+
}
|
|
6
20
|
class TasksApprovalsResolve extends base_command_1.BaseCommand {
|
|
7
21
|
static description = 'Resolve (approve or reject) a task approval';
|
|
8
22
|
static examples = [
|
|
@@ -27,15 +41,15 @@ class TasksApprovalsResolve extends base_command_1.BaseCommand {
|
|
|
27
41
|
this.requireAuth();
|
|
28
42
|
const { args, flags } = await this.parse(TasksApprovalsResolve);
|
|
29
43
|
const taskRef = await this.resolveEntityRef('task', args.id);
|
|
30
|
-
const data = { status: flags.status };
|
|
31
|
-
if (flags.reason)
|
|
32
|
-
data.reason = flags.reason;
|
|
33
44
|
let result;
|
|
34
45
|
try {
|
|
35
46
|
result = await data_provider_1.dataService.resolveTaskApproval({
|
|
36
47
|
taskId: taskRef,
|
|
37
48
|
approvalId: args.approvalId,
|
|
38
|
-
data:
|
|
49
|
+
data: approvalResolutionBody({
|
|
50
|
+
status: flags.status,
|
|
51
|
+
reason: flags.reason,
|
|
52
|
+
}),
|
|
39
53
|
});
|
|
40
54
|
}
|
|
41
55
|
catch (err) {
|
|
@@ -46,7 +60,9 @@ class TasksApprovalsResolve extends base_command_1.BaseCommand {
|
|
|
46
60
|
return;
|
|
47
61
|
}
|
|
48
62
|
const a = result;
|
|
49
|
-
|
|
63
|
+
// Say what was recorded, so a reason that was not stored cannot pass unseen.
|
|
64
|
+
const recorded = typeof a.comment === 'string' && a.comment ? ` (reason: ${a.comment})` : '';
|
|
65
|
+
this.log(`Approval ${a.id ?? a._id ?? args.approvalId} → ${flags.status}${recorded}`);
|
|
50
66
|
}
|
|
51
67
|
}
|
|
52
68
|
exports.default = TasksApprovalsResolve;
|
|
@@ -91,7 +91,9 @@ class TriggersCreate extends base_command_1.BaseCommand {
|
|
|
91
91
|
multiple: true,
|
|
92
92
|
}),
|
|
93
93
|
timezone: core_1.Flags.string({ description: 'IANA timezone for schedule triggers' }),
|
|
94
|
-
'max-fires-per-day': core_1.Flags.integer({
|
|
94
|
+
'max-fires-per-day': core_1.Flags.integer({
|
|
95
|
+
description: 'Daily fire cap (schedule, stochastic and self_scheduled triggers only)',
|
|
96
|
+
}),
|
|
95
97
|
'max-runs': core_1.Flags.integer({ description: 'Lifetime fire cap for schedule triggers' }),
|
|
96
98
|
'end-at': core_1.Flags.string({
|
|
97
99
|
description: 'ISO-8601 datetime after which a schedule stops firing',
|
|
@@ -192,6 +194,9 @@ class TriggersCreate extends base_command_1.BaseCommand {
|
|
|
192
194
|
let payload;
|
|
193
195
|
try {
|
|
194
196
|
payload = await (0, prompt_1.readJsonBody)(flags['from-json']);
|
|
197
|
+
// A flag for a kind other than --kind is refused, not dropped: the block it fills is not
|
|
198
|
+
// sent, so the caller would believe a limit was set that nothing holds (OSK-15491).
|
|
199
|
+
(0, triggers_1.assertKindFlagsApply)(flags.kind ?? payload.kind, flags);
|
|
195
200
|
const refs = (0, triggers_1.parseKeyValueFlags)(flags.ref, '--ref', flags.scope ?? payload.scope?.kind);
|
|
196
201
|
if (flags.scope || Object.keys(refs).length > 0) {
|
|
197
202
|
payload.scope = {
|
|
@@ -267,22 +272,8 @@ class TriggersCreate extends base_command_1.BaseCommand {
|
|
|
267
272
|
filterField: flags['filter-field'],
|
|
268
273
|
filterValues,
|
|
269
274
|
});
|
|
270
|
-
(0, triggers_1.
|
|
271
|
-
|
|
272
|
-
eventType: flags.event,
|
|
273
|
-
filterMode: flags['filter-mode'],
|
|
274
|
-
filterField: flags['filter-field'],
|
|
275
|
-
filterValue: filterValues && filterValues.length > 1 ? filterValues : filterValues?.[0],
|
|
276
|
-
debounceMs: flags['debounce-ms'],
|
|
277
|
-
maxPerHour: flags['max-per-hour'],
|
|
278
|
-
});
|
|
279
|
-
const stochastic = {};
|
|
280
|
-
(0, triggers_1.setDefined)(stochastic, {
|
|
281
|
-
probability: flags.probability === undefined ? undefined : Number(flags.probability),
|
|
282
|
-
jitterMs: flags['jitter-ms'],
|
|
283
|
-
maxFiresPerDay: flags['max-fires-per-day'],
|
|
284
|
-
evalIntervalMinutes: flags['eval-interval-minutes'],
|
|
285
|
-
});
|
|
275
|
+
Object.assign(event, (0, triggers_1.buildEventConfigFromFlags)(flags, filterValues));
|
|
276
|
+
const stochastic = (0, triggers_1.buildStochasticConfigFromFlags)(flags);
|
|
286
277
|
const selfScheduled = (0, triggers_1.buildSelfScheduledConfigFromFlags)(flags);
|
|
287
278
|
const form = (0, triggers_1.buildFormConfigFromFlags)(flags);
|
|
288
279
|
const chatMessage = (0, triggers_1.buildChatMessageConfigFromFlags)(flags);
|
|
@@ -41,6 +41,9 @@ class TriggersFire extends base_command_1.BaseCommand {
|
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
this.log(`Fire requested: outcome=${String(result.outcome ?? '-')} reason=${String(result.reason ?? '-')}`);
|
|
44
|
+
const note = (0, triggers_1.describeScheduledFire)(result);
|
|
45
|
+
if (note)
|
|
46
|
+
this.log(note);
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
exports.default = TriggersFire;
|
|
@@ -48,7 +48,9 @@ class TriggersUpdate extends base_command_1.BaseCommand {
|
|
|
48
48
|
multiple: true,
|
|
49
49
|
}),
|
|
50
50
|
timezone: core_1.Flags.string({ description: 'IANA timezone for schedule triggers' }),
|
|
51
|
-
'max-fires-per-day': core_1.Flags.integer({
|
|
51
|
+
'max-fires-per-day': core_1.Flags.integer({
|
|
52
|
+
description: 'Daily fire cap (schedule, stochastic and self_scheduled triggers only)',
|
|
53
|
+
}),
|
|
52
54
|
'max-runs': core_1.Flags.integer({ description: 'Lifetime fire cap for schedule triggers' }),
|
|
53
55
|
'end-at': core_1.Flags.string({
|
|
54
56
|
description: 'ISO-8601 datetime after which a schedule stops firing',
|
|
@@ -150,6 +152,29 @@ class TriggersUpdate extends base_command_1.BaseCommand {
|
|
|
150
152
|
throw new Error('Trigger kind cannot be changed after creation. ' +
|
|
151
153
|
'Remove `kind` from --from-json, or delete and recreate the trigger.');
|
|
152
154
|
}
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
this.error(err.message, { exit: 1 });
|
|
158
|
+
}
|
|
159
|
+
// The kind is the server's to say and cannot change, so the flags are checked against it and
|
|
160
|
+
// only the config block it owns is sent. Every block used to go: `--max-fires-per-day` wrote
|
|
161
|
+
// `stochastic` and `selfScheduled` onto a schedule trigger, and onto an on_demand one a cap
|
|
162
|
+
// nothing reads (OSK-15491).
|
|
163
|
+
let existing;
|
|
164
|
+
try {
|
|
165
|
+
existing = await triggers_1.triggerApi.get(args.id);
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
this.handleApiError(err);
|
|
169
|
+
}
|
|
170
|
+
const existingKind = existing?.kind;
|
|
171
|
+
if (typeof existingKind !== 'string') {
|
|
172
|
+
this.error(`Trigger ${args.id} did not report its kind, so its settings cannot be checked.`, {
|
|
173
|
+
exit: 1,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
(0, triggers_1.assertKindFlagsApply)(existingKind, flags, { kindIsFixed: true });
|
|
153
178
|
const execution = {};
|
|
154
179
|
const builtinAction = (0, triggers_1.buildBuiltinActionPayload)({
|
|
155
180
|
builtinAction: flags['builtin-action'],
|
|
@@ -169,7 +194,10 @@ class TriggersUpdate extends base_command_1.BaseCommand {
|
|
|
169
194
|
});
|
|
170
195
|
const schedule = (0, triggers_1.buildScheduleConfigFromFlags)(flags);
|
|
171
196
|
(0, triggers_1.assertScheduleTimingShape)(schedule);
|
|
172
|
-
const once = (0, triggers_1.buildOnceConfigFromFlags)(flags
|
|
197
|
+
const once = (0, triggers_1.buildOnceConfigFromFlags)(flags, {
|
|
198
|
+
kind: existingKind,
|
|
199
|
+
allowNextRunAtAlias: true,
|
|
200
|
+
});
|
|
173
201
|
const filterValues = (0, triggers_1.splitRepeated)(flags['filter-value']);
|
|
174
202
|
// Consult the authoritative server catalog before warning about a local
|
|
175
203
|
// snapshot miss; server validation remains final when it is unavailable.
|
|
@@ -188,22 +216,8 @@ class TriggersUpdate extends base_command_1.BaseCommand {
|
|
|
188
216
|
filterValues,
|
|
189
217
|
});
|
|
190
218
|
const event = {};
|
|
191
|
-
(0, triggers_1.
|
|
192
|
-
|
|
193
|
-
eventType: flags.event,
|
|
194
|
-
filterMode: flags['filter-mode'],
|
|
195
|
-
filterField: flags['filter-field'],
|
|
196
|
-
filterValue: filterValues && filterValues.length > 1 ? filterValues : filterValues?.[0],
|
|
197
|
-
debounceMs: flags['debounce-ms'],
|
|
198
|
-
maxPerHour: flags['max-per-hour'],
|
|
199
|
-
});
|
|
200
|
-
const stochastic = {};
|
|
201
|
-
(0, triggers_1.setDefined)(stochastic, {
|
|
202
|
-
probability: flags.probability === undefined ? undefined : Number(flags.probability),
|
|
203
|
-
jitterMs: flags['jitter-ms'],
|
|
204
|
-
maxFiresPerDay: flags['max-fires-per-day'],
|
|
205
|
-
evalIntervalMinutes: flags['eval-interval-minutes'],
|
|
206
|
-
});
|
|
219
|
+
Object.assign(event, (0, triggers_1.buildEventConfigFromFlags)(flags, filterValues));
|
|
220
|
+
const stochastic = (0, triggers_1.buildStochasticConfigFromFlags)(flags);
|
|
207
221
|
const selfScheduled = (0, triggers_1.buildSelfScheduledConfigFromFlags)(flags);
|
|
208
222
|
const form = (0, triggers_1.buildFormConfigFromFlags)(flags);
|
|
209
223
|
const chatMessage = (0, triggers_1.buildChatMessageConfigFromFlags)(flags);
|
|
@@ -215,15 +229,15 @@ class TriggersUpdate extends base_command_1.BaseCommand {
|
|
|
215
229
|
idempotencyWindow: flags['idempotency-window'],
|
|
216
230
|
});
|
|
217
231
|
(0, triggers_1.mergeObjectField)(payload, 'execution', execution);
|
|
218
|
-
(0, triggers_1.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
232
|
+
(0, triggers_1.mergeTriggerKindConfig)(payload, existingKind, {
|
|
233
|
+
schedule,
|
|
234
|
+
once,
|
|
235
|
+
event,
|
|
236
|
+
stochastic,
|
|
237
|
+
selfScheduled,
|
|
238
|
+
form,
|
|
239
|
+
chatMessage,
|
|
240
|
+
});
|
|
227
241
|
}
|
|
228
242
|
catch (err) {
|
|
229
243
|
this.error(err.message, { exit: 1 });
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
/**
|
|
3
|
+
* `skrr widgets check` — the offline self-check a harness runs in Bash BEFORE it
|
|
4
|
+
* replies, because a rejected widget is only discovered after the message is sent.
|
|
5
|
+
*
|
|
6
|
+
* It extends `Command`, not `BaseCommand`, on purpose: it reads a file or stdin and
|
|
7
|
+
* prints, so it must work with no sign-in, no network and no daemon credential
|
|
8
|
+
* (a sandboxed harness has none, and `BaseCommand.init()` resolves one before any
|
|
9
|
+
* command body runs). The same reason `instructions guide` does it.
|
|
10
|
+
*
|
|
11
|
+
* It reaches NO API. There is no route behind it, so there is nothing for the
|
|
12
|
+
* CLI/API parity spec (`domain-api-parity.spec.ts`) to hold it to: it adds nothing
|
|
13
|
+
* to `api-endpoints.ts` or `dataService`, and it is safe to run anywhere.
|
|
14
|
+
*
|
|
15
|
+
* Exit codes: 0 every widget would be accepted and has no error finding; 1 at
|
|
16
|
+
* least one would be refused or has an error finding (or none was found); 2 the
|
|
17
|
+
* input could not be read.
|
|
18
|
+
*/
|
|
19
|
+
export declare const WIDGETS_CHECK_EXIT: {
|
|
20
|
+
readonly ok: 0;
|
|
21
|
+
readonly notOk: 1;
|
|
22
|
+
readonly unreadable: 2;
|
|
23
|
+
};
|
|
24
|
+
export default class WidgetsCheck extends Command {
|
|
25
|
+
static description: string;
|
|
26
|
+
static examples: string[];
|
|
27
|
+
static args: {
|
|
28
|
+
file: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
29
|
+
};
|
|
30
|
+
static flags: {
|
|
31
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
32
|
+
format: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
33
|
+
strict: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
34
|
+
'render-fence': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
35
|
+
};
|
|
36
|
+
run(): Promise<void>;
|
|
37
|
+
private readInput;
|
|
38
|
+
private emitInputError;
|
|
39
|
+
}
|