@krodak/clickup-cli 1.5.2 → 1.6.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "clickup-cli",
3
3
  "description": "ClickUp CLI skills for managing tasks, sprints, comments, checklists, custom fields, tags, and time tracking via the cup command",
4
- "version": "1.5.2",
4
+ "version": "1.6.0",
5
5
  "author": {
6
6
  "name": "Krzysztof Rodak"
7
7
  },
package/README.md CHANGED
@@ -165,18 +165,41 @@ Most commands scope to your assigned tasks by default - keeping output small and
165
165
 
166
166
  ## Configuration
167
167
 
168
+ ### Profiles
169
+
170
+ Multiple profiles for different workspaces or accounts:
171
+
172
+ ```bash
173
+ cup profile add work # interactive setup
174
+ cup profile add personal # another workspace
175
+ cup profile list # show all profiles
176
+ cup profile use personal # switch default
177
+ cup tasks -p work # one-off profile override
178
+ ```
179
+
168
180
  ### Config file
169
181
 
170
182
  `~/.config/cup/config.json` (or `$XDG_CONFIG_HOME/cup/config.json`):
171
183
 
172
184
  ```json
173
185
  {
174
- "apiToken": "pk_...",
175
- "teamId": "12345678",
176
- "sprintFolderId": "optional - folder ID to skip auto-detection"
186
+ "defaultProfile": "work",
187
+ "profiles": {
188
+ "work": {
189
+ "apiToken": "pk_...",
190
+ "teamId": "12345678",
191
+ "sprintFolderId": "optional"
192
+ },
193
+ "personal": {
194
+ "apiToken": "pk_...",
195
+ "teamId": "87654321"
196
+ }
197
+ }
177
198
  }
178
199
  ```
179
200
 
201
+ Old flat configs (pre-profiles) are auto-migrated on first load.
202
+
180
203
  ### Environment variables
181
204
 
182
205
  Environment variables override config file values:
@@ -185,9 +208,10 @@ Environment variables override config file values:
185
208
  | -------------- | ----------------------------------------------------------------- |
186
209
  | `CU_API_TOKEN` | ClickUp personal API token (`pk_`) |
187
210
  | `CU_TEAM_ID` | Workspace (team) ID |
211
+ | `CU_PROFILE` | Profile name (overrides `defaultProfile`, overridden by `-p`) |
188
212
  | `CU_OUTPUT` | Set to `json` to force JSON output when piped (default: markdown) |
189
213
 
190
- When both are set, the config file is not required. Useful for CI/CD and containerized agents.
214
+ When both `CU_API_TOKEN` and `CU_TEAM_ID` are set, the config file is not required. Useful for CI/CD and containerized agents.
191
215
 
192
216
  ## Custom Task IDs
193
217
 
