@profullstack/timer 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -42,6 +42,8 @@ Everything `--json` returns is built from this shape:
42
42
  "billable": true,
43
43
  "rate": null,
44
44
  "agent": "claude-opus-5",
45
+ "agents": 1,
46
+ "agentHours": 1.5,
45
47
  "notes": "",
46
48
  "meta": {}
47
49
  }
@@ -59,6 +61,11 @@ ID=$(timer start acme --task "refactor auth" --json | jq -r .started.id)
59
61
  timer stop --id "$ID" --json
60
62
  ```
61
63
 
64
+ `--agents N` records how many engines were working during the entry, which is
65
+ what an agent-priced rate multiplies by. It takes a whole number: `auto` is
66
+ rejected rather than silently treated as 1, because this package has no herd to
67
+ count and under-billing quietly is worse than an error.
68
+
62
69
  Several clocks may run at once, which is the point: parallel agents each track
63
70
  their own work and do not stop each other. Use `--switch` only if you mean to
64
71
  close everyone else's clock.
package/README.md CHANGED
@@ -36,7 +36,7 @@ you start a clock.
36
36
 
37
37
  | Command | What it does |
38
38
  | --- | --- |
39
- | `start <project> [task…]` | Start a clock. `--at 09:15`, `--at -20m`, `--tag`, `--note`, `--rate`, `--switch` |
39
+ | `start <project> [task…]` | Start a clock. `--at 09:15`, `--at -20m`, `--tag`, `--note`, `--rate`, `--agents N`, `--switch` |
40
40
  | `stop [id]` | Stop the newest clock, an id, `--project <p>`, or `--all` |
41
41
  | `status` | Running clocks and today's total |
42
42
  | `log [project]` | List entries in a window |
@@ -65,6 +65,23 @@ A window compares against the entry's **start**, and `--until` is exclusive. An
65
65
  entry that runs past midnight therefore belongs to the day it began on — which
66
66
  is what keeps a total from being counted twice.
67
67
 
68
+ ### Counting agents
69
+
70
+ An hour of agentic work is an hour times however many engines ran in it, so an
71
+ entry carries an agent count:
72
+
73
+ ```sh
74
+ timer start acme refactor auth --agents 4
75
+ timer add acme code review --duration 45m --agents 2
76
+ ```
77
+
78
+ `@profullstack/billing` multiplies by it when the rate says to
79
+ (`$100/hour/agent/upto:4`), and ignores it when the rate is flat. It defaults to
80
+ 1, so you can ignore the whole idea until you need it.
81
+
82
+ `start` is also spelled `on` and `stop` is also spelled `off`, so muscle memory
83
+ from other timers works.
84
+
68
85
  ### Billable and not
69
86
 
70
87
  Every entry is billable unless you say otherwise with `--no-billable`. Reports
@@ -97,7 +114,9 @@ timer log --today --json
97
114
  ```
98
115
 
99
116
  `--meta '{"pr":42}'` hangs your own identifiers off an entry, and they survive
100
- round-trip unchanged.
117
+ round-trip unchanged. `--agents N` records how many engines were working, which
118
+ is what an agent-priced rate multiplies by; it takes a number, not `auto`, since
119
+ this package has no herd to count.
101
120
 
102
121
  There is more detail, including the entry schema, in [AGENTS.md](AGENTS.md).
103
122
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profullstack/timer",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "A time tracker for the terminal and for agents — start a clock against a project, stop it, and get billable hours back as text or JSON.",
6
6
  "keywords": ["timer", "time-tracking", "timesheet", "billable", "hours", "cli", "agent", "freelance"],
package/src/cli.mjs CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  } from "./entries.mjs";
25
25
  import { formatDuration, hours, parseMoment, resolveWindow, shortStamp } from "./time.mjs";
26
26
 
27
- export const VERSION = "0.1.0";
27
+ export const VERSION = "0.2.0";
28
28
 
29
29
  /** A bad command line — worth a different exit code than a failed operation. */
