+137
| # Tracklify CLI — Local Debug Guide | ||
| Run and test the Tracklify CLI locally (without compiling or publishing to npm). This guide also explains how the CLI stores your personal token separately for local vs production use. | ||
| ## Prerequisites | ||
| - Node.js 16+ installed | ||
| - Tracklify backend reachable: | ||
| - Production: https://ws1.tracklify.com (default) | ||
| - Local dev: http://tracklify.localhost:3102 (requires your local stack running) | ||
| - A CLI secret token from Tracklify: | ||
| - Production UI: https://web.tracklify.com/get-cli/ | ||
| - Local UI: http://web.tracklify.localhost:8080/get-cli/ | ||
| ## Project layout | ||
| This folder (`npm-cli`) contains the CLI entry `tracklify.js`. The `package.json` already maps the binary name `tracklify` to this file. | ||
| ``` | ||
| npm-cli/ | ||
| ├─ tracklify.js # CLI entry point | ||
| └─ package.json # exposes `tracklify` bin | ||
| ``` | ||
| ## How secrets are stored | ||
| The CLI keeps your secret in your home directory under `~/.tracklify/`: | ||
| - Production (default): `~/.tracklify/key` | ||
| - Local dev (`--local`): `~/.tracklify/key_local` | ||
| This separation lets you use different tokens for production and local stacks without overwriting each other. | ||
| ## Run locally without publishing | ||
| You have two easy options. | ||
| ### Option A — Run directly with Node | ||
| From the repo root: | ||
| ```bash | ||
| # Show help | ||
| node npm-cli/tracklify.js --help | ||
| # Log in (prod) | ||
| node npm-cli/tracklify.js login <SECRET> | ||
| # Log in (local) — stores token in ~/.tracklify/key_local and uses local API | ||
| node npm-cli/tracklify.js login <SECRET> --local | ||
| # Print current-task branch name (prod) | ||
| node npm-cli/tracklify.js branch | ||
| # Print current-task branch name (local) | ||
| node npm-cli/tracklify.js branch --local | ||
| # Print current-task commit suffix (prod) | ||
| node npm-cli/tracklify.js task | ||
| # Print current-task commit suffix (local) | ||
| node npm-cli/tracklify.js task --local | ||
| ``` | ||
| ### Option B — Use npm link (optional) | ||
| If you prefer to use the `tracklify` command directly without `node` prefix: | ||
| ```bash | ||
| cd npm-cli | ||
| npm link | ||
| # Then anywhere on your machine you can run: | ||
| tracklify --help | ||
| tracklify login <SECRET> | ||
| tracklify login <SECRET> --local | ||
| tracklify branch | ||
| tracklify branch --local | ||
| tracklify task | ||
| tracklify task --local | ||
| ``` | ||
| To remove the link later: | ||
| ```bash | ||
| cd npm-cli | ||
| npm unlink -g | ||
| ``` | ||
| ## Commands and behavior | ||
| - `login <secret> [--local]` | ||
| - Saves your token to `~/.tracklify/key` (prod) or `~/.tracklify/key_local` (local). | ||
| - If `--local` is provided, subsequent commands with `--local` talk to `http://tracklify.localhost:3102`. | ||
| - `branch [--local]` | ||
| - Prints the suggested Git branch for your currently tracked task. | ||
| - `task [--local]` | ||
| - Prints the `<project>/<taskHid>/<taskSlug>` suffix for commit messages. | ||
| ## Examples | ||
| ```bash | ||
| # Create a new branch for the current task (prod) | ||
| git checkout -b $(node npm-cli/tracklify.js branch) | ||
| # Same, but against your local Tracklify stack | ||
| git checkout -b $(node npm-cli/tracklify.js branch --local) | ||
| # Add task suffix to commit message (prod) | ||
| git commit -m "my text $(node npm-cli/tracklify.js task)" | ||
| # Local stack variant | ||
| git commit -m "my text $(node npm-cli/tracklify.js task --local)" | ||
| ``` | ||
| ## Where do I get the secret? | ||
| - Production: open the Tracklify web app → user menu → "Get CLI" → copy your token. | ||
| - Local dev: open http://web.tracklify.localhost:8080/get-cli/ and copy the local token. | ||
| Regenerating the token in the web UI will require you to run the CLI `login` again. | ||
| ## Troubleshooting | ||
| - Not logged in / bad token | ||
| - The CLI prints a helpful message and exits with code `2`. | ||
| - Run `login` again with the correct token (remember to include `--local` if you’re testing locally). | ||
| - No active task | ||
| - The CLI exits with code `3` and tells you you’re not tracking any task. Start tracking a task in Tracklify and retry. | ||
| - Other errors | ||
| - The CLI exits with code `1` and prints the error message. | ||
| ## Notes | ||
| - `--local` switches both the API base URL and the secret file (`key_local`). | ||
| - For backward compatibility, if `--local` is used but `key_local` isn’t present, the CLI will try falling back to `key` when reading the token. | ||
| - Secrets are stored with `0600` permissions under a `0700` directory for safety. |
+1
-1
| { | ||
| "name": "tracklify", | ||
| "version": "1.0.6", | ||
| "version": "1.0.7", | ||
| "description": "Tracklify CLI integration tool (get active task)", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
+1
-1
@@ -1,1 +0,1 @@ | ||
| NPM_TOKEN=`cat ~/.npm_tracklify` npm version patch && npm publish --access public. | ||
| npm version patch && npm publish --access public. |
+195
-46
| #!/usr/bin/env node | ||
| // Tracklify CLI | ||
| // Commands: | ||
| // - login <secret> [--local]: stores secret into ~/.tracklify/key (prod) or ~/.tracklify/key_local (local) | ||
| // - branch [--local]: prints branch name for current task | ||
| // - task [--local]: prints project/task suffix for commit message | ||
| const fs = require('fs'); | ||
| const os = require('os'); | ||
| const path = require('path'); | ||
| const http = require('http'); | ||
| const PC_INTEGRATIONS_PORT = 59600; | ||
| const cmd = process.argv[2]; | ||
| const https = require('https'); | ||
| function doComm(url, callback) { | ||
| http.get({ | ||
| hostname: '127.0.0.1', | ||
| port: PC_INTEGRATIONS_PORT, | ||
| path: url, | ||
| agent: false // Create a new agent just for this one request | ||
| }, (resp) => { | ||
| let data = ''; | ||
| // A chunk of data has been recieved. | ||
| resp.on('data', (chunk) => { | ||
| data += chunk; | ||
| }); | ||
| // The whole response has been received. Print out the result. | ||
| resp.on('end', () => { | ||
| console.log(data); | ||
| process.exit(0); | ||
| }); | ||
| }).on("error", (err) => { | ||
| // err.message | ||
| console.error("Error: can't get active task, is tracking started? "); | ||
| process.exit(-1); | ||
| }); | ||
| const REMOTE_BASE = 'https://ws1.tracklify.com'; | ||
| const LOCAL_BASE = 'http://tracklify.localhost:3102'; | ||
| const PROD_GET_CLI_URL = 'https://web.tracklify.com/get-cli/'; | ||
| const LOCAL_GET_CLI_URL = 'http://web.tracklify.localhost:8080/get-cli/'; | ||
| const KEY_DIR = path.join(os.homedir(), '.tracklify'); | ||
| const KEY_FILE = path.join(KEY_DIR, 'key'); | ||
| const KEY_LOCAL_FILE = path.join(KEY_DIR, 'key_local'); | ||
| function printUsage() { | ||
| console.log('Usage: tracklify <command> [options]'); | ||
| console.log('Commands:'); | ||
| console.log(' login <secret> [--local] Store your personal CLI secret'); | ||
| console.log(' branch [--local] Print branch name for current task'); | ||
| console.log(' task [--local] Print commit suffix for current task'); | ||
| console.log(''); | ||
| console.log('Secrets are stored in:'); | ||
| console.log(' Prod (default): ~/.tracklify/key'); | ||
| console.log(' Local (--local): ~/.tracklify/key_local'); | ||
| } | ||
| const callers = { | ||
| 'task': { | ||
| call: function () { | ||
| doComm('/taskUrl'); | ||
| }, | ||
| description: 'Get current tracked task URL 🔗', | ||
| }, | ||
| 'taskname': { | ||
| call: function () { | ||
| doComm('/taskName'); | ||
| }, | ||
| description: 'Get current tracked task name ✒', | ||
| }, | ||
| function parseArgs(argv) { | ||
| const args = argv.slice(2); | ||
| const flags = new Set(args.filter(a => a.startsWith('--'))); | ||
| const positional = args.filter(a => !a.startsWith('--')); | ||
| return { args: positional, flags }; | ||
| } | ||
| if (!Object.keys(callers).includes(cmd)) { | ||
| let e = 'Wrong argument 🤷♂️ Supported commands:\n'; | ||
| Object.keys(callers).forEach((el) => { | ||
| e += `${el.padStart(10, ' ')} - ${callers[el].description}\n`; | ||
| }) | ||
| console.error(e); | ||
| process.exit(-1); | ||
| function readSecret(useLocal) { | ||
| try { | ||
| const file = useLocal ? KEY_LOCAL_FILE : KEY_FILE; | ||
| const data = fs.readFileSync(file, 'utf8').trim(); | ||
| return data; | ||
| } catch (e) { | ||
| // Backward compatibility: if local requested but key_local doesn't exist, try prod key | ||
| if (useLocal) { | ||
| try { | ||
| const fallback = fs.readFileSync(KEY_FILE, 'utf8').trim(); | ||
| return fallback; | ||
| } catch (_) {} | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
| callers[cmd].call(); | ||
| function writeSecret(secret, useLocal) { | ||
| if (!fs.existsSync(KEY_DIR)) fs.mkdirSync(KEY_DIR, { recursive: true, mode: 0o700 }); | ||
| const file = useLocal ? KEY_LOCAL_FILE : KEY_FILE; | ||
| fs.writeFileSync(file, secret + '\n', { mode: 0o600 }); | ||
| } | ||
| function postJSON(base, pathName, payload) { | ||
| const url = new URL(pathName, base); | ||
| const isHttps = url.protocol === 'https:'; | ||
| const data = JSON.stringify(payload || {}); | ||
| const options = { | ||
| method: 'POST', | ||
| hostname: url.hostname, | ||
| port: url.port || (isHttps ? 443 : 80), | ||
| path: url.pathname, | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'Content-Length': Buffer.byteLength(data), | ||
| }, | ||
| }; | ||
| const client = isHttps ? https : http; | ||
| return new Promise((resolve, reject) => { | ||
| const req = client.request(options, (res) => { | ||
| let body = ''; | ||
| res.setEncoding('utf8'); | ||
| res.on('data', chunk => body += chunk); | ||
| res.on('end', () => { | ||
| try { | ||
| const json = JSON.parse(body || '{}'); | ||
| if (res.statusCode >= 200 && res.statusCode < 300) { | ||
| resolve(json); | ||
| } else { | ||
| const err = new Error(json.error || `HTTP ${res.statusCode}`); | ||
| err.statusCode = res.statusCode; | ||
| reject(err); | ||
| } | ||
| } catch (e) { | ||
| const err = new Error('Invalid response'); | ||
| err.cause = e; | ||
| reject(err); | ||
| } | ||
| }); | ||
| }); | ||
| req.on('error', reject); | ||
| req.write(data); | ||
| req.end(); | ||
| }); | ||
| } | ||
| async function cmdLogin(secret, base, useLocal) { | ||
| if (!secret || secret.length < 8) { | ||
| console.error('Error: secret is required and should look like a token'); | ||
| process.exit(2); | ||
| } | ||
| try { | ||
| writeSecret(secret, useLocal); | ||
| console.log(`Tracklify CLI: secret saved to ${useLocal ? '~/.tracklify/key_local' : '~/.tracklify/key'}`); | ||
| // Optional sanity check: call /task but ignore errors silently | ||
| try { await postJSON(base, '/api/cli/task', { secret }); } catch (_) {} | ||
| } catch (e) { | ||
| console.error('Failed to save secret:', e.message); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| async function cmdBranch(base, useLocal) { | ||
| const secret = readSecret(useLocal); | ||
| if (!secret) { | ||
| const url = useLocal ? LOCAL_GET_CLI_URL : PROD_GET_CLI_URL; | ||
| console.error(`\nSeams like you are not logged in\n\nPlease go to ${url}, and re-run login command`); | ||
| process.exit(2); | ||
| } | ||
| try { | ||
| const resp = await postJSON(base, '/api/cli/branch', { secret }); | ||
| if (!resp.branch) throw new Error('No branch in response'); | ||
| console.log(resp.branch); | ||
| } catch (e) { | ||
| if (e && e.statusCode === 401) { | ||
| const url = useLocal ? LOCAL_GET_CLI_URL : PROD_GET_CLI_URL; | ||
| console.error(`\nSeams like you are not logged in\n\nPlease go to ${url}, and re-run login command`); | ||
| process.exit(2); | ||
| } | ||
| if (e && e.statusCode === 404) { | ||
| console.error('You are not tracking any task right now. Start tracking a task in Tracklify and try again.'); | ||
| process.exit(3); | ||
| } | ||
| console.error('Error:', e.message); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| async function cmdTask(base, useLocal) { | ||
| const secret = readSecret(useLocal); | ||
| if (!secret) { | ||
| const url = useLocal ? LOCAL_GET_CLI_URL : PROD_GET_CLI_URL; | ||
| console.error(`\nSeams like you are not logged in\n\nPlease go to ${url}, and re-run login command`); | ||
| process.exit(2); | ||
| } | ||
| try { | ||
| const resp = await postJSON(base, '/api/cli/task', { secret }); | ||
| if (!resp.task) throw new Error('No task in response'); | ||
| console.log(resp.task); | ||
| } catch (e) { | ||
| if (e && e.statusCode === 401) { | ||
| const url = useLocal ? LOCAL_GET_CLI_URL : PROD_GET_CLI_URL; | ||
| console.error(`\nSeams like you are not logged in\n\nPlease go to ${url}, and re-run login command`); | ||
| process.exit(2); | ||
| } | ||
| if (e && e.statusCode === 404) { | ||
| console.error('You are not tracking any task right now. Start tracking a task in Tracklify and try again.'); | ||
| process.exit(3); | ||
| } | ||
| console.error('Error:', e.message); | ||
| process.exit(1); | ||
| } | ||
| } | ||
| (async function main() { | ||
| const { args, flags } = parseArgs(process.argv); | ||
| const useLocal = flags.has('--local'); | ||
| const base = useLocal ? LOCAL_BASE : REMOTE_BASE; | ||
| const cmd = args[0]; | ||
| if (!cmd || flags.has('--help') || flags.has('-h')) { | ||
| printUsage(); | ||
| process.exit(0); | ||
| } | ||
| if (cmd === 'login') { | ||
| await cmdLogin(args[1], base, useLocal); | ||
| return; | ||
| } | ||
| if (cmd === 'branch') { | ||
| await cmdBranch(base, useLocal); | ||
| return; | ||
| } | ||
| if (cmd === 'task') { | ||
| await cmdTask(base, useLocal); | ||
| return; | ||
| } | ||
| console.error(`Unknown command: ${cmd}`); | ||
| printUsage(); | ||
| process.exit(2); | ||
| })(); | ||
-15
| CLI for https://tracklify.com - Next-age task management tool with built-in time tracking | ||
| How to use CLI: https://tracklify.com/features/git-commit-messages-from-an-active-task | ||
| Basically two commands are avaialble: | ||
| ``` | ||
| tracklify task | ||
| ``` | ||
| return full task url | ||
| ``` | ||
| tracklify taskname | ||
| ``` | ||
| Returns only taskname |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
10882
436.06%188
276%138
820%3
50%3
200%