@skrr-ai/cli 0.1.35 → 0.1.36

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.
Files changed (41) hide show
  1. package/dist/commands/balance/index.js +2 -1
  2. package/dist/commands/balance/overage.d.ts +12 -0
  3. package/dist/commands/balance/overage.js +43 -0
  4. package/dist/commands/balance/plan.d.ts +11 -0
  5. package/dist/commands/balance/plan.js +59 -0
  6. package/dist/commands/balance/usage/events.d.ts +15 -0
  7. package/dist/commands/balance/usage/events.js +69 -0
  8. package/dist/commands/balance/usage.js +13 -2
  9. package/dist/commands/commitments/export.d.ts +3 -0
  10. package/dist/commands/commitments/export.js +59 -14
  11. package/dist/commands/learning/export.d.ts +5 -1
  12. package/dist/commands/learning/export.js +85 -21
  13. package/dist/commands/tasks/export.d.ts +6 -2
  14. package/dist/commands/tasks/export.js +69 -21
  15. package/dist/commands/tones/export.d.ts +3 -1
  16. package/dist/commands/tones/export.js +50 -21
  17. package/dist/lib/artifact-export.d.ts +84 -0
  18. package/dist/lib/artifact-export.js +122 -0
  19. package/dist/lib/balance.d.ts +13 -1
  20. package/dist/lib/balance.js +30 -0
  21. package/dist/lib/web-url.d.ts +2 -0
  22. package/dist/lib/web-url.js +2 -0
  23. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +15 -10
  24. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +18 -11
  25. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +9 -3
  26. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +15 -10
  27. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +18 -11
  28. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +9 -3
  29. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  30. package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
  31. package/dist/node_modules/@skrr-ai/inference-broker/dist/cjs/loopback-server.d.ts +2 -0
  32. package/dist/node_modules/@skrr-ai/inference-broker/dist/cjs/loopback-server.js +8 -3
  33. package/dist/node_modules/@skrr-ai/inference-broker/dist/cjs/managed-inference-broker.d.ts +2 -0
  34. package/dist/node_modules/@skrr-ai/inference-broker/dist/cjs/managed-inference-broker.js +6 -1
  35. package/dist/node_modules/@skrr-ai/inference-broker/dist/esm/loopback-server.d.ts +2 -0
  36. package/dist/node_modules/@skrr-ai/inference-broker/dist/esm/loopback-server.js +9 -4
  37. package/dist/node_modules/@skrr-ai/inference-broker/dist/esm/managed-inference-broker.d.ts +2 -0
  38. package/dist/node_modules/@skrr-ai/inference-broker/dist/esm/managed-inference-broker.js +7 -2
  39. package/dist/node_modules/@skrr-ai/inference-broker/package.json +1 -1
  40. package/oclif.manifest.json +6974 -6657
  41. package/package.json +1 -1
@@ -19,10 +19,11 @@ const show_1 = __importDefault(require("./show"));
19
19
  */
20
20
  class Balance extends show_1.default {
21
21
  static description = 'Show your hosted usage balance — the ceiling that refuses a turn (not the x402 wallet). ' +
22
- 'See `balance usage`, `balance statement` and `balance history` for the detail behind it.';
22
+ 'See `balance usage`, `balance overage`, `balance statement` and `balance history` for the detail behind it.';
23
23
  static examples = [
24
24
  '<%= config.bin %> balance',
25
25
  '<%= config.bin %> balance --json',
26
+ '<%= config.bin %> balance plan',
26
27
  '<%= config.bin %> balance statement --desc',
27
28
  ];
28
29
  }
