@skrr-ai/cli 0.1.43 → 0.1.44
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.d.ts +2 -0
- package/dist/base-command.js +1 -0
- package/dist/commands/balance/index.js +1 -1
- package/dist/commands/balance/show.d.ts +32 -0
- package/dist/commands/balance/show.js +74 -3
- package/dist/commands/code/handover.d.ts +1 -0
- package/dist/commands/code/handover.js +4 -0
- package/dist/commands/code/jobs/run.d.ts +1 -0
- package/dist/commands/code/jobs/run.js +4 -0
- package/dist/commands/daemon/install.js +7 -0
- package/dist/commands/harnesses/leases/show.js +14 -0
- package/dist/commands/instructions/install.d.ts +22 -0
- package/dist/commands/instructions/install.js +83 -7
- package/dist/commands/instructions/list.js +5 -0
- package/dist/commands/instructions/show.d.ts +6 -0
- package/dist/commands/instructions/show.js +34 -1
- package/dist/commands/instructions/status.js +17 -1
- package/dist/commands/payments/wallet.js +2 -2
- package/dist/commands/tasks/create.d.ts +6 -0
- package/dist/commands/tasks/create.js +26 -3
- package/dist/commands/tasks/labels/attach.js +4 -0
- package/dist/commands/tasks/list.d.ts +1 -0
- package/dist/commands/tasks/list.js +9 -0
- package/dist/commands/tasks/show.d.ts +23 -0
- package/dist/commands/tasks/show.js +61 -1
- package/dist/commands/tasks/update.js +12 -0
- package/dist/commands/views/create.js +12 -2
- package/dist/commands/views/list.js +3 -2
- package/dist/commands/views/show.js +2 -0
- package/dist/lib/agentic-stream.d.ts +10 -4
- package/dist/lib/agentic-stream.js +25 -11
- package/dist/lib/cli-installers.js +9 -1
- package/dist/lib/daemon-setup.d.ts +18 -1
- package/dist/lib/daemon-setup.js +33 -1
- package/dist/lib/first-party-harness-agent.d.ts +16 -1
- package/dist/lib/first-party-harness-agent.js +41 -12
- package/dist/lib/first-party-harness-doctor.js +34 -16
- package/dist/lib/first-party-harness.d.ts +18 -11
- package/dist/lib/first-party-harness.js +26 -21
- package/dist/lib/harnesses.d.ts +6 -0
- package/dist/lib/instruction-input.d.ts +21 -0
- package/dist/lib/instruction-input.js +30 -0
- package/dist/lib/instruction-provenance.d.ts +54 -0
- package/dist/lib/instruction-provenance.js +84 -0
- package/dist/lib/task-view-render.d.ts +14 -0
- package/dist/lib/task-view-render.js +55 -0
- package/dist/lib/tasks.d.ts +18 -0
- package/dist/lib/tasks.js +22 -1
- package/dist/lib/views/vocabulary.d.ts +1 -1
- package/dist/lib/views/vocabulary.js +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +75 -24
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +143 -34
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/legacyStatePreflight.d.ts +21 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/legacyStatePreflight.js +75 -19
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +75 -24
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +138 -34
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/legacyStatePreflight.d.ts +21 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/legacyStatePreflight.js +74 -19
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3516 -3513
- package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
- package/oclif.manifest.json +21649 -21626
- package/package.json +3 -3
package/dist/base-command.d.ts
CHANGED
|
@@ -346,6 +346,8 @@ export declare function parseGlobalFlags(argv: readonly string[]): ParsedGlobals
|
|
|
346
346
|
export declare function apiRefusal(err: unknown): {
|
|
347
347
|
code?: string;
|
|
348
348
|
message?: string;
|
|
349
|
+
/** The parsed JSON body, for a refusal that carries structured context. */
|
|
350
|
+
body?: Record<string, unknown>;
|
|
349
351
|
};
|
|
350
352
|
/**
|
|
351
353
|
* Prefix a failure message with the resource it is about.
|
package/dist/base-command.js
CHANGED
|
@@ -18,7 +18,7 @@ const show_1 = __importDefault(require("./show"));
|
|
|
18
18
|
* lists every subcommand, which is where a menu belongs.
|
|
19
19
|
*/
|
|
20
20
|
class Balance extends show_1.default {
|
|
21
|
-
static description = 'Show your hosted usage balance — the
|
|
21
|
+
static description = 'Show your hosted usage balance and plan limits — the ceilings that refuse a turn (not the x402 wallet). ' +
|
|
22
22
|
'See `balance usage`, `balance overage`, `balance statement` and `balance history` for the detail behind it.';
|
|
23
23
|
static examples = [
|
|
24
24
|
'<%= config.bin %> balance',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { TSkyCodeUsageSnapshot } from '@skrr-ai/data-provider';
|
|
1
2
|
import { BaseCommand } from '../../base-command';
|
|
2
3
|
import { type RuntimeMonthlySpend } from '../../lib/harnesses';
|
|
3
4
|
/**
|
|
@@ -7,6 +8,30 @@ import { type RuntimeMonthlySpend } from '../../lib/harnesses';
|
|
|
7
8
|
* the wording is part of the observable contract and is pinned directly.
|
|
8
9
|
*/
|
|
9
10
|
export declare function renderRuntimeSpend(spend: RuntimeMonthlySpend[], log: (line: string) => void): void;
|
|
11
|
+
/**
|
|
12
|
+
* Your plan's ceilings, as lines.
|
|
13
|
+
*
|
|
14
|
+
* This screen showed the credits and the spend cap under a footer that called
|
|
15
|
+
* them "the ceiling that refuses a turn" — while the plan's monthly allowance,
|
|
16
|
+
* its five-hour window and its concurrent-session limit, each of which refuses a
|
|
17
|
+
* turn with its own message ("plan allowance is used up", "five-hour usage
|
|
18
|
+
* window is used up", "concurrent-session limit is reached"), were only on
|
|
19
|
+
* `balance usage` (OSK-4638). A refusal that names a ceiling should find that
|
|
20
|
+
* ceiling here, in the same words.
|
|
21
|
+
*
|
|
22
|
+
* The allowance is spent before the credits above; past it, overage consent
|
|
23
|
+
* decides whether the credits pay or the turn is refused. The five-hour window
|
|
24
|
+
* and the session limit are enforced together or not at all (admission needs
|
|
25
|
+
* both to be set), and what is left of the window is what admission computes:
|
|
26
|
+
* limit less used less reserved.
|
|
27
|
+
*
|
|
28
|
+
* Personal only. The workspace read does not report the workspace's overage
|
|
29
|
+
* terms, so rendering this for a workspace would state a refusal rule it cannot
|
|
30
|
+
* see.
|
|
31
|
+
*
|
|
32
|
+
* Pure so the wording is pinned directly, like `renderRuntimeSpend`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function renderPlanCeilings(usage: TSkyCodeUsageSnapshot, log: (line: string) => void, bin?: string): void;
|
|
10
35
|
/**
|
|
11
36
|
* YOUR hosted usage balance — the personal account, and only that one.
|
|
12
37
|
*
|
|
@@ -41,6 +66,13 @@ export default class BalanceShow extends BaseCommand {
|
|
|
41
66
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
42
67
|
};
|
|
43
68
|
run(): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* Your plan's ceilings, beside the balance they are spent before.
|
|
71
|
+
*
|
|
72
|
+
* Best-effort, like the runtime cap below it: a failed read prints one line
|
|
73
|
+
* and leaves the balance intact rather than failing the command.
|
|
74
|
+
*/
|
|
75
|
+
private renderPlanCeilings;
|
|
44
76
|
/**
|
|
45
77
|
* The OTHER cap — the one that actually refuses a Hosted Machine.
|
|
46
78
|
*
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.renderRuntimeSpend = renderRuntimeSpend;
|
|
4
|
+
exports.renderPlanCeilings = renderPlanCeilings;
|
|
4
5
|
const core_1 = require("@oclif/core");
|
|
6
|
+
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
5
7
|
const base_command_1 = require("../../base-command");
|
|
6
8
|
const balance_1 = require("../../lib/balance");
|
|
7
9
|
const workspaces_1 = require("../../lib/workspaces");
|
|
@@ -35,6 +37,52 @@ function renderRuntimeSpend(spend, log) {
|
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Your plan's ceilings, as lines.
|
|
42
|
+
*
|
|
43
|
+
* This screen showed the credits and the spend cap under a footer that called
|
|
44
|
+
* them "the ceiling that refuses a turn" — while the plan's monthly allowance,
|
|
45
|
+
* its five-hour window and its concurrent-session limit, each of which refuses a
|
|
46
|
+
* turn with its own message ("plan allowance is used up", "five-hour usage
|
|
47
|
+
* window is used up", "concurrent-session limit is reached"), were only on
|
|
48
|
+
* `balance usage` (OSK-4638). A refusal that names a ceiling should find that
|
|
49
|
+
* ceiling here, in the same words.
|
|
50
|
+
*
|
|
51
|
+
* The allowance is spent before the credits above; past it, overage consent
|
|
52
|
+
* decides whether the credits pay or the turn is refused. The five-hour window
|
|
53
|
+
* and the session limit are enforced together or not at all (admission needs
|
|
54
|
+
* both to be set), and what is left of the window is what admission computes:
|
|
55
|
+
* limit less used less reserved.
|
|
56
|
+
*
|
|
57
|
+
* Personal only. The workspace read does not report the workspace's overage
|
|
58
|
+
* terms, so rendering this for a workspace would state a refusal rule it cannot
|
|
59
|
+
* see.
|
|
60
|
+
*
|
|
61
|
+
* Pure so the wording is pinned directly, like `renderRuntimeSpend`.
|
|
62
|
+
*/
|
|
63
|
+
function renderPlanCeilings(usage, log, bin = 'skrr') {
|
|
64
|
+
const resets = (at) => (at ? ` — resets ${(0, balance_1.formatBalanceTimestamp)(at)}` : '');
|
|
65
|
+
const window = usage.fiveHour;
|
|
66
|
+
log('');
|
|
67
|
+
log(`${auth_core_1.FIRST_PARTY_HARNESS.displayName} ${usage.plan ?? 'current'} plan — spent before the credits, and each limit refuses a turn:`);
|
|
68
|
+
log(` Plan monthly: ${(0, balance_1.formatCreditsWithUsd)(usage.monthly.remainingUsageMicros)} left${resets(usage.monthly.resetsAt)}`);
|
|
69
|
+
if (window.limitUsageMicros > 0 && window.maxConcurrentSessions > 0) {
|
|
70
|
+
const windowLeft = Math.max(0, window.limitUsageMicros - window.usedUsageMicros - window.reservedUsageMicros);
|
|
71
|
+
log(` Plan 5-hour: ${(0, balance_1.formatCreditsWithUsd)(windowLeft)} left of ${(0, balance_1.formatCreditsWithUsd)(window.limitUsageMicros)}${resets(window.resetsAt)}`);
|
|
72
|
+
log(` Plan sessions: ${window.activeSessions} of ${window.maxConcurrentSessions} running`);
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
log(' Plan 5-hour: no five-hour or concurrent-session limit');
|
|
76
|
+
}
|
|
77
|
+
log(usage.overageEnabled
|
|
78
|
+
? ' Past the plan: paid from the credits (overage on)'
|
|
79
|
+
: ` Past the plan: refused — overage is off (\`${bin} balance overage enabled\`)`);
|
|
80
|
+
if (usage.partialReasons?.includes('missing_entitlement_period')) {
|
|
81
|
+
// Catalog defaults, not this account's period: say so rather than present
|
|
82
|
+
// them as measured.
|
|
83
|
+
log(' (plan period unavailable; catalog limits shown)');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
38
86
|
/**
|
|
39
87
|
* YOUR hosted usage balance — the personal account, and only that one.
|
|
40
88
|
*
|
|
@@ -171,17 +219,40 @@ class BalanceShow extends base_command_1.BaseCommand {
|
|
|
171
219
|
else {
|
|
172
220
|
this.log(' Auto-refill: off');
|
|
173
221
|
}
|
|
222
|
+
await this.renderPlanCeilings();
|
|
174
223
|
await this.renderRuntimeSpend();
|
|
175
224
|
this.log('');
|
|
176
225
|
this.log('A credit is a micro-dollar: 1,000,000 credits = $1.00. The spend cap');
|
|
177
226
|
this.log('is a per-period ceiling, not a second balance. `skrr balance statement`');
|
|
178
227
|
this.log('itemises this same account and says what each charge was for.');
|
|
179
228
|
this.log('');
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
229
|
+
// Plural, because they are: a turn you own can be refused by the credits,
|
|
230
|
+
// the spend cap, a plan limit or a hosted runtime's monthly cap, and the
|
|
231
|
+
// refusal names which (OSK-4638).
|
|
232
|
+
this.log('These are the ceilings that refuse a turn for work you own; a refusal');
|
|
233
|
+
this.log('names the one that ran out. A session carrying a workspace is refused');
|
|
234
|
+
this.log("against the WORKSPACE's balance instead — see");
|
|
235
|
+
this.log('`skrr balance show --workspace <id>`. `skrr payments wallet` is the');
|
|
183
236
|
this.log('separate x402 crypto wallet, in USDC — not this balance.');
|
|
184
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Your plan's ceilings, beside the balance they are spent before.
|
|
240
|
+
*
|
|
241
|
+
* Best-effort, like the runtime cap below it: a failed read prints one line
|
|
242
|
+
* and leaves the balance intact rather than failing the command.
|
|
243
|
+
*/
|
|
244
|
+
async renderPlanCeilings() {
|
|
245
|
+
let usage;
|
|
246
|
+
try {
|
|
247
|
+
usage = await (0, balance_1.getSkyCodeUsage)();
|
|
248
|
+
}
|
|
249
|
+
catch {
|
|
250
|
+
this.log('');
|
|
251
|
+
this.log(` Plan limits: unavailable (\`${this.config.bin} balance usage\` to retry)`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
renderPlanCeilings(usage, (line) => this.log(line), this.config.bin);
|
|
255
|
+
}
|
|
185
256
|
/**
|
|
186
257
|
* The OTHER cap — the one that actually refuses a Hosted Machine.
|
|
187
258
|
*
|
|
@@ -37,6 +37,7 @@ export default class CodeHandover extends BaseCommand {
|
|
|
37
37
|
repo: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
38
38
|
base: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
39
39
|
'allow-dirty': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
40
|
+
browser: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
40
41
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
41
42
|
};
|
|
42
43
|
/** Run git, returning null rather than throwing — every caller has a fallback. */
|
|
@@ -61,6 +61,9 @@ class CodeHandover extends base_command_1.BaseCommand {
|
|
|
61
61
|
description: 'Hand over anyway when the working tree is dirty or the branch is unpushed. The cloud agent will NOT see that work.',
|
|
62
62
|
default: false,
|
|
63
63
|
}),
|
|
64
|
+
browser: core_1.Flags.boolean({
|
|
65
|
+
description: 'Give the receiving agent a browser. Refused where the deployment has no browser runtime, never run without one.',
|
|
66
|
+
}),
|
|
64
67
|
json: core_1.Flags.boolean({ description: 'Output the created task as JSON' }),
|
|
65
68
|
};
|
|
66
69
|
/** Run git, returning null rather than throwing — every caller has a fallback. */
|
|
@@ -152,6 +155,7 @@ class CodeHandover extends base_command_1.BaseCommand {
|
|
|
152
155
|
baseBranch: branch,
|
|
153
156
|
prompt,
|
|
154
157
|
machineProduct: 'hosted-job-machine',
|
|
158
|
+
...(flags.browser ? { browser: true } : {}),
|
|
155
159
|
clientRequestId: (0, node_crypto_1.randomUUID)(),
|
|
156
160
|
});
|
|
157
161
|
}
|
|
@@ -22,6 +22,7 @@ export default class CodeJobsRun extends BaseCommand {
|
|
|
22
22
|
prompt: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
23
|
'prompt-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
24
24
|
egress: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
25
|
+
browser: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
25
26
|
wait: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
26
27
|
interval: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
27
28
|
timeout: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -40,6 +40,9 @@ class CodeJobsRun extends base_command_1.BaseCommand {
|
|
|
40
40
|
description: 'Network reach. Default `isolated` — the control plane only, nothing else.',
|
|
41
41
|
options: ['isolated', 'allowlist', 'open'],
|
|
42
42
|
}),
|
|
43
|
+
browser: core_1.Flags.boolean({
|
|
44
|
+
description: 'Run the job with a browser. Refused where the deployment has no browser runtime, never run without one.',
|
|
45
|
+
}),
|
|
43
46
|
wait: core_1.Flags.boolean({ description: 'Wait for the job to finish and print its result' }),
|
|
44
47
|
interval: core_1.Flags.integer({ description: 'Poll interval in seconds while waiting', default: 5 }),
|
|
45
48
|
timeout: core_1.Flags.integer({ description: 'Give up waiting after this many minutes', default: 30 }),
|
|
@@ -88,6 +91,7 @@ class CodeJobsRun extends base_command_1.BaseCommand {
|
|
|
88
91
|
mode: 'bare',
|
|
89
92
|
machineProduct: 'hosted-job-machine',
|
|
90
93
|
...(flags.egress ? { egressPolicy: flags.egress } : {}),
|
|
94
|
+
...(flags.browser ? { browser: true } : {}),
|
|
91
95
|
clientRequestId: (0, node_crypto_1.randomUUID)(),
|
|
92
96
|
});
|
|
93
97
|
task = res?.task;
|
|
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
|
|
|
4
4
|
const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
|
|
5
5
|
const daemon_binding_1 = require("../../lib/daemon-binding");
|
|
6
6
|
const daemon_installer_1 = require("../../lib/daemon-installer");
|
|
7
|
+
const daemon_setup_1 = require("../../lib/daemon-setup");
|
|
7
8
|
/**
|
|
8
9
|
* `skrr daemon install` — install the local runtime's OS service.
|
|
9
10
|
*
|
|
@@ -52,6 +53,12 @@ class DaemonInstall extends core_1.Command {
|
|
|
52
53
|
// own comment records `skrr code run -m <model>` dying the same way. Same
|
|
53
54
|
// mistake, one directory over.
|
|
54
55
|
const argv = this.argv;
|
|
56
|
+
// Before fetching anything: a machine still carrying the previous product
|
|
57
|
+
// identity's state is refused by the runtime's own install, so downloading a
|
|
58
|
+
// release first only delays the same refusal (OSK-6895).
|
|
59
|
+
if ((0, daemon_setup_1.refuseOnLegacyLocalState)((line) => this.logToStderr(line))) {
|
|
60
|
+
this.exit(1);
|
|
61
|
+
}
|
|
55
62
|
// Bootstrap only when there is nothing to forward to. An existing runtime —
|
|
56
63
|
// brew, the script installer, a dev checkout — is left exactly alone; this
|
|
57
64
|
// is a floor under the missing case, not a second update channel competing
|
|
@@ -82,6 +82,20 @@ class HarnessesLeasesShow extends base_command_1.BaseCommand {
|
|
|
82
82
|
line('Updated:', lease.updatedAt);
|
|
83
83
|
// Money last, and only when the server sent it — an absent billing block
|
|
84
84
|
// and a zero are different states.
|
|
85
|
+
//
|
|
86
|
+
// Who pays comes first. `harnesses leases billing` used to be "change who
|
|
87
|
+
// pays", so there was a way to move the payer (which never worked) and no
|
|
88
|
+
// way to read it (OSK-4628). The payer is fixed at create, and the lease
|
|
89
|
+
// carries it as `workspaceId`: the workspace account it bills, or `null`
|
|
90
|
+
// for the owner's personal account — the same scope the monthly cap below
|
|
91
|
+
// is counted in. A server that does not send the key says nothing here,
|
|
92
|
+
// because a missing field is not evidence of a personal lease.
|
|
93
|
+
if (Object.prototype.hasOwnProperty.call(lease, 'workspaceId')) {
|
|
94
|
+
const workspaceId = typeof lease.workspaceId === 'string' ? lease.workspaceId.trim() : '';
|
|
95
|
+
line('Billed to:', workspaceId
|
|
96
|
+
? `workspace ${workspaceId} (\`${this.config.bin} balance show --workspace ${workspaceId}\`)`
|
|
97
|
+
: 'your personal account');
|
|
98
|
+
}
|
|
85
99
|
line('Billing:', nested(lease.billing, 'billableState'));
|
|
86
100
|
// The monthly cap, which is what actually refuses a Hosted Machine.
|
|
87
101
|
//
|
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/** The target a `TARGET_CONFLICT` refusal names, as the API sends it. */
|
|
3
|
+
export interface ConflictingTarget {
|
|
4
|
+
id: string;
|
|
5
|
+
desiredState?: string;
|
|
6
|
+
status?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function conflictingTargetOf(body: Record<string, unknown> | undefined): ConflictingTarget | null;
|
|
9
|
+
/**
|
|
10
|
+
* What to do about a placement that already has a target, as commands.
|
|
11
|
+
*
|
|
12
|
+
* Re-running an install whose first attempt had failed printed "An unexpected
|
|
13
|
+
* error occurred. Code: TARGET_CONFLICT" (OSK-4631): no target, no path, no next
|
|
14
|
+
* step, and no word on whether the re-run had partly applied. The API now names
|
|
15
|
+
* the target in the way; this turns that into the commands for its state. A live
|
|
16
|
+
* target is changed or removed, never installed twice; one being removed can be
|
|
17
|
+
* restored, or purged once its daemon confirms cleanup.
|
|
18
|
+
*/
|
|
19
|
+
export declare function describeTargetConflict(target: ConflictingTarget, { bin, placement, created }: {
|
|
20
|
+
bin: string;
|
|
21
|
+
placement: string;
|
|
22
|
+
created: string[];
|
|
23
|
+
}): string;
|
|
2
24
|
export default class InstructionsInstall extends BaseCommand {
|
|
3
25
|
static description: string;
|
|
4
26
|
static flags: {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.conflictingTargetOf = conflictingTargetOf;
|
|
4
|
+
exports.describeTargetConflict = describeTargetConflict;
|
|
3
5
|
const core_1 = require("@oclif/core");
|
|
4
6
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
5
7
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
@@ -9,6 +11,41 @@ const harness_provider_input_1 = require("../../lib/harness-provider-input");
|
|
|
9
11
|
const prompt_1 = require("../../lib/prompt");
|
|
10
12
|
/** The providers a managed instruction can target, canonical spellings. */
|
|
11
13
|
const MANAGED_INSTRUCTION_PROVIDERS = ['claude', 'codex', auth_core_1.FIRST_PARTY_HARNESS.provider];
|
|
14
|
+
function conflictingTargetOf(body) {
|
|
15
|
+
const value = body?.conflictingTarget;
|
|
16
|
+
return value && typeof value.id === 'string' && value.id ? value : null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* What to do about a placement that already has a target, as commands.
|
|
20
|
+
*
|
|
21
|
+
* Re-running an install whose first attempt had failed printed "An unexpected
|
|
22
|
+
* error occurred. Code: TARGET_CONFLICT" (OSK-4631): no target, no path, no next
|
|
23
|
+
* step, and no word on whether the re-run had partly applied. The API now names
|
|
24
|
+
* the target in the way; this turns that into the commands for its state. A live
|
|
25
|
+
* target is changed or removed, never installed twice; one being removed can be
|
|
26
|
+
* restored, or purged once its daemon confirms cleanup.
|
|
27
|
+
*/
|
|
28
|
+
function describeTargetConflict(target, { bin, placement, created }) {
|
|
29
|
+
const state = [target.desiredState, target.status].filter(Boolean).join(', ');
|
|
30
|
+
const lines = target.desiredState === 'removed'
|
|
31
|
+
? [
|
|
32
|
+
`Target ${target.id} already holds ${placement} on that machine and is being removed${state ? ` (${state})` : ''}.`,
|
|
33
|
+
`Bring it back with \`${bin} instructions restore-target ${target.id}\`, or install again ` +
|
|
34
|
+
`after its daemon confirms cleanup and \`${bin} instructions purge-target ${target.id}\`.`,
|
|
35
|
+
]
|
|
36
|
+
: [
|
|
37
|
+
`Target ${target.id} already manages ${placement} on that machine${state ? ` (${state})` : ''}.`,
|
|
38
|
+
...(target.status === 'error'
|
|
39
|
+
? [`Its last sync failed; \`${bin} instructions status\` shows why.`]
|
|
40
|
+
: []),
|
|
41
|
+
`Change what it applies with \`${bin} instructions update-target ${target.id} --bundle <id>\`, ` +
|
|
42
|
+
`or drop it with \`${bin} instructions remove ${target.id}\`.`,
|
|
43
|
+
];
|
|
44
|
+
lines.push(created.length
|
|
45
|
+
? `Created before this refusal: ${created.join(', ')}.`
|
|
46
|
+
: 'No target was created by this command.');
|
|
47
|
+
return lines.join(' ');
|
|
48
|
+
}
|
|
12
49
|
class InstructionsInstall extends base_command_1.BaseCommand {
|
|
13
50
|
static description = 'Opt in a daemon target to ongoing managed instruction reconciliation';
|
|
14
51
|
static flags = {
|
|
@@ -32,7 +69,13 @@ class InstructionsInstall extends base_command_1.BaseCommand {
|
|
|
32
69
|
default: MANAGED_INSTRUCTION_PROVIDERS,
|
|
33
70
|
}),
|
|
34
71
|
scope: core_1.Flags.string({ description: 'global or project', options: ['global', 'project'] }),
|
|
35
|
-
project: core_1.Flags.string({
|
|
72
|
+
project: core_1.Flags.string({
|
|
73
|
+
// The daemon writes a project target at the repository root, so a path
|
|
74
|
+
// outside any Git repository is accepted here and then fails on the
|
|
75
|
+
// machine. Saying so is the only warning this command can give: the path
|
|
76
|
+
// is checked where it lives, not here (OSK-4626).
|
|
77
|
+
description: "Absolute project path on the daemon's machine, inside a Git repository (the block is written at its root)",
|
|
78
|
+
}),
|
|
36
79
|
mode: core_1.Flags.string({
|
|
37
80
|
description: 'Project footprint',
|
|
38
81
|
options: ['local', 'team'],
|
|
@@ -87,12 +130,20 @@ class InstructionsInstall extends base_command_1.BaseCommand {
|
|
|
87
130
|
if (providers.includes('codex') && Buffer.byteLength(previewBody, 'utf8') > 30 * 1024) {
|
|
88
131
|
this.error('Codex managed instruction bodies are limited to 30 KiB; no targets were created', { exit: 2 });
|
|
89
132
|
}
|
|
90
|
-
|
|
133
|
+
// The body is shown so the confirmation below is informed. With `--yes`
|
|
134
|
+
// there is no confirmation to inform, and printing a ~9 KB body anyway put
|
|
135
|
+
// the command's actual outcome — often a refusal — a screen of scrollback
|
|
136
|
+
// away (OSK-4631). Say what was authorized, and where to read it.
|
|
137
|
+
if (!flags.json && !flags.yes) {
|
|
91
138
|
this.log('Exact managed instruction body:');
|
|
92
139
|
this.log('---');
|
|
93
140
|
this.log(previewBody);
|
|
94
141
|
this.log('---');
|
|
95
142
|
}
|
|
143
|
+
else if (!flags.json) {
|
|
144
|
+
this.log(`Authorizing ${Buffer.byteLength(previewBody, 'utf8').toLocaleString()} bytes of instruction ` +
|
|
145
|
+
`${flags.bundle}; \`${this.config.bin} instructions show ${flags.bundle}\` prints them.`);
|
|
146
|
+
}
|
|
96
147
|
const teamWarning = scope === 'project' && flags.mode === 'team'
|
|
97
148
|
? ' Team mode writes to repository files and may affect every contributor.'
|
|
98
149
|
: '';
|
|
@@ -101,8 +152,8 @@ class InstructionsInstall extends base_command_1.BaseCommand {
|
|
|
101
152
|
return this.log('Aborted.');
|
|
102
153
|
const daemonId = await this.resolveDaemon(flags.daemon);
|
|
103
154
|
const results = [];
|
|
104
|
-
|
|
105
|
-
|
|
155
|
+
for (const provider of providers) {
|
|
156
|
+
try {
|
|
106
157
|
results.push(await data_provider_1.dataService.createManagedInstructionTarget({
|
|
107
158
|
daemonId,
|
|
108
159
|
bundleId: flags.bundle,
|
|
@@ -115,15 +166,40 @@ class InstructionsInstall extends base_command_1.BaseCommand {
|
|
|
115
166
|
label: flags.label,
|
|
116
167
|
}));
|
|
117
168
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
169
|
+
catch (error) {
|
|
170
|
+
// One target per provider, created in turn, so a refusal part-way
|
|
171
|
+
// through leaves the earlier ones in place. Say which.
|
|
172
|
+
const created = results.map((result) => `${(0, harness_provider_input_1.canonicalHarnessInput)(result.target.provider)} (${result.target.id})`);
|
|
173
|
+
const refusal = (0, base_command_1.apiRefusal)(error);
|
|
174
|
+
const conflict = refusal.code === 'TARGET_CONFLICT' ? conflictingTargetOf(refusal.body) : null;
|
|
175
|
+
if (conflict) {
|
|
176
|
+
this.failWithCliError({
|
|
177
|
+
message: describeTargetConflict(conflict, {
|
|
178
|
+
bin: this.config.bin,
|
|
179
|
+
placement: `${(0, harness_provider_input_1.canonicalHarnessInput)(provider)} ${scope}${flags.project ? ` ${flags.project}` : ''}`,
|
|
180
|
+
created,
|
|
181
|
+
}),
|
|
182
|
+
code: 'TARGET_CONFLICT',
|
|
183
|
+
status: 409,
|
|
184
|
+
exit: 1,
|
|
185
|
+
retryable: false,
|
|
186
|
+
details: { ...refusal.body, created: results.map((result) => result.target) },
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
if (created.length) {
|
|
190
|
+
this.warn(`Created before this failure: ${created.join(', ')}.`);
|
|
191
|
+
}
|
|
192
|
+
this.handleApiError(error);
|
|
193
|
+
}
|
|
121
194
|
}
|
|
122
195
|
if (flags.json)
|
|
123
196
|
return this.log(JSON.stringify(results, null, 2));
|
|
124
197
|
for (const result of results) {
|
|
125
198
|
this.log(`Install queued: ${(0, harness_provider_input_1.canonicalHarnessInput)(result.target.provider)} ${result.target.scope}${result.target.projectPath ? ` ${result.target.projectPath}` : ''} (${result.target.id})`);
|
|
126
199
|
}
|
|
200
|
+
// Queued is not applied: the daemon checks the placement when it writes, and
|
|
201
|
+
// a path it cannot use fails there, after this command has exited 0.
|
|
202
|
+
this.log(`The daemon applies these on its next sync; \`${this.config.bin} instructions status\` shows when, or why not.`);
|
|
127
203
|
}
|
|
128
204
|
/**
|
|
129
205
|
* The daemon the targets are created on, by the rule every daemon-picking
|
|
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
|
|
|
4
4
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
5
|
const base_command_1 = require("../../base-command");
|
|
6
6
|
const format_1 = require("../../lib/format");
|
|
7
|
+
const instruction_provenance_1 = require("../../lib/instruction-provenance");
|
|
7
8
|
class InstructionsList extends base_command_1.BaseCommand {
|
|
8
9
|
static description = 'List version-controlled instruction prompts';
|
|
9
10
|
static flags = {
|
|
@@ -32,12 +33,16 @@ class InstructionsList extends base_command_1.BaseCommand {
|
|
|
32
33
|
id: bundle.id,
|
|
33
34
|
name: bundle.name,
|
|
34
35
|
version: String(bundle.currentVersionNumber),
|
|
36
|
+
// The built-in release the CURRENT body is, if any — a separate counter
|
|
37
|
+
// from VERSION, which is this prompt's own (OSK-4647).
|
|
38
|
+
template: (0, instruction_provenance_1.builtInLabel)(bundle.currentVersion),
|
|
35
39
|
status: bundle.deletedAt ? 'deleted' : bundle.status,
|
|
36
40
|
updated: bundle.updatedAt,
|
|
37
41
|
})), [
|
|
38
42
|
{ key: 'id', header: 'ID' },
|
|
39
43
|
{ key: 'name', header: 'NAME', maxWidth: 32 },
|
|
40
44
|
{ key: 'version', header: 'VERSION' },
|
|
45
|
+
{ key: 'template', header: 'TEMPLATE' },
|
|
41
46
|
{ key: 'status', header: 'STATUS' },
|
|
42
47
|
{ key: 'updated', header: 'UPDATED' },
|
|
43
48
|
], (line) => this.log(line));
|
|
@@ -9,4 +9,10 @@ export default class InstructionsShow extends BaseCommand {
|
|
|
9
9
|
versions: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
10
|
};
|
|
11
11
|
run(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* The built-in release this server would install today, or undefined. Read
|
|
14
|
+
* from the offer, which already reports it as `templateVersion`; a failure
|
|
15
|
+
* costs only the "is there a newer one" clause, never the command.
|
|
16
|
+
*/
|
|
17
|
+
private latestTemplateVersion;
|
|
12
18
|
}
|
|
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
|
|
|
4
4
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
5
|
const base_command_1 = require("../../base-command");
|
|
6
6
|
const format_1 = require("../../lib/format");
|
|
7
|
+
const instruction_provenance_1 = require("../../lib/instruction-provenance");
|
|
7
8
|
class InstructionsShow extends base_command_1.BaseCommand {
|
|
8
9
|
static description = 'Show exact prompt content and version history';
|
|
9
10
|
static args = {
|
|
@@ -38,6 +39,9 @@ class InstructionsShow extends base_command_1.BaseCommand {
|
|
|
38
39
|
(0, format_1.renderTable)(response.versions.map((version) => ({
|
|
39
40
|
id: version.id,
|
|
40
41
|
version: String(version.versionNumber),
|
|
42
|
+
// Its own column, never folded into VERSION: the prompt's counter
|
|
43
|
+
// and the built-in release are different numbers (OSK-4647).
|
|
44
|
+
template: (0, instruction_provenance_1.builtInLabel)(version),
|
|
41
45
|
hash: version.hash.slice(0, 12),
|
|
42
46
|
status: version.archivedAt
|
|
43
47
|
? 'archived'
|
|
@@ -48,10 +52,20 @@ class InstructionsShow extends base_command_1.BaseCommand {
|
|
|
48
52
|
})), [
|
|
49
53
|
{ key: 'id', header: 'ID' },
|
|
50
54
|
{ key: 'version', header: 'VERSION' },
|
|
51
|
-
{ key: 'hash', header: 'HASH' },
|
|
55
|
+
{ key: 'hash', header: 'BODY HASH' },
|
|
52
56
|
{ key: 'status', header: 'STATUS' },
|
|
57
|
+
{ key: 'template', header: 'TEMPLATE' },
|
|
53
58
|
{ key: 'message', header: 'MESSAGE', maxWidth: 48 },
|
|
54
59
|
], (line) => this.log(line));
|
|
60
|
+
// Three hashes answer "is my file current?", and this table shows one.
|
|
61
|
+
// A file's `v=` is the body as rendered for its target, so beside an
|
|
62
|
+
// agent-identity section it matches none of these (OSK-4644).
|
|
63
|
+
this.log('');
|
|
64
|
+
this.log("BODY HASH is the version as written. A managed file's `v=` is the body as");
|
|
65
|
+
this.log('rendered for its target, which differs when it fills in an agent identity;');
|
|
66
|
+
this.log(`compare it with FILE HASH in \`${this.config.bin} instructions status\`.`);
|
|
67
|
+
this.log("VERSION counts this prompt's saved bodies; TEMPLATE is the built-in release a");
|
|
68
|
+
this.log('body was taken from, which is a separate number.');
|
|
55
69
|
return;
|
|
56
70
|
}
|
|
57
71
|
const response = await data_provider_1.dataService.getManagedInstructionBundle(args.id);
|
|
@@ -61,6 +75,9 @@ class InstructionsShow extends base_command_1.BaseCommand {
|
|
|
61
75
|
this.log(`${bundle.name} (${bundle.id}) — v${bundle.currentVersionNumber} — ${bundle.status}`);
|
|
62
76
|
if (bundle.description)
|
|
63
77
|
this.log(bundle.description);
|
|
78
|
+
const provenance = (0, instruction_provenance_1.describeTemplateProvenance)(bundle, await this.latestTemplateVersion(bundle.sourceTemplateKey));
|
|
79
|
+
if (provenance)
|
|
80
|
+
this.log(provenance);
|
|
64
81
|
this.log('');
|
|
65
82
|
this.log(bundle.currentVersion?.body || '(no active content)');
|
|
66
83
|
}
|
|
@@ -84,5 +101,21 @@ class InstructionsShow extends base_command_1.BaseCommand {
|
|
|
84
101
|
this.handleApiError(error);
|
|
85
102
|
}
|
|
86
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* The built-in release this server would install today, or undefined. Read
|
|
106
|
+
* from the offer, which already reports it as `templateVersion`; a failure
|
|
107
|
+
* costs only the "is there a newer one" clause, never the command.
|
|
108
|
+
*/
|
|
109
|
+
async latestTemplateVersion(templateKey) {
|
|
110
|
+
if (!templateKey)
|
|
111
|
+
return undefined;
|
|
112
|
+
try {
|
|
113
|
+
const offer = await data_provider_1.dataService.getManagedInstructionOffer(templateKey);
|
|
114
|
+
return offer?.preset?.templateVersion;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
87
120
|
}
|
|
88
121
|
exports.default = InstructionsShow;
|
|
@@ -5,6 +5,7 @@ const data_provider_1 = require("@skrr-ai/data-provider");
|
|
|
5
5
|
const base_command_1 = require("../../base-command");
|
|
6
6
|
const format_1 = require("../../lib/format");
|
|
7
7
|
const instruction_input_1 = require("../../lib/instruction-input");
|
|
8
|
+
const instruction_provenance_1 = require("../../lib/instruction-provenance");
|
|
8
9
|
class InstructionsStatus extends base_command_1.BaseCommand {
|
|
9
10
|
static description = 'Show managed targets, applied versions, paths, and reconciliation errors';
|
|
10
11
|
static flags = {
|
|
@@ -42,12 +43,14 @@ class InstructionsStatus extends base_command_1.BaseCommand {
|
|
|
42
43
|
// Leave it empty.
|
|
43
44
|
}
|
|
44
45
|
let bundleNames = new Map();
|
|
46
|
+
let bundlesById = new Map();
|
|
45
47
|
try {
|
|
46
48
|
const bundleResponse = await data_provider_1.dataService.listManagedInstructionBundles({
|
|
47
49
|
includeArchived: true,
|
|
48
50
|
includeDeleted: true,
|
|
49
51
|
});
|
|
50
52
|
bundleNames = (0, instruction_input_1.buildBundleNameIndex)(bundleResponse.bundles);
|
|
53
|
+
bundlesById = new Map(bundleResponse.bundles.map((bundle) => [bundle.id, bundle]));
|
|
51
54
|
}
|
|
52
55
|
catch {
|
|
53
56
|
// Leave the index empty — rows show the full bundle id instead.
|
|
@@ -73,7 +76,17 @@ class InstructionsStatus extends base_command_1.BaseCommand {
|
|
|
73
76
|
target: target.appliedPath ||
|
|
74
77
|
(target.scope === 'global' ? 'global' : target.projectPath || 'project'),
|
|
75
78
|
desired: target.desiredState,
|
|
76
|
-
|
|
79
|
+
// `v3 (built-in v5)`: the prompt's version, and the built-in release
|
|
80
|
+
// it is when that is known. `APPLIED v3` alone read as two releases
|
|
81
|
+
// behind a preset announced as v5 (OSK-4647).
|
|
82
|
+
applied: (0, instruction_provenance_1.appliedVersionLabel)(target.appliedVersionNumber, bundlesById.get(target.bundleId)),
|
|
83
|
+
// The hash the managed block in the file starts with (`v=`), cut to the
|
|
84
|
+
// same 12 characters `show --versions` prints. It is the body as
|
|
85
|
+
// rendered for THIS target, so it matches a version's hash only when
|
|
86
|
+
// nothing was filled in — and was `--json` only, which made "is the
|
|
87
|
+
// block in my CLAUDE.md current?" read as "my machine is running a body
|
|
88
|
+
// the server never heard of" (OSK-4644).
|
|
89
|
+
fileHash: target.appliedHash ? target.appliedHash.slice(0, 12) : '-',
|
|
77
90
|
status: (0, instruction_input_1.describeTargetSync)(target, Date.now(), liveDaemonIds),
|
|
78
91
|
})), [
|
|
79
92
|
{ key: 'id', header: 'TARGET ID' },
|
|
@@ -88,6 +101,7 @@ class InstructionsStatus extends base_command_1.BaseCommand {
|
|
|
88
101
|
{ key: 'target', header: 'PATH', maxWidth: 36 },
|
|
89
102
|
{ key: 'desired', header: 'DESIRED' },
|
|
90
103
|
{ key: 'applied', header: 'APPLIED' },
|
|
104
|
+
{ key: 'fileHash', header: 'FILE HASH' },
|
|
91
105
|
// Wide enough for the stall note to keep its remedy. Truncating at 48
|
|
92
106
|
// cut `purge-target` off the end, which hid the one actionable half of
|
|
93
107
|
// the sentence.
|
|
@@ -106,6 +120,8 @@ class InstructionsStatus extends base_command_1.BaseCommand {
|
|
|
106
120
|
for (const command of rebinds)
|
|
107
121
|
this.log(` ${command}`);
|
|
108
122
|
}
|
|
123
|
+
for (const line of (0, instruction_input_1.failedTargetLines)(response.targets, this.config.bin))
|
|
124
|
+
this.log(line);
|
|
109
125
|
// Where the text went.
|
|
110
126
|
//
|
|
111
127
|
// The count in the STATUS column says skrr overwrote something you
|
|
@@ -52,8 +52,8 @@ class PaymentsWallet extends base_command_1.BaseCommand {
|
|
|
52
52
|
// CLI — so anyone looking for "my balance" found USDC and drew the wrong
|
|
53
53
|
// conclusion about their usage ceiling. Name the other one.
|
|
54
54
|
this.log('');
|
|
55
|
-
this.log('This is the x402 crypto wallet, in USDC. Your hosted USAGE balance
|
|
56
|
-
this.log('the
|
|
55
|
+
this.log('This is the x402 crypto wallet, in USDC. Your hosted USAGE balance and');
|
|
56
|
+
this.log('plan limits — the ceilings that refuse a turn — are `skrr balance show`.');
|
|
57
57
|
const payments = history.payments ?? [];
|
|
58
58
|
if (payments.length === 0) {
|
|
59
59
|
this.log('');
|