@skrr-ai/cli 0.1.102 → 0.1.104
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/create.js +2 -0
- 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 +106 -1
- package/dist/commands/daemon/logs.js +311 -15
- 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/cjs/telemetryRedaction.d.ts +61 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/telemetryRedaction.js +177 -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/dist/esm/telemetryRedaction.d.ts +61 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/telemetryRedaction.js +171 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +11908 -8693
- package/oclif.manifest.json +219 -6
- package/package.json +2 -2
package/dist/base-command.js
CHANGED
|
@@ -261,6 +261,30 @@ class BaseCommand extends core_1.Command {
|
|
|
261
261
|
this.credentialProvenance = { kind: 'daemon-delegated' };
|
|
262
262
|
}
|
|
263
263
|
catch (err) {
|
|
264
|
+
// OSK-15990 — a refusal only a new daemon sign-in repairs (e.g.
|
|
265
|
+
// DEVICE_PROOF_PIN_MISMATCH) is NOT retryable: exit 75 told every
|
|
266
|
+
// caller to loop on it. Say so, and say what to run.
|
|
267
|
+
// Matched by name, not instanceof: specs mock `./lib/delegated-cli`
|
|
268
|
+
// down to one or two functions, and a missing class would throw here.
|
|
269
|
+
if (err?.name === 'DelegatedCliUnavailableError') {
|
|
270
|
+
const e = err;
|
|
271
|
+
const remedy = e.remedy && !e.message.includes(e.remedy) ? ` Fix: ${e.remedy}` : '';
|
|
272
|
+
this.failWithCliError({
|
|
273
|
+
message: `Daemon delegated skrr credential is unavailable: ${e.message}${remedy}`,
|
|
274
|
+
code: 'DELEGATED_CLI_CREDENTIAL_UNAVAILABLE',
|
|
275
|
+
// 75 (EX_TEMPFAIL) only when waiting can help; 77 (EX_NOPERM) when
|
|
276
|
+
// the daemon must be signed in again first.
|
|
277
|
+
exit: e.retryable ? 75 : 77,
|
|
278
|
+
retryable: e.retryable,
|
|
279
|
+
details: {
|
|
280
|
+
...(e.upstreamStatus !== undefined ? { upstreamStatus: e.upstreamStatus } : {}),
|
|
281
|
+
...(e.upstreamCode ? { upstreamCode: e.upstreamCode } : {}),
|
|
282
|
+
...(e.upstreamReason ? { upstreamReason: e.upstreamReason } : {}),
|
|
283
|
+
...(e.retryAt ? { retryAt: e.retryAt } : {}),
|
|
284
|
+
...(e.remedy ? { remedy: e.remedy } : {}),
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
}
|
|
264
288
|
this.failWithCliError({
|
|
265
289
|
message: `Daemon delegated skrr credential is unavailable: ${err.message}`,
|
|
266
290
|
code: 'DELEGATED_CLI_CREDENTIAL_UNAVAILABLE',
|
|
@@ -535,9 +535,13 @@ function messageSegmentationAdvisory({ destination, text, followingParts, }) {
|
|
|
535
535
|
return undefined;
|
|
536
536
|
if (destination.kind !== 'app_owner_dm' && destination.kind !== 'slack_owner_dm')
|
|
537
537
|
return undefined;
|
|
538
|
-
|
|
538
|
+
// Measure what is READ. A widget block is not a thought to be split: its css, html and
|
|
539
|
+
// script are a program, and an advisory that counted them would invite an agent to cut
|
|
540
|
+
// one block across `--part`s, which the app could not read as a widget.
|
|
541
|
+
const prose = (0, data_provider_1.stripUiWidgetFencesForDisplay)(text, { final: true });
|
|
542
|
+
if (prose.length <= exports.OWNER_DM_SEGMENTATION_SOFT_LIMIT)
|
|
539
543
|
return undefined;
|
|
540
|
-
return (`[skrr] Owner-DM message is ${
|
|
544
|
+
return (`[skrr] Owner-DM message is ${prose.length} characters. It may read better as 2–4 short thoughts: ` +
|
|
541
545
|
'use --part with one explicit --idempotency-key. This is only an advisory; ' +
|
|
542
546
|
'use --no-message-lint to suppress it.');
|
|
543
547
|
}
|
|
@@ -66,7 +66,9 @@ class AgentsTriggersCreate extends base_command_1.BaseCommand {
|
|
|
66
66
|
multiple: true,
|
|
67
67
|
}),
|
|
68
68
|
timezone: core_1.Flags.string({ description: 'IANA timezone for schedule/once triggers' }),
|
|
69
|
-
'max-fires-per-day': core_1.Flags.integer({
|
|
69
|
+
'max-fires-per-day': core_1.Flags.integer({
|
|
70
|
+
description: 'Daily fire cap (schedule, stochastic and self_scheduled triggers only)',
|
|
71
|
+
}),
|
|
70
72
|
'max-runs': core_1.Flags.integer({
|
|
71
73
|
description: 'Lifetime fire cap for a schedule — the trigger disables itself after N total runs',
|
|
72
74
|
}),
|
|
@@ -172,6 +174,8 @@ class AgentsTriggersCreate extends base_command_1.BaseCommand {
|
|
|
172
174
|
let payload;
|
|
173
175
|
try {
|
|
174
176
|
payload = await (0, prompt_1.readJsonBody)(flags['from-json']);
|
|
177
|
+
// A flag for another kind is refused, not dropped (OSK-15491).
|
|
178
|
+
(0, triggers_1.assertKindFlagsApply)(flags.kind ?? payload.kind, flags);
|
|
175
179
|
// Agent scope is injected unconditionally — even when --from-json
|
|
176
180
|
// supplies a scope, the agent identity from the positional arg wins so
|
|
177
181
|
// the trigger can never be silently mis-scoped to a different agent.
|
|
@@ -213,22 +217,8 @@ class AgentsTriggersCreate extends base_command_1.BaseCommand {
|
|
|
213
217
|
filterField: flags['filter-field'],
|
|
214
218
|
filterValues,
|
|
215
219
|
});
|
|
216
|
-
(0, triggers_1.
|
|
217
|
-
|
|
218
|
-
eventType: flags.event,
|
|
219
|
-
filterMode: flags['filter-mode'],
|
|
220
|
-
filterField: flags['filter-field'],
|
|
221
|
-
filterValue: filterValues && filterValues.length > 1 ? filterValues : filterValues?.[0],
|
|
222
|
-
debounceMs: flags['debounce-ms'],
|
|
223
|
-
maxPerHour: flags['max-per-hour'],
|
|
224
|
-
});
|
|
225
|
-
const stochastic = {};
|
|
226
|
-
(0, triggers_1.setDefined)(stochastic, {
|
|
227
|
-
probability: flags.probability === undefined ? undefined : Number(flags.probability),
|
|
228
|
-
jitterMs: flags['jitter-ms'],
|
|
229
|
-
maxFiresPerDay: flags['max-fires-per-day'],
|
|
230
|
-
evalIntervalMinutes: flags['eval-interval-minutes'],
|
|
231
|
-
});
|
|
220
|
+
Object.assign(event, (0, triggers_1.buildEventConfigFromFlags)(flags, filterValues));
|
|
221
|
+
const stochastic = (0, triggers_1.buildStochasticConfigFromFlags)(flags);
|
|
232
222
|
const selfScheduled = (0, triggers_1.buildSelfScheduledConfigFromFlags)(flags);
|
|
233
223
|
const form = (0, triggers_1.buildFormConfigFromFlags)(flags);
|
|
234
224
|
const chatMessage = (0, triggers_1.buildChatMessageConfigFromFlags)(flags);
|
|
@@ -55,7 +55,9 @@ class AgentsTriggersUpdate extends base_command_1.BaseCommand {
|
|
|
55
55
|
multiple: true,
|
|
56
56
|
}),
|
|
57
57
|
timezone: core_1.Flags.string({ description: 'IANA timezone for schedule triggers' }),
|
|
58
|
-
'max-fires-per-day': core_1.Flags.integer({
|
|
58
|
+
'max-fires-per-day': core_1.Flags.integer({
|
|
59
|
+
description: 'Daily fire cap (schedule, stochastic and self_scheduled triggers only)',
|
|
60
|
+
}),
|
|
59
61
|
'max-runs': core_1.Flags.integer({ description: 'Lifetime fire cap for a schedule' }),
|
|
60
62
|
'end-at': core_1.Flags.string({
|
|
61
63
|
description: 'ISO-8601 datetime after which a schedule stops firing',
|
|
@@ -156,6 +158,29 @@ class AgentsTriggersUpdate extends base_command_1.BaseCommand {
|
|
|
156
158
|
// Scope is immutable from this surface — an agent trigger stays bound to
|
|
157
159
|
// its agent. Strip any scope in the JSON so the patch can't re-home it.
|
|
158
160
|
delete payload.scope;
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
this.error(err.message, { exit: 1 });
|
|
164
|
+
}
|
|
165
|
+
// The trigger's kind says which config block is its own, and only that block is sent. Every
|
|
166
|
+
// block used to go, so `--max-fires-per-day` wrote `stochastic` and `selfScheduled` onto a
|
|
167
|
+
// schedule trigger and a cap onto an on_demand one that nothing reads (OSK-15491). The scope
|
|
168
|
+
// check that used to follow the build now comes first because it is what reads the trigger.
|
|
169
|
+
let scoped;
|
|
170
|
+
try {
|
|
171
|
+
scoped = await (0, triggers_1.fetchAgentScopedTrigger)(args.agentId, args.triggerId);
|
|
172
|
+
}
|
|
173
|
+
catch (err) {
|
|
174
|
+
if (err instanceof triggers_1.AgentTriggerScopeError)
|
|
175
|
+
this.error(err.message, { exit: 4 });
|
|
176
|
+
this.handleApiError(err);
|
|
177
|
+
}
|
|
178
|
+
const scopedKind = scoped.kind;
|
|
179
|
+
if (typeof scopedKind !== 'string') {
|
|
180
|
+
this.error(`Trigger ${args.triggerId} did not report its kind, so its settings cannot be checked.`, { exit: 1 });
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
(0, triggers_1.assertKindFlagsApply)(scopedKind, flags, { kindIsFixed: true });
|
|
159
184
|
const execution = {};
|
|
160
185
|
const builtinAction = (0, triggers_1.buildBuiltinActionPayload)({
|
|
161
186
|
builtinAction: flags['builtin-action'],
|
|
@@ -175,7 +200,10 @@ class AgentsTriggersUpdate extends base_command_1.BaseCommand {
|
|
|
175
200
|
});
|
|
176
201
|
const schedule = (0, triggers_1.buildScheduleConfigFromFlags)(flags);
|
|
177
202
|
(0, triggers_1.assertScheduleTimingShape)(schedule);
|
|
178
|
-
const once = (0, triggers_1.buildOnceConfigFromFlags)(flags
|
|
203
|
+
const once = (0, triggers_1.buildOnceConfigFromFlags)(flags, {
|
|
204
|
+
kind: scopedKind,
|
|
205
|
+
allowNextRunAtAlias: true,
|
|
206
|
+
});
|
|
179
207
|
const filterValues = (0, triggers_1.splitRepeated)(flags['filter-value']);
|
|
180
208
|
// Advisory: a catalog this CLI has not caught up with must not make a
|
|
181
209
|
// server-supported event unreachable. Warn and send; the server validates.
|
|
@@ -190,22 +218,8 @@ class AgentsTriggersUpdate extends base_command_1.BaseCommand {
|
|
|
190
218
|
filterValues,
|
|
191
219
|
});
|
|
192
220
|
const event = {};
|
|
193
|
-
(0, triggers_1.
|
|
194
|
-
|
|
195
|
-
eventType: flags.event,
|
|
196
|
-
filterMode: flags['filter-mode'],
|
|
197
|
-
filterField: flags['filter-field'],
|
|
198
|
-
filterValue: filterValues && filterValues.length > 1 ? filterValues : filterValues?.[0],
|
|
199
|
-
debounceMs: flags['debounce-ms'],
|
|
200
|
-
maxPerHour: flags['max-per-hour'],
|
|
201
|
-
});
|
|
202
|
-
const stochastic = {};
|
|
203
|
-
(0, triggers_1.setDefined)(stochastic, {
|
|
204
|
-
probability: flags.probability === undefined ? undefined : Number(flags.probability),
|
|
205
|
-
jitterMs: flags['jitter-ms'],
|
|
206
|
-
maxFiresPerDay: flags['max-fires-per-day'],
|
|
207
|
-
evalIntervalMinutes: flags['eval-interval-minutes'],
|
|
208
|
-
});
|
|
221
|
+
Object.assign(event, (0, triggers_1.buildEventConfigFromFlags)(flags, filterValues));
|
|
222
|
+
const stochastic = (0, triggers_1.buildStochasticConfigFromFlags)(flags);
|
|
209
223
|
const selfScheduled = (0, triggers_1.buildSelfScheduledConfigFromFlags)(flags);
|
|
210
224
|
const form = (0, triggers_1.buildFormConfigFromFlags)(flags);
|
|
211
225
|
const chatMessage = (0, triggers_1.buildChatMessageConfigFromFlags)(flags);
|
|
@@ -217,15 +231,15 @@ class AgentsTriggersUpdate extends base_command_1.BaseCommand {
|
|
|
217
231
|
idempotencyWindow: flags['idempotency-window'],
|
|
218
232
|
});
|
|
219
233
|
(0, triggers_1.mergeObjectField)(payload, 'execution', execution);
|
|
220
|
-
(0, triggers_1.
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
234
|
+
(0, triggers_1.mergeTriggerKindConfig)(payload, scopedKind, {
|
|
235
|
+
schedule,
|
|
236
|
+
once,
|
|
237
|
+
event,
|
|
238
|
+
stochastic,
|
|
239
|
+
selfScheduled,
|
|
240
|
+
form,
|
|
241
|
+
chatMessage,
|
|
242
|
+
});
|
|
229
243
|
}
|
|
230
244
|
catch (err) {
|
|
231
245
|
this.error(err.message, { exit: 1 });
|
|
@@ -235,7 +249,6 @@ class AgentsTriggersUpdate extends base_command_1.BaseCommand {
|
|
|
235
249
|
}
|
|
236
250
|
let trigger;
|
|
237
251
|
try {
|
|
238
|
-
await (0, triggers_1.fetchAgentScopedTrigger)(args.agentId, args.triggerId);
|
|
239
252
|
trigger = await triggers_1.triggerApi.update(args.triggerId, payload);
|
|
240
253
|
}
|
|
241
254
|
catch (err) {
|
|
@@ -164,6 +164,8 @@ class CommitmentsCreate extends base_command_1.BaseCommand {
|
|
|
164
164
|
'<%= config.bin %> commitments create --agent <agent-id> --title "AWS cost watch" --outcome "Cost regressions are investigated" --agent-directive-file ./aws-cost-policy.md',
|
|
165
165
|
'<%= config.bin %> commitments create --from-json commitment.json --json',
|
|
166
166
|
`# Replace <agent-id> with your Agent ID; this creates a draft, not a running loop.
|
|
167
|
+
# In "policy", 0 is a ZERO budget (nothing may run or be spent), not "no limit".
|
|
168
|
+
# To remove a limit set it to null (e.g. "maxCostUsdPerDay": null) or leave the key out.
|
|
167
169
|
cat > commitment.json <<'JSON'
|
|
168
170
|
{
|
|
169
171
|
"agentId": "<agent-id>",
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.preflightGateLines = preflightGateLines;
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
6
|
const base_command_1 = require("../../base-command");
|
|
6
7
|
const commitments_1 = require("../../lib/commitments");
|
|
7
8
|
const prompt_1 = require("../../lib/prompt");
|
|
@@ -23,6 +24,22 @@ const prompt_1 = require("../../lib/prompt");
|
|
|
23
24
|
* dash as a short code puts two different kinds of thing in one slot.
|
|
24
25
|
*/
|
|
25
26
|
function preflightGateLines(gate, verbose = false) {
|
|
27
|
+
// "Could it contact you now?" is not a Run gate. It answers whether a message
|
|
28
|
+
// would reach the owner, and a limit the owner set themselves (zero nudges a day,
|
|
29
|
+
// quiet hours) is a chosen quiet, not a defect. It carried the same BLOCK tag, a
|
|
30
|
+
// bare code and no setting as the real blockers beside it ("paused", "Always-On
|
|
31
|
+
// off"), while the report's own `admission` field said it blocks nothing
|
|
32
|
+
// (OSK-15420). Say what it is in words, and which setting moves it.
|
|
33
|
+
if (gate.id === 'attention' && gate.verdict === 'block') {
|
|
34
|
+
const copy = (0, data_provider_1.describeSuppression)(gate.reason);
|
|
35
|
+
const lines = [
|
|
36
|
+
` ${'info'.padEnd(5)} ${gate.label} — ${copy.title.toLowerCase()}${verbose && gate.reason ? ` (${gate.reason})` : ''}`,
|
|
37
|
+
` ${copy.detail}`,
|
|
38
|
+
];
|
|
39
|
+
if (copy.settingKey)
|
|
40
|
+
lines.push(` Setting: ${copy.settingKey}`);
|
|
41
|
+
return lines;
|
|
42
|
+
}
|
|
26
43
|
const mark = gate.verdict === 'pass' ? 'ok' : gate.verdict === 'block' ? 'BLOCK' : 'warn';
|
|
27
44
|
// A BLOCKING gate always states its reason. `ok` gates got a helpful `note`
|
|
28
45
|
// and `warn` gates got a reason token, and the BLOCK gates — the only ones
|
|
@@ -176,7 +193,7 @@ class CommitmentsPreflight extends base_command_1.BaseCommand {
|
|
|
176
193
|
(endpoint.reason ? ` (${endpoint.reason})` : '') +
|
|
177
194
|
(!endpoint.available && endpoint.detail ? ` — ${endpoint.detail}` : ''));
|
|
178
195
|
}
|
|
179
|
-
this.log(`\nAttention: ${report.attention?.allowed ? 'delivery allowed now' : `
|
|
196
|
+
this.log(`\nAttention: ${report.attention?.allowed ? 'delivery allowed now' : `held back — ${(0, data_provider_1.describeSuppression)(report.attention?.suppressedBy).title.toLowerCase()}${report.attention?.suppressedBy ? ` (${report.attention.suppressedBy})` : ''}`}`);
|
|
180
197
|
if (report.verdict === 'blocked')
|
|
181
198
|
process.exitCode = 1;
|
|
182
199
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* Every Commitment one agent holds — system roles included — with whether its
|
|
4
|
+
* loop is actually installed and working: what it watches, when it next wakes,
|
|
5
|
+
* whether the last check was stopped, and what it ran today.
|
|
6
|
+
*/
|
|
7
|
+
export default class CommitmentsRoster extends BaseCommand {
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static flags: {
|
|
11
|
+
agent: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_command_1 = require("../../base-command");
|
|
5
|
+
const commitments_1 = require("../../lib/commitments");
|
|
6
|
+
const commitment_loop_status_1 = require("../../lib/commitment-loop-status");
|
|
7
|
+
/**
|
|
8
|
+
* Every Commitment one agent holds — system roles included — with whether its
|
|
9
|
+
* loop is actually installed and working: what it watches, when it next wakes,
|
|
10
|
+
* whether the last check was stopped, and what it ran today.
|
|
11
|
+
*/
|
|
12
|
+
class CommitmentsRoster extends base_command_1.BaseCommand {
|
|
13
|
+
static description = "List an agent's commitments with each one's loop state (healthy, blocked, paused, unarmed, degraded, inactive, unknown)";
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> commitments roster --agent agent_abc123',
|
|
16
|
+
'<%= config.bin %> commitments roster --agent agent_abc123 --json',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
agent: core_1.Flags.string({ description: 'Agent ID', required: true }),
|
|
20
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
21
|
+
};
|
|
22
|
+
async run() {
|
|
23
|
+
this.requireAuth();
|
|
24
|
+
const { flags } = await this.parse(CommitmentsRoster);
|
|
25
|
+
try {
|
|
26
|
+
const roster = await commitments_1.commitmentApi.roster(flags.agent);
|
|
27
|
+
if (flags.json) {
|
|
28
|
+
this.log(JSON.stringify(roster, null, 2));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
for (const line of (0, commitment_loop_status_1.formatCommitmentRoster)(roster))
|
|
32
|
+
this.log(line);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
this.handleApiError(err);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.default = CommitmentsRoster;
|
|
@@ -49,7 +49,7 @@ class CommitmentsShow extends base_command_1.BaseCommand {
|
|
|
49
49
|
this.log(`Commitment ${args.id} not found.`);
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
|
-
(0, commitments_1.renderCommitmentSummary)(response.commitment, (line) => this.log(line), response.latestCheck);
|
|
52
|
+
(0, commitments_1.renderCommitmentSummary)((0, commitments_1.withLiveAssessment)(response.commitment, response.operatingState), (line) => this.log(line), response.latestCheck);
|
|
53
53
|
// Whose permissions a run uses (OSK-11975) — read from the operating state,
|
|
54
54
|
// which carries the same projection preflight reports (OSK-12065).
|
|
55
55
|
const autonomy = response.operatingState?.execution;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* Is this commitment's loop installed and working? Names the loop state and
|
|
4
|
+
* why, the next wakes, each watched source's access, the last stop and its
|
|
5
|
+
* fix, and today's runs and spend.
|
|
6
|
+
*/
|
|
7
|
+
export default class CommitmentsStatus extends BaseCommand {
|
|
8
|
+
static description: string;
|
|
9
|
+
static examples: string[];
|
|
10
|
+
static args: {
|
|
11
|
+
commitmentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
12
|
+
};
|
|
13
|
+
static flags: {
|
|
14
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_command_1 = require("../../base-command");
|
|
5
|
+
const commitments_1 = require("../../lib/commitments");
|
|
6
|
+
const commitment_loop_status_1 = require("../../lib/commitment-loop-status");
|
|
7
|
+
/**
|
|
8
|
+
* Is this commitment's loop installed and working? Names the loop state and
|
|
9
|
+
* why, the next wakes, each watched source's access, the last stop and its
|
|
10
|
+
* fix, and today's runs and spend.
|
|
11
|
+
*/
|
|
12
|
+
class CommitmentsStatus extends base_command_1.BaseCommand {
|
|
13
|
+
static description = "Show whether a commitment's loop is installed and working: state, next wakes, source access, last stop, cost";
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> commitments status obj_abc123',
|
|
16
|
+
'<%= config.bin %> commitments status obj_abc123 --json',
|
|
17
|
+
];
|
|
18
|
+
static args = {
|
|
19
|
+
commitmentId: core_1.Args.string({ ignoreStdin: true, description: 'Commitment ID', required: true }),
|
|
20
|
+
};
|
|
21
|
+
static flags = {
|
|
22
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
this.requireAuth();
|
|
26
|
+
const { args, flags } = await this.parse(CommitmentsStatus);
|
|
27
|
+
try {
|
|
28
|
+
const status = await commitments_1.commitmentApi.loopStatus(args.commitmentId);
|
|
29
|
+
if (flags.json) {
|
|
30
|
+
this.log(JSON.stringify(status, null, 2));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const line of (0, commitment_loop_status_1.formatCommitmentLoopStatus)(status))
|
|
34
|
+
this.log(line);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
this.handleApiError(err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.default = CommitmentsStatus;
|
|
@@ -320,6 +320,18 @@ JSON
|
|
|
320
320
|
cadence.mode = flags['cadence-mode'];
|
|
321
321
|
if (flags.timezone)
|
|
322
322
|
cadence.timezone = flags.timezone;
|
|
323
|
+
// A daily cadence runs at set times and has no interval, so `--cadence 6h`
|
|
324
|
+
// on one was stored as an `intervalMs` nothing reads: the command succeeded,
|
|
325
|
+
// the version moved, and the cadence still read "daily 08:00". Refuse it and
|
|
326
|
+
// say what would do what was meant (OSK-15418).
|
|
327
|
+
if (flags.cadence && cadence.mode === 'daily') {
|
|
328
|
+
const at = Array.isArray(cadence.times) && cadence.times.length
|
|
329
|
+
? ` at ${cadence.times.join(', ')}`
|
|
330
|
+
: '';
|
|
331
|
+
this.error(`This commitment's cadence is daily${at}, which has no interval, so --cadence ${flags.cadence} would change nothing. ` +
|
|
332
|
+
`To check every ${flags.cadence} instead, add --cadence-mode interval. ` +
|
|
333
|
+
'To change the daily times, use --from-json with cadence.times.', { exit: 1 });
|
|
334
|
+
}
|
|
323
335
|
// A mode change must not carry the previous mode's fields into the
|
|
324
336
|
// replacement. Keep explicitly authored fields so invalid combinations
|
|
325
337
|
// are refused rather than silently discarding what the user requested.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
+
import { execRuntimeBinaryCapture } from '../../lib/exec-runtime-binary';
|
|
2
3
|
/**
|
|
3
4
|
* `skrr daemon logs` — forwards to `oversky logs`, which resolves the
|
|
4
5
|
* per-profile log directory (the daemon owns that mapping, and it varies by
|
|
@@ -55,6 +56,110 @@ export declare function resolveLegacyDaemonLogPath(flags: Pick<DaemonLogsFlags,
|
|
|
55
56
|
home?: string;
|
|
56
57
|
env?: NodeJS.ProcessEnv;
|
|
57
58
|
}): string;
|
|
59
|
+
/** The profile `logs` addresses: --profile, then the profile environment variable, then the pin. */
|
|
60
|
+
export declare function resolveDaemonLogsProfile(flags: Pick<DaemonLogsFlags, 'profile'>, options?: {
|
|
61
|
+
home?: string;
|
|
62
|
+
env?: NodeJS.ProcessEnv;
|
|
63
|
+
}): string;
|
|
64
|
+
/** Output destinations the installed launchd definition for a profile declares. */
|
|
65
|
+
export interface InstalledServiceLogDestinations {
|
|
66
|
+
/** The `--log-file` in ProgramArguments, when present. */
|
|
67
|
+
logFile: string | null;
|
|
68
|
+
stdoutPath: string | null;
|
|
69
|
+
stderrPath: string | null;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Read the installed launchd definition's log destinations (OSK-15993). The
|
|
73
|
+
* desktop supervisor starts the daemon with
|
|
74
|
+
* `--log-file ~/Library/Logs/Skrr/daemon.app.log` and redirects stderr to
|
|
75
|
+
* `daemon.stderr.log`, so the profile's `logs/` directory can hold a file
|
|
76
|
+
* nobody has written for a week. Read-only: the plist is parsed, never loaded.
|
|
77
|
+
*/
|
|
78
|
+
export declare function installedServiceLogDestinations(profile: string, options?: {
|
|
79
|
+
home?: string;
|
|
80
|
+
platform?: NodeJS.Platform;
|
|
81
|
+
}): InstalledServiceLogDestinations | null;
|
|
82
|
+
/** The installed service's log files for the main log, or for `--stderr`. */
|
|
83
|
+
export declare function installedServiceLogCandidates(profile: string, stderr: boolean, options?: {
|
|
84
|
+
home?: string;
|
|
85
|
+
platform?: NodeJS.Platform;
|
|
86
|
+
}): string[];
|
|
87
|
+
/**
|
|
88
|
+
* Pick the file the legacy fallback reads. An old daemon cannot say which file
|
|
89
|
+
* it writes, so the most recently written of the profile log and the installed
|
|
90
|
+
* service's logs is the best evidence of the live one — and whenever that is
|
|
91
|
+
* not the profile log, the choice is said out loud rather than made silently.
|
|
92
|
+
*/
|
|
93
|
+
export declare function chooseLegacyFallbackLog(args: {
|
|
94
|
+
profilePath: string;
|
|
95
|
+
serviceCandidates: readonly string[];
|
|
96
|
+
mtimeOf: (filePath: string) => number | null;
|
|
97
|
+
/** Why the runtime's own answer is not used; ends the note. */
|
|
98
|
+
reason?: string;
|
|
99
|
+
}): {
|
|
100
|
+
path: string;
|
|
101
|
+
note: string | null;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Ask the installed runtime which file `logs` will read — the same live-aware
|
|
105
|
+
* answer it uses itself — so the age banner describes the file actually shown.
|
|
106
|
+
* Null when the runtime cannot answer in time; the banner is then skipped
|
|
107
|
+
* rather than printed about a file that may not be the one shown.
|
|
108
|
+
*/
|
|
109
|
+
export declare function resolveForwardedLogPath(flags: Pick<DaemonLogsFlags, 'stderr' | 'profile'>, capture?: typeof execRuntimeBinaryCapture): Promise<string | null>;
|
|
110
|
+
/**
|
|
111
|
+
* The first runtime whose own `logs` reads the LIVE daemon's `--log-file`
|
|
112
|
+
* (OSK-15993, `daemon/src/daemon-log-target.ts`). 0.8.112 through 0.8.118 have
|
|
113
|
+
* a `logs` subcommand, so the subcommand probe says "supported", but it reads
|
|
114
|
+
* the profile's `logs/daemon.log` unconditionally — with a desktop-managed
|
|
115
|
+
* daemon, a file nobody has written for a week. Forwarding to one of those is
|
|
116
|
+
* forwarding to the bug, so the CLI forwards only at or above this version.
|
|
117
|
+
*/
|
|
118
|
+
export declare const LIVE_LOG_RESOLUTION_RUNTIME_VERSION = "0.8.119";
|
|
119
|
+
/** `x.y.z` from a version string, or null. A prerelease suffix is ignored. */
|
|
120
|
+
export declare function parseRuntimeVersion(text: string): [number, number, number] | null;
|
|
121
|
+
/** Whether `version` is at least `floor`; null when either is not a version. */
|
|
122
|
+
export declare function runtimeVersionAtLeast(version: string, floor: string): boolean | null;
|
|
123
|
+
/** The installed runtime's `--version`, or null when it cannot say in time. */
|
|
124
|
+
export declare function probeRuntimeVersion(capture?: typeof execRuntimeBinaryCapture): Promise<string | null>;
|
|
125
|
+
/**
|
|
126
|
+
* The live daemon's log as the runtime's `status --json` reports it
|
|
127
|
+
* (`service.logs`). Status has named the live `--log-file` since before `logs`
|
|
128
|
+
* did, so on 0.8.117 status is right while logs is wrong. Null when status
|
|
129
|
+
* cannot answer, or answers with anything but an absolute path.
|
|
130
|
+
*/
|
|
131
|
+
export declare function probeStatusLogPath(flags: Pick<DaemonLogsFlags, 'profile'>, capture?: typeof execRuntimeBinaryCapture): Promise<string | null>;
|
|
132
|
+
/** What `skrr daemon logs` does: forward to the runtime, or read a file itself. */
|
|
133
|
+
export type DaemonLogsPlan = {
|
|
134
|
+
kind: 'forward';
|
|
135
|
+
}
|
|
136
|
+
/** `note` goes to stderr before any log content; never printed for `--path`. */
|
|
137
|
+
| {
|
|
138
|
+
kind: 'read';
|
|
139
|
+
path: string;
|
|
140
|
+
note: string | null;
|
|
141
|
+
missingHint: string;
|
|
142
|
+
};
|
|
143
|
+
export interface DaemonLogsPlanDeps {
|
|
144
|
+
supportsLogs(): Promise<boolean | null>;
|
|
145
|
+
runtimeVersion(): Promise<string | null>;
|
|
146
|
+
statusLogPath(): Promise<string | null>;
|
|
147
|
+
/** The profile log for this invocation (`daemon.err.log` under `--stderr`). */
|
|
148
|
+
profilePath: string;
|
|
149
|
+
service: InstalledServiceLogDestinations | null;
|
|
150
|
+
mtimeOf(filePath: string): number | null;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Decide which runtime, if any, `skrr daemon logs` may trust (OSK-15993).
|
|
154
|
+
*
|
|
155
|
+
* - No `logs` subcommand: the legacy fallback, as before.
|
|
156
|
+
* - A runtime at or above {@link LIVE_LOG_RESOLUTION_RUNTIME_VERSION}: forward.
|
|
157
|
+
* - An older runtime, or one whose version cannot be read: ask its `status`
|
|
158
|
+
* which file the live daemon writes and read that file here. When status
|
|
159
|
+
* cannot answer, an older runtime gets the legacy fallback (the newest of the
|
|
160
|
+
* profile log and the installed service's logs); an unknown one is forwarded.
|
|
161
|
+
*/
|
|
162
|
+
export declare function planDaemonLogs(flags: Pick<DaemonLogsFlags, 'stderr'>, deps: DaemonLogsPlanDeps): Promise<DaemonLogsPlan>;
|
|
58
163
|
/** Read a bounded trailing window, so an unrotated legacy err log is safe. */
|
|
59
164
|
export declare function readDaemonLogTail(filePath: string, maxLines: number): string[];
|
|
60
165
|
/**
|
|
@@ -65,7 +170,7 @@ export declare function readDaemonLogTail(filePath: string, maxLines: number): s
|
|
|
65
170
|
* `completeTaskDurably`. Every flag declared on the command must appear here,
|
|
66
171
|
* or it parses successfully and is then silently dropped.
|
|
67
172
|
*/
|
|
68
|
-
export declare function
|
|
173
|
+
export declare function buildDaemonLogsArgs(flags: DaemonLogsFlags): string[];
|
|
69
174
|
export default class DaemonLogs extends Command {
|
|
70
175
|
static aliases: string[];
|
|
71
176
|
static description: string;
|