@@ -0,0 +1,12 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ export default class BalanceOverage extends BaseCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ state: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ };
11
+ run(): Promise<void>;
12
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const base_command_1 = require("../../base-command");
5
+ const balance_1 = require("../../lib/balance");
6
+ class BalanceOverage extends base_command_1.BaseCommand {
7
+ static description = 'Enable or disable personal Sky Code overage after included allowance is exhausted';
8
+ static examples = [
9
+ '<%= config.bin %> balance overage enabled',
10
+ '<%= config.bin %> balance overage disabled',
11
+ '<%= config.bin %> balance overage disabled --json',
12
+ ];
13
+ static args = {
14
+ state: core_1.Args.string({
15
+ description: 'Whether personal stored balance may pay for Sky Code overage',
16
+ required: true,
17
+ options: ['enabled', 'disabled'],
18
+ ignoreStdin: true,
19
+ }),
20
+ };
21
+ static flags = { json: core_1.Flags.boolean({ description: 'Output as JSON' }) };
22
+ async run() {
23
+ this.requireAuth();
24
+ const { args, flags } = await this.parse(BalanceOverage);
25
+ const state = args.state;
26
+ let result;
27
+ try {
28
+ result = await (0, balance_1.setSkyCodeOverageConsent)(state === 'enabled');
29
+ }
30
+ catch (error) {
31
+ this.handleApiError(error);
32
+ }
33
+ if (flags.json) {
34
+ this.log(JSON.stringify({ ownerType: 'user', overageEnabled: result?.enabled === true }, null, 2));
35
+ return;
36
+ }
37
+ this.log(`Sky Code overage ${result?.enabled ? 'enabled' : 'disabled'} for your personal billing account.`);
38
+ this.log(result?.enabled
39
+ ? 'New admissions may use stored balance after included allowance is exhausted.'
40
+ : 'New admissions will stop when included allowance is exhausted.');
41
+ }
42
+ }
43
+ exports.default = BalanceOverage;
@@ -0,0 +1,11 @@
1
+ import { type TSubscription } from '@skrr-ai/data-provider';
2
+ import { BaseCommand } from '../../base-command';
3
+ export default class BalancePlan extends BaseCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ run(): Promise<void>;
10
+ }
11
+ export declare function renderSkyCodePlan(subscription: TSubscription, lifecycleUrl: string | null, log: (line: string) => void): void;
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderSkyCodePlan = renderSkyCodePlan;
4
+ const core_1 = require("@oclif/core");
5
+ const data_provider_1 = require("@skrr-ai/data-provider");
6
+ const base_command_1 = require("../../base-command");
7
+ const balance_1 = require("../../lib/balance");
8
+ const web_url_1 = require("../../lib/web-url");
9
+ class BalancePlan extends base_command_1.BaseCommand {
10
+ static description = 'Show your Sky Code plan, renewal state, available tiers, and the matching lifecycle page';
11
+ static examples = [
12
+ '<%= config.bin %> balance plan',
13
+ '<%= config.bin %> balance plan --json',
14
+ ];
15
+ static flags = { json: core_1.Flags.boolean({ description: 'Output as JSON' }) };
16
+ async run() {
17
+ this.requireAuth();
18
+ const { flags } = await this.parse(BalancePlan);
19
+ let subscription;
20
+ try {
21
+ subscription = await (0, balance_1.getSkyCodeSubscription)();
22
+ }
23
+ catch (error) {
24
+ this.handleApiError(error);
25
+ }
26
+ const lifecycleUrl = (0, web_url_1.webUrl)(this.cliConfig.baseURL, { kind: 'billing' });
27
+ if (flags.json) {
28
+ this.log(JSON.stringify({ subscription, plans: data_provider_1.SKY_CODE_PLAN_CATALOG, lifecycleUrl }, null, 2));
29
+ return;
30
+ }
31
+ renderSkyCodePlan(subscription, lifecycleUrl, (line) => this.log(line));
32
+ }
33
+ }
34
+ exports.default = BalancePlan;
35
+ function renderSkyCodePlan(subscription, lifecycleUrl, log) {
36
+ log(`Sky Code plan — ${subscription.tierName}`);
37
+ log(` Status ${subscription.status}`);
38
+ log(` Price $${subscription.monthlyPriceUsd}/month`);
39
+ log(` Current period ${subscription.currentPeriodStart ?? 'not started'}`);
40
+ log(` Renews or ends ${subscription.currentPeriodEnd ?? 'not scheduled'}`);
41
+ log(` Monthly included ${subscription.includedUsageMicros?.toLocaleString() ?? 'not reported'}`);
42
+ log(` 5-hour included ${subscription.fiveHourUsageMicros?.toLocaleString() ?? 'not reported'}`);
43
+ log(` Concurrent sessions ${subscription.concurrentSessions ?? 'not reported'}`);
44
+ log(` Requests per minute ${subscription.rateLimitPerMin}`);
45
+ log('');
46
+ log('Available Sky Code plans:');
47
+ for (const key of data_provider_1.SKY_CODE_PLAN_KEYS.filter((plan) => plan !== 'free')) {
48
+ const plan = data_provider_1.SKY_CODE_PLAN_CATALOG[key];
49
+ log(` ${plan.displayName.padEnd(18)} $${plan.monthlyPriceUsd}/month · ` +
50
+ `${(0, data_provider_1.usageMicrosToDisplayCredits)(plan.includedUsageMicros).toLocaleString()} monthly credits · ` +
51
+ `${(0, data_provider_1.usageMicrosToDisplayCredits)(plan.fiveHourUsageMicros).toLocaleString()} per 5 hours · ` +
52
+ `${plan.concurrentSessions} sessions · ${plan.requestsPerMinute} RPM`);
53
+ }
54
+ if (lifecycleUrl) {
55
+ log('');
56
+ log(`Apply, change, manage, or cancel this billing subscription: ${lifecycleUrl}`);
57
+ log('This is separate from `skrr subscriptions`, which manages published agent services.');
58
+ }
59
+ }
@@ -0,0 +1,15 @@
1
+ import type { TSkyCodeUsageEvents } from '@skrr-ai/data-provider';
2
+ import { BaseCommand } from '../../../base-command';
3
+ export default class BalanceUsageEvents extends BaseCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ from: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
+ to: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ cursor: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ limit: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
15
+ export declare function renderSkyCodeUsageEvents(events: TSkyCodeUsageEvents, log: (line: string) => void): void;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderSkyCodeUsageEvents = renderSkyCodeUsageEvents;
4
+ const core_1 = require("@oclif/core");
5
+ const base_command_1 = require("../../../base-command");
6
+ const balance_1 = require("../../../lib/balance");
7
+ class BalanceUsageEvents extends base_command_1.BaseCommand {
8
+ static description = 'List the Sky Code charges and run/session attribution behind aggregate allowance usage';
9
+ static examples = [
10
+ '<%= config.bin %> balance usage events',
11
+ '<%= config.bin %> balance usage events --limit 20 --json',
12
+ '<%= config.bin %> balance usage events --workspace <workspace-id>',
13
+ '<%= config.bin %> balance usage events --from 2026-09-01 --to 2026-09-15',
14
+ ];
15
+ static flags = {
16
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
17
+ from: core_1.Flags.string({ description: 'Inclusive ISO start time; defaults to 30 days ago' }),
18
+ to: core_1.Flags.string({ description: 'Inclusive ISO end time; defaults to now' }),
19
+ cursor: core_1.Flags.string({ description: 'Opaque cursor returned by the previous page' }),
20
+ limit: core_1.Flags.integer({ description: 'Rows to return (1-200)', min: 1, max: 200, default: 50 }),
21
+ };
22
+ async run() {
23
+ this.requireAuth();
24
+ const { flags } = await this.parse(BalanceUsageEvents);
25
+ const workspaceId = flags.workspace;
26
+ let events;
27
+ try {
28
+ events = await (0, balance_1.getSkyCodeUsageEvents)(workspaceId, {
29
+ from: flags.from,
30
+ to: flags.to,
31
+ cursor: flags.cursor,
32
+ limit: flags.limit,
33
+ });
34
+ }
35
+ catch (error) {
36
+ this.handleApiError(error);
37
+ }
38
+ if (flags.json) {
39
+ this.log(JSON.stringify(events, null, 2));
40
+ return;
41
+ }
42
+ renderSkyCodeUsageEvents(events, (line) => this.log(line));
43
+ }
44
+ }
45
+ exports.default = BalanceUsageEvents;
46
+ function renderSkyCodeUsageEvents(events, log) {
47
+ log(`Sky Code usage events · ${events.ownerType} ${events.ownerId}`);
48
+ log(` Window ${events.from} to ${events.to}`);
49
+ if (events.items.length === 0) {
50
+ log(' No usage events in this window.');
51
+ return;
52
+ }
53
+ for (const item of events.items) {
54
+ const providerModel = [item.provider, item.model].filter(Boolean).join('/') || 'provider unknown';
55
+ const incurredBy = [
56
+ item.runId ? `run ${item.runId}` : null,
57
+ item.taskId ? `task ${item.taskId}` : null,
58
+ item.sessionId ? `session ${item.sessionId}` : null,
59
+ item.agentId ? `agent ${item.agentId}` : null,
60
+ ]
61
+ .filter(Boolean)
62
+ .join(' · ');
63
+ log(` ${item.createdAt} ${(0, balance_1.formatMicros)(item.amountMicros)} ${providerModel}`);
64
+ log(` ${incurredBy || 'No run/session attribution'} · charge ${item.chargeId}`);
65
+ }
66
+ if (events.nextCursor) {
67
+ log(` Next cursor: ${events.nextCursor}`);
68
+ }
69
+ }
@@ -9,7 +9,12 @@ const balance_1 = require("../../lib/balance");
9
9
  */
