@skrr-ai/cli 0.1.38 → 0.1.40
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/README.md +1 -1
- package/bin/dev-fallback.js +8 -0
- package/dist/base-command.js +8 -0
- package/dist/commands/agents/create.d.ts +7 -0
- package/dist/commands/agents/create.js +13 -1
- package/dist/commands/agents/triggers/policy.d.ts +1 -0
- package/dist/commands/agents/triggers/policy.js +32 -0
- package/dist/commands/agents/update.js +4 -3
- package/dist/commands/followups/observability.js +6 -2
- package/dist/commands/followups/remind.js +5 -2
- package/dist/commands/followups/reschedule.js +2 -1
- package/dist/commands/followups/show.js +4 -0
- package/dist/commands/followups/watch.d.ts +1 -0
- package/dist/commands/followups/watch.js +9 -3
- package/dist/commands/machines/dedicated/create.js +6 -2
- package/dist/commands/machines/dedicated/destroy.js +10 -10
- package/dist/commands/machines/dedicated/exec.js +3 -1
- package/dist/commands/machines/dedicated/price-book.d.ts +17 -0
- package/dist/commands/machines/dedicated/price-book.js +35 -0
- package/dist/commands/tasks/lifecycle/detach.js +26 -5
- package/dist/lib/dedicated-lease-command.d.ts +3 -1
- package/dist/lib/dedicated-lease-command.js +8 -3
- package/dist/lib/dedicated-machines.d.ts +48 -1
- package/dist/lib/dedicated-machines.js +93 -7
- package/dist/lib/dedicated-wait.js +13 -0
- package/dist/lib/followups.d.ts +8 -0
- package/dist/lib/label-ref.d.ts +8 -1
- package/dist/lib/label-ref.js +12 -2
- package/dist/lib/option-hint.d.ts +15 -0
- package/dist/lib/option-hint.js +75 -0
- package/dist/lib/workspaces.d.ts +3 -0
- package/dist/lib/workspaces.js +38 -0
- package/oclif.manifest.json +4952 -4885
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ observability), and shared links.
|
|
|
38
38
|
| **spaces widgets** | `list`, `add`, `remove` |
|
|
39
39
|
| **spaces skills** | `list`, `add`, `remove`, `pin` |
|
|
40
40
|
| **spaces labels** | `list`, `add`, `remove` (attach/detach; filter with `spaces list --label`) |
|
|
41
|
-
| **labels** | `list`, `palette`, `create`, `update`, `archive`, `restore`, `delete`, `reorder` (unified labels across `user
|
|
41
|
+
| **labels** | `list`, `palette`, `create`, `update`, `archive`, `restore`, `delete`, `reorder` (unified labels across `user` and `workspace` scopes) |
|
|
42
42
|
| **chats triggers** | `show`, `set` |
|
|
43
43
|
| **dm** | `list`, `show`, `messages`, `threads`, `thread show/messages/follow/mute/share`, `send` (agent 1:1 main chat and Slack-style Thread controls) |
|
|
44
44
|
| **diagnostics** | `context`, `doctor`, `usage`, `commands` (agent-friendly runtime identity, readiness, progressive token-budgeted discovery, and complete machine-readable schemas) |
|
package/bin/dev-fallback.js
CHANGED
|
@@ -245,6 +245,14 @@ function findAlternateCli({
|
|
|
245
245
|
function missingLocalCommandBuild({ cliRoot, argv, fileSystem = fs, verifySourceIdentity = true }) {
|
|
246
246
|
const commandsDirectory = path.join(cliRoot, 'dist', 'commands');
|
|
247
247
|
const sourceCommandsDirectory = path.join(cliRoot, 'src', 'commands');
|
|
248
|
+
// Staleness is a relation between a build and its SOURCE, so a CLI without
|
|
249
|
+
// source — every installed npm package, which ships `dist` and no `src` — has
|
|
250
|
+
// nothing to be stale against. Every check below is a development-checkout
|
|
251
|
+
// guard. Only the per-command one happened to notice: it looks a source file
|
|
252
|
+
// up first. The `--version` branch did not, read the missing source-digest
|
|
253
|
+
// manifest as a missing build, and made an installed 0.1.38 report whatever
|
|
254
|
+
// other `skrr` was on the PATH, or fail (OSK-9883).
|
|
255
|
+
if (!fileSystem.existsSync(sourceCommandsDirectory)) return null;
|
|
248
256
|
let commandBuildManifest;
|
|
249
257
|
try {
|
|
250
258
|
commandBuildManifest = JSON.parse(
|
package/dist/base-command.js
CHANGED
|
@@ -16,6 +16,7 @@ exports.isTypedServerRefusal = isTypedServerRefusal;
|
|
|
16
16
|
exports.isRetryableFailure = isRetryableFailure;
|
|
17
17
|
const core_1 = require("@oclif/core");
|
|
18
18
|
Object.defineProperty(exports, "Flags", { enumerable: true, get: function () { return core_1.Flags; } });
|
|
19
|
+
const option_hint_1 = require("./lib/option-hint");
|
|
19
20
|
const failed_lookup_1 = require("./lib/failed-lookup");
|
|
20
21
|
// Re-exported from its home in `lib/keychain`, beside the other profile
|
|
21
22
|
// functions. It lived here while `init()` was its only caller; a lib module
|
|
@@ -788,6 +789,13 @@ class BaseCommand extends core_1.Command {
|
|
|
788
789
|
if (retiredHint) {
|
|
789
790
|
err.message = `${err.message}\n\n${retiredHint}`;
|
|
790
791
|
}
|
|
792
|
+
// Sibling domains spell one lifecycle differently (`in_progress` for a
|
|
793
|
+
// space, `active` for a goal), and oclif's enum refusal named neither the
|
|
794
|
+
// difference nor the value to use (OSK-9735).
|
|
795
|
+
const optionHint = (0, option_hint_1.invalidOptionHint)(err.message || '');
|
|
796
|
+
if (optionHint) {
|
|
797
|
+
err.message = `${err.message}\n\n${optionHint}`;
|
|
798
|
+
}
|
|
791
799
|
if (this.wantsJsonOutput()) {
|
|
792
800
|
const exit = err.exitCode ?? err.oclif?.exit ?? 1;
|
|
793
801
|
process.exitCode = exit;
|
|
@@ -64,6 +64,13 @@ export declare function agentCreateMissingFields(payload: Record<string, unknown
|
|
|
64
64
|
* a failed create would be worse than the silence.
|
|
65
65
|
*/
|
|
66
66
|
export declare function unappliedPayloadKeys(sent: Record<string, unknown>, created: unknown, ignore?: readonly string[]): string[];
|
|
67
|
+
/**
|
|
68
|
+
* Why a key can come back missing. Both readings are live: a typo of a real
|
|
69
|
+
* field (`max_turns` for `maxTurns`), and a real field only the server sets
|
|
70
|
+
* (`nativeRole`, `author`). Naming only the first told a caller who had tried
|
|
71
|
+
* to claim a platform role that they had misspelled something.
|
|
72
|
+
*/
|
|
73
|
+
export declare function unappliedPayloadKeysWarning(keys: readonly string[]): string;
|
|
67
74
|
export declare function backgroundLoopLines(agentId: string, relationshipLoopsSkipped: boolean): string[];
|
|
68
75
|
export default class AgentsCreate extends BaseCommand {
|
|
69
76
|
static description: string;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.agentCreateMissingFieldsMessage = agentCreateMissingFieldsMessage;
|
|
4
4
|
exports.agentCreateMissingFields = agentCreateMissingFields;
|
|
5
5
|
exports.unappliedPayloadKeys = unappliedPayloadKeys;
|
|
6
|
+
exports.unappliedPayloadKeysWarning = unappliedPayloadKeysWarning;
|
|
6
7
|
exports.backgroundLoopLines = backgroundLoopLines;
|
|
7
8
|
const core_1 = require("@oclif/core");
|
|
8
9
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
@@ -105,6 +106,17 @@ function unappliedPayloadKeys(sent, created, ignore = []) {
|
|
|
105
106
|
.filter((key) => !Object.prototype.hasOwnProperty.call(back, key))
|
|
106
107
|
.sort();
|
|
107
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* Why a key can come back missing. Both readings are live: a typo of a real
|
|
111
|
+
* field (`max_turns` for `maxTurns`), and a real field only the server sets
|
|
112
|
+
* (`nativeRole`, `author`). Naming only the first told a caller who had tried
|
|
113
|
+
* to claim a platform role that they had misspelled something.
|
|
114
|
+
*/
|
|
115
|
+
function unappliedPayloadKeysWarning(keys) {
|
|
116
|
+
const one = keys.length === 1;
|
|
117
|
+
return (`The server did not keep ${keys.join(', ')}: ${one ? 'it is not an agent field' : 'they are not agent fields'}, ` +
|
|
118
|
+
`or only the server sets ${one ? 'it' : 'them'}. \`skrr agents show <id> --json\` shows what the agent stores.`);
|
|
119
|
+
}
|
|
108
120
|
function backgroundLoopLines(agentId, relationshipLoopsSkipped) {
|
|
109
121
|
if (relationshipLoopsSkipped) {
|
|
110
122
|
return [
|
|
@@ -368,7 +380,7 @@ class AgentsCreate extends base_command_1.BaseCommand {
|
|
|
368
380
|
// Agent document field and is intentionally absent from the response.
|
|
369
381
|
const unapplied = unappliedPayloadKeys(payload, created, ['relationshipLoops']);
|
|
370
382
|
if (unapplied.length > 0) {
|
|
371
|
-
this.warn(
|
|
383
|
+
this.warn(unappliedPayloadKeysWarning(unapplied));
|
|
372
384
|
}
|
|
373
385
|
if (flags.json) {
|
|
374
386
|
this.log(JSON.stringify((0, web_url_1.withAgentUrl)(this.cliConfig.baseURL, created), null, 2));
|
|
@@ -15,6 +15,7 @@ export default class AgentsTriggersPolicy extends BaseCommand {
|
|
|
15
15
|
'max-per-hour': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
16
|
'working-hours': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
17
|
'quiet-hours': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
|
+
'followup-budget': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
19
|
};
|
|
19
20
|
run(): Promise<void>;
|
|
20
21
|
}
|
|
@@ -8,6 +8,28 @@ const triggers_1 = require("../../../lib/triggers");
|
|
|
8
8
|
// used to carry its own copy that stopped one rung short, so `auto_run` was
|
|
9
9
|
// accepted by the API and unreachable from the CLI.
|
|
10
10
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
11
|
+
/** `watch=10 checkin=default` → `{ watch: 10, checkin: null }`; the server validates the pools. */
|
|
12
|
+
function parseFollowUpBudget(values) {
|
|
13
|
+
if (!values || values.length === 0)
|
|
14
|
+
return undefined;
|
|
15
|
+
const budget = {};
|
|
16
|
+
for (const entry of (0, triggers_1.splitRepeated)(values) ?? []) {
|
|
17
|
+
const [pool, raw] = entry.split('=');
|
|
18
|
+
if (!pool || raw === undefined || raw === '') {
|
|
19
|
+
throw new Error(`--followup-budget expects pool=count, got "${entry}"`);
|
|
20
|
+
}
|
|
21
|
+
if (raw === 'default') {
|
|
22
|
+
budget[pool] = null;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const count = Number(raw);
|
|
26
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
27
|
+
throw new Error(`--followup-budget ${pool} must be a non-negative integer or "default"`);
|
|
28
|
+
}
|
|
29
|
+
budget[pool] = count;
|
|
30
|
+
}
|
|
31
|
+
return budget;
|
|
32
|
+
}
|
|
11
33
|
class AgentsTriggersPolicy extends base_command_1.BaseCommand {
|
|
12
34
|
static description = "Show or update an agent's initiative policy for agent-scoped triggers";
|
|
13
35
|
static examples = [
|
|
@@ -38,6 +60,10 @@ class AgentsTriggersPolicy extends base_command_1.BaseCommand {
|
|
|
38
60
|
'max-per-hour': core_1.Flags.integer({ description: 'Agent initiative hourly fire cap' }),
|
|
39
61
|
'working-hours': core_1.Flags.string({ description: 'Policy workingHours JSON object' }),
|
|
40
62
|
'quiet-hours': core_1.Flags.string({ description: 'Policy quietHours JSON object' }),
|
|
63
|
+
'followup-budget': core_1.Flags.string({
|
|
64
|
+
description: 'Daily follow-up creation cap for this agent, as pool=count (pools: watch, checkin, act; count "default" clears it). Repeatable',
|
|
65
|
+
multiple: true,
|
|
66
|
+
}),
|
|
41
67
|
};
|
|
42
68
|
async run() {
|
|
43
69
|
this.requireAuth();
|
|
@@ -52,6 +78,7 @@ class AgentsTriggersPolicy extends base_command_1.BaseCommand {
|
|
|
52
78
|
maxPerHour: flags['max-per-hour'],
|
|
53
79
|
workingHours: (0, triggers_1.parseJsonObject)(flags['working-hours'], '--working-hours'),
|
|
54
80
|
quietHours: (0, triggers_1.parseJsonObject)(flags['quiet-hours'], '--quiet-hours'),
|
|
81
|
+
followUpBudget: parseFollowUpBudget(flags['followup-budget']),
|
|
55
82
|
});
|
|
56
83
|
}
|
|
57
84
|
catch (err) {
|
|
@@ -77,6 +104,11 @@ class AgentsTriggersPolicy extends base_command_1.BaseCommand {
|
|
|
77
104
|
this.log(`Allowed kinds: ${Array.isArray(policy.allowedKinds) ? policy.allowedKinds.join(', ') : '-'}`);
|
|
78
105
|
this.log(`Max fires/day: ${String(policy.maxFiresPerDay ?? '-')}`);
|
|
79
106
|
this.log(`Max/hour: ${String(policy.maxPerHour ?? '-')}`);
|
|
107
|
+
const followUpBudget = policy.followUpBudget;
|
|
108
|
+
const budgetEntries = Object.entries(followUpBudget ?? {}).filter(([, v]) => v != null);
|
|
109
|
+
this.log(`Follow-up caps: ${budgetEntries.length > 0
|
|
110
|
+
? budgetEntries.map(([pool, cap]) => `${pool} ${String(cap)}/day`).join(', ')
|
|
111
|
+
: 'defaults'}`);
|
|
80
112
|
if (Object.keys(counts).length > 0) {
|
|
81
113
|
this.log(`Triggers: ${String(counts.total ?? 0)} total, ${String(counts.enabled ?? 0)} enabled`);
|
|
82
114
|
}
|
|
@@ -186,13 +186,14 @@ class AgentsUpdate extends base_command_1.BaseCommand {
|
|
|
186
186
|
this.handleApiError(err);
|
|
187
187
|
}
|
|
188
188
|
// The same check `agents create` makes. Without it, a key the server does
|
|
189
|
-
// not model
|
|
190
|
-
// was discarded with the same "Updated agent"
|
|
189
|
+
// not model, a typo of one that it does (`max_turns` for `maxTurns`), or a
|
|
190
|
+
// field only the server sets was discarded with the same "Updated agent"
|
|
191
|
+
// line as a real change.
|
|
191
192
|
// `removeProjectIds` is an instruction, not a stored field, so it never
|
|
192
193
|
// comes back and is not reported.
|
|
193
194
|
const unapplied = (0, create_1.unappliedPayloadKeys)(payload, updated, ['removeProjectIds']);
|
|
194
195
|
if (unapplied.length > 0) {
|
|
195
|
-
this.warn(
|
|
196
|
+
this.warn((0, create_1.unappliedPayloadKeysWarning)(unapplied));
|
|
196
197
|
}
|
|
197
198
|
if (flags.json) {
|
|
198
199
|
this.log(JSON.stringify((0, web_url_1.withAgentUrl)(this.cliConfig.baseURL, updated), null, 2));
|
|
@@ -24,7 +24,7 @@ const outcomes = (terminal) => Object.entries(terminal)
|
|
|
24
24
|
* every agent only with `--all-agents`.
|
|
25
25
|
*/
|
|
26
26
|
class FollowupsObservability extends base_command_1.BaseCommand {
|
|
27
|
-
static description = 'Per-agent, per-kind rollup — created, outcomes, escalation, budget, check-in silence';
|
|
27
|
+
static description = 'Per-agent, per-kind rollup — created, outcomes, escalation, spend, budget, check-in silence';
|
|
28
28
|
static examples = [
|
|
29
29
|
'<%= config.bin %> followups observability',
|
|
30
30
|
'<%= config.bin %> followups observability --agent agent_1',
|
|
@@ -93,6 +93,9 @@ class FollowupsObservability extends base_command_1.BaseCommand {
|
|
|
93
93
|
outcomes: outcomes(kind.terminal),
|
|
94
94
|
escalated: rate(kind.escalationRate),
|
|
95
95
|
fires: String(kind.fires),
|
|
96
|
+
spend: kind.spend
|
|
97
|
+
? `$${kind.spend.today.toFixed(2)} / $${kind.spend.total.toFixed(2)}`
|
|
98
|
+
: '—',
|
|
96
99
|
budget: kind.budget ? `${kind.budget.used}/${kind.budget.limit ?? '∞'}` : '—',
|
|
97
100
|
silence: rate(kind.silenceRate),
|
|
98
101
|
})), [
|
|
@@ -104,11 +107,12 @@ class FollowupsObservability extends base_command_1.BaseCommand {
|
|
|
104
107
|
{ key: 'outcomes', header: 'OUTCOMES' },
|
|
105
108
|
{ key: 'escalated', header: 'ESCALATED' },
|
|
106
109
|
{ key: 'fires', header: 'FIRES' },
|
|
110
|
+
{ key: 'spend', header: 'SPENT TODAY/ALL' },
|
|
107
111
|
{ key: 'budget', header: 'TODAY/LIMIT' },
|
|
108
112
|
{ key: 'silence', header: 'SILENT' },
|
|
109
113
|
], (line) => this.log(line));
|
|
110
114
|
});
|
|
111
|
-
this.log(`\nAs of ${report.generatedAt ?? 'now'}. TODAY/LIMIT is the daily budget (reminders are not metered); SILENT is how often a check-in chose to stay quiet.`);
|
|
115
|
+
this.log(`\nAs of ${report.generatedAt ?? 'now'}. SPENT is the agent turns these follow-ups woke (USD, today / all time); TODAY/LIMIT is the daily creation budget (reminders are not metered); SILENT is how often a check-in chose to stay quiet.`);
|
|
112
116
|
}
|
|
113
117
|
}
|
|
114
118
|
exports.default = FollowupsObservability;
|
|
@@ -15,11 +15,14 @@ class FollowupsRemind extends base_command_1.BaseCommand {
|
|
|
15
15
|
static description = 'Remind the owner at a time — the reminder follow-up';
|
|
16
16
|
static examples = [
|
|
17
17
|
'<%= config.bin %> followups remind --subject task:OSK-42 --content "pick the deploy up" --in 2h',
|
|
18
|
-
'<%= config.bin %> followups remind --subject conversation:conv_9 --content "ask how the move went" --at 2026-09-20T09:00:00Z',
|
|
18
|
+
'<%= config.bin %> followups remind --subject conversation:conv_9 --content "ask how the move went" --at 2026-09-20T09:00:00Z --active --agent agent_1',
|
|
19
19
|
'<%= config.bin %> followups remind --subject task:OSK-42 --content "ping me" --at 2026-09-20T09:00:00Z --active --agent agent_1',
|
|
20
20
|
];
|
|
21
21
|
static flags = {
|
|
22
|
-
subject: core_1.Flags.string({
|
|
22
|
+
subject: core_1.Flags.string({
|
|
23
|
+
description: 'Subject ref: type:externalKey. A passive reminder attaches to a task, project, goal or wiki_page; use --active for a conversation',
|
|
24
|
+
required: true,
|
|
25
|
+
}),
|
|
23
26
|
content: core_1.Flags.string({ description: "The reminder's own words", required: true }),
|
|
24
27
|
at: core_1.Flags.string({ description: 'ISO-8601 instant to remind at', exclusive: ['in'] }),
|
|
25
28
|
in: core_1.Flags.string({
|
|
@@ -42,7 +42,8 @@ class FollowupsReschedule extends base_command_1.BaseCommand {
|
|
|
42
42
|
this.log(JSON.stringify(result, null, 2));
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
const next = result.followup?.nextRunAt;
|
|
46
|
+
this.log(`Follow-up ${args.id} rescheduled to ${typeof next === 'string' ? next : at}.`);
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
exports.default = FollowupsReschedule;
|
|
@@ -35,6 +35,10 @@ class FollowupsShow extends base_command_1.BaseCommand {
|
|
|
35
35
|
['intent', row.intentKey],
|
|
36
36
|
['producer', row.producer],
|
|
37
37
|
['terminal', row.terminal ? `${row.terminal} (${row.terminalReason ?? '—'})` : undefined],
|
|
38
|
+
[
|
|
39
|
+
'settled',
|
|
40
|
+
row.terminal ? `${row.terminalAt ?? '—'} by ${row.terminalBy ?? 'unknown'}` : undefined,
|
|
41
|
+
],
|
|
38
42
|
['deadline', row.deadlineAt],
|
|
39
43
|
['escalated to', row.escalatedInitiativeId],
|
|
40
44
|
]) {
|
|
@@ -21,7 +21,11 @@ const triggers_1 = require("../../lib/triggers");
|
|
|
21
21
|
* cannot stop is a leak.
|
|
22
22
|
*/
|
|
23
23
|
class FollowupsWatch extends base_command_1.BaseCommand {
|
|
24
|
-
static
|
|
24
|
+
static summary = 'Watch an asynchronous tail until it settles — the watch follow-up';
|
|
25
|
+
static description = [
|
|
26
|
+
'Two forms, and they cost different amounts. Prefer the predicate form whenever the state is readable over HTTPS: --url + --until is evaluated by the server, wakes the agent once when the condition settles, and always ends at --deadline.',
|
|
27
|
+
"The recipe form (--check / --success / --abort) wakes the agent for one turn per check, and every turn counts against the agent's watch budget. Use it only for a check a predicate cannot express.",
|
|
28
|
+
].join('\n\n');
|
|
25
29
|
static examples = [
|
|
26
30
|
'<%= config.bin %> followups watch --subject task:OSK-42 --why "deploy still running" --url https://ci.example.com/api/build/9 --until \'{"path":"status","op":"equals","value":"done"}\' --deadline 6h',
|
|
27
31
|
'<%= config.bin %> followups watch --subject task:OSK-42 --why "pr review" --check "gh pr view 42 --json state" --success "state == MERGED" --abort "state == CLOSED" --wait event --wait-entity github --wait-event pull_request_merged --wait-fallback 2h',
|
|
@@ -34,7 +38,7 @@ class FollowupsWatch extends base_command_1.BaseCommand {
|
|
|
34
38
|
// predicate form
|
|
35
39
|
url: core_1.Flags.string({ description: 'HTTPS endpoint to observe (predicate form)' }),
|
|
36
40
|
until: core_1.Flags.string({
|
|
37
|
-
description: 'Predicate tree as JSON (predicate form): a leaf {"path","op","value"} or {"all"|"any": [...]}. op is equals, not_equals, contains, starts_with, in, gt, lt, exists, … — an unknown op is refused with the full list',
|
|
41
|
+
description: 'Predicate tree as JSON (predicate form — the server evaluates it; no agent turn per check): a leaf {"path","op","value"} or {"all"|"any": [...]}. op is equals, not_equals, contains, starts_with, in, gt, lt, exists, … — an unknown op is refused with the full list',
|
|
38
42
|
}),
|
|
39
43
|
grant: core_1.Flags.string({ description: 'Grant id supplying the watch credentials' }),
|
|
40
44
|
deadline: core_1.Flags.string({ description: 'When the watch must stop (predicate form, required)' }),
|
|
@@ -43,7 +47,9 @@ class FollowupsWatch extends base_command_1.BaseCommand {
|
|
|
43
47
|
'root-path': core_1.Flags.string({ description: 'JSON root path inside the response' }),
|
|
44
48
|
'resume-prompt': core_1.Flags.string({ description: 'What to tell the settling turn' }),
|
|
45
49
|
// recipe form
|
|
46
|
-
check: core_1.Flags.string({
|
|
50
|
+
check: core_1.Flags.string({
|
|
51
|
+
description: 'The check the agent runs (recipe form) — every check wakes the agent for one turn, counted against the watch budget',
|
|
52
|
+
}),
|
|
47
53
|
success: core_1.Flags.string({ description: 'What state means the wait resolved (recipe form)' }),
|
|
48
54
|
abort: core_1.Flags.string({ description: 'When to stop and escalate (recipe form)' }),
|
|
49
55
|
wait: core_1.Flags.string({
|
|
@@ -72,11 +72,15 @@ class DedicatedMachinesCreate extends dedicated_lease_command_1.DedicatedLeaseCo
|
|
|
72
72
|
if (flags.repository !== undefined && !(0, dedicated_machines_1.isDedicatedRepositoryRef)(flags.repository)) {
|
|
73
73
|
this.error(`--repository must look like owner/repo, got "${flags.repository}".`, { exit: 2 });
|
|
74
74
|
}
|
|
75
|
+
// The server trims the preset, so ' standard' buys a standard machine; the
|
|
76
|
+
// preview must judge the same value, or it calls a size it is about to buy
|
|
77
|
+
// "not in the catalog" and then buys it anyway (OSK-9592).
|
|
78
|
+
const sizePreset = flags['size-preset'].trim();
|
|
75
79
|
const requestId = flags['request-id'] || newRequestId();
|
|
76
80
|
const body = (0, dedicated_machines_1.dedicatedRuntimeCreateInput)({
|
|
77
81
|
requestId,
|
|
78
82
|
displayName: flags.name,
|
|
79
|
-
sizePreset
|
|
83
|
+
sizePreset,
|
|
80
84
|
monthlySpendingLimitCents: flags['spending-limit-cents'],
|
|
81
85
|
regionClass: flags['region-class'],
|
|
82
86
|
storageGb: flags['storage-gb'],
|
|
@@ -93,7 +97,7 @@ class DedicatedMachinesCreate extends dedicated_lease_command_1.DedicatedLeaseCo
|
|
|
93
97
|
const catalog = await this.readCatalogForSummary();
|
|
94
98
|
const summary = (0, dedicated_machines_1.dedicatedCreateSummary)(catalog, {
|
|
95
99
|
name: flags.name,
|
|
96
|
-
sizePreset
|
|
100
|
+
sizePreset,
|
|
97
101
|
storageGb: flags['storage-gb'],
|
|
98
102
|
retentionClass: flags['retention-class'],
|
|
99
103
|
spendingLimitCents: flags['spending-limit-cents'],
|
|
@@ -73,16 +73,16 @@ class DedicatedMachinesDestroy extends dedicated_lease_command_1.DedicatedLeaseC
|
|
|
73
73
|
else if (flags['retain-storage'])
|
|
74
74
|
retentionIntent = 'retain';
|
|
75
75
|
const response = await this.callDedicated(() => (0, dedicated_machines_1.destroyDedicatedRuntime)(leaseId, { ...this.scopeFrom(flags), retentionIntent }));
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
76
|
+
const note = (0, dedicated_machines_1.dedicatedDestroyRetentionNote)(prior, {
|
|
77
|
+
purge: Boolean(flags['purge-storage']),
|
|
78
|
+
retain: Boolean(flags['retain-storage']),
|
|
79
|
+
bin: this.config.bin,
|
|
80
|
+
});
|
|
81
|
+
// A script reading --json is exactly the caller that never sees a printed
|
|
82
|
+
// line, so the kept-and-billing fact rides in the document too (OSK-9593).
|
|
83
|
+
await this.reportLeaseAction(response, flags, { kind: 'destroyed' }, 'Destroying', note ? { storageNote: note } : {});
|
|
84
|
+
if (!flags.json && note)
|
|
85
|
+
this.log(note);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
88
|
exports.default = DedicatedMachinesDestroy;
|
|
@@ -55,6 +55,8 @@ class DedicatedMachinesExec extends base_command_1.BaseCommand {
|
|
|
55
55
|
// What a script depends on, in the help a script author reads (OSK-8703).
|
|
56
56
|
static description = [
|
|
57
57
|
'Runs the command once, as the workload user, in /workspace or --cwd, through a shell with no TTY. ' +
|
|
58
|
+
'The workload user can read /workspace but writes only under user-data/ (its HOME) and repos/, so a ' +
|
|
59
|
+
'command that writes to its starting directory needs --cwd user-data or a path under one of those. ' +
|
|
58
60
|
'Give it after -- as words (each one quoted for you), or as one shell string with --command; not both. ' +
|
|
59
61
|
'The agent shell policy does not apply. A start whose answer is lost is followed up under the same ' +
|
|
60
62
|
'request id, never run a second time.',
|
|
@@ -95,7 +97,7 @@ class DedicatedMachinesExec extends base_command_1.BaseCommand {
|
|
|
95
97
|
description: 'Command to run as one shell string, instead of the words after --',
|
|
96
98
|
}),
|
|
97
99
|
cwd: core_1.Flags.string({
|
|
98
|
-
description: 'Working directory, relative to /workspace (for example repos/app)',
|
|
100
|
+
description: 'Working directory, relative to /workspace (for example repos/app or user-data). /workspace itself is not writable by the workload user.',
|
|
99
101
|
}),
|
|
100
102
|
timeout: core_1.Flags.string({
|
|
101
103
|
description: `How long to let it run, such as 30s or 5m (default 2m, max ${dedicated_machines_1.DEDICATED_EXEC_MAX_TIMEOUT_MS / 60000}m). The process group is killed at the deadline.`,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DedicatedLeaseCommand } from '../../../lib/dedicated-lease-command';
|
|
2
|
+
/**
|
|
3
|
+
* `skrr machines dedicated price-book` — the operator's answer to "under what
|
|
4
|
+
* policy, and at what margin, is each tier sold".
|
|
5
|
+
*
|
|
6
|
+
* `catalog` is the customer view: prices and the book version, never a cost or
|
|
7
|
+
* a policy. This is admin-only on the server, because costs and margins are not
|
|
8
|
+
* customer facts.
|
|
9
|
+
*/
|
|
10
|
+
export default class DedicatedMachinesPriceBook extends DedicatedLeaseCommand {
|
|
11
|
+
static description: string;
|
|
12
|
+
static examples: string[];
|
|
13
|
+
static flags: {
|
|
14
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const dedicated_lease_command_1 = require("../../../lib/dedicated-lease-command");
|
|
5
|
+
const dedicated_machines_1 = require("../../../lib/dedicated-machines");
|
|
6
|
+
/**
|
|
7
|
+
* `skrr machines dedicated price-book` — the operator's answer to "under what
|
|
8
|
+
* policy, and at what margin, is each tier sold".
|
|
9
|
+
*
|
|
10
|
+
* `catalog` is the customer view: prices and the book version, never a cost or
|
|
11
|
+
* a policy. This is admin-only on the server, because costs and margins are not
|
|
12
|
+
* customer facts.
|
|
13
|
+
*/
|
|
14
|
+
class DedicatedMachinesPriceBook extends dedicated_lease_command_1.DedicatedLeaseCommand {
|
|
15
|
+
static description = 'Show the Dedicated Runtime price book an operator audits: version, pricing policy, and each tier rate, cost and margin (admin only)';
|
|
16
|
+
static examples = [
|
|
17
|
+
'<%= config.bin %> machines dedicated price-book',
|
|
18
|
+
'<%= config.bin %> machines dedicated price-book --json',
|
|
19
|
+
];
|
|
20
|
+
static flags = {
|
|
21
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
22
|
+
};
|
|
23
|
+
async run() {
|
|
24
|
+
this.requireAuth();
|
|
25
|
+
const { flags } = await this.parse(DedicatedMachinesPriceBook);
|
|
26
|
+
const read = await this.callDedicated(() => (0, dedicated_machines_1.getDedicatedOperatorPriceBook)());
|
|
27
|
+
if (flags.json) {
|
|
28
|
+
this.log(JSON.stringify(read, null, 2));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
for (const line of (0, dedicated_machines_1.dedicatedOperatorPriceBookLines)(read))
|
|
32
|
+
this.log(line);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.default = DedicatedMachinesPriceBook;
|
|
@@ -43,11 +43,26 @@ class TasksLifecycleDetach extends base_command_1.BaseCommand {
|
|
|
43
43
|
return this.log('No lifecycle is attached to this Task — nothing to detach.');
|
|
44
44
|
this.log(`Would detach ${lifecycle.packId ? `pack ${lifecycle.packId}` : `definition ${lifecycle.definitionId}`}` +
|
|
45
45
|
` at generation ${lifecycle.generation}, stage ${lifecycle.currentStageKey ?? '—'}.`);
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
if (lifecycle.runId) {
|
|
47
|
+
this.log(' The run is cancelled and kept as history; nothing already recorded is removed.');
|
|
48
|
+
this.log(' The generation is bumped, so no in-flight writer from the old run can land.');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.log(' It has not started, so there is no run to cancel.');
|
|
52
|
+
}
|
|
48
53
|
this.log(' Re-attaching afterwards starts a NEW generation — it does not resume this one.');
|
|
49
54
|
return;
|
|
50
55
|
}
|
|
56
|
+
// The server clears runId on detach, so whether there WAS a run to cancel
|
|
57
|
+
// is only knowable before the write. Best-effort: a failed read must not
|
|
58
|
+
// block the detach, it only costs the sentence its precision.
|
|
59
|
+
let hadRun;
|
|
60
|
+
try {
|
|
61
|
+
hadRun = Boolean((await data_provider_1.dataService.getTaskLifecycle(taskId)).lifecycle?.runId);
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
hadRun = undefined;
|
|
65
|
+
}
|
|
51
66
|
let response;
|
|
52
67
|
try {
|
|
53
68
|
response = await data_provider_1.dataService.detachTaskLifecycle({
|
|
@@ -62,9 +77,15 @@ class TasksLifecycleDetach extends base_command_1.BaseCommand {
|
|
|
62
77
|
return this.log(JSON.stringify(response, null, 2));
|
|
63
78
|
const lifecycle = response.lifecycle;
|
|
64
79
|
// Name what changed. "Detached lifecycle from the Task." left the reader to
|
|
65
|
-
// diff the state afterwards to learn what had happened
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
// diff the state afterwards to learn what had happened — and saying a run
|
|
81
|
+
// was cancelled when none had started is the same problem the other way.
|
|
82
|
+
const runSentence = hadRun === false
|
|
83
|
+
? 'it had not started, so there was no run to cancel'
|
|
84
|
+
: hadRun
|
|
85
|
+
? 'the run is cancelled and kept as history'
|
|
86
|
+
: 'any run is cancelled and kept as history';
|
|
87
|
+
this.log(`Detached${lifecycle?.packId ? ` pack ${lifecycle.packId}` : ''} — ${runSentence},` +
|
|
88
|
+
` and the binding is now generation ${lifecycle?.generation ?? '?'}` +
|
|
68
89
|
` (${lifecycle?.status ?? 'detached'}).`);
|
|
69
90
|
this.log('Re-attach with: skrr tasks lifecycle attach <task-id> --pack <pack>');
|
|
70
91
|
}
|
|
@@ -61,7 +61,9 @@ export declare abstract class DedicatedLeaseCommand extends BaseCommand {
|
|
|
61
61
|
timeout?: string;
|
|
62
62
|
interval?: number;
|
|
63
63
|
workspace?: string;
|
|
64
|
-
}, goal: DedicatedWaitGoal | null, verb: string
|
|
64
|
+
}, goal: DedicatedWaitGoal | null, verb: string,
|
|
65
|
+
/** Fields a `--json` caller needs that the server response does not carry. */
|
|
66
|
+
jsonExtra?: Record<string, unknown>): Promise<void>;
|
|
65
67
|
}
|
|
66
68
|
/** The same transient verdict BaseCommand gives an ordinary API call. */
|
|
67
69
|
export declare function dedicatedRuntimeApiErrorRetryable(err: unknown): boolean;
|
|
@@ -127,7 +127,10 @@ class DedicatedLeaseCommand extends base_command_1.BaseCommand {
|
|
|
127
127
|
return await fn();
|
|
128
128
|
}
|
|
129
129
|
catch (err) {
|
|
130
|
-
const message = (0, dedicated_machines_1.formatDedicatedRuntimeApiError)(err
|
|
130
|
+
const message = (0, dedicated_machines_1.formatDedicatedRuntimeApiError)(err, {
|
|
131
|
+
command: this.id,
|
|
132
|
+
bin: this.config.bin,
|
|
133
|
+
});
|
|
131
134
|
if (message) {
|
|
132
135
|
this.failWithCliError({
|
|
133
136
|
message,
|
|
@@ -145,7 +148,9 @@ class DedicatedLeaseCommand extends base_command_1.BaseCommand {
|
|
|
145
148
|
* Print the lease an action returned, or wait for its operation to finish
|
|
146
149
|
* first when `--wait` was passed.
|
|
147
150
|
*/
|
|
148
|
-
async reportLeaseAction(response, flags, goal, verb
|
|
151
|
+
async reportLeaseAction(response, flags, goal, verb,
|
|
152
|
+
/** Fields a `--json` caller needs that the server response does not carry. */
|
|
153
|
+
jsonExtra = {}) {
|
|
149
154
|
let lease = response?.lease;
|
|
150
155
|
const id = (0, dedicated_machines_1.dedicatedLeaseId)(lease);
|
|
151
156
|
if (flags.wait && goal && id) {
|
|
@@ -201,7 +206,7 @@ class DedicatedLeaseCommand extends base_command_1.BaseCommand {
|
|
|
201
206
|
}
|
|
202
207
|
}
|
|
203
208
|
if (flags.json) {
|
|
204
|
-
this.log(JSON.stringify({ ...(response || {}), lease }, null, 2));
|
|
209
|
+
this.log(JSON.stringify({ ...(response || {}), lease, ...jsonExtra }, null, 2));
|
|
205
210
|
return;
|
|
206
211
|
}
|
|
207
212
|
this.log(`Dedicated Runtime ${id || 'unknown'} ${(0, dedicated_machines_1.dedicatedLeaseState)(lease)}.`);
|
|
@@ -221,6 +221,46 @@ export declare function dedicatedRuntimeCreateInput(input: {
|
|
|
221
221
|
*/
|
|
222
222
|
export declare function isDedicatedRepositoryRef(value: string): boolean;
|
|
223
223
|
export declare function createDedicatedRuntime(input: CreateDedicatedRuntimeInput): Promise<DedicatedRuntimeResponse>;
|
|
224
|
+
/** One offered tier as the operator price book checks it. */
|
|
225
|
+
export interface DedicatedPriceBookPresetCheck {
|
|
226
|
+
sizePreset: string;
|
|
227
|
+
customerRateCentsPerHour: number | null;
|
|
228
|
+
providerCostCentsPerHour: number | null;
|
|
229
|
+
grossMarginBps: number | null;
|
|
230
|
+
priced: boolean;
|
|
231
|
+
providerCostRecorded: boolean;
|
|
232
|
+
marginTargetMet: boolean;
|
|
233
|
+
}
|
|
234
|
+
/** `GET /api/machines/dedicated/price-book` — admin only. */
|
|
235
|
+
export interface DedicatedOperatorPriceBook {
|
|
236
|
+
timestamp?: string;
|
|
237
|
+
availability: 'available' | 'disabled';
|
|
238
|
+
disabledReason: string | null;
|
|
239
|
+
configError: string | null;
|
|
240
|
+
priceBook: {
|
|
241
|
+
required: boolean;
|
|
242
|
+
approved: boolean;
|
|
243
|
+
version: string | null;
|
|
244
|
+
pricingPolicyRef: string | null;
|
|
245
|
+
targetGrossMarginBps: number | null;
|
|
246
|
+
missingReason: string | null;
|
|
247
|
+
presetChecks: DedicatedPriceBookPresetCheck[];
|
|
248
|
+
};
|
|
249
|
+
sizeAvailability: Array<{
|
|
250
|
+
id: string;
|
|
251
|
+
offered: boolean;
|
|
252
|
+
withheldReason: string | null;
|
|
253
|
+
}>;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* The Dedicated Runtime price book as an operator reads it: which version and
|
|
257
|
+
* which pricing policy, and each tier's rate, cost and margin. The customer
|
|
258
|
+
* catalog carries none of the policy or cost facts, and the admin plane that
|
|
259
|
+
* also serves this is not deployed in production (OSK-9562).
|
|
260
|
+
*/
|
|
261
|
+
export declare function getDedicatedOperatorPriceBook(): Promise<DedicatedOperatorPriceBook>;
|
|
262
|
+
/** Human lines for `machines dedicated price-book`. Pure, so it can be pinned. */
|
|
263
|
+
export declare function dedicatedOperatorPriceBookLines(read: DedicatedOperatorPriceBook): string[];
|
|
224
264
|
export declare function listDedicatedRuntimes(options?: {
|
|
225
265
|
limit?: number;
|
|
226
266
|
state?: string;
|
|
@@ -603,5 +643,12 @@ export declare function dedicatedRuntimeApiErrorCode(err: unknown): string | und
|
|
|
603
643
|
* second list would only drift from the first.
|
|
604
644
|
*/
|
|
605
645
|
export declare function dedicatedRuntimeApiErrorDetails(err: unknown): Record<string, unknown> | undefined;
|
|
606
|
-
|
|
646
|
+
/** What a refused command knows about itself, so advice can be one it can follow. */
|
|
647
|
+
export interface DedicatedApiErrorContext {
|
|
648
|
+
/** oclif command id, e.g. `machines:dedicated:create`. */
|
|
649
|
+
command?: string;
|
|
650
|
+
bin?: string;
|
|
651
|
+
leaseId?: string;
|
|
652
|
+
}
|
|
653
|
+
export declare function formatDedicatedRuntimeApiError(err: unknown, context?: DedicatedApiErrorContext): string | null;
|
|
607
654
|
export {};
|