@krodak/clickup-cli 1.5.1 → 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.1",
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
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
+ import { realpathSync } from "fs";
4
5
  import { basename, resolve } from "path";
5
6
  import { Command } from "commander";
6
7
  import { createRequire } from "module";
@@ -740,62 +741,243 @@ function migrateFromLegacy() {
740
741
  function configPath() {
741
742
  return join(configDir(), "config.json");
742
743
  }
743
- 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) {
744
806
  migrateFromLegacy();
745
807
  const envToken = process.env.CU_API_TOKEN?.trim();
746
808
  const envTeamId = process.env.CU_TEAM_ID?.trim();
747
- let fileToken;
748
- let fileTeamId;
749
- 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
+ }
750
815
  const path = configPath();
751
- if (fs.existsSync(path)) {
752
- const raw = fs.readFileSync(path, "utf-8");
753
- const parsed = parseConfigFile(raw, path, true);
754
- fileToken = parsed.apiToken;
755
- fileTeamId = parsed.teamId;
756
- 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>");
757
847
  }
758
- 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();
759
854
  if (!apiToken) {
760
- 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
+ );
761
858
  }
762
859
  if (!apiToken.startsWith("pk_")) {
763
860
  throw new Error("Config apiToken must start with pk_. The configured token does not.");
764
861
  }
765
- const teamId = envTeamId || fileTeamId;
862
+ const teamId = envTeamId ?? profile.teamId?.trim();
766
863
  if (!teamId) {
767
- 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);
768
888
  }
769
- return { apiToken, teamId, ...fileSprintFolderId ? { sprintFolderId: fileSprintFolderId } : {} };
889
+ return migrateToMultiProfile(parsed, path);
770
890
  }
771
- 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) {
772
940
  migrateFromLegacy();
773
941
  const path = configPath();
774
942
  if (!fs.existsSync(path)) return {};
775
- 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] ?? {};
776
959
  }
777
960
  function getConfigPath() {
778
961
  migrateFromLegacy();
779
962
  return configPath();
780
963
  }
781
- function writeConfig(config) {
782
- const dir = configDir();
783
- if (!fs.existsSync(dir)) {
784
- fs.mkdirSync(dir, { recursive: true, mode: 448 });
785
- }
786
- const filePath = join(dir, "config.json");
787
- const apiToken = trimConfigValue(config.apiToken) ?? "";
788
- 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;
789
969
  const sprintFolderId = trimConfigValue(config.sprintFolderId);
790
970
  const normalizedConfig = {
791
971
  ...apiToken ? { apiToken } : {},
792
972
  ...teamId ? { teamId } : {},
793
973
  ...sprintFolderId ? { sprintFolderId } : {}
794
974
  };
795
- fs.writeFileSync(filePath, JSON.stringify(normalizedConfig, null, 2) + "\n", {
796
- encoding: "utf-8",
797
- mode: 384
798
- });
975
+ multi.profiles[name] = {
976
+ ...multi.profiles[name],
977
+ ...normalizedConfig
978
+ };
979
+ if (!multi.defaultProfile) multi.defaultProfile = name;
980
+ saveMultiProfileConfig(multi);
799
981
  }
800
982
 
801
983
  // src/date.ts
@@ -2254,12 +2436,12 @@ function assertValidKey(key) {
2254
2436
  throw new Error(`Unknown config key: ${key}. Valid keys: ${[...VALID_KEYS].join(", ")}`);
2255
2437
  }
2256
2438
  }