30
30
  export class UsageError extends Error {
@@ -73,6 +73,8 @@ function serialize(entry, now = new Date()) {
73
73
  billable: entry.billable,
74
74
  rate: entry.rate,
75
75
  agent: entry.agent,
76
+ agents: entry.agents ?? 1,
77
+ agentHours: hours(secs * (entry.agents ?? 1)),
76
78
  notes: entry.notes,
77
79
  meta: entry.meta,
78
80
  };
@@ -84,6 +86,7 @@ const ENTRY_COLUMNS = [
84
86
  { header: "PROJECT", get: (e) => e.project },
85
87
  { header: "TASK", get: (e) => e.task || "-" },
86
88
  { header: "TIME", get: (e) => formatDuration(e.seconds), align: "right" },
89
+ { header: "AGENTS", get: (e) => (e.agents > 1 ? e.agents : "") , align: "right" },
87
90
  { header: "TAGS", get: (e) => (e.tags.length ? e.tags.join(",") : "-") },
88
91
  { header: "", get: (e) => (e.running ? "running" : (e.billable ? "" : "unbillable")) },
89
92
  ];
@@ -107,6 +110,26 @@ function sumOf(rows) {
107
110
  return { entries: rows.length, seconds: secs, hours: hours(secs), billableSeconds: billable, billableHours: hours(billable) };
108
111
  }
109
112
 
113
+ /**
114
+ * How many agents were working, from --agents.
115
+ *
116
+ * A number only. moshcode's `--agents auto` reads the count off its own herd
117
+ * and passes the result here: this package has no herd to ask, and silently
118
+ * treating "auto" as 1 would under-bill every entry it appeared on.
119
+ */
120
+ function agentCount(flags) {
121
+ if (flags.agents == null) return 1;
122
+ const n = Number(flags.agents);
123
+ if (!Number.isFinite(n) || n < 1 || !Number.isInteger(n)) {
124
+ throw new UsageError(
125
+ `--agents: "${flags.agents}" is not a whole number of agents`
126
+ + (String(flags.agents).toLowerCase() === "auto"
127
+ ? " (this package has no herd to count; pass the number)" : ""),
128
+ );
129
+ }
130
+ return n;
131
+ }
132
+
110
133
  function requireProject(positional, flags) {
111
134
  const project = flags.project || positional[0];
112
135
  if (!project) throw new UsageError("which project? e.g. timer start acme");
@@ -118,11 +141,11 @@ function requireProject(positional, flags) {
118
141
  const COMMANDS = [
119
142
  {
120
143
  name: "start",
121
- aliases: ["begin", "in"],
144
+ aliases: ["begin", "in", "on"],
122
145
  args: "<project> [task words…]",
123
146
  summary: "start the clock on a project",
124
147
  booleans: ["billable", "switch"],
125
- values: ["task", "note", "agent", "rate", "at", "project", "meta"],
148
+ values: ["task", "note", "agent", "agents", "rate", "at", "project", "meta"],
126
149
  multi: ["tag"],
127
150
  detail: [
128
151
  "Everything after the project name is taken as the task, so you can type",
@@ -154,6 +177,7 @@ const COMMANDS = [
154
177
  start: at,
155
178
  notes: flags.note || "",
156
179
  agent: flags.agent || process.env.TIMER_AGENT || null,
180
+ agents: agentCount(flags),
157
181
  rate: flags.rate,
158
182
  billable: flags.billable !== false,
159
183
  meta,
@@ -175,7 +199,7 @@ const COMMANDS = [
175
199
  },
176
200
  {
177
201
  name: "stop",
178
- aliases: ["out"],
202
+ aliases: ["out", "off"],
179
203
  args: "[id]",
180
204
  summary: "stop a running clock",
181
205
  booleans: ["all"],
@@ -299,7 +323,7 @@ const COMMANDS = [
299
323
  args: "<project> [task words…]",
300
324
  summary: "record time you did not clock",
301
325
  booleans: ["billable"],
302
- values: ["from", "to", "duration", "task", "note", "agent", "rate", "project", "meta"],
326
+ values: ["from", "to", "duration", "task", "note", "agent", "agents", "rate", "project", "meta"],
303
327
  multi: ["tag"],
304
328
  detail: [
305
329
  "Give any two of --from, --to and --duration; with only --duration the entry",
@@ -338,6 +362,7 @@ const COMMANDS = [
338
362
  end: bounds.end,
339
363
  notes: flags.note || "",
340
364
  agent: flags.agent || process.env.TIMER_AGENT || null,
365
+ agents: agentCount(flags),
341
366
  rate: flags.rate,
342
367
  billable: flags.billable !== false,
343
368
  meta,
@@ -358,7 +383,7 @@ const COMMANDS = [
358
383
  args: "<id>",
359
384
  summary: "change an entry",
360
385
  booleans: ["billable"],
361
- values: ["project", "task", "note", "agent", "rate", "from", "to", "duration", "meta"],
386
+ values: ["project", "task", "note", "agent", "agents", "rate", "from", "to", "duration", "meta"],
362
387
  multi: ["tag"],
363
388
  detail: ["Only the fields you name change. --tag replaces the whole tag list."],
364
389
  run({ positional, flags, file }) {
@@ -372,6 +397,7 @@ const COMMANDS = [
372
397
  if (flags.task != null) e.task = flags.task;
373
398
  if (flags.note != null) e.notes = flags.note;
374
399
  if (flags.agent != null) e.agent = flags.agent || null;
400
+ if (flags.agents != null) e.agents = agentCount(flags);
375
401
  if (flags.rate != null) e.rate = Number(flags.rate);
376
402
  if ("billable" in flags) e.billable = Boolean(flags.billable);
377
403
  if (flags.tag) e.tags = [...new Set(flags.tag)];
@@ -459,6 +485,7 @@ const COMMANDS = [
459
485
  start: at,
460
486
  notes: "",
461
487
  agent: source.agent,
488
+ agents: source.agents ?? 1,
462
489
  rate: source.rate,
463
490
  billable: source.billable,
464
491
  meta: source.meta,
package/src/entries.mjs CHANGED
@@ -24,6 +24,7 @@ export function makeEntry({
24
24
  end = null,
25
25
  notes = "",
26
26
  agent = null,
27
+ agents = 1,
27
28
  rate = null,
28
29
  billable = true,
29
30
  meta = {},
@@ -38,6 +39,11 @@ export function makeEntry({
38
39
  end,
39
40
  notes: String(notes || ""),
40
41
  agent: agent ? String(agent) : null,
42
+ // How many engines were working during this entry. An hour of agentic work
43
+ // is an hour times however many agents ran in it, and a rate priced per
44
+ // agent needs that number per entry - averaging it across a day bills a
45
+ // two-agent afternoon at the four-agent rate.
46
+ agents: Math.max(1, Math.round(Number(agents) || 1)),
41
47
  rate: rate == null ? null : Number(rate),
42
48
  billable: Boolean(billable),
43
49
  meta: meta && typeof meta === "object" ? meta : {},