@skrr-ai/cli 0.1.101 → 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/control/apply.d.ts +15 -0
- package/dist/commands/commitments/control/apply.js +36 -0
- package/dist/commands/commitments/control/preview.d.ts +17 -0
- package/dist/commands/commitments/control/preview.js +43 -0
- package/dist/commands/commitments/control/show.d.ts +12 -0
- package/dist/commands/commitments/control/show.js +29 -0
- package/dist/commands/commitments/create.js +5 -2
- package/dist/commands/commitments/observation/probe.d.ts +15 -0
- package/dist/commands/commitments/observation/probe.js +46 -0
- package/dist/commands/commitments/observation/summary.d.ts +9 -0
- package/dist/commands/commitments/observation/summary.js +31 -0
- package/dist/commands/commitments/preflight.js +18 -1
- package/dist/commands/commitments/readiness/show.d.ts +12 -0
- package/dist/commands/commitments/readiness/show.js +29 -0
- package/dist/commands/commitments/readiness/verify.d.ts +13 -0
- package/dist/commands/commitments/readiness/verify.js +33 -0
- 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 +23 -9
- package/dist/commands/commitments/work-plan.d.ts +11 -0
- package/dist/commands/commitments/work-plan.js +36 -0
- package/dist/commands/compass/effective-policy.d.ts +12 -0
- package/dist/commands/compass/effective-policy.js +34 -0
- package/dist/commands/compass/update.d.ts +12 -0
- package/dist/commands/compass/update.js +27 -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/machines/dedicated/exec.js +3 -1
- package/dist/commands/proposals/discard.d.ts +4 -0
- package/dist/commands/proposals/discard.js +3 -0
- package/dist/commands/proposals/keep.d.ts +4 -0
- package/dist/commands/proposals/keep.js +3 -0
- package/dist/commands/proposals/snooze.d.ts +4 -0
- package/dist/commands/proposals/snooze.js +3 -0
- package/dist/commands/spaces/workflows/cancel-node.d.ts +16 -0
- package/dist/commands/spaces/workflows/cancel-node.js +44 -0
- package/dist/commands/spaces/workflows/cancel.d.ts +40 -0
- package/dist/commands/spaces/workflows/cancel.js +109 -3
- package/dist/commands/spaces/workflows/run.d.ts +9 -0
- package/dist/commands/spaces/workflows/run.js +38 -0
- package/dist/commands/tasks/approvals/resolve.d.ts +12 -0
- package/dist/commands/tasks/approvals/resolve.js +21 -5
- package/dist/commands/tasks/schedule.js +1 -1
- package/dist/commands/triggers/capabilities.js +7 -0
- package/dist/commands/triggers/create.js +12 -18
- 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 +47 -1
- package/dist/lib/commitments.js +218 -6
- package/dist/lib/compasses.d.ts +3 -0
- package/dist/lib/compasses.js +1 -0
- 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/responsibility-controls.d.ts +11 -0
- package/dist/lib/responsibility-controls.js +119 -0
- package/dist/lib/session-task-write.d.ts +13 -0
- package/dist/lib/session-task-write.js +63 -3
- package/dist/lib/stewardship-output.js +13 -2
- 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/lib/workflow-node-control.d.ts +10 -0
- package/dist/lib/workflow-node-control.js +76 -0
- package/dist/lib/workspaces.d.ts +2 -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 +11924 -8704
- package/oclif.manifest.json +1487 -396
- package/package.json +11 -2
|
@@ -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;
|
|
@@ -103,11 +103,11 @@ JSON
|
|
|
103
103
|
options: [...commitments_1.COMMITMENT_WATCHED_SOURCES],
|
|
104
104
|
}),
|
|
105
105
|
'watch-space': core_1.Flags.string({
|
|
106
|
-
description: 'Space to watch
|
|
106
|
+
description: 'One Space to watch. Accepts a space id, ref, or unique title. Implies --watch space.',
|
|
107
107
|
multiple: true,
|
|
108
108
|
}),
|
|
109
109
|
'watch-monitor': core_1.Flags.string({
|
|
110
|
-
description: 'Signal Monitor id to watch
|
|
110
|
+
description: 'One Signal Monitor id to watch. Implies --watch monitor.',
|
|
111
111
|
multiple: true,
|
|
112
112
|
}),
|
|
113
113
|
cadence: core_1.Flags.string({
|
|
@@ -187,7 +187,8 @@ JSON
|
|
|
187
187
|
? { reviewPolicy: (0, commitment_product_1.normalizeTaskReviewPolicy)(flags['task-review-policy']) }
|
|
188
188
|
: {}),
|
|
189
189
|
};
|
|
190
|
-
payload.expectedVersion
|
|
190
|
+
if (payload.expectedVersion === undefined)
|
|
191
|
+
payload.expectedVersion = current?.version;
|
|
191
192
|
}
|
|
192
193
|
const declaredDelivery = payload.delivery;
|
|
193
194
|
if (declaredDelivery?.reviewPolicy !== undefined)
|
|
@@ -270,15 +271,13 @@ JSON
|
|
|
270
271
|
monitorIds: (0, triggers_1.splitRepeated)(flags['watch-monitor']),
|
|
271
272
|
});
|
|
272
273
|
if (built) {
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
// authored; rebuilding a bare source silently erased them (OSK-15110).
|
|
274
|
+
// A flag edit selects the full replacement set. Keep sensing declarations
|
|
275
|
+
// only for a binding it still names; explicit JSON is the authored base.
|
|
276
276
|
const declarations = Array.isArray(payload.watchedSources)
|
|
277
|
-
? payload.watchedSources
|
|
277
|
+
? (0, commitments_1.normalizeWatchedSourceDeclarations)(payload.watchedSources)
|
|
278
278
|
: current?.watchedSources ?? [];
|
|
279
279
|
payload.watchedSources = built.map((next) => {
|
|
280
|
-
const scopeKey = next.source === 'space' ? 'spaceId'
|
|
281
|
-
: next.source === 'monitor' ? 'monitorId' : undefined;
|
|
280
|
+
const scopeKey = next.source === 'space' ? 'spaceId' : next.source === 'monitor' ? 'monitorId' : undefined;
|
|
282
281
|
const nextConfig = next.config;
|
|
283
282
|
const prior = declarations.find((binding) => binding?.source === next.source &&
|
|
284
283
|
(!scopeKey || binding.config?.[scopeKey] === nextConfig?.[scopeKey]));
|
|
@@ -321,6 +320,18 @@ JSON
|
|
|
321
320
|
cadence.mode = flags['cadence-mode'];
|
|
322
321
|
if (flags.timezone)
|
|
323
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
|
+
}
|
|
324
335
|
// A mode change must not carry the previous mode's fields into the
|
|
325
336
|
// replacement. Keep explicitly authored fields so invalid combinations
|
|
326
337
|
// are refused rather than silently discarding what the user requested.
|
|
@@ -371,6 +382,9 @@ JSON
|
|
|
371
382
|
Object.prototype.hasOwnProperty.call(finalEnvelope, 'maxConcurrentRuns')) {
|
|
372
383
|
throw new Error('An autonomous Agent cannot change owner Commitment concurrency policy. Ask the owner to run this update outside the Agent session.');
|
|
373
384
|
}
|
|
385
|
+
if (Array.isArray(payload.watchedSources)) {
|
|
386
|
+
payload.watchedSources = (0, commitments_1.normalizeWatchedSourceDeclarations)(payload.watchedSources);
|
|
387
|
+
}
|
|
374
388
|
}
|
|
375
389
|
catch (err) {
|
|
376
390
|
this.error(err.message, { exit: 1 });
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
export default class CommitmentsWorkPlan extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static args: {
|
|
5
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static flags: {
|
|
8
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
class CommitmentsWorkPlan extends base_command_1.BaseCommand {
|
|
7
|
+
static description = 'Read permission-filtered delegated assignments and workflow episodes for a responsibility';
|
|
8
|
+
static args = { id: core_1.Args.string({ description: 'Commitment ID', required: true, ignoreStdin: true }) };
|
|
9
|
+
static flags = { json: core_1.Flags.boolean({ description: 'Output the complete public work-plan projection as JSON' }) };
|
|
10
|
+
async run() {
|
|
11
|
+
this.requireAuth();
|
|
12
|
+
const { args, flags } = await this.parse(CommitmentsWorkPlan);
|
|
13
|
+
try {
|
|
14
|
+
const plan = await commitments_1.commitmentApi.workPlan(args.id);
|
|
15
|
+
if (flags.json) {
|
|
16
|
+
this.log(JSON.stringify(plan, null, 2));
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
for (const key of ['assignments', 'episodes']) {
|
|
20
|
+
const rows = plan[key];
|
|
21
|
+
this.log(`${key}: ${Array.isArray(rows) ? rows.length : 'unavailable'}`);
|
|
22
|
+
if (Array.isArray(rows))
|
|
23
|
+
for (const row of rows) {
|
|
24
|
+
if (typeof row === 'object' && row !== null)
|
|
25
|
+
this.log(` ${JSON.stringify(row)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
this.log(`Coverage: ${JSON.stringify(plan.coverage ?? {})}`);
|
|
29
|
+
this.log(`Coordinator: ${JSON.stringify(plan.coordinatorState ?? {})}`);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
this.handleApiError(error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
exports.default = CommitmentsWorkPlan;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
export default class CompassEffectivePolicy extends BaseCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static args: {
|
|
5
|
+
compassId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
};
|
|
7
|
+
static flags: {
|
|
8
|
+
agent: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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 compasses_1 = require("../../lib/compasses");
|
|
6
|
+
class CompassEffectivePolicy extends base_command_1.BaseCommand {
|
|
7
|
+
static description = 'Read a Compass policy revision, effective defaults and field provenance';
|
|
8
|
+
static args = { compassId: core_1.Args.string({ description: 'Compass ID', required: true, ignoreStdin: true }) };
|
|
9
|
+
static flags = {
|
|
10
|
+
agent: core_1.Flags.string({ description: 'Agent ID that owns the Compass', required: true }),
|
|
11
|
+
json: core_1.Flags.boolean({ description: 'Output the complete policy and provenance as JSON' }),
|
|
12
|
+
};
|
|
13
|
+
async run() {
|
|
14
|
+
this.requireAuth();
|
|
15
|
+
const { args, flags } = await this.parse(CompassEffectivePolicy);
|
|
16
|
+
try {
|
|
17
|
+
const policy = await compasses_1.compassApi.effectivePolicy(args.compassId, { agentId: flags.agent });
|
|
18
|
+
if (flags.json) {
|
|
19
|
+
this.log(JSON.stringify(policy, null, 2));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
this.log(`Compass ${policy.compassId}, revision ${policy.compassVersion}: ${policy.status}`);
|
|
23
|
+
this.log(`Applies to runtime: ${policy.appliesToRuntime === true ? 'yes' : 'no'}`);
|
|
24
|
+
this.log(`Autonomy defaults: ${JSON.stringify(policy.autonomy ?? {})}`);
|
|
25
|
+
this.log(`Communication defaults: ${JSON.stringify(policy.communication ?? {})}`);
|
|
26
|
+
this.log(`Authority: ${policy.authority ?? 'unknown'}`);
|
|
27
|
+
this.log(`Field provenance: ${JSON.stringify(policy.fieldProvenance ?? {})}`);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
this.handleApiError(error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.default = CompassEffectivePolicy;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* The server fences every compass write on the version the caller last saw
|
|
4
|
+
* (`expectedVersion`, required), so a stale writer gets a conflict instead of
|
|
5
|
+
* silently overwriting a newer revision. Precedence: an explicit flag, then a
|
|
6
|
+
* value the caller put in `--json-body` (passed through untouched, so an invalid
|
|
7
|
+
* one is the server's to refuse), then the current version read just now.
|
|
8
|
+
*/
|
|
9
|
+
export declare function resolveExpectedVersion(explicit: number | undefined, fileBody: Record<string, unknown>, fetchCurrentVersion: () => Promise<number>): Promise<{
|
|
10
|
+
version: unknown;
|
|
11
|
+
source: 'flag' | 'json-body' | 'current';
|
|
12
|
+
}>;
|
|
2
13
|
/**
|
|
3
14
|
* `skrr compass update <compass-id> --agent <id>`
|
|
4
15
|
*
|
|
@@ -24,6 +35,7 @@ export default class CompassUpdate extends BaseCommand {
|
|
|
24
35
|
status: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
25
36
|
invariant: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
26
37
|
reason: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
38
|
+
'expected-version': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
27
39
|
'json-body': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
28
40
|
};
|
|
29
41
|
run(): Promise<void>;
|
|
@@ -1,10 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveExpectedVersion = resolveExpectedVersion;
|
|
3
4
|
const core_1 = require("@oclif/core");
|
|
4
5
|
const base_command_1 = require("../../base-command");
|
|
5
6
|
const prompt_1 = require("../../lib/prompt");
|
|
6
7
|
const compasses_1 = require("../../lib/compasses");
|
|
7
8
|
const web_url_1 = require("../../lib/web-url");
|
|
9
|
+
/**
|
|
10
|
+
* The server fences every compass write on the version the caller last saw
|
|
11
|
+
* (`expectedVersion`, required), so a stale writer gets a conflict instead of
|
|
12
|
+
* silently overwriting a newer revision. Precedence: an explicit flag, then a
|
|
13
|
+
* value the caller put in `--json-body` (passed through untouched, so an invalid
|
|
14
|
+
* one is the server's to refuse), then the current version read just now.
|
|
15
|
+
*/
|
|
16
|
+
async function resolveExpectedVersion(explicit, fileBody, fetchCurrentVersion) {
|
|
17
|
+
if (explicit !== undefined)
|
|
18
|
+
return { version: explicit, source: 'flag' };
|
|
19
|
+
if (fileBody.expectedVersion !== undefined) {
|
|
20
|
+
return { version: fileBody.expectedVersion, source: 'json-body' };
|
|
21
|
+
}
|
|
22
|
+
return { version: await fetchCurrentVersion(), source: 'current' };
|
|
23
|
+
}
|
|
8
24
|
/**
|
|
9
25
|
* `skrr compass update <compass-id> --agent <id>`
|
|
10
26
|
*
|
|
@@ -22,6 +38,7 @@ class CompassUpdate extends base_command_1.BaseCommand {
|
|
|
22
38
|
'<%= config.bin %> compass update agch_abc --agent agent_xyz --title "Reliability & cost"',
|
|
23
39
|
'<%= config.bin %> compass update agch_abc --agent agent_xyz --invariant "p99 under 300ms" --reason "new SLO"',
|
|
24
40
|
'<%= config.bin %> compass update agch_abc --agent agent_xyz --json-body ./compass.json',
|
|
41
|
+
'<%= config.bin %> compass update agch_abc --agent agent_xyz --title "New" --expected-version 4',
|
|
25
42
|
];
|
|
26
43
|
static args = {
|
|
27
44
|
compassId: core_1.Args.string({ ignoreStdin: true, description: 'Compass ID', required: true }),
|
|
@@ -37,6 +54,11 @@ class CompassUpdate extends base_command_1.BaseCommand {
|
|
|
37
54
|
multiple: true,
|
|
38
55
|
}),
|
|
39
56
|
reason: core_1.Flags.string({ description: 'Why — recorded on the revision ledger' }),
|
|
57
|
+
'expected-version': core_1.Flags.integer({
|
|
58
|
+
min: 1,
|
|
59
|
+
description: 'Compass version this change is based on; the update is refused if it moved. ' +
|
|
60
|
+
'Defaults to the version read at the start of this command.',
|
|
61
|
+
}),
|
|
40
62
|
'json-body': core_1.Flags.string({ description: 'Path to a JSON file with the full body' }),
|
|
41
63
|
};
|
|
42
64
|
async run() {
|
|
@@ -61,6 +83,11 @@ class CompassUpdate extends base_command_1.BaseCommand {
|
|
|
61
83
|
}
|
|
62
84
|
let compass;
|
|
63
85
|
try {
|
|
86
|
+
const expected = await resolveExpectedVersion(flags['expected-version'], fileBody, async () => (await compasses_1.compassApi.get(args.compassId, { agentId: flags.agent })).version);
|
|
87
|
+
body.expectedVersion = expected.version;
|
|
88
|
+
if (expected.source === 'current' && !flags.json) {
|
|
89
|
+
this.logToStderr(`No --expected-version given; updating from the current version (v${String(expected.version)}).`);
|
|
90
|
+
}
|
|
64
91
|
compass = await compasses_1.compassApi.update(args.compassId, body);
|
|
65
92
|
}
|
|
66
93
|
catch (err) {
|
|
@@ -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,43 @@ 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
|
+
/**
|
|
65
|
+
* Log files the installed launchd definition for this profile sends output to
|
|
66
|
+
* (OSK-15993). The desktop supervisor starts the daemon with
|
|
67
|
+
* `--log-file ~/Library/Logs/Skrr/daemon.app.log` and redirects stderr to
|
|
68
|
+
* `daemon.stderr.log`, so the profile's `logs/` directory can hold a file
|
|
69
|
+
* nobody has written for a week. Read-only: the plist is parsed, never loaded.
|
|
70
|
+
*/
|
|
71
|
+
export declare function installedServiceLogCandidates(profile: string, stderr: boolean, options?: {
|
|
72
|
+
home?: string;
|
|
73
|
+
platform?: NodeJS.Platform;
|
|
74
|
+
}): string[];
|
|
75
|
+
/**
|
|
76
|
+
* Pick the file the legacy fallback reads. An old daemon cannot say which file
|
|
77
|
+
* it writes, so the most recently written of the profile log and the installed
|
|
78
|
+
* service's logs is the best evidence of the live one — and whenever that is
|
|
79
|
+
* not the profile log, the choice is said out loud rather than made silently.
|
|
80
|
+
*/
|
|
81
|
+
export declare function chooseLegacyFallbackLog(args: {
|
|
82
|
+
profilePath: string;
|
|
83
|
+
serviceCandidates: readonly string[];
|
|
84
|
+
mtimeOf: (filePath: string) => number | null;
|
|
85
|
+
}): {
|
|
86
|
+
path: string;
|
|
87
|
+
note: string | null;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Ask the installed runtime which file `logs` will read — the same live-aware
|
|
91
|
+
* answer it uses itself — so the age banner describes the file actually shown.
|
|
92
|
+
* Null when the runtime cannot answer in time; the banner is then skipped
|
|
93
|
+
* rather than printed about a file that may not be the one shown.
|
|
94
|
+
*/
|
|
95
|
+
export declare function resolveForwardedLogPath(flags: Pick<DaemonLogsFlags, 'stderr' | 'profile'>, capture?: typeof execRuntimeBinaryCapture): Promise<string | null>;
|
|
58
96
|
/** Read a bounded trailing window, so an unrotated legacy err log is safe. */
|
|
59
97
|
export declare function readDaemonLogTail(filePath: string, maxLines: number): string[];
|
|
60
98
|
/**
|
|
@@ -65,7 +103,7 @@ export declare function readDaemonLogTail(filePath: string, maxLines: number): s
|
|
|
65
103
|
* `completeTaskDurably`. Every flag declared on the command must appear here,
|
|
66
104
|
* or it parses successfully and is then silently dropped.
|
|
67
105
|
*/
|
|
68
|
-
export declare function
|
|
106
|
+
export declare function buildDaemonLogsArgs(flags: DaemonLogsFlags): string[];
|
|
69
107
|
export default class DaemonLogs extends Command {
|
|
70
108
|
static aliases: string[];
|
|
71
109
|
static description: string;
|
|
@@ -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
|
}
|