@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/.eslintrc.json +16 -0
- package/.github/dependabot.yml +11 -0
- package/.github/workflows/main.yml +27 -0
- package/.github/workflows/publish.yml +54 -0
- package/CHANGELOG.md +38 -0
- package/README.md +85 -0
- package/cli.js +13 -0
- package/client.js +29 -0
- package/cmds/continue.mjs +55 -0
- package/cmds/currentTimeEntry.mjs +16 -0
- package/cmds/edit.mjs +84 -0
- package/cmds/index.mjs +26 -0
- package/cmds/ls.mjs +30 -0
- package/cmds/ls.test.js +6 -0
- package/cmds/me.mjs +44 -0
- package/cmds/projects/add.mjs +7 -0
- package/cmds/projects/index.mjs +3 -0
- package/cmds/projects/list.mjs +25 -0
- package/cmds/projects.mjs +11 -0
- package/cmds/startTimeEntry.mjs +38 -0
- package/cmds/stopTimeEntry.mjs +18 -0
- package/cmds/today.mjs +83 -0
- package/cmds/web.mjs +23 -0
- package/cmds/weekly.mjs +98 -0
- package/cmds/workspace.mjs +10 -0
- package/cmds/workspaces/add.mjs +7 -0
- package/cmds/workspaces/index.mjs +3 -0
- package/cmds/workspaces/list.mjs +14 -0
- package/package.json +50 -0
- package/standalone/currentTimeEntry.js +12 -0
- package/standalone/getWorkspaces.js +11 -0
- package/standalone/listProjects.js +21 -0
- package/standalone/startTimeEntry.js +47 -0
- package/standalone/stopCurrentTimeEntry.js +21 -0
- package/standalone/todayReport.js +101 -0
- package/standalone/weeklyReport.js +57 -0
- package/utils.js +133 -0
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# To get started with Dependabot version updates, you'll need to specify which
|
|
2
|
+
# package ecosystems to update and where the package manifests are located.
|
|
3
|
+
# Please see the documentation for all configuration options:
|
|
4
|
+
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
|
5
|
+
|
|
6
|
+
version: 2
|
|
7
|
+
updates:
|
|
8
|
+
- package-ecosystem: "npm" # See documentation for possible values
|
|
9
|
+
directory: "/" # Location of package manifests
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: Node.js CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
strategy:
|
|
16
|
+
matrix:
|
|
17
|
+
node-version: [16.x]
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v3
|
|
21
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
22
|
+
uses: actions/setup-node@v3
|
|
23
|
+
with:
|
|
24
|
+
node-version: ${{ matrix.node-version }}
|
|
25
|
+
- run: npm ci
|
|
26
|
+
- run: npm run build --if-present
|
|
27
|
+
- run: npm test
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: 'Publish to NPM'
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_run:
|
|
5
|
+
workflows: ['Node.js CI']
|
|
6
|
+
types: [completed]
|
|
7
|
+
branches: [master,main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
publish-new-version:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v3
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: '0'
|
|
17
|
+
- name: git setup
|
|
18
|
+
run: |
|
|
19
|
+
git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
|
20
|
+
git config --local user.name "github-actions[bot]"
|
|
21
|
+
- name: setup node
|
|
22
|
+
uses: actions/setup-node@v3
|
|
23
|
+
with:
|
|
24
|
+
node-version: 16.x
|
|
25
|
+
registry-url: 'https://registry.npmjs.org'
|
|
26
|
+
- name: npm install
|
|
27
|
+
run: npm ci
|
|
28
|
+
|
|
29
|
+
- name: Should release
|
|
30
|
+
id: should_release
|
|
31
|
+
continue-on-error: true
|
|
32
|
+
run: npm run should-release -- -v
|
|
33
|
+
|
|
34
|
+
- name: No release
|
|
35
|
+
if: steps.should_release.outcome != 'success'
|
|
36
|
+
run: echo "No release required. Skipping publishing."
|
|
37
|
+
|
|
38
|
+
- name: Version bump
|
|
39
|
+
if: steps.should_release.outcome == 'success'
|
|
40
|
+
run: npm run release
|
|
41
|
+
|
|
42
|
+
- name: Publish to NPM
|
|
43
|
+
if: steps.should_release.outcome == 'success'
|
|
44
|
+
run: npm publish
|
|
45
|
+
env:
|
|
46
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
47
|
+
|
|
48
|
+
- name: Push commits to GitHub
|
|
49
|
+
if: steps.should_release.outcome == 'success'
|
|
50
|
+
uses: ad-m/github-push-action@master
|
|
51
|
+
with:
|
|
52
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
53
|
+
branch: ${{ github.ref }}
|
|
54
|
+
tags: true
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
+
|
|
5
|
+
### [0.10.5](https://github.com/beauraines/toggl-cli-node/compare/v0.10.4...v0.10.5) (2023-03-04)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* makes weekly report work in v9 API ([#42](https://github.com/beauraines/toggl-cli-node/issues/42)) ([5de62c8](https://github.com/beauraines/toggl-cli-node/commit/5de62c833bb05840efadd3065ca60ce4c1bb6d1b))
|
|
11
|
+
|
|
12
|
+
### [0.10.4](https://github.com/beauraines/toggl-cli-node/compare/v0.10.3...v0.10.4) (2023-03-02)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* Cannot edit time entries ([ec6611a](https://github.com/beauraines/toggl-cli-node/commit/ec6611aee147ff0355af664ee7bf05aa6b2c4f38)), closes [#40](https://github.com/beauraines/toggl-cli-node/issues/40)
|
|
18
|
+
|
|
19
|
+
### [0.10.3](https://github.com/beauraines/toggl-cli-node/compare/v0.10.2...v0.10.3) (2023-03-02)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* converts project id, workspace id to number ([8cefe8c](https://github.com/beauraines/toggl-cli-node/commit/8cefe8cce9d8aa7651171e3a35d69ee9ba278950)), closes [#38](https://github.com/beauraines/toggl-cli-node/issues/38)
|
|
25
|
+
|
|
26
|
+
### 0.10.2 (2023-02-28)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
* adds Me command ([#1](https://github.com/beauraines/toggl-cli-node/issues/1)) ([6e656e8](https://github.com/beauraines/toggl-cli-node/commit/6e656e8468f181f43f27880372d18ae67e7e677f))
|
|
32
|
+
|
|
33
|
+
### 0.10.1 (2023-02-28)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Features
|
|
37
|
+
|
|
38
|
+
* adds Me command ([#1](https://github.com/beauraines/toggl-cli-node/issues/1)) ([6e656e8](https://github.com/beauraines/toggl-cli-node/commit/6e656e8468f181f43f27880372d18ae67e7e677f))
|
package/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# toggl-cli-node
|
|
2
|
+
|
|
3
|
+
**UPDATED TO USE Toggl v9 API**
|
|
4
|
+
|
|
5
|
+
A command line interface for [toggl](https://toggl.com) written in node, based on the python [TogglCli](https://github.com/AuHau/toggl-cli) project. Attempting to use similar syntax.
|
|
6
|
+
|
|
7
|
+
This was made possible because [saintedlama](https://github.com/saintedlama) had already built a node API for toggl.
|
|
8
|
+
|
|
9
|
+
## Configuration
|
|
10
|
+
|
|
11
|
+
1. Configure your environment, with environment variables or a `.env` file in the project root. Eventually, these will be read from a config file as an alternative
|
|
12
|
+
1. `TOGGL_API_TOKEN` (required)
|
|
13
|
+
2. `TOGGL_DEFAULT_WORKSPACE_ID` (required)
|
|
14
|
+
3. `TOGGL_DEFAULT_PROJECT_ID` (optional)
|
|
15
|
+
4. `TOGGL_TIMEZONE=America/Los_Angeles` (defaults to `America/New_York`)
|
|
16
|
+
## Dependencies
|
|
17
|
+
|
|
18
|
+
1. toggl-client - Used from [saintedlama/toggl-client](https://github.com/saintedlama/toggl-client) repository as the latest code hasn't been released to npm
|
|
19
|
+
3. dotenv
|
|
20
|
+
4. yargs
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
| Feature | Available | Comments |
|
|
27
|
+
| ------------------------------------ | --------- | ----------------------------------------------------------- |
|
|
28
|
+
| Start time entry | ✅ | |
|
|
29
|
+
| Start time entry with description | ✅ | |
|
|
30
|
+
| Start time entry with project | ✅ | |
|
|
31
|
+
| stop time entry | ✅ | |
|
|
32
|
+
| Continue named time entry | ✅ | |
|
|
33
|
+
| Report today by project | ✅ | |
|
|
34
|
+
| Report this week by project by day | ✅ | |
|
|
35
|
+
| Edit time entry | ✅ | |
|
|
36
|
+
| Use config from file | | |
|
|
37
|
+
| Save config to file | | |
|
|
38
|
+
| Refactor: Display and format modules | | |
|
|
39
|
+
| Client: reset PAT | | |
|
|
40
|
+
| Client: other user feature? | | |
|
|
41
|
+
| Client: specify client name | | |
|
|
42
|
+
| Colorize output | | |
|
|
43
|
+
| Better table output | | |
|
|
44
|
+
| List recent time entries | ✅ | |
|
|
45
|
+
| Command line completion | ✅ | [#6](https://github.com/beauraines/toggl-cli-node/issues/6) |
|
|
46
|
+
|
|
47
|
+
## Development Road Map
|
|
48
|
+
|
|
49
|
+
Priority order... I think.
|
|
50
|
+
|
|
51
|
+
1. ~today - improve the output~
|
|
52
|
+
2. ~weekly - improve the output format~
|
|
53
|
+
3. ~now - format the display time entry output~
|
|
54
|
+
4. ~toggl continue~
|
|
55
|
+
5. ~duration to display helper function?~
|
|
56
|
+
6. read configuration (token, default workspace) from files (client and utils)
|
|
57
|
+
7. now - update running description, project, start time
|
|
58
|
+
8. now - update start time with overlap detection and adjust prior time entry
|
|
59
|
+
9. project list - format the output, group by client
|
|
60
|
+
10. Add ability to lookup project by name not just id
|
|
61
|
+
11. start - add project name to output, add time started
|
|
62
|
+
12. toggl workspace add - this command is not yet supported.
|
|
63
|
+
13. toggl workspace list - improve output
|
|
64
|
+
14. colorized output
|
|
65
|
+
15. config - some way to save /me information to a file
|
|
66
|
+
1. default workspace?
|
|
67
|
+
2. API key?
|
|
68
|
+
3. display formats?
|
|
69
|
+
16. toggl project add - this command is not yet supported.
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
## Planned Limitations
|
|
74
|
+
|
|
75
|
+
There are several features that I do not use from Toggl, so including them is a priority for me. I'm not opposed to them being in there and would welcome collaboration to include them.
|
|
76
|
+
|
|
77
|
+
1. tags
|
|
78
|
+
2. multiple workspaces
|
|
79
|
+
3. billable
|
|
80
|
+
|
|
81
|
+
## How to Contribute
|
|
82
|
+
|
|
83
|
+
I need to clean up and refactor the code and establish a few more helper and utility functions, but I'm open to contributions!
|
|
84
|
+
|
|
85
|
+
|
package/cli.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import yargs from 'yargs'
|
|
4
|
+
import { hideBin } from 'yargs/helpers'
|
|
5
|
+
import { commands } from './cmds/index.mjs'
|
|
6
|
+
|
|
7
|
+
yargs(hideBin(process.argv))
|
|
8
|
+
.scriptName('toggl')
|
|
9
|
+
.command(commands)
|
|
10
|
+
.completion('completion', 'Outputs bash/zsh-completion shortcuts for commands and options to add to .bashrc or .bash_profile')
|
|
11
|
+
.demandCommand()
|
|
12
|
+
.help()
|
|
13
|
+
.parse()
|
package/client.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import dotenv from 'dotenv'
|
|
2
|
+
import togglClient from 'toggl-client'
|
|
3
|
+
dotenv.config()
|
|
4
|
+
|
|
5
|
+
export default function () {
|
|
6
|
+
if (!process.env.TOGGL_API_TOKEN) {
|
|
7
|
+
console.log('TOGGL_API_TOKEN environment variable is not set.')
|
|
8
|
+
console.log('For development, it can be set in the .env file in the project root')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// TODO Try to read rc file
|
|
12
|
+
|
|
13
|
+
// FIXME apiToken is not needed
|
|
14
|
+
const apiToken = process.env.TOGGL_API_TOKEN
|
|
15
|
+
const client = togglClient()
|
|
16
|
+
// const client = togglClient({ apiToken });
|
|
17
|
+
// ? Why doesn't a try/catch block work?
|
|
18
|
+
// try {
|
|
19
|
+
// const client = togglClient({ apiToken });
|
|
20
|
+
// } catch (error) {
|
|
21
|
+
// console.error(error);
|
|
22
|
+
// }
|
|
23
|
+
if (!client) {
|
|
24
|
+
console.error('There was a problem')
|
|
25
|
+
process.exit(1)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return client
|
|
29
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import Client from '../client.js'
|
|
2
|
+
import { createTimeEntry, getProjectById } from '../utils.js'
|
|
3
|
+
import dayjs from 'dayjs'
|
|
4
|
+
|
|
5
|
+
export const command = 'continue [description]'
|
|
6
|
+
|
|
7
|
+
export const desc = 'Continues an existing time entry. If description is included it will search for the most' +
|
|
8
|
+
'recent entry including that description. If no description is provided, the most recent entry will be restarted.'
|
|
9
|
+
|
|
10
|
+
export const builder = {
|
|
11
|
+
description: {
|
|
12
|
+
describe: 'The time entry to continue',
|
|
13
|
+
type: 'string'
|
|
14
|
+
}
|
|
15
|
+
// -s, --start DATETIME Sets a start time.
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const handler = async function (argv) {
|
|
19
|
+
const client = Client()
|
|
20
|
+
const timeEntries = await client.timeEntries.list(
|
|
21
|
+
{
|
|
22
|
+
start_date: dayjs().subtract(14, 'days').toISOString(),
|
|
23
|
+
end_date: dayjs().toISOString()
|
|
24
|
+
}
|
|
25
|
+
) // Gets time entries for last 14 days, up to 1000 entries
|
|
26
|
+
|
|
27
|
+
timeEntries.sort((a, b) => dayjs(a.start).toDate() - dayjs(b.start).toDate())
|
|
28
|
+
|
|
29
|
+
let matchingTimeEntry
|
|
30
|
+
switch (argv.description) {
|
|
31
|
+
case undefined:
|
|
32
|
+
matchingTimeEntry = timeEntries.slice(-1)[0]
|
|
33
|
+
break
|
|
34
|
+
default:
|
|
35
|
+
{ const searchName = argv.description.toLowerCase()
|
|
36
|
+
matchingTimeEntry = timeEntries.find(x => x.description.toLowerCase().includes(searchName)) }
|
|
37
|
+
break
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const params = {
|
|
41
|
+
projectId: matchingTimeEntry?.pid,
|
|
42
|
+
workspaceId: matchingTimeEntry?.wid,
|
|
43
|
+
description: matchingTimeEntry?.description || 'no description',
|
|
44
|
+
billable: matchingTimeEntry?.billable,
|
|
45
|
+
dur: matchingTimeEntry?.dur
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (matchingTimeEntry) {
|
|
49
|
+
const timeEntry = await createTimeEntry(params)
|
|
50
|
+
const project = await getProjectById(timeEntry.wid, timeEntry.pid)
|
|
51
|
+
console.info(`Continued ${timeEntry?.description} for project ${project?.name}`)
|
|
52
|
+
} else {
|
|
53
|
+
console.info('No matching time entry found!')
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import Client from '../client.js'
|
|
2
|
+
import { displayTimeEntry } from '../utils.js'
|
|
3
|
+
|
|
4
|
+
export const command = 'now'
|
|
5
|
+
export const desc = 'Displays the current running time entry'
|
|
6
|
+
export const builder = {}
|
|
7
|
+
|
|
8
|
+
export const handler = async function (argv) {
|
|
9
|
+
const client = new Client()
|
|
10
|
+
const currentTimeEntry = await client.timeEntries.current()
|
|
11
|
+
if (currentTimeEntry) {
|
|
12
|
+
await displayTimeEntry(currentTimeEntry)
|
|
13
|
+
} else {
|
|
14
|
+
console.log('There is no time entry running!')
|
|
15
|
+
}
|
|
16
|
+
}
|
package/cmds/edit.mjs
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import Client from '../client.js'
|
|
2
|
+
import { defaultWorkspaceId, getProjectByName, getProjectById, appName, displayTimeEntry } from '../utils.js'
|
|
3
|
+
import dayjs from 'dayjs'
|
|
4
|
+
import utc from 'dayjs/plugin/utc.js'
|
|
5
|
+
import timezone from 'dayjs/plugin/timezone.js'
|
|
6
|
+
import yargs from 'yargs'
|
|
7
|
+
dayjs.extend(utc)
|
|
8
|
+
dayjs.extend(timezone)
|
|
9
|
+
|
|
10
|
+
export const command = 'edit'
|
|
11
|
+
// FIXME editing not working
|
|
12
|
+
export const desc = 'SOMETHING IS NOT RIGHT Edits the current running time entry'
|
|
13
|
+
|
|
14
|
+
export const builder = {
|
|
15
|
+
d: { alias: ['description'], describe: 'Time entry name', type: 'string:' },
|
|
16
|
+
p: { alias: ['projectId', 'project'], describe: 'The case insensitive project name or project id.', type: 'string', demandOption: false },
|
|
17
|
+
s: { alias: ['start', 'startTime'], describe: 'The case insensitive project name or project id.', type: 'string', demandOption: false },
|
|
18
|
+
e: { alias: ['end', 'endTime'], describe: 'The case insensitive project name or project id.', type: 'string', demandOption: false }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const handler = async function (argv) {
|
|
22
|
+
if (!(argv.d || argv.p || argv.s || argv.e)) {
|
|
23
|
+
console.error('At least one option must be provided, description, project, start or end')
|
|
24
|
+
yargs().help()
|
|
25
|
+
yargs().exit(1, new Error('At least one option must be provided, description, project, start or end'))
|
|
26
|
+
}
|
|
27
|
+
const client = new Client()
|
|
28
|
+
const currentTimeEntry = await client.timeEntries.current()
|
|
29
|
+
|
|
30
|
+
const params = {}
|
|
31
|
+
|
|
32
|
+
params.workspace_id = +defaultWorkspaceId
|
|
33
|
+
let project
|
|
34
|
+
if (argv.projectId) {
|
|
35
|
+
if (isNaN(argv.projectId)) {
|
|
36
|
+
project = await getProjectByName(params.workspace_id, argv.projectId)
|
|
37
|
+
} else {
|
|
38
|
+
project = await getProjectById(params.workspace_id, argv.projectId)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
let startTime, endTime
|
|
43
|
+
if (dayjs(argv.startTime).isValid()) {
|
|
44
|
+
startTime = argv.startTime
|
|
45
|
+
} else {
|
|
46
|
+
// Parse the time and set it based upon the current time
|
|
47
|
+
startTime = parseTime(argv.startTime)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (dayjs(argv.endTime).isValid()) {
|
|
51
|
+
endTime = argv.endTime
|
|
52
|
+
} else {
|
|
53
|
+
// Parse the time and set it based upon the current time
|
|
54
|
+
endTime = parseTime(argv.endTime)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
params.created_with = appName
|
|
58
|
+
params.at = dayjs().toISOString()
|
|
59
|
+
project ? params.project_id = +project.id : undefined
|
|
60
|
+
startTime ? params.start = startTime.toISOString() : undefined
|
|
61
|
+
endTime ? params.stop = endTime.toISOString() : undefined
|
|
62
|
+
endTime ? params.duration = endTime.diff(startTime, 'seconds') : undefined
|
|
63
|
+
argv.description ? params.description = argv.description : undefined
|
|
64
|
+
const timeEntry = await client.timeEntries.update(currentTimeEntry.id, params)
|
|
65
|
+
await displayTimeEntry(timeEntry)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Parses a timelike string into a dayjs object of the current date and that time
|
|
70
|
+
* @param {string} timeString timelike string e.g. 4:50PM '12:00 AM' etc.
|
|
71
|
+
* @returns {object} dayjs object
|
|
72
|
+
*/
|
|
73
|
+
function parseTime (timeString) {
|
|
74
|
+
let h, m
|
|
75
|
+
// Assumes time in format 4:50 PM
|
|
76
|
+
const time = timeString.split(':', 2)
|
|
77
|
+
h = time[0]
|
|
78
|
+
m = time[1].match(/[0-9]+/)[0]
|
|
79
|
+
if (timeString.match(/PM/i) && h <= 12) {
|
|
80
|
+
// + in front of string converts to a number, cool!
|
|
81
|
+
h = +h + 12
|
|
82
|
+
}
|
|
83
|
+
return dayjs().hour(h).minute(m).second(0)
|
|
84
|
+
}
|
package/cmds/index.mjs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import * as ls from './ls.mjs'
|
|
2
|
+
import * as me from './me.mjs'
|
|
3
|
+
import * as continueEntry from './continue.mjs'
|
|
4
|
+
import * as current from './currentTimeEntry.mjs'
|
|
5
|
+
import * as workspace from './workspace.mjs'
|
|
6
|
+
import * as projects from './projects.mjs'
|
|
7
|
+
import * as edit from './edit.mjs'
|
|
8
|
+
import * as web from './web.mjs'
|
|
9
|
+
import * as startTimeEntry from './startTimeEntry.mjs'
|
|
10
|
+
import * as stopTimeEntry from './stopTimeEntry.mjs'
|
|
11
|
+
import * as today from './today.mjs'
|
|
12
|
+
import * as weekly from './weekly.mjs'
|
|
13
|
+
export const commands = [
|
|
14
|
+
continueEntry,
|
|
15
|
+
current,
|
|
16
|
+
edit,
|
|
17
|
+
ls,
|
|
18
|
+
me,
|
|
19
|
+
projects,
|
|
20
|
+
startTimeEntry,
|
|
21
|
+
stopTimeEntry,
|
|
22
|
+
today,
|
|
23
|
+
web,
|
|
24
|
+
weekly,
|
|
25
|
+
workspace
|
|
26
|
+
]
|
package/cmds/ls.mjs
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import Client from '../client.js'
|
|
2
|
+
import { convertUtcTime, formatDuration } from '../utils.js'
|
|
3
|
+
import dayjs from 'dayjs'
|
|
4
|
+
|
|
5
|
+
export const command = 'ls'
|
|
6
|
+
export const desc = 'Lists time entries'
|
|
7
|
+
|
|
8
|
+
export const builder = {
|
|
9
|
+
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const handler = async function (argv) {
|
|
13
|
+
const client = Client()
|
|
14
|
+
// TODO update these dates
|
|
15
|
+
const timeEntries = await client.timeEntries.list({start_date:dayjs().subtract(14,'days').toISOString(),end_date:dayjs().toISOString()});
|
|
16
|
+
|
|
17
|
+
const report = []
|
|
18
|
+
timeEntries.forEach(element => {
|
|
19
|
+
report.push(
|
|
20
|
+
{
|
|
21
|
+
description: element.description,
|
|
22
|
+
start: convertUtcTime(element.start),
|
|
23
|
+
stop: convertUtcTime(element.stop),
|
|
24
|
+
duration: formatDuration(element.duration * 1000)
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
console.table(report, ['description', 'start', 'stop', 'duration'])
|
|
30
|
+
}
|
package/cmds/ls.test.js
ADDED
package/cmds/me.mjs
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import dayjs from 'dayjs'
|
|
2
|
+
import Client from '../client.js'
|
|
3
|
+
|
|
4
|
+
export const command = 'me'
|
|
5
|
+
export const desc = 'Displays the current user'
|
|
6
|
+
export const builder = {}
|
|
7
|
+
|
|
8
|
+
export const handler = async function (argv) {
|
|
9
|
+
const client = Client()
|
|
10
|
+
const currentUser = await client.user.current()
|
|
11
|
+
// console.log(currentUser);
|
|
12
|
+
|
|
13
|
+
console.log(`Default workspace: ${currentUser.default_workspace_id}`)
|
|
14
|
+
console.log(`API Token: ${currentUser.api_token}`)
|
|
15
|
+
console.log(`Fullname: ${currentUser.fullname}`)
|
|
16
|
+
console.log(`Timezone: ${currentUser.timezone}`)
|
|
17
|
+
// TODO These look like they come from the `/preferences` endpoint
|
|
18
|
+
// console.log(`Date format: ${currentUser.date_format}`);
|
|
19
|
+
// console.log(`jquery date format: ${currentUser.jquery_date_format}`);
|
|
20
|
+
// console.log(`jquery_timeofday_format: ${currentUser.jquery_timeofday_format}`);
|
|
21
|
+
|
|
22
|
+
const weekStart = dayjs().day(currentUser.beginning_of_week).format('dddd')
|
|
23
|
+
console.log(`beginning_of_week: ${weekStart}`)
|
|
24
|
+
|
|
25
|
+
// Api token:
|
|
26
|
+
// Beginning of week: Sunday
|
|
27
|
+
// Date format: MM/DD/YYYY
|
|
28
|
+
// Default workspace: beau.raines's workspace (#403916)
|
|
29
|
+
// Fullname: Beau Raines
|
|
30
|
+
// Image url: https://assets.track.toggl.com/avatars/913dfa76c01d730711eb00a1c3e6d57c.jpg
|
|
31
|
+
// Language: en_US
|
|
32
|
+
// Send timer notifications: True
|
|
33
|
+
// Timeofday format: 12-hour
|
|
34
|
+
// Timezone: America/Los_Angeles
|
|
35
|
+
// Workspace: beau.raines's workspace (#403916)
|
|
36
|
+
|
|
37
|
+
const workspaces = (await client.workspaces.list()).map(w => w.name).join(', ')
|
|
38
|
+
console.log('')
|
|
39
|
+
console.log(`Workspaces: ${workspaces}`)
|
|
40
|
+
// FIXME since is no longer in the response
|
|
41
|
+
const since = dayjs.unix(currentUser.since)
|
|
42
|
+
console.log('')
|
|
43
|
+
console.log(`Toggl user since ${since}`)
|
|
44
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import Client from '../../client.js'
|
|
2
|
+
|
|
3
|
+
export const command = 'list'
|
|
4
|
+
export const desc = 'Lists active projects from the current workspace'
|
|
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
|
+
|
|
14
|
+
const workspace = workspaces[0]
|
|
15
|
+
|
|
16
|
+
console.info('Workspace: ' + workspace.name)
|
|
17
|
+
console.info('id: ' + workspace.id)
|
|
18
|
+
|
|
19
|
+
const projects = await client.workspaces.projects(workspace.id)
|
|
20
|
+
|
|
21
|
+
const activeProjects = projects.filter(x => x.active)
|
|
22
|
+
console.info(`Found ${activeProjects.length} projects`)
|
|
23
|
+
activeProjects.map(p => { console.log(p.name + ' ' + p.id) })
|
|
24
|
+
|
|
25
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { projects } from './projects/index.mjs'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export const command = 'project <command>'
|
|
5
|
+
export const desc = 'Manage projects'
|
|
6
|
+
|
|
7
|
+
export const builder = function (yargs) {
|
|
8
|
+
return yargs.command(projects)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const handler = function (argv) {}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defaultWorkspaceId,getProjectByName, createTimeEntry, getProjectById, defaultProjectId } from '../utils.js'
|
|
2
|
+
|
|
3
|
+
export const command = 'start'
|
|
4
|
+
export const desc = 'Starts a time entry'
|
|
5
|
+
|
|
6
|
+
export const builder = {
|
|
7
|
+
description: {
|
|
8
|
+
describe: 'Time entry name',
|
|
9
|
+
type: 'string:'
|
|
10
|
+
},
|
|
11
|
+
p: { alias: ['projectId', 'project'], describe: 'The case insensitive project name or project id.', type: 'string', demandOption: false },
|
|
12
|
+
// TODO default to default workspace
|
|
13
|
+
w: { alias: ['workspaceId', 'workspace'], describe: 'The case insensitive workspace name or workspace id.', type: 'number', demandOption: false }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const handler = async function (argv) {
|
|
17
|
+
// console.info(`${argv.$0} ${argv._.join(' ')} - this command is not yet supported.`);
|
|
18
|
+
// console.debug(argv);
|
|
19
|
+
// TODO validate options
|
|
20
|
+
// TODO check that description was provided or provide a default
|
|
21
|
+
const params = {}
|
|
22
|
+
params.description = argv.description || argv._.slice(1).join(' ') || 'no description'
|
|
23
|
+
// TODO lookup workspace
|
|
24
|
+
params.workspaceId = defaultWorkspaceId
|
|
25
|
+
let project
|
|
26
|
+
if (argv.projectId) {
|
|
27
|
+
if (isNaN(argv.projectId)) {
|
|
28
|
+
project = await getProjectByName(params.workspaceId, argv.projectId)
|
|
29
|
+
} else {
|
|
30
|
+
project = await getProjectById(params.workspaceId, argv.projectId)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
params.projectId = project?.id || defaultProjectId || null
|
|
35
|
+
// TODO check for invalid projectId or catch the error when creating fails
|
|
36
|
+
const timeEntry = await createTimeEntry(params)
|
|
37
|
+
console.info(`Started ${timeEntry?.description} ${project?.name ? `for project ${project.name}` : 'without a project'}`)
|
|
38
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import Client from '../client.js'
|
|
2
|
+
import dayjs from 'dayjs'
|
|
3
|
+
|
|
4
|
+
export const command = 'stop'
|
|
5
|
+
export const desc = 'Stops the current running time entry'
|
|
6
|
+
export const builder = {}
|
|
7
|
+
|
|
8
|
+
export const handler = async function (argv) {
|
|
9
|
+
const client = Client()
|
|
10
|
+
const currentTimeEntry = await client.timeEntries.current()
|
|
11
|
+
if (currentTimeEntry) {
|
|
12
|
+
const stopped = await client.timeEntries.stop(currentTimeEntry)
|
|
13
|
+
const duration = dayjs.duration(stopped.duration * 1000).format('H[h] m[m]')
|
|
14
|
+
console.log(`Stopped ${stopped.description} after ${duration}`)
|
|
15
|
+
} else {
|
|
16
|
+
console.log('There is no time entry running!')
|
|
17
|
+
}
|
|
18
|
+
}
|