package/dist/index.js CHANGED
@@ -741,62 +741,243 @@ function migrateFromLegacy() {
741
741
  function configPath() {
742
742
  return join(configDir(), "config.json");
743
743
  }
744
- function loadConfig() {
744
+ function migrateToMultiProfile(parsed, filePath) {
745
+ if (typeof parsed.apiToken === "string" && !parsed.profiles) {
746
+ const profile = {};
747
+ const token = trimConfigValue(parsed.apiToken);
748
+ if (token) profile.apiToken = token;
749
+ const team = typeof parsed.teamId === "string" ? trimConfigValue(parsed.teamId) : void 0;
750
+ if (team) profile.teamId = team;
751
+ const sprint = typeof parsed.sprintFolderId === "string" ? trimConfigValue(parsed.sprintFolderId) : void 0;
752
+ if (sprint) profile.sprintFolderId = sprint;
753
+ const migrated = {
754
+ defaultProfile: "default",
755
+ profiles: { default: profile }
756
+ };
757
+ const dir = configDir();
758
+ if (!fs.existsSync(dir)) {
759
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
760
+ }
761
+ fs.writeFileSync(join(dir, "config.json"), JSON.stringify(migrated, null, 2) + "\n", {
762
+ encoding: "utf-8",
763
+ mode: 384
764
+ });
765
+ return migrated;
766
+ }
767
+ if (isRecord2(parsed.profiles)) {
768
+ const profiles = {};
769
+ for (const [name, value] of Object.entries(parsed.profiles)) {
770
+ if (isRecord2(value)) {
771
+ const p = {};
772
+ if (typeof value.apiToken === "string" && value.apiToken.trim())
773
+ p.apiToken = value.apiToken.trim();
774
+ if (typeof value.teamId === "string" && value.teamId.trim()) p.teamId = value.teamId.trim();
775
+ if (typeof value.sprintFolderId === "string" && value.sprintFolderId.trim())
776
+ p.sprintFolderId = value.sprintFolderId.trim();
777
+ profiles[name] = p;
778
+ }
779
+ }
780
+ return {
781
+ defaultProfile: typeof parsed.defaultProfile === "string" ? parsed.defaultProfile : "",
782
+ profiles
783
+ };
784
+ }
785
+ throw new Error(`Config file at ${filePath} has unrecognized format.`);
786
+ }
787
+ function parseRawConfig(filePath) {
788
+ const raw = fs.readFileSync(filePath, "utf-8");
789
+ let parsed;
790
+ try {
791
+ parsed = JSON.parse(raw);
792
+ } catch {
793
+ throw new Error(
794
+ `Config file at ${filePath} contains invalid JSON. Please check the file syntax.`
795
+ );
796
+ }
797
+ if (!isRecord2(parsed)) {
798
+ throw new Error(`Config file at ${filePath} must contain a JSON object.`);
799
+ }
800
+ return { parsed, raw };
801
+ }
802
+ function isOldFormat(parsed) {
803
+ return typeof parsed.apiToken === "string" && !parsed.profiles;
804
+ }
805
+ function loadConfig(profileName) {
745
806
  migrateFromLegacy();
746
807
  const envToken = process.env.CU_API_TOKEN?.trim();
747
808
  const envTeamId = process.env.CU_TEAM_ID?.trim();
748
- let fileToken;
749
- let fileTeamId;
750
- let fileSprintFolderId;
809
+ if (envToken && envTeamId) {
810
+ if (!envToken.startsWith("pk_")) {
811
+ throw new Error("CU_API_TOKEN must start with pk_.");
812
+ }
813
+ return { apiToken: envToken, teamId: envTeamId };
814
+ }
751
815
  const path = configPath();
752
- if (fs.existsSync(path)) {
753
- const raw = fs.readFileSync(path, "utf-8");
754
- const parsed = parseConfigFile(raw, path, true);
755
- fileToken = parsed.apiToken;
756
- fileTeamId = parsed.teamId;
757
- fileSprintFolderId = parsed.sprintFolderId;
816
+ if (!fs.existsSync(path)) {
817
+ if (envToken || envTeamId) {
818
+ throw new Error("Both CU_API_TOKEN and CU_TEAM_ID must be set, or run: cup init");
819
+ }
820
+ throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cup init");
821
+ }
822
+ const { parsed } = parseRawConfig(path);
823
+ if (isOldFormat(parsed)) {
824
+ const fileConfig = parseConfigFile(JSON.stringify(parsed), path, true);
825
+ const apiToken2 = envToken ?? fileConfig.apiToken;
826
+ if (!apiToken2) {
827
+ throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cup init");
828
+ }
829
+ if (!apiToken2.startsWith("pk_")) {
830
+ throw new Error("Config apiToken must start with pk_. The configured token does not.");
831
+ }
832
+ const teamId2 = envTeamId ?? fileConfig.teamId;
833
+ if (!teamId2) {
834
+ throw new Error("Config missing required field: teamId.\nSet CU_TEAM_ID or run: cup init");
835
+ }
836
+ migrateToMultiProfile(parsed, path);
837
+ return {
838
+ apiToken: apiToken2,
839
+ teamId: teamId2,
840
+ ...fileConfig.sprintFolderId ? { sprintFolderId: fileConfig.sprintFolderId } : {}
841
+ };
842
+ }
843
+ const multi = migrateToMultiProfile(parsed, path);
844
+ const resolvedProfile = profileName ?? process.env.CU_PROFILE?.trim() ?? multi.defaultProfile;
845
+ if (!resolvedProfile) {
846
+ throw new Error("No default profile set. Run: cup profile use <name>");
758
847
  }
759
- const apiToken = envToken || fileToken;
848
+ const profile = multi.profiles[resolvedProfile];
849
+ if (!profile) {
850
+ const available = Object.keys(multi.profiles).join(", ");
851
+ throw new Error(`Profile "${resolvedProfile}" not found. Available: ${available}`);
852
+ }
853
+ const apiToken = envToken ?? profile.apiToken?.trim();
760
854
  if (!apiToken) {
761
- throw new Error("Config missing required field: apiToken.\nSet CU_API_TOKEN or run: cup init");
855
+ throw new Error(
856
+ `Profile "${resolvedProfile}" missing apiToken. Run: cup profile add ${resolvedProfile}`
857
+ );
762
858
  }
763
859
  if (!apiToken.startsWith("pk_")) {
764
860
  throw new Error("Config apiToken must start with pk_. The configured token does not.");
765
861
  }
766
- const teamId = envTeamId || fileTeamId;
862
+ const teamId = envTeamId ?? profile.teamId?.trim();
767
863
  if (!teamId) {
768
- throw new Error("Config missing required field: teamId.\nSet CU_TEAM_ID or run: cup init");
864
+ throw new Error(`Profile "${resolvedProfile}" missing teamId.`);
865
+ }
866
+ return {
867
+ apiToken,
868
+ teamId,
869
+ ...profile.sprintFolderId ? { sprintFolderId: profile.sprintFolderId } : {}
870
+ };
871
+ }
872
+ function loadMultiProfileConfig() {
873
+ migrateFromLegacy();
874
+ const path = configPath();
875
+ if (!fs.existsSync(path)) {
876
+ return { defaultProfile: "", profiles: {} };
877
+ }
878
+ let parsed;
879
+ try {
880
+ const raw = fs.readFileSync(path, "utf-8");
881
+ parsed = JSON.parse(raw);
882
+ } catch {
883
+ return { defaultProfile: "", profiles: {} };
884
+ }
885
+ if (!isRecord2(parsed)) return { defaultProfile: "", profiles: {} };
886
+ if (isOldFormat(parsed)) {
887
+ return migrateToMultiProfile(parsed, path);
769
888
  }
770
- return { apiToken, teamId, ...fileSprintFolderId ? { sprintFolderId: fileSprintFolderId } : {} };
889
+ return migrateToMultiProfile(parsed, path);
771
890
  }
772
- function loadRawConfig() {
891
+ function saveMultiProfileConfig(config) {
892
+ const dir = configDir();
893
+ if (!fs.existsSync(dir)) {
894
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
895
+ }
896
+ fs.writeFileSync(join(dir, "config.json"), JSON.stringify(config, null, 2) + "\n", {
897
+ encoding: "utf-8",
898
+ mode: 384
899
+ });
900
+ }
901
+ function addProfile(name, profile) {
902
+ const multi = loadMultiProfileConfig();
903
+ multi.profiles[name] = profile;
904
+ if (!multi.defaultProfile) multi.defaultProfile = name;
905
+ saveMultiProfileConfig(multi);
906
+ }
907
+ function removeProfile(name) {
908
+ const multi = loadMultiProfileConfig();
909
+ if (!multi.profiles[name]) {
910
+ throw new Error(`Profile "${name}" not found.`);
911
+ }
912
+ const keys = Object.keys(multi.profiles);
913
+ if (keys.length <= 1) {
914
+ throw new Error("Cannot remove the last profile.");
915
+ }
916
+ delete multi.profiles[name];
917
+ if (multi.defaultProfile === name) {
918
+ multi.defaultProfile = Object.keys(multi.profiles)[0] ?? "";
919
+ }
920
+ saveMultiProfileConfig(multi);
921
+ }
922
+ function setDefaultProfile(name) {
923
+ const multi = loadMultiProfileConfig();
924
+ if (!multi.profiles[name]) {
925
+ const available = Object.keys(multi.profiles).join(", ");
926
+ throw new Error(`Profile "${name}" not found. Available: ${available}`);
927
+ }
928
+ multi.defaultProfile = name;
929
+ saveMultiProfileConfig(multi);
930
+ }
931
+ function listProfiles() {
932
+ const multi = loadMultiProfileConfig();
933
+ return Object.entries(multi.profiles).map(([name, profile]) => ({
934
+ name,
935
+ isDefault: name === multi.defaultProfile,
936
+ teamId: profile.teamId
937
+ }));
938
+ }
939
+ function loadRawConfig(profileName) {
773
940
  migrateFromLegacy();
774
941
  const path = configPath();
775
942
  if (!fs.existsSync(path)) return {};
776
- return parseConfigFile(fs.readFileSync(path, "utf-8"), path, false, true);
943
+ let parsed;
944
+ try {
945
+ const raw = fs.readFileSync(path, "utf-8");
946
+ parsed = JSON.parse(raw);
947
+ } catch {
948
+ return {};
949
+ }
950
+ if (!isRecord2(parsed)) {
951
+ throw new Error(`Config file at ${path} must contain a JSON object.`);
952
+ }
953
+ if (isOldFormat(parsed)) {
954
+ return parseConfigFile(JSON.stringify(parsed), path, false, true);
955
+ }
956
+ const multi = migrateToMultiProfile(parsed, path);
957
+ const name = profileName || multi.defaultProfile || "default";
958
+ return multi.profiles[name] ?? {};
777
959
  }
778
960
  function getConfigPath() {
779
961
  migrateFromLegacy();
780
962
  return configPath();
781
963
  }
782
- function writeConfig(config) {
783
- const dir = configDir();
784
- if (!fs.existsSync(dir)) {
785
- fs.mkdirSync(dir, { recursive: true, mode: 448 });
786
- }
787
- const filePath = join(dir, "config.json");
788
- const apiToken = trimConfigValue(config.apiToken) ?? "";
789
- const teamId = trimConfigValue(config.teamId) ?? "";
964
+ function writeConfig(config, profileName) {
965
+ const multi = loadMultiProfileConfig();
966
+ const name = profileName || multi.defaultProfile || "default";
967
+ const apiToken = trimConfigValue(config.apiToken) ?? void 0;
968
+ const teamId = trimConfigValue(config.teamId) ?? void 0;
790
969
  const sprintFolderId = trimConfigValue(config.sprintFolderId);
791
970
  const normalizedConfig = {
792
971
  ...apiToken ? { apiToken } : {},
793
972
  ...teamId ? { teamId } : {},
794
973
  ...sprintFolderId ? { sprintFolderId } : {}
795
974
  };
796
- fs.writeFileSync(filePath, JSON.stringify(normalizedConfig, null, 2) + "\n", {
797
- encoding: "utf-8",
798
- mode: 384
799
- });
975
+ multi.profiles[name] = {
976
+ ...multi.profiles[name],
977
+ ...normalizedConfig
978
+ };
979
+ if (!multi.defaultProfile) multi.defaultProfile = name;
980
+ saveMultiProfileConfig(multi);
800
981
  }
801
982
 
802
983
  // src/date.ts
@@ -2255,12 +2436,12 @@ function assertValidKey(key) {
2255
2436
  throw new Error(`Unknown config key: ${key}. Valid keys: ${[...VALID_KEYS].join(", ")}`);
2256
2437
  }
2257
2438
  }
2258
- function getConfigValue(key) {
2439
+ function getConfigValue(key, profileName) {
2259
2440
  assertValidKey(key);
2260
- const raw = loadRawConfig();
2441
+ const raw = loadRawConfig(profileName);
2261
2442
  return readStoredString(raw[key]);
2262
2443
  }
2263
- function setConfigValue(key, value) {
2444
+ function setConfigValue(key, value, profileName) {
2264
2445
  assertValidKey(key);
2265
2446
  const normalizedValue = readStoredString(value);
2266
2447
  if (key === "apiToken" && (!normalizedValue || !normalizedValue.startsWith("pk_"))) {
@@ -2269,7 +2450,7 @@ function setConfigValue(key, value) {
2269
2450
  if (key === "teamId" && !normalizedValue) {
2270
2451
  throw new Error("teamId must be non-empty");
2271
2452
  }
2272
- const raw = loadRawConfig();
2453
+ const raw = loadRawConfig(profileName);
2273
2454
  const sprintFolderId = readStoredString(raw.sprintFolderId);
2274
2455
  const merged = {
2275
2456
  ...readStoredString(raw.apiToken) ? { apiToken: readStoredString(raw.apiToken) } : {},
@@ -2285,7 +2466,7 @@ function setConfigValue(key, value) {
2285
2466
  } else {
2286
2467
  merged[key] = normalizedValue;
2287
2468
  }
2288
- writeConfig(merged);
2469
+ writeConfig(merged, profileName);
2289
2470
  }
2290
2471
  function configPath2() {
2291
2472
  return getConfigPath();
@@ -2960,6 +3141,13 @@ var commandMetadata = [
2960
3141
  flags: ["--json"],
2961
3142
  quickReference: [{ section: "read", usage: "templates", description: "List task templates" }]
2962
3143
  },
3144
+ {
3145
+ name: "profile",
3146
+ description: "Manage profiles",
3147
+ quickReference: [
3148
+ { section: "configuration", usage: "profile", description: "Manage profiles" }
3149
+ ]
3150
+ },
2963
3151
  {
2964
3152
  name: "config",
2965
3153
  description: "Manage CLI configuration",
@@ -3016,7 +3204,14 @@ function topLevelCommandNames() {
3016
3204
  }
3017
3205
 
3018
3206
  // src/commands/completion.ts
3019
- var bashSpecialCaseCommands = /* @__PURE__ */ new Set(["checklist", "time", "bulk", "config", "completion"]);
3207
+ var bashSpecialCaseCommands = /* @__PURE__ */ new Set([
3208
+ "checklist",
3209
+ "time",
3210
+ "bulk",
3211
+ "config",
3212
+ "profile",
3213
+ "completion"
3214
+ ]);
3020
3215
  function escapeSingleQuotes(value) {
3021
3216
  return value.replaceAll("'", "'\\''");
3022
3217
  }
@@ -3109,6 +3304,11 @@ ${renderBashCommandCases()}
3109
3304
  COMPREPLY=($(compgen -W "status" -- "$cur"))
3110
3305
  fi
3111
3306
  ;;
3307
+ profile)
3308
+ if [[ $cword -eq 2 ]]; then
3309
+ COMPREPLY=($(compgen -W "list add remove use" -- "$cur"))
3310
+ fi
3311
+ ;;
3112
3312
  config)
3113
3313
  if [[ $cword -eq 2 ]]; then
3114
3314
  COMPREPLY=($(compgen -W "get set path" -- "$cur"))
@@ -3646,6 +3846,33 @@ ${renderZshTopLevelCommands(name)}
3646
3846
  '(-c --content)'{-c,--content}'[New page content]:text:' \\
3647
3847
  '--json[Force JSON output]'
3648
3848
  ;;
3849
+ profile)
3850
+ local -a profile_cmds
3851
+ profile_cmds=(
3852
+ 'list:List all profiles'
3853
+ 'add:Add a new profile'
3854
+ 'remove:Remove a profile'
3855
+ 'use:Set the default profile'
3856
+ )
3857
+ _arguments -C \\
3858
+ '1:profile command:->profile_cmd' \\
3859
+ '*::profile_arg:->profile_args'
3860
+ case $state in
3861
+ profile_cmd)
3862
+ _describe 'profile command' profile_cmds
3863
+ ;;
3864
+ profile_args)
3865
+ case $words[1] in
3866
+ list)
3867
+ _arguments '--json[Force JSON output]'
3868
+ ;;
3869
+ add|remove|use)
3870
+ _arguments '1:name:'
3871
+ ;;
3872
+ esac
3873
+ ;;
3874
+ esac
3875
+ ;;
3649
3876
  config)
3650
3877
  local -a config_cmds
3651
3878
  config_cmds=(
@@ -3726,6 +3953,12 @@ complete -c ${name} -n '__fish_seen_subcommand_from attach' -F
3726
3953
  complete -c ${name} -n '__fish_seen_subcommand_from bulk; and not __fish_seen_subcommand_from status' -a status -d 'Update status of multiple tasks'
3727
3954
  complete -c ${name} -n '__fish_seen_subcommand_from status; and __fish_seen_subcommand_from bulk' -l json -d 'Force JSON output'
3728
3955
 
3956
+ complete -c ${name} -n '__fish_seen_subcommand_from profile; and not __fish_seen_subcommand_from list add remove use' -a list -d 'List all profiles'
3957
+ complete -c ${name} -n '__fish_seen_subcommand_from profile; and not __fish_seen_subcommand_from list add remove use' -a add -d 'Add a new profile'
3958
+ complete -c ${name} -n '__fish_seen_subcommand_from profile; and not __fish_seen_subcommand_from list add remove use' -a remove -d 'Remove a profile'
3959
+ complete -c ${name} -n '__fish_seen_subcommand_from profile; and not __fish_seen_subcommand_from list add remove use' -a use -d 'Set the default profile'
3960
+ complete -c ${name} -n '__fish_seen_subcommand_from list; and __fish_seen_subcommand_from profile' -l json -d 'Force JSON output'
3961
+
3729
3962
  complete -c ${name} -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a get -d 'Print a config value'
3730
3963
  complete -c ${name} -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a set -d 'Set a config value'
3731
3964
  complete -c ${name} -n '__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from get set path' -a path -d 'Print config file path'
@@ -4552,7 +4785,10 @@ function parseOptionalNumberOption(value, optionName) {
4552
4785
  }
4553
4786
  function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4554
4787
  const program = new Command();
4555
- program.name(programName).description("ClickUp CLI for AI agents").version(version).allowExcessArguments(false);
4788
+ program.name(programName).description("ClickUp CLI for AI agents").version(version).allowExcessArguments(false).option("-p, --profile <name>", "Use a specific profile");
4789
+ function getProfileName() {
4790
+ return program.opts().profile;
4791
+ }
4556
4792
  program.command("init").description(`Set up ${programName} for the first time`).action(
4557
4793
  wrapAction(async () => {
4558
4794
  await runInitCommand();
@@ -4560,7 +4796,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4560
4796
  );
4561
4797
  program.command("auth").description("Validate API token and show current user").option("--json", "Force JSON output even in terminal").action(
4562
4798
  wrapAction(async (opts) => {
4563
- const config = loadConfig();
4799
+ const config = loadConfig(getProfileName());
4564
4800
  const result = await checkAuth(config);
4565
4801
  if (shouldOutputJson(opts.json ?? false)) {
4566
4802
  console.log(JSON.stringify(result, null, 2));
@@ -4576,7 +4812,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4576
4812
  'Filter by task type (e.g. "task", "initiative", or custom type name/ID)'
4577
4813
  ).option("--include-closed", "Include done/closed tasks").option("--json", "Force JSON output even in terminal").action(
4578
4814
  wrapAction(async (opts) => {
4579
- const config = loadConfig();
4815
+ const config = loadConfig(getProfileName());
4580
4816
  const tasks = await fetchMyTasks(config, {
4581
4817
  typeFilter: opts.type,
4582
4818
  statuses: opts.status ? [opts.status] : void 0,
@@ -4590,7 +4826,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4590
4826
  );
4591
4827
  program.command("task <taskId>").description("Get task details").option("--json", "Force JSON output even in terminal").action(
4592
4828
  wrapAction(async (taskId, opts) => {
4593
- const config = loadConfig();
4829
+ const config = loadConfig(getProfileName());
4594
4830
  const result = await getTask(config, taskId);
4595
4831
  if (shouldOutputJson(opts.json ?? false)) {
4596
4832
  console.log(JSON.stringify(result, null, 2));
@@ -4603,7 +4839,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4603
4839
  );
4604
4840
  program.command("update <taskId>").description("Update a task").option("-n, --name <text>", "New task name").option("-d, --description <text>", "New description (markdown supported)").option("-s, --status <status>", 'New status (e.g. "in progress", "done")').option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--assignee <userId>", 'Add assignee by user ID or "me"').option("--parent <taskId>", "Set parent task (makes this a subtask)").option("--json", "Force JSON output even in terminal").action(
4605
4841
  wrapAction(async (taskId, opts) => {
4606
- const config = loadConfig();
4842
+ const config = loadConfig(getProfileName());
4607
4843
  if (opts.assignee === "me") {
4608
4844
  const client = new ClickUpClient(config);
4609
4845
  opts.assignee = String(await resolveAssigneeId(client, "me"));
@@ -4619,7 +4855,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4619
4855
  );
4620
4856
  program.command("create").description("Create a new task").option("-l, --list <listId>", "Target list ID (auto-detected from --parent if omitted)").requiredOption("-n, --name <name>", "Task name").option("-d, --description <text>", "Task description (markdown supported)").option("-p, --parent <taskId>", "Parent task ID (list auto-detected from parent)").option("-s, --status <status>", "Initial status").option("--priority <level>", "Priority: urgent, high, normal, low (or 1-4)").option("--due-date <date>", "Due date (YYYY-MM-DD)").option("--assignee <userId>", 'Assignee user ID or "me"').option("--tags <tags>", "Comma-separated tag names").option("--custom-item-id <id>", "Custom task type ID (use to create initiatives)").option("--time-estimate <duration>", 'Time estimate (e.g. "2h", "30m", "1h30m")').option("--template <id>", "Create from a task template").option("--json", "Force JSON output even in terminal").action(
4621
4857
  wrapAction(async (opts) => {
4622
- const config = loadConfig();
4858
+ const config = loadConfig(getProfileName());
4623
4859
  if (opts.assignee === "me") {
4624
4860
  const client = new ClickUpClient(config);
4625
4861
  opts.assignee = String(await resolveAssigneeId(client, "me"));
@@ -4635,21 +4871,21 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4635
4871
  program.command("sprint").description("List my tasks in the current active sprint (auto-detected)").option("--status <status>", "Filter by status").option("--space <nameOrId>", "Narrow sprint search to a specific space (partial name or ID)").option("--folder <folderId>", "Sprint folder ID (overrides config and auto-detection)").option("--include-closed", "Include done/closed tasks").option("--json", "Force JSON output even in terminal").action(
4636
4872
  wrapAction(
4637
4873
  async (opts) => {
4638
- const config = loadConfig();
4874
+ const config = loadConfig(getProfileName());
4639
4875
  await runSprintCommand(config, opts);
4640
4876
  }
4641
4877
  )
4642
4878
  );
4643
4879
  program.command("sprints").description("List all sprints in sprint folders").option("--space <nameOrId>", "Filter by space (partial name or ID)").option("--json", "Force JSON output even in terminal").action(
4644
4880
  wrapAction(async (opts) => {
4645
- const config = loadConfig();
4881
+ const config = loadConfig(getProfileName());
4646
4882
  await listSprints(config, opts);
4647
4883
  })
4648
4884
  );
4649
4885
  program.command("subtasks <taskId>").description("List subtasks of a task or initiative").option("--status <status>", "Filter by status").option("--name <partial>", "Filter by name (case-insensitive contains)").option("--include-closed", "Include closed/done subtasks").option("--json", "Force JSON output even in terminal").action(
4650
4886
  wrapAction(
4651
4887
  async (taskId, opts) => {
4652
- const config = loadConfig();
4888
+ const config = loadConfig(getProfileName());
4653
4889
  let tasks = await fetchSubtasks(config, taskId, { includeClosed: opts.includeClosed });
4654
4890
  if (opts.status) {
4655
4891
  const lower = opts.status.toLowerCase();
@@ -4666,7 +4902,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4666
4902
  program.command("comment <taskId>").description("Post a comment on a task").requiredOption("-m, --message <text>", "Comment text").option("--notify-all", "Notify all assignees").option("--json", "Force JSON output even in terminal").action(
4667
4903
  wrapAction(
4668
4904
  async (taskId, opts) => {
4669
- const config = loadConfig();
4905
+ const config = loadConfig(getProfileName());
4670
4906
  const result = await postComment(config, taskId, opts.message, opts.notifyAll);
4671
4907
  if (shouldOutputJson(opts.json ?? false)) {
4672
4908
  console.log(JSON.stringify(result, null, 2));
@@ -4678,7 +4914,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4678
4914
  );
4679
4915
  program.command("comments <taskId>").description("List comments on a task").option("--json", "Force JSON output even in terminal").action(
4680
4916
  wrapAction(async (taskId, opts) => {
4681
- const config = loadConfig();
4917
+ const config = loadConfig(getProfileName());
4682
4918
  const comments = await fetchComments(config, taskId);
4683
4919
  printComments(comments, opts.json ?? false);
4684
4920
  })
@@ -4686,7 +4922,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4686
4922
  program.command("comment-edit <commentId>").description("Edit an existing comment").requiredOption("-m, --message <text>", "New comment text").option("--resolved", "Mark comment as resolved").option("--unresolved", "Mark comment as unresolved").option("--json", "Force JSON output even in terminal").action(
4687
4923
  wrapAction(
4688
4924
  async (commentId, opts) => {
4689
- const config = loadConfig();
4925
+ const config = loadConfig(getProfileName());
4690
4926
  let resolved;
4691
4927
  if (opts.resolved) resolved = true;
4692
4928
  if (opts.unresolved) resolved = false;
@@ -4701,7 +4937,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4701
4937
  );
4702
4938
  program.command("comment-delete <commentId>").description("Delete a comment").option("--json", "Force JSON output even in terminal").action(
4703
4939
  wrapAction(async (commentId, opts) => {
4704
- const config = loadConfig();
4940
+ const config = loadConfig(getProfileName());
4705
4941
  await deleteComment(config, commentId);
4706
4942
  if (shouldOutputJson(opts.json ?? false)) {
4707
4943
  console.log(JSON.stringify({ success: true, commentId }, null, 2));
@@ -4712,7 +4948,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4712
4948
  );
4713
4949
  program.command("replies <commentId>").description("List threaded replies on a comment").option("--json", "Force JSON output even in terminal").action(
4714
4950
  wrapAction(async (commentId, opts) => {
4715
- const config = loadConfig();
4951
+ const config = loadConfig(getProfileName());
4716
4952
  const replies = await getReplies(config, commentId);
4717
4953
  if (shouldOutputJson(opts.json ?? false)) {
4718
4954
  console.log(JSON.stringify(replies, null, 2));
@@ -4726,7 +4962,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4726
4962
  program.command("reply <commentId>").description("Reply to a comment").requiredOption("-m, --message <text>", "Reply text").option("--notify-all", "Notify all assignees").option("--json", "Force JSON output even in terminal").action(
4727
4963
  wrapAction(
4728
4964
  async (commentId, opts) => {
4729
- const config = loadConfig();
4965
+ const config = loadConfig(getProfileName());
4730
4966
  await createReply(config, commentId, opts.message, opts.notifyAll);
4731
4967
  if (shouldOutputJson(opts.json ?? false)) {
4732
4968
  console.log(JSON.stringify({ success: true, commentId }, null, 2));
@@ -4738,27 +4974,27 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4738
4974
  );
4739
4975
  program.command("activity <taskId>").description("Show task details and comments combined").option("--json", "Force JSON output even in terminal").action(
4740
4976
  wrapAction(async (taskId, opts) => {
4741
- const config = loadConfig();
4977
+ const config = loadConfig(getProfileName());
4742
4978
  const result = await fetchActivity(config, taskId);
4743
4979
  printActivity(result, opts.json ?? false);
4744
4980
  })
4745
4981
  );
4746
4982
  program.command("lists <spaceId>").description("List all lists in a space (including lists inside folders)").option("--name <partial>", "Filter by name (case-insensitive contains)").option("--json", "Force JSON output even in terminal").action(
4747
4983
  wrapAction(async (spaceId, opts) => {
4748
- const config = loadConfig();
4984
+ const config = loadConfig(getProfileName());
4749
4985
  const lists = await fetchLists(config, spaceId, { name: opts.name });
4750
4986
  printLists(lists, opts.json ?? false);
4751
4987
  })
4752
4988
  );
4753
4989
  program.command("spaces").description("List spaces in your workspace").option("--name <partial>", "Filter spaces by name (case-insensitive contains)").option("--my", "Show only spaces where I have assigned tasks").option("--json", "Force JSON output even in terminal").action(
4754
4990
  wrapAction(async (opts) => {
4755
- const config = loadConfig();
4991
+ const config = loadConfig(getProfileName());
4756
4992
  await listSpaces(config, opts);
4757
4993
  })
4758
4994
  );
4759
4995
  program.command("inbox").description("Recently updated tasks grouped by time period").option("--include-closed", "Include done/closed tasks").option("--json", "Force JSON output even in terminal").option("--days <n>", "Lookback period in days", "30").action(
4760
4996
  wrapAction(async (opts) => {
4761
- const config = loadConfig();
4997
+ const config = loadConfig(getProfileName());
4762
4998
  const days = Number(opts.days ?? 30);
4763
4999
  if (!Number.isFinite(days) || days <= 0) {
4764
5000
  throw new Error("--days must be a positive number");
@@ -4769,20 +5005,20 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4769
5005
  );
4770
5006
  program.command("assigned").description("Show all tasks assigned to me, grouped by status").option("--status <status>", "Show only tasks with this status").option("--include-closed", "Include done/closed tasks").option("--json", "Force JSON output even in terminal").action(
4771
5007
  wrapAction(async (opts) => {
4772
- const config = loadConfig();
5008
+ const config = loadConfig(getProfileName());
4773
5009
  await runAssignedCommand(config, opts);
4774
5010
  })
4775
5011
  );
4776
5012
  program.command("open <query>").description("Open a task in the browser by ID or name").option("--json", "Output task JSON instead of opening").action(
4777
5013
  wrapAction(async (query, opts) => {
4778
- const config = loadConfig();
5014
+ const config = loadConfig(getProfileName());
4779
5015
  await openTask(config, query, opts);
4780
5016
  })
4781
5017
  );
4782
5018
  program.command("search <query>").description("Search my tasks by name").option("--status <status>", "Filter by status").option("--include-closed", "Include done/closed tasks in search").option("--json", "Force JSON output even in terminal").action(
4783
5019
  wrapAction(
4784
5020
  async (query, opts) => {
4785
- const config = loadConfig();
5021
+ const config = loadConfig(getProfileName());
4786
5022
  const tasks = await searchTasks(config, query, {
4787
5023
  status: opts.status,
4788
5024
  includeClosed: opts.includeClosed
@@ -4793,7 +5029,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4793
5029
  );
4794
5030
  program.command("summary").description("Daily standup summary: completed, in-progress, overdue").option("--hours <n>", "Completed-tasks lookback in hours", "24").option("--json", "Force JSON output even in terminal").action(
4795
5031
  wrapAction(async (opts) => {
4796
- const config = loadConfig();
5032
+ const config = loadConfig(getProfileName());
4797
5033
  const hours = Number(opts.hours ?? 24);
4798
5034
  if (!Number.isFinite(hours) || hours <= 0) {
4799
5035
  throw new Error("--hours must be a positive number");
@@ -4803,14 +5039,14 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4803
5039
  );
4804
5040
  program.command("overdue").description("List tasks that are past their due date").option("--include-closed", "Include done/closed overdue tasks").option("--json", "Force JSON output even in terminal").action(
4805
5041
  wrapAction(async (opts) => {
4806
- const config = loadConfig();
5042
+ const config = loadConfig(getProfileName());
4807
5043
  const tasks = await fetchOverdueTasks(config, { includeClosed: opts.includeClosed });
4808
5044
  await printTasks(tasks, opts.json ?? false, config);
4809
5045
  })
4810
5046
  );
4811
5047
  program.command("assign <taskId>").description("Assign or unassign users from a task").option("--to <userId>", 'Add assignee (user ID or "me")').option("--remove <userId>", 'Remove assignee (user ID or "me")').option("--json", "Force JSON output even in terminal").action(
4812
5048
  wrapAction(async (taskId, opts) => {
4813
- const config = loadConfig();
5049
+ const config = loadConfig(getProfileName());
4814
5050
  const result = await assignTask(config, taskId, opts);
4815
5051
  if (shouldOutputJson(opts.json ?? false)) {
4816
5052
  console.log(JSON.stringify(result, null, 2));
@@ -4821,7 +5057,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4821
5057
  );
4822
5058
  program.command("depend <taskId>").description("Add or remove task dependencies").option("--on <taskId>", "Task that this task depends on (waiting on)").option("--blocks <taskId>", "Task that this task blocks").option("--remove", "Remove the dependency instead of adding it").option("--json", "Force JSON output even in terminal").action(
4823
5059
  wrapAction(async (taskId, opts) => {
4824
- const config = loadConfig();
5060
+ const config = loadConfig(getProfileName());
4825
5061
  const message = await manageDependency(config, taskId, opts);
4826
5062
  if (shouldOutputJson(opts.json ?? false)) {
4827
5063
  console.log(
@@ -4839,7 +5075,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4839
5075
  program.command("link <taskId> <linksTo>").description("Add or remove a link between two tasks").option("--remove", "Remove the link instead of adding it").option("--json", "Force JSON output even in terminal").action(
4840
5076
  wrapAction(
4841
5077
  async (taskId, linksTo, opts) => {
4842
- const config = loadConfig();
5078
+ const config = loadConfig(getProfileName());
4843
5079
  const result = await manageTaskLink(config, taskId, linksTo, opts.remove ?? false);
4844
5080
  if (shouldOutputJson(opts.json ?? false)) {
4845
5081
  console.log(
@@ -4857,7 +5093,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4857
5093
  );
4858
5094
  program.command("attach <taskId> <filePath>").description("Upload a file attachment to a task").option("--json", "Force JSON output even in terminal").action(
4859
5095
  wrapAction(async (taskId, filePath, opts) => {
4860
- const config = loadConfig();
5096
+ const config = loadConfig(getProfileName());
4861
5097
  const result = await attachFile(config, taskId, filePath);
4862
5098
  if (shouldOutputJson(opts.json ?? false)) {
4863
5099
  console.log(JSON.stringify(result, null, 2));
@@ -4869,7 +5105,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4869
5105
  );
4870
5106
  program.command("move <taskId>").description("Add or remove a task from a list").option("--to <listId>", "Add task to this list").option("--remove <listId>", "Remove task from this list").option("--json", "Force JSON output even in terminal").action(
4871
5107
  wrapAction(async (taskId, opts) => {
4872
- const config = loadConfig();
5108
+ const config = loadConfig(getProfileName());
4873
5109
  const message = await moveTask(config, taskId, opts);
4874
5110
  if (shouldOutputJson(opts.json ?? false)) {
4875
5111
  console.log(
@@ -4883,7 +5119,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4883
5119
  program.command("field <taskId>").description("Set or remove a custom field value on a task").option("--set <nameAndValue...>", 'Set field: --set "Field Name" value').option("--remove <fieldName>", "Remove field value by name").option("--json", "Force JSON output even in terminal").action(
4884
5120
  wrapAction(
4885
5121
  async (taskId, opts) => {
4886
- const config = loadConfig();
5122
+ const config = loadConfig(getProfileName());
4887
5123
  const fieldOpts = {};
4888
5124
  if (opts.set) {
4889
5125
  if (opts.set.length !== 2) {
@@ -4911,7 +5147,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4911
5147
  );
4912
5148
  program.command("delete <taskId>").description("Delete a task (requires confirmation)").option("--confirm", "Skip confirmation prompt (required in non-interactive mode)").option("--json", "Force JSON output even in terminal").action(
4913
5149
  wrapAction(async (taskId, opts) => {
4914
- const config = loadConfig();
5150
+ const config = loadConfig(getProfileName());
4915
5151
  const result = await deleteTaskCommand(config, taskId, opts);
4916
5152
  if (shouldOutputJson(opts.json ?? false)) {
4917
5153
  console.log(JSON.stringify(result, null, 2));
@@ -4923,7 +5159,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4923
5159
  program.command("tag <taskId>").description("Add or remove tags from a task").option("--add <tags>", "Comma-separated tag names to add").option("--remove <tags>", "Comma-separated tag names to remove").option("--json", "Force JSON output even in terminal").action(
4924
5160
  wrapAction(
4925
5161
  async (taskId, opts) => {
4926
- const config = loadConfig();
5162
+ const config = loadConfig(getProfileName());
4927
5163
  const result = await manageTags(config, taskId, opts);
4928
5164
  if (shouldOutputJson(opts.json ?? false)) {
4929
5165
  console.log(JSON.stringify(result, null, 2));
@@ -4939,7 +5175,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4939
5175
  const checklistCmd = program.command("checklist").description("Manage checklists on a task");
4940
5176
  checklistCmd.command("view <taskId>").description("View checklists on a task").option("--json", "Force JSON output even in terminal").action(
4941
5177
  wrapAction(async (taskId, opts) => {
4942
- const config = loadConfig();
5178
+ const config = loadConfig(getProfileName());
4943
5179
  const checklists = await viewChecklists(config, taskId);
4944
5180
  if (shouldOutputJson(opts.json ?? false)) {
4945
5181
  console.log(JSON.stringify(checklists, null, 2));
@@ -4952,7 +5188,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4952
5188
  );
4953
5189
  checklistCmd.command("create <taskId> <name>").description("Create a checklist on a task").option("--json", "Force JSON output even in terminal").action(
4954
5190
  wrapAction(async (taskId, name, opts) => {
4955
- const config = loadConfig();
5191
+ const config = loadConfig(getProfileName());
4956
5192
  const result = await createChecklist(config, taskId, name);
4957
5193
  if (shouldOutputJson(opts.json ?? false)) {
4958
5194
  console.log(JSON.stringify(result, null, 2));
@@ -4963,7 +5199,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4963
5199
  );
4964
5200
  checklistCmd.command("delete <checklistId>").description("Delete a checklist").option("--json", "Force JSON output even in terminal").action(
4965
5201
  wrapAction(async (checklistId, opts) => {
4966
- const config = loadConfig();
5202
+ const config = loadConfig(getProfileName());
4967
5203
  const result = await deleteChecklist(config, checklistId);
4968
5204
  if (shouldOutputJson(opts.json ?? false)) {
4969
5205
  console.log(JSON.stringify(result, null, 2));
@@ -4974,7 +5210,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4974
5210
  );
4975
5211
  checklistCmd.command("add-item <checklistId> <name>").description("Add an item to a checklist").option("--json", "Force JSON output even in terminal").action(
4976
5212
  wrapAction(async (checklistId, name, opts) => {
4977
- const config = loadConfig();
5213
+ const config = loadConfig(getProfileName());
4978
5214
  const result = await addChecklistItem(config, checklistId, name);
4979
5215
  if (shouldOutputJson(opts.json ?? false)) {
4980
5216
  console.log(JSON.stringify(result, null, 2));
@@ -4986,7 +5222,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4986
5222
  checklistCmd.command("edit-item <checklistId> <checklistItemId>").description("Edit a checklist item").option("--name <name>", "New item name").option("--resolved", "Mark item as resolved").option("--unresolved", "Mark item as unresolved").option("--assignee <userId>", 'Assign user by ID (use "null" to unassign)').option("--json", "Force JSON output even in terminal").action(
4987
5223
  wrapAction(
4988
5224
  async (checklistId, checklistItemId, opts) => {
4989
- const config = loadConfig();
5225
+ const config = loadConfig(getProfileName());
4990
5226
  const updates = {};
4991
5227
  if (opts.name) updates.name = opts.name;
4992
5228
  if (opts.resolved) updates.resolved = true;
@@ -5005,7 +5241,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5005
5241
  );
5006
5242
  checklistCmd.command("delete-item <checklistId> <checklistItemId>").description("Delete a checklist item").option("--json", "Force JSON output even in terminal").action(
5007
5243
  wrapAction(async (checklistId, checklistItemId, opts) => {
5008
- const config = loadConfig();
5244
+ const config = loadConfig(getProfileName());
5009
5245
  const result = await deleteChecklistItem(config, checklistId, checklistItemId);
5010
5246
  if (shouldOutputJson(opts.json ?? false)) {
5011
5247
  console.log(JSON.stringify(result, null, 2));
@@ -5017,7 +5253,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5017
5253
  const timeCmd = program.command("time").description("Track time on tasks");
5018
5254
  timeCmd.command("start <taskId>").description("Start tracking time on a task").option("-d, --description <text>", "Description for the time entry").option("--json", "Force JSON output even in terminal").action(
5019
5255
  wrapAction(async (taskId, opts) => {
5020
- const config = loadConfig();
5256
+ const config = loadConfig(getProfileName());
5021
5257
  const result = await startTimer(config, taskId, opts.description);
5022
5258
  if (shouldOutputJson(opts.json ?? false)) {
5023
5259
  console.log(JSON.stringify(result, null, 2));
@@ -5029,7 +5265,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5029
5265
  );
5030
5266
  timeCmd.command("stop").description("Stop the running timer").option("--json", "Force JSON output even in terminal").action(
5031
5267
  wrapAction(async (opts) => {
5032
- const config = loadConfig();
5268
+ const config = loadConfig(getProfileName());
5033
5269
  const result = await stopTimer(config);
5034
5270
  if (shouldOutputJson(opts.json ?? false)) {
5035
5271
  console.log(JSON.stringify(result, null, 2));
@@ -5042,7 +5278,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5042
5278
  );
5043
5279
  timeCmd.command("status").description("Show the currently running timer").option("--json", "Force JSON output even in terminal").action(
5044
5280
  wrapAction(async (opts) => {
5045
- const config = loadConfig();
5281
+ const config = loadConfig(getProfileName());
5046
5282
  const result = await timerStatus(config);
5047
5283
  if (shouldOutputJson(opts.json ?? false)) {
5048
5284
  console.log(JSON.stringify(result, null, 2));
@@ -5058,7 +5294,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5058
5294
  timeCmd.command("log <taskId> <duration>").description('Log a manual time entry (e.g. "2h", "30m", "1h30m")').option("-d, --description <text>", "Description for the time entry").option("--json", "Force JSON output even in terminal").action(
5059
5295
  wrapAction(
5060
5296
  async (taskId, duration, opts) => {
5061
- const config = loadConfig();
5297
+ const config = loadConfig(getProfileName());
5062
5298
  const result = await logTime(config, taskId, duration, opts.description);
5063
5299
  if (shouldOutputJson(opts.json ?? false)) {
5064
5300
  console.log(JSON.stringify(result, null, 2));
@@ -5070,7 +5306,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5070
5306
  );
5071
5307
  timeCmd.command("list").description("List recent time entries (default: last 7 days)").option("--days <n>", "Number of days to look back", "7").option("--task <taskId>", "Filter by task ID").option("--json", "Force JSON output even in terminal").action(
5072
5308
  wrapAction(async (opts) => {
5073
- const config = loadConfig();
5309
+ const config = loadConfig(getProfileName());
5074
5310
  const days = opts.days ? Number(opts.days) : 7;
5075
5311
  if (!Number.isFinite(days) || days <= 0) {
5076
5312
  throw new Error("--days must be a positive number");
@@ -5088,7 +5324,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5088
5324
  timeCmd.command("update <timeEntryId>").description("Update a time entry").option("-d, --description <text>", "New description").option("--duration <duration>", 'New duration (e.g. "2h", "30m")').option("--json", "Force JSON output even in terminal").action(
5089
5325
  wrapAction(
5090
5326
  async (timeEntryId, opts) => {
5091
- const config = loadConfig();
5327
+ const config = loadConfig(getProfileName());
5092
5328
  const entry = await updateTimeEntry(config, timeEntryId, {
5093
5329
  description: opts.description,
5094
5330
  duration: opts.duration
@@ -5105,7 +5341,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5105
5341
  );
5106
5342
  timeCmd.command("delete <timeEntryId>").description("Delete a time entry").option("--json", "Force JSON output even in terminal").action(
5107
5343
  wrapAction(async (timeEntryId, opts) => {
5108
- const config = loadConfig();
5344
+ const config = loadConfig(getProfileName());
5109
5345
  await deleteTimeEntry(config, timeEntryId);
5110
5346
  if (shouldOutputJson(opts.json ?? false)) {
5111
5347
  console.log(JSON.stringify({ deleted: timeEntryId }));
@@ -5116,7 +5352,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5116
5352
  );
5117
5353
  program.command("tags <spaceId>").description("List tags in a space").option("--json", "Force JSON output even in terminal").action(
5118
5354
  wrapAction(async (spaceId, opts) => {
5119
- const config = loadConfig();
5355
+ const config = loadConfig(getProfileName());
5120
5356
  const tags = await listSpaceTags(config, spaceId);
5121
5357
  if (shouldOutputJson(opts.json ?? false)) {
5122
5358
  console.log(JSON.stringify(tags, null, 2));
@@ -5130,7 +5366,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5130
5366
  program.command("tag-create <spaceId> <name>").description("Create a tag in a space").option("--fg <color>", "Foreground color (hex)").option("--bg <color>", "Background color (hex)").option("--json", "Force JSON output even in terminal").action(
5131
5367
  wrapAction(
5132
5368
  async (spaceId, name, opts) => {
5133
- const config = loadConfig();
5369
+ const config = loadConfig(getProfileName());
5134
5370
  await createSpaceTag(config, spaceId, name, opts.fg, opts.bg);
5135
5371
  if (shouldOutputJson(opts.json ?? false)) {
5136
5372
  console.log(JSON.stringify({ success: true, spaceId, tag: name }, null, 2));
@@ -5142,7 +5378,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5142
5378
  );
5143
5379
  program.command("tag-delete <spaceId> <name>").description("Delete a tag from a space").option("--json", "Force JSON output even in terminal").action(
5144
5380
  wrapAction(async (spaceId, name, opts) => {
5145
- const config = loadConfig();
5381
+ const config = loadConfig(getProfileName());
5146
5382
  await deleteSpaceTag(config, spaceId, name);
5147
5383
  if (shouldOutputJson(opts.json ?? false)) {
5148
5384
  console.log(JSON.stringify({ success: true, spaceId, tag: name }, null, 2));
@@ -5153,7 +5389,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5153
5389
  );
5154
5390
  program.command("members").description("List workspace members").option("--json", "Force JSON output even in terminal").action(
5155
5391
  wrapAction(async (opts) => {
5156
- const config = loadConfig();
5392
+ const config = loadConfig(getProfileName());
5157
5393
  const members = await listMembers(config);
5158
5394
  if (shouldOutputJson(opts.json ?? false)) {
5159
5395
  console.log(JSON.stringify(members, null, 2));
@@ -5166,7 +5402,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5166
5402
  );
5167
5403
  program.command("fields <listId>").description("List custom fields for a list").option("--json", "Force JSON output even in terminal").action(
5168
5404
  wrapAction(async (listId, opts) => {
5169
- const config = loadConfig();
5405
+ const config = loadConfig(getProfileName());
5170
5406
  const fields = await listFields(config, listId);
5171
5407
  if (shouldOutputJson(opts.json ?? false)) {
5172
5408
  console.log(JSON.stringify(fields, null, 2));
@@ -5179,7 +5415,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5179
5415
  );
5180
5416
  program.command("duplicate <taskId>").description("Duplicate a task").option("--json", "Force JSON output even in terminal").action(
5181
5417
  wrapAction(async (taskId, opts) => {
5182
- const config = loadConfig();
5418
+ const config = loadConfig(getProfileName());
5183
5419
  const result = await duplicateTask(config, taskId);
5184
5420
  if (shouldOutputJson(opts.json ?? false)) {
5185
5421
  console.log(JSON.stringify(result, null, 2));
@@ -5191,7 +5427,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5191
5427
  const bulkCmd = program.command("bulk").description("Bulk task operations");
5192
5428
  bulkCmd.command("status <status> <taskIds...>").description("Update status of multiple tasks").option("--json", "Force JSON output even in terminal").action(
5193
5429
  wrapAction(async (status, taskIds, opts) => {
5194
- const config = loadConfig();
5430
+ const config = loadConfig(getProfileName());
5195
5431
  const result = await bulkUpdateStatus(config, taskIds, status);
5196
5432
  if (shouldOutputJson(opts.json ?? false)) {
5197
5433
  console.log(JSON.stringify(result, null, 2));
@@ -5207,7 +5443,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5207
5443
  );
5208
5444
  program.command("goals").description("List goals in your workspace").option("--json", "Force JSON output even in terminal").action(
5209
5445
  wrapAction(async (opts) => {
5210
- const config = loadConfig();
5446
+ const config = loadConfig(getProfileName());
5211
5447
  const goals = await listGoals(config);
5212
5448
  if (shouldOutputJson(opts.json ?? false)) {
5213
5449
  console.log(JSON.stringify(goals, null, 2));
@@ -5221,7 +5457,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5221
5457
  program.command("goal-create <name>").description("Create a goal").option("-d, --description <text>", "Goal description").option("--color <hex>", "Goal color (hex)").option("--json", "Force JSON output even in terminal").action(
5222
5458
  wrapAction(
5223
5459
  async (name, opts) => {
5224
- const config = loadConfig();
5460
+ const config = loadConfig(getProfileName());
5225
5461
  const goal = await createGoal(config, name, {
5226
5462
  description: opts.description,
5227
5463
  color: opts.color
@@ -5237,7 +5473,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5237
5473
  program.command("goal-update <goalId>").description("Update a goal").option("-n, --name <text>", "New goal name").option("-d, --description <text>", "New description").option("--color <hex>", "New color (hex)").option("--json", "Force JSON output even in terminal").action(
5238
5474
  wrapAction(
5239
5475
  async (goalId, opts) => {
5240
- const config = loadConfig();
5476
+ const config = loadConfig(getProfileName());
5241
5477
  const goal = await updateGoal(config, goalId, {
5242
5478
  name: opts.name,
5243
5479
  description: opts.description,
@@ -5253,7 +5489,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5253
5489
  );
5254
5490
  program.command("goal-delete <goalId>").description("Delete a goal").option("--json", "Force JSON output even in terminal").action(
5255
5491
  wrapAction(async (goalId, opts) => {
5256
- const config = loadConfig();
5492
+ const config = loadConfig(getProfileName());
5257
5493
  await deleteGoal(config, goalId);
5258
5494
  if (shouldOutputJson(opts.json ?? false)) {
5259
5495
  console.log(JSON.stringify({ success: true, goalId }, null, 2));
@@ -5264,7 +5500,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5264
5500
  );
5265
5501
  program.command("key-results <goalId>").description("List key results for a goal").option("--json", "Force JSON output even in terminal").action(
5266
5502
  wrapAction(async (goalId, opts) => {
5267
- const config = loadConfig();
5503
+ const config = loadConfig(getProfileName());
5268
5504
  const krs = await listKeyResults(config, goalId);
5269
5505
  if (shouldOutputJson(opts.json ?? false)) {
5270
5506
  console.log(JSON.stringify(krs, null, 2));
@@ -5278,7 +5514,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5278
5514
  program.command("key-result-create <goalId> <name>").description("Create a key result on a goal").option("--type <type>", "Key result type (number or percentage)", "number").option("--target <n>", "Target value", "100").option("--json", "Force JSON output even in terminal").action(
5279
5515
  wrapAction(
5280
5516
  async (goalId, name, opts) => {
5281
- const config = loadConfig();
5517
+ const config = loadConfig(getProfileName());
5282
5518
  const target = Number(opts.target ?? 100);
5283
5519
  if (!Number.isFinite(target) || target <= 0) {
5284
5520
  throw new Error("--target must be a positive number");
@@ -5295,7 +5531,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5295
5531
  program.command("key-result-update <keyResultId>").description("Update a key result").option("--progress <n>", "Current progress value").option("--note <text>", "Progress note").option("--json", "Force JSON output even in terminal").action(
5296
5532
  wrapAction(
5297
5533
  async (keyResultId, opts) => {
5298
- const config = loadConfig();
5534
+ const config = loadConfig(getProfileName());
5299
5535
  const updates = {};
5300
5536
  if (opts.progress !== void 0) {
5301
5537
  const p = Number(opts.progress);
@@ -5314,7 +5550,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5314
5550
  );
5315
5551
  program.command("key-result-delete <keyResultId>").description("Delete a key result").option("--json", "Force JSON output even in terminal").action(
5316
5552
  wrapAction(async (keyResultId, opts) => {
5317
- const config = loadConfig();
5553
+ const config = loadConfig(getProfileName());
5318
5554
  await deleteKeyResult(config, keyResultId);
5319
5555
  if (shouldOutputJson(opts.json ?? false)) {
5320
5556
  console.log(JSON.stringify({ success: true, keyResultId }, null, 2));
@@ -5325,7 +5561,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5325
5561
  );
5326
5562
  program.command("docs [query]").description("List workspace docs (optionally filter by name)").option("--json", "Force JSON output even in terminal").action(
5327
5563
  wrapAction(async (query, opts) => {
5328
- const config = loadConfig();
5564
+ const config = loadConfig(getProfileName());
5329
5565
  const docs = await listDocs(config, query);
5330
5566
  if (shouldOutputJson(opts.json ?? false)) {
5331
5567
  console.log(JSON.stringify(docs, null, 2));
@@ -5338,7 +5574,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5338
5574
  );
5339
5575
  program.command("doc <docId> [pageId]").description("View a doc (metadata + page tree) or a specific page").option("--json", "Force JSON output even in terminal").action(
5340
5576
  wrapAction(async (docId, pageId, opts) => {
5341
- const config = loadConfig();
5577
+ const config = loadConfig(getProfileName());
5342
5578
  if (pageId) {
5343
5579
  const page = await getDocPage(config, docId, pageId);
5344
5580
  if (shouldOutputJson(opts.json ?? false)) {
@@ -5362,7 +5598,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5362
5598
  );
5363
5599
  program.command("doc-pages <docId>").description("List all pages in a doc with content").option("--json", "Force JSON output even in terminal").action(
5364
5600
  wrapAction(async (docId, opts) => {
5365
- const config = loadConfig();
5601
+ const config = loadConfig(getProfileName());
5366
5602
  const pages = await getAllDocPages(config, docId);
5367
5603
  if (shouldOutputJson(opts.json ?? false)) {
5368
5604
  console.log(JSON.stringify(pages, null, 2));
@@ -5375,7 +5611,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5375
5611
  );
5376
5612
  program.command("folders <spaceId>").description("List folders in a space (with their lists)").option("--name <partial>", "Filter folders by partial name match").option("--json", "Force JSON output even in terminal").action(
5377
5613
  wrapAction(async (spaceId, opts) => {
5378
- const config = loadConfig();
5614
+ const config = loadConfig(getProfileName());
5379
5615
  const folders = await listFolders(config, spaceId, opts.name);
5380
5616
  if (shouldOutputJson(opts.json ?? false)) {
5381
5617
  console.log(JSON.stringify(folders, null, 2));
@@ -5388,7 +5624,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5388
5624
  );
5389
5625
  program.command("doc-create <title>").description("Create a new doc").option("-c, --content <text>", "Initial content (markdown)").option("--json", "Force JSON output even in terminal").action(
5390
5626
  wrapAction(async (title, opts) => {
5391
- const config = loadConfig();
5627
+ const config = loadConfig(getProfileName());
5392
5628
  const result = await createDoc(config, title, opts.content);
5393
5629
  if (shouldOutputJson(opts.json ?? false)) {
5394
5630
  console.log(JSON.stringify(result, null, 2));
@@ -5400,7 +5636,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5400
5636
  program.command("doc-page-create <docId> <name>").description("Create a page in a doc").option("-c, --content <text>", "Page content (markdown)").option("--parent-page <pageId>", "Parent page ID for nesting").option("--json", "Force JSON output even in terminal").action(
5401
5637
  wrapAction(
5402
5638
  async (docId, name, opts) => {
5403
- const config = loadConfig();
5639
+ const config = loadConfig(getProfileName());
5404
5640
  const page = await createDocPage(config, docId, name, opts.content, opts.parentPage);
5405
5641
  if (shouldOutputJson(opts.json ?? false)) {
5406
5642
  console.log(JSON.stringify(page, null, 2));
@@ -5413,7 +5649,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5413
5649
  program.command("doc-page-edit <docId> <pageId>").description("Edit a doc page").option("--name <text>", "New page name").option("-c, --content <text>", "New page content (markdown)").option("--json", "Force JSON output even in terminal").action(
5414
5650
  wrapAction(
5415
5651
  async (docId, pageId, opts) => {
5416
- const config = loadConfig();
5652
+ const config = loadConfig(getProfileName());
5417
5653
  const page = await editDocPage(config, docId, pageId, {
5418
5654
  name: opts.name,
5419
5655
  content: opts.content
@@ -5428,7 +5664,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5428
5664
  );
5429
5665
  program.command("doc-delete <docId>").description("Delete a doc").option("--json", "Force JSON output even in terminal").action(
5430
5666
  wrapAction(async (docId, opts) => {
5431
- const config = loadConfig();
5667
+ const config = loadConfig(getProfileName());
5432
5668
  await deleteDoc(config, docId);
5433
5669
  if (shouldOutputJson(opts.json ?? false)) {
5434
5670
  console.log(JSON.stringify({ success: true, docId }, null, 2));
@@ -5439,7 +5675,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5439
5675
  );
5440
5676
  program.command("doc-page-delete <docId> <pageId>").description("Delete a doc page").option("--json", "Force JSON output even in terminal").action(
5441
5677
  wrapAction(async (docId, pageId, opts) => {
5442
- const config = loadConfig();
5678
+ const config = loadConfig(getProfileName());
5443
5679
  await deleteDocPage(config, docId, pageId);
5444
5680
  if (shouldOutputJson(opts.json ?? false)) {
5445
5681
  console.log(JSON.stringify({ success: true, docId, pageId }, null, 2));
@@ -5451,7 +5687,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5451
5687
  program.command("tag-update <spaceId> <tagName>").description("Update a tag in a space").requiredOption("--name <newName>", "New tag name").option("--fg <color>", "New foreground color (hex)").option("--bg <color>", "New background color (hex)").option("--json", "Force JSON output even in terminal").action(
5452
5688
  wrapAction(
5453
5689
  async (spaceId, tagName, opts) => {
5454
- const config = loadConfig();
5690
+ const config = loadConfig(getProfileName());
5455
5691
  await updateSpaceTag(config, spaceId, tagName, {
5456
5692
  name: opts.name,
5457
5693
  fg: opts.fg,
@@ -5473,7 +5709,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5473
5709
  );
5474
5710
  program.command("task-types").description("List custom task types in your workspace").option("--json", "Force JSON output even in terminal").action(
5475
5711
  wrapAction(async (opts) => {
5476
- const config = loadConfig();
5712
+ const config = loadConfig(getProfileName());
5477
5713
  const types = await listTaskTypes(config);
5478
5714
  if (shouldOutputJson(opts.json ?? false)) {
5479
5715
  console.log(JSON.stringify(types, null, 2));
@@ -5486,7 +5722,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5486
5722
  );
5487
5723
  program.command("templates").description("List task templates in your workspace").option("--json", "Force JSON output even in terminal").action(
5488
5724
  wrapAction(async (opts) => {
5489
- const config = loadConfig();
5725
+ const config = loadConfig(getProfileName());
5490
5726
  const templates = await listTemplates(config);
5491
5727
  if (shouldOutputJson(opts.json ?? false)) {
5492
5728
  console.log(JSON.stringify(templates, null, 2));
@@ -5497,10 +5733,65 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5497
5733
  }
5498
5734
  })
5499
5735
  );
5736
+ const profileCmd = program.command("profile").description("Manage profiles");
5737
+ profileCmd.command("list").description("List all profiles").option("--json", "Force JSON output even in terminal").action(
5738
+ wrapAction(async (opts) => {
5739
+ const profiles = listProfiles();
5740
+ if (shouldOutputJson(opts.json ?? false)) {
5741
+ console.log(JSON.stringify(profiles, null, 2));
5742
+ } else {
5743
+ for (const p of profiles) {
5744
+ const marker = p.isDefault ? " (default)" : "";
5745
+ console.log(`${p.name}${marker}${p.teamId ? ` [team: ${p.teamId}]` : ""}`);
5746
+ }
5747
+ if (profiles.length === 0)
5748
+ console.log("No profiles configured. Run: cup profile add <name>");
5749
+ }
5750
+ })
5751
+ );
5752
+ profileCmd.command("add <name>").description("Add a new profile").action(
5753
+ wrapAction(async (name) => {
5754
+ const { password: password2, select: select3 } = await import("@inquirer/prompts");
5755
+ const apiToken = (await password2({ message: "ClickUp API token (pk_...):" })).trim();
5756
+ if (!apiToken.startsWith("pk_")) throw new Error("Token must start with pk_");
5757
+ const client = new ClickUpClient({ apiToken });
5758
+ const me = await client.getMe();
5759
+ process.stdout.write(`Authenticated as @${me.username}
5760
+ `);
5761
+ const teams = await client.getTeams();
5762
+ if (teams.length === 0) throw new Error("No workspaces found for this token.");
5763
+ let teamId;
5764
+ if (teams.length === 1) {
5765
+ teamId = teams[0].id;
5766
+ process.stdout.write(`Workspace: ${teams[0].name}
5767
+ `);
5768
+ } else {
5769
+ teamId = await select3({
5770
+ message: "Select workspace:",
5771
+ choices: teams.map((t) => ({ name: t.name, value: t.id }))
5772
+ });
5773
+ }
5774
+ addProfile(name, { apiToken, teamId });
5775
+ process.stdout.write(`Profile "${name}" added.
5776
+ `);
5777
+ })
5778
+ );
5779
+ profileCmd.command("remove <name>").description("Remove a profile").action(
5780
+ wrapAction(async (name) => {
5781
+ removeProfile(name);
5782
+ console.log(`Removed profile "${name}"`);
5783
+ })
5784
+ );
5785
+ profileCmd.command("use <name>").description("Set the default profile").action(
5786
+ wrapAction(async (name) => {
5787
+ setDefaultProfile(name);
5788
+ console.log(`Default profile set to "${name}"`);
5789
+ })
5790
+ );
5500
5791
  const configCmd = program.command("config").description("Manage CLI configuration");
5501
5792
  configCmd.command("get <key>").description("Print a config value").action(
5502
5793
  wrapAction(async (key) => {
5503
- const value = getConfigValue(key);
5794
+ const value = getConfigValue(key, getProfileName());
5504
5795
  if (value !== void 0) {
5505
5796
  console.log(value);
5506
5797
  }
@@ -5508,7 +5799,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5508
5799
  );
5509
5800
  configCmd.command("set <key> <value>").description("Set a config value").action(
5510
5801
  wrapAction(async (key, value) => {
5511
- setConfigValue(key, value);
5802
+ setConfigValue(key, value, getProfileName());
5512
5803
  })
5513
5804
  );
5514
5805
  configCmd.command("path").description("Print config file path").action(
@@ -5533,7 +5824,12 @@ process.on("SIGINT", () => {
5533
5824
  process.stderr.write("\nInterrupted\n");
5534
5825
  process.exit(130);
5535
5826
  });
5536
- var isDirectExecution = process.argv[1] !== void 0 && fileURLToPath(import.meta.url) === realpathSync(resolve(process.argv[1]));
5827
+ var isDirectExecution = false;
5828
+ try {
5829
+ isDirectExecution = process.argv[1] !== void 0 && fileURLToPath(import.meta.url) === realpathSync(resolve(process.argv[1]));
5830
+ } catch {
5831
+ isDirectExecution = false;
5832
+ }
5537
5833
  if (isDirectExecution) {
5538
5834
  await run();
5539
5835
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.5.2",
3
+ "version": "1.6.0",
4
4
  "description": "ClickUp CLI for AI agents and humans",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,9 +9,11 @@ Reference for AI agents using the `cup` CLI tool. Covers task management, sprint
9
9
 
10
10
  ## Setup
11
11
 
12
- Config at `~/.config/cup/config.json` with `apiToken` and `teamId`. Optional: `sprintFolderId` to pin sprint detection to a specific folder. Run `cup init` to set up interactively.
12
+ Config at `~/.config/cup/config.json` with named profiles. Each profile has `apiToken` and `teamId`. Optional: `sprintFolderId` to pin sprint detection to a specific folder. Run `cup init` to set up interactively.
13
13
 
14
- Environment variables `CU_API_TOKEN` and `CU_TEAM_ID` override config file when both are set.
14
+ Multiple profiles supported - use `cup profile add <name>` to create, `cup profile use <name>` to switch, or `-p <name>` flag for one-off overrides.
15
+
16
+ Environment variables `CU_API_TOKEN` and `CU_TEAM_ID` override config file when both are set. `CU_PROFILE` selects a profile (overridden by `-p` flag).
15
17
 
16
18
  ## Output Modes
17
19
 
@@ -109,9 +111,20 @@ All commands support `--help` for full flag details. All commands support `--jso
109
111
  | `cup tag-create <spaceId> <name> [--fg color] [--bg color]` | Create space tag |
110
112
  | `cup tag-update <spaceId> <tagName> --name <newName> [--fg c] [--bg c]` | Update space tag |
111
113
  | `cup tag-delete <spaceId> <name>` | Delete space tag |
114
+ | `cup profile list [--json]` | List all profiles |
115
+ | `cup profile add <name>` | Add a new profile (interactive) |
116
+ | `cup profile remove <name>` | Remove a profile |
117
+ | `cup profile use <name>` | Set the default profile |
112
118
  | `cup config get <key>` / `set <key> <value>` / `path` | Manage config |
113
119
  | `cup completion <shell>` | Shell completions (bash/zsh/fish) |
114
120
 
121
+ ## Global Flags
122
+
123
+ | Flag | Description |
124
+ | ------------------- | --------------------------------------------- |
125
+ | `-p, --profile <n>` | Use a specific profile for this command |
126
+ | `--json` | Force JSON output (available on all commands) |
127
+
115
128
  ## Flags & Conventions
116
129
 
117
130
  | Topic | Detail |
@@ -218,6 +231,15 @@ cup key-result-create g123 "API coverage" --type percentage --target 80
218
231
  cup key-result-update kr456 --progress 60 --note "On track"
219
232
  ```
220
233
 
234
+ ### Profiles
235
+
236
+ ```bash
237
+ cup profile add personal # add a profile (interactive)
238
+ cup profile list # list all profiles
239
+ cup profile use personal # switch default
240
+ cup tasks -p work # use specific profile for one command
241
+ ```
242
+
221
243
  ### Standup
222
244
 
223
245
  ```bash