@skrr-ai/cli 0.1.50 → 0.1.52
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 +7 -0
- package/dist/base-command.js +95 -8
- package/dist/commands/agents/chat.d.ts +14 -0
- package/dist/commands/agents/chat.js +37 -1
- package/dist/commands/agents/create.js +8 -4
- package/dist/commands/agents/show.d.ts +9 -0
- package/dist/commands/agents/show.js +53 -0
- package/dist/commands/agents/update.d.ts +32 -0
- package/dist/commands/agents/update.js +81 -9
- package/dist/commands/balance/show.d.ts +34 -0
- package/dist/commands/balance/show.js +92 -2
- package/dist/commands/commitments/doctor.d.ts +1 -0
- package/dist/commands/commitments/doctor.js +19 -4
- package/dist/commands/commitments/effective-policy.js +15 -1
- package/dist/commands/commitments/next-tick.js +12 -2
- package/dist/commands/commitments/preflight.js +10 -0
- package/dist/commands/commitments/show.js +11 -1
- package/dist/commands/context.js +2 -1
- package/dist/commands/daemon/config.d.ts +15 -0
- package/dist/commands/daemon/config.js +53 -0
- package/dist/commands/daemon/index.js +1 -0
- package/dist/commands/daemon/status.js +15 -1
- package/dist/commands/doctor.js +8 -1
- package/dist/commands/harnesses/models.d.ts +54 -0
- package/dist/commands/harnesses/models.js +100 -7
- package/dist/commands/harnesses/show.d.ts +11 -0
- package/dist/commands/harnesses/show.js +35 -0
- package/dist/commands/harnesses/update.d.ts +28 -1
- package/dist/commands/harnesses/update.js +73 -10
- package/dist/commands/login.d.ts +2 -1
- package/dist/commands/login.js +26 -7
- package/dist/commands/logout.js +3 -0
- package/dist/commands/machines/dedicated/show.js +25 -2
- package/dist/commands/outbox/list.d.ts +12 -1
- package/dist/commands/outbox/list.js +31 -2
- package/dist/commands/revoke-daemon.js +9 -0
- package/dist/commands/spaces/create.d.ts +1 -0
- package/dist/commands/spaces/create.js +17 -0
- package/dist/commands/spaces/show.js +5 -0
- package/dist/commands/spaces/update.d.ts +1 -0
- package/dist/commands/spaces/update.js +16 -0
- package/dist/commands/tasks/create.d.ts +6 -0
- package/dist/commands/tasks/create.js +31 -6
- package/dist/commands/tasks/show.js +45 -0
- package/dist/commands/tasks/start.d.ts +14 -0
- package/dist/commands/tasks/start.js +31 -1
- package/dist/commands/tasks/update.d.ts +1 -0
- package/dist/commands/tasks/update.js +29 -1
- package/dist/commands/tasks/upsert.d.ts +1 -0
- package/dist/commands/tasks/upsert.js +16 -0
- package/dist/commands/whoami.js +5 -3
- package/dist/lib/agent-config.d.ts +2 -0
- package/dist/lib/agent-config.js +14 -10
- package/dist/lib/agentic-stream.d.ts +88 -1
- package/dist/lib/agentic-stream.js +330 -9
- package/dist/lib/auth-core-init.d.ts +2 -10
- package/dist/lib/auth-core-init.js +20 -67
- package/dist/lib/auth-failure.d.ts +33 -0
- package/dist/lib/auth-failure.js +68 -0
- package/dist/lib/auth-storage.d.ts +24 -0
- package/dist/lib/auth-storage.js +342 -11
- package/dist/lib/cli-id.d.ts +22 -0
- package/dist/lib/cli-id.js +56 -0
- package/dist/lib/cli-identity.d.ts +49 -0
- package/dist/lib/cli-identity.js +159 -0
- package/dist/lib/commitments.d.ts +25 -1
- package/dist/lib/commitments.js +46 -1
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.js +15 -3
- package/dist/lib/daemonBroker.d.ts +37 -1
- package/dist/lib/daemonBroker.js +146 -22
- package/dist/lib/daemonBrokerRefusal.d.ts +51 -0
- package/dist/lib/daemonBrokerRefusal.js +127 -0
- package/dist/lib/dedicated-machines.d.ts +33 -2
- package/dist/lib/dedicated-machines.js +50 -1
- package/dist/lib/delegated-cli.js +20 -1
- package/dist/lib/exec-runtime-binary.d.ts +3 -1
- package/dist/lib/exec-runtime-binary.js +4 -2
- package/dist/lib/first-party-harness-broker.js +10 -5
- package/dist/lib/harnesses.d.ts +10 -0
- package/dist/lib/keychain.d.ts +39 -0
- package/dist/lib/keychain.js +228 -39
- package/dist/lib/label-ref.d.ts +34 -0
- package/dist/lib/label-ref.js +68 -1
- package/dist/lib/live-runtime-binary.d.ts +40 -0
- package/dist/lib/live-runtime-binary.js +193 -0
- package/dist/lib/login.d.ts +23 -0
- package/dist/lib/login.js +67 -21
- package/dist/lib/message-intent.js +3 -1
- package/dist/lib/oauthLogin.js +2 -8
- package/dist/lib/refresh.js +6 -2
- package/dist/lib/tasks.d.ts +20 -0
- package/dist/lib/tasks.js +87 -8
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialEnvelopeBridge.js +26 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.d.ts +28 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/daemonToolApproval.js +23 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +25 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/loopbackHttp.js +268 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineId.js +11 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.d.ts +36 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/machineUuid.js +156 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.d.ts +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/refresh.js +8 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +61 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.d.ts +2 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialEnvelopeBridge.js +25 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.d.ts +28 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/daemonToolApproval.js +22 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +41 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.d.ts +32 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/loopbackHttp.js +257 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineId.js +11 -10
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.d.ts +36 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/machineUuid.js +147 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.d.ts +8 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/refresh.js +8 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +22 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +58 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +21 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3139 -3097
- package/oclif.manifest.json +28591 -28438
- package/package.json +1 -1
|
@@ -1,9 +1,95 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CURATED_ACCEPTS_ANY_ID = void 0;
|
|
4
|
+
exports.describeModelDefaults = describeModelDefaults;
|
|
5
|
+
exports.curatedReasonClause = curatedReasonClause;
|
|
6
|
+
exports.describeModelSource = describeModelSource;
|
|
7
|
+
exports.describeEmptyModels = describeEmptyModels;
|
|
3
8
|
const core_1 = require("@oclif/core");
|
|
4
9
|
const base_command_1 = require("../../base-command");
|
|
5
10
|
const format_1 = require("../../lib/format");
|
|
6
11
|
const harnesses_1 = require("../../lib/harnesses");
|
|
12
|
+
/**
|
|
13
|
+
* What `default` means on this harness, in lines under the table (OSK-12114).
|
|
14
|
+
*
|
|
15
|
+
* Three candidates can answer "which model runs on `default`": the harness
|
|
16
|
+
* row's stored default, the harness's own configuration on that machine (for
|
|
17
|
+
* Codex, `model` in its config.toml), and the vendor's recommendation that the
|
|
18
|
+
* live list marks. They win in that order, and the list alone cannot see the
|
|
19
|
+
* middle one — so say the order rather than imply the marked model runs.
|
|
20
|
+
*/
|
|
21
|
+
function describeModelDefaults(report) {
|
|
22
|
+
const provider = report?.provider || 'the harness';
|
|
23
|
+
const lines = [];
|
|
24
|
+
const marked = (report?.models ?? []).find((m) => m.isDefault)?.value;
|
|
25
|
+
if (report?.defaultModel) {
|
|
26
|
+
lines.push(`Harness default: ${report.defaultModel} — what an agent on \`default\` runs on this harness.`);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
lines.push(`No harness default is set, so an agent on \`default\` runs the model the daemon's config ` +
|
|
30
|
+
`or ${provider}'s own config on that machine names` +
|
|
31
|
+
`${marked ? `, else ${marked} (* — ${provider}'s own recommendation)` : ''}.`);
|
|
32
|
+
}
|
|
33
|
+
if (report?.defaultReasoningEffort) {
|
|
34
|
+
lines.push(`Harness default effort: ${report.defaultReasoningEffort}.`);
|
|
35
|
+
}
|
|
36
|
+
lines.push('The model a turn actually ran is reported when it ends (`agents chat` prints `[run] model:`).');
|
|
37
|
+
return lines;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Stated under every curated answer: the list is ours, not the harness's
|
|
41
|
+
* admission rule. Without it `curated` reads as "these are the only choices"
|
|
42
|
+
* — the web card has always said this; the CLI never did.
|
|
43
|
+
*/
|
|
44
|
+
exports.CURATED_ACCEPTS_ANY_ID = 'Any model id the harness itself accepts works too, even if it is not listed — set it with `skrr agents update <agent> --model <id>`.';
|
|
45
|
+
/**
|
|
46
|
+
* Why a curated answer is curated, as a clause. `codex · curated` used to
|
|
47
|
+
* print identically for an offline daemon, a daemon too old to probe, and a
|
|
48
|
+
* probe that timed out — three different next actions (OSK-12062).
|
|
49
|
+
*/
|
|
50
|
+
function curatedReasonClause(report) {
|
|
51
|
+
const provider = report?.provider || 'this harness';
|
|
52
|
+
const detail = report?.sourceDetail ? ` (${report.sourceDetail})` : '';
|
|
53
|
+
switch (report?.sourceReason) {
|
|
54
|
+
case 'no_live_catalog_for_harness':
|
|
55
|
+
return `${provider} has no way to list its own models, so this list is the catalog`;
|
|
56
|
+
case 'daemon_offline':
|
|
57
|
+
return `the machine running ${provider} is offline, so its live model list could not be read`;
|
|
58
|
+
case 'live_catalog_unsupported':
|
|
59
|
+
return `the daemon on that machine does not offer a live model list — update it to get one`;
|
|
60
|
+
case 'live_probe_failed':
|
|
61
|
+
return `asking ${provider} for its live model list failed${detail}`;
|
|
62
|
+
case undefined:
|
|
63
|
+
case '':
|
|
64
|
+
return 'the server did not say why the live list was not used';
|
|
65
|
+
default:
|
|
66
|
+
return `live list not used: ${report?.sourceReason}${detail}`;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/** One line saying where the list came from. */
|
|
70
|
+
function describeModelSource(report) {
|
|
71
|
+
switch (report?.source) {
|
|
72
|
+
case 'managed':
|
|
73
|
+
return 'managed — the models your plan admits for this harness';
|
|
74
|
+
case 'live':
|
|
75
|
+
return `live — reported by the installed ${report.provider || 'harness'} on its machine`;
|
|
76
|
+
case 'curated':
|
|
77
|
+
return `curated — skrr's own list; ${curatedReasonClause(report)}`;
|
|
78
|
+
default:
|
|
79
|
+
return report?.source ?? 'unknown source';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** What to print when the answer carries no models at all. */
|
|
83
|
+
function describeEmptyModels(report) {
|
|
84
|
+
const resolved = report?.provider;
|
|
85
|
+
if (!resolved) {
|
|
86
|
+
return 'This harness resolved to no provider, so no model list could be built. That is a defect, not a configuration problem — report it with the harness id.';
|
|
87
|
+
}
|
|
88
|
+
// An empty answer is always curated: a live or managed answer with no models
|
|
89
|
+
// degrades to curated server-side. So the reason is what explains it — for
|
|
90
|
+
// opencode it is usually the live probe, which the old message never named.
|
|
91
|
+
return `No models are listed for ${resolved}: skrr's curated list has no entries for it, and ${curatedReasonClause(report)}. ${exports.CURATED_ACCEPTS_ANY_ID}`;
|
|
92
|
+
}
|
|
7
93
|
/**
|
|
8
94
|
* Which models this harness can actually run.
|
|
9
95
|
*
|
|
@@ -44,29 +130,36 @@ class HarnessesModels extends base_command_1.BaseCommand {
|
|
|
44
130
|
// where the list comes from picks one — and, in the bug this replaced,
|
|
45
131
|
// would have shown `provider` as null on a Claude harness and named the
|
|
46
132
|
// defect on sight.
|
|
47
|
-
|
|
48
|
-
this.log(resolved
|
|
49
|
-
? `No models are listed for ${resolved}. skrr keeps a curated list per provider and ${resolved} has no entries in it, so pick a model on the agent instead — \`skrr agents update <agent> --model <id>\` accepts an id this list does not name.`
|
|
50
|
-
: 'This harness resolved to no provider, so no model list could be built. That is a defect, not a configuration problem — report it with the harness id.');
|
|
133
|
+
this.log(describeEmptyModels(report));
|
|
51
134
|
return;
|
|
52
135
|
}
|
|
53
136
|
if (report?.provider || report?.source) {
|
|
54
137
|
// The source matters when the list is short: `curated` is our table,
|
|
55
|
-
// anything else came from the harness itself
|
|
56
|
-
|
|
138
|
+
// anything else came from the harness itself — and a curated answer
|
|
139
|
+
// says why the harness's own list was not used.
|
|
140
|
+
this.log(`${report.provider ?? 'unknown provider'} · ${describeModelSource(report)}`);
|
|
141
|
+
if (report.source === 'curated')
|
|
142
|
+
this.log(exports.CURATED_ACCEPTS_ANY_ID);
|
|
57
143
|
this.log('');
|
|
58
144
|
}
|
|
59
145
|
(0, format_1.renderTable)(models.map((m) => ({
|
|
60
|
-
|
|
146
|
+
// `*` is the harness's own recommendation, which the JSON carried as
|
|
147
|
+
// `isDefault` and this table dropped (OSK-12114).
|
|
148
|
+
model: `${(0, format_1.formatCell)(m.value)}${m.isDefault ? ' *' : ''}`,
|
|
61
149
|
label: (0, format_1.formatCell)(m.label),
|
|
62
150
|
// An empty efforts array means the model takes no effort setting at
|
|
63
151
|
// all, which is different from one we failed to read.
|
|
64
152
|
efforts: m.efforts && m.efforts.length > 0 ? m.efforts.join(', ') : '-',
|
|
153
|
+
defaultEffort: m.defaultEffort ? String(m.defaultEffort) : '-',
|
|
65
154
|
})), [
|
|
66
155
|
{ key: 'model', header: 'MODEL' },
|
|
67
156
|
{ key: 'label', header: 'LABEL', maxWidth: 26 },
|
|
68
157
|
{ key: 'efforts', header: 'EFFORTS' },
|
|
158
|
+
{ key: 'defaultEffort', header: 'DEFAULT EFFORT' },
|
|
69
159
|
], (line) => this.log(line));
|
|
160
|
+
this.log('');
|
|
161
|
+
for (const line of describeModelDefaults(report))
|
|
162
|
+
this.log(line);
|
|
70
163
|
}
|
|
71
164
|
}
|
|
72
165
|
exports.default = HarnessesModels;
|
|
@@ -10,6 +10,17 @@ export default class HarnessesShow extends BaseCommand {
|
|
|
10
10
|
};
|
|
11
11
|
run(): Promise<void>;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Which permission modes this harness accepts, and its own default (OSK-12130).
|
|
15
|
+
*
|
|
16
|
+
* The rule used to surface only as an agent write refusing a mode, so the way
|
|
17
|
+
* to learn that Gemini runs only as bypassPermissions was three refusals in a
|
|
18
|
+
* row. Printed only when the server sent it (a local harness row).
|
|
19
|
+
*/
|
|
20
|
+
export declare function harnessPermissionLines(harness: {
|
|
21
|
+
permissionModes?: unknown;
|
|
22
|
+
defaultPermissionMode?: unknown;
|
|
23
|
+
}): string[];
|
|
13
24
|
/**
|
|
14
25
|
* Say what the caller DID pass, not only what they did not.
|
|
15
26
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.harnessPermissionLines = harnessPermissionLines;
|
|
3
4
|
exports.describeWrongHarnessRef = describeWrongHarnessRef;
|
|
4
5
|
exports.resolveHarnessRef = resolveHarnessRef;
|
|
5
6
|
exports.pickHarnessRef = pickHarnessRef;
|
|
@@ -113,6 +114,8 @@ class HarnessesShow extends base_command_1.BaseCommand {
|
|
|
113
114
|
}
|
|
114
115
|
if (harness.daemonId)
|
|
115
116
|
this.log(` daemon ${harness.daemonId}`);
|
|
117
|
+
for (const line of harnessPermissionLines(harness))
|
|
118
|
+
this.log(line);
|
|
116
119
|
if (verdict) {
|
|
117
120
|
this.log(` readiness ${verdict.label} — ${verdict.detail}`);
|
|
118
121
|
}
|
|
@@ -124,6 +127,38 @@ class HarnessesShow extends base_command_1.BaseCommand {
|
|
|
124
127
|
}
|
|
125
128
|
}
|
|
126
129
|
exports.default = HarnessesShow;
|
|
130
|
+
const PERMISSION_MODES_SOURCE_LABELS = {
|
|
131
|
+
daemon: 'reported by the daemon',
|
|
132
|
+
daemon_record: 'as the daemon last reported it',
|
|
133
|
+
static: "from skrr's harness table",
|
|
134
|
+
vocabulary: 'not declared for this harness; the daemon enforces',
|
|
135
|
+
};
|
|
136
|
+
/**
|
|
137
|
+
* Which permission modes this harness accepts, and its own default (OSK-12130).
|
|
138
|
+
*
|
|
139
|
+
* The rule used to surface only as an agent write refusing a mode, so the way
|
|
140
|
+
* to learn that Gemini runs only as bypassPermissions was three refusals in a
|
|
141
|
+
* row. Printed only when the server sent it (a local harness row).
|
|
142
|
+
*/
|
|
143
|
+
function harnessPermissionLines(harness) {
|
|
144
|
+
const report = harness.permissionModes;
|
|
145
|
+
const modes = Array.isArray(report?.modes)
|
|
146
|
+
? report.modes.filter((m) => typeof m === 'string')
|
|
147
|
+
: [];
|
|
148
|
+
if (modes.length === 0)
|
|
149
|
+
return [];
|
|
150
|
+
const source = typeof report?.source === 'string'
|
|
151
|
+
? (PERMISSION_MODES_SOURCE_LABELS[report.source] ?? report.source)
|
|
152
|
+
: '';
|
|
153
|
+
const stored = typeof harness.defaultPermissionMode === 'string' && harness.defaultPermissionMode
|
|
154
|
+
? harness.defaultPermissionMode
|
|
155
|
+
: null;
|
|
156
|
+
return [
|
|
157
|
+
` permission accepts ${modes.join(', ')}${source ? ` (${source})` : ''}`,
|
|
158
|
+
` default ${stored ??
|
|
159
|
+
"not set — an agent's own mode applies, else the daemon's config (`harnesses update --default-permission-mode`)"}`,
|
|
160
|
+
];
|
|
161
|
+
}
|
|
127
162
|
/**
|
|
128
163
|
* Say what the caller DID pass, not only what they did not.
|
|
129
164
|
*
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
2
|
import { getHarness } from '../../lib/harnesses';
|
|
3
3
|
/**
|
|
4
|
-
* Rename
|
|
4
|
+
* Rename a harness, change how Claude Code runs on it, or set the model,
|
|
5
|
+
* reasoning effort and permission mode its sessions default to.
|
|
5
6
|
*
|
|
6
7
|
* `--name` renames it FOR EVERYONE it is shared with. `skrr harnesses alias`
|
|
7
8
|
* changes only what you see; the two are different operations and the server
|
|
@@ -21,9 +22,35 @@ export default class HarnessesUpdate extends BaseCommand {
|
|
|
21
22
|
name: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
22
23
|
description: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
24
|
'claude-engine': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
25
|
+
'default-model': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
26
|
+
'clear-default-model': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
27
|
+
'default-effort': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
28
|
+
'clear-default-effort': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
29
|
+
'default-permission-mode': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
30
|
+
'clear-default-permission-mode': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
24
31
|
};
|
|
25
32
|
run(): Promise<void>;
|
|
26
33
|
}
|
|
34
|
+
export interface HarnessUpdateFlags {
|
|
35
|
+
name?: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
'claude-engine'?: string;
|
|
38
|
+
'default-model'?: string;
|
|
39
|
+
'clear-default-model'?: boolean;
|
|
40
|
+
'default-effort'?: string;
|
|
41
|
+
'clear-default-effort'?: boolean;
|
|
42
|
+
'default-permission-mode'?: string;
|
|
43
|
+
'clear-default-permission-mode'?: boolean;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Flags → the `patch` of `PATCH /api/harnesses/:id`. The clear spellings are
|
|
47
|
+
* the server's own: an empty string clears `defaultModel` and
|
|
48
|
+
* `defaultReasoningEffort`, `null` clears `defaultPermissionMode`. Clearing is
|
|
49
|
+
* a separate boolean flag rather than a sentinel value because `none` is a
|
|
50
|
+
* real reasoning effort (GPT-5.x accepts it), so `--default-effort none` has
|
|
51
|
+
* to mean "set it to none", not "remove it".
|
|
52
|
+
*/
|
|
53
|
+
export declare function buildHarnessPatch(flags: HarnessUpdateFlags): Record<string, unknown>;
|
|
27
54
|
/**
|
|
28
55
|
* `--name` sets the name everyone sees. When YOUR alias covers it, you will not
|
|
29
56
|
* see the new name anywhere you look, and "Updated <name>." read as though the
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildHarnessPatch = buildHarnessPatch;
|
|
3
4
|
exports.sharedRenameMessage = sharedRenameMessage;
|
|
4
5
|
const core_1 = require("@oclif/core");
|
|
6
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
7
|
const base_command_1 = require("../../base-command");
|
|
6
8
|
const harnesses_1 = require("../../lib/harnesses");
|
|
7
9
|
const harness_labels_1 = require("../../lib/harness-labels");
|
|
8
10
|
/**
|
|
9
|
-
* Rename
|
|
11
|
+
* Rename a harness, change how Claude Code runs on it, or set the model,
|
|
12
|
+
* reasoning effort and permission mode its sessions default to.
|
|
10
13
|
*
|
|
11
14
|
* `--name` renames it FOR EVERYONE it is shared with. `skrr harnesses alias`
|
|
12
15
|
* changes only what you see; the two are different operations and the server
|
|
@@ -16,10 +19,13 @@ const harness_labels_1 = require("../../lib/harness-labels");
|
|
|
16
19
|
* version first and a concurrent edit answers 409 instead of overwriting.
|
|
17
20
|
*/
|
|
18
21
|
class HarnessesUpdate extends base_command_1.BaseCommand {
|
|
19
|
-
static description = 'Rename a harness
|
|
22
|
+
static description = 'Rename a harness, set how Claude Code is driven on it, or set its default model, effort, and permission mode';
|
|
20
23
|
static examples = [
|
|
21
24
|
'<%= config.bin %> harnesses update <harness-id> --name "Studio iMac"',
|
|
22
25
|
'<%= config.bin %> harnesses update <harness-id> --claude-engine sdk',
|
|
26
|
+
'<%= config.bin %> harnesses update <harness-id> --default-model opus --default-effort high',
|
|
27
|
+
'<%= config.bin %> harnesses update <harness-id> --default-permission-mode acceptEdits',
|
|
28
|
+
'<%= config.bin %> harnesses update <harness-id> --clear-default-model --clear-default-effort',
|
|
23
29
|
];
|
|
24
30
|
static args = {
|
|
25
31
|
harness: core_1.Args.string({ ignoreStdin: true, description: 'Harness ID', required: true }),
|
|
@@ -34,19 +40,45 @@ class HarnessesUpdate extends base_command_1.BaseCommand {
|
|
|
34
40
|
description: 'How Claude Code runs on this machine: `sdk` in-process (needed for AskUserQuestion), `cli` as a child process. Local CLAUDE harnesses only — the server refuses it on any other provider, because nothing would ever read it.',
|
|
35
41
|
options: ['cli', 'sdk'],
|
|
36
42
|
}),
|
|
43
|
+
'default-model': core_1.Flags.string({
|
|
44
|
+
description: "Model this harness's sessions use when the agent or task names none, in the harness's own " +
|
|
45
|
+
'namespace (`opus`, `gpt-5-codex`, …). `skrr harnesses models <harness-id>` lists what it can run. ' +
|
|
46
|
+
'Not allowlisted: any safe model identifier is stored as given, so check the spelling.',
|
|
47
|
+
exclusive: ['clear-default-model'],
|
|
48
|
+
}),
|
|
49
|
+
'clear-default-model': core_1.Flags.boolean({
|
|
50
|
+
description: "Remove the default model, so the harness's own default applies",
|
|
51
|
+
exclusive: ['default-model'],
|
|
52
|
+
}),
|
|
53
|
+
'default-effort': core_1.Flags.string({
|
|
54
|
+
description: "Reasoning effort this harness's sessions default to. Checked against the model the write leaves " +
|
|
55
|
+
'in place (the new --default-model when you pass both); `skrr harnesses models <harness-id>` lists ' +
|
|
56
|
+
'the efforts each model accepts. Harnesses with no effort setting refuse it.',
|
|
57
|
+
exclusive: ['clear-default-effort'],
|
|
58
|
+
}),
|
|
59
|
+
'clear-default-effort': core_1.Flags.boolean({
|
|
60
|
+
description: "Remove the default reasoning effort, so the harness's own default applies",
|
|
61
|
+
exclusive: ['default-effort'],
|
|
62
|
+
}),
|
|
63
|
+
'default-permission-mode': core_1.Flags.string({
|
|
64
|
+
description: "Permission mode this harness's sessions fall back to when the agent sets none. Local harnesses " +
|
|
65
|
+
'only. One-shot harnesses (gemini, opencode, cursor, copilot, goose, pi, openclaw) can only run as ' +
|
|
66
|
+
'bypassPermissions; any other mode is refused for them.',
|
|
67
|
+
options: [...data_provider_1.LOCAL_HARNESS_PERMISSION_MODES],
|
|
68
|
+
exclusive: ['clear-default-permission-mode'],
|
|
69
|
+
}),
|
|
70
|
+
'clear-default-permission-mode': core_1.Flags.boolean({
|
|
71
|
+
description: "Remove this harness's permission-mode override, so the daemon's own configured default applies",
|
|
72
|
+
exclusive: ['default-permission-mode'],
|
|
73
|
+
}),
|
|
37
74
|
};
|
|
38
75
|
async run() {
|
|
39
76
|
this.requireAuth();
|
|
40
77
|
const { args, flags } = await this.parse(HarnessesUpdate);
|
|
41
|
-
const patch =
|
|
42
|
-
if (flags.name !== undefined)
|
|
43
|
-
patch.name = flags.name;
|
|
44
|
-
if (flags.description !== undefined)
|
|
45
|
-
patch.description = flags.description;
|
|
46
|
-
if (flags['claude-engine'] !== undefined)
|
|
47
|
-
patch.claudeEngine = flags['claude-engine'];
|
|
78
|
+
const patch = buildHarnessPatch(flags);
|
|
48
79
|
if (Object.keys(patch).length === 0) {
|
|
49
|
-
this.error('Nothing to change. Pass --name, --description,
|
|
80
|
+
this.error('Nothing to change. Pass --name, --description, --claude-engine, --default-model, ' +
|
|
81
|
+
'--default-effort, --default-permission-mode, or a --clear-default-* flag.', { exit: 1 });
|
|
50
82
|
return;
|
|
51
83
|
}
|
|
52
84
|
try {
|
|
@@ -65,6 +97,37 @@ class HarnessesUpdate extends base_command_1.BaseCommand {
|
|
|
65
97
|
}
|
|
66
98
|
}
|
|
67
99
|
exports.default = HarnessesUpdate;
|
|
100
|
+
/**
|
|
101
|
+
* Flags → the `patch` of `PATCH /api/harnesses/:id`. The clear spellings are
|
|
102
|
+
* the server's own: an empty string clears `defaultModel` and
|
|
103
|
+
* `defaultReasoningEffort`, `null` clears `defaultPermissionMode`. Clearing is
|
|
104
|
+
* a separate boolean flag rather than a sentinel value because `none` is a
|
|
105
|
+
* real reasoning effort (GPT-5.x accepts it), so `--default-effort none` has
|
|
106
|
+
* to mean "set it to none", not "remove it".
|
|
107
|
+
*/
|
|
108
|
+
function buildHarnessPatch(flags) {
|
|
109
|
+
const patch = {};
|
|
110
|
+
if (flags.name !== undefined)
|
|
111
|
+
patch.name = flags.name;
|
|
112
|
+
if (flags.description !== undefined)
|
|
113
|
+
patch.description = flags.description;
|
|
114
|
+
if (flags['claude-engine'] !== undefined)
|
|
115
|
+
patch.claudeEngine = flags['claude-engine'];
|
|
116
|
+
if (flags['default-model'] !== undefined)
|
|
117
|
+
patch.defaultModel = flags['default-model'];
|
|
118
|
+
if (flags['clear-default-model'])
|
|
119
|
+
patch.defaultModel = '';
|
|
120
|
+
if (flags['default-effort'] !== undefined)
|
|
121
|
+
patch.defaultReasoningEffort = flags['default-effort'];
|
|
122
|
+
if (flags['clear-default-effort'])
|
|
123
|
+
patch.defaultReasoningEffort = '';
|
|
124
|
+
if (flags['default-permission-mode'] !== undefined) {
|
|
125
|
+
patch.defaultPermissionMode = flags['default-permission-mode'];
|
|
126
|
+
}
|
|
127
|
+
if (flags['clear-default-permission-mode'])
|
|
128
|
+
patch.defaultPermissionMode = null;
|
|
129
|
+
return patch;
|
|
130
|
+
}
|
|
68
131
|
/**
|
|
69
132
|
* `--name` sets the name everyone sees. When YOUR alias covers it, you will not
|
|
70
133
|
* see the new name anywhere you look, and "Updated <name>." read as though the
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -44,7 +44,8 @@ export default class Login extends BaseCommand {
|
|
|
44
44
|
*
|
|
45
45
|
* `cliId` resolution priority:
|
|
46
46
|
* 1. `OVERSKY_CLI_ID` env var — explicit, wins over everything.
|
|
47
|
-
* 2. `cliConfig
|
|
47
|
+
* 2. `resolveCliId(cliConfig)` — the id a prior `skrr login` of this
|
|
48
|
+
* profile presents (its own, or the machine's).
|
|
48
49
|
* Honored so a same-machine restore "just works" without forcing
|
|
49
50
|
* the operator to re-export the cliId.
|
|
50
51
|
* 3. error — without a cliId the refresh row binding can't be matched
|
package/dist/commands/login.js
CHANGED
|
@@ -7,6 +7,7 @@ const base_command_1 = require("../base-command");
|
|
|
7
7
|
const daemonHandoff_1 = require("../lib/daemonHandoff");
|
|
8
8
|
const daemon_setup_1 = require("../lib/daemon-setup");
|
|
9
9
|
const config_1 = require("../lib/config");
|
|
10
|
+
const cli_id_1 = require("../lib/cli-id");
|
|
10
11
|
const keychain_1 = require("../lib/keychain");
|
|
11
12
|
const login_1 = require("../lib/login");
|
|
12
13
|
const device_code_1 = require("../lib/device-code");
|
|
@@ -40,16 +41,33 @@ const prompt_1 = require("../lib/prompt");
|
|
|
40
41
|
* `cliLoginWithRefreshToken` for the full rationale.
|
|
41
42
|
*/
|
|
42
43
|
class Login extends base_command_1.BaseCommand {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
// OSK-12152 — the help used to describe only the browser flow. `skrr login`
|
|
45
|
+
// asks the local daemon FIRST, and nothing said so; nor that what it stores
|
|
46
|
+
// differs from what plain commands borrow from the daemon on their own.
|
|
47
|
+
static description = 'Sign in to a skrr instance and store a durable session for this profile.\n\n' +
|
|
48
|
+
'It tries, in order:\n' +
|
|
49
|
+
' 1. the skrr background service on this computer, when one is running and signed in to the ' +
|
|
50
|
+
'same server — it mints this profile a CLI session with no browser;\n' +
|
|
51
|
+
' 2. a browser sign-in through a localhost callback (PKCE);\n' +
|
|
52
|
+
' 3. a device code — under SSH, with --device-code, or when the callback cannot run.\n' +
|
|
53
|
+
'If the background service answers but refuses, skrr says why before moving on.\n\n' +
|
|
54
|
+
'What it stores: a refresh session in this profile’s credential store (the macOS Keychain, ' +
|
|
55
|
+
'or ~/.skrr/cli-auth.json elsewhere) that keeps renewing itself. Plain skrr commands on a ' +
|
|
56
|
+
'computer whose background service is signed in need no login at all: they borrow a ' +
|
|
57
|
+
'short-lived token from it for each run and store nothing (an older background service ' +
|
|
58
|
+
'mints them a stored session instead). On a Dedicated Runtime, where the service only ' +
|
|
59
|
+
'lends short-lived tokens, nothing is stored.\n\n' +
|
|
60
|
+
'Set OVERSKY_SKIP_DAEMON_BROKER=1 to skip step 1 (--device-code skips it too). After signing ' +
|
|
61
|
+
'in, skrr also signs the background service in, and on an interactive first login on a ' +
|
|
62
|
+
'computer without it, offers to install it. Set SKRR_SKIP_DAEMON_HANDOFF=1 to skip that ' +
|
|
63
|
+
'optional setup; it does not skip step 1.';
|
|
47
64
|
static examples = [
|
|
48
65
|
'<%= config.bin %> login',
|
|
49
66
|
'<%= config.bin %> login --env prod',
|
|
50
67
|
'<%= config.bin %> login --base-url https://skrr.ai',
|
|
51
68
|
'<%= config.bin %> login --server http://localhost:3090',
|
|
52
69
|
'<%= config.bin %> login --device-code',
|
|
70
|
+
'OVERSKY_SKIP_DAEMON_BROKER=1 <%= config.bin %> login',
|
|
53
71
|
'<%= config.bin %> login --port 1455',
|
|
54
72
|
'<%= config.bin %> login --recovery-code "W7K2J-X9P3M-R6Y4Q-..."',
|
|
55
73
|
'OVERSKY_REFRESH_TOKEN=... OVERSKY_CLI_ID=... <%= config.bin %> login',
|
|
@@ -244,7 +262,8 @@ class Login extends base_command_1.BaseCommand {
|
|
|
244
262
|
*
|
|
245
263
|
* `cliId` resolution priority:
|
|
246
264
|
* 1. `OVERSKY_CLI_ID` env var — explicit, wins over everything.
|
|
247
|
-
* 2. `cliConfig
|
|
265
|
+
* 2. `resolveCliId(cliConfig)` — the id a prior `skrr login` of this
|
|
266
|
+
* profile presents (its own, or the machine's).
|
|
248
267
|
* Honored so a same-machine restore "just works" without forcing
|
|
249
268
|
* the operator to re-export the cliId.
|
|
250
269
|
* 3. error — without a cliId the refresh row binding can't be matched
|
|
@@ -252,8 +271,8 @@ class Login extends base_command_1.BaseCommand {
|
|
|
252
271
|
* pointing at `skrr whoami` on the original box.
|
|
253
272
|
*/
|
|
254
273
|
async runRefreshTokenLogin(refreshToken) {
|
|
255
|
-
|
|
256
|
-
const cliId =
|
|
274
|
+
// `resolveCliId` already puts OVERSKY_CLI_ID / SKRR_CLI_ID first.
|
|
275
|
+
const cliId = (0, cli_id_1.resolveCliId)(this.cliConfig);
|
|
257
276
|
if (!cliId) {
|
|
258
277
|
this.error('OVERSKY_REFRESH_TOKEN is set but no cliId is available. The refresh\n' +
|
|
259
278
|
'row is bound to the cliId it was issued for; without it the server\n' +
|
package/dist/commands/logout.js
CHANGED
|
@@ -244,6 +244,9 @@ class Logout extends base_command_1.BaseCommand {
|
|
|
244
244
|
(0, config_1.saveConfig)({
|
|
245
245
|
baseURL: cfg.baseURL,
|
|
246
246
|
...(cfg.cliId ? { cliId: cfg.cliId } : {}),
|
|
247
|
+
// A named profile's own identity outlives a session exactly as the
|
|
248
|
+
// machine's does, so its next login re-presents it (OSK-12156).
|
|
249
|
+
...(cfg.profileCliId ? { profileCliId: cfg.profileCliId } : {}),
|
|
247
250
|
...(agentSetting.value ? { [first_party_harness_managed_1.AGENT_CONFIG_KEY]: agentSetting.value } : {}),
|
|
248
251
|
...(cfg.credentials ? { credentials: cfg.credentials } : {}),
|
|
249
252
|
migrated: { ...(cfg.migrated ?? {}), authOutOfConfig: true },
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_1 = require("@oclif/core");
|
|
4
4
|
const dedicated_lease_command_1 = require("../../../lib/dedicated-lease-command");
|
|
5
|
+
const daemon_installer_1 = require("../../../lib/daemon-installer");
|
|
5
6
|
const dedicated_machines_1 = require("../../../lib/dedicated-machines");
|
|
6
7
|
const harnesses_1 = require("../../../lib/harnesses");
|
|
7
8
|
class DedicatedMachinesShow extends dedicated_lease_command_1.DedicatedLeaseCommand {
|
|
@@ -40,6 +41,12 @@ class DedicatedMachinesShow extends dedicated_lease_command_1.DedicatedLeaseComm
|
|
|
40
41
|
catch {
|
|
41
42
|
harnesses = [];
|
|
42
43
|
}
|
|
44
|
+
// The latest daemon RELEASE, from the same signed feed a daemon updates
|
|
45
|
+
// from. The lease says which daemon the approved image carries; only this
|
|
46
|
+
// says whether a newer release exists that no approved image carries yet —
|
|
47
|
+
// which is what `skrrd update --check` on the machine reports (OSK-12153).
|
|
48
|
+
// Best-effort: an unreachable feed is said, never read as "none newer".
|
|
49
|
+
const latestDaemonRelease = lease.health?.image ? await readLatestDaemonRelease() : null;
|
|
43
50
|
if (flags.json) {
|
|
44
51
|
const engines = (0, dedicated_machines_1.dedicatedLeaseHarnesses)(lease, harnesses).map((harness) => ({
|
|
45
52
|
harnessId: harness._id,
|
|
@@ -47,11 +54,27 @@ class DedicatedMachinesShow extends dedicated_lease_command_1.DedicatedLeaseComm
|
|
|
47
54
|
status: harness.status,
|
|
48
55
|
lastSeenAt: harness.lastSeenAt,
|
|
49
56
|
}));
|
|
50
|
-
this.log(JSON.stringify({
|
|
57
|
+
this.log(JSON.stringify({
|
|
58
|
+
...response,
|
|
59
|
+
harnesses: engines,
|
|
60
|
+
...(latestDaemonRelease ? { latestDaemonRelease } : {}),
|
|
61
|
+
}, null, 2));
|
|
51
62
|
return;
|
|
52
63
|
}
|
|
53
|
-
for (const line of (0, dedicated_machines_1.describeDedicatedLease)(lease, harnesses))
|
|
64
|
+
for (const line of (0, dedicated_machines_1.describeDedicatedLease)(lease, harnesses, { latestDaemonRelease })) {
|
|
54
65
|
this.log(line);
|
|
66
|
+
}
|
|
55
67
|
}
|
|
56
68
|
}
|
|
57
69
|
exports.default = DedicatedMachinesShow;
|
|
70
|
+
async function readLatestDaemonRelease() {
|
|
71
|
+
try {
|
|
72
|
+
const result = await (0, daemon_installer_1.fetchVerifiedDaemonManifest)();
|
|
73
|
+
if (result.ok)
|
|
74
|
+
return { version: result.manifest.version };
|
|
75
|
+
return { error: result.code };
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
return { error: err?.message || 'unreadable' };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
|
+
import { type OutboxEntry } from '../../lib/outbox';
|
|
3
|
+
/**
|
|
4
|
+
* Why one entry was queued, in words (OSK-12121). The queue holds more than
|
|
5
|
+
* outage casualties — a 401 is queued too, so authored content survives a lapsed
|
|
6
|
+
* sign-in — and a list that described every entry as "the API was unavailable"
|
|
7
|
+
* sent a signed-out reader to wait for an outage that was not happening.
|
|
8
|
+
*/
|
|
9
|
+
export declare function queuedBecause(entry: Pick<OutboxEntry, 'reason'>): {
|
|
10
|
+
label: string;
|
|
11
|
+
kind: 'auth' | 'throttled' | 'unavailable' | 'other';
|
|
12
|
+
};
|
|
2
13
|
export declare function outboxListSnapshot(profile?: string): {
|
|
3
|
-
queued:
|
|
14
|
+
queued: OutboxEntry[];
|
|
4
15
|
incident: import("../../lib/outbox").IncidentState | null;
|
|
5
16
|
incidentActive: boolean;
|
|
6
17
|
};
|
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.queuedBecause = queuedBecause;
|
|
3
4
|
exports.outboxListSnapshot = outboxListSnapshot;
|
|
4
5
|
const core_1 = require("@oclif/core");
|
|
5
6
|
const base_command_1 = require("../../base-command");
|
|
6
7
|
const keychain_1 = require("../../lib/keychain");
|
|
7
8
|
const format_1 = require("../../lib/format");
|
|
8
9
|
const outbox_1 = require("../../lib/outbox");
|
|
10
|
+
/**
|
|
11
|
+
* Why one entry was queued, in words (OSK-12121). The queue holds more than
|
|
12
|
+
* outage casualties — a 401 is queued too, so authored content survives a lapsed
|
|
13
|
+
* sign-in — and a list that described every entry as "the API was unavailable"
|
|
14
|
+
* sent a signed-out reader to wait for an outage that was not happening.
|
|
15
|
+
*/
|
|
16
|
+
function queuedBecause(entry) {
|
|
17
|
+
const status = entry.reason?.status;
|
|
18
|
+
if (status === 401)
|
|
19
|
+
return { label: 'sign-in refused (401)', kind: 'auth' };
|
|
20
|
+
if (status === 429)
|
|
21
|
+
return { label: 'throttled (429)', kind: 'throttled' };
|
|
22
|
+
if ((typeof status === 'number' && status >= 500) || (0, outbox_1.isOutageFailure)(entry.reason)) {
|
|
23
|
+
return { label: status ? `unavailable (${status})` : 'unreachable', kind: 'unavailable' };
|
|
24
|
+
}
|
|
25
|
+
return { label: status ? `HTTP ${status}` : 'unknown', kind: 'other' };
|
|
26
|
+
}
|
|
9
27
|
function outboxListSnapshot(profile) {
|
|
10
28
|
const queued = (0, outbox_1.listWrites)(profile);
|
|
11
29
|
const incident = (0, outbox_1.readIncident)(profile);
|
|
@@ -24,7 +42,7 @@ function outboxListSnapshot(profile) {
|
|
|
24
42
|
* where they find out.
|
|
25
43
|
*/
|
|
26
44
|
class OutboxList extends base_command_1.BaseCommand {
|
|
27
|
-
static description = 'Show writes queued locally because the
|
|
45
|
+
static description = 'Show writes queued locally because the server could not take them: an outage, throttling, or a lapsed sign-in';
|
|
28
46
|
static examples = [
|
|
29
47
|
'<%= config.bin %> outbox list',
|
|
30
48
|
'<%= config.bin %> outbox list --json',
|
|
@@ -46,7 +64,9 @@ class OutboxList extends base_command_1.BaseCommand {
|
|
|
46
64
|
`${incident.consecutive} consecutive failure(s).`);
|
|
47
65
|
this.log('');
|
|
48
66
|
}
|
|
49
|
-
else if (incident) {
|
|
67
|
+
else if (incident && entries.some((e) => queuedBecause(e).kind === 'unavailable')) {
|
|
68
|
+
// A past incident is context only for entries an outage put here. Beside
|
|
69
|
+
// a queue of sign-in refusals it reads as the explanation, and is not.
|
|
50
70
|
this.log(`Last service incident: ${incident.baseURL} failed at ${incident.lastOutageAt}` +
|
|
51
71
|
`${incident.status ? ` (HTTP ${incident.status})` : ''}. ` +
|
|
52
72
|
'No current outage is confirmed.');
|
|
@@ -61,6 +81,7 @@ class OutboxList extends base_command_1.BaseCommand {
|
|
|
61
81
|
queued: e.queuedAt,
|
|
62
82
|
command: e.command,
|
|
63
83
|
summary: e.summary,
|
|
84
|
+
why: queuedBecause(e).label,
|
|
64
85
|
// Surface the attempt count only once it matters; a column of "0"s is
|
|
65
86
|
// noise, but an entry nearing the cap needs a human.
|
|
66
87
|
attempts: e.attempts > 0 ? `${e.attempts}/${outbox_1.MAX_REPLAY_ATTEMPTS}` : '-',
|
|
@@ -69,6 +90,7 @@ class OutboxList extends base_command_1.BaseCommand {
|
|
|
69
90
|
{ key: 'queued', header: 'QUEUED', maxWidth: 24 },
|
|
70
91
|
{ key: 'command', header: 'COMMAND', maxWidth: 24 },
|
|
71
92
|
{ key: 'summary', header: 'SUMMARY', maxWidth: 40 },
|
|
93
|
+
{ key: 'why', header: 'WHY', maxWidth: 24 },
|
|
72
94
|
{ key: 'attempts', header: 'TRIES' },
|
|
73
95
|
], (line) => this.log(line));
|
|
74
96
|
const exhausted = entries.filter((e) => e.attempts >= outbox_1.MAX_REPLAY_ATTEMPTS);
|
|
@@ -77,6 +99,13 @@ class OutboxList extends base_command_1.BaseCommand {
|
|
|
77
99
|
this.log(`${exhausted.length} entry(ies) hit the ${outbox_1.MAX_REPLAY_ATTEMPTS}-attempt cap and will not be ` +
|
|
78
100
|
'retried automatically. Inspect the failure, then retry or drop them explicitly.');
|
|
79
101
|
}
|
|
102
|
+
const authQueued = entries.filter((e) => queuedBecause(e).kind === 'auth').length;
|
|
103
|
+
if (authQueued > 0) {
|
|
104
|
+
this.log('');
|
|
105
|
+
this.log(`${authQueued} entry(ies) were queued because the server refused this sign-in, not because ` +
|
|
106
|
+
`it was down. Sign in with \`${this.config.bin} login\` first; a replay before that is ` +
|
|
107
|
+
'refused the same way.');
|
|
108
|
+
}
|
|
80
109
|
this.log('');
|
|
81
110
|
this.log(`Replay: ${this.config.bin} outbox retry`);
|
|
82
111
|
this.log(`Discard: ${this.config.bin} outbox drop <id>`);
|
|
@@ -67,6 +67,8 @@ class RevokeDaemon extends base_command_1.BaseCommand {
|
|
|
67
67
|
const host = resolvedDaemon?.machineUuid
|
|
68
68
|
? resolvedDaemon.machineUuid.slice(0, 12)
|
|
69
69
|
: (resolvedDaemon?.os ?? 'unknown host');
|
|
70
|
+
this.log('Revoking a daemon also revokes every CLI sign-in it brokered — on its machine, ' +
|
|
71
|
+
`\`${this.config.bin} login\` without a browser signs in through the daemon.`);
|
|
70
72
|
const confirmed = await this.confirm(`Revoke daemon "${name}" on ${host}? [y/N] `);
|
|
71
73
|
if (!confirmed) {
|
|
72
74
|
this.log('Aborted.');
|
|
@@ -99,6 +101,13 @@ class RevokeDaemon extends base_command_1.BaseCommand {
|
|
|
99
101
|
const daemonId = (res.daemon?.id ?? resolvedId).slice(0, 12);
|
|
100
102
|
if (res.revoked) {
|
|
101
103
|
this.log(`Revoked daemon ${daemonName} (${daemonId}).`);
|
|
104
|
+
if (res.callerSessionRevoked === true) {
|
|
105
|
+
// Said now, not discovered later as a refresh failure: the sign-in this
|
|
106
|
+
// command used was brokered by that daemon and died with it.
|
|
107
|
+
this.warn(`This also signed out ${this.config.bin} on this machine: its sign-in was brokered ` +
|
|
108
|
+
`by that daemon and was revoked with it. Run \`${this.config.bin} daemon login\` to ` +
|
|
109
|
+
`re-enroll the daemon, then \`${this.config.bin} login\` to sign the CLI in again.`);
|
|
110
|
+
}
|
|
102
111
|
// exit 0 (default)
|
|
103
112
|
}
|
|
104
113
|
else if (res.alreadyRevoked) {
|