@skrr-ai/cli 0.1.41 → 0.1.43
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/commands/balance/overage.js +4 -3
- package/dist/commands/balance/plan.js +10 -6
- package/dist/commands/balance/usage/events.js +3 -2
- package/dist/commands/balance/usage.js +2 -1
- package/dist/commands/browser/install.js +3 -2
- package/dist/commands/browser/uninstall.js +3 -2
- package/dist/commands/code/install.js +6 -6
- package/dist/commands/followups/remind.js +4 -0
- package/dist/commands/harnesses/install.d.ts +29 -0
- package/dist/commands/harnesses/install.js +153 -25
- package/dist/commands/harnesses/installers.d.ts +10 -0
- package/dist/commands/harnesses/installers.js +31 -0
- package/dist/commands/harnesses/list.js +3 -2
- package/dist/commands/skills/import-as-actions.js +6 -15
- package/dist/commands/tasks/attachments/set-role.d.ts +24 -0
- package/dist/commands/tasks/attachments/set-role.js +60 -0
- package/dist/commands/tasks/attachments/upload.d.ts +1 -0
- package/dist/commands/tasks/attachments/upload.js +18 -52
- package/dist/commands/tasks/comments/add.d.ts +1 -0
- package/dist/commands/tasks/comments/add.js +67 -2
- package/dist/commands/tasks/complete.d.ts +1 -0
- package/dist/commands/tasks/complete.js +69 -1
- package/dist/commands/tasks/result/submit.d.ts +3 -0
- package/dist/commands/tasks/result/submit.js +78 -5
- package/dist/commands/tasks/update.js +4 -2
- package/dist/commands/tasks/updates/add.d.ts +1 -0
- package/dist/commands/tasks/updates/add.js +63 -0
- package/dist/lib/api-fetch.js +7 -2
- package/dist/lib/cli-installers.d.ts +63 -4
- package/dist/lib/cli-installers.js +98 -8
- package/dist/lib/dedicated-machines.js +4 -2
- package/dist/lib/file-mime.js +1 -1
- package/dist/lib/first-party-harness-agent.js +6 -5
- package/dist/lib/first-party-harness-doctor.js +15 -32
- package/dist/lib/first-party-harness-managed.d.ts +9 -4
- package/dist/lib/first-party-harness-managed.js +13 -10
- package/dist/lib/first-party-harness.d.ts +25 -21
- package/dist/lib/first-party-harness.js +40 -27
- package/dist/lib/followups.d.ts +2 -0
- package/dist/lib/harness-provider-input.d.ts +18 -12
- package/dist/lib/harness-provider-input.js +18 -12
- package/dist/lib/harness-tiers.d.ts +4 -3
- package/dist/lib/harness-tiers.js +4 -3
- package/dist/lib/html-text.js +25 -0
- package/dist/lib/local-skills.d.ts +27 -0
- package/dist/lib/local-skills.js +38 -0
- package/dist/lib/session-task-endpoints.d.ts +1 -1
- package/dist/lib/session-task-endpoints.js +3 -0
- package/dist/lib/task-asset-upload.d.ts +84 -0
- package/dist/lib/task-asset-upload.js +374 -0
- package/dist/lib/task-closure.d.ts +28 -0
- package/dist/lib/task-closure.js +37 -0
- package/dist/lib/tasks.d.ts +8 -0
- package/dist/lib/tasks.js +16 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +113 -39
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +148 -71
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.d.ts +21 -33
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +24 -50
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.d.ts +19 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.js +26 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.d.ts +9 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.js +9 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +1 -13
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +113 -39
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +146 -70
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.d.ts +21 -33
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +24 -49
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.d.ts +19 -7
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.js +27 -11
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.d.ts +9 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.js +9 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -4
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +4025 -3938
- package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
- package/oclif.manifest.json +12853 -12721
- package/package.json +2 -2
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_1 = require("@oclif/core");
|
|
4
|
+
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
4
5
|
const base_command_1 = require("../../base-command");
|
|
5
6
|
const balance_1 = require("../../lib/balance");
|
|
6
7
|
class BalanceOverage extends base_command_1.BaseCommand {
|
|
7
|
-
static description =
|
|
8
|
+
static description = `Enable or disable personal ${auth_core_1.FIRST_PARTY_HARNESS.displayName} overage after included allowance is exhausted`;
|
|
8
9
|
static examples = [
|
|
9
10
|
'<%= config.bin %> balance overage enabled',
|
|
10
11
|
'<%= config.bin %> balance overage disabled',
|
|
@@ -12,7 +13,7 @@ class BalanceOverage extends base_command_1.BaseCommand {
|
|
|
12
13
|
];
|
|
13
14
|
static args = {
|
|
14
15
|
state: core_1.Args.string({
|
|
15
|
-
description:
|
|
16
|
+
description: `Whether personal stored balance may pay for ${auth_core_1.FIRST_PARTY_HARNESS.displayName} overage`,
|
|
16
17
|
required: true,
|
|
17
18
|
options: ['enabled', 'disabled'],
|
|
18
19
|
ignoreStdin: true,
|
|
@@ -34,7 +35,7 @@ class BalanceOverage extends base_command_1.BaseCommand {
|
|
|
34
35
|
this.log(JSON.stringify({ ownerType: 'user', overageEnabled: result?.enabled === true }, null, 2));
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
|
-
this.log(
|
|
38
|
+
this.log(`${auth_core_1.FIRST_PARTY_HARNESS.displayName} overage ${result?.enabled ? 'enabled' : 'disabled'} for your personal billing account.`);
|
|
38
39
|
this.log(result?.enabled
|
|
39
40
|
? 'New admissions may use stored balance after included allowance is exhausted.'
|
|
40
41
|
: 'New admissions will stop when included allowance is exhausted.');
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.renderSkyCodePlan = renderSkyCodePlan;
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
|
+
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
5
6
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
6
7
|
const base_command_1 = require("../../base-command");
|
|
7
8
|
const balance_1 = require("../../lib/balance");
|
|
8
9
|
const web_url_1 = require("../../lib/web-url");
|
|
9
10
|
class BalancePlan extends base_command_1.BaseCommand {
|
|
10
|
-
static description =
|
|
11
|
+
static description = `Show your ${auth_core_1.FIRST_PARTY_HARNESS.displayName} plan, renewal state, available tiers, and the matching lifecycle page`;
|
|
11
12
|
static examples = [
|
|
12
13
|
'<%= config.bin %> balance plan',
|
|
13
14
|
'<%= config.bin %> balance plan --json',
|
|
@@ -33,7 +34,7 @@ class BalancePlan extends base_command_1.BaseCommand {
|
|
|
33
34
|
}
|
|
34
35
|
exports.default = BalancePlan;
|
|
35
36
|
function renderSkyCodePlan(subscription, lifecycleUrl, log) {
|
|
36
|
-
log(
|
|
37
|
+
log(`${auth_core_1.FIRST_PARTY_HARNESS.displayName} plan — ${subscription.tierName}`);
|
|
37
38
|
log(` Status ${subscription.status}`);
|
|
38
39
|
log(` Price $${subscription.monthlyPriceUsd}/month`);
|
|
39
40
|
log(` Current period ${subscription.currentPeriodStart ?? 'not started'}`);
|
|
@@ -43,10 +44,13 @@ function renderSkyCodePlan(subscription, lifecycleUrl, log) {
|
|
|
43
44
|
log(` Concurrent sessions ${subscription.concurrentSessions ?? 'not reported'}`);
|
|
44
45
|
log(` Requests per minute ${subscription.rateLimitPerMin}`);
|
|
45
46
|
log('');
|
|
46
|
-
log(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
log(`Available ${auth_core_1.FIRST_PARTY_HARNESS.displayName} plans:`);
|
|
48
|
+
const paidPlans = data_provider_1.SKY_CODE_PLAN_KEYS.filter((plan) => plan !== 'free').map((key) => data_provider_1.SKY_CODE_PLAN_CATALOG[key]);
|
|
49
|
+
// Padded to the longest name rather than a fixed width: plan names embed the
|
|
50
|
+
// product's display name, whose length is not this renderer's to assume.
|
|
51
|
+
const nameWidth = Math.max(...paidPlans.map((plan) => plan.displayName.length)) + 1;
|
|
52
|
+
for (const plan of paidPlans) {
|
|
53
|
+
log(` ${plan.displayName.padEnd(nameWidth)} $${plan.monthlyPriceUsd}/month · ` +
|
|
50
54
|
`${(0, data_provider_1.usageMicrosToDisplayCredits)(plan.includedUsageMicros).toLocaleString()} monthly credits · ` +
|
|
51
55
|
`${(0, data_provider_1.usageMicrosToDisplayCredits)(plan.fiveHourUsageMicros).toLocaleString()} per 5 hours · ` +
|
|
52
56
|
`${plan.concurrentSessions} sessions · ${plan.requestsPerMinute} RPM`);
|
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.renderSkyCodeUsageEvents = renderSkyCodeUsageEvents;
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
|
+
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
5
6
|
const base_command_1 = require("../../../base-command");
|
|
6
7
|
const balance_1 = require("../../../lib/balance");
|
|
7
8
|
class BalanceUsageEvents extends base_command_1.BaseCommand {
|
|
8
|
-
static description =
|
|
9
|
+
static description = `List the ${auth_core_1.FIRST_PARTY_HARNESS.displayName} charges and run/session attribution behind aggregate allowance usage`;
|
|
9
10
|
static examples = [
|
|
10
11
|
'<%= config.bin %> balance usage events',
|
|
11
12
|
'<%= config.bin %> balance usage events --limit 20 --json',
|
|
@@ -44,7 +45,7 @@ class BalanceUsageEvents extends base_command_1.BaseCommand {
|
|
|
44
45
|
}
|
|
45
46
|
exports.default = BalanceUsageEvents;
|
|
46
47
|
function renderSkyCodeUsageEvents(events, log) {
|
|
47
|
-
log(
|
|
48
|
+
log(`${auth_core_1.FIRST_PARTY_HARNESS.displayName} usage events · ${events.ownerType} ${events.ownerId}`);
|
|
48
49
|
log(` Window ${events.from} to ${events.to}`);
|
|
49
50
|
if (events.items.length === 0) {
|
|
50
51
|
log(' No usage events in this window.');
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.renderSkyCodeUsage = renderSkyCodeUsage;
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
|
+
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
5
6
|
const base_command_1 = require("../../base-command");
|
|
6
7
|
const balance_1 = require("../../lib/balance");
|
|
7
8
|
/**
|
|
@@ -40,7 +41,7 @@ class BalanceUsage extends base_command_1.BaseCommand {
|
|
|
40
41
|
}
|
|
41
42
|
exports.default = BalanceUsage;
|
|
42
43
|
function renderSkyCodeUsage(usage, log) {
|
|
43
|
-
log(
|
|
44
|
+
log(`${auth_core_1.FIRST_PARTY_HARNESS.displayName} ${usage.plan ?? 'current'} plan · ${usage.ownerType} ${usage.ownerId}`);
|
|
44
45
|
log(` Monthly used ${(0, balance_1.formatCreditsWithUsd)(usage.monthly.usedUsageMicros)}`);
|
|
45
46
|
log(` Monthly reserved ${(0, balance_1.formatCreditsWithUsd)(usage.monthly.reservedUsageMicros)}`);
|
|
46
47
|
log(` Monthly remaining ${(0, balance_1.formatCreditsWithUsd)(usage.monthly.remainingUsageMicros)}`);
|
|
@@ -23,8 +23,9 @@ class BrowserInstall extends core_1.Command {
|
|
|
23
23
|
async run() {
|
|
24
24
|
const { flags } = await this.parse(BrowserInstall);
|
|
25
25
|
const args = ['install'];
|
|
26
|
-
// Any spelling of the first-party harness is
|
|
27
|
-
//
|
|
26
|
+
// Any ACCEPTED spelling of the first-party harness is handed to the daemon in
|
|
27
|
+
// the wire spelling; anything else — a retired spelling included — is forwarded
|
|
28
|
+
// unchanged for the daemon's own parser to accept or refuse.
|
|
28
29
|
if (flags.harness)
|
|
29
30
|
args.push('--harness', (0, harness_provider_input_1.daemonHarnessArgument)(flags.harness));
|
|
30
31
|
if (flags.config)
|
|
@@ -23,8 +23,9 @@ class BrowserUninstall extends core_1.Command {
|
|
|
23
23
|
async run() {
|
|
24
24
|
const { flags } = await this.parse(BrowserUninstall);
|
|
25
25
|
const args = ['uninstall'];
|
|
26
|
-
// Any spelling of the first-party harness is
|
|
27
|
-
//
|
|
26
|
+
// Any ACCEPTED spelling of the first-party harness is handed to the daemon in
|
|
27
|
+
// the wire spelling; anything else — a retired spelling included — is forwarded
|
|
28
|
+
// unchanged for the daemon's own parser to accept or refuse.
|
|
28
29
|
if (flags.harness)
|
|
29
30
|
args.push('--harness', (0, harness_provider_input_1.daemonHarnessArgument)(flags.harness));
|
|
30
31
|
if (flags.config)
|
|
@@ -4,7 +4,6 @@ const core_1 = require("@oclif/core");
|
|
|
4
4
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
5
5
|
const base_command_1 = require("../../base-command");
|
|
6
6
|
const api_fetch_1 = require("../../lib/api-fetch");
|
|
7
|
-
const daemon_ref_1 = require("../../lib/daemon-ref");
|
|
8
7
|
const daemon_target_1 = require("../../lib/daemon-target");
|
|
9
8
|
const first_party_harness_1 = require("../../lib/first-party-harness");
|
|
10
9
|
/**
|
|
@@ -84,11 +83,11 @@ class CodeInstall extends base_command_1.BaseCommand {
|
|
|
84
83
|
// survive in mine.
|
|
85
84
|
const reason = await this.installerNote(daemonId);
|
|
86
85
|
if (reason) {
|
|
87
|
-
this.log(`Nothing to do on ${
|
|
86
|
+
this.log(`Nothing to do on ${daemonId} — ${reason}`);
|
|
88
87
|
return;
|
|
89
88
|
}
|
|
90
89
|
this.failWithCliError({
|
|
91
|
-
message: `The daemon on ${
|
|
90
|
+
message: `The daemon on ${daemonId} accepted the request and returned no ` +
|
|
92
91
|
'install job, and does not say why. Nothing has been shown to start. ' +
|
|
93
92
|
`Check what it offers with \`${this.config.bin} harnesses installers --daemon ${daemonId}\`, and ` +
|
|
94
93
|
`\`${this.config.bin} code doctor\` here once you expect it to have landed.`,
|
|
@@ -104,7 +103,7 @@ class CodeInstall extends base_command_1.BaseCommand {
|
|
|
104
103
|
this.log(JSON.stringify({ daemonId, ...started }, null, 2));
|
|
105
104
|
return;
|
|
106
105
|
}
|
|
107
|
-
this.log(`Install started on ${
|
|
106
|
+
this.log(`Install started on ${daemonId} — job ${jobId}.`);
|
|
108
107
|
return;
|
|
109
108
|
}
|
|
110
109
|
const job = await this.followJob(daemonId, jobId, flags.json === true);
|
|
@@ -202,8 +201,9 @@ class CodeInstall extends base_command_1.BaseCommand {
|
|
|
202
201
|
async installerNote(daemonId) {
|
|
203
202
|
try {
|
|
204
203
|
const res = await (0, api_fetch_1.apiFetch)(`/api/daemons/${encodeURIComponent(daemonId)}/cli-installers`, { credential: this.resolvedCredential });
|
|
205
|
-
// Any spelling: a daemon lists the provider the way it registered it,
|
|
206
|
-
// one that predates or postdates
|
|
204
|
+
// Any accepted spelling: a daemon lists the provider the way it registered it,
|
|
205
|
+
// and during a rename's alias window one that predates or postdates the flip
|
|
206
|
+
// spells it differently.
|
|
207
207
|
const row = (res.providers ?? []).find((p) => (0, auth_core_1.isFirstPartyHarnessProvider)(p.provider));
|
|
208
208
|
const reason = row?.installDisabledReason?.trim();
|
|
209
209
|
if (!reason)
|
|
@@ -95,6 +95,10 @@ class FollowupsRemind extends base_command_1.BaseCommand {
|
|
|
95
95
|
return;
|
|
96
96
|
}
|
|
97
97
|
const row = response.followup;
|
|
98
|
+
if (response.reused && response.rescheduled) {
|
|
99
|
+
this.log(`Moved: ${row?.id} now fires at ${row?.nextRunAt ?? at} — the same reminder already existed, so it was rescheduled rather than duplicated.`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
98
102
|
if (response.reused) {
|
|
99
103
|
this.log(`Already armed: ${row?.id} — the same reminder exists and was not duplicated.`);
|
|
100
104
|
return;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
|
+
import { type ManagedCliProviderState } from '../../lib/cli-installers';
|
|
2
3
|
/**
|
|
3
4
|
* Install one managed coding-agent CLI on a machine.
|
|
4
5
|
*
|
|
@@ -33,9 +34,37 @@ export default class HarnessesInstall extends BaseCommand {
|
|
|
33
34
|
static flags: {
|
|
34
35
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
35
36
|
daemon: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
37
|
+
update: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
36
38
|
};
|
|
37
39
|
run(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Update an installed CLI in place (OSK-10096).
|
|
42
|
+
*
|
|
43
|
+
* Asks the inventory first rather than posting blind: a daemon older than
|
|
44
|
+
* managed updates has no handler for the request and would leave it to time
|
|
45
|
+
* out, and a CLI that updates some other way (the copy inside Devin.app) has a
|
|
46
|
+
* reason the daemon already words. Both are answered before anything runs.
|
|
47
|
+
*/
|
|
48
|
+
private update;
|
|
49
|
+
/**
|
|
50
|
+
* Start an install or update, and survive a gateway that cut the request off.
|
|
51
|
+
*
|
|
52
|
+
* The relay holds the request while the daemon answers, and a gateway can end
|
|
53
|
+
* it first: an update returned HTTP 504 after 21.9s while the job it started
|
|
54
|
+
* ran to success, and the caller was told the outcome was unknown (OSK-10096
|
|
55
|
+
* verifier). The daemon is the only party that knows whether the request
|
|
56
|
+
* arrived, so on a server-side failure we ask it, and follow the job it
|
|
57
|
+
* started rather than reporting a result we do not have.
|
|
58
|
+
*/
|
|
59
|
+
private start;
|
|
60
|
+
/** Follow a job to its end, or report why it could not be followed (and return nothing). */
|
|
61
|
+
private follow;
|
|
38
62
|
/** What the daemon says about this provider right now, best effort. */
|
|
39
63
|
private installerNote;
|
|
40
64
|
private daemonIdFor;
|
|
41
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* The next step after "Already installed.": how to update it, or where its
|
|
68
|
+
* update comes from — or nothing from a daemon that cannot say (OSK-10096).
|
|
69
|
+
*/
|
|
70
|
+
export declare function updateHint(provider: string, state: ManagedCliProviderState | undefined, bin: string): string | null;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.updateHint = updateHint;
|
|
3
4
|
const core_1 = require("@oclif/core");
|
|
4
5
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
6
|
const base_command_1 = require("../../base-command");
|
|
6
7
|
const cli_installers_1 = require("../../lib/cli-installers");
|
|
7
|
-
const daemon_ref_1 = require("../../lib/daemon-ref");
|
|
8
8
|
const daemon_target_1 = require("../../lib/daemon-target");
|
|
9
9
|
/**
|
|
10
10
|
* Install one managed coding-agent CLI on a machine.
|
|
@@ -32,10 +32,11 @@ const daemon_target_1 = require("../../lib/daemon-target");
|
|
|
32
32
|
* come back as an opaque refusal.
|
|
33
33
|
*/
|
|
34
34
|
class HarnessesInstall extends base_command_1.BaseCommand {
|
|
35
|
-
static description = 'Install a managed coding-agent CLI (claude, codex, devin, …) on one of your machines';
|
|
35
|
+
static description = 'Install a managed coding-agent CLI (claude, codex, devin, …) on one of your machines, or update one already installed (--update)';
|
|
36
36
|
static examples = [
|
|
37
37
|
'<%= config.bin %> harnesses install devin',
|
|
38
38
|
'<%= config.bin %> harnesses install codex --daemon daemon_07c086e3c249_cli_default',
|
|
39
|
+
'<%= config.bin %> harnesses install claude --update',
|
|
39
40
|
];
|
|
40
41
|
static args = {
|
|
41
42
|
provider: core_1.Args.string({
|
|
@@ -49,6 +50,9 @@ class HarnessesInstall extends base_command_1.BaseCommand {
|
|
|
49
50
|
daemon: core_1.Flags.string({
|
|
50
51
|
description: 'Which machine to install on. Defaults to your only connected daemon; required when several are connected.',
|
|
51
52
|
}),
|
|
53
|
+
update: core_1.Flags.boolean({
|
|
54
|
+
description: "Update the CLI already installed, with that CLI's own updater. When a CLI updates some other way (e.g. inside its desktop app), the daemon says so instead.",
|
|
55
|
+
}),
|
|
52
56
|
};
|
|
53
57
|
async run() {
|
|
54
58
|
this.requireAuth();
|
|
@@ -66,14 +70,13 @@ class HarnessesInstall extends base_command_1.BaseCommand {
|
|
|
66
70
|
listActiveDaemons: () => (0, daemon_target_1.listConnectedDaemons)(this.resolvedCredential),
|
|
67
71
|
});
|
|
68
72
|
const daemonId = this.daemonIdFor(target);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
start = await (0, cli_installers_1.startManagedCliInstall)(daemonId, provider, this.resolvedCredential);
|
|
72
|
-
}
|
|
73
|
-
catch (err) {
|
|
74
|
-
this.handleApiError(err);
|
|
73
|
+
if (flags.update) {
|
|
74
|
+
await this.update(daemonId, provider, Boolean(flags.json));
|
|
75
75
|
return;
|
|
76
76
|
}
|
|
77
|
+
const start = await this.start(daemonId, provider, () => (0, cli_installers_1.startManagedCliInstall)(daemonId, provider, this.resolvedCredential), Boolean(flags.json));
|
|
78
|
+
if (!start)
|
|
79
|
+
return;
|
|
77
80
|
const jobId = start.job?.id;
|
|
78
81
|
if (!start.started || !jobId) {
|
|
79
82
|
// The daemon declined and already knows why. Read its own reason off the
|
|
@@ -83,27 +86,22 @@ class HarnessesInstall extends base_command_1.BaseCommand {
|
|
|
83
86
|
const reason = start.provider?.installDisabledReason?.trim() ||
|
|
84
87
|
(await this.installerNote(daemonId, provider));
|
|
85
88
|
this.log(reason ? `${provider}: ${reason}` : `${provider}: the daemon started no install job.`);
|
|
89
|
+
// "Already installed." used to be the end of the road: the product could
|
|
90
|
+
// install a CLI and then had no word about updating it (OSK-10096).
|
|
91
|
+
if (start.alreadyInstalled) {
|
|
92
|
+
const next = updateHint(provider, start.provider, this.config.bin);
|
|
93
|
+
if (next)
|
|
94
|
+
this.log(next);
|
|
95
|
+
}
|
|
86
96
|
return;
|
|
87
97
|
}
|
|
98
|
+
// The raw id: it is what `--daemon` takes, and a formatDaemonRef prefix of a
|
|
99
|
+
// `daemon_<hash>_…` id reads `d_daemon_4...` for every local daemon (OSK-10182).
|
|
88
100
|
if (!flags.json)
|
|
89
|
-
this.logToStderr(`Installing ${provider} on ${
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
jobId,
|
|
93
|
-
credential: this.resolvedCredential,
|
|
94
|
-
onLogLine: flags.json ? undefined : (line) => this.logToStderr(` ${line}`),
|
|
95
|
-
});
|
|
96
|
-
if (outcome.kind === 'missing') {
|
|
97
|
-
this.error(`The daemon no longer has install job ${jobId}.`, { exit: 1 });
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (outcome.kind === 'timeout') {
|
|
101
|
-
// Not a failure: the daemon is still working, and calling this failed
|
|
102
|
-
// would send someone to debug a download that is going fine.
|
|
103
|
-
this.error(`Still installing after ${Math.round(cli_installers_1.INSTALL_MAX_WAIT_MS / 60000)} minutes — the daemon is still working on job ${jobId}. Check \`${this.config.bin} harnesses installers\` in a while.`, { exit: 1 });
|
|
101
|
+
this.logToStderr(`Installing ${provider} on ${daemonId}…`);
|
|
102
|
+
const job = await this.follow(daemonId, jobId, 'install', Boolean(flags.json));
|
|
103
|
+
if (!job)
|
|
104
104
|
return;
|
|
105
|
-
}
|
|
106
|
-
const job = outcome.job;
|
|
107
105
|
if (flags.json) {
|
|
108
106
|
this.log(JSON.stringify({ daemonId, provider, job }, null, 2));
|
|
109
107
|
if (job.status !== 'succeeded')
|
|
@@ -123,6 +121,123 @@ class HarnessesInstall extends base_command_1.BaseCommand {
|
|
|
123
121
|
const state = await this.installerNote(daemonId, provider, true);
|
|
124
122
|
this.log(`${provider} installed.${state ? ` ${state}` : ''}`);
|
|
125
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Update an installed CLI in place (OSK-10096).
|
|
126
|
+
*
|
|
127
|
+
* Asks the inventory first rather than posting blind: a daemon older than
|
|
128
|
+
* managed updates has no handler for the request and would leave it to time
|
|
129
|
+
* out, and a CLI that updates some other way (the copy inside Devin.app) has a
|
|
130
|
+
* reason the daemon already words. Both are answered before anything runs.
|
|
131
|
+
*/
|
|
132
|
+
async update(daemonId, provider, json) {
|
|
133
|
+
let inventory;
|
|
134
|
+
try {
|
|
135
|
+
inventory = await (0, cli_installers_1.listCliInstallers)(daemonId, this.resolvedCredential);
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
this.handleApiError(err);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const row = (inventory.providers ?? []).find((p) => (0, data_provider_1.normalizeManagedCliProvider)(p.provider) === provider);
|
|
142
|
+
if (!row || row.updatable === undefined) {
|
|
143
|
+
this.failWithCliError({
|
|
144
|
+
message: `The daemon ${daemonId} (${inventory.daemonVersion ?? 'version not reported'}) cannot update ${provider}: it predates managed updates. ` +
|
|
145
|
+
`Update the daemon, then run this again — \`${this.config.bin} daemon status\` says how.`,
|
|
146
|
+
code: 'DAEMON_UPDATE_UNSUPPORTED',
|
|
147
|
+
exit: 1,
|
|
148
|
+
retryable: false,
|
|
149
|
+
});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!row.updatable) {
|
|
153
|
+
this.failWithCliError({
|
|
154
|
+
message: `${provider}: ${row.updateDisabledReason?.trim() || 'the daemon cannot update it here.'}`,
|
|
155
|
+
code: 'CLI_NOT_UPDATABLE',
|
|
156
|
+
exit: 1,
|
|
157
|
+
retryable: false,
|
|
158
|
+
});
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const start = await this.start(daemonId, provider, () => (0, cli_installers_1.startManagedCliUpdate)(daemonId, provider, this.resolvedCredential), json);
|
|
162
|
+
if (!start)
|
|
163
|
+
return;
|
|
164
|
+
const jobId = start.job?.id;
|
|
165
|
+
if (!start.started || !jobId) {
|
|
166
|
+
this.log(start.alreadyRunning
|
|
167
|
+
? `${provider}: an install or update is already running (job ${start.job?.id ?? 'unknown'}).`
|
|
168
|
+
: `${provider}: ${start.provider?.updateDisabledReason?.trim() || 'the daemon started no update job.'}`);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!json) {
|
|
172
|
+
this.logToStderr(`Updating ${provider} ${row.version ? `${row.version} ` : ''}on ${daemonId}…`);
|
|
173
|
+
}
|
|
174
|
+
const job = await this.follow(daemonId, jobId, 'update', json);
|
|
175
|
+
if (!job)
|
|
176
|
+
return;
|
|
177
|
+
if (json) {
|
|
178
|
+
this.log(JSON.stringify({ daemonId, provider, job }, null, 2));
|
|
179
|
+
if (job.status !== 'succeeded')
|
|
180
|
+
process.exitCode = 1;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (job.status === 'failed') {
|
|
184
|
+
this.error(`Updating ${provider} failed: ${job.error || 'the daemon gave no reason.'}`, {
|
|
185
|
+
exit: 1,
|
|
186
|
+
});
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
this.log((0, cli_installers_1.updateOutcomeLine)(job) ??
|
|
190
|
+
`${provider} updated${job.installedVersion ? ` to ${job.installedVersion}` : ''}.`);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Start an install or update, and survive a gateway that cut the request off.
|
|
194
|
+
*
|
|
195
|
+
* The relay holds the request while the daemon answers, and a gateway can end
|
|
196
|
+
* it first: an update returned HTTP 504 after 21.9s while the job it started
|
|
197
|
+
* ran to success, and the caller was told the outcome was unknown (OSK-10096
|
|
198
|
+
* verifier). The daemon is the only party that knows whether the request
|
|
199
|
+
* arrived, so on a server-side failure we ask it, and follow the job it
|
|
200
|
+
* started rather than reporting a result we do not have.
|
|
201
|
+
*/
|
|
202
|
+
async start(daemonId, provider, begin, json) {
|
|
203
|
+
try {
|
|
204
|
+
return await begin();
|
|
205
|
+
}
|
|
206
|
+
catch (err) {
|
|
207
|
+
const status = err.status ?? 0;
|
|
208
|
+
if (status >= 500 || status === 408) {
|
|
209
|
+
const job = await (0, cli_installers_1.jobStartedDespiteTimeout)(daemonId, provider, this.resolvedCredential);
|
|
210
|
+
if (job?.id) {
|
|
211
|
+
if (!json) {
|
|
212
|
+
this.logToStderr(`The request to ${daemonId} did not come back (HTTP ${status}), but the daemon started job ${job.id}. Following it.`);
|
|
213
|
+
}
|
|
214
|
+
return { started: true, job };
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
this.handleApiError(err);
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/** Follow a job to its end, or report why it could not be followed (and return nothing). */
|
|
222
|
+
async follow(daemonId, jobId, verb, json) {
|
|
223
|
+
const outcome = await (0, cli_installers_1.followManagedCliInstallJob)({
|
|
224
|
+
daemonId,
|
|
225
|
+
jobId,
|
|
226
|
+
credential: this.resolvedCredential,
|
|
227
|
+
onLogLine: json ? undefined : (line) => this.logToStderr(` ${line}`),
|
|
228
|
+
});
|
|
229
|
+
if (outcome.kind === 'missing') {
|
|
230
|
+
this.error(`The daemon no longer has ${verb} job ${jobId}.`, { exit: 1 });
|
|
231
|
+
return undefined;
|
|
232
|
+
}
|
|
233
|
+
if (outcome.kind === 'timeout') {
|
|
234
|
+
// Not a failure: the daemon is still working, and calling this failed
|
|
235
|
+
// would send someone to debug a download that is going fine.
|
|
236
|
+
this.error(`Still ${verb === 'install' ? 'installing' : 'updating'} after ${Math.round(cli_installers_1.INSTALL_MAX_WAIT_MS / 60000)} minutes — the daemon is still working on job ${jobId}. Check \`${this.config.bin} harnesses installers\` in a while.`, { exit: 1 });
|
|
237
|
+
return undefined;
|
|
238
|
+
}
|
|
239
|
+
return outcome.job;
|
|
240
|
+
}
|
|
126
241
|
/** What the daemon says about this provider right now, best effort. */
|
|
127
242
|
async installerNote(daemonId, provider, readiness = false) {
|
|
128
243
|
try {
|
|
@@ -175,3 +290,16 @@ class HarnessesInstall extends base_command_1.BaseCommand {
|
|
|
175
290
|
}
|
|
176
291
|
}
|
|
177
292
|
exports.default = HarnessesInstall;
|
|
293
|
+
/**
|
|
294
|
+
* The next step after "Already installed.": how to update it, or where its
|
|
295
|
+
* update comes from — or nothing from a daemon that cannot say (OSK-10096).
|
|
296
|
+
*/
|
|
297
|
+
function updateHint(provider, state, bin) {
|
|
298
|
+
if (!state || state.updatable === undefined)
|
|
299
|
+
return null;
|
|
300
|
+
if (state.updatable) {
|
|
301
|
+
return `To update it: ${bin} harnesses install ${provider} --update`;
|
|
302
|
+
}
|
|
303
|
+
const reason = state.updateDisabledReason?.trim();
|
|
304
|
+
return reason ? `Updating: ${reason}` : null;
|
|
305
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
|
+
import { type ManagedCliProviderState } from '../../lib/cli-installers';
|
|
2
3
|
/**
|
|
3
4
|
* What each managed provider's state is on one machine.
|
|
4
5
|
*
|
|
@@ -43,3 +44,12 @@ export default class HarnessesInstallers extends BaseCommand {
|
|
|
43
44
|
*/
|
|
44
45
|
private daemonIdFor;
|
|
45
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* What each installed CLI's update story is, as sentences under the table.
|
|
49
|
+
*
|
|
50
|
+
* Sentences, not a column: an update reason is "the copy inside Devin.app
|
|
51
|
+
* updates with that app", which no column fits. Silent for a daemon that
|
|
52
|
+
* reports no update fields at all — it cannot say, and inventing "no" for it
|
|
53
|
+
* would be worse than nothing.
|
|
54
|
+
*/
|
|
55
|
+
export declare function updateNotes(providers: readonly ManagedCliProviderState[], bin: string): string[];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.updateNotes = updateNotes;
|
|
3
4
|
const core_1 = require("@oclif/core");
|
|
4
5
|
const base_command_1 = require("../../base-command");
|
|
5
6
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
@@ -126,6 +127,14 @@ class HarnessesInstallers extends base_command_1.BaseCommand {
|
|
|
126
127
|
for (const note of notes)
|
|
127
128
|
this.log(` ${note.provider}: ${note.detail}`);
|
|
128
129
|
}
|
|
130
|
+
// The VERSION column is where a reader asks "can I get a newer one", and
|
|
131
|
+
// the answer lived only in `--json` (OSK-10096 verifier).
|
|
132
|
+
const updateLines = updateNotes(providers, this.config.bin);
|
|
133
|
+
if (updateLines.length > 0) {
|
|
134
|
+
this.log('');
|
|
135
|
+
for (const line of updateLines)
|
|
136
|
+
this.log(` ${line}`);
|
|
137
|
+
}
|
|
129
138
|
// A provider the platform knows and this daemon did not mention. Without
|
|
130
139
|
// this it simply is not on the list, which reads as dropped support rather
|
|
131
140
|
// than as an older daemon — `devin` arrived in the 0.8.45 daemon release, so 0.8.35
|
|
@@ -177,3 +186,25 @@ class HarnessesInstallers extends base_command_1.BaseCommand {
|
|
|
177
186
|
}
|
|
178
187
|
}
|
|
179
188
|
exports.default = HarnessesInstallers;
|
|
189
|
+
/**
|
|
190
|
+
* What each installed CLI's update story is, as sentences under the table.
|
|
191
|
+
*
|
|
192
|
+
* Sentences, not a column: an update reason is "the copy inside Devin.app
|
|
193
|
+
* updates with that app", which no column fits. Silent for a daemon that
|
|
194
|
+
* reports no update fields at all — it cannot say, and inventing "no" for it
|
|
195
|
+
* would be worse than nothing.
|
|
196
|
+
*/
|
|
197
|
+
function updateNotes(providers, bin) {
|
|
198
|
+
const lines = [];
|
|
199
|
+
for (const provider of providers) {
|
|
200
|
+
if (provider.updatable === true) {
|
|
201
|
+
lines.push(`${provider.provider}: update it with \`${bin} harnesses install ${provider.provider} --update\``);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
for (const provider of providers) {
|
|
205
|
+
if (provider.updatable === false && provider.installed && provider.updateDisabledReason) {
|
|
206
|
+
lines.push(`${provider.provider}: ${provider.updateDisabledReason}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return lines;
|
|
210
|
+
}
|
|
@@ -421,8 +421,9 @@ class HarnessesList extends base_command_1.BaseCommand {
|
|
|
421
421
|
// mode. Materialize that default before the request so JSON cannot quietly
|
|
422
422
|
// return every historical offline row while the table shows seven live ones.
|
|
423
423
|
const status = flags.online ? 'online' : (flags.status ?? (flags.all ? undefined : 'online'));
|
|
424
|
-
// Any spelling of the first-party harness filters the same rows; the
|
|
425
|
-
// asked for the canonical one, which it matches against every
|
|
424
|
+
// Any accepted spelling of the first-party harness filters the same rows; the
|
|
425
|
+
// server is asked for the canonical one, which it matches against every
|
|
426
|
+
// accepted stored spelling.
|
|
426
427
|
const provider = flags.provider ? (0, harness_provider_input_1.canonicalHarnessInput)(flags.provider) : undefined;
|
|
427
428
|
const params = new URLSearchParams();
|
|
428
429
|
const serverStatus = serverStatusParam(status);
|
|
@@ -140,35 +140,26 @@ class SkillsImportAsActions extends base_command_1.BaseCommand {
|
|
|
140
140
|
this.handleApiError(err);
|
|
141
141
|
return;
|
|
142
142
|
}
|
|
143
|
-
const failed =
|
|
143
|
+
const { rows, summary, failed } = (0, local_skills_1.summarizeSkillActionImport)(result);
|
|
144
144
|
if (flags.json) {
|
|
145
145
|
this.log(JSON.stringify(result, null, 2));
|
|
146
146
|
}
|
|
147
147
|
else {
|
|
148
|
-
(0, format_1.renderTable)(
|
|
149
|
-
skill: r.name,
|
|
150
|
-
import: r.status,
|
|
151
|
-
action: r.actionCreated
|
|
152
|
-
? 'created'
|
|
153
|
-
: (r.actionSkippedReason ??
|
|
154
|
-
r.actionError ??
|
|
155
|
-
(r.status === 'failed' ? '-' : 'not added')),
|
|
156
|
-
detail: r.error ?? '',
|
|
157
|
-
})), [
|
|
148
|
+
(0, format_1.renderTable)(rows, [
|
|
158
149
|
{ key: 'skill', header: 'SKILL' },
|
|
159
150
|
{ key: 'import', header: 'IMPORT' },
|
|
160
151
|
{ key: 'action', header: 'ACTION' },
|
|
161
152
|
{ key: 'detail', header: 'DETAIL' },
|
|
162
153
|
], (line) => this.log(line));
|
|
163
|
-
this.log(
|
|
164
|
-
`${failed.length} skill(s) failed.`);
|
|
154
|
+
this.log(summary);
|
|
165
155
|
if (result.actionsError) {
|
|
166
156
|
this.log(`The skills imported, but their actions could not be added: ${result.actionsError.message}`);
|
|
167
157
|
}
|
|
168
158
|
}
|
|
169
159
|
// Partial success is normal and reported above; exit non-zero only when
|
|
170
|
-
// something the caller asked for did not happen
|
|
171
|
-
|
|
160
|
+
// something the caller asked for did not happen — including an action the
|
|
161
|
+
// full library could not take.
|
|
162
|
+
if (failed)
|
|
172
163
|
this.exit(1);
|
|
173
164
|
}
|
|
174
165
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* `skrr tasks attachments set-role <task> <attachment> --role …`
|
|
4
|
+
*
|
|
5
|
+
* The one way to change what an existing asset IS on a Task. Its real use is
|
|
6
|
+
* promotion: an agent may only upload `evidence` or `deliverable`, because an
|
|
7
|
+
* `input` is forwarded into every future Run of the Task — so when an agent's
|
|
8
|
+
* screenshot should become an input, a person promotes it here. The server
|
|
9
|
+
* refuses the call from an agent, whatever credential it holds, and moves the
|
|
10
|
+
* asset's ledger link with the role.
|
|
11
|
+
*/
|
|
12
|
+
export default class TasksAttachmentsSetRole extends BaseCommand {
|
|
13
|
+
static description: string;
|
|
14
|
+
static examples: string[];
|
|
15
|
+
static args: {
|
|
16
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
17
|
+
attachmentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
18
|
+
};
|
|
19
|
+
static flags: {
|
|
20
|
+
role: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
22
|
+
};
|
|
23
|
+
run(): Promise<void>;
|
|
24
|
+
}
|