@togglhq/cli 1.11.227 → 1.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/build/cli.js +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.js +1 -1
- package/build/{program-BB9t7xvO.js → program-BLPqhZj7.js} +435 -113
- package/build/{program-Bby4U_hT.d.ts → program-CxOdqdfD.d.ts} +2 -2
- package/build/program.d.ts +2 -2
- package/build/program.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,18 @@ Opens a browser for OAuth sign-in. On success, credentials are saved to `~/.togg
|
|
|
31
31
|
|
|
32
32
|
Sessions refresh automatically. Re-run `toggl auth` only to switch workspaces or after 4 weeks of inactivity.
|
|
33
33
|
|
|
34
|
+
### API key (CI or headless)
|
|
35
|
+
|
|
36
|
+
Set `TOGGL_API_TOKEN` to a personal API key (`toggl_sk_…`, from Toggl 2.0 **Settings → API token**). It takes precedence over saved profiles; nothing opens a browser or writes to `~/.toggl/focus-tools.json`.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export TOGGL_API_TOKEN=toggl_sk_...
|
|
40
|
+
export TOGGL_ORGANIZATION_ID=4364968 # usually required: API keys cannot list organizations
|
|
41
|
+
toggl tasks list
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`TOGGL_WORKSPACE_ID` (default: your current Toggl 2.0 workspace) and `TOGGL_USER_ID` (default: looked up from the key) are optional; `--workspace-id` overrides `TOGGL_WORKSPACE_ID`. `organization …` and `shared …` commands call Toggl Accounts, which does not accept API keys, so they ask you to unset the variable and run `toggl auth`. Profile and workspace switching are disabled in this mode.
|
|
45
|
+
|
|
34
46
|
## Profiles
|
|
35
47
|
|
|
36
48
|
Use profiles to switch between saved accounts and environments:
|
package/build/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as CommanderError, i as shutdownCliTelemetry, n as captureCliException, r as initCliTelemetry, t as createProgram } from "./program-
|
|
2
|
+
import { a as CommanderError, i as shutdownCliTelemetry, n as captureCliException, r as initCliTelemetry, t as createProgram } from "./program-BLPqhZj7.js";
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
package/build/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { n as createProgram } from "./program-CxOdqdfD.js";
|
|
2
2
|
export { createProgram };
|
package/build/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createProgram } from "./program-
|
|
1
|
+
import { t as createProgram } from "./program-BLPqhZj7.js";
|
|
2
2
|
export { createProgram };
|
|
@@ -5204,104 +5204,6 @@ var dist_default = createPrompt((config, done) => {
|
|
|
5204
5204
|
].filter(Boolean).join("\n").trimEnd()}${cursorHide}`;
|
|
5205
5205
|
});
|
|
5206
5206
|
//#endregion
|
|
5207
|
-
//#region ../cli-core/build/profile-Empu4JWK.js
|
|
5208
|
-
const CONFIG_DIR = path$1.join(os.homedir(), ".toggl");
|
|
5209
|
-
const CONFIG_FILE = path$1.join(CONFIG_DIR, "focus-tools.json");
|
|
5210
|
-
const LEGACY_MCP_CONFIG_FILE = path$1.join(os.homedir(), ".toggl-focus-mcp", "config.json");
|
|
5211
|
-
function readJsonFile(filePath) {
|
|
5212
|
-
if (!fs$1.existsSync(filePath)) return null;
|
|
5213
|
-
try {
|
|
5214
|
-
return JSON.parse(fs$1.readFileSync(filePath, "utf-8"));
|
|
5215
|
-
} catch (err) {
|
|
5216
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5217
|
-
throw new Error(`Failed to read ${filePath}: ${msg}`, { cause: err });
|
|
5218
|
-
}
|
|
5219
|
-
}
|
|
5220
|
-
function saveLegacyAuthConfig(config) {
|
|
5221
|
-
const dir = path$1.dirname(LEGACY_MCP_CONFIG_FILE);
|
|
5222
|
-
fs$1.mkdirSync(dir, { recursive: true });
|
|
5223
|
-
const tmp = `${LEGACY_MCP_CONFIG_FILE}.${process.pid}.tmp`;
|
|
5224
|
-
fs$1.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
5225
|
-
fs$1.renameSync(tmp, LEGACY_MCP_CONFIG_FILE);
|
|
5226
|
-
}
|
|
5227
|
-
function loadDeveloperToolsConfig() {
|
|
5228
|
-
const config = readJsonFile(CONFIG_FILE);
|
|
5229
|
-
if (!config || !config.profiles) return null;
|
|
5230
|
-
return config;
|
|
5231
|
-
}
|
|
5232
|
-
function loadProfile(scope = "cli", profile) {
|
|
5233
|
-
const config = loadDeveloperToolsConfig();
|
|
5234
|
-
if (config) {
|
|
5235
|
-
const activeProfile = profile ?? config.active[scope];
|
|
5236
|
-
if (activeProfile && config.profiles[activeProfile]) return config.profiles[activeProfile];
|
|
5237
|
-
}
|
|
5238
|
-
const legacy = readJsonFile(LEGACY_MCP_CONFIG_FILE);
|
|
5239
|
-
if (!legacy?.access_token || !legacy.workspace_id) return null;
|
|
5240
|
-
return legacy;
|
|
5241
|
-
}
|
|
5242
|
-
function saveDeveloperToolsConfig(config) {
|
|
5243
|
-
fs$1.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
5244
|
-
const tmp = `${CONFIG_FILE}.${process.pid}.tmp`;
|
|
5245
|
-
fs$1.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
5246
|
-
fs$1.renameSync(tmp, CONFIG_FILE);
|
|
5247
|
-
}
|
|
5248
|
-
function profileEnvironment(focusApiUrl) {
|
|
5249
|
-
const hostname = new URL(focusApiUrl).hostname;
|
|
5250
|
-
if (hostname === "focus.toggl.com") return "production";
|
|
5251
|
-
return hostname.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
5252
|
-
}
|
|
5253
|
-
function profileSlug(value, fallback) {
|
|
5254
|
-
return (value?.split("@")[0] ?? fallback).normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "").toLowerCase() || fallback;
|
|
5255
|
-
}
|
|
5256
|
-
function deriveCliProfileName(options) {
|
|
5257
|
-
return `${profileSlug(options.email, "user")}-${options.userId}`;
|
|
5258
|
-
}
|
|
5259
|
-
function toProfileRow(name, auth, activeProfile) {
|
|
5260
|
-
const workspace = auth.workspaces?.find((candidate) => candidate.workspace_id === auth.workspace_id);
|
|
5261
|
-
return {
|
|
5262
|
-
name,
|
|
5263
|
-
active: name === activeProfile,
|
|
5264
|
-
environment: profileEnvironment(auth.focus_api_url),
|
|
5265
|
-
user_id: auth.user_id,
|
|
5266
|
-
organization_id: auth.organization_id,
|
|
5267
|
-
organization_name: workspace?.organization_name,
|
|
5268
|
-
workspace_id: auth.workspace_id,
|
|
5269
|
-
workspace_name: auth.workspace_name,
|
|
5270
|
-
focus_api_url: auth.focus_api_url,
|
|
5271
|
-
accounts_api_url: auth.accounts_api_url,
|
|
5272
|
-
authenticated_at: auth.authenticated_at,
|
|
5273
|
-
expires_at: auth.expires_at,
|
|
5274
|
-
token_expired: auth.expires_at <= Date.now() / 1e3
|
|
5275
|
-
};
|
|
5276
|
-
}
|
|
5277
|
-
function listCliProfiles() {
|
|
5278
|
-
const config = loadDeveloperToolsConfig();
|
|
5279
|
-
if (!config?.profiles) return [];
|
|
5280
|
-
return Object.entries(config.profiles).map(([name, auth]) => toProfileRow(name, auth, config.active.cli)).sort((a, b) => a.name.localeCompare(b.name));
|
|
5281
|
-
}
|
|
5282
|
-
function getCurrentCliProfile() {
|
|
5283
|
-
const config = loadDeveloperToolsConfig();
|
|
5284
|
-
const activeProfile = config?.active.cli;
|
|
5285
|
-
if (!config?.profiles || !activeProfile || !config.profiles[activeProfile]) throw new Error("No active CLI profile. Run `toggl auth` to sign in.");
|
|
5286
|
-
return toProfileRow(activeProfile, config.profiles[activeProfile], activeProfile);
|
|
5287
|
-
}
|
|
5288
|
-
function switchCliProfile(profileName) {
|
|
5289
|
-
const config = loadDeveloperToolsConfig();
|
|
5290
|
-
if (!config?.profiles?.[profileName]) throw new Error(`Profile '${profileName}' not found. Run \`toggl profile list\` to see profiles.`);
|
|
5291
|
-
config.active.cli = profileName;
|
|
5292
|
-
saveDeveloperToolsConfig(config);
|
|
5293
|
-
return toProfileRow(profileName, config.profiles[profileName], profileName);
|
|
5294
|
-
}
|
|
5295
|
-
function removeCliProfile(profileName) {
|
|
5296
|
-
const config = loadDeveloperToolsConfig();
|
|
5297
|
-
if (!config?.profiles?.[profileName]) throw new Error(`Profile '${profileName}' not found. Run \`toggl profile list\` to see profiles.`);
|
|
5298
|
-
const removed = toProfileRow(profileName, config.profiles[profileName], config.active.cli);
|
|
5299
|
-
delete config.profiles[profileName];
|
|
5300
|
-
if (config.active.cli === profileName) config.active.cli = Object.keys(config.profiles).sort()[0];
|
|
5301
|
-
saveDeveloperToolsConfig(config);
|
|
5302
|
-
return removed;
|
|
5303
|
-
}
|
|
5304
|
-
//#endregion
|
|
5305
5207
|
//#region ../cli-core/build/env-CiVJIUnv.js
|
|
5306
5208
|
const PRODUCTION_URLS = {
|
|
5307
5209
|
focusApiUrl: "https://focus.toggl.com",
|
|
@@ -11038,7 +10940,7 @@ async function deleteMyWorkingHours(httpClient, organization_id, data, signal) {
|
|
|
11038
10940
|
return response.json();
|
|
11039
10941
|
}
|
|
11040
10942
|
//#endregion
|
|
11041
|
-
//#region ../cli-core/build/focus-client-
|
|
10943
|
+
//#region ../cli-core/build/focus-client-CGUlmnDg.js
|
|
11042
10944
|
/** Request header the API reads to attribute traffic to MCP or CLI. */
|
|
11043
10945
|
const TOGGL_CLIENT_HEADER = "X-Toggl-Client";
|
|
11044
10946
|
/** Header the Focus API reads for analytics and presence `platform_origin`. */
|
|
@@ -12018,7 +11920,313 @@ var FocusClient = class {
|
|
|
12018
11920
|
}
|
|
12019
11921
|
};
|
|
12020
11922
|
//#endregion
|
|
11923
|
+
//#region ../cli-core/build/profile-BNQd2PG5.js
|
|
11924
|
+
const CONFIG_DIR = path$1.join(os.homedir(), ".toggl");
|
|
11925
|
+
const CONFIG_FILE = path$1.join(CONFIG_DIR, "focus-tools.json");
|
|
11926
|
+
const LEGACY_MCP_CONFIG_FILE = path$1.join(os.homedir(), ".toggl-focus-mcp", "config.json");
|
|
11927
|
+
function readJsonFile(filePath) {
|
|
11928
|
+
if (!fs$1.existsSync(filePath)) return null;
|
|
11929
|
+
try {
|
|
11930
|
+
return JSON.parse(fs$1.readFileSync(filePath, "utf-8"));
|
|
11931
|
+
} catch (err) {
|
|
11932
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
11933
|
+
throw new Error(`Failed to read ${filePath}: ${msg}`, { cause: err });
|
|
11934
|
+
}
|
|
11935
|
+
}
|
|
11936
|
+
function saveLegacyAuthConfig(config) {
|
|
11937
|
+
const dir = path$1.dirname(LEGACY_MCP_CONFIG_FILE);
|
|
11938
|
+
fs$1.mkdirSync(dir, { recursive: true });
|
|
11939
|
+
const tmp = `${LEGACY_MCP_CONFIG_FILE}.${process.pid}.tmp`;
|
|
11940
|
+
fs$1.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
11941
|
+
fs$1.renameSync(tmp, LEGACY_MCP_CONFIG_FILE);
|
|
11942
|
+
}
|
|
11943
|
+
function loadDeveloperToolsConfig() {
|
|
11944
|
+
const config = readJsonFile(CONFIG_FILE);
|
|
11945
|
+
if (!config || !config.profiles) return null;
|
|
11946
|
+
return config;
|
|
11947
|
+
}
|
|
11948
|
+
function loadProfile(scope = "cli", profile) {
|
|
11949
|
+
const config = loadDeveloperToolsConfig();
|
|
11950
|
+
if (config) {
|
|
11951
|
+
const activeProfile = profile ?? config.active[scope];
|
|
11952
|
+
if (activeProfile && config.profiles[activeProfile]) return config.profiles[activeProfile];
|
|
11953
|
+
}
|
|
11954
|
+
const legacy = readJsonFile(LEGACY_MCP_CONFIG_FILE);
|
|
11955
|
+
if (!legacy?.access_token || !legacy.workspace_id) return null;
|
|
11956
|
+
return legacy;
|
|
11957
|
+
}
|
|
11958
|
+
function saveDeveloperToolsConfig(config) {
|
|
11959
|
+
fs$1.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
11960
|
+
const tmp = `${CONFIG_FILE}.${process.pid}.tmp`;
|
|
11961
|
+
fs$1.writeFileSync(tmp, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
11962
|
+
fs$1.renameSync(tmp, CONFIG_FILE);
|
|
11963
|
+
}
|
|
11964
|
+
function profileEnvironment(focusApiUrl) {
|
|
11965
|
+
const hostname = new URL(focusApiUrl).hostname;
|
|
11966
|
+
if (hostname === "focus.toggl.com") return "production";
|
|
11967
|
+
return hostname.replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "").toLowerCase();
|
|
11968
|
+
}
|
|
11969
|
+
function profileSlug(value, fallback) {
|
|
11970
|
+
return (value?.split("@")[0] ?? fallback).normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-zA-Z0-9]+/g, "-").replace(/^-|-$/g, "").toLowerCase() || fallback;
|
|
11971
|
+
}
|
|
11972
|
+
function deriveCliProfileName(options) {
|
|
11973
|
+
return `${profileSlug(options.email, "user")}-${options.userId}`;
|
|
11974
|
+
}
|
|
11975
|
+
function toProfileRow(name, auth, activeProfile) {
|
|
11976
|
+
const workspace = auth.workspaces?.find((candidate) => candidate.workspace_id === auth.workspace_id);
|
|
11977
|
+
return {
|
|
11978
|
+
name,
|
|
11979
|
+
active: name === activeProfile,
|
|
11980
|
+
environment: profileEnvironment(auth.focus_api_url),
|
|
11981
|
+
user_id: auth.user_id,
|
|
11982
|
+
organization_id: auth.organization_id,
|
|
11983
|
+
organization_name: workspace?.organization_name,
|
|
11984
|
+
workspace_id: auth.workspace_id,
|
|
11985
|
+
workspace_name: auth.workspace_name,
|
|
11986
|
+
focus_api_url: auth.focus_api_url,
|
|
11987
|
+
accounts_api_url: auth.accounts_api_url,
|
|
11988
|
+
authenticated_at: auth.authenticated_at,
|
|
11989
|
+
expires_at: auth.expires_at,
|
|
11990
|
+
token_expired: auth.expires_at <= Date.now() / 1e3
|
|
11991
|
+
};
|
|
11992
|
+
}
|
|
11993
|
+
function listCliProfiles() {
|
|
11994
|
+
const config = loadDeveloperToolsConfig();
|
|
11995
|
+
if (!config?.profiles) return [];
|
|
11996
|
+
return Object.entries(config.profiles).map(([name, auth]) => toProfileRow(name, auth, config.active.cli)).sort((a, b) => a.name.localeCompare(b.name));
|
|
11997
|
+
}
|
|
11998
|
+
function getCurrentCliProfile() {
|
|
11999
|
+
const config = loadDeveloperToolsConfig();
|
|
12000
|
+
const activeProfile = config?.active.cli;
|
|
12001
|
+
if (!config?.profiles || !activeProfile || !config.profiles[activeProfile]) throw new Error("No active CLI profile. Run `toggl auth` to sign in.");
|
|
12002
|
+
return toProfileRow(activeProfile, config.profiles[activeProfile], activeProfile);
|
|
12003
|
+
}
|
|
12004
|
+
function switchCliProfile(profileName) {
|
|
12005
|
+
const config = loadDeveloperToolsConfig();
|
|
12006
|
+
if (!config?.profiles?.[profileName]) throw new Error(`Profile '${profileName}' not found. Run \`toggl profile list\` to see profiles.`);
|
|
12007
|
+
config.active.cli = profileName;
|
|
12008
|
+
saveDeveloperToolsConfig(config);
|
|
12009
|
+
return toProfileRow(profileName, config.profiles[profileName], profileName);
|
|
12010
|
+
}
|
|
12011
|
+
function removeCliProfile(profileName) {
|
|
12012
|
+
const config = loadDeveloperToolsConfig();
|
|
12013
|
+
if (!config?.profiles?.[profileName]) throw new Error(`Profile '${profileName}' not found. Run \`toggl profile list\` to see profiles.`);
|
|
12014
|
+
const removed = toProfileRow(profileName, config.profiles[profileName], config.active.cli);
|
|
12015
|
+
delete config.profiles[profileName];
|
|
12016
|
+
if (config.active.cli === profileName) config.active.cli = Object.keys(config.profiles).sort()[0];
|
|
12017
|
+
saveDeveloperToolsConfig(config);
|
|
12018
|
+
return removed;
|
|
12019
|
+
}
|
|
12020
|
+
//#endregion
|
|
12021
12021
|
//#region ../cli-core/build/index.js
|
|
12022
|
+
/**
|
|
12023
|
+
* Headless auth with a personal Toggl API key (`toggl_sk_…`).
|
|
12024
|
+
*
|
|
12025
|
+
* When `TOGGL_API_TOKEN` is set it wins over any OAuth profile: no browser, no
|
|
12026
|
+
* refresh, and nothing is written to `~/.toggl/focus-tools.json`. The key is sent
|
|
12027
|
+
* to the Toggl 2.0 API as `Authorization: Bearer toggl_sk_…`. The API never falls
|
|
12028
|
+
* back to session auth once a key is presented, so a bad key is a hard 401.
|
|
12029
|
+
*
|
|
12030
|
+
* API-key requests carry no Accounts session. Toggl Accounts itself only accepts
|
|
12031
|
+
* OAuth/session credentials, so anything that calls Accounts directly (the `org`
|
|
12032
|
+
* and `shared` operation families) needs an OAuth sign-in instead.
|
|
12033
|
+
*/
|
|
12034
|
+
const TOGGL_API_TOKEN_ENV = "TOGGL_API_TOKEN";
|
|
12035
|
+
const TOGGL_USER_ID_ENV = "TOGGL_USER_ID";
|
|
12036
|
+
const TOGGL_WORKSPACE_ID_ENV = "TOGGL_WORKSPACE_ID";
|
|
12037
|
+
const TOGGL_ORGANIZATION_ID_ENV = "TOGGL_ORGANIZATION_ID";
|
|
12038
|
+
/** Matches `apikey.KeyPrefix` in toggl/pkg. */
|
|
12039
|
+
const TOGGL_API_KEY_PREFIX = "toggl_sk_";
|
|
12040
|
+
const REDACTED_API_TOKEN = `${TOGGL_API_KEY_PREFIX}[REDACTED]`;
|
|
12041
|
+
var ApiTokenAuthError = class extends Error {
|
|
12042
|
+
status;
|
|
12043
|
+
constructor(message, status) {
|
|
12044
|
+
super(message);
|
|
12045
|
+
this.name = "ApiTokenAuthError";
|
|
12046
|
+
this.status = status;
|
|
12047
|
+
}
|
|
12048
|
+
};
|
|
12049
|
+
/** The raw `TOGGL_API_TOKEN` value, trimmed; blank counts as unset. */
|
|
12050
|
+
function readApiTokenFromEnv(env = process.env) {
|
|
12051
|
+
const value = env[TOGGL_API_TOKEN_ENV]?.trim();
|
|
12052
|
+
return value ? value : void 0;
|
|
12053
|
+
}
|
|
12054
|
+
/** True whenever `TOGGL_API_TOKEN` is set, valid or not: a bad key must fail, not fall back to OAuth. */
|
|
12055
|
+
function isApiTokenMode(env = process.env) {
|
|
12056
|
+
return readApiTokenFromEnv(env) !== void 0;
|
|
12057
|
+
}
|
|
12058
|
+
/** Returns the key, or throws without ever echoing the value back. */
|
|
12059
|
+
function requireApiToken(env = process.env) {
|
|
12060
|
+
const token = readApiTokenFromEnv(env);
|
|
12061
|
+
if (!token) throw new ApiTokenAuthError(`${TOGGL_API_TOKEN_ENV} is not set.`);
|
|
12062
|
+
if (!token.startsWith("toggl_sk_")) throw new ApiTokenAuthError(`${TOGGL_API_TOKEN_ENV} is not a Toggl API key: keys start with "${TOGGL_API_KEY_PREFIX}". Create one in Toggl 2.0 under Settings → API token.`);
|
|
12063
|
+
return token;
|
|
12064
|
+
}
|
|
12065
|
+
const API_KEY_PATTERN = new RegExp(`${TOGGL_API_KEY_PREFIX}[^\\s"'\`,;)}\\]]*`, "g");
|
|
12066
|
+
/** Replace every `toggl_sk_…` key, plus the configured token verbatim, in free text. */
|
|
12067
|
+
function redactApiTokens(text, env = process.env) {
|
|
12068
|
+
let redacted = text.replace(API_KEY_PATTERN, REDACTED_API_TOKEN);
|
|
12069
|
+
const token = readApiTokenFromEnv(env);
|
|
12070
|
+
if (token && token.length >= 8) redacted = redacted.split(token).join(REDACTED_API_TOKEN);
|
|
12071
|
+
return redacted;
|
|
12072
|
+
}
|
|
12073
|
+
/** Deep-redact API keys in every string of a JSON-like value, in place where possible. */
|
|
12074
|
+
function redactApiTokensDeep(value, env = process.env) {
|
|
12075
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
12076
|
+
const visit = (node) => {
|
|
12077
|
+
if (typeof node === "string") return redactApiTokens(node, env);
|
|
12078
|
+
if (!node || typeof node !== "object") return node;
|
|
12079
|
+
if (seen.has(node)) return node;
|
|
12080
|
+
seen.add(node);
|
|
12081
|
+
if (Array.isArray(node)) {
|
|
12082
|
+
for (let i = 0; i < node.length; i++) node[i] = visit(node[i]);
|
|
12083
|
+
return node;
|
|
12084
|
+
}
|
|
12085
|
+
const record = node;
|
|
12086
|
+
for (const key of Object.keys(record)) record[key] = visit(record[key]);
|
|
12087
|
+
return node;
|
|
12088
|
+
};
|
|
12089
|
+
return visit(value);
|
|
12090
|
+
}
|
|
12091
|
+
function parseIdEnv(env, name) {
|
|
12092
|
+
const raw = env[name]?.trim();
|
|
12093
|
+
if (!raw) return void 0;
|
|
12094
|
+
if (!/^\d+$/.test(raw)) throw new ApiTokenAuthError(`${name} must be a positive integer.`);
|
|
12095
|
+
const id = Number(raw);
|
|
12096
|
+
if (id <= 0 || id > Number.MAX_SAFE_INTEGER) throw new ApiTokenAuthError(`${name} must be a positive integer.`);
|
|
12097
|
+
return id;
|
|
12098
|
+
}
|
|
12099
|
+
function positiveId(value) {
|
|
12100
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
12101
|
+
}
|
|
12102
|
+
function focusApiBase(focusApiUrl) {
|
|
12103
|
+
const trimmed = focusApiUrl.replace(/\/+$/, "");
|
|
12104
|
+
return trimmed.endsWith("/api") ? trimmed : `${trimmed}/api`;
|
|
12105
|
+
}
|
|
12106
|
+
const INVALID_KEY_MESSAGE = `Toggl rejected ${TOGGL_API_TOKEN_ENV} (401). The key may have been revoked or replaced — creating a new key invalidates the previous one. Create a fresh key in Toggl 2.0 under Settings → API token.`;
|
|
12107
|
+
async function getJson(url, token, options) {
|
|
12108
|
+
const doFetch = options.fetch ?? fetch;
|
|
12109
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
12110
|
+
const origin = new URL(url).origin;
|
|
12111
|
+
try {
|
|
12112
|
+
const response = await doFetch(url, {
|
|
12113
|
+
headers: {
|
|
12114
|
+
Authorization: `Bearer ${token}`,
|
|
12115
|
+
Accept: "application/json",
|
|
12116
|
+
...togglClientHeaders(options.clientSurface)
|
|
12117
|
+
},
|
|
12118
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
12119
|
+
});
|
|
12120
|
+
if (!response.ok) return { status: response.status };
|
|
12121
|
+
return {
|
|
12122
|
+
status: response.status,
|
|
12123
|
+
body: await response.json()
|
|
12124
|
+
};
|
|
12125
|
+
} catch (error) {
|
|
12126
|
+
if (error instanceof DOMException && error.name === "TimeoutError") throw new ApiTokenAuthError(`The Toggl 2.0 API at ${origin} did not answer within ${Math.round(timeoutMs / 1e3)}s.`);
|
|
12127
|
+
if (error instanceof SyntaxError) throw new ApiTokenAuthError(`The Toggl 2.0 API at ${origin} returned a response that is not JSON.`);
|
|
12128
|
+
throw new ApiTokenAuthError(`Could not reach the Toggl 2.0 API at ${origin}: ${redactApiTokens(error instanceof Error ? error.message : String(error), options.env)}`);
|
|
12129
|
+
}
|
|
12130
|
+
}
|
|
12131
|
+
/**
|
|
12132
|
+
* Work out the user, workspace and organization the key acts in.
|
|
12133
|
+
*
|
|
12134
|
+
* Explicit values win (`--workspace-id`, then `TOGGL_*_ID`). Otherwise:
|
|
12135
|
+
* - user: `GET /api/accounts/me` → `user_account_id`
|
|
12136
|
+
* - workspace: `GET /api/users/me/settings` → `current_workspace_id`, then the
|
|
12137
|
+
* workspace last picked in Toggl's hosted MCP
|
|
12138
|
+
* - organization: the hosted-MCP selection when it names the same workspace.
|
|
12139
|
+
* The Toggl 2.0 API has no "my organizations" endpoint an API key can read,
|
|
12140
|
+
* so any other case needs `TOGGL_ORGANIZATION_ID`.
|
|
12141
|
+
*
|
|
12142
|
+
* The settings call always runs, so a revoked key fails here rather than on the
|
|
12143
|
+
* first tool call. The chosen pair is then checked with one org+workspace-scoped
|
|
12144
|
+
* read, which is what turns a wrong or foreign organization into a clear error.
|
|
12145
|
+
*/
|
|
12146
|
+
async function resolveApiTokenIdentity(options) {
|
|
12147
|
+
const env = options.env ?? process.env;
|
|
12148
|
+
const token = requireApiToken(env);
|
|
12149
|
+
const api = focusApiBase(options.focusApiUrl);
|
|
12150
|
+
const envUserId = parseIdEnv(env, TOGGL_USER_ID_ENV);
|
|
12151
|
+
const envWorkspaceId = parseIdEnv(env, TOGGL_WORKSPACE_ID_ENV);
|
|
12152
|
+
const envOrganizationId = parseIdEnv(env, TOGGL_ORGANIZATION_ID_ENV);
|
|
12153
|
+
const settings = await getJson(`${api}/users/me/settings`, token, options);
|
|
12154
|
+
if (settings.status === 401) throw new ApiTokenAuthError(INVALID_KEY_MESSAGE, 401);
|
|
12155
|
+
if (!settings.body) throw new ApiTokenAuthError(`Could not read your Toggl 2.0 user settings (${settings.status}).`, settings.status);
|
|
12156
|
+
let userId = envUserId;
|
|
12157
|
+
let userSource = "env";
|
|
12158
|
+
if (userId === void 0) {
|
|
12159
|
+
const me = await getJson(`${api}/accounts/me`, token, options);
|
|
12160
|
+
if (me.status === 401) throw new ApiTokenAuthError(INVALID_KEY_MESSAGE, 401);
|
|
12161
|
+
userId = positiveId(me.body?.user_account_id);
|
|
12162
|
+
if (userId === void 0) throw new ApiTokenAuthError(`Could not determine your Toggl user ID (${me.status}). Set ${TOGGL_USER_ID_ENV}.`, me.status);
|
|
12163
|
+
userSource = "api";
|
|
12164
|
+
}
|
|
12165
|
+
const hostedMcpWorkspaceId = positiveId(settings.body.mcp_active_workspace_id);
|
|
12166
|
+
const hostedMcpOrganizationId = positiveId(settings.body.mcp_active_organization_id);
|
|
12167
|
+
let workspaceId;
|
|
12168
|
+
let workspaceSource;
|
|
12169
|
+
if (options.workspaceIdOverride !== void 0) {
|
|
12170
|
+
workspaceId = options.workspaceIdOverride;
|
|
12171
|
+
workspaceSource = "flag";
|
|
12172
|
+
} else if (envWorkspaceId !== void 0) {
|
|
12173
|
+
workspaceId = envWorkspaceId;
|
|
12174
|
+
workspaceSource = "env";
|
|
12175
|
+
} else {
|
|
12176
|
+
workspaceId = positiveId(settings.body.current_workspace_id) ?? hostedMcpWorkspaceId;
|
|
12177
|
+
workspaceSource = "api";
|
|
12178
|
+
}
|
|
12179
|
+
if (workspaceId === void 0) throw new ApiTokenAuthError(`Could not determine a workspace: your Toggl 2.0 settings have no current workspace. Set ${TOGGL_WORKSPACE_ID_ENV}.`);
|
|
12180
|
+
let organizationId = envOrganizationId;
|
|
12181
|
+
let organizationSource = "env";
|
|
12182
|
+
if (organizationId === void 0) if (hostedMcpOrganizationId !== void 0 && hostedMcpWorkspaceId === workspaceId) {
|
|
12183
|
+
organizationId = hostedMcpOrganizationId;
|
|
12184
|
+
organizationSource = "api";
|
|
12185
|
+
} else throw new ApiTokenAuthError(`Could not determine the organization for workspace ${workspaceId}: API keys cannot list organizations. Set ${TOGGL_ORGANIZATION_ID_ENV} (app URLs read focus.toggl.com/<organization_id>/workspaces/<workspace_id>/…).`);
|
|
12186
|
+
const probe = await getJson(`${api}/organizations/${organizationId}/workspaces/${workspaceId}/custom-fields`, token, options);
|
|
12187
|
+
if (probe.status === 401) throw new ApiTokenAuthError(INVALID_KEY_MESSAGE, 401);
|
|
12188
|
+
if (probe.status === 403 || probe.status === 404) throw new ApiTokenAuthError(`This API key has no access to workspace ${workspaceId} in organization ${organizationId}. Check ${TOGGL_ORGANIZATION_ID_ENV} and ${TOGGL_WORKSPACE_ID_ENV}.`, probe.status);
|
|
12189
|
+
if (probe.status >= 400) throw new ApiTokenAuthError(`Could not verify access to workspace ${workspaceId} in organization ${organizationId} (${probe.status}).`, probe.status);
|
|
12190
|
+
return {
|
|
12191
|
+
user_id: userId,
|
|
12192
|
+
organization_id: organizationId,
|
|
12193
|
+
workspace_id: workspaceId,
|
|
12194
|
+
focus_api_url: options.focusApiUrl,
|
|
12195
|
+
accounts_api_url: options.accountsApiUrl,
|
|
12196
|
+
sources: {
|
|
12197
|
+
user_id: userSource,
|
|
12198
|
+
organization_id: organizationSource,
|
|
12199
|
+
workspace_id: workspaceSource
|
|
12200
|
+
}
|
|
12201
|
+
};
|
|
12202
|
+
}
|
|
12203
|
+
function createApiTokenIdentityCache(options) {
|
|
12204
|
+
let resolved;
|
|
12205
|
+
let pending;
|
|
12206
|
+
let failure;
|
|
12207
|
+
return {
|
|
12208
|
+
resolve() {
|
|
12209
|
+
if (resolved) return Promise.resolve(resolved);
|
|
12210
|
+
pending ??= resolveApiTokenIdentity(options()).then((identity) => {
|
|
12211
|
+
resolved = identity;
|
|
12212
|
+
failure = void 0;
|
|
12213
|
+
return identity;
|
|
12214
|
+
}).catch((error) => {
|
|
12215
|
+
failure = error instanceof Error ? error.message : String(error);
|
|
12216
|
+
throw error;
|
|
12217
|
+
}).finally(() => {
|
|
12218
|
+
pending = void 0;
|
|
12219
|
+
});
|
|
12220
|
+
return pending;
|
|
12221
|
+
},
|
|
12222
|
+
peek: () => resolved,
|
|
12223
|
+
lastError: () => failure
|
|
12224
|
+
};
|
|
12225
|
+
}
|
|
12226
|
+
/** Text every OAuth-only surface returns while `TOGGL_API_TOKEN` is set. */
|
|
12227
|
+
function oauthRequiredMessage(subject, authCommand) {
|
|
12228
|
+
return `${subject} requires OAuth sign-in (run auth): it calls Toggl Accounts, which does not accept API keys. Unset ${TOGGL_API_TOKEN_ENV} and run \`${authCommand}\` to use it.`;
|
|
12229
|
+
}
|
|
12022
12230
|
function openAuthBrowser(url) {
|
|
12023
12231
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start \"\"" : "xdg-open";
|
|
12024
12232
|
exec(`${cmd} "${url}"`, (err) => {
|
|
@@ -12643,6 +12851,7 @@ async function refreshTokens(accountsApiUrl, refreshToken, clientSurface, client
|
|
|
12643
12851
|
return response.json();
|
|
12644
12852
|
}
|
|
12645
12853
|
async function getValidToken(scope = "cli", profile) {
|
|
12854
|
+
if (isApiTokenMode()) return requireApiToken();
|
|
12646
12855
|
const authConfig = loadProfile(scope, profile);
|
|
12647
12856
|
if (!authConfig) throw new Error(scope === "cli" ? "Not authenticated. Run `toggl auth` to sign in." : `Not authenticated. Run \`${npxMcpAuthCommand()}\` to sign in.`);
|
|
12648
12857
|
const now = Date.now() / 1e3;
|
|
@@ -12862,7 +13071,7 @@ function scrubSpan(span) {
|
|
|
12862
13071
|
/**
|
|
12863
13072
|
* Last word on every event the public build sends: messages reduced to fixed
|
|
12864
13073
|
* categories, no hostname, no breadcrumbs, no dependency list, no request data,
|
|
12865
|
-
* package-relative stack paths,
|
|
13074
|
+
* package-relative stack paths, no query strings on spans, and no API keys.
|
|
12866
13075
|
*/
|
|
12867
13076
|
function scrubPublicSentryEvent(event) {
|
|
12868
13077
|
redactUnsafeSentryMessages(event);
|
|
@@ -12874,7 +13083,7 @@ function scrubPublicSentryEvent(event) {
|
|
|
12874
13083
|
for (const thread of event.threads?.values ?? []) scrubStacktrace(thread.stacktrace);
|
|
12875
13084
|
for (const span of event.spans ?? []) scrubSpan(span);
|
|
12876
13085
|
scrubSpan(event.contexts?.trace);
|
|
12877
|
-
return event;
|
|
13086
|
+
return redactApiTokensDeep(event);
|
|
12878
13087
|
}
|
|
12879
13088
|
/**
|
|
12880
13089
|
* Privacy options shared by `initCliTelemetry` and `initPublicSentry`.
|
|
@@ -13056,6 +13265,7 @@ function decodeAccessTokenSubject(token) {
|
|
|
13056
13265
|
* from every other identity we already have for the same user.
|
|
13057
13266
|
*/
|
|
13058
13267
|
function resolveProductAnalyticsDistinctId(scope) {
|
|
13268
|
+
if (isApiTokenMode()) return null;
|
|
13059
13269
|
try {
|
|
13060
13270
|
return decodeAccessTokenSubject(loadProfile(scope)?.access_token);
|
|
13061
13271
|
} catch {
|
|
@@ -13156,7 +13366,7 @@ async function runSkillSetup(options) {
|
|
|
13156
13366
|
});
|
|
13157
13367
|
}
|
|
13158
13368
|
//#endregion
|
|
13159
|
-
//#region ../operations/build/validation-
|
|
13369
|
+
//#region ../operations/build/validation-Cl22AQf7.js
|
|
13160
13370
|
const optionalNumber = /* @__PURE__ */ optional$1(/* @__PURE__ */ number$2());
|
|
13161
13371
|
const perPageField = (max) => /* @__PURE__ */ optional$1(/* @__PURE__ */ pipe$2(/* @__PURE__ */ number$2(), /* @__PURE__ */ integer(), /* @__PURE__ */ minValue$1(1), /* @__PURE__ */ maxValue$1(max), /* @__PURE__ */ description$1(`Results per page (1-${max})`)));
|
|
13162
13372
|
const perPageSchema = perPageField(100);
|
|
@@ -14552,6 +14762,14 @@ const orgSharedOperations = [
|
|
|
14552
14762
|
function operationRequiresExplicitAcknowledgment(operation) {
|
|
14553
14763
|
return operation.destructive;
|
|
14554
14764
|
}
|
|
14765
|
+
/**
|
|
14766
|
+
* `org` and `shared` operations call Toggl Accounts directly, and Accounts only
|
|
14767
|
+
* accepts OAuth/session credentials. With `TOGGL_API_TOKEN` they cannot work, so
|
|
14768
|
+
* both surfaces short-circuit them with a sign-in message instead of a 401.
|
|
14769
|
+
*/
|
|
14770
|
+
function operationRequiresOAuth(operation) {
|
|
14771
|
+
return operation.family === "org" || operation.family === "shared";
|
|
14772
|
+
}
|
|
14555
14773
|
const MAX_ERROR_LENGTH = 200;
|
|
14556
14774
|
const MAX_RECEIVED_LENGTH = 40;
|
|
14557
14775
|
function formatIssueMessage(issue) {
|
|
@@ -27929,6 +28147,17 @@ function filterRowsByQuerySlice(rows, query) {
|
|
|
27929
28147
|
const q = query.trim().toLowerCase();
|
|
27930
28148
|
return (q.length === 0 ? rows : rows.filter((r) => r.name.toLowerCase().includes(q) || (r.description?.toLowerCase().includes(q) ?? false) || String(r.value).includes(q))).slice(0, 25);
|
|
27931
28149
|
}
|
|
28150
|
+
/** Pickers listed from Toggl Accounts, which rejects `TOGGL_API_TOKEN` keys. */
|
|
28151
|
+
const ACCOUNTS_BACKED_PICKERS = new Set(["org_workspace"]);
|
|
28152
|
+
/**
|
|
28153
|
+
* The picker to offer for a prompt path, or null for plain ID input. With an API key the
|
|
28154
|
+
* Accounts-backed pickers would only fail with a 401, so the prompt asks for IDs directly.
|
|
28155
|
+
*/
|
|
28156
|
+
function usableEntityKindForPromptPath(path, env = process.env) {
|
|
28157
|
+
const kind = entityKindForPromptPath(path);
|
|
28158
|
+
if (kind && isApiTokenMode(env) && ACCOUNTS_BACKED_PICKERS.has(kind)) return null;
|
|
28159
|
+
return kind;
|
|
28160
|
+
}
|
|
27932
28161
|
function entityKindForPromptPath(path) {
|
|
27933
28162
|
const leaf = path.split(".").pop() ?? path;
|
|
27934
28163
|
if (leaf === "project_id") return "project";
|
|
@@ -28243,7 +28472,7 @@ async function promptForField(field, adapter, entityContext) {
|
|
|
28243
28472
|
default: false
|
|
28244
28473
|
});
|
|
28245
28474
|
if (field.type === "number") {
|
|
28246
|
-
const entityKind =
|
|
28475
|
+
const entityKind = usableEntityKindForPromptPath(field.path);
|
|
28247
28476
|
if (entityContext && entityKind && adapter.searchNumber) {
|
|
28248
28477
|
const source = createEntitySearchSource(entityKind, entityContext);
|
|
28249
28478
|
const wrappedSource = async (term, opt) => {
|
|
@@ -28270,7 +28499,7 @@ async function promptForField(field, adapter, entityContext) {
|
|
|
28270
28499
|
}
|
|
28271
28500
|
if (field.type === "array") {
|
|
28272
28501
|
if (field.itemSchema) return promptArrayOfObjectItems(field, adapter, entityContext);
|
|
28273
|
-
const arrayEntityKind = field.itemType === "number" ?
|
|
28502
|
+
const arrayEntityKind = field.itemType === "number" ? usableEntityKindForPromptPath(field.path) : null;
|
|
28274
28503
|
if (entityContext && arrayEntityKind) {
|
|
28275
28504
|
let query;
|
|
28276
28505
|
for (;;) {
|
|
@@ -28637,7 +28866,7 @@ async function runValidatedOperation(operation, options, deps) {
|
|
|
28637
28866
|
return;
|
|
28638
28867
|
}
|
|
28639
28868
|
if (operationRequiresExplicitAcknowledgment(operation) && !options.yes) throw new Error(`${operation.id} is a destructive operation. Pass --yes to confirm, or --dry-run to preview.`);
|
|
28640
|
-
const response = await operation.execute(input, { client: deps.getClient() });
|
|
28869
|
+
const response = await operation.execute(input, { client: await deps.getClient() });
|
|
28641
28870
|
if (!response.ok) throw new Error(`${response.status}: ${response.message}`);
|
|
28642
28871
|
deps.output(response.data);
|
|
28643
28872
|
}
|
|
@@ -29015,7 +29244,68 @@ function outputJson(data, globalOptions) {
|
|
|
29015
29244
|
if (globalOptions.json) process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
29016
29245
|
else process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
29017
29246
|
}
|
|
29018
|
-
|
|
29247
|
+
const CLI_API_TOKEN_PROFILES_MESSAGE = "Saved profiles are not used while TOGGL_API_TOKEN is set: every command authenticates with the API key. Unset TOGGL_API_TOKEN to use OAuth profiles again.";
|
|
29248
|
+
const CLI_API_TOKEN_WORKSPACE_MESSAGE = "While TOGGL_API_TOKEN is set the workspace comes from --workspace-id, TOGGL_WORKSPACE_ID and TOGGL_ORGANIZATION_ID (or your Toggl 2.0 current workspace).";
|
|
29249
|
+
/**
|
|
29250
|
+
* Resolves the API-key identity once per URL pair and `--workspace-id`, so a prompted
|
|
29251
|
+
* command (entity pickers, then the operation itself) does not repeat the probes.
|
|
29252
|
+
*/
|
|
29253
|
+
function createCliApiTokenResolver(resolveUrls) {
|
|
29254
|
+
const caches = /* @__PURE__ */ new Map();
|
|
29255
|
+
function identity(options, urls = resolveUrls()) {
|
|
29256
|
+
const workspaceIdOverride = options.workspaceId ? parseWorkspaceIdArg(options.workspaceId) : void 0;
|
|
29257
|
+
const key = [
|
|
29258
|
+
urls.focusApiUrl,
|
|
29259
|
+
urls.accountsApiUrl,
|
|
29260
|
+
workspaceIdOverride ?? ""
|
|
29261
|
+
].join("\n");
|
|
29262
|
+
let cache = caches.get(key);
|
|
29263
|
+
if (!cache) {
|
|
29264
|
+
cache = createApiTokenIdentityCache(() => ({
|
|
29265
|
+
focusApiUrl: urls.focusApiUrl,
|
|
29266
|
+
accountsApiUrl: urls.accountsApiUrl,
|
|
29267
|
+
clientSurface: "cli",
|
|
29268
|
+
workspaceIdOverride
|
|
29269
|
+
}));
|
|
29270
|
+
caches.set(key, cache);
|
|
29271
|
+
}
|
|
29272
|
+
return cache.resolve();
|
|
29273
|
+
}
|
|
29274
|
+
async function client(options) {
|
|
29275
|
+
const resolved = await identity(options);
|
|
29276
|
+
return new FocusClient({
|
|
29277
|
+
baseUrl: resolved.focus_api_url,
|
|
29278
|
+
accountsApiUrl: resolved.accounts_api_url,
|
|
29279
|
+
tokenProvider: () => getValidToken("cli"),
|
|
29280
|
+
userId: resolved.user_id,
|
|
29281
|
+
organizationId: String(resolved.organization_id),
|
|
29282
|
+
workspaceId: String(resolved.workspace_id),
|
|
29283
|
+
clientSurface: "cli"
|
|
29284
|
+
});
|
|
29285
|
+
}
|
|
29286
|
+
return {
|
|
29287
|
+
identity,
|
|
29288
|
+
client
|
|
29289
|
+
};
|
|
29290
|
+
}
|
|
29291
|
+
function describeCliApiTokenIdentity(identity) {
|
|
29292
|
+
return {
|
|
29293
|
+
auth_mode: "api_token",
|
|
29294
|
+
user_id: identity.user_id,
|
|
29295
|
+
organization_id: identity.organization_id,
|
|
29296
|
+
workspace_id: identity.workspace_id,
|
|
29297
|
+
focus_api_url: identity.focus_api_url,
|
|
29298
|
+
id_sources: identity.sources
|
|
29299
|
+
};
|
|
29300
|
+
}
|
|
29301
|
+
/** `org`/`shared` commands call Toggl Accounts, which rejects API keys. */
|
|
29302
|
+
function assertOperationAvailableInAuthMode(operation) {
|
|
29303
|
+
if (!isApiTokenMode() || !operationRequiresOAuth(operation)) return;
|
|
29304
|
+
const command = [...operation.cli.group, operation.cli.command].join(" ");
|
|
29305
|
+
throw new Error(oauthRequiredMessage(`\`toggl ${command}\``, "toggl auth"));
|
|
29306
|
+
}
|
|
29307
|
+
function getClient(options, apiToken) {
|
|
29308
|
+
if (isApiTokenMode()) return apiToken.client(options);
|
|
29019
29309
|
const config = loadProfile("cli", options.profile);
|
|
29020
29310
|
if (!config) throw new Error(`Not authenticated. Run \`toggl auth\` to sign in, or \`${npxMcpAuthCommand()}\` for a shared session.`);
|
|
29021
29311
|
const urls = resolveEnvironmentUrls({
|
|
@@ -29032,10 +29322,10 @@ function getClient(options) {
|
|
|
29032
29322
|
clientSurface: "cli"
|
|
29033
29323
|
});
|
|
29034
29324
|
}
|
|
29035
|
-
async function runOperation(operation, globalOptions, options, rawInput) {
|
|
29325
|
+
async function runOperation(operation, globalOptions, options, apiToken, rawInput) {
|
|
29036
29326
|
await runValidatedOperation(operation, options, {
|
|
29037
29327
|
rawInput,
|
|
29038
|
-
getClient: () => getClient(globalOptions),
|
|
29328
|
+
getClient: () => getClient(globalOptions, apiToken),
|
|
29039
29329
|
output: (data) => output(data, globalOptions),
|
|
29040
29330
|
outputJson: (data) => outputJson(data, globalOptions)
|
|
29041
29331
|
});
|
|
@@ -29083,6 +29373,7 @@ function createProgram(programOptions = {}) {
|
|
|
29083
29373
|
const promptAdapter = programOptions.promptAdapter ?? inquirerPromptAdapter;
|
|
29084
29374
|
const isTty = programOptions.isTty ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY));
|
|
29085
29375
|
const env = programOptions.env ?? process.env;
|
|
29376
|
+
const apiToken = createCliApiTokenResolver(() => programOptions.resolveAuthUrls?.({}) ?? resolveEnvironmentUrls());
|
|
29086
29377
|
const program = new Command();
|
|
29087
29378
|
program.name("toggl").description("Toggl CLI — generated from the shared operation catalog").version(CLI_PACKAGE_VERSION).option("--json", "Output pretty-printed JSON").option("--profile <name>", "Use a saved auth profile").option("--workspace-id <id>", "Override the active workspace ID").exitOverride();
|
|
29088
29379
|
const authCommand = program.command("auth").description("Authenticate with your Toggl account");
|
|
@@ -29090,6 +29381,12 @@ function createProgram(programOptions = {}) {
|
|
|
29090
29381
|
authCommand.option("--workspace-id <id>", "Select a workspace by ID (required if you have multiple)").option("--profile <name>", "Profile name to save credentials under").option("--manual", "Headless/SSH flow: open the printed link, copy the code from accounts.toggl.com, paste here").option("--code <value>", "Authorization code from the login link (pair with --manual when not using localhost)").action(async (opts) => {
|
|
29091
29382
|
try {
|
|
29092
29383
|
const urls = programOptions.resolveAuthUrls?.(opts) ?? resolveEnvironmentUrls();
|
|
29384
|
+
if (isApiTokenMode()) {
|
|
29385
|
+
const identity = await apiToken.identity(program.opts(), urls);
|
|
29386
|
+
process.stderr.write("TOGGL_API_TOKEN is set, so no browser sign-in runs and no profile is saved.\n");
|
|
29387
|
+
output(describeCliApiTokenIdentity(identity), program.opts());
|
|
29388
|
+
return;
|
|
29389
|
+
}
|
|
29093
29390
|
const globalOpts = program.opts();
|
|
29094
29391
|
const workspaceId = opts.workspaceId ?? globalOpts.workspaceId;
|
|
29095
29392
|
const profile = opts.profile ?? globalOpts.profile;
|
|
@@ -29128,6 +29425,17 @@ function createProgram(programOptions = {}) {
|
|
|
29128
29425
|
workspaceCommand.command("list").description("Show workspaces you can use (active workspace is marked)").option("--refresh", "Re-fetch workspaces from your account").action(async (opts) => {
|
|
29129
29426
|
try {
|
|
29130
29427
|
const globalOptions = program.opts();
|
|
29428
|
+
if (isApiTokenMode()) {
|
|
29429
|
+
if (opts.refresh) throw new Error(oauthRequiredMessage("`toggl workspace list --refresh`", "toggl auth"));
|
|
29430
|
+
const identity = await apiToken.identity(globalOptions);
|
|
29431
|
+
output([{
|
|
29432
|
+
workspace_id: identity.workspace_id,
|
|
29433
|
+
organization_id: identity.organization_id,
|
|
29434
|
+
active: true,
|
|
29435
|
+
auth_mode: "api_token"
|
|
29436
|
+
}], globalOptions);
|
|
29437
|
+
return;
|
|
29438
|
+
}
|
|
29131
29439
|
output(opts.refresh ? await refreshCliWorkspaces(globalOptions.profile) : listCachedCliWorkspaces(globalOptions.profile), globalOptions);
|
|
29132
29440
|
} catch (error) {
|
|
29133
29441
|
captureCliException(error);
|
|
@@ -29137,6 +29445,7 @@ function createProgram(programOptions = {}) {
|
|
|
29137
29445
|
});
|
|
29138
29446
|
workspaceCommand.command("switch").description("Set the active workspace ID used for Toggl 2.0 API calls").argument("[workspace-id]", "Workspace ID to activate (omit to choose interactively)").action(async (workspaceIdArg) => {
|
|
29139
29447
|
try {
|
|
29448
|
+
if (isApiTokenMode()) throw new Error(CLI_API_TOKEN_WORKSPACE_MESSAGE);
|
|
29140
29449
|
const globalOptions = program.opts();
|
|
29141
29450
|
const target = switchCliWorkspace(await resolveWorkspaceIdOptional(workspaceIdArg, globalOptions, globalOptions.profile, {
|
|
29142
29451
|
isTty,
|
|
@@ -29156,8 +29465,14 @@ function createProgram(programOptions = {}) {
|
|
|
29156
29465
|
}
|
|
29157
29466
|
});
|
|
29158
29467
|
const profileCommand = program.command("profile").description("List, inspect, switch, or remove saved CLI profiles");
|
|
29159
|
-
profileCommand.command("list").alias("ls").description("Show saved CLI profiles across environments").action(() => {
|
|
29468
|
+
profileCommand.command("list").alias("ls").description("Show saved CLI profiles across environments").action(async () => {
|
|
29160
29469
|
try {
|
|
29470
|
+
if (isApiTokenMode()) {
|
|
29471
|
+
const identity = await apiToken.identity(program.opts());
|
|
29472
|
+
process.stderr.write(`${CLI_API_TOKEN_PROFILES_MESSAGE}\n`);
|
|
29473
|
+
output([describeCliApiTokenIdentity(identity)], program.opts());
|
|
29474
|
+
return;
|
|
29475
|
+
}
|
|
29161
29476
|
output(listCliProfiles(), program.opts());
|
|
29162
29477
|
} catch (error) {
|
|
29163
29478
|
captureCliException(error);
|
|
@@ -29165,8 +29480,12 @@ function createProgram(programOptions = {}) {
|
|
|
29165
29480
|
process.exitCode = 1;
|
|
29166
29481
|
}
|
|
29167
29482
|
});
|
|
29168
|
-
profileCommand.command("current").description("Show the active CLI profile").action(() => {
|
|
29483
|
+
profileCommand.command("current").description("Show the active CLI profile").action(async () => {
|
|
29169
29484
|
try {
|
|
29485
|
+
if (isApiTokenMode()) {
|
|
29486
|
+
output(describeCliApiTokenIdentity(await apiToken.identity(program.opts())), program.opts());
|
|
29487
|
+
return;
|
|
29488
|
+
}
|
|
29170
29489
|
output(getCurrentCliProfile(), program.opts());
|
|
29171
29490
|
} catch (error) {
|
|
29172
29491
|
captureCliException(error);
|
|
@@ -29176,6 +29495,7 @@ function createProgram(programOptions = {}) {
|
|
|
29176
29495
|
});
|
|
29177
29496
|
profileCommand.command("switch").description("Set the active CLI profile").argument("[profile]", "Profile name to activate (omit to choose interactively)").action(async (profileArg) => {
|
|
29178
29497
|
try {
|
|
29498
|
+
if (isApiTokenMode()) throw new Error(CLI_API_TOKEN_PROFILES_MESSAGE);
|
|
29179
29499
|
const globalOptions = program.opts();
|
|
29180
29500
|
output(switchCliProfile(await resolveCliProfileNameOptional(profileArg, globalOptions, {
|
|
29181
29501
|
isTty,
|
|
@@ -29190,6 +29510,7 @@ function createProgram(programOptions = {}) {
|
|
|
29190
29510
|
});
|
|
29191
29511
|
profileCommand.command("remove").description("Remove a saved CLI profile").argument("[profile]", "Profile name to remove (omit to choose interactively)").action(async (profileArg) => {
|
|
29192
29512
|
try {
|
|
29513
|
+
if (isApiTokenMode()) throw new Error(CLI_API_TOKEN_PROFILES_MESSAGE);
|
|
29193
29514
|
const globalOptions = program.opts();
|
|
29194
29515
|
output(removeCliProfile(await resolveCliProfileNameOptional(profileArg, globalOptions, {
|
|
29195
29516
|
isTty,
|
|
@@ -29249,6 +29570,7 @@ Common commands:
|
|
|
29249
29570
|
if (operationRequiresExplicitAcknowledgment(operation)) command.option("--yes", "Confirm destructive operation without prompting");
|
|
29250
29571
|
command.action(async (cmdOptions) => {
|
|
29251
29572
|
try {
|
|
29573
|
+
assertOperationAvailableInAuthMode(operation);
|
|
29252
29574
|
const globalOptions = program.opts();
|
|
29253
29575
|
const explicitInput = hasExplicitInput(operation, cmdOptions);
|
|
29254
29576
|
const rawInput = await readInput(operation, cmdOptions);
|
|
@@ -29260,8 +29582,8 @@ Common commands:
|
|
|
29260
29582
|
isTty: isTty(),
|
|
29261
29583
|
env
|
|
29262
29584
|
});
|
|
29263
|
-
const entityContext = willPrompt && !cmdOptions.dryRun ? { client: getClient(globalOptions) } : void 0;
|
|
29264
|
-
await runOperation(operation, globalOptions, cmdOptions, willPrompt ? await promptForOperationInput(operation, rawInput, promptAdapter, entityContext) : rawInput);
|
|
29585
|
+
const entityContext = willPrompt && !cmdOptions.dryRun ? { client: await getClient(globalOptions, apiToken) } : void 0;
|
|
29586
|
+
await runOperation(operation, globalOptions, cmdOptions, apiToken, willPrompt ? await promptForOperationInput(operation, rawInput, promptAdapter, entityContext) : rawInput);
|
|
29265
29587
|
} catch (error) {
|
|
29266
29588
|
captureCliException(error);
|
|
29267
29589
|
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -29292,4 +29614,4 @@ Common commands:
|
|
|
29292
29614
|
//#endregion
|
|
29293
29615
|
export { CommanderError as a, shutdownCliTelemetry as i, captureCliException as n, initCliTelemetry as r, createProgram as t };
|
|
29294
29616
|
|
|
29295
|
-
//# sourceMappingURL=program-
|
|
29617
|
+
//# sourceMappingURL=program-BLPqhZj7.js.map
|
|
@@ -978,5 +978,5 @@ interface CreateProgramOptions {
|
|
|
978
978
|
}
|
|
979
979
|
declare function createProgram(programOptions?: CreateProgramOptions): Command;
|
|
980
980
|
//#endregion
|
|
981
|
-
export { createProgram as t };
|
|
982
|
-
//# sourceMappingURL=program-
|
|
981
|
+
export { createProgram as n, CreateProgramOptions as t };
|
|
982
|
+
//# sourceMappingURL=program-CxOdqdfD.d.ts.map
|
package/build/program.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { createProgram };
|
|
1
|
+
import { n as createProgram, t as CreateProgramOptions } from "./program-CxOdqdfD.js";
|
|
2
|
+
export { CreateProgramOptions, createProgram };
|
package/build/program.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as createProgram } from "./program-
|
|
1
|
+
import { t as createProgram } from "./program-BLPqhZj7.js";
|
|
2
2
|
export { createProgram };
|