10
10
  class BalanceUsage extends base_command_1.BaseCommand {
11
11
  static description = 'Usage summary against your hosted usage balance';
12
- static examples = ['<%= config.bin %> balance usage'];
12
+ static examples = [
13
+ '<%= config.bin %> balance usage',
14
+ '<%= config.bin %> balance plan',
15
+ '<%= config.bin %> balance usage events --limit 20',
16
+ '<%= config.bin %> balance overage enabled',
17
+ ];
13
18
  static flags = { json: core_1.Flags.boolean({ description: 'Output as JSON' }) };
14
19
  async run() {
15
20
  this.requireAuth();
@@ -48,6 +53,12 @@ function renderSkyCodeUsage(usage, log) {
48
53
  log(` Available overage ${(0, balance_1.formatCreditsWithUsd)(usage.balance.availableMicros)}`);
49
54
  log(` Overage consent ${usage.overageEnabled ? 'enabled' : 'disabled'}`);
50
55
  if (usage.dataStatus === 'partial') {
51
- log(` Reconciliation partial (${usage.unresolvedUsageCount} unresolved)`);
56
+ if (usage.partialReasons?.includes('missing_entitlement_period')) {
57
+ log(' Entitlement period unavailable; catalog limits shown');
58
+ }
59
+ if (usage.partialReasons?.includes('unresolved_provider_usage') ||
60
+ usage.unresolvedUsageCount > 0) {
61
+ log(` Reconciliation partial (${usage.unresolvedUsageCount} unresolved)`);
62
+ }
52
63
  }
53
64
  }
@@ -15,6 +15,9 @@ export default class CommitmentsExport extends BaseCommand {
15
15
  };
16
16
  static flags: {
17
17
  out: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
18
+ summary: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
19
+ output: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
20
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
18
21
  };
19
22
  run(): Promise<void>;
20
23
  }
@@ -1,9 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
- const node_fs_1 = require("node:fs");
5
4
  const base_command_1 = require("../../base-command");
6
5
  const commitments_1 = require("../../lib/commitments");
6
+ const artifact_export_1 = require("../../lib/artifact-export");
7
7
  /**
8
8
  * `skrr commitments export` — export a commitment as a portable manifest.
9
9
  *
@@ -15,31 +15,76 @@ const commitments_1 = require("../../lib/commitments");
15
15
  class CommitmentsExport extends base_command_1.BaseCommand {
16
16
  static description = 'Export a commitment as a portable manifest';
17
17
  static examples = [
18
- '<%= config.bin %> commitments export <commitment-id>',
19
- '<%= config.bin %> commitments export <commitment-id> --out ./commitment.json',
18
+ '<%= config.bin %> commitments export <commitment-id> --summary',
19
+ '<%= config.bin %> commitments export <commitment-id> --output ./commitment.json',
20
+ '<%= config.bin %> commitments export <commitment-id> --output - | jq .',
20
21
  ];
21
22
  static args = {
22
23
  commitmentId: core_1.Args.string({ ignoreStdin: true, description: 'Commitment ID', required: true }),
23
24
  };
24
25
  static flags = {
25
- out: core_1.Flags.string({ description: 'Write to a file instead of stdout' }),
26
+ ...(0, artifact_export_1.standardExportFlags)(),
27
+ out: core_1.Flags.string({ description: 'Deprecated alias for --output' }),
26
28
  };
27
29
  async run() {
28
30
  this.requireAuth();
29
31
  const { args, flags } = await this.parse(CommitmentsExport);
30
- const manifest = await commitments_1.commitmentApi.manifest(args.commitmentId);
31
- const text = JSON.stringify(manifest, null, 2);
32
- if (flags.out) {
33
- (0, node_fs_1.writeFileSync)(flags.out, text, 'utf8');
34
- this.log(`Wrote manifest to ${flags.out}.`);
35
- const requires = manifest.requires;
36
- const pending = (requires?.grants?.length ?? 0) + (requires?.sensors?.length ?? 0);
37
- if (pending > 0) {
38
- this.log(`Whoever imports it must attach ${pending} credential(s)/sensor(s) of their own.`);
32
+ let selection;
33
+ try {
34
+ selection = (0, artifact_export_1.resolveExportSelection)(flags);
35
+ }
36
+ catch (err) {
37
+ if (err instanceof artifact_export_1.ExportContractError) {
38
+ this.failWithCliError({
39
+ message: err.message,
40
+ code: err.code,
41
+ exit: 2,
42
+ retryable: false,
43
+ });
39
44
  }
45
+ throw err;
46
+ }
47
+ const manifest = await commitments_1.commitmentApi.manifest(args.commitmentId);
48
+ const requires = manifest.requires;
49
+ const grants = requires?.grants?.length ?? 0;
50
+ const sensors = requires?.sensors?.length ?? 0;
51
+ const artifactSchemaVersion = manifest.version ?? 1;
52
+ if (selection.summary) {
53
+ const summary = (0, artifact_export_1.contentFreeSummary)({
54
+ command: 'commitments export',
55
+ artifactSchemaVersion,
56
+ itemCount: 1,
57
+ counts: { commitments: 1, requiredGrants: grants, requiredSensors: sensors },
58
+ });
59
+ this.log(selection.json ? JSON.stringify(summary, null, 2) : (0, artifact_export_1.humanExportSummary)(summary));
40
60
  return;
41
61
  }
42
- this.log(text);
62
+ const text = (0, artifact_export_1.serializedArtifact)(manifest);
63
+ if (selection.output === '-') {
64
+ this.log(text.trimEnd());
65
+ return;
66
+ }
67
+ let receipt;
68
+ try {
69
+ receipt = await (0, artifact_export_1.writeExportArtifact)({
70
+ command: 'commitments export',
71
+ artifactSchemaVersion,
72
+ format: 'json',
73
+ output: selection.output,
74
+ content: text,
75
+ itemCount: 1,
76
+ });
77
+ }
78
+ catch (err) {
79
+ this.failWithCliError({
80
+ message: `Could not write ${selection.output}: ${err.message}`,
81
+ code: 'COMMITMENT_EXPORT_WRITE_FAILED',
82
+ exit: 1,
83
+ retryable: false,
84
+ details: { path: selection.output },
85
+ });
86
+ }
87
+ this.log(selection.json ? JSON.stringify(receipt, null, 2) : (0, artifact_export_1.humanExportReceipt)(receipt));
43
88
  }
44
89
  }
45
90
  exports.default = CommitmentsExport;
@@ -3,7 +3,11 @@ export default class LearningExport extends BaseCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
- output: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
6
+ 'max-items': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
7
+ cursor: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ summary: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
+ output: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
11
  };
8
12
  run(): Promise<void>;
9
13
  }
@@ -1,56 +1,120 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const promises_1 = require("node:fs/promises");
4
- const core_1 = require("@oclif/core");
5
3
  const base_command_1 = require("../../base-command");
6
4
  const api_fetch_1 = require("../../lib/api-fetch");
5
+ const artifact_export_1 = require("../../lib/artifact-export");
6
+ function validSummary(value) {
7
+ const summary = value;
8
+ return Boolean(summary?.contentFree === true &&
9
+ Number.isInteger(summary.schemaVersion) &&
10
+ Number.isInteger(summary.itemCount) &&
11
+ summary.counts &&
12
+ ['states', 'episodes', 'observations'].every((key) => Number.isInteger(summary.counts?.[key])));
13
+ }
7
14
  class LearningExport extends base_command_1.BaseCommand {
8
15
  static description = 'Export your adaptive-learning consent, state, episodes, and outcomes as JSON';
9
16
  static examples = [
17
+ '<%= config.bin %> learning export --summary',
10
18
  '<%= config.bin %> learning export --output learning-data.json',
19
+ '<%= config.bin %> learning export --output learning-data-2.json --cursor <next-cursor>',
11
20
  '<%= config.bin %> learning export --output - | jq .consent',
12
21
  ];
13
- static flags = {
14
- output: core_1.Flags.string({
15
- char: 'o',
16
- description: 'Required destination path; use "-" only for deliberate stdout output',
17
- required: true,
18
- }),
19
- };
22
+ static flags = (0, artifact_export_1.paginatedExportFlags)({ defaultMaxItems: 1000 });
20
23
  async run() {
21
24
  this.requireAuth();
22
25
  const { flags } = await this.parse(LearningExport);
26
+ let selection;
27
+ try {
28
+ selection = (0, artifact_export_1.resolveExportSelection)(flags);
29
+ }
30
+ catch (err) {
31
+ if (err instanceof artifact_export_1.ExportContractError) {
32
+ this.failWithCliError({
33
+ message: err.message,
34
+ code: err.code,
35
+ exit: 2,
36
+ retryable: false,
37
+ });
38
+ }
39
+ throw err;
40
+ }
41
+ if (selection.summary) {
42
+ let result;
43
+ try {
44
+ result = await (0, api_fetch_1.apiFetch)('/api/user/adaptive-learning/export-summary', {
45
+ credential: this.resolvedCredential,
46
+ });
47
+ }
48
+ catch (err) {
49
+ if (err instanceof api_fetch_1.ApiFetchError && err.status === 404) {
50
+ this.failWithCliError({
51
+ message: 'This server does not support content-free adaptive-learning summaries yet. ' +
52
+ 'Upgrade the server or use --output <path>.',
53
+ code: 'LEARNING_EXPORT_SUMMARY_UNSUPPORTED',
54
+ exit: 2,
55
+ retryable: false,
56
+ });
57
+ }
58
+ this.handleApiError(err);
59
+ }
60
+ if (!validSummary(result)) {
61
+ this.failWithCliError({
62
+ message: 'The server returned an invalid adaptive-learning summary.',
63
+ code: 'LEARNING_EXPORT_SUMMARY_INVALID',
64
+ exit: 1,
65
+ retryable: false,
66
+ });
67
+ }
68
+ const summary = (0, artifact_export_1.contentFreeSummary)({
69
+ command: 'learning export',
70
+ artifactSchemaVersion: result.schemaVersion,
71
+ itemCount: result.itemCount,
72
+ counts: result.counts,
73
+ });
74
+ this.log(selection.json ? JSON.stringify(summary, null, 2) : (0, artifact_export_1.humanExportSummary)(summary));
75
+ return;
76
+ }
23
77
  let result;
24
78
  try {
25
- result = await (0, api_fetch_1.apiFetch)('/api/user/adaptive-learning/export', {
26
- credential: this.resolvedCredential,
79
+ const query = new URLSearchParams({
80
+ maxItems: String(flags['max-items'] ?? 1000),
81
+ ...(flags.cursor ? { cursor: flags.cursor } : {}),
27
82
  });
83
+ result = await (0, api_fetch_1.apiFetch)(`/api/user/adaptive-learning/export?${query.toString()}`, { credential: this.resolvedCredential });
28
84
  }
29
85
  catch (err) {
30
86
  this.handleApiError(err);
31
87
  }
32
- const serialized = `${JSON.stringify(result, null, 2)}\n`;
33
- if (flags.output === '-') {
34
- this.log(serialized.trimEnd());
88
+ const content = (0, artifact_export_1.serializedArtifact)(result);
89
+ if (selection.output === '-') {
90
+ this.log(content.trimEnd());
35
91
  return;
36
92
  }
93
+ let receipt;
37
94
  try {
38
- await (0, promises_1.writeFile)(flags.output, serialized, {
39
- encoding: 'utf8',
40
- flag: 'wx',
41
- mode: 0o600,
95
+ receipt = await (0, artifact_export_1.writeExportArtifact)({
96
+ command: 'learning export',
97
+ artifactSchemaVersion: result.schemaVersion ?? 1,
98
+ format: 'json',
99
+ output: selection.output,
100
+ content,
101
+ itemCount: (result.states?.length ?? 0) +
102
+ (result.episodes?.length ?? 0) +
103
+ (result.observations?.length ?? 0),
104
+ truncated: Boolean(result.page?.truncated ?? result.truncated),
105
+ nextCursor: result.page?.nextCursor,
42
106
  });
43
107
  }
44
108
  catch (err) {
45
109
  this.failWithCliError({
46
- message: `Could not write ${flags.output}: ${err.message}`,
110
+ message: `Could not write ${selection.output}: ${err.message}`,
47
111
  code: 'ADAPTIVE_LEARNING_EXPORT_WRITE_FAILED',
48
112
  exit: 1,
49
113
  retryable: false,
50
- details: { path: flags.output },
114
+ details: { path: selection.output },
51
115
  });
52
116
  }
53
- this.log(`Wrote adaptive-learning export to ${flags.output}.`);
117
+ this.log(selection.json ? JSON.stringify(receipt, null, 2) : (0, artifact_export_1.humanExportReceipt)(receipt));
54
118
  }
55
119
  }
56
120
  exports.default = LearningExport;
@@ -4,9 +4,13 @@ export default class TasksExport extends BaseCommand {
4
4
  static examples: string[];
5
5
  static flags: {
6
6
  format: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
7
- output: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
8
7
  'with-comments': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
- max: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ max: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
+ 'max-items': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ cursor: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ summary: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
12
+ output: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
14
  space: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
15
  goal: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
16
  project: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;