2257
- function getConfigValue(key) {
2439
+ function getConfigValue(key, profileName) {
2258
2440
  assertValidKey(key);
2259
- const raw = loadRawConfig();
2441
+ const raw = loadRawConfig(profileName);
2260
2442
  return readStoredString(raw[key]);
2261
2443
  }
2262
- function setConfigValue(key, value) {
2444
+ function setConfigValue(key, value, profileName) {
2263
2445
  assertValidKey(key);
2264
2446
  const normalizedValue = readStoredString(value);
2265
2447
  if (key === "apiToken" && (!normalizedValue || !normalizedValue.startsWith("pk_"))) {
@@ -2268,7 +2450,7 @@ function setConfigValue(key, value) {
2268
2450
  if (key === "teamId" && !normalizedValue) {
2269
2451
  throw new Error("teamId must be non-empty");
2270
2452
  }
2271
- const raw = loadRawConfig();
2453
+ const raw = loadRawConfig(profileName);
2272
2454
  const sprintFolderId = readStoredString(raw.sprintFolderId);
2273
2455
  const merged = {
2274
2456
  ...readStoredString(raw.apiToken) ? { apiToken: readStoredString(raw.apiToken) } : {},
@@ -2284,7 +2466,7 @@ function setConfigValue(key, value) {
2284
2466
  } else {
2285
2467
  merged[key] = normalizedValue;
2286
2468
  }
2287
- writeConfig(merged);
2469
+ writeConfig(merged, profileName);
2288
2470
  }
2289
2471
  function configPath2() {
2290
2472
  return getConfigPath();
@@ -2959,6 +3141,13 @@ var commandMetadata = [
2959
3141
  flags: ["--json"],
2960
3142
  quickReference: [{ section: "read", usage: "templates", description: "List task templates" }]
2961
3143
  },
3144
+ {
3145
+ name: "profile",
3146
+ description: "Manage profiles",
3147
+ quickReference: [
3148
+ { section: "configuration", usage: "profile", description: "Manage profiles" }
3149
+ ]
3150
+ },
2962
3151
  {
2963
3152
  name: "config",
2964
3153
  description: "Manage CLI configuration",
@@ -3015,7 +3204,14 @@ function topLevelCommandNames() {
3015
3204
  }
3016
3205
 
3017
3206
  // src/commands/completion.ts
3018
- 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
+ ]);
3019
3215
  function escapeSingleQuotes(value) {
3020
3216
  return value.replaceAll("'", "'\\''");
3021
3217
  }
@@ -3108,6 +3304,11 @@ ${renderBashCommandCases()}
3108
3304
  COMPREPLY=($(compgen -W "status" -- "$cur"))
3109
3305
  fi
3110
3306
  ;;
3307
+ profile)
3308
+ if [[ $cword -eq 2 ]]; then
3309
+ COMPREPLY=($(compgen -W "list add remove use" -- "$cur"))
3310
+ fi
3311
+ ;;
3111
3312
  config)
3112
3313
  if [[ $cword -eq 2 ]]; then
3113
3314
  COMPREPLY=($(compgen -W "get set path" -- "$cur"))
@@ -3645,6 +3846,33 @@ ${renderZshTopLevelCommands(name)}
3645
3846
  '(-c --content)'{-c,--content}'[New page content]:text:' \\
3646
3847
  '--json[Force JSON output]'
3647
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
+ ;;
3648
3876
  config)
3649
3877
  local -a config_cmds
3650
3878
  config_cmds=(
@@ -3725,6 +3953,12 @@ complete -c ${name} -n '__fish_seen_subcommand_from attach' -F
3725
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'
3726
3954
  complete -c ${name} -n '__fish_seen_subcommand_from status; and __fish_seen_subcommand_from bulk' -l json -d 'Force JSON output'
3727
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
+
3728
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'
3729
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'
3730
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'
@@ -4551,7 +4785,10 @@ function parseOptionalNumberOption(value, optionName) {
4551
4785
  }
4552
4786
  function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4553
4787
  const program = new Command();
4554
- 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
+ }
4555
4792
  program.command("init").description(`Set up ${programName} for the first time`).action(
4556
4793
  wrapAction(async () => {
4557
4794
  await runInitCommand();
@@ -4559,7 +4796,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4559
4796
  );
4560
4797
  program.command("auth").description("Validate API token and show current user").option("--json", "Force JSON output even in terminal").action(
4561
4798
  wrapAction(async (opts) => {
4562
- const config = loadConfig();
4799
+ const config = loadConfig(getProfileName());
4563
4800
  const result = await checkAuth(config);
4564
4801
  if (shouldOutputJson(opts.json ?? false)) {
4565
4802
  console.log(JSON.stringify(result, null, 2));
@@ -4575,7 +4812,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4575
4812
  'Filter by task type (e.g. "task", "initiative", or custom type name/ID)'
4576
4813
  ).option("--include-closed", "Include done/closed tasks").option("--json", "Force JSON output even in terminal").action(
4577
4814
  wrapAction(async (opts) => {
4578
- const config = loadConfig();
4815
+ const config = loadConfig(getProfileName());
4579
4816
  const tasks = await fetchMyTasks(config, {
4580
4817
  typeFilter: opts.type,
4581
4818
  statuses: opts.status ? [opts.status] : void 0,
@@ -4589,7 +4826,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4589
4826
  );
4590
4827
  program.command("task <taskId>").description("Get task details").option("--json", "Force JSON output even in terminal").action(
4591
4828
  wrapAction(async (taskId, opts) => {
4592
- const config = loadConfig();
4829
+ const config = loadConfig(getProfileName());
4593
4830
  const result = await getTask(config, taskId);
4594
4831
  if (shouldOutputJson(opts.json ?? false)) {
4595
4832
  console.log(JSON.stringify(result, null, 2));
@@ -4602,7 +4839,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4602
4839
  );
4603
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(
4604
4841
  wrapAction(async (taskId, opts) => {
4605
- const config = loadConfig();
4842
+ const config = loadConfig(getProfileName());
4606
4843
  if (opts.assignee === "me") {
4607
4844
  const client = new ClickUpClient(config);
4608
4845
  opts.assignee = String(await resolveAssigneeId(client, "me"));
@@ -4618,7 +4855,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4618
4855
  );
4619
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(
4620
4857
  wrapAction(async (opts) => {
4621
- const config = loadConfig();
4858
+ const config = loadConfig(getProfileName());
4622
4859
  if (opts.assignee === "me") {
4623
4860
  const client = new ClickUpClient(config);
4624
4861
  opts.assignee = String(await resolveAssigneeId(client, "me"));
@@ -4634,21 +4871,21 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4634
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(
4635
4872
  wrapAction(
4636
4873
  async (opts) => {
4637
- const config = loadConfig();
4874
+ const config = loadConfig(getProfileName());
4638
4875
  await runSprintCommand(config, opts);
4639
4876
  }
4640
4877
  )
4641
4878
  );
4642
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(
4643
4880
  wrapAction(async (opts) => {
4644
- const config = loadConfig();
4881
+ const config = loadConfig(getProfileName());
4645
4882
  await listSprints(config, opts);
4646
4883
  })
4647
4884
  );
4648
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(
4649
4886
  wrapAction(
4650
4887
  async (taskId, opts) => {
4651
- const config = loadConfig();
4888
+ const config = loadConfig(getProfileName());
4652
4889
  let tasks = await fetchSubtasks(config, taskId, { includeClosed: opts.includeClosed });
4653
4890
  if (opts.status) {
4654
4891
  const lower = opts.status.toLowerCase();
@@ -4665,7 +4902,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4665
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(
4666
4903
  wrapAction(
4667
4904
  async (taskId, opts) => {
4668
- const config = loadConfig();
4905
+ const config = loadConfig(getProfileName());
4669
4906
  const result = await postComment(config, taskId, opts.message, opts.notifyAll);
4670
4907
  if (shouldOutputJson(opts.json ?? false)) {
4671
4908
  console.log(JSON.stringify(result, null, 2));
@@ -4677,7 +4914,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4677
4914
  );
4678
4915
  program.command("comments <taskId>").description("List comments on a task").option("--json", "Force JSON output even in terminal").action(
4679
4916
  wrapAction(async (taskId, opts) => {
4680
- const config = loadConfig();
4917
+ const config = loadConfig(getProfileName());
4681
4918
  const comments = await fetchComments(config, taskId);
4682
4919
  printComments(comments, opts.json ?? false);
4683
4920
  })
@@ -4685,7 +4922,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4685
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(
4686
4923
  wrapAction(
4687
4924
  async (commentId, opts) => {
4688
- const config = loadConfig();
4925
+ const config = loadConfig(getProfileName());
4689
4926
  let resolved;
4690
4927
  if (opts.resolved) resolved = true;
4691
4928
  if (opts.unresolved) resolved = false;
@@ -4700,7 +4937,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4700
4937
  );
4701
4938
  program.command("comment-delete <commentId>").description("Delete a comment").option("--json", "Force JSON output even in terminal").action(
4702
4939
  wrapAction(async (commentId, opts) => {
4703
- const config = loadConfig();
4940
+ const config = loadConfig(getProfileName());
4704
4941
  await deleteComment(config, commentId);
4705
4942
  if (shouldOutputJson(opts.json ?? false)) {
4706
4943
  console.log(JSON.stringify({ success: true, commentId }, null, 2));
@@ -4711,7 +4948,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4711
4948
  );
4712
4949
  program.command("replies <commentId>").description("List threaded replies on a comment").option("--json", "Force JSON output even in terminal").action(
4713
4950
  wrapAction(async (commentId, opts) => {
4714
- const config = loadConfig();
4951
+ const config = loadConfig(getProfileName());
4715
4952
  const replies = await getReplies(config, commentId);
4716
4953
  if (shouldOutputJson(opts.json ?? false)) {
4717
4954
  console.log(JSON.stringify(replies, null, 2));
@@ -4725,7 +4962,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4725
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(
4726
4963
  wrapAction(
4727
4964
  async (commentId, opts) => {
4728
- const config = loadConfig();
4965
+ const config = loadConfig(getProfileName());
4729
4966
  await createReply(config, commentId, opts.message, opts.notifyAll);
4730
4967
  if (shouldOutputJson(opts.json ?? false)) {
4731
4968
  console.log(JSON.stringify({ success: true, commentId }, null, 2));
@@ -4737,27 +4974,27 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4737
4974
  );
4738
4975
  program.command("activity <taskId>").description("Show task details and comments combined").option("--json", "Force JSON output even in terminal").action(
4739
4976
  wrapAction(async (taskId, opts) => {
4740
- const config = loadConfig();
4977
+ const config = loadConfig(getProfileName());
4741
4978
  const result = await fetchActivity(config, taskId);
4742
4979
  printActivity(result, opts.json ?? false);
4743
4980
  })
4744
4981
  );
4745
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(
4746
4983
  wrapAction(async (spaceId, opts) => {
4747
- const config = loadConfig();
4984
+ const config = loadConfig(getProfileName());
4748
4985
  const lists = await fetchLists(config, spaceId, { name: opts.name });
4749
4986
  printLists(lists, opts.json ?? false);
4750
4987
  })
4751
4988
  );
4752
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(
4753
4990
  wrapAction(async (opts) => {
4754
- const config = loadConfig();
4991
+ const config = loadConfig(getProfileName());
4755
4992
  await listSpaces(config, opts);
4756
4993
  })
4757
4994
  );
4758
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(
4759
4996
  wrapAction(async (opts) => {
4760
- const config = loadConfig();
4997
+ const config = loadConfig(getProfileName());
4761
4998
  const days = Number(opts.days ?? 30);
4762
4999
  if (!Number.isFinite(days) || days <= 0) {
4763
5000
  throw new Error("--days must be a positive number");
@@ -4768,20 +5005,20 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4768
5005
  );
4769
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(
4770
5007
  wrapAction(async (opts) => {
4771
- const config = loadConfig();
5008
+ const config = loadConfig(getProfileName());
4772
5009
  await runAssignedCommand(config, opts);
4773
5010
  })
4774
5011
  );
4775
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(
4776
5013
  wrapAction(async (query, opts) => {
4777
- const config = loadConfig();
5014
+ const config = loadConfig(getProfileName());
4778
5015
  await openTask(config, query, opts);
4779
5016
  })
4780
5017
  );
4781
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(
4782
5019
  wrapAction(
4783
5020
  async (query, opts) => {
4784
- const config = loadConfig();
5021
+ const config = loadConfig(getProfileName());
4785
5022
  const tasks = await searchTasks(config, query, {
4786
5023
  status: opts.status,
4787
5024
  includeClosed: opts.includeClosed
@@ -4792,7 +5029,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4792
5029
  );
4793
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(
4794
5031
  wrapAction(async (opts) => {
4795
- const config = loadConfig();
5032
+ const config = loadConfig(getProfileName());
4796
5033
  const hours = Number(opts.hours ?? 24);
4797
5034
  if (!Number.isFinite(hours) || hours <= 0) {
4798
5035
  throw new Error("--hours must be a positive number");
@@ -4802,14 +5039,14 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4802
5039
  );
4803
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(
4804
5041
  wrapAction(async (opts) => {
4805
- const config = loadConfig();
5042
+ const config = loadConfig(getProfileName());
4806
5043
  const tasks = await fetchOverdueTasks(config, { includeClosed: opts.includeClosed });
4807
5044
  await printTasks(tasks, opts.json ?? false, config);
4808
5045
  })
4809
5046
  );
4810
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(
4811
5048
  wrapAction(async (taskId, opts) => {
4812
- const config = loadConfig();
5049
+ const config = loadConfig(getProfileName());
4813
5050
  const result = await assignTask(config, taskId, opts);
4814
5051
  if (shouldOutputJson(opts.json ?? false)) {
4815
5052
  console.log(JSON.stringify(result, null, 2));
@@ -4820,7 +5057,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4820
5057
  );
4821
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(
4822
5059
  wrapAction(async (taskId, opts) => {
4823
- const config = loadConfig();
5060
+ const config = loadConfig(getProfileName());
4824
5061
  const message = await manageDependency(config, taskId, opts);
4825
5062
  if (shouldOutputJson(opts.json ?? false)) {
4826
5063
  console.log(
@@ -4838,7 +5075,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4838
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(
4839
5076
  wrapAction(
4840
5077
  async (taskId, linksTo, opts) => {
4841
- const config = loadConfig();
5078
+ const config = loadConfig(getProfileName());
4842
5079
  const result = await manageTaskLink(config, taskId, linksTo, opts.remove ?? false);
4843
5080
  if (shouldOutputJson(opts.json ?? false)) {
4844
5081
  console.log(
@@ -4856,7 +5093,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4856
5093
  );
4857
5094
  program.command("attach <taskId> <filePath>").description("Upload a file attachment to a task").option("--json", "Force JSON output even in terminal").action(
4858
5095
  wrapAction(async (taskId, filePath, opts) => {
4859
- const config = loadConfig();
5096
+ const config = loadConfig(getProfileName());
4860
5097
  const result = await attachFile(config, taskId, filePath);
4861
5098
  if (shouldOutputJson(opts.json ?? false)) {
4862
5099
  console.log(JSON.stringify(result, null, 2));
@@ -4868,7 +5105,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4868
5105
  );
4869
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(
4870
5107
  wrapAction(async (taskId, opts) => {
4871
- const config = loadConfig();
5108
+ const config = loadConfig(getProfileName());
4872
5109
  const message = await moveTask(config, taskId, opts);
4873
5110
  if (shouldOutputJson(opts.json ?? false)) {
4874
5111
  console.log(
@@ -4882,7 +5119,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4882
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(
4883
5120
  wrapAction(
4884
5121
  async (taskId, opts) => {
4885
- const config = loadConfig();
5122
+ const config = loadConfig(getProfileName());
4886
5123
  const fieldOpts = {};
4887
5124
  if (opts.set) {
4888
5125
  if (opts.set.length !== 2) {
@@ -4910,7 +5147,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4910
5147
  );
4911
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(
4912
5149
  wrapAction(async (taskId, opts) => {
4913
- const config = loadConfig();
5150
+ const config = loadConfig(getProfileName());
4914
5151
  const result = await deleteTaskCommand(config, taskId, opts);
4915
5152
  if (shouldOutputJson(opts.json ?? false)) {
4916
5153
  console.log(JSON.stringify(result, null, 2));
@@ -4922,7 +5159,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4922
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(
4923
5160
  wrapAction(
4924
5161
  async (taskId, opts) => {
4925
- const config = loadConfig();
5162
+ const config = loadConfig(getProfileName());
4926
5163
  const result = await manageTags(config, taskId, opts);
4927
5164
  if (shouldOutputJson(opts.json ?? false)) {
4928
5165
  console.log(JSON.stringify(result, null, 2));
@@ -4938,7 +5175,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4938
5175
  const checklistCmd = program.command("checklist").description("Manage checklists on a task");
4939
5176
  checklistCmd.command("view <taskId>").description("View checklists on a task").option("--json", "Force JSON output even in terminal").action(
4940
5177
  wrapAction(async (taskId, opts) => {
4941
- const config = loadConfig();
5178
+ const config = loadConfig(getProfileName());
4942
5179
  const checklists = await viewChecklists(config, taskId);
4943
5180
  if (shouldOutputJson(opts.json ?? false)) {
4944
5181
  console.log(JSON.stringify(checklists, null, 2));
@@ -4951,7 +5188,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4951
5188
  );
4952
5189
  checklistCmd.command("create <taskId> <name>").description("Create a checklist on a task").option("--json", "Force JSON output even in terminal").action(
4953
5190
  wrapAction(async (taskId, name, opts) => {
4954
- const config = loadConfig();
5191
+ const config = loadConfig(getProfileName());
4955
5192
  const result = await createChecklist(config, taskId, name);
4956
5193
  if (shouldOutputJson(opts.json ?? false)) {
4957
5194
  console.log(JSON.stringify(result, null, 2));
@@ -4962,7 +5199,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4962
5199
  );
4963
5200
  checklistCmd.command("delete <checklistId>").description("Delete a checklist").option("--json", "Force JSON output even in terminal").action(
4964
5201
  wrapAction(async (checklistId, opts) => {
4965
- const config = loadConfig();
5202
+ const config = loadConfig(getProfileName());
4966
5203
  const result = await deleteChecklist(config, checklistId);
4967
5204
  if (shouldOutputJson(opts.json ?? false)) {
4968
5205
  console.log(JSON.stringify(result, null, 2));
@@ -4973,7 +5210,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4973
5210
  );
4974
5211
  checklistCmd.command("add-item <checklistId> <name>").description("Add an item to a checklist").option("--json", "Force JSON output even in terminal").action(
4975
5212
  wrapAction(async (checklistId, name, opts) => {
4976
- const config = loadConfig();
5213
+ const config = loadConfig(getProfileName());
4977
5214
  const result = await addChecklistItem(config, checklistId, name);
4978
5215
  if (shouldOutputJson(opts.json ?? false)) {
4979
5216
  console.log(JSON.stringify(result, null, 2));
@@ -4985,7 +5222,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
4985
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(
4986
5223
  wrapAction(
4987
5224
  async (checklistId, checklistItemId, opts) => {
4988
- const config = loadConfig();
5225
+ const config = loadConfig(getProfileName());
4989
5226
  const updates = {};
4990
5227
  if (opts.name) updates.name = opts.name;
4991
5228
  if (opts.resolved) updates.resolved = true;
@@ -5004,7 +5241,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5004
5241
  );
5005
5242
  checklistCmd.command("delete-item <checklistId> <checklistItemId>").description("Delete a checklist item").option("--json", "Force JSON output even in terminal").action(
5006
5243
  wrapAction(async (checklistId, checklistItemId, opts) => {
5007
- const config = loadConfig();
5244
+ const config = loadConfig(getProfileName());
5008
5245
  const result = await deleteChecklistItem(config, checklistId, checklistItemId);
5009
5246
  if (shouldOutputJson(opts.json ?? false)) {
5010
5247
  console.log(JSON.stringify(result, null, 2));
@@ -5016,7 +5253,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5016
5253
  const timeCmd = program.command("time").description("Track time on tasks");
5017
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(
5018
5255
  wrapAction(async (taskId, opts) => {
5019
- const config = loadConfig();
5256
+ const config = loadConfig(getProfileName());
5020
5257
  const result = await startTimer(config, taskId, opts.description);
5021
5258
  if (shouldOutputJson(opts.json ?? false)) {
5022
5259
  console.log(JSON.stringify(result, null, 2));
@@ -5028,7 +5265,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5028
5265
  );
5029
5266
  timeCmd.command("stop").description("Stop the running timer").option("--json", "Force JSON output even in terminal").action(
5030
5267
  wrapAction(async (opts) => {
5031
- const config = loadConfig();
5268
+ const config = loadConfig(getProfileName());
5032
5269
  const result = await stopTimer(config);
5033
5270
  if (shouldOutputJson(opts.json ?? false)) {
5034
5271
  console.log(JSON.stringify(result, null, 2));
@@ -5041,7 +5278,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5041
5278
  );
5042
5279
  timeCmd.command("status").description("Show the currently running timer").option("--json", "Force JSON output even in terminal").action(
5043
5280
  wrapAction(async (opts) => {
5044
- const config = loadConfig();
5281
+ const config = loadConfig(getProfileName());
5045
5282
  const result = await timerStatus(config);
5046
5283
  if (shouldOutputJson(opts.json ?? false)) {
5047
5284
  console.log(JSON.stringify(result, null, 2));
@@ -5057,7 +5294,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5057
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(
5058
5295
  wrapAction(
5059
5296
  async (taskId, duration, opts) => {
5060
- const config = loadConfig();
5297
+ const config = loadConfig(getProfileName());
5061
5298
  const result = await logTime(config, taskId, duration, opts.description);
5062
5299
  if (shouldOutputJson(opts.json ?? false)) {
5063
5300
  console.log(JSON.stringify(result, null, 2));
@@ -5069,7 +5306,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5069
5306
  );
5070
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(
5071
5308
  wrapAction(async (opts) => {
5072
- const config = loadConfig();
5309
+ const config = loadConfig(getProfileName());
5073
5310
  const days = opts.days ? Number(opts.days) : 7;
5074
5311
  if (!Number.isFinite(days) || days <= 0) {
5075
5312
  throw new Error("--days must be a positive number");
@@ -5087,7 +5324,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5087
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(
5088
5325
  wrapAction(
5089
5326
  async (timeEntryId, opts) => {
5090
- const config = loadConfig();
5327
+ const config = loadConfig(getProfileName());
5091
5328
  const entry = await updateTimeEntry(config, timeEntryId, {
5092
5329
  description: opts.description,
5093
5330
  duration: opts.duration
@@ -5104,7 +5341,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5104
5341
  );
5105
5342
  timeCmd.command("delete <timeEntryId>").description("Delete a time entry").option("--json", "Force JSON output even in terminal").action(
5106
5343
  wrapAction(async (timeEntryId, opts) => {
5107
- const config = loadConfig();
5344
+ const config = loadConfig(getProfileName());
5108
5345
  await deleteTimeEntry(config, timeEntryId);
5109
5346
  if (shouldOutputJson(opts.json ?? false)) {
5110
5347
  console.log(JSON.stringify({ deleted: timeEntryId }));
@@ -5115,7 +5352,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5115
5352
  );
5116
5353
  program.command("tags <spaceId>").description("List tags in a space").option("--json", "Force JSON output even in terminal").action(
5117
5354
  wrapAction(async (spaceId, opts) => {
5118
- const config = loadConfig();
5355
+ const config = loadConfig(getProfileName());
5119
5356
  const tags = await listSpaceTags(config, spaceId);
5120
5357
  if (shouldOutputJson(opts.json ?? false)) {
5121
5358
  console.log(JSON.stringify(tags, null, 2));
@@ -5129,7 +5366,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5129
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(
5130
5367
  wrapAction(
5131
5368
  async (spaceId, name, opts) => {
5132
- const config = loadConfig();
5369
+ const config = loadConfig(getProfileName());
5133
5370
  await createSpaceTag(config, spaceId, name, opts.fg, opts.bg);
5134
5371
  if (shouldOutputJson(opts.json ?? false)) {
5135
5372
  console.log(JSON.stringify({ success: true, spaceId, tag: name }, null, 2));
@@ -5141,7 +5378,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5141
5378
  );
5142
5379
  program.command("tag-delete <spaceId> <name>").description("Delete a tag from a space").option("--json", "Force JSON output even in terminal").action(
5143
5380
  wrapAction(async (spaceId, name, opts) => {
5144
- const config = loadConfig();
5381
+ const config = loadConfig(getProfileName());
5145
5382
  await deleteSpaceTag(config, spaceId, name);
5146
5383
  if (shouldOutputJson(opts.json ?? false)) {
5147
5384
  console.log(JSON.stringify({ success: true, spaceId, tag: name }, null, 2));
@@ -5152,7 +5389,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5152
5389
  );
5153
5390
  program.command("members").description("List workspace members").option("--json", "Force JSON output even in terminal").action(
5154
5391
  wrapAction(async (opts) => {
5155
- const config = loadConfig();
5392
+ const config = loadConfig(getProfileName());
5156
5393
  const members = await listMembers(config);
5157
5394
  if (shouldOutputJson(opts.json ?? false)) {
5158
5395
  console.log(JSON.stringify(members, null, 2));
@@ -5165,7 +5402,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5165
5402
  );
5166
5403
  program.command("fields <listId>").description("List custom fields for a list").option("--json", "Force JSON output even in terminal").action(
5167
5404
  wrapAction(async (listId, opts) => {
5168
- const config = loadConfig();
5405
+ const config = loadConfig(getProfileName());
5169
5406
  const fields = await listFields(config, listId);
5170
5407
  if (shouldOutputJson(opts.json ?? false)) {
5171
5408
  console.log(JSON.stringify(fields, null, 2));
@@ -5178,7 +5415,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5178
5415
  );
5179
5416
  program.command("duplicate <taskId>").description("Duplicate a task").option("--json", "Force JSON output even in terminal").action(
5180
5417
  wrapAction(async (taskId, opts) => {
5181
- const config = loadConfig();
5418
+ const config = loadConfig(getProfileName());
5182
5419
  const result = await duplicateTask(config, taskId);
5183
5420
  if (shouldOutputJson(opts.json ?? false)) {
5184
5421
  console.log(JSON.stringify(result, null, 2));
@@ -5190,7 +5427,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5190
5427
  const bulkCmd = program.command("bulk").description("Bulk task operations");
5191
5428
  bulkCmd.command("status <status> <taskIds...>").description("Update status of multiple tasks").option("--json", "Force JSON output even in terminal").action(
5192
5429
  wrapAction(async (status, taskIds, opts) => {
5193
- const config = loadConfig();
5430
+ const config = loadConfig(getProfileName());
5194
5431
  const result = await bulkUpdateStatus(config, taskIds, status);
5195
5432
  if (shouldOutputJson(opts.json ?? false)) {
5196
5433
  console.log(JSON.stringify(result, null, 2));
@@ -5206,7 +5443,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5206
5443
  );
5207
5444
  program.command("goals").description("List goals in your workspace").option("--json", "Force JSON output even in terminal").action(
5208
5445
  wrapAction(async (opts) => {
5209
- const config = loadConfig();
5446
+ const config = loadConfig(getProfileName());
5210
5447
  const goals = await listGoals(config);
5211
5448
  if (shouldOutputJson(opts.json ?? false)) {
5212
5449
  console.log(JSON.stringify(goals, null, 2));
@@ -5220,7 +5457,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5220
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(
5221
5458
  wrapAction(
5222
5459
  async (name, opts) => {
5223
- const config = loadConfig();
5460
+ const config = loadConfig(getProfileName());
5224
5461
  const goal = await createGoal(config, name, {
5225
5462
  description: opts.description,
5226
5463
  color: opts.color
@@ -5236,7 +5473,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5236
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(
5237
5474
  wrapAction(
5238
5475
  async (goalId, opts) => {
5239
- const config = loadConfig();
5476
+ const config = loadConfig(getProfileName());
5240
5477
  const goal = await updateGoal(config, goalId, {
5241
5478
  name: opts.name,
5242
5479
  description: opts.description,
@@ -5252,7 +5489,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5252
5489
  );
5253
5490
  program.command("goal-delete <goalId>").description("Delete a goal").option("--json", "Force JSON output even in terminal").action(
5254
5491
  wrapAction(async (goalId, opts) => {
5255
- const config = loadConfig();
5492
+ const config = loadConfig(getProfileName());
5256
5493
  await deleteGoal(config, goalId);
5257
5494
  if (shouldOutputJson(opts.json ?? false)) {
5258
5495
  console.log(JSON.stringify({ success: true, goalId }, null, 2));
@@ -5263,7 +5500,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5263
5500
  );
5264
5501
  program.command("key-results <goalId>").description("List key results for a goal").option("--json", "Force JSON output even in terminal").action(
5265
5502
  wrapAction(async (goalId, opts) => {
5266
- const config = loadConfig();
5503
+ const config = loadConfig(getProfileName());
5267
5504
  const krs = await listKeyResults(config, goalId);
5268
5505
  if (shouldOutputJson(opts.json ?? false)) {
5269
5506
  console.log(JSON.stringify(krs, null, 2));
@@ -5277,7 +5514,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5277
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(
5278
5515
  wrapAction(
5279
5516
  async (goalId, name, opts) => {
5280
- const config = loadConfig();
5517
+ const config = loadConfig(getProfileName());
5281
5518
  const target = Number(opts.target ?? 100);
5282
5519
  if (!Number.isFinite(target) || target <= 0) {
5283
5520
  throw new Error("--target must be a positive number");
@@ -5294,7 +5531,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5294
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(
5295
5532
  wrapAction(
5296
5533
  async (keyResultId, opts) => {
5297
- const config = loadConfig();
5534
+ const config = loadConfig(getProfileName());
5298
5535
  const updates = {};
5299
5536
  if (opts.progress !== void 0) {
5300
5537
  const p = Number(opts.progress);
@@ -5313,7 +5550,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5313
5550
  );
5314
5551
  program.command("key-result-delete <keyResultId>").description("Delete a key result").option("--json", "Force JSON output even in terminal").action(
5315
5552
  wrapAction(async (keyResultId, opts) => {
5316
- const config = loadConfig();
5553
+ const config = loadConfig(getProfileName());
5317
5554
  await deleteKeyResult(config, keyResultId);
5318
5555
  if (shouldOutputJson(opts.json ?? false)) {
5319
5556
  console.log(JSON.stringify({ success: true, keyResultId }, null, 2));
@@ -5324,7 +5561,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5324
5561
  );
5325
5562
  program.command("docs [query]").description("List workspace docs (optionally filter by name)").option("--json", "Force JSON output even in terminal").action(
5326
5563
  wrapAction(async (query, opts) => {
5327
- const config = loadConfig();
5564
+ const config = loadConfig(getProfileName());
5328
5565
  const docs = await listDocs(config, query);
5329
5566
  if (shouldOutputJson(opts.json ?? false)) {
5330
5567
  console.log(JSON.stringify(docs, null, 2));
@@ -5337,7 +5574,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5337
5574
  );
5338
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(
5339
5576
  wrapAction(async (docId, pageId, opts) => {
5340
- const config = loadConfig();
5577
+ const config = loadConfig(getProfileName());
5341
5578
  if (pageId) {
5342
5579
  const page = await getDocPage(config, docId, pageId);
5343
5580
  if (shouldOutputJson(opts.json ?? false)) {
@@ -5361,7 +5598,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5361
5598
  );
5362
5599
  program.command("doc-pages <docId>").description("List all pages in a doc with content").option("--json", "Force JSON output even in terminal").action(
5363
5600
  wrapAction(async (docId, opts) => {
5364
- const config = loadConfig();
5601
+ const config = loadConfig(getProfileName());
5365
5602
  const pages = await getAllDocPages(config, docId);
5366
5603
  if (shouldOutputJson(opts.json ?? false)) {
5367
5604
  console.log(JSON.stringify(pages, null, 2));
@@ -5374,7 +5611,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5374
5611
  );
5375
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(
5376
5613
  wrapAction(async (spaceId, opts) => {
5377
- const config = loadConfig();
5614
+ const config = loadConfig(getProfileName());
5378
5615
  const folders = await listFolders(config, spaceId, opts.name);
5379
5616
  if (shouldOutputJson(opts.json ?? false)) {
5380
5617
  console.log(JSON.stringify(folders, null, 2));
@@ -5387,7 +5624,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5387
5624
  );
5388
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(
5389
5626
  wrapAction(async (title, opts) => {
5390
- const config = loadConfig();
5627
+ const config = loadConfig(getProfileName());
5391
5628
  const result = await createDoc(config, title, opts.content);
5392
5629
  if (shouldOutputJson(opts.json ?? false)) {
5393
5630
  console.log(JSON.stringify(result, null, 2));
@@ -5399,7 +5636,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5399
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(
5400
5637
  wrapAction(
5401
5638
  async (docId, name, opts) => {
5402
- const config = loadConfig();
5639
+ const config = loadConfig(getProfileName());
5403
5640
  const page = await createDocPage(config, docId, name, opts.content, opts.parentPage);
5404
5641
  if (shouldOutputJson(opts.json ?? false)) {
5405
5642
  console.log(JSON.stringify(page, null, 2));
@@ -5412,7 +5649,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5412
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(
5413
5650
  wrapAction(
5414
5651
  async (docId, pageId, opts) => {
5415
- const config = loadConfig();
5652
+ const config = loadConfig(getProfileName());
5416
5653
  const page = await editDocPage(config, docId, pageId, {
5417
5654
  name: opts.name,
5418
5655
  content: opts.content
@@ -5427,7 +5664,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5427
5664
  );
5428
5665
  program.command("doc-delete <docId>").description("Delete a doc").option("--json", "Force JSON output even in terminal").action(
5429
5666
  wrapAction(async (docId, opts) => {
5430
- const config = loadConfig();
5667
+ const config = loadConfig(getProfileName());
5431
5668
  await deleteDoc(config, docId);
5432
5669
  if (shouldOutputJson(opts.json ?? false)) {
5433
5670
  console.log(JSON.stringify({ success: true, docId }, null, 2));
@@ -5438,7 +5675,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5438
5675
  );
5439
5676
  program.command("doc-page-delete <docId> <pageId>").description("Delete a doc page").option("--json", "Force JSON output even in terminal").action(
5440
5677
  wrapAction(async (docId, pageId, opts) => {
5441
- const config = loadConfig();
5678
+ const config = loadConfig(getProfileName());
5442
5679
  await deleteDocPage(config, docId, pageId);
5443
5680
  if (shouldOutputJson(opts.json ?? false)) {
5444
5681
  console.log(JSON.stringify({ success: true, docId, pageId }, null, 2));
@@ -5450,7 +5687,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5450
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(
5451
5688
  wrapAction(
5452
5689
  async (spaceId, tagName, opts) => {
5453
- const config = loadConfig();
5690
+ const config = loadConfig(getProfileName());
5454
5691
  await updateSpaceTag(config, spaceId, tagName, {
5455
5692
  name: opts.name,
5456
5693
  fg: opts.fg,
@@ -5472,7 +5709,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5472
5709
  );
5473
5710
  program.command("task-types").description("List custom task types in your workspace").option("--json", "Force JSON output even in terminal").action(
5474
5711
  wrapAction(async (opts) => {
5475
- const config = loadConfig();
5712
+ const config = loadConfig(getProfileName());
5476
5713
  const types = await listTaskTypes(config);
5477
5714
  if (shouldOutputJson(opts.json ?? false)) {
5478
5715
  console.log(JSON.stringify(types, null, 2));
@@ -5485,7 +5722,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5485
5722
  );
5486
5723
  program.command("templates").description("List task templates in your workspace").option("--json", "Force JSON output even in terminal").action(
5487
5724
  wrapAction(async (opts) => {
5488
- const config = loadConfig();
5725
+ const config = loadConfig(getProfileName());
5489
5726
  const templates = await listTemplates(config);
5490
5727
  if (shouldOutputJson(opts.json ?? false)) {
5491
5728
  console.log(JSON.stringify(templates, null, 2));
@@ -5496,10 +5733,65 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5496
5733
  }
5497
5734
  })
5498
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
+ );
5499
5791
  const configCmd = program.command("config").description("Manage CLI configuration");
5500
5792
  configCmd.command("get <key>").description("Print a config value").action(
5501
5793
  wrapAction(async (key) => {
5502
- const value = getConfigValue(key);
5794
+ const value = getConfigValue(key, getProfileName());
5503
5795
  if (value !== void 0) {
5504
5796
  console.log(value);
5505
5797
  }
@@ -5507,7 +5799,7 @@ function buildProgram(programName = basename(process.argv[1] ?? "cup")) {
5507
5799
  );
5508
5800
  configCmd.command("set <key> <value>").description("Set a config value").action(
5509
5801
  wrapAction(async (key, value) => {
5510
- setConfigValue(key, value);
5802
+ setConfigValue(key, value, getProfileName());
5511
5803
  })
5512
5804
  );
5513
5805
  configCmd.command("path").description("Print config file path").action(
@@ -5532,7 +5824,12 @@ process.on("SIGINT", () => {
5532
5824
  process.stderr.write("\nInterrupted\n");
5533
5825
  process.exit(130);
5534
5826
  });
5535
- var isDirectExecution = process.argv[1] !== void 0 && fileURLToPath(import.meta.url) === 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
+ }
5536
5833
  if (isDirectExecution) {
5537
5834
  await run();
5538
5835
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krodak/clickup-cli",
3
- "version": "1.5.1",
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