@beauraines/toggl-cli 0.10.5

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/cmds/today.mjs ADDED
@@ -0,0 +1,83 @@
1
+ import Client from '../client.js'
2
+ import dayjs from 'dayjs'
3
+ import {getWorkspace,getProjects, formatDuration,formatDurationAsTime} from '../utils.js'
4
+ import dur from 'dayjs/plugin/duration.js'
5
+ import relativeTime from 'dayjs/plugin/relativeTime.js'
6
+ dayjs.extend(relativeTime)
7
+ dayjs.extend(dur)
8
+
9
+ export const command = 'today'
10
+ export const desc = 'Reports today\'s activities by project'
11
+ export const builder = {}
12
+
13
+ export const handler = async function (argv) {
14
+ const client = Client()
15
+ const workspace = await getWorkspace()
16
+ const projects = await getProjects(workspace.id)
17
+ const params = {
18
+ start_date: dayjs().startOf('day').toISOString(),
19
+ end_date: dayjs().toISOString()
20
+ }
21
+
22
+ const timeEntries = await client.timeEntries.list(params)
23
+
24
+ let todaysProjects = []
25
+ timeEntries.map(e => {
26
+ todaysProjects.push(e.pid)
27
+ })
28
+ todaysProjects = [...new Set(todaysProjects)]
29
+
30
+ const report = []
31
+ todaysProjects.map(p => {
32
+ let total = 0
33
+ timeEntries.filter(x => x.pid == p).map(e => {
34
+ if (e.duration >= 0) {
35
+ total += e.duration // UNLESS e.duration is less than zero - meaning currently running
36
+ } else {
37
+ const startTime = dayjs.unix(e.duration * -1)
38
+ const duration = dayjs().diff(startTime, 's')
39
+ total += duration
40
+ }
41
+ })
42
+ const project = projects.find(x => x.id == p)
43
+ report.push({
44
+ project,
45
+ project_name: project?.name,
46
+ seconds: total,
47
+ duration_formatted: formatDuration(total * 1000),
48
+ duration: formatDurationAsTime(total * 1000)
49
+ })
50
+ })
51
+
52
+ // Compute total row
53
+ const totalRow = {
54
+ project_name: 'Total'
55
+ }
56
+ totalRow.seconds = report.reduce((total, project) => {
57
+ return total + project.seconds
58
+ }, 0)
59
+ totalRow.duration_formatted = formatDuration(totalRow.seconds * 1000)
60
+ totalRow.duration = formatDurationAsTime(totalRow.seconds * 1000)
61
+ report.push(totalRow)
62
+
63
+ // TODO make format a CLI option
64
+ const format = 'table' // csv | json | table defaults to table
65
+ displayDailyReport(report, format)
66
+ }
67
+
68
+ // TODO should this be moved to a formatter file?
69
+ function displayDailyReport (report, format) {
70
+ switch (format) {
71
+ case 'csv':
72
+ // TODO maybe use jsontocsv
73
+ console.log(JSON.stringify(report))
74
+ break
75
+ case 'json':
76
+ console.log(JSON.stringify(report))
77
+ break
78
+ case 'table':
79
+ default:
80
+ console.table(report, ['project_name', 'duration_formatted'])
81
+ break
82
+ }
83
+ }
package/cmds/web.mjs ADDED
@@ -0,0 +1,23 @@
1
+ import open from 'open'
2
+
3
+ export const command = 'web'
4
+ export const desc = 'Opens toggl track website with default browser, defaults to the timer page'
5
+ export const builder = {
6
+ t: { alias: ['timer'], describe: 'opens the timer page ', requiresArg: false },
7
+ r: { alias: ['reports'], describe: 'opens the reports page ', requiresArg: false },
8
+
9
+ }
10
+
11
+ export const handler = async function (argv) {
12
+ let baseUrl = 'https://track.toggl.com'
13
+ let pageUrl
14
+ if ( argv.timer) {
15
+ pageUrl = 'timer'
16
+ } else if ( argv.reports) {
17
+ pageUrl = 'reports'
18
+ } else {
19
+ pageUrl = 'timer'
20
+ }
21
+ console.log(`Opening ${baseUrl}/${pageUrl}`)
22
+ open(`${baseUrl}/${pageUrl}`)
23
+ }
@@ -0,0 +1,98 @@
1
+ import Client from '../client.js'
2
+ import { getWorkspace, formatDuration, getProjectById } from '../utils.js'
3
+ import dayjs from 'dayjs'
4
+ import dur from 'dayjs/plugin/duration.js'
5
+ import relativeTime from 'dayjs/plugin/relativeTime.js'
6
+ dayjs.extend(relativeTime)
7
+ dayjs.extend(dur)
8
+
9
+ export const command = 'week'
10
+ // FIXME descriptions
11
+ export const desc = 'Weekly project summary by day'
12
+ export const builder = {}
13
+
14
+ export const handler = async function (argv) {
15
+ const client = Client()
16
+ const workspace = await getWorkspace()
17
+
18
+ const params = { } // Leave this for future options, like rounding
19
+ const weeklyReport = await client.reports.weekly(workspace.id, params)
20
+
21
+ const reportData = []
22
+ const totals = [0, 0, 0, 0, 0, 0, 0] // ? Is there a better way to do this?
23
+ for (const project of weeklyReport) {
24
+ const currentProject = await getProjectById(workspace.id, project.project_id)
25
+ const row = {
26
+ projectName: currentProject.name,
27
+ Total: 0
28
+ }
29
+
30
+ for (let i = 0; i < project.seconds.length; i++) {
31
+ const element = project.seconds[i]
32
+ const date = dayjs().startOf('week').add(i, 'days').format('ddd MM-DD')
33
+ const duration = element || 0
34
+ row[date] = formatDuration(duration * 1000)
35
+ totals[i] += duration // for use with a daily total row
36
+ row.Total += duration // accumulate the projects weekly totoal
37
+ }
38
+ reportData.push(row)
39
+ }
40
+
41
+ const totalRow = {
42
+ projectName: 'Total',
43
+ Total: 0
44
+ }
45
+
46
+ for (let i = 0; i < totals.length; i++) {
47
+ const seconds = totals[i]
48
+ const date = dayjs().startOf('week').add(i, 'days').format('ddd MM-DD')
49
+ totalRow[date] = formatDuration(seconds * 1000)
50
+ totalRow.Total += seconds // accumulate the projects weekly totoal
51
+ }
52
+ reportData.push(totalRow)
53
+
54
+ for (const project of reportData) {
55
+ project.Total = formatDuration(project.Total * 1000)
56
+ }
57
+ console.table(reportData)
58
+ }
59
+
60
+ // TODO figure out what to do with these
61
+ function displayReportJson (data) {
62
+ const json = []
63
+ data.map(project => {
64
+ const startOfWeek = dayjs().startOf('week')
65
+ project.totals.slice(0, 7).map((total, i) => {
66
+ const duration = dayjs.duration(total, 'milliseconds').format('H[h] m[m]')
67
+ const day = startOfWeek.add(i, 'days').format('YYYY-MM-DD')
68
+ // i == 7 ? console.info(`\tTotal : ${duration}`) : console.info(`\t ${day} - ${duration}`);
69
+ // TODO revise this format - maybe nested
70
+ const entry = {
71
+ project: project.title.project,
72
+ date: day,
73
+ duration,
74
+ total
75
+ }
76
+ json.push(entry)
77
+ })
78
+ })
79
+ console.log(JSON.stringify(json))
80
+ console.table(json)
81
+ }
82
+
83
+ // TODO figure out what do to with these
84
+ function displayReportText (data) {
85
+ data.map(project => {
86
+ console.info(project.title.project)
87
+ const startOfWeek = dayjs().startOf('week')
88
+ project.totals.map((total, i) => {
89
+ // TODO see https://runkit.com/6022b36c23da0600130851a0/6022b36c7614ed001ad11853 for formatting options
90
+ const dur = dayjs.duration(total, 'milliseconds')
91
+ const hours = (dur.days() * 24) + dur.hours()
92
+ const duration = `${hours}h ${dur.minutes()}m `
93
+ // let duration = `${hours}h ${dur.minutes()}m ${dur.seconds()}s`
94
+ const day = startOfWeek.add(i, 'days').format('ddd MMM D')
95
+ i == 7 ? console.info(`\tTotal : ${duration}`) : console.info(`\t ${day} - ${duration}`)
96
+ })
97
+ })
98
+ }
@@ -0,0 +1,10 @@
1
+ import { workspaces } from './workspaces/index.mjs'
2
+
3
+ export const command = 'workspace <command>'
4
+ export const desc = 'Manage workspaces'
5
+
6
+ export const builder = function (yargs) {
7
+ return yargs.command(workspaces)
8
+ }
9
+
10
+ export const handler = function (argv) {}
@@ -0,0 +1,7 @@
1
+ export const command = 'add'
2
+ export const desc = 'Creates a new workspace.'
3
+ export const builder = {}
4
+
5
+ export const handler = async function (argv) {
6
+ console.info(`${argv.$0} ${argv._.join(' ')} - this command is not yet supported.`)
7
+ }
@@ -0,0 +1,3 @@
1
+ import * as add from './add.mjs'
2
+ import * as list from './list.mjs'
3
+ export const workspaces = [add, list]
@@ -0,0 +1,14 @@
1
+ import Client from '../../client.js'
2
+
3
+ export const command = 'list'
4
+ export const desc = 'Lists workspaces'
5
+
6
+ export const builder = {
7
+
8
+ }
9
+
10
+ export const handler = async function (argv) {
11
+ const client = Client()
12
+ const workspaces = await client.workspaces.list()
13
+ console.log(workspaces)
14
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@beauraines/toggl-cli",
3
+ "version": "0.10.5",
4
+ "description": "",
5
+ "main": "cli.js",
6
+ "bin": {
7
+ "toggl": "./cli.js"
8
+ },
9
+ "scripts": {
10
+ "test": "jest",
11
+ "lint": "eslint . ./**/*.js ./**/*.js",
12
+ "lint:fix": "eslint . ./**/*.js ./**/*.mjs --fix",
13
+ "release": "standard-version",
14
+ "should-release": "should-release"
15
+ },
16
+ "type": "module",
17
+ "keywords": [
18
+ "toggl",
19
+ "cli",
20
+ "time",
21
+ "tracking"
22
+ ],
23
+ "author": "Beau Raines <beau.raines@gmail.com>",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/beauraines/toggl-cli-node.git"
27
+ },
28
+ "bugs": {
29
+ "url": "https://github.com/beauraines/toggl-cli-node/issues"
30
+ },
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "dayjs": "^1.11.6",
34
+ "dotenv": "^16.0.3",
35
+ "open": "^8.4.0",
36
+ "toggl-client": "^3.1.1",
37
+ "yargs": "^17.6.0"
38
+ },
39
+ "devDependencies": {
40
+ "eslint": "^8.34.0",
41
+ "eslint-config-standard": "^17.0.0",
42
+ "eslint-plugin-import": "^2.27.5",
43
+ "eslint-plugin-jest": "^27.2.1",
44
+ "eslint-plugin-n": "^15.6.1",
45
+ "eslint-plugin-promise": "^6.1.1",
46
+ "jest": "^29.4.3",
47
+ "should-release": "^1.2.0",
48
+ "standard-version": "^9.5.0"
49
+ }
50
+ }
@@ -0,0 +1,12 @@
1
+ import Client from 'toggl-client'
2
+ import { displayTimeEntry } from '../utils.js'
3
+ import dotenv from 'dotenv'
4
+ dotenv.config()
5
+
6
+ async function main () {
7
+ const client = new Client()
8
+ const currentTimeEntry = await client.timeEntries.current()
9
+ await displayTimeEntry(currentTimeEntry)
10
+ }
11
+
12
+ main()
@@ -0,0 +1,11 @@
1
+ import togglClient from 'toggl-client'
2
+ import dotenv from 'dotenv'
3
+ const client = togglClient()
4
+ dotenv.config()
5
+
6
+ async function main () {
7
+ const workspaces = await client.workspaces.list()
8
+ console.log(workspaces)
9
+ }
10
+
11
+ main()
@@ -0,0 +1,21 @@
1
+ import togglClient from 'toggl-client'
2
+ import dotenv from 'dotenv'
3
+ const client = togglClient()
4
+ dotenv.config()
5
+
6
+ async function main () {
7
+ const workspaces = await client.workspaces.list()
8
+
9
+ const workspace = workspaces[0]
10
+
11
+ console.info('Workspace: ' + workspace.name)
12
+ console.info('id: ' + workspace.id)
13
+
14
+ const projects = await client.workspaces.projects(workspace.id)
15
+
16
+ const activeProjects = projects.filter(x => x.active)
17
+ console.info(`Found ${activeProjects.length} projects`)
18
+ activeProjects.map(p => { console.log(p.name + ' ' + p.id) })
19
+ }
20
+
21
+ main()
@@ -0,0 +1,47 @@
1
+ import togglClient from 'toggl-client'
2
+ import dayjs from 'dayjs'
3
+ import yargs from 'yargs'
4
+ import { hideBin } from 'yargs/helpers'
5
+ import { defaultWorkspaceId, defaultProjectId } from '../utils.js'
6
+ import dotenv from 'dotenv'
7
+ dotenv.config()
8
+
9
+ const client = togglClient()
10
+
11
+ const options = yargs(hideBin(process.argv))
12
+ .usage('Usage: $0 -p <project_id> -w <workspace_id> [time entry description]')
13
+ .option('p', { alias: 'projectId', describe: 'project id', type: 'string', demandOption: false })
14
+ .option('w', { alias: 'workspaceId', describe: 'workspace id', type: 'number', demandOption: false })
15
+ .argv
16
+
17
+ async function main () {
18
+ // console.debug(JSON.stringify(options))
19
+ // TODO validate options
20
+ const description = options._.join(' ')
21
+ // TODO check that description was provided or provide a default
22
+ const params = { description }
23
+ params.workspaceId = options.workspaceId || defaultWorkspaceId
24
+ params.projectId = options.projectId || defaultProjectId
25
+ // console.debug(params)
26
+ const timeEntry = await createTimeEntry(params)
27
+ console.info(`Started ${timeEntry.description} with project id ${timeEntry.pid}`)
28
+ }
29
+
30
+ async function createTimeEntry (params) {
31
+ // const client = Client()
32
+ const timeEntry = await client.timeEntries.create(
33
+ {
34
+ description: params.description,
35
+ workspace_id: +params.workspaceId,
36
+ project_id: +params.projectId,
37
+ start: dayjs().toISOString(),
38
+ duration: -1 * dayjs().unix(),
39
+ created_with: 'My app',
40
+ at: dayjs().toISOString()
41
+ }
42
+ )
43
+ // console.log(timeEntry)
44
+ return timeEntry
45
+ }
46
+
47
+ main()
@@ -0,0 +1,21 @@
1
+ import togglClient from 'toggl-client'
2
+ import dayjs from 'dayjs'
3
+ import duration from 'dayjs/plugin/duration.js'
4
+ import relativeTime from 'dayjs/plugin/relativeTime.js'
5
+ import dotenv from 'dotenv'
6
+ dayjs.extend(relativeTime)
7
+ dayjs.extend(duration)
8
+ dotenv.config()
9
+
10
+ const client = togglClient()
11
+
12
+ async function main () {
13
+ const currentTimeEntry = await client.timeEntries.current()
14
+ // TODO check if there is actually something to stop
15
+ const stopped = await client.timeEntries.stop(currentTimeEntry.id)
16
+
17
+ const duration = dayjs.duration({ seconds: stopped.duration }).humanize()
18
+ console.log(`Stopped ${stopped.description} after ${duration}`)
19
+ }
20
+
21
+ main()
@@ -0,0 +1,101 @@
1
+ import togglClient from 'toggl-client'
2
+ import dayjs from 'dayjs'
3
+ import duration from 'dayjs/plugin/duration.js'
4
+ import relativeTime from 'dayjs/plugin/relativeTime.js'
5
+ import dotenv from 'dotenv'
6
+ dayjs.extend(relativeTime)
7
+ dayjs.extend(duration)
8
+ dotenv.config()
9
+
10
+ const client = togglClient()
11
+
12
+ async function getWorkspace () {
13
+ const workspaces = await client.workspaces.list()
14
+ return workspaces[0]
15
+ }
16
+
17
+ async function main () {
18
+ const workspace = await getWorkspace()
19
+ const params = { start_date: dayjs().startOf('day').toISOString() }
20
+
21
+ const timeEntries = await client.timeEntries.list(params)
22
+
23
+ // [
24
+ // {
25
+ // id: 2463026053,
26
+ // guid: 'dba205a234d78492f3f7510c0c00b790',
27
+ // wid: 403916,
28
+ // pid: 174558624,
29
+ // billable: false,
30
+ // start: '2022-04-22T17:38:27+00:00',
31
+ // stop: '2022-04-22T18:07:50+00:00',
32
+ // duration: 1763,
33
+ // description: 'recommendations',
34
+ // duronly: false,
35
+ // at: '2022-04-22T18:07:51+00:00',
36
+ // uid: 530321
37
+ // },
38
+ // {
39
+ // id: 2463068736,
40
+ // guid: 'e9a444f99da3a09de1f16d242546e18e',
41
+ // wid: 403916,
42
+ // pid: 174558624,
43
+ // billable: false,
44
+ // start: '2022-04-22T18:16:01+00:00',
45
+ // stop: '2022-04-22T18:33:44+00:00',
46
+ // duration: 1063,
47
+ // description: 'profile updates',
48
+ // duronly: false,
49
+ // at: '2022-04-22T18:33:44+00:00',
50
+ // uid: 530321
51
+ // }
52
+ // ]
53
+
54
+ console.log(timeEntries)
55
+ let projects = []
56
+ timeEntries.map(e => {
57
+ console.log(`${e.start} ${e.description} ${e.duration}`)
58
+ // find unique projects
59
+ projects.push(e.pid)
60
+ // filter for each project
61
+ // reduce() or _.groupBy() or groupBy()
62
+ // sum duration
63
+ // 2022-04-22T18:53:24+00:00 Time tracking - cli 52
64
+ // 2022-04-22T18:54:15+00:00 Time tracking - cli 225
65
+ // 2022-04-22T18:58:00+00:00 MCP All Hands 3191
66
+ // 2022-04-22T19:56:37+00:00 Time tracking - cli 303
67
+ // 2022-04-22T20:01:39+00:00 Time tracking - cli -1650657699
68
+ })
69
+ projects = [...new Set(projects)]
70
+
71
+ const foo = []
72
+ projects.map(p => {
73
+ let total = 0
74
+ timeEntries.filter(x => x.pid == p).map(e => {
75
+ if (e.duration >= 0) {
76
+ total += e.duration // UNLESS e.duration is less than zero - meaning currently running
77
+ } else {
78
+ const startTime = dayjs.unix(e.duration * -1)
79
+ console.log(`computed ${startTime}`)
80
+ console.log(`timeentry.start ${e.start}`)
81
+ const duration = dayjs().diff(startTime, 's')
82
+ total += duration
83
+ }
84
+ })
85
+ foo.push({ [p]: total })
86
+ })
87
+ console.log(foo)
88
+
89
+ console.log(timeEntries[0])
90
+ console.log(timeEntries[timeEntries.length - 1])
91
+
92
+ // ! Not supported in Node
93
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/groupBy
94
+ // let result = timeEntries.groupBy( ({ pid }) => pid );
95
+ // console.log(result);
96
+
97
+ // displayReportText(weeklyReport.data);
98
+ // displayReportJson(weeklyReport.data);
99
+ }
100
+
101
+ main()
@@ -0,0 +1,57 @@
1
+ import togglClient from 'toggl-client'
2
+ import dayjs from 'dayjs'
3
+ import duration from 'dayjs/plugin/duration.js'
4
+ import relativeTime from 'dayjs/plugin/relativeTime.js'
5
+
6
+ import dotenv from 'dotenv'
7
+ dayjs.extend(relativeTime)
8
+ dayjs.extend(duration)
9
+
10
+ const client = togglClient()
11
+ dotenv.config()
12
+ async function getWorkspace () {
13
+ const workspaces = await client.workspaces.list()
14
+ return workspaces[0]
15
+ }
16
+
17
+ async function main () {
18
+ const workspace = await getWorkspace()
19
+ const params = { since: dayjs().startOf('week').toISOString() }
20
+ const weeklyReport = await client.reports.weekly(workspace.id, params)
21
+ displayReportText(weeklyReport.data)
22
+ // displayReportJson(weeklyReport.data);
23
+ }
24
+
25
+ main()
26
+
27
+ function displayReportJson (data) {
28
+ const json = []
29
+ data.map(project => {
30
+ const startOfWeek = dayjs().startOf('week')
31
+ project.totals.slice(0, 7).map((total, i) => {
32
+ const duration = dayjs.duration(total, 'milliseconds').format('H[h] m[m]')
33
+ const day = startOfWeek.add(i, 'days').format('YYYY-MM-DD')
34
+ // i == 7 ? console.info(`\tTotal : ${duration}`) : console.info(`\t ${day} - ${duration}`);
35
+ // TODO revise this format - maybe nested
36
+ const entry = {
37
+ project: project.title.project,
38
+ date: day,
39
+ duration
40
+ }
41
+ json.push(entry)
42
+ })
43
+ })
44
+ console.log(JSON.stringify(json))
45
+ }
46
+
47
+ function displayReportText (data) {
48
+ data.map(project => {
49
+ console.info(project.title.project)
50
+ const startOfWeek = dayjs().startOf('week')
51
+ project.totals.map((total, i) => {
52
+ const duration = dayjs.duration(total, 'milliseconds').format('H[h] m[m]')
53
+ const day = startOfWeek.add(i, 'days').format('ddd MMM D')
54
+ i == 7 ? console.info(`\tTotal : ${duration}`) : console.info(`\t ${day} - ${duration}`)
55
+ })
56
+ })
57
+ }