@putdotio/cli 1.0.11 → 1.1.0

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/README.md CHANGED
@@ -100,10 +100,10 @@ Link your account:
100
100
  putio auth login
101
101
  ```
102
102
 
103
- Check the account:
103
+ Check the auth source:
104
104
 
105
105
  ```bash
106
- putio whoami --output json
106
+ putio whoami --fields auth --output json
107
107
  ```
108
108
 
109
109
  Read a small JSON result:
package/dist/bin.mjs CHANGED
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { A as translate, C as CliOutput, D as CliConfigLive, E as renderJson, O as CliRuntime, S as CliSdkLive, T as detectOutputModeFromArgv, a as searchCommand, c as brandCommand, i as filesCommand, j as version, k as CliRuntimeLive, l as versionCommand, m as CliStateLive, n as whoamiCommand, o as eventsCommand, r as transfersCommand, s as downloadLinksCommand, t as describeCli, u as makeAuthCommand, w as CliOutputLive } from "./metadata-DzqePHlY.mjs";
3
- import { Cause, Console, Effect, Layer } from "effect";
4
- import { Command } from "@effect/cli";
5
- import { NodeContext, NodeRuntime } from "@effect/platform-node";
6
- import * as NodeTerminal from "@effect/platform-node/NodeTerminal";
2
+ import { A as CliRuntimeLive, C as CliOutput, D as renderJson, E as isStructuredOutputMode, M as version, O as CliConfigLive, S as CliSdkLive, T as detectOutputModeFromArgv, a as searchCommand, c as brandCommand, i as filesCommand, j as translate, k as CliRuntime, l as versionCommand, m as CliStateLive, n as whoamiCommand, o as eventsCommand, r as transfersCommand, s as downloadLinksCommand, t as describeCli, u as makeAuthCommand, w as CliOutputLive } from "./metadata-C_yXlqxj.mjs";
3
+ import { Cause, Console, Effect, Layer, Result } from "effect";
4
+ import { CliError, Command } from "effect/unstable/cli";
5
+ import { NodeRuntime, NodeServices } from "@effect/platform-node";
7
6
  //#region src/cli.ts
8
7
  const authCommand = makeAuthCommand();
9
8
  const describeCommand = Command.make("describe", {}, () => Console.log(renderJson(describeCli())));
@@ -19,21 +18,127 @@ const command = Command.make("putio", {}, () => Console.log(translate("cli.root.
19
18
  searchCommand,
20
19
  transfersCommand
21
20
  ]));
21
+ const makeBufferedConsole = (entries) => ({
22
+ assert: (condition, ...args) => entries.push({
23
+ args: [condition, ...args],
24
+ method: "assert"
25
+ }),
26
+ clear: () => entries.push({
27
+ args: [],
28
+ method: "clear"
29
+ }),
30
+ count: (label) => entries.push({
31
+ args: label === void 0 ? [] : [label],
32
+ method: "count"
33
+ }),
34
+ countReset: (label) => entries.push({
35
+ args: label === void 0 ? [] : [label],
36
+ method: "countReset"
37
+ }),
38
+ debug: (...args) => entries.push({
39
+ args,
40
+ method: "debug"
41
+ }),
42
+ dir: (item, options) => entries.push({
43
+ args: options === void 0 ? [item] : [item, options],
44
+ method: "dir"
45
+ }),
46
+ dirxml: (...args) => entries.push({
47
+ args,
48
+ method: "dirxml"
49
+ }),
50
+ error: (...args) => entries.push({
51
+ args,
52
+ method: "error"
53
+ }),
54
+ group: (...args) => entries.push({
55
+ args,
56
+ method: "group"
57
+ }),
58
+ groupCollapsed: (...args) => entries.push({
59
+ args,
60
+ method: "groupCollapsed"
61
+ }),
62
+ groupEnd: () => entries.push({
63
+ args: [],
64
+ method: "groupEnd"
65
+ }),
66
+ info: (...args) => entries.push({
67
+ args,
68
+ method: "info"
69
+ }),
70
+ log: (...args) => entries.push({
71
+ args,
72
+ method: "log"
73
+ }),
74
+ table: (tabularData, properties) => entries.push({
75
+ args: properties === void 0 ? [tabularData] : [tabularData, properties],
76
+ method: "table"
77
+ }),
78
+ time: (label) => entries.push({
79
+ args: label === void 0 ? [] : [label],
80
+ method: "time"
81
+ }),
82
+ timeEnd: (label) => entries.push({
83
+ args: label === void 0 ? [] : [label],
84
+ method: "timeEnd"
85
+ }),
86
+ timeLog: (label, ...args) => entries.push({
87
+ args: label === void 0 ? args : [label, ...args],
88
+ method: "timeLog"
89
+ }),
90
+ trace: (...args) => entries.push({
91
+ args,
92
+ method: "trace"
93
+ }),
94
+ warn: (...args) => entries.push({
95
+ args,
96
+ method: "warn"
97
+ })
98
+ });
99
+ const replayBufferedConsole = (console, entries) => Effect.sync(() => {
100
+ for (const entry of entries) {
101
+ const method = console[entry.method];
102
+ method(...entry.args);
103
+ }
104
+ });
105
+ const formatCliParserError = (error) => error.errors.map((nestedError) => nestedError.message).join("\n");
106
+ const executableName = (value) => {
107
+ const normalized = value.replaceAll("\\", "/");
108
+ return normalized.slice(normalized.lastIndexOf("/") + 1).toLowerCase();
109
+ };
110
+ const commandArgsFromArgv = (args) => {
111
+ const [first] = args;
112
+ if (first === void 0) return args;
113
+ const firstName = executableName(first);
114
+ if (firstName === "node" || firstName === "node.exe") return args.slice(2);
115
+ if (firstName === "putio" || firstName === "putio.exe" || firstName === "bin.mjs") return args.slice(1);
116
+ return args;
117
+ };
22
118
  function runCli(args) {
23
- return Command.run(command, {
24
- name: "putio",
25
- version: `v${version}`
26
- })(args);
119
+ const run = Command.runWith(command, { version });
120
+ return Effect.flatMap(CliRuntime, (runtime) => {
121
+ const outputMode = detectOutputModeFromArgv(args, runtime.isInteractiveTerminal);
122
+ const commandArgs = commandArgsFromArgv(args);
123
+ if (!isStructuredOutputMode(outputMode)) return run(commandArgs);
124
+ return Console.consoleWith((currentConsole) => {
125
+ const entries = [];
126
+ return run(commandArgs).pipe(Effect.provideService(Console.Console, makeBufferedConsole(entries)), Effect.tap(() => replayBufferedConsole(currentConsole, entries)), Effect.catchFilter((error) => CliError.isCliError(error) && error._tag === "ShowHelp" ? Result.succeed(error) : Result.fail(error), (error) => {
127
+ if (error.errors.length === 0) return replayBufferedConsole(currentConsole, entries);
128
+ return Effect.fail(new Error(formatCliParserError(error)));
129
+ }, (error) => Effect.fail(error)));
130
+ });
131
+ });
27
132
  }
28
133
  //#endregion
29
134
  //#region src/internal/app-layer.ts
30
135
  const makeCliAppLayer = (runtime) => {
31
136
  const runtimeLayer = runtime ? Layer.succeed(CliRuntime, runtime) : CliRuntimeLive;
32
- return Layer.mergeAll(NodeContext.layer, NodeTerminal.layer, runtimeLayer, CliOutputLive.pipe(Layer.provide(runtimeLayer)), CliConfigLive.pipe(Layer.provide(runtimeLayer)), CliSdkLive, CliStateLive);
137
+ return Layer.mergeAll(NodeServices.layer, runtimeLayer, CliOutputLive.pipe(Layer.provide(runtimeLayer)), CliConfigLive.pipe(Layer.provide(runtimeLayer)), CliSdkLive, CliStateLive);
33
138
  };
34
139
  //#endregion
35
140
  //#region src/bin.ts
36
- NodeRuntime.runMain(Effect.scoped(Effect.flatMap(CliRuntime, (runtime) => runCli(runtime.argv)).pipe(Effect.catchAllCause((cause) => Effect.gen(function* () {
141
+ NodeRuntime.runMain(Effect.scoped(Effect.flatMap(CliRuntime, (runtime) => runCli(runtime.argv)).pipe(Effect.catchCause((cause) => Effect.gen(function* () {
37
142
  const cliOutput = yield* CliOutput;
38
143
  const runtime = yield* CliRuntime;
39
144
  const outputMode = detectOutputModeFromArgv(runtime.argv, runtime.isInteractiveTerminal);
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { _ as clearPersistedState, b as resolveAuthState, d as AuthStateError, f as AuthStatusSchema, g as ResolvedAuthStateSchema, h as PutioCliConfigSchema, m as CliStateLive, p as CliState, t as describeCli, v as getAuthStatus, x as savePersistedState, y as loadPersistedState } from "./metadata-DzqePHlY.mjs";
1
+ import { _ as clearPersistedState, b as resolveAuthState, d as AuthStateError, f as AuthStatusSchema, g as ResolvedAuthStateSchema, h as PutioCliConfigSchema, m as CliStateLive, p as CliState, t as describeCli, v as getAuthStatus, x as savePersistedState, y as loadPersistedState } from "./metadata-C_yXlqxj.mjs";
2
2
  export { AuthStateError, AuthStatusSchema, CliState, CliStateLive, PutioCliConfigSchema, ResolvedAuthStateSchema, clearPersistedState, describeCli, getAuthStatus, loadPersistedState, resolveAuthState, savePersistedState };
@@ -1,19 +1,19 @@
1
- import { Clock, Config, Console, Context, Data, Duration, Effect, Fiber, Layer, Option, Schema } from "effect";
1
+ import { Cause, Clock, Config, Console, Context, Data, Duration, Effect, Fiber, Layer, Option, Queue, Schema } from "effect";
2
2
  import i18next from "i18next";
3
- import { Command, Options } from "@effect/cli";
4
- import * as Terminal from "@effect/platform/Terminal";
5
- import { DEFAULT_PUTIO_API_BASE_URL, DEFAULT_PUTIO_WEB_APP_URL, DownloadLinksCreateInputSchema, TransferAddInputSchema, createPutioSdkEffectClient, makePutioSdkLayer } from "@putdotio/sdk";
3
+ import { Command, Flag } from "effect/unstable/cli";
4
+ import * as Terminal from "effect/Terminal";
5
+ import { DEFAULT_PUTIO_API_BASE_URL, DEFAULT_PUTIO_WEB_APP_URL, DownloadLinksCreateInputSchema, TransferAddInputSchema, createPutioSdkEffectClient, makePutioSdkLiveLayer } from "@putdotio/sdk";
6
6
  import { spawn } from "node:child_process";
7
7
  import { homedir, hostname } from "node:os";
8
8
  import { dirname, join } from "node:path";
9
+ import * as SchemaAST from "effect/SchemaAST";
9
10
  import { LocalizedError, createLocalizeError } from "@putdotio/sdk/utilities";
10
11
  import Table from "cli-table3";
11
- import { FetchHttpClient } from "@effect/platform";
12
- import * as FileSystem from "@effect/platform/FileSystem";
13
- import { SystemError } from "@effect/platform/Error";
12
+ import * as FileSystem from "effect/FileSystem";
13
+ import { PlatformError, SystemError } from "effect/PlatformError";
14
14
  //#region package.json
15
15
  var name = "@putdotio/cli";
16
- var version = "1.0.11";
16
+ var version = "1.1.0";
17
17
  //#endregion
18
18
  //#region src/i18n/translate.ts
19
19
  const resources = { en: { translation: {
@@ -358,7 +358,15 @@ const createTranslator = (locale = "en") => {
358
358
  const translate = createTranslator();
359
359
  //#endregion
360
360
  //#region src/internal/agent-dx.ts
361
- const AgentDxCategoryNameSchema = Schema.Literal("machineReadableOutput", "rawPayloadInput", "schemaIntrospection", "contextWindowDiscipline", "inputHardening", "safetyRails", "agentKnowledgePackaging");
361
+ const AgentDxCategoryNameSchema = Schema.Literals([
362
+ "machineReadableOutput",
363
+ "rawPayloadInput",
364
+ "schemaIntrospection",
365
+ "contextWindowDiscipline",
366
+ "inputHardening",
367
+ "safetyRails",
368
+ "agentKnowledgePackaging"
369
+ ]);
362
370
  const AgentDxDimensionSchema = Schema.Struct({
363
371
  maxScore: Schema.Literal(3),
364
372
  name: AgentDxCategoryNameSchema,
@@ -455,7 +463,7 @@ const ENV_CLI_TOKEN = "PUTIO_CLI_TOKEN";
455
463
  const ENV_XDG_CONFIG_HOME = "XDG_CONFIG_HOME";
456
464
  //#endregion
457
465
  //#region src/internal/runtime.ts
458
- var CliRuntime = class extends Context.Tag("@putdotio/cli/CliRuntime")() {};
466
+ var CliRuntime = class extends Context.Service()("@putdotio/cli/CliRuntime") {};
459
467
  const openExternalWithPlatform = (platform, url) => {
460
468
  const command = platform === "darwin" ? {
461
469
  file: "open",
@@ -506,6 +514,20 @@ const makeCliRuntime = (options = {}) => {
506
514
  setExitCode: (code) => Effect.sync(() => {
507
515
  process.exitCode = code;
508
516
  }),
517
+ writeStdout: (message) => Effect.sync(() => {
518
+ if (options.writeStdout) {
519
+ options.writeStdout(message);
520
+ return;
521
+ }
522
+ process.stdout.write(message);
523
+ }),
524
+ writeStderr: (message) => Effect.sync(() => {
525
+ if (options.writeStderr) {
526
+ options.writeStderr(message);
527
+ return;
528
+ }
529
+ process.stderr.write(message);
530
+ }),
509
531
  openExternal: (url) => Effect.sync(() => openExternalWithPlatform(platform, url)),
510
532
  startSpinner: (message) => Effect.sync(() => {
511
533
  let frameIndex = 0;
@@ -532,27 +554,27 @@ const makeCliRuntime = (options = {}) => {
532
554
  const CliRuntimeLive = Layer.sync(CliRuntime, () => makeCliRuntime());
533
555
  //#endregion
534
556
  //#region src/internal/config.ts
535
- const NonEmptyStringSchema$4 = Schema.String.pipe(Schema.filter((value) => value.length > 0, { message: () => "Expected a non-empty string" }));
536
- const UrlStringSchema = NonEmptyStringSchema$4.pipe(Schema.filter((value) => {
557
+ const NonEmptyStringSchema$3 = Schema.String.check(Schema.isNonEmpty());
558
+ const UrlStringSchema = NonEmptyStringSchema$3.pipe(Schema.check(Schema.makeFilter((value) => {
537
559
  try {
538
560
  new URL(value);
539
- return true;
561
+ return;
540
562
  } catch {
541
- return false;
563
+ return "Expected a valid absolute URL";
542
564
  }
543
- }, { message: () => "Expected a valid absolute URL" }));
565
+ })));
544
566
  const PutioCliAuthFlowConfigSchema = Schema.Struct({
545
- appId: NonEmptyStringSchema$4,
546
- clientName: NonEmptyStringSchema$4,
567
+ appId: NonEmptyStringSchema$3,
568
+ clientName: NonEmptyStringSchema$3,
547
569
  webAppUrl: UrlStringSchema
548
570
  });
549
571
  const CliRuntimeConfigSchema = Schema.Struct({
550
572
  apiBaseUrl: UrlStringSchema,
551
- configPath: NonEmptyStringSchema$4,
552
- token: Schema.optional(NonEmptyStringSchema$4)
573
+ configPath: NonEmptyStringSchema$3,
574
+ token: Schema.optional(NonEmptyStringSchema$3)
553
575
  });
554
576
  var CliConfigError = class extends Data.TaggedError("CliConfigError") {};
555
- var CliConfig = class extends Context.Tag("@putdotio/cli/CliConfig")() {};
577
+ var CliConfig = class extends Context.Service()("@putdotio/cli/CliConfig") {};
556
578
  const optionalTrimmedString = (name) => Config.option(Config.string(name)).pipe(Config.map((value) => Option.flatMap(value, (raw) => {
557
579
  const trimmed = raw.trim();
558
580
  return trimmed.length > 0 ? Option.some(trimmed) : Option.none();
@@ -621,23 +643,51 @@ const waitForDeviceToken = (options) => Effect.gen(function* () {
621
643
  });
622
644
  //#endregion
623
645
  //#region src/internal/command-specs.ts
624
- const NonEmptyStringSchema$3 = Schema.String.pipe(Schema.filter((value) => value.length > 0, { message: () => "Expected a non-empty string" }));
625
- const OutputModeSchema = Schema.Literal("json", "text", "ndjson");
626
- const InternalRendererSchema = Schema.Literal("json", "terminal", "ndjson");
627
- const CommandKindSchema = Schema.Literal("utility", "auth", "read", "write");
628
- const CommandOptionTypeSchema = Schema.Literal("string", "integer", "boolean", "enum");
629
- const JsonPrimitiveKindSchema = Schema.Literal("string", "integer", "boolean", "null");
646
+ const NonEmptyStringSchema$2 = Schema.String.check(Schema.isNonEmpty());
647
+ const OutputModeSchema = Schema.Literals([
648
+ "json",
649
+ "text",
650
+ "ndjson"
651
+ ]);
652
+ const InternalRendererSchema = Schema.Literals([
653
+ "json",
654
+ "terminal",
655
+ "ndjson"
656
+ ]);
657
+ const CommandKindSchema = Schema.Literals([
658
+ "utility",
659
+ "auth",
660
+ "read",
661
+ "write"
662
+ ]);
663
+ const CommandOptionTypeSchema = Schema.Literals([
664
+ "string",
665
+ "integer",
666
+ "boolean",
667
+ "enum"
668
+ ]);
669
+ const JsonPrimitiveKindSchema = Schema.Literals([
670
+ "string",
671
+ "integer",
672
+ "boolean",
673
+ "null"
674
+ ]);
630
675
  const JsonScalarSchema = Schema.Struct({ kind: JsonPrimitiveKindSchema });
631
- const JsonEnumValueSchema = Schema.Union(Schema.String, Schema.Number, Schema.Boolean, Schema.Null);
676
+ const JsonEnumValueSchema = Schema.Union([
677
+ Schema.String,
678
+ Schema.Number,
679
+ Schema.Boolean,
680
+ Schema.Null
681
+ ]);
632
682
  const JsonPropertySchema = Schema.Struct({
633
- name: NonEmptyStringSchema$3,
683
+ name: NonEmptyStringSchema$2,
634
684
  required: Schema.Boolean,
635
685
  schema: Schema.suspend(() => CommandJsonShapeSchema)
636
686
  });
637
687
  const JsonObjectSchema = Schema.Struct({
638
688
  kind: Schema.Literal("object"),
639
689
  properties: Schema.Array(JsonPropertySchema),
640
- rules: Schema.optional(Schema.Array(NonEmptyStringSchema$3))
690
+ rules: Schema.optional(Schema.Array(NonEmptyStringSchema$2))
641
691
  });
642
692
  const JsonArraySchema = Schema.Struct({
643
693
  kind: Schema.Literal("array"),
@@ -647,12 +697,21 @@ const JsonEnumSchema = Schema.Struct({
647
697
  kind: Schema.Literal("enum"),
648
698
  values: Schema.Array(JsonEnumValueSchema)
649
699
  });
650
- const CommandJsonShapeSchema = Schema.Union(JsonScalarSchema, JsonEnumSchema, JsonObjectSchema, JsonArraySchema);
700
+ const CommandJsonShapeSchema = Schema.Union([
701
+ JsonScalarSchema,
702
+ JsonEnumSchema,
703
+ JsonObjectSchema,
704
+ JsonArraySchema
705
+ ]);
651
706
  const CommandOptionSchema = Schema.Struct({
652
- choices: Schema.optional(Schema.Array(NonEmptyStringSchema$3)),
653
- defaultValue: Schema.optional(Schema.Union(NonEmptyStringSchema$3, Schema.Number, Schema.Boolean)),
654
- description: Schema.optional(NonEmptyStringSchema$3),
655
- name: NonEmptyStringSchema$3,
707
+ choices: Schema.optional(Schema.Array(NonEmptyStringSchema$2)),
708
+ defaultValue: Schema.optional(Schema.Union([
709
+ NonEmptyStringSchema$2,
710
+ Schema.Number,
711
+ Schema.Boolean
712
+ ])),
713
+ description: Schema.optional(NonEmptyStringSchema$2),
714
+ name: NonEmptyStringSchema$2,
656
715
  repeated: Schema.Boolean,
657
716
  required: Schema.Boolean,
658
717
  type: CommandOptionTypeSchema
@@ -671,10 +730,10 @@ const CommandAuthSchema = Schema.Struct({ required: Schema.Boolean });
671
730
  const CommandDescriptorSchema = Schema.Struct({
672
731
  auth: CommandAuthSchema,
673
732
  capabilities: CommandCapabilitiesSchema,
674
- command: NonEmptyStringSchema$3,
733
+ command: NonEmptyStringSchema$2,
675
734
  input: CommandInputSchema,
676
735
  kind: CommandKindSchema,
677
- purpose: NonEmptyStringSchema$3
736
+ purpose: NonEmptyStringSchema$2
678
737
  });
679
738
  const CliOutputContractSchema = Schema.Struct({
680
739
  defaultInteractive: Schema.Literal("text"),
@@ -808,31 +867,38 @@ const enumFlag = (name, choices, options = {}) => ({
808
867
  });
809
868
  const unwrapSchemaAst = (ast) => {
810
869
  let current = ast;
811
- while (current && (current._tag === "Refinement" || current._tag === "Transformation" || current._tag === "Suspend")) current = current._tag === "Suspend" ? current.type : current._tag === "Transformation" ? current.to : current.from;
870
+ while (current && current._tag === "Suspend") current = current.thunk();
812
871
  return current;
813
872
  };
814
873
  const schemaAstToJsonShape = (ast) => {
815
874
  const current = unwrapSchemaAst(ast);
816
875
  switch (current?._tag) {
817
- case "StringKeyword": return stringShape();
818
- case "NumberKeyword": return integerShape();
819
- case "BooleanKeyword": return booleanShape();
820
- case "UndefinedKeyword":
821
- case "VoidKeyword":
822
- case "NullKeyword": return nullShape();
823
- case "Literal": return enumShape([current.literal ?? null]);
824
- case "TupleType": {
825
- const item = current.rest?.[0]?.type;
876
+ case "String": return stringShape();
877
+ case "Number": return integerShape();
878
+ case "Boolean": return booleanShape();
879
+ case "Undefined":
880
+ case "Void":
881
+ case "Null": return nullShape();
882
+ case "Literal": {
883
+ const literal = current.literal ?? null;
884
+ if (typeof literal === "bigint") throw new Error("BigInt literals are not supported in CLI json metadata.");
885
+ return enumShape([literal]);
886
+ }
887
+ case "Arrays": {
888
+ const item = current.rest[0] ?? current.elements[0];
826
889
  if (!item) throw new Error("Unable to derive an array item schema from an empty tuple AST.");
827
890
  return arrayShape(schemaAstToJsonShape(item));
828
891
  }
829
- case "TypeLiteral": return objectShape((current.propertySignatures ?? []).map((propertySignature) => property(propertySignature.name, schemaAstToJsonShape(propertySignature.type), propertySignature.isOptional !== true)));
892
+ case "Objects": return objectShape(current.propertySignatures.map((propertySignature) => property(String(propertySignature.name), schemaAstToJsonShape(propertySignature.type), !SchemaAST.isOptional(propertySignature.type))));
830
893
  case "Union": {
831
- const definedTypes = (current.types ?? []).filter((type) => unwrapSchemaAst(type)?._tag !== "UndefinedKeyword");
894
+ const definedTypes = current.types.filter((type) => unwrapSchemaAst(type)?._tag !== "Undefined");
832
895
  const enumValues = definedTypes.flatMap((type) => {
833
896
  const unwrapped = unwrapSchemaAst(type);
834
- if (unwrapped?._tag === "Literal") return [unwrapped.literal ?? null];
835
- if (unwrapped?._tag === "NullKeyword" || unwrapped?._tag === "VoidKeyword") return [null];
897
+ if (unwrapped?._tag === "Literal") {
898
+ const literal = unwrapped.literal ?? null;
899
+ return typeof literal === "bigint" ? [] : [literal];
900
+ }
901
+ if (unwrapped?._tag === "Null" || unwrapped?._tag === "Void") return [null];
836
902
  return [];
837
903
  });
838
904
  if (enumValues.length === definedTypes.length && enumValues.length > 0) return enumShape(enumValues);
@@ -1272,6 +1338,7 @@ const normalizeOutputMode = (output, isInteractiveTerminal = true) => {
1272
1338
  if (output === "text") return "terminal";
1273
1339
  return isInteractiveTerminal ? "terminal" : "json";
1274
1340
  };
1341
+ const normalizeRequestedOrResolvedOutputMode = (output, isInteractiveTerminal = true) => output === "terminal" ? "terminal" : normalizeOutputMode(output, isInteractiveTerminal);
1275
1342
  const detectOutputModeFromArgv = (argv, isInteractiveTerminal = true) => {
1276
1343
  for (let index = 0; index < argv.length; index += 1) {
1277
1344
  const argument = argv[index];
@@ -1290,15 +1357,28 @@ const SAFE_SENSITIVE_SENTINEL_VALUES = new Set([
1290
1357
  REDACTED_VALUE
1291
1358
  ]);
1292
1359
  const PROMPT_INJECTION_PATTERN = /\b(ignore (?:all|any|previous|above)|system prompt|developer message|tool call|function call|follow these instructions|you are chatgpt|you are an ai)\b/iu;
1293
- const isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1294
- const sanitizeTerminalText = (value) => value.replace(/(Authorization\s*:\s*Bearer\s+)([A-Za-z0-9._~+/=-]+)/gi, (_match, prefix) => `${prefix}${REDACTED_VALUE}`).replace(/((?:auth_?token|access_?token|refresh_?token|oauth_?token|token|password|secret|cookie)["']?\s*[:=]\s*["']?)([^"'&,\s}]+)/gi, (_match, prefix) => `${prefix}${REDACTED_VALUE}`).replace(/(Bearer\s+)([A-Za-z0-9._~+/=-]+)/gi, (_match, prefix) => `${prefix}${REDACTED_VALUE}`).replace(/([?&](?:auth_?token|access_?token|refresh_?token|oauth_?token|token)=)([^&\s]+)/gi, (_match, prefix) => `${prefix}${REDACTED_VALUE}`);
1360
+ const TERMINAL_ESCAPE_PATTERN = new RegExp(String.raw`\u001B(?:\][^\u0007]*(?:\u0007|\u001B\\)|\[[0-?]*[ -/]*[@-~]|[@-Z\\-_])`, "gu");
1361
+ const TERMINAL_CONTROL_PATTERN = new RegExp(String.raw`[\u0000-\u0008\u000B-\u001F\u007F-\u009F]`, "gu");
1362
+ const isPlainObject = (value) => {
1363
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
1364
+ const prototype = Object.getPrototypeOf(value);
1365
+ return prototype === Object.prototype || prototype === null;
1366
+ };
1367
+ const redactSensitiveText = (value) => value.replace(/(Authorization\s*:\s*Bearer\s+)([A-Za-z0-9._~+/=-]+)/gi, (_match, prefix) => `${prefix}${REDACTED_VALUE}`).replace(/((?:auth_?token|access_?token|refresh_?token|oauth_?token|token|password|secret|cookie)["']?\s*[:=]\s*["']?)([^"'&,\s}]+)/gi, (_match, prefix) => `${prefix}${REDACTED_VALUE}`).replace(/(Bearer\s+)([A-Za-z0-9._~+/=-]+)/gi, (_match, prefix) => `${prefix}${REDACTED_VALUE}`).replace(/([?&](?:auth_?token|access_?token|refresh_?token|oauth_?token|token)=)([^&\s]+)/gi, (_match, prefix) => `${prefix}${REDACTED_VALUE}`);
1368
+ const sanitizeTerminalText = (value) => redactSensitiveText(value).replace(TERMINAL_ESCAPE_PATTERN, "").replace(TERMINAL_CONTROL_PATTERN, "");
1369
+ const sanitizeTerminalValue = (value) => {
1370
+ if (typeof value === "string") return sanitizeTerminalText(value);
1371
+ if (Array.isArray(value)) return value.map(sanitizeTerminalValue);
1372
+ if (isPlainObject(value)) return Object.fromEntries(Object.entries(value).map(([key, nestedValue]) => [key, SENSITIVE_KEY_PATTERN.test(key) && typeof nestedValue === "string" ? SAFE_SENSITIVE_SENTINEL_VALUES.has(nestedValue) ? nestedValue : REDACTED_VALUE : sanitizeTerminalValue(nestedValue)]));
1373
+ return value;
1374
+ };
1295
1375
  const joinPath = (segments) => segments.reduce((path, segment) => {
1296
1376
  if (segment.startsWith("[")) return `${path}${segment}`;
1297
1377
  return path === "$" ? `$.${segment}` : `${path}.${segment}`;
1298
1378
  }, "$");
1299
1379
  const sanitizeStructuredValueInternal = (value, path) => {
1300
1380
  if (typeof value === "string") {
1301
- const sanitized = sanitizeTerminalText(value);
1381
+ const sanitized = redactSensitiveText(value);
1302
1382
  return {
1303
1383
  untrustedTextPaths: PROMPT_INJECTION_PATTERN.test(sanitized) ? [joinPath(path)] : [],
1304
1384
  value: sanitized
@@ -1356,7 +1436,7 @@ const sanitizeStructuredValueInternal = (value, path) => {
1356
1436
  const sanitizeStructuredValue = (value) => sanitizeStructuredValueInternal(value, []).value;
1357
1437
  const renderJson = (value) => JSON.stringify(sanitizeStructuredValue(value), null, 2);
1358
1438
  const renderNdjson = (value) => JSON.stringify(sanitizeStructuredValue(value));
1359
- const renderTerminal = (value, renderTerminalValue) => sanitizeTerminalText(renderTerminalValue(value));
1439
+ const renderTerminal = (value, renderTerminalValue) => redactSensitiveText(renderTerminalValue(sanitizeTerminalValue(value)));
1360
1440
  const toCliErrorView = (error) => {
1361
1441
  const meta = error.meta;
1362
1442
  return {
@@ -1381,61 +1461,60 @@ const toCliErrorJson = (error) => {
1381
1461
  };
1382
1462
  const localizeError = (error) => isLocalizedError(error) ? error : localizeCliError(error);
1383
1463
  const formatCliError = (error) => {
1384
- return sanitizeTerminalText(renderCliErrorTerminal(toCliErrorView(localizeError(error))));
1464
+ return redactSensitiveText(renderCliErrorTerminal(sanitizeTerminalValue(toCliErrorView(localizeError(error)))));
1385
1465
  };
1386
1466
  const formatCliErrorJson = (error) => {
1387
1467
  return renderJson(toCliErrorJson(isLocalizedError(error) ? error : localizeCliError(error)));
1388
1468
  };
1389
- var CliOutput = class extends Context.Tag("@putdotio/cli/CliOutput")() {};
1469
+ var CliOutput = class extends Context.Service()("@putdotio/cli/CliOutput") {};
1390
1470
  const makeCliOutput = (runtime) => ({
1391
- formatError: (error, output) => isStructuredOutputMode(normalizeOutputMode(output, runtime.isInteractiveTerminal)) ? formatCliErrorJson(error) : formatCliError(error),
1471
+ formatError: (error, output) => isStructuredOutputMode(normalizeRequestedOrResolvedOutputMode(output, runtime.isInteractiveTerminal)) ? formatCliErrorJson(error) : formatCliError(error),
1392
1472
  error: (message) => Console.error(sanitizeTerminalText(message)),
1393
- write: (value, output, renderTerminalValue) => Console.log((() => {
1394
- switch (normalizeOutputMode(output, runtime.isInteractiveTerminal)) {
1395
- case "terminal": return renderTerminal(value, renderTerminalValue);
1396
- case "ndjson": return renderNdjson(value);
1397
- case "json": return renderJson(value);
1398
- }
1399
- })())
1473
+ write: (value, output, renderTerminalValue) => {
1474
+ const outputMode = normalizeOutputMode(output, runtime.isInteractiveTerminal);
1475
+ const rendered = outputMode === "terminal" ? renderTerminal(value, renderTerminalValue) : outputMode === "ndjson" ? renderNdjson(value) : renderJson(value);
1476
+ return runtime.writeStdout(`${rendered}\n`);
1477
+ }
1400
1478
  });
1401
1479
  const CliOutputLive = Layer.effect(CliOutput, Effect.map(CliRuntime, makeCliOutput));
1402
1480
  const writeOutput = (value, output, renderTerminalValue) => Effect.flatMap(CliOutput, (cliOutput) => cliOutput.write(value, output, renderTerminalValue));
1403
1481
  //#endregion
1404
1482
  //#region src/internal/sdk.ts
1405
1483
  const sdk = createPutioSdkEffectClient();
1406
- var CliSdk = class extends Context.Tag("@putdotio/cli/CliSdk")() {};
1484
+ var CliSdk = class extends Context.Service()("@putdotio/cli/CliSdk") {};
1407
1485
  const makeCliSdk = () => ({
1408
1486
  client: sdk,
1409
- provide: (config, program) => program.pipe(Effect.provide(makePutioSdkLayer({
1487
+ provide: (config, program) => program.pipe(Effect.provide(makePutioSdkLiveLayer({
1410
1488
  accessToken: config.token,
1411
1489
  baseUrl: config.apiBaseUrl
1412
1490
  })))
1413
1491
  });
1414
- const CliSdkLive = Layer.mergeAll(FetchHttpClient.layer, Layer.succeed(CliSdk, makeCliSdk()));
1492
+ const CliSdkLive = Layer.succeed(CliSdk, makeCliSdk());
1415
1493
  const provideSdk = (config, program) => Effect.flatMap(CliSdk, (cliSdk) => cliSdk.provide(config, program));
1416
1494
  //#endregion
1417
1495
  //#region src/internal/state.ts
1418
- const NonEmptyStringSchema$2 = Schema.String.pipe(Schema.filter((value) => value.length > 0, { message: () => "Expected a non-empty string" }));
1496
+ const NonEmptyStringSchema$1 = Schema.String.check(Schema.isNonEmpty());
1419
1497
  const PutioCliConfigSchema = Schema.Struct({
1420
- api_base_url: NonEmptyStringSchema$2,
1421
- auth_token: Schema.optional(NonEmptyStringSchema$2)
1498
+ api_base_url: NonEmptyStringSchema$1,
1499
+ auth_token: Schema.optional(NonEmptyStringSchema$1)
1422
1500
  });
1423
1501
  const ResolvedAuthStateSchema = Schema.Struct({
1424
- apiBaseUrl: NonEmptyStringSchema$2,
1425
- configPath: NonEmptyStringSchema$2,
1426
- source: Schema.Literal("env", "config"),
1427
- token: NonEmptyStringSchema$2
1502
+ apiBaseUrl: NonEmptyStringSchema$1,
1503
+ configPath: NonEmptyStringSchema$1,
1504
+ source: Schema.Literals(["env", "config"]),
1505
+ token: NonEmptyStringSchema$1
1428
1506
  });
1429
1507
  const AuthStatusSchema = Schema.Struct({
1430
- apiBaseUrl: NonEmptyStringSchema$2,
1508
+ apiBaseUrl: NonEmptyStringSchema$1,
1431
1509
  authenticated: Schema.Boolean,
1432
- configPath: NonEmptyStringSchema$2,
1433
- source: Schema.NullOr(Schema.Literal("env", "config"))
1510
+ configPath: NonEmptyStringSchema$1,
1511
+ source: Schema.NullOr(Schema.Literals(["env", "config"]))
1434
1512
  });
1435
1513
  var AuthStateError = class extends Data.TaggedError("AuthStateError") {};
1436
- var CliState = class extends Context.Tag("@putdotio/cli/CliState")() {};
1514
+ var CliState = class extends Context.Service()("@putdotio/cli/CliState") {};
1437
1515
  const decodePersistedConfig = Schema.decodeUnknownSync(PutioCliConfigSchema);
1438
1516
  const mapFileSystemError = (error, message) => error instanceof AuthStateError ? error : new AuthStateError({ message });
1517
+ const resolveAuthRuntimeConfig = () => resolveCliRuntimeConfig().pipe(Effect.mapError((error) => new AuthStateError({ message: error.message })));
1439
1518
  const parsePersistedConfig = (raw) => {
1440
1519
  let value;
1441
1520
  try {
@@ -1451,8 +1530,8 @@ const parsePersistedConfig = (raw) => {
1451
1530
  };
1452
1531
  const loadPersistedStateEffect = (configPath) => Effect.gen(function* () {
1453
1532
  const fs = yield* FileSystem.FileSystem;
1454
- const effectiveConfigPath = configPath ?? (yield* resolveCliRuntimeConfig()).configPath;
1455
- const rawConfig = yield* fs.readFileString(effectiveConfigPath, "utf8").pipe(Effect.catchIf((error) => error instanceof SystemError && error.reason === "NotFound", () => Effect.succeed(null)), Effect.mapError((error) => mapFileSystemError(error, `Unable to read CLI config at ${effectiveConfigPath}.`)));
1533
+ const effectiveConfigPath = configPath ?? (yield* resolveAuthRuntimeConfig()).configPath;
1534
+ const rawConfig = yield* fs.readFileString(effectiveConfigPath, "utf8").pipe(Effect.catchIf((error) => error instanceof PlatformError && error.reason instanceof SystemError && error.reason._tag === "NotFound", () => Effect.succeed(null)), Effect.mapError((error) => mapFileSystemError(error, `Unable to read CLI config at ${effectiveConfigPath}.`)));
1456
1535
  if (rawConfig === null) return null;
1457
1536
  return yield* Effect.try({
1458
1537
  try: () => parsePersistedConfig(rawConfig),
@@ -1462,7 +1541,7 @@ const loadPersistedStateEffect = (configPath) => Effect.gen(function* () {
1462
1541
  const savePersistedStateEffect = (state, configPath) => Effect.gen(function* () {
1463
1542
  const fs = yield* FileSystem.FileSystem;
1464
1543
  const runtime = yield* CliRuntime;
1465
- const effectiveConfigPath = configPath ?? (yield* resolveCliRuntimeConfig()).configPath;
1544
+ const effectiveConfigPath = configPath ?? (yield* resolveAuthRuntimeConfig()).configPath;
1466
1545
  const existingConfig = yield* loadPersistedStateEffect(effectiveConfigPath);
1467
1546
  const persistedState = {
1468
1547
  api_base_url: state.apiBaseUrl ?? existingConfig?.api_base_url ?? DEFAULT_PUTIO_API_BASE_URL,
@@ -1479,7 +1558,7 @@ const savePersistedStateEffect = (state, configPath) => Effect.gen(function* ()
1479
1558
  const clearPersistedStateEffect = (configPath) => Effect.gen(function* () {
1480
1559
  const fs = yield* FileSystem.FileSystem;
1481
1560
  const runtime = yield* CliRuntime;
1482
- const effectiveConfigPath = configPath ?? (yield* resolveCliRuntimeConfig()).configPath;
1561
+ const effectiveConfigPath = configPath ?? (yield* resolveAuthRuntimeConfig()).configPath;
1483
1562
  const existingConfig = yield* loadPersistedStateEffect(effectiveConfigPath);
1484
1563
  if (existingConfig && existingConfig.api_base_url !== DEFAULT_PUTIO_API_BASE_URL) {
1485
1564
  const nextConfig = { api_base_url: existingConfig.api_base_url };
@@ -1490,7 +1569,7 @@ const clearPersistedStateEffect = (configPath) => Effect.gen(function* () {
1490
1569
  return { configPath: effectiveConfigPath };
1491
1570
  });
1492
1571
  const getAuthStatusEffect = () => Effect.gen(function* () {
1493
- const runtime = yield* resolveCliRuntimeConfig();
1572
+ const runtime = yield* resolveAuthRuntimeConfig();
1494
1573
  if (runtime.token) return {
1495
1574
  authenticated: true,
1496
1575
  source: "env",
@@ -1511,7 +1590,7 @@ const getAuthStatusEffect = () => Effect.gen(function* () {
1511
1590
  };
1512
1591
  });
1513
1592
  const resolveAuthStateEffect = () => Effect.gen(function* () {
1514
- const runtime = yield* resolveCliRuntimeConfig();
1593
+ const runtime = yield* resolveAuthRuntimeConfig();
1515
1594
  if (runtime.token) return {
1516
1595
  token: runtime.token,
1517
1596
  apiBaseUrl: runtime.apiBaseUrl,
@@ -1542,60 +1621,73 @@ const getAuthStatus = () => Effect.flatMap(CliState, (state) => state.getAuthSta
1542
1621
  const resolveAuthState = () => Effect.flatMap(CliState, (state) => state.resolveAuthState());
1543
1622
  //#endregion
1544
1623
  //#region src/internal/command.ts
1545
- const outputOption = Options.choice("output", [
1624
+ const outputOption = Flag.choice("output", [
1546
1625
  "json",
1547
1626
  "text",
1548
1627
  "ndjson"
1549
- ]).pipe(Options.optional);
1550
- const dryRunOption = Options.boolean("dry-run").pipe(Options.withDefault(false));
1551
- const fieldsOption = Options.text("fields").pipe(Options.optional);
1552
- const jsonOption = Options.text("json").pipe(Options.optional);
1553
- const pageAllOption = Options.boolean("page-all").pipe(Options.withDefault(false));
1628
+ ]).pipe(Flag.optional);
1629
+ const dryRunOption = Flag.boolean("dry-run").pipe(Flag.withDefault(false));
1630
+ const fieldsOption = Flag.string("fields").pipe(Flag.optional);
1631
+ const jsonOption = Flag.string("json").pipe(Flag.optional);
1632
+ const pageAllOption = Flag.boolean("page-all").pipe(Flag.withDefault(false));
1554
1633
  const defineBooleanOption = (name, options = {}) => {
1555
- const option = options.defaultValue === void 0 ? Options.boolean(name) : Options.boolean(name).pipe(Options.withDefault(options.defaultValue));
1634
+ const option = options.defaultValue === void 0 ? Flag.boolean(name) : Flag.boolean(name).pipe(Flag.withDefault(options.defaultValue));
1556
1635
  return {
1557
1636
  flag: booleanFlag(name, options),
1558
1637
  option
1559
1638
  };
1560
1639
  };
1561
- const defineIntegerOption = (name, options = {}) => {
1562
- const option = options.optional ? Options.integer(name).pipe(Options.optional) : Options.integer(name);
1563
- return {
1564
- flag: integerFlag(name, {
1565
- description: options.description,
1566
- required: options.required ?? options.optional !== true
1567
- }),
1568
- option
1640
+ function defineIntegerOption(name, options = {}) {
1641
+ const flag = integerFlag(name, {
1642
+ description: options.description,
1643
+ required: options.required ?? options.optional !== true
1644
+ });
1645
+ return options.optional === true ? {
1646
+ flag,
1647
+ option: Flag.integer(name).pipe(Flag.optional)
1648
+ } : {
1649
+ flag,
1650
+ option: Flag.integer(name)
1569
1651
  };
1570
- };
1571
- const defineTextOption = (name, options = {}) => {
1572
- let option = Options.text(name);
1573
- if (options.defaultValue !== void 0) option = option.pipe(Options.withDefault(options.defaultValue));
1574
- else if (options.optional) option = option.pipe(Options.optional);
1575
- return {
1576
- flag: stringFlag(name, {
1577
- defaultValue: options.defaultValue,
1578
- description: options.description,
1579
- required: options.required ?? (options.defaultValue === void 0 && options.optional !== true)
1580
- }),
1581
- option
1652
+ }
1653
+ function defineTextOption(name, options = {}) {
1654
+ const flag = stringFlag(name, {
1655
+ defaultValue: options.defaultValue,
1656
+ description: options.description,
1657
+ required: options.required ?? (options.defaultValue === void 0 && options.optional !== true)
1658
+ });
1659
+ if (options.defaultValue !== void 0) return {
1660
+ flag,
1661
+ option: Flag.string(name).pipe(Flag.withDefault(options.defaultValue))
1582
1662
  };
1583
- };
1584
- const defineChoiceOption = (name, choices, options = {}) => {
1585
- const option = options.optional ? Options.choice(name, choices).pipe(Options.optional) : Options.choice(name, choices);
1586
- return {
1587
- flag: enumFlag(name, choices, {
1588
- description: options.description,
1589
- required: options.required ?? options.optional !== true
1590
- }),
1591
- option
1663
+ return options.optional === true ? {
1664
+ flag,
1665
+ option: Flag.string(name).pipe(Flag.optional)
1666
+ } : {
1667
+ flag,
1668
+ option: Flag.string(name)
1592
1669
  };
1593
- };
1670
+ }
1671
+ function defineChoiceOption(name, choices, options = {}) {
1672
+ const flag = enumFlag(name, choices, {
1673
+ description: options.description,
1674
+ required: options.required ?? options.optional !== true
1675
+ });
1676
+ return options.optional === true ? {
1677
+ flag,
1678
+ option: Flag.choice(name, choices).pipe(Flag.optional)
1679
+ } : {
1680
+ flag,
1681
+ option: Flag.choice(name, choices)
1682
+ };
1683
+ }
1594
1684
  const getOption = (option) => Option.getOrUndefined(option);
1595
1685
  var CliCommandInputError = class extends Data.TaggedError("CliCommandInputError") {};
1596
1686
  const PATH_TRAVERSAL_PATTERN = /(?:^|[\\/])\.\.(?:[\\/]|$)|%2e/iu;
1597
1687
  const QUERY_OR_FRAGMENT_PATTERN = /[?#]/u;
1598
1688
  const TOP_LEVEL_FIELD_PATTERN = /^[A-Za-z0-9_-]+$/u;
1689
+ const MAX_CURSOR_PAGES = 1e3;
1690
+ const MAX_CURSOR_ITEMS = 1e5;
1599
1691
  const ownKeys = (value) => Object.keys(value);
1600
1692
  const hasControlCharacters = (value) => [...value].some((character) => {
1601
1693
  const codePoint = character.codePointAt(0);
@@ -1620,10 +1712,10 @@ const validateNameLikeInput = (label, value) => validateSafeString({
1620
1712
  const parseRequestedFields = (raw) => {
1621
1713
  const parts = raw.split(",").map((part) => part.trim());
1622
1714
  if (parts.length === 0 || parts.some((part) => part.length === 0)) throw new CliCommandInputError({ message: "Expected `--fields` to be a comma-separated list of top-level field names." });
1623
- return [...new Set(parts.map((part) => validateSafeString({
1624
- label: `\`--fields\` selector \`${part}\``,
1715
+ return [...new Set(parts.map((part, index) => validateSafeString({
1716
+ label: `\`--fields\` selector #${index + 1}`,
1625
1717
  pattern: TOP_LEVEL_FIELD_PATTERN,
1626
- patternMessage: "`--fields` only accepts top-level field names without dots, brackets, or slashes.",
1718
+ patternMessage: `\`--fields\` selector #${index + 1} only accepts top-level field names without dots, brackets, or slashes.`,
1627
1719
  value: part
1628
1720
  })))];
1629
1721
  };
@@ -1637,6 +1729,17 @@ const readPageItems = (value, itemKey, command) => {
1637
1729
  if (!Array.isArray(items)) throw new CliCommandInputError({ message: `Expected \`${command}\` responses to include an array at \`${itemKey}\`.` });
1638
1730
  return items;
1639
1731
  };
1732
+ const assertCursorPageBudget = (input) => {
1733
+ if (input.pageCount > MAX_CURSOR_PAGES) throw new CliCommandInputError({ message: `\`${input.command}\` pagination exceeded ${MAX_CURSOR_PAGES} pages.` });
1734
+ if (input.itemCount > MAX_CURSOR_ITEMS) throw new CliCommandInputError({ message: `\`${input.command}\` pagination exceeded ${MAX_CURSOR_ITEMS} items.` });
1735
+ };
1736
+ const assertCursorNotSeen = (input) => {
1737
+ if (input.seenCursors.has(input.cursor)) throw new CliCommandInputError({ message: `\`${input.command}\` pagination returned a repeated cursor.` });
1738
+ input.seenCursors.add(input.cursor);
1739
+ };
1740
+ const assertCursorNotRepeated = (input) => {
1741
+ if (input.cursor !== null && input.seenCursors.has(input.cursor)) throw new CliCommandInputError({ message: `\`${input.command}\` pagination returned a repeated cursor.` });
1742
+ };
1640
1743
  const integerPattern = /^-?\d+$/;
1641
1744
  const parseRepeatedIntegers = (values) => {
1642
1745
  const parsed = [];
@@ -1646,14 +1749,14 @@ const parseRepeatedIntegers = (values) => {
1646
1749
  }
1647
1750
  return Option.some(parsed);
1648
1751
  };
1649
- const parseRepeatedIntegerOption = (name) => Options.text(name).pipe(Options.repeated, Options.filterMap(parseRepeatedIntegers, `Expected \`--${name}\` values to be integers.`));
1752
+ const parseRepeatedIntegerOption = (name) => Flag.string(name).pipe(Flag.atLeast(0), Flag.filterMap(parseRepeatedIntegers, () => `Expected \`--${name}\` values to be integers.`));
1650
1753
  const defineRepeatedIntegerOption = (name, options = {}) => ({
1651
1754
  flag: repeatedIntegerFlag(name, options),
1652
1755
  option: parseRepeatedIntegerOption(name)
1653
1756
  });
1654
1757
  const defineRepeatedTextOption = (name, options = {}) => ({
1655
1758
  flag: repeatedStringFlag(name, options),
1656
- option: Options.text(name).pipe(Options.repeated)
1759
+ option: Flag.string(name).pipe(Flag.atLeast(0))
1657
1760
  });
1658
1761
  const mapInputError = (error, fallbackMessage) => error instanceof CliCommandInputError ? error : new CliCommandInputError({ message: fallbackMessage });
1659
1762
  const decodeJsonOption = (schema, raw) => Effect.try({
@@ -1701,11 +1804,36 @@ const selectTopLevelFields = (input) => Effect.try({
1701
1804
  const collectAllCursorPages = (input) => Effect.gen(function* () {
1702
1805
  if (!input.pageAll) return input.initial;
1703
1806
  const collectedItems = [...readPageItems(input.initial, input.itemKey, input.command)];
1807
+ const seenCursors = /* @__PURE__ */ new Set();
1704
1808
  let cursor = readCursor(input.initial);
1809
+ let pageCount = 1;
1810
+ assertCursorPageBudget({
1811
+ command: input.command,
1812
+ itemCount: collectedItems.length,
1813
+ pageCount
1814
+ });
1705
1815
  while (cursor !== null) {
1816
+ assertCursorNotSeen({
1817
+ command: input.command,
1818
+ cursor,
1819
+ seenCursors
1820
+ });
1706
1821
  const nextPage = yield* input.continueWithCursor(cursor);
1707
- collectedItems.push(...readPageItems(nextPage, input.itemKey, input.command));
1708
- cursor = readCursor(nextPage);
1822
+ const nextCursor = readCursor(nextPage);
1823
+ assertCursorNotRepeated({
1824
+ command: input.command,
1825
+ cursor: nextCursor,
1826
+ seenCursors
1827
+ });
1828
+ const pageItems = readPageItems(nextPage, input.itemKey, input.command);
1829
+ pageCount += 1;
1830
+ collectedItems.push(...pageItems);
1831
+ assertCursorPageBudget({
1832
+ command: input.command,
1833
+ itemCount: collectedItems.length,
1834
+ pageCount
1835
+ });
1836
+ cursor = nextCursor;
1709
1837
  }
1710
1838
  return {
1711
1839
  ...input.initial,
@@ -1740,7 +1868,18 @@ const writeReadPages = (input) => Effect.gen(function* () {
1740
1868
  });
1741
1869
  }
1742
1870
  let current = input.initial;
1871
+ const seenCursors = /* @__PURE__ */ new Set();
1872
+ let pageCount = 1;
1873
+ let streamedItemCount = 0;
1743
1874
  while (true) {
1875
+ if (input.itemKey) {
1876
+ streamedItemCount += readPageItems(current, input.itemKey, input.command).length;
1877
+ assertCursorPageBudget({
1878
+ command: input.command,
1879
+ itemCount: streamedItemCount,
1880
+ pageCount
1881
+ });
1882
+ }
1744
1883
  yield* writeOutput(yield* selectTopLevelFields({
1745
1884
  command: input.command,
1746
1885
  requestedFields: input.controls.requestedFields,
@@ -1749,7 +1888,19 @@ const writeReadPages = (input) => Effect.gen(function* () {
1749
1888
  if (!input.controls.pageAll || !input.continueWithCursor || !input.itemKey) return;
1750
1889
  const cursor = readCursor(current);
1751
1890
  if (cursor === null) return;
1752
- current = yield* input.continueWithCursor(cursor);
1891
+ assertCursorNotSeen({
1892
+ command: input.command,
1893
+ cursor,
1894
+ seenCursors
1895
+ });
1896
+ const nextPage = yield* input.continueWithCursor(cursor);
1897
+ assertCursorNotRepeated({
1898
+ command: input.command,
1899
+ cursor: readCursor(nextPage),
1900
+ seenCursors
1901
+ });
1902
+ current = nextPage;
1903
+ pageCount += 1;
1753
1904
  }
1754
1905
  });
1755
1906
  const renderDryRunPlanTerminal = (value) => [
@@ -1851,15 +2002,13 @@ const previewCodeOption = previewCodeConfig.option;
1851
2002
  const waitForOpenShortcut = (url) => Effect.gen(function* () {
1852
2003
  const runtimeService = yield* CliRuntime;
1853
2004
  if (!runtimeService.isInteractiveTerminal) return false;
1854
- const terminal = yield* Terminal.Terminal;
1855
- if (!(yield* terminal.isTTY)) return false;
1856
- const input = yield* terminal.readInput;
2005
+ const input = yield* (yield* Terminal.Terminal).readInput;
1857
2006
  while (true) {
1858
- const event = yield* input.take;
2007
+ const event = yield* Queue.take(input);
1859
2008
  const keyInput = Option.getOrElse(event.input, () => "").toLowerCase();
1860
2009
  if (event.key.name === "o" || keyInput === "o") return yield* runtimeService.openExternal(url);
1861
2010
  }
1862
- }).pipe(Effect.catchTag("NoSuchElementException", () => Effect.succeed(false)));
2011
+ }).pipe(Effect.catchIf(Cause.isDone, () => Effect.succeed(false)));
1863
2012
  const renderAuthStatus = (status) => status.authenticated ? [
1864
2013
  translate("cli.auth.status.authenticatedYes"),
1865
2014
  translate("cli.auth.status.source", { value: status.source ?? translate("cli.auth.status.unknown") }),
@@ -1899,7 +2048,7 @@ const authLogin = Command.make("login", {
1899
2048
  `code: ${code}`,
1900
2049
  translate("cli.auth.login.waiting")
1901
2050
  ].join("\n");
1902
- yield* outputMode === "terminal" ? Console.log(instructionMessage) : Console.error(instructionMessage);
2051
+ yield* outputMode === "terminal" ? Console.log(instructionMessage) : runtimeService.writeStderr(`${instructionMessage}\n`);
1903
2052
  const openShortcutFiber = outputMode === "terminal" && !browserOpened ? yield* Effect.forkScoped(waitForOpenShortcut(linkUrl)) : void 0;
1904
2053
  yield* Effect.addFinalizer(() => openShortcutFiber ? Fiber.interrupt(openShortcutFiber) : Effect.void);
1905
2054
  const { configPath, state } = yield* savePersistedState({
@@ -2361,15 +2510,15 @@ const fileTypeOption = fileTypeConfig.option;
2361
2510
  const sortByOption = sortByConfig.option;
2362
2511
  const optionalFileIdOption = optionalFileIdConfig.option;
2363
2512
  const optionalFileNameOption = optionalFileNameConfig.option;
2364
- const NonEmptyStringSchema$1 = Schema.String.pipe(Schema.filter((value) => value.trim().length > 0, { message: () => "Expected a non-empty string" }));
2365
- const NonEmptyIdsSchema$1 = Schema.Array(Schema.Number).pipe(Schema.filter((value) => value.length > 0, { message: () => "Expected at least one id" }));
2513
+ const NonBlankStringSchema = Schema.String.check(Schema.makeFilter((value) => value.trim().length > 0 ? void 0 : "Expected a non-empty string"));
2514
+ const NonEmptyIdsSchema$1 = Schema.Array(Schema.Number).check(Schema.isNonEmpty());
2366
2515
  const FilesMkdirInputSchema = Schema.Struct({
2367
- name: NonEmptyStringSchema$1,
2516
+ name: NonBlankStringSchema,
2368
2517
  parent_id: Schema.optional(Schema.Number)
2369
2518
  });
2370
2519
  const FilesRenameInputSchema = Schema.Struct({
2371
2520
  file_id: Schema.Number,
2372
- name: NonEmptyStringSchema$1
2521
+ name: NonBlankStringSchema
2373
2522
  });
2374
2523
  const FilesDeleteInputSchema = Schema.Struct({
2375
2524
  ids: NonEmptyIdsSchema$1,
@@ -2812,8 +2961,8 @@ const WATCH_TERMINAL_STATUSES = [
2812
2961
  "ERROR",
2813
2962
  "SEEDING"
2814
2963
  ];
2815
- const NonEmptyIdsSchema = Schema.Array(Schema.Number).pipe(Schema.filter((value) => value.length > 0, { message: () => "Expected at least one id" }));
2816
- const TransfersAddInputSchema = Schema.Array(TransferAddInputSchema).pipe(Schema.filter((value) => value.length > 0, { message: () => "Expected at least one transfer input" }));
2964
+ const NonEmptyIdsSchema = Schema.Array(Schema.Number).check(Schema.isNonEmpty());
2965
+ const TransfersAddInputSchema = Schema.Array(TransferAddInputSchema).check(Schema.isNonEmpty());
2817
2966
  const TransfersCancelInputSchema = Schema.Struct({ ids: NonEmptyIdsSchema });
2818
2967
  const TransfersSingleIdInputSchema = Schema.Struct({ id: Schema.Number });
2819
2968
  const TransfersCleanInputSchema = Schema.Struct({ ids: Schema.optional(NonEmptyIdsSchema) });
@@ -2928,12 +3077,15 @@ const transfersRetry = Command.make("retry", {
2928
3077
  }));
2929
3078
  const transfersClean = Command.make("clean", {
2930
3079
  dryRun: dryRunOption,
2931
- id: transferIdsOption.pipe(Options.optional),
3080
+ id: transferIdsOption.pipe(Flag.optional),
2932
3081
  json: jsonOption,
2933
3082
  output: outputOption
2934
3083
  }, ({ dryRun, id, json, output }) => Effect.gen(function* () {
2935
3084
  const input = yield* resolveMutationInput({
2936
- buildFromFlags: () => ({ ids: Option.getOrUndefined(id) }),
3085
+ buildFromFlags: () => {
3086
+ const ids = Option.getOrUndefined(id);
3087
+ return ids === void 0 || ids.length === 0 ? {} : { ids };
3088
+ },
2937
3089
  json,
2938
3090
  schema: TransfersCleanInputSchema
2939
3091
  });
@@ -3311,7 +3463,7 @@ const commandCatalog = decodeCommandSpecs([
3311
3463
  ]);
3312
3464
  //#endregion
3313
3465
  //#region src/internal/metadata.ts
3314
- const NonEmptyStringSchema = Schema.String.pipe(Schema.filter((value) => value.length > 0, { message: () => "Expected a non-empty string" }));
3466
+ const NonEmptyStringSchema = Schema.String.check(Schema.isNonEmpty());
3315
3467
  const CliMetadataSchema = Schema.Struct({
3316
3468
  agentDx: AgentDxScorecardSchema,
3317
3469
  auth: Schema.Struct({
@@ -3386,4 +3538,4 @@ const describeCli = () => decodeCliMetadata({
3386
3538
  version
3387
3539
  });
3388
3540
  //#endregion
3389
- export { translate as A, CliOutput as C, CliConfigLive as D, renderJson as E, CliRuntime as O, CliSdkLive as S, detectOutputModeFromArgv as T, clearPersistedState as _, searchCommand as a, resolveAuthState as b, brandCommand as c, AuthStateError as d, AuthStatusSchema as f, ResolvedAuthStateSchema as g, PutioCliConfigSchema as h, filesCommand as i, version as j, CliRuntimeLive as k, versionCommand as l, CliStateLive as m, whoamiCommand as n, eventsCommand as o, CliState as p, transfersCommand as r, downloadLinksCommand as s, describeCli as t, makeAuthCommand as u, getAuthStatus as v, CliOutputLive as w, savePersistedState as x, loadPersistedState as y };
3541
+ export { CliRuntimeLive as A, CliOutput as C, renderJson as D, isStructuredOutputMode as E, version as M, CliConfigLive as O, CliSdkLive as S, detectOutputModeFromArgv as T, clearPersistedState as _, searchCommand as a, resolveAuthState as b, brandCommand as c, AuthStateError as d, AuthStatusSchema as f, ResolvedAuthStateSchema as g, PutioCliConfigSchema as h, filesCommand as i, translate as j, CliRuntime as k, versionCommand as l, CliStateLive as m, whoamiCommand as n, eventsCommand as o, CliState as p, transfersCommand as r, downloadLinksCommand as s, describeCli as t, makeAuthCommand as u, getAuthStatus as v, CliOutputLive as w, savePersistedState as x, loadPersistedState as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@putdotio/cli",
3
- "version": "1.0.11",
3
+ "version": "1.1.0",
4
4
  "description": "Agent-first CLI for the put.io API.",
5
5
  "homepage": "https://github.com/putdotio/putio-cli#readme",
6
6
  "bugs": {
@@ -34,6 +34,7 @@
34
34
  "check": "vp check .",
35
35
  "coverage": "vp test --coverage",
36
36
  "dev": "vp pack --watch",
37
+ "prepare": "./scripts/prepare-effect.sh",
37
38
  "prepack": "vp pack",
38
39
  "smoke:pack": "node ./scripts/smoke-packed-install.mjs",
39
40
  "test": "vp test",
@@ -42,18 +43,17 @@
42
43
  "verify": "vp check . && vp pack && vp test && vp test --coverage"
43
44
  },
44
45
  "dependencies": {
45
- "@effect/cli": "^0.73.2",
46
- "@effect/platform": "0.94.5",
47
- "@effect/platform-node": "0.104.1",
48
- "@putdotio/sdk": "^9.1.0",
46
+ "@effect/platform-node": "4.0.0-beta.66",
47
+ "@putdotio/sdk": "^9.3.0",
49
48
  "cli-table3": "^0.6.5",
50
- "effect": "3.19.19",
49
+ "effect": "4.0.0-beta.66",
51
50
  "i18next": "^25.5.2"
52
51
  },
53
52
  "devDependencies": {
54
53
  "@types/node": "^24.0.0",
55
54
  "@vitest/coverage-v8": "^4.1.5",
56
55
  "esbuild": "^0.27.0",
56
+ "is-ci": "^4.1.0",
57
57
  "postject": "^1.0.0-alpha.6",
58
58
  "typescript": "^5.9.3",
59
59
  "vite-plus": "0.1.20"