@skrr-ai/cli 0.1.37 → 0.1.38
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/bin/dev-fallback.js +167 -4
- package/dist/base-command.js +46 -2
- package/dist/commands/agents/actions/create.js +18 -7
- package/dist/commands/agents/actions/delete.d.ts +1 -0
- package/dist/commands/agents/actions/delete.js +7 -0
- package/dist/commands/agents/actions/update.js +5 -3
- package/dist/commands/agents/api-actions/add.js +5 -2
- package/dist/commands/agents/api-actions/index.d.ts +11 -0
- package/dist/commands/agents/api-actions/index.js +27 -1
- package/dist/commands/agents/avatar/default/index.d.ts +11 -0
- package/dist/commands/agents/avatar/default/index.js +17 -0
- package/dist/commands/agents/avatar/default/set.d.ts +2 -1
- package/dist/commands/agents/avatar/default/set.js +32 -7
- package/dist/commands/agents/avatar/upload.js +7 -1
- package/dist/commands/agents/categories/delete.js +8 -0
- package/dist/commands/agents/categories/index.d.ts +21 -5
- package/dist/commands/agents/categories/index.js +68 -15
- package/dist/commands/agents/chat.d.ts +21 -0
- package/dist/commands/agents/chat.js +94 -7
- package/dist/commands/agents/create.js +11 -4
- package/dist/commands/agents/list.d.ts +15 -0
- package/dist/commands/agents/list.js +31 -1
- package/dist/commands/agents/scene-background/upload.d.ts +30 -0
- package/dist/commands/agents/scene-background/upload.js +97 -0
- package/dist/commands/agents/show.js +13 -0
- package/dist/commands/agents/tools/calls.d.ts +23 -5
- package/dist/commands/agents/tools/calls.js +81 -18
- package/dist/commands/agents/update.js +15 -5
- package/dist/commands/code/{oversky-agent.d.ts → skrr-agent.d.ts} +3 -3
- package/dist/commands/code/{oversky-agent.js → skrr-agent.js} +11 -8
- package/dist/commands/convos/messages.d.ts +13 -0
- package/dist/commands/convos/messages.js +40 -16
- package/dist/commands/daemon/index.js +8 -1
- package/dist/commands/daemon/logs.d.ts +26 -0
- package/dist/commands/daemon/logs.js +56 -0
- package/dist/commands/daemon/uninstall.js +1 -1
- package/dist/commands/followups/act.js +3 -2
- package/dist/commands/followups/cancel.d.ts +6 -3
- package/dist/commands/followups/cancel.js +16 -6
- package/dist/commands/followups/observability.d.ts +23 -0
- package/dist/commands/followups/observability.js +114 -0
- package/dist/commands/followups/reschedule.js +1 -1
- package/dist/commands/followups/resolve.js +3 -3
- package/dist/commands/followups/show.js +2 -1
- package/dist/commands/followups/watch.js +9 -5
- package/dist/commands/harnesses/forget.js +28 -0
- package/dist/commands/harnesses/install.d.ts +41 -0
- package/dist/commands/harnesses/install.js +165 -0
- package/dist/commands/harnesses/installers.d.ts +45 -0
- package/dist/commands/harnesses/installers.js +155 -0
- package/dist/commands/harnesses/list.d.ts +82 -1
- package/dist/commands/harnesses/list.js +104 -12
- package/dist/commands/harnesses/models.js +10 -1
- package/dist/commands/harnesses/show.d.ts +15 -0
- package/dist/commands/harnesses/show.js +44 -3
- package/dist/commands/harnesses/update.js +1 -1
- package/dist/commands/harnesses/usage.d.ts +25 -0
- package/dist/commands/harnesses/usage.js +24 -2
- package/dist/commands/instructions/status.js +13 -1
- package/dist/commands/machines/dedicated/catalog.js +4 -15
- package/dist/commands/machines/dedicated/create.d.ts +8 -0
- package/dist/commands/machines/dedicated/create.js +58 -1
- package/dist/commands/machines/dedicated/destroy.js +18 -4
- package/dist/commands/machines/dedicated/list.js +9 -6
- package/dist/commands/machines/hosted/list.js +12 -0
- package/dist/commands/machines/list.js +11 -0
- package/dist/commands/machines/show.d.ts +1 -0
- package/dist/commands/machines/show.js +40 -11
- package/dist/commands/profiles/list.d.ts +23 -0
- package/dist/commands/profiles/list.js +96 -0
- package/dist/commands/search/skills.d.ts +11 -1
- package/dist/commands/search/skills.js +26 -8
- package/dist/commands/skills/details.d.ts +2 -0
- package/dist/commands/skills/details.js +24 -1
- package/dist/commands/skills/import-as-actions.d.ts +32 -0
- package/dist/commands/skills/import-as-actions.js +175 -0
- package/dist/commands/skills/install.d.ts +2 -0
- package/dist/commands/skills/install.js +24 -1
- package/dist/commands/skills/invocable.js +6 -2
- package/dist/commands/skills/local.d.ts +40 -0
- package/dist/commands/skills/local.js +160 -0
- package/dist/commands/skills/search.d.ts +11 -1
- package/dist/commands/skills/search.js +29 -8
- package/dist/commands/spaces/actions/add.js +23 -5
- package/dist/commands/spaces/actions/available.js +3 -0
- package/dist/commands/spaces/actions/list.d.ts +5 -0
- package/dist/commands/spaces/actions/list.js +49 -3
- package/dist/commands/spaces/actions/run.d.ts +28 -0
- package/dist/commands/spaces/actions/run.js +146 -0
- package/dist/commands/spaces/actions/update.js +27 -5
- package/dist/commands/spaces/label-summaries.d.ts +27 -0
- package/dist/commands/spaces/label-summaries.js +64 -0
- package/dist/commands/spaces/task-rollups.d.ts +5 -0
- package/dist/commands/spaces/task-rollups.js +15 -4
- package/dist/commands/spaces/workflows/lifecycle-diagnostics.js +63 -3
- package/dist/commands/tasks/actions/attach.js +32 -7
- package/dist/commands/tasks/complete.js +13 -0
- package/dist/commands/tasks/create.js +1 -1
- package/dist/commands/tasks/events/append.js +29 -6
- package/dist/commands/tasks/expectations/assess.d.ts +1 -0
- package/dist/commands/tasks/expectations/assess.js +43 -5
- package/dist/commands/tasks/expectations.js +77 -16
- package/dist/commands/tasks/follow-up.js +2 -2
- package/dist/commands/tasks/lifecycle/definitions.js +21 -2
- package/dist/commands/tasks/lifecycle/detach.d.ts +7 -0
- package/dist/commands/tasks/lifecycle/detach.js +43 -3
- package/dist/commands/tasks/lifecycle/migrate.d.ts +2 -0
- package/dist/commands/tasks/lifecycle/migrate.js +42 -3
- package/dist/commands/tasks/lifecycle/reopen.d.ts +2 -0
- package/dist/commands/tasks/lifecycle/reopen.js +40 -3
- package/dist/commands/tasks/lifecycle/transition.js +33 -2
- package/dist/commands/tasks/list.js +16 -9
- package/dist/commands/tasks/move.js +11 -13
- package/dist/commands/tasks/review-queue.d.ts +15 -0
- package/dist/commands/tasks/review-queue.js +180 -31
- package/dist/commands/tasks/show.d.ts +11 -0
- package/dist/commands/tasks/show.js +105 -12
- package/dist/commands/tasks/trust.js +7 -2
- package/dist/commands/tasks/update.js +46 -10
- package/dist/commands/tasks/updates/add.js +1 -1
- package/dist/commands/token/list.d.ts +22 -0
- package/dist/commands/token/list.js +68 -0
- package/dist/commands/token/revoke.d.ts +23 -0
- package/dist/commands/token/revoke.js +53 -0
- package/dist/lib/agent-actions.d.ts +57 -2
- package/dist/lib/agent-actions.js +113 -21
- package/dist/lib/agent-pin-mute.d.ts +10 -5
- package/dist/lib/agent-pin-mute.js +17 -4
- package/dist/lib/agent-visual-refs.d.ts +27 -0
- package/dist/lib/agent-visual-refs.js +127 -2
- package/dist/lib/agentic-stream.d.ts +6 -0
- package/dist/lib/agentic-stream.js +34 -3
- package/dist/lib/assignee-resolver.d.ts +1 -1
- package/dist/lib/assignee-resolver.js +8 -0
- package/dist/lib/bulk-task-targets.d.ts +12 -0
- package/dist/lib/bulk-task-targets.js +34 -1
- package/dist/lib/cli-installers.d.ts +130 -0
- package/dist/lib/cli-installers.js +145 -0
- package/dist/lib/command-miss.js +55 -7
- package/dist/lib/dedicated-lease-command.d.ts +13 -0
- package/dist/lib/dedicated-lease-command.js +22 -1
- package/dist/lib/dedicated-machines.d.ts +95 -1
- package/dist/lib/dedicated-machines.js +236 -2
- package/dist/lib/failed-lookup.d.ts +3 -0
- package/dist/lib/failed-lookup.js +40 -0
- package/dist/lib/first-party-harness-agent.js +9 -2
- package/dist/lib/first-party-harness-doctor.d.ts +15 -0
- package/dist/lib/first-party-harness-doctor.js +126 -27
- package/dist/lib/followups.d.ts +14 -6
- package/dist/lib/followups.js +31 -6
- package/dist/lib/harnesses.d.ts +8 -0
- package/dist/lib/instruction-input.d.ts +19 -1
- package/dist/lib/instruction-input.js +52 -1
- package/dist/lib/label-scope.d.ts +15 -0
- package/dist/lib/label-scope.js +25 -5
- package/dist/lib/local-skills.d.ts +98 -0
- package/dist/lib/local-skills.js +195 -0
- package/dist/lib/machines.d.ts +10 -10
- package/dist/lib/machines.js +23 -1
- package/dist/lib/search-query.d.ts +19 -0
- package/dist/lib/search-query.js +35 -0
- package/dist/lib/space-custom-actions.d.ts +72 -0
- package/dist/lib/space-custom-actions.js +117 -0
- package/dist/lib/space-resolver.d.ts +1 -21
- package/dist/lib/space-resolver.js +19 -10
- package/dist/lib/task-closure.d.ts +3 -2
- package/dist/lib/task-closure.js +18 -20
- package/dist/lib/task-lifecycle-output.js +117 -9
- package/dist/lib/task-ref-resolver.d.ts +1 -17
- package/dist/lib/task-ref-resolver.js +18 -8
- package/dist/lib/tasks.js +9 -3
- package/dist/node_modules/@skrr-ai/data-provider/index.js +5076 -4947
- package/oclif.manifest.json +26635 -25649
- package/package.json +9 -3
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.selectUsageRows = selectUsageRows;
|
|
4
|
+
exports.usageRowHarnessCell = usageRowHarnessCell;
|
|
3
5
|
exports.buildUsageTable = buildUsageTable;
|
|
4
6
|
const core_1 = require("@oclif/core");
|
|
5
7
|
const base_command_1 = require("../../base-command");
|
|
@@ -22,6 +24,26 @@ function count(value) {
|
|
|
22
24
|
const n = Number(value);
|
|
23
25
|
return Number.isFinite(n) ? n.toLocaleString('en-US') : '-';
|
|
24
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* The rows to render, whichever spelling the server used.
|
|
29
|
+
*
|
|
30
|
+
* `byHarness` is what it sends today. This reader asked only for `byRuntime` —
|
|
31
|
+
* the pre-rename spelling — so the fleet rollup was ALWAYS empty and the
|
|
32
|
+
* command always printed "No usage recorded.", while `--json` on the same call
|
|
33
|
+
* showed $24.93 across 46 sessions. A rollup cannot be empty while its own
|
|
34
|
+
* members are not, which is what made it findable.
|
|
35
|
+
*
|
|
36
|
+
* The old spellings stay as fallbacks, after the current one, so an older
|
|
37
|
+
* deployment still renders. They are not the primary, which is the whole bug.
|
|
38
|
+
*/
|
|
39
|
+
function selectUsageRows(payload) {
|
|
40
|
+
return payload?.byHarness ?? payload?.byRuntime ?? payload?.usage ?? [];
|
|
41
|
+
}
|
|
42
|
+
/** The HARNESS cell, under either spelling of the id. */
|
|
43
|
+
function usageRowHarnessCell(row) {
|
|
44
|
+
const id = row.harnessId ?? row.runtimeId;
|
|
45
|
+
return id ? String(id).slice(0, 12) : '-';
|
|
46
|
+
}
|
|
25
47
|
const SHARED_COLUMNS = [
|
|
26
48
|
{ key: 'provider', header: 'PROVIDER' },
|
|
27
49
|
{ key: 'sessions', header: 'SESSIONS' },
|
|
@@ -66,7 +88,7 @@ function buildUsageTable(rows, perHarness) {
|
|
|
66
88
|
...SHARED_COLUMNS,
|
|
67
89
|
],
|
|
68
90
|
rows: rows.map((r) => ({
|
|
69
|
-
harness:
|
|
91
|
+
harness: usageRowHarnessCell(r),
|
|
70
92
|
name: (0, format_1.formatCell)(r.name),
|
|
71
93
|
...shared(r),
|
|
72
94
|
})),
|
|
@@ -105,7 +127,7 @@ class HarnessesUsage extends base_command_1.BaseCommand {
|
|
|
105
127
|
// `{usage, totals}`. Neither is a superset, so read both rather than
|
|
106
128
|
// assuming one and rendering a table of dashes for the other.
|
|
107
129
|
const payload = report;
|
|
108
|
-
const rows = payload
|
|
130
|
+
const rows = selectUsageRows(payload);
|
|
109
131
|
const totals = payload?.grandTotals ?? payload?.totals;
|
|
110
132
|
if (rows.length === 0) {
|
|
111
133
|
this.log(args.harness ? 'No usage recorded for this harness.' : 'No usage recorded.');
|
|
@@ -29,6 +29,18 @@ class InstructionsStatus extends base_command_1.BaseCommand {
|
|
|
29
29
|
// only `bundleId`, so join to the bundle list — including archived/deleted,
|
|
30
30
|
// since a target can outlive its bundle. A failed lookup is non-fatal: the
|
|
31
31
|
// column falls back to the full bundle id.
|
|
32
|
+
// The live daemons, so a stalled row can tell "this machine now starts its
|
|
33
|
+
// daemon a different way" from "the daemon is gone" — and only the second
|
|
34
|
+
// earns a destructive remedy. Non-fatal: an empty list degrades to the old
|
|
35
|
+
// wording, which is a guess, not a wrong claim about a daemon we checked.
|
|
36
|
+
let liveDaemonIds = [];
|
|
37
|
+
try {
|
|
38
|
+
const daemons = await data_provider_1.dataService.getDaemons();
|
|
39
|
+
liveDaemonIds = (daemons.active || []).map((d) => d.daemonId);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Leave it empty.
|
|
43
|
+
}
|
|
32
44
|
let bundleNames = new Map();
|
|
33
45
|
try {
|
|
34
46
|
const bundleResponse = await data_provider_1.dataService.listManagedInstructionBundles({
|
|
@@ -62,7 +74,7 @@ class InstructionsStatus extends base_command_1.BaseCommand {
|
|
|
62
74
|
(target.scope === 'global' ? 'global' : target.projectPath || 'project'),
|
|
63
75
|
desired: target.desiredState,
|
|
64
76
|
applied: target.appliedVersionNumber ? `v${target.appliedVersionNumber}` : '-',
|
|
65
|
-
status: (0, instruction_input_1.describeTargetSync)(target),
|
|
77
|
+
status: (0, instruction_input_1.describeTargetSync)(target, Date.now(), liveDaemonIds),
|
|
66
78
|
})), [
|
|
67
79
|
{ key: 'id', header: 'TARGET ID' },
|
|
68
80
|
// Wide enough for a prompt name (matches `instructions list`'s NAME
|
|
@@ -41,16 +41,8 @@ class DedicatedMachinesCatalog extends dedicated_lease_command_1.DedicatedLeaseC
|
|
|
41
41
|
this.log('\nSizes (--size-preset)');
|
|
42
42
|
if (catalog.sizes.length === 0)
|
|
43
43
|
this.log(' none offered right now');
|
|
44
|
-
for (const
|
|
45
|
-
|
|
46
|
-
size.vcpus ? `${size.vcpus} vCPU` : '',
|
|
47
|
-
size.memoryGiB ? `${size.memoryGiB} GiB RAM` : '',
|
|
48
|
-
size.accelerator || '',
|
|
49
|
-
]
|
|
50
|
-
.filter(Boolean)
|
|
51
|
-
.join(' · ');
|
|
52
|
-
this.log(` ${String(size.id).padEnd(14)}${spec}`);
|
|
53
|
-
}
|
|
44
|
+
for (const line of (0, dedicated_machines_1.dedicatedCatalogSizeLines)(catalog))
|
|
45
|
+
this.log(line);
|
|
54
46
|
if (catalog.storage) {
|
|
55
47
|
const { minGb, defaultGb, maxGb } = catalog.storage;
|
|
56
48
|
this.log(`\nWorkspace (--storage-gb)\n ${minGb}–${maxGb} GiB, default ${defaultGb}; grow later, never shrink`);
|
|
@@ -64,11 +56,8 @@ class DedicatedMachinesCatalog extends dedicated_lease_command_1.DedicatedLeaseC
|
|
|
64
56
|
if (catalog.regionClasses.length) {
|
|
65
57
|
this.log(`\nRegion (--region-class)\n ${catalog.regionClasses.join(', ')}`);
|
|
66
58
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
this.log(' not published by this server');
|
|
70
|
-
for (const line of catalog.priceLines) {
|
|
71
|
-
this.log(` ${line.display || `${(0, dedicated_machines_1.formatUsdCents)(line.amountCents)} ${line.label || ''}`}`);
|
|
59
|
+
for (const line of (0, dedicated_machines_1.dedicatedCatalogPriceLineOutput)(catalog.priceLines, catalog.priceBookVersion)) {
|
|
60
|
+
this.log(line);
|
|
72
61
|
}
|
|
73
62
|
}
|
|
74
63
|
}
|
|
@@ -17,6 +17,14 @@ export default class DedicatedMachinesCreate extends DedicatedLeaseCommand {
|
|
|
17
17
|
workspace: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
18
|
'request-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
19
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
20
|
+
yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
21
|
+
'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
20
22
|
};
|
|
21
23
|
run(): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* The catalog for the summary, or null. Not through `callDedicated`, which
|
|
26
|
+
* exits on an API error: an unreachable catalog must cost the summary its
|
|
27
|
+
* detail, not stop a create the user is about to confirm anyway.
|
|
28
|
+
*/
|
|
29
|
+
private readCatalogForSummary;
|
|
22
30
|
}
|
|
@@ -4,12 +4,14 @@ const core_1 = require("@oclif/core");
|
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
const dedicated_lease_command_1 = require("../../../lib/dedicated-lease-command");
|
|
6
6
|
const dedicated_machines_1 = require("../../../lib/dedicated-machines");
|
|
7
|
+
const harnesses_1 = require("../../../lib/harnesses");
|
|
7
8
|
class DedicatedMachinesCreate extends dedicated_lease_command_1.DedicatedLeaseCommand {
|
|
8
9
|
static description = 'Request a Dedicated Runtime lease. See `machines dedicated catalog` for sizes, storage bounds and prices.';
|
|
9
10
|
static examples = [
|
|
10
11
|
'<%= config.bin %> machines dedicated create --name dev-box --size-preset standard',
|
|
11
12
|
'<%= config.bin %> machines dedicated create --name dev-box --size-preset standard --spending-limit-cents 20000',
|
|
12
|
-
'<%= config.bin %> machines dedicated create --name dev-box --size-preset standard --
|
|
13
|
+
'<%= config.bin %> machines dedicated create --name dev-box --size-preset standard --dry-run',
|
|
14
|
+
'<%= config.bin %> machines dedicated create --name dev-box --size-preset standard --spending-limit-cents 20000 --repository acme/app --yes --wait --json',
|
|
13
15
|
];
|
|
14
16
|
static flags = {
|
|
15
17
|
name: core_1.Flags.string({ description: 'Display name for the dedicated machine', required: true }),
|
|
@@ -55,6 +57,13 @@ class DedicatedMachinesCreate extends dedicated_lease_command_1.DedicatedLeaseCo
|
|
|
55
57
|
description: 'Idempotency key. Reuse it to safely retry the same create request.',
|
|
56
58
|
}),
|
|
57
59
|
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
60
|
+
yes: core_1.Flags.boolean({
|
|
61
|
+
char: 'y',
|
|
62
|
+
description: 'Skip the confirmation. A create starts billing, so it asks first; required when not at a terminal.',
|
|
63
|
+
}),
|
|
64
|
+
'dry-run': core_1.Flags.boolean({
|
|
65
|
+
description: 'Show the size, price, storage, retention and cap this create would use, and create nothing',
|
|
66
|
+
}),
|
|
58
67
|
...dedicated_lease_command_1.dedicatedWaitFlags,
|
|
59
68
|
};
|
|
60
69
|
async run() {
|
|
@@ -76,6 +85,40 @@ class DedicatedMachinesCreate extends dedicated_lease_command_1.DedicatedLeaseCo
|
|
|
76
85
|
repositoryRef: flags.repository,
|
|
77
86
|
workspaceId: flags.workspace,
|
|
78
87
|
});
|
|
88
|
+
// Say what is about to be bought before it is, in the catalog's own numbers.
|
|
89
|
+
// `destroy` has always asked first; the command that starts a meter did not
|
|
90
|
+
// (OSK-9592). A catalog read that fails only loses the summary's detail,
|
|
91
|
+
// never the confirmation itself.
|
|
92
|
+
if (flags['dry-run'] || !flags.yes) {
|
|
93
|
+
const catalog = await this.readCatalogForSummary();
|
|
94
|
+
const summary = (0, dedicated_machines_1.dedicatedCreateSummary)(catalog, {
|
|
95
|
+
name: flags.name,
|
|
96
|
+
sizePreset: flags['size-preset'],
|
|
97
|
+
storageGb: flags['storage-gb'],
|
|
98
|
+
retentionClass: flags['retention-class'],
|
|
99
|
+
spendingLimitCents: flags['spending-limit-cents'],
|
|
100
|
+
});
|
|
101
|
+
if (flags['dry-run']) {
|
|
102
|
+
if (flags.json) {
|
|
103
|
+
this.log(JSON.stringify({ dryRun: true, request: body, summary }, null, 2));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
for (const line of summary)
|
|
107
|
+
this.log(line);
|
|
108
|
+
this.log('Dry run: nothing was created.');
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (!process.stdin.isTTY) {
|
|
113
|
+
this.error('Creating a Dedicated Runtime starts billing. Pass --yes to confirm when not at a terminal, or --dry-run to see what it would create.', { exit: 2 });
|
|
114
|
+
}
|
|
115
|
+
for (const line of summary)
|
|
116
|
+
this.log(line);
|
|
117
|
+
if (!(await this.confirm('Create it? [y/N] '))) {
|
|
118
|
+
this.log('Aborted. Nothing was created.');
|
|
119
|
+
this.exit(1);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
79
122
|
const response = await this.callDedicated(() => (0, dedicated_machines_1.createDedicatedRuntime)(body));
|
|
80
123
|
const lease = response?.lease;
|
|
81
124
|
const id = (0, dedicated_machines_1.dedicatedLeaseId)(lease);
|
|
@@ -97,6 +140,20 @@ class DedicatedMachinesCreate extends dedicated_lease_command_1.DedicatedLeaseCo
|
|
|
97
140
|
this.log(`Poll: ${this.config.bin} machines dedicated show ${id} (or pass --wait)`);
|
|
98
141
|
}
|
|
99
142
|
}
|
|
143
|
+
/**
|
|
144
|
+
* The catalog for the summary, or null. Not through `callDedicated`, which
|
|
145
|
+
* exits on an API error: an unreachable catalog must cost the summary its
|
|
146
|
+
* detail, not stop a create the user is about to confirm anyway.
|
|
147
|
+
*/
|
|
148
|
+
async readCatalogForSummary() {
|
|
149
|
+
try {
|
|
150
|
+
return (0, dedicated_machines_1.dedicatedCatalogFromProducts)(await (0, harnesses_1.listHarnessProducts)());
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
process.stderr.write(`Could not read the catalog for the summary (${error?.message || 'unknown error'}); showing what was passed.\n`);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
100
157
|
}
|
|
101
158
|
exports.default = DedicatedMachinesCreate;
|
|
102
159
|
function newRequestId() {
|
|
@@ -45,12 +45,17 @@ class DedicatedMachinesDestroy extends dedicated_lease_command_1.DedicatedLeaseC
|
|
|
45
45
|
// so it does not ask. This mirrors the server's rule only to decide whether
|
|
46
46
|
// to PROMPT; the server still decides what happens.
|
|
47
47
|
let willPurge = Boolean(flags['purge-storage']);
|
|
48
|
+
// Read even under --yes when the workspace may be kept: whether a destroy
|
|
49
|
+
// keeps billing storage is worth saying afterwards, and only the lease as it
|
|
50
|
+
// stood before the destroy can say it.
|
|
51
|
+
const prior = willPurge
|
|
52
|
+
? undefined
|
|
53
|
+
: (await this.callDedicated(() => (0, dedicated_machines_1.getDedicatedRuntime)(leaseId, this.scopeFrom(flags))))
|
|
54
|
+
?.lease;
|
|
48
55
|
if (!willPurge && !flags.yes) {
|
|
49
|
-
const current = await this.callDedicated(() => (0, dedicated_machines_1.getDedicatedRuntime)(leaseId, this.scopeFrom(flags)));
|
|
50
|
-
const lease = current?.lease;
|
|
51
56
|
willPurge =
|
|
52
|
-
|
|
53
|
-
(!flags['retain-storage'] && (
|
|
57
|
+
prior?.state === 'archived' ||
|
|
58
|
+
(!flags['retain-storage'] && (prior?.storage?.retentionClass || 'none') === 'none');
|
|
54
59
|
}
|
|
55
60
|
if (willPurge && !flags.yes) {
|
|
56
61
|
if (!process.stdin.isTTY) {
|
|
@@ -69,6 +74,15 @@ class DedicatedMachinesDestroy extends dedicated_lease_command_1.DedicatedLeaseC
|
|
|
69
74
|
retentionIntent = 'retain';
|
|
70
75
|
const response = await this.callDedicated(() => (0, dedicated_machines_1.destroyDedicatedRuntime)(leaseId, { ...this.scopeFrom(flags), retentionIntent }));
|
|
71
76
|
await this.reportLeaseAction(response, flags, { kind: 'destroyed' }, 'Destroying');
|
|
77
|
+
if (!flags.json) {
|
|
78
|
+
const note = (0, dedicated_machines_1.dedicatedDestroyRetentionNote)(prior, {
|
|
79
|
+
purge: Boolean(flags['purge-storage']),
|
|
80
|
+
retain: Boolean(flags['retain-storage']),
|
|
81
|
+
bin: this.config.bin,
|
|
82
|
+
});
|
|
83
|
+
if (note)
|
|
84
|
+
this.log(note);
|
|
85
|
+
}
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
88
|
exports.default = DedicatedMachinesDestroy;
|
|
@@ -40,9 +40,14 @@ class DedicatedMachinesList extends dedicated_lease_command_1.DedicatedLeaseComm
|
|
|
40
40
|
}
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
|
+
const scopeSpendCents = (0, dedicated_machines_1.dedicatedScopeMonthSpendCents)(leases);
|
|
44
|
+
if (scopeSpendCents !== null) {
|
|
45
|
+
this.log(`Dedicated Runtime spend this month, across every machine listed: ${(0, dedicated_machines_1.formatUsdCents)(scopeSpendCents)}`);
|
|
46
|
+
this.log('');
|
|
47
|
+
}
|
|
43
48
|
(0, format_1.renderTable)(leases.map((lease) => ({
|
|
44
49
|
id: (0, format_1.formatCell)((0, dedicated_machines_1.dedicatedLeaseId)(lease)),
|
|
45
|
-
status: (0, format_1.formatCell)((0, dedicated_machines_1.
|
|
50
|
+
status: (0, format_1.formatCell)((0, dedicated_machines_1.dedicatedLeaseStatusCell)(lease)),
|
|
46
51
|
name: (0, format_1.formatCell)((0, dedicated_machines_1.dedicatedLeaseDisplayName)(lease)),
|
|
47
52
|
size: (0, format_1.formatCell)([
|
|
48
53
|
lease.resources?.sizePreset,
|
|
@@ -50,17 +55,15 @@ class DedicatedMachinesList extends dedicated_lease_command_1.DedicatedLeaseComm
|
|
|
50
55
|
]
|
|
51
56
|
.filter(Boolean)
|
|
52
57
|
.join(' ') || '-'),
|
|
53
|
-
|
|
54
|
-
? `${(0, dedicated_machines_1.formatUsdCents)(lease.billing.currentMonthSpendCents)}/${(0, dedicated_machines_1.formatUsdCents)(lease.billing.monthlySpendLimitCents)}${lease.billing.spendingLimitExceeded ? ' OVER' : ''}`
|
|
55
|
-
: '-'),
|
|
58
|
+
cap: (0, format_1.formatCell)((0, dedicated_machines_1.dedicatedLeaseCapCell)(lease)),
|
|
56
59
|
retention: (0, format_1.formatCell)(lease.storage?.retentionClass || '-'),
|
|
57
60
|
backup: (0, format_1.formatCell)(lease.storage?.lastBackupAt || '-'),
|
|
58
61
|
})), [
|
|
59
62
|
{ key: 'id', header: 'ID' },
|
|
60
|
-
{ key: 'status', header: 'STATUS' },
|
|
63
|
+
{ key: 'status', header: 'STATUS', maxWidth: 28 },
|
|
61
64
|
{ key: 'name', header: 'NAME', maxWidth: 28 },
|
|
62
65
|
{ key: 'size', header: 'SIZE', maxWidth: 18 },
|
|
63
|
-
{ key: '
|
|
66
|
+
{ key: 'cap', header: 'MONTHLY CAP', maxWidth: 24 },
|
|
64
67
|
{ key: 'retention', header: 'RETENTION', maxWidth: 14 },
|
|
65
68
|
{ key: 'backup', header: 'LAST BACKUP', maxWidth: 24 },
|
|
66
69
|
], (line) => this.log(line));
|
|
@@ -61,6 +61,18 @@ class HostedMachinesList extends base_command_1.BaseCommand {
|
|
|
61
61
|
{ key: 'repos', header: 'REPOS', maxWidth: 36 },
|
|
62
62
|
{ key: 'preview', header: 'PREVIEW', maxWidth: 48 },
|
|
63
63
|
], (line) => this.log(line));
|
|
64
|
+
// How to find the same box in the other list. `machines list` identifies a
|
|
65
|
+
// hosted machine by `lease:<leaseId>` — a different random string from the
|
|
66
|
+
// `sbs_` id in the ID column above — so side by side the two commands read
|
|
67
|
+
// as two unrelated machines. Saying the mapping once, where both values are
|
|
68
|
+
// already on screen, is cheaper than a column nobody can interpret.
|
|
69
|
+
const withLease = sessions.filter((session) => session.machineLeaseId);
|
|
70
|
+
if (withLease.length > 0) {
|
|
71
|
+
this.log('');
|
|
72
|
+
this.log(`In \`${this.config.bin} machines list\` these appear under their LEASE id as ` +
|
|
73
|
+
`\`lease:<lease>\` — e.g. \`lease:${String(withLease[0].machineLeaseId)}\`. ` +
|
|
74
|
+
`\`machines show\` takes either.`);
|
|
75
|
+
}
|
|
64
76
|
}
|
|
65
77
|
/**
|
|
66
78
|
* OSK-2045 — this command lists Hosted Machine *sessions* and nothing else,
|
|
@@ -78,6 +78,17 @@ class MachinesList extends base_command_1.BaseCommand {
|
|
|
78
78
|
{ key: 'platform', header: 'PLATFORM' },
|
|
79
79
|
{ key: 'seen', header: 'LAST SEEN' },
|
|
80
80
|
], (line) => this.log(line));
|
|
81
|
+
// Why a machine is not `online`, under the table. `degraded` on its own is
|
|
82
|
+
// unactionable — the dogfood note on this output was "I still do not know
|
|
83
|
+
// whether something is wrong with my laptop" — and it is usually one named
|
|
84
|
+
// row, not the machine.
|
|
85
|
+
const explained = shown.filter((m) => m.status !== 'online' && m.statusReason);
|
|
86
|
+
if (explained.length > 0) {
|
|
87
|
+
this.log('');
|
|
88
|
+
for (const m of explained) {
|
|
89
|
+
this.log(` ${m.preferences?.name || m.name}: ${m.statusReason}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
81
92
|
const hidden = all.length - shown.length;
|
|
82
93
|
if (hidden > 0) {
|
|
83
94
|
this.log('');
|
|
@@ -9,6 +9,7 @@ class MachinesShow extends base_command_1.BaseCommand {
|
|
|
9
9
|
static description = 'Show one machine — its identity, harnesses, and available actions';
|
|
10
10
|
static examples = [
|
|
11
11
|
'<%= config.bin %> machines show 03548116',
|
|
12
|
+
'<%= config.bin %> machines show 03548116 --all',
|
|
12
13
|
'<%= config.bin %> machines show "frankdu@DS-3" --json',
|
|
13
14
|
];
|
|
14
15
|
static args = {
|
|
@@ -18,7 +19,13 @@ class MachinesShow extends base_command_1.BaseCommand {
|
|
|
18
19
|
required: true,
|
|
19
20
|
}),
|
|
20
21
|
};
|
|
21
|
-
static flags = {
|
|
22
|
+
static flags = {
|
|
23
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
24
|
+
all: core_1.Flags.boolean({
|
|
25
|
+
description: 'Include harness registrations superseded by newer ones',
|
|
26
|
+
default: false,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
22
29
|
async run() {
|
|
23
30
|
this.requireAuth();
|
|
24
31
|
const { args, flags } = await this.parse(MachinesShow);
|
|
@@ -43,6 +50,10 @@ class MachinesShow extends base_command_1.BaseCommand {
|
|
|
43
50
|
this.log(` id ${machine.id}`);
|
|
44
51
|
this.log(` identity ${machine.identityKind}`);
|
|
45
52
|
this.log(` status ${machine.status}`);
|
|
53
|
+
// The status alone does not say what to do. Naming the rows that decided it
|
|
54
|
+
// is the actionable half — bring that provider back, or forget the row.
|
|
55
|
+
if (machine.statusReason)
|
|
56
|
+
this.log(` why ${machine.statusReason}`);
|
|
46
57
|
if (machine.machineUuid)
|
|
47
58
|
this.log(` machineUuid ${machine.machineUuid}`);
|
|
48
59
|
if (machine.daemonId)
|
|
@@ -60,20 +71,34 @@ class MachinesShow extends base_command_1.BaseCommand {
|
|
|
60
71
|
const harnesses = machine.harnesses ?? [];
|
|
61
72
|
const live = (0, machines_1.liveHarnessCount)(machine);
|
|
62
73
|
const superseded = harnesses.length - live;
|
|
74
|
+
// Superseded registrations are history, not capabilities, and they are not
|
|
75
|
+
// counted as missing ones next to a status of `online`. They are hidden by
|
|
76
|
+
// default, the convention `machines list` already set for dead machines:
|
|
77
|
+
// printed, they were 35 of a machine's 42 rows, back two months, under a
|
|
78
|
+
// header whose count read like a summary of rows it then printed anyway
|
|
79
|
+
// (OSK-9389). `--all` shows them, and `--json` always carries every row.
|
|
80
|
+
const shown = flags.all ? harnesses : harnesses.filter((h) => !h.superseded);
|
|
63
81
|
this.log('');
|
|
64
|
-
// The superseded rows are still listed — labelled, not dropped, so they can
|
|
65
|
-
// be seen and reaped — but they are not missing capabilities and must not
|
|
66
|
-
// be counted as such next to a status of `online`.
|
|
67
82
|
this.log(`Harnesses (${machine.onlineHarnessCount}/${live} online` +
|
|
68
|
-
(superseded > 0 ? `, ${superseded} superseded by newer registrations` : '') +
|
|
83
|
+
(flags.all && superseded > 0 ? `, ${superseded} superseded by newer registrations` : '') +
|
|
69
84
|
')');
|
|
70
|
-
if (
|
|
71
|
-
(0, format_1.renderTable)(
|
|
85
|
+
if (shown.length > 0) {
|
|
86
|
+
(0, format_1.renderTable)(shown.map((h) => ({
|
|
72
87
|
id: (0, format_1.formatCell)(h.id),
|
|
73
88
|
provider: (0, format_1.formatCell)(h.provider),
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
|
|
89
|
+
// Liveness only. Whether a newer registration replaced the row is a
|
|
90
|
+
// different fact — a superseded row can have been online — so it
|
|
91
|
+
// gets its own column rather than overwriting this one.
|
|
92
|
+
status: (0, format_1.formatCell)(h.status),
|
|
93
|
+
registration: h.superseded ? 'superseded' : 'current',
|
|
94
|
+
// `authStatus` is the DAEMON's standing with skrr, not this
|
|
95
|
+
// provider's standing with its vendor. It is written across every row
|
|
96
|
+
// of a daemon at once (`HarnessService.markAuthOkAfterRegister` does
|
|
97
|
+
// one `updateMany({owner, daemonId})`), so it is structurally the
|
|
98
|
+
// same value for all of them and can never answer "is this harness
|
|
99
|
+
// signed in". Under a bare `AUTH` header it read as the second
|
|
100
|
+
// question and answered `ok` for a Devin whose own CLI said
|
|
101
|
+
// "Not logged in." The header now names what the value is.
|
|
77
102
|
auth: (0, format_1.formatCell)(h.authStatus),
|
|
78
103
|
seen: h.lastSeenAt
|
|
79
104
|
? new Date(h.lastSeenAt).toISOString().slice(0, 16).replace('T', ' ')
|
|
@@ -82,10 +107,14 @@ class MachinesShow extends base_command_1.BaseCommand {
|
|
|
82
107
|
{ key: 'id', header: 'HARNESS ID' },
|
|
83
108
|
{ key: 'provider', header: 'PROVIDER' },
|
|
84
109
|
{ key: 'status', header: 'STATUS' },
|
|
85
|
-
{ key: '
|
|
110
|
+
...(flags.all ? [{ key: 'registration', header: 'REGISTRATION' }] : []),
|
|
111
|
+
{ key: 'auth', header: 'DAEMON AUTH' },
|
|
86
112
|
{ key: 'seen', header: 'LAST SEEN' },
|
|
87
113
|
], (line) => this.log(` ${line}`));
|
|
88
114
|
}
|
|
115
|
+
if (!flags.all && superseded > 0) {
|
|
116
|
+
this.log(` ${superseded} superseded registration(s) hidden — pass --all to include them.`);
|
|
117
|
+
}
|
|
89
118
|
// Print what is PERMITTED, not the whole control map: a list of six
|
|
90
119
|
// booleans mostly reading false is noise, and the useful question is
|
|
91
120
|
// "what can I do to this machine right now".
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* Which stored sessions exist on this machine.
|
|
4
|
+
*
|
|
5
|
+
* Every command advertises `--profile` and nothing could tell you what to pass
|
|
6
|
+
* it. Profiles are additive and stored on disk — the default at
|
|
7
|
+
* `<root>/cli-config.json`, named ones at `<root>/profiles/<name>/cli-config.json`
|
|
8
|
+
* — so the list was always knowable; there was simply no way to ask.
|
|
9
|
+
*
|
|
10
|
+
* That is not only a discoverability nit. A second profile is how one machine
|
|
11
|
+
* holds a second identity, which is what any test of a NON-OWNER path needs. A
|
|
12
|
+
* dogfood run of the `force`-past-a-required-Review flow stalled on exactly
|
|
13
|
+
* this: the flag was there, the second identity was reachable, and nothing named
|
|
14
|
+
* it.
|
|
15
|
+
*/
|
|
16
|
+
export default class ProfilesList extends BaseCommand {
|
|
17
|
+
static description: string;
|
|
18
|
+
static examples: string[];
|
|
19
|
+
static flags: {
|
|
20
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
21
|
+
};
|
|
22
|
+
run(): Promise<void>;
|
|
23
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const core_1 = require("@oclif/core");
|
|
9
|
+
const base_command_1 = require("../../base-command");
|
|
10
|
+
const config_1 = require("../../lib/config");
|
|
11
|
+
const keychain_1 = require("../../lib/keychain");
|
|
12
|
+
/**
|
|
13
|
+
* Which stored sessions exist on this machine.
|
|
14
|
+
*
|
|
15
|
+
* Every command advertises `--profile` and nothing could tell you what to pass
|
|
16
|
+
* it. Profiles are additive and stored on disk — the default at
|
|
17
|
+
* `<root>/cli-config.json`, named ones at `<root>/profiles/<name>/cli-config.json`
|
|
18
|
+
* — so the list was always knowable; there was simply no way to ask.
|
|
19
|
+
*
|
|
20
|
+
* That is not only a discoverability nit. A second profile is how one machine
|
|
21
|
+
* holds a second identity, which is what any test of a NON-OWNER path needs. A
|
|
22
|
+
* dogfood run of the `force`-past-a-required-Review flow stalled on exactly
|
|
23
|
+
* this: the flag was there, the second identity was reachable, and nothing named
|
|
24
|
+
* it.
|
|
25
|
+
*/
|
|
26
|
+
class ProfilesList extends base_command_1.BaseCommand {
|
|
27
|
+
static description = 'List the stored sessions (profiles) on this machine';
|
|
28
|
+
static examples = [
|
|
29
|
+
'<%= config.bin %> profiles list',
|
|
30
|
+
'<%= config.bin %> profiles list --json',
|
|
31
|
+
'<%= config.bin %> login --profile reviewer',
|
|
32
|
+
];
|
|
33
|
+
static flags = {
|
|
34
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
35
|
+
};
|
|
36
|
+
async run() {
|
|
37
|
+
const { flags } = await this.parse(ProfilesList);
|
|
38
|
+
const root = (0, config_1.configRoot)();
|
|
39
|
+
const active = (0, keychain_1.getActiveProfile)() || keychain_1.DEFAULT_PROFILE;
|
|
40
|
+
const rows = [];
|
|
41
|
+
const seen = new Set();
|
|
42
|
+
const describe = (name, configFile) => {
|
|
43
|
+
/*
|
|
44
|
+
* Deliberately NOT a "signed in" column.
|
|
45
|
+
*
|
|
46
|
+
* The credential does not live in `cli-config.json` — it is in the
|
|
47
|
+
* keychain — so this file can only report the deployment. A first draft
|
|
48
|
+
* inferred sign-in from a `token` key that is never there and printed
|
|
49
|
+
* `no` for the profile the caller was actively authenticated as: a
|
|
50
|
+
* confident wrong answer, which is worse than an absent one and exactly
|
|
51
|
+
* the failure this whole surface keeps making. Read the server, say the
|
|
52
|
+
* server.
|
|
53
|
+
*/
|
|
54
|
+
if (seen.has(name))
|
|
55
|
+
return;
|
|
56
|
+
seen.add(name);
|
|
57
|
+
let server;
|
|
58
|
+
try {
|
|
59
|
+
const raw = JSON.parse(node_fs_1.default.readFileSync(configFile, 'utf8'));
|
|
60
|
+
server = typeof raw.baseURL === 'string' ? raw.baseURL : undefined;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
// A profile directory with no readable config is still a profile; say
|
|
64
|
+
// so rather than hiding it, because a half-written one is exactly what
|
|
65
|
+
// someone would be trying to diagnose.
|
|
66
|
+
}
|
|
67
|
+
rows.push({ name, active: name === active, server });
|
|
68
|
+
};
|
|
69
|
+
describe(keychain_1.DEFAULT_PROFILE, node_path_1.default.join(root, 'cli-config.json'));
|
|
70
|
+
try {
|
|
71
|
+
for (const entry of node_fs_1.default.readdirSync(node_path_1.default.join(root, 'profiles'), { withFileTypes: true })) {
|
|
72
|
+
if (!entry.isDirectory())
|
|
73
|
+
continue;
|
|
74
|
+
describe(entry.name, node_path_1.default.join(root, 'profiles', entry.name, 'cli-config.json'));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
// No `profiles/` directory means no named profiles — not an error.
|
|
79
|
+
}
|
|
80
|
+
if (flags.json) {
|
|
81
|
+
this.log(JSON.stringify({ root, active, profiles: rows }, null, 2));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Width from the DATA, so a long profile name cannot run into the next
|
|
85
|
+
// column — `df-memory-verify-isolated` did exactly that at a fixed 24.
|
|
86
|
+
const width = Math.max(4, ...rows.map((row) => row.name.length)) + 2;
|
|
87
|
+
this.log('NAME'.padEnd(width) + 'ACTIVE'.padEnd(9) + 'SERVER');
|
|
88
|
+
for (const row of rows) {
|
|
89
|
+
this.log(row.name.padEnd(width) + (row.active ? 'yes' : '-').padEnd(9) + (row.server || '-'));
|
|
90
|
+
}
|
|
91
|
+
this.log('');
|
|
92
|
+
this.log(`Add one: ${this.config.bin} login --profile <name>`);
|
|
93
|
+
this.log(`Use one: ${this.config.bin} <command> --profile <name> (or OVERSKY_PROFILE)`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.default = ProfilesList;
|
|
@@ -2,9 +2,19 @@ import { BaseCommand } from '../../base-command';
|
|
|
2
2
|
export default class SearchSkills extends BaseCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
|
+
/**
|
|
6
|
+
* `skills search` and `search skills` are the SAME search, and both spellings
|
|
7
|
+
* stay. Neither said so, their one-liners differed, and only one carried short
|
|
8
|
+
* flags — so a reader picked one at random with no way to tell whether they had
|
|
9
|
+
* picked a deprecated spelling. Duplication that is deliberate should say so;
|
|
10
|
+
* duplication that is silent reads as a mistake either way.
|
|
11
|
+
*/
|
|
12
|
+
static args: {
|
|
13
|
+
query: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
14
|
+
};
|
|
5
15
|
static flags: {
|
|
6
16
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
|
-
query: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
|
+
query: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
18
|
category: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
19
|
limit: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
20
|
};
|
|
@@ -4,19 +4,36 @@ 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 search_query_1 = require("../../lib/search-query");
|
|
7
8
|
class SearchSkills extends base_command_1.BaseCommand {
|
|
8
|
-
static description = 'Search skills in the skrr store';
|
|
9
|
+
static description = 'Search skills in the skrr store by keyword (same as `skills search`)';
|
|
9
10
|
static examples = [
|
|
10
|
-
'<%= config.bin %> search skills
|
|
11
|
-
'<%= config.bin %> search skills
|
|
12
|
-
'<%= config.bin %> search skills
|
|
11
|
+
'<%= config.bin %> search skills "web scraping"',
|
|
12
|
+
'<%= config.bin %> search skills summarize --category productivity',
|
|
13
|
+
'<%= config.bin %> search skills translate --limit 10 --json',
|
|
14
|
+
'<%= config.bin %> search skills -q translate',
|
|
13
15
|
];
|
|
16
|
+
/**
|
|
17
|
+
* `skills search` and `search skills` are the SAME search, and both spellings
|
|
18
|
+
* stay. Neither said so, their one-liners differed, and only one carried short
|
|
19
|
+
* flags — so a reader picked one at random with no way to tell whether they had
|
|
20
|
+
* picked a deprecated spelling. Duplication that is deliberate should say so;
|
|
21
|
+
* duplication that is silent reads as a mistake either way.
|
|
22
|
+
*/
|
|
23
|
+
static args = {
|
|
24
|
+
query: core_1.Args.string({
|
|
25
|
+
description: 'Search term (same value as --query; pass either, not both)',
|
|
26
|
+
required: false,
|
|
27
|
+
ignoreStdin: true,
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
14
30
|
static flags = {
|
|
15
31
|
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
32
|
+
// See `skills search` — the positional satisfies it and the flag stays so
|
|
33
|
+
// existing scripts keep working.
|
|
16
34
|
query: core_1.Flags.string({
|
|
17
35
|
char: 'q',
|
|
18
36
|
description: 'Search query',
|
|
19
|
-
required: true,
|
|
20
37
|
}),
|
|
21
38
|
category: core_1.Flags.string({
|
|
22
39
|
char: 'c',
|
|
@@ -29,11 +46,12 @@ class SearchSkills extends base_command_1.BaseCommand {
|
|
|
29
46
|
};
|
|
30
47
|
async run() {
|
|
31
48
|
this.requireAuth();
|
|
32
|
-
const { flags } = await this.parse(SearchSkills);
|
|
49
|
+
const { args, flags } = await this.parse(SearchSkills);
|
|
50
|
+
const query = (0, search_query_1.resolveSearchQuery)(this, args.query, flags.query, 'search skills');
|
|
33
51
|
let response;
|
|
34
52
|
try {
|
|
35
53
|
response = await data_provider_1.dataService.getStoreSkills({
|
|
36
|
-
q:
|
|
54
|
+
q: query,
|
|
37
55
|
category: flags.category,
|
|
38
56
|
limit: flags.limit,
|
|
39
57
|
});
|
|
@@ -48,7 +66,7 @@ class SearchSkills extends base_command_1.BaseCommand {
|
|
|
48
66
|
const r = response;
|
|
49
67
|
const items = Array.isArray(r) ? r : (r?.data ?? []);
|
|
50
68
|
if (items.length === 0) {
|
|
51
|
-
this.log(
|
|
69
|
+
this.log(`No skills found matching '${query}'.`);
|
|
52
70
|
return;
|
|
53
71
|
}
|
|
54
72
|
const rows = items.map((s) => ({
|