Skip to content

Noun-first commands, multi-profile support, semantic WP identifiers, and new commands#15

Open
cbliard wants to merge 27 commits into
mainfrom
cbl-improvements
Open

Noun-first commands, multi-profile support, semantic WP identifiers, and new commands#15
cbliard wants to merge 27 commits into
mainfrom
cbl-improvements

Conversation

@cbliard
Copy link
Copy Markdown
Member

@cbliard cbliard commented Jun 4, 2026

Summary

This PR brings a series of improvements to the OpenProject CLI, built incrementally
over ~20 commits. The changes span command naming, new features, and identifier handling.

Sorry for not splitting it in multiple PRs, but I don't have much time for that, and each commits build on top of each other.

Command restructuring (breaking change)

Commands now follow op <noun> <verb> instead of op <verb> <noun>. The old
verb-first syntax has been removed entirely:

Before After
op list workpackages op work-package list
op create workpackage op work-package create
op update workpackage op work-package update
op list time-entries op time-entry list

Each noun (work-package, time-entry, project, budget, …) is now its own
subcommand group with discoverable verbs and consistent flag help.

Multi-profile support

op login now supports named profiles for working with multiple OpenProject instances:

op login --profile work
op login --profile staging
op work-package list --profile work

The OP_CLI_PROFILE env var selects a profile for the whole session.

New commands

  • op whoami — shows all configured profiles and the authenticated user for each
  • op time-entry create — creates a time entry with activity, hours, date, comment
  • op budget list / op budget inspect — lists and inspects project budgets
  • op work-package search — searches work packages by subject, type, status, project name, or identifier; multiple words are ANDed; returns up to 100 results

New flags

  • op work-package list --parent-id — filter by parent work package
  • op work-package create/update --assignee — assign a user
  • op work-package create/update --description — set description

Semantic work package identifiers

Work packages can now be referenced by their project-based identifier wherever a
numeric ID was previously required. Both slug-based identifiers (e.g. PROJ-42,
used before semantic identifiers were introduced) and semantic identifiers
(e.g. PROJ-123) are supported. The displayId field from the API drives display:
semantic form when project-based identifiers are enabled, #N otherwise.

JSON output

All list/inspect commands support --format json for machine-readable output.

Project identifiers

Project path arguments now accept either a numeric ID or a human-readable slug
(e.g. my-project), matching what the OpenProject API supports natively.

Developer docs

Added CLAUDE.md documenting the architecture, conventions, and data flow for
contributors.

Suggested test plan

  • go test ./... passes
  • op login --profile <name> creates and stores a named profile
  • op whoami lists all profiles with their URLs and users
  • op work-package list --format json returns valid JSON
  • op work-package list --parent-id <id> filters correctly
  • op time-entry create prompts for missing required fields and submits
  • Work packages can be referenced as PROJ-42 or PROJ-123 in inspect/update/open
  • op budget list and op budget inspect work on a project with budgets
  • op work-package search cascade returns matching work packages
  • op work-package search fix login ANDs both terms

cbliard added 23 commits April 20, 2026 14:26
New noun-first commands (old verb-first commands kept during transition):

  op workpackage list/create/update/inspect  (was: op list/create/update/inspect workpackage[s])
  op activities list [--wp id]               (was: op list activities [id])
  op project list/inspect                    (was: op list/inspect project[s])
  op user search                             (was: op search user)
  op timeentry list                          (was: op list timeentries)
  op type list                               (was: op list types)
  op status list                             (was: op list status)
  op notification list                       (was: op list notifications)

op activities list uses --wp flag instead of positional argument to
allow future scoping (--project, global) without breaking the interface.

op git start workpackage is unchanged.
Deleted: cmd/list/, cmd/create/, cmd/update/, cmd/inspect/, cmd/search/

Remaining commands:
  op workpackage list/create/update/inspect
  op activities list [--wp id]
  op project list/inspect
  op user search
  op timeentry list
  op type list
  op status list
  op notification list
  op git start workpackage (unchanged)
  op login (unchanged)
- --wp renamed to --work-package for consistency with other flag names
- MarkFlagRequired ensures Cobra validates the flag before execution
  rather than failing at runtime
Introduce a Renderer interface with TextRenderer (existing behavior) and
JsonRenderer (new) implementations. The active renderer is selected at startup
via the root command's PersistentPreRun based on the --format flag value.
The predicate (Id == target) is not monotone — sort.Search requires >=.
Also, len(users)-1 as the upper bound excluded the last user from the search.
Together, these bugs caused the wrong user name to be resolved when the
target user appeared before the last position in the slice.
Cobra supports hyphens in command names; aligning with common CLI conventions
(docker, kubectl, gh). Package names remain unhyphenated as Go requires.
Also updates README and CLAUDE.md to reflect noun-first syntax throughout.
go build -o op produces a local binary that should not be versioned.
Lists budgets for a given project via GET /api/v3/projects/{id}/budgets.
Fetches a single budget by ID via GET /api/v3/budgets/{id}.
These flags are irrelevant for shell completion generation.
Assignee was already supported for update and display; create was missing it.
Takes a user ID, consistent with the update command.
Supports multi-line markdown descriptions. Format is hardcoded to markdown
as textile is no longer supported in OpenProject.
Shows configured server URL and current user info.
Handles missing config (not logged in) and 401 (invalid token) gracefully.
Supports --work-package (required), --hours (required), --activity (optional,
looked up by name), --spent-on (default: today), --user, --comment flags.

Also fixes nil pointer dereferences in TimeEntryDto.Convert() for optional
linked resources (project, work package, user, activity, comment).
When GET /api/v3/time_entries/activities returns an error (endpoint not
available in all OpenProject versions), show a clear message instead of
the raw API error. When the endpoint works but the name doesn't match,
list available activities.
Previously the CLI stored a single host+token pair in a flat config file,
making it awkward to switch between instances (community, self-hosted, staging,
etc.). Users had to re-run `op login` each time, overwriting their credentials.

This commit introduces named profiles so multiple instances can be configured
and used side-by-side.

Config format
  The config file (~/.config/openproject/config, or $XDG_CONFIG_HOME) is now
  stored as INI with one section per profile:

    [default]
    host  = https://community.openproject.org
    token = abc123

    [work]
    host  = https://work.example.com
    token = xyz789

  Existing single-line files ("host token") are silently migrated to [default]
  on the first read.

Profile names
  Only letters, digits, - and _ are allowed, with no leading/trailing hyphens.
  The interactive prompt sanitizes invalid input and re-prompts with the
  corrected name as the new default.

op login
  - Prompts "Profile name? [default]" before asking for credentials.
  - --profile <name> skips the prompt and uses that name directly (validated
    upfront; errors immediately if the name is invalid).
  - OP_CLI_PROFILE env var behaves the same as --profile for login.
  - If the profile already exists the user is asked to confirm overwrite.

op logout (new command)
  - Removes a profile from the config file.
  - Defaults to "default"; use --profile to target another.
  - Idempotent: no error if the profile does not exist.
  - Always asks for confirmation before deleting.

op whoami
  - Without --profile: shows every configured profile (server + user), each
    block separated by a blank line.
  - With --profile <name>: shows only that profile.
  - Output now includes a "Profile:" header line per entry.

All other commands
  - Accept a global --profile flag (persistent, default "default").
  - OP_CLI_PROFILE env var sets the default profile; --profile overrides it.
  - OP_CLI_HOST / OP_CLI_TOKEN still override everything (highest priority).
  - If an explicitly named profile does not exist, an error is shown and
    nothing is done.
Hiding flags via InheritedFlags().MarkHidden() mutates the shared *Flag
structs, accidentally hiding --format and --verbose from all sub-commands.
Removing the loop lets them appear correctly under "Global Flags" everywhere.
Allows listing the direct children of a work package by passing its ID:

  op work-package list --parent-id 42

The flag translates to a parent filter on the OpenProject API v3
(operator "="), which is ANDed with any other active filters such as
--project-id, --status, or --type.

A pre-flight lookup verifies that the given work package exists before
issuing the filter query. If it does not, the command exits with a
clear error naming the flag:

  --parent-id: work package #42 not found.

Only direct children are returned (depth 1). Listing a full subtree
would require multiple API calls and is out of scope.
All commands that reference a project — op project inspect, op work-package
list, op work-package create, and op budget list — now accept both the
numeric project ID (e.g. 42) and the human-readable project identifier
found in the URL (e.g. "foobar" from /projects/foobar/work_packages).

Changes:
- --project-id flag on op work-package list renamed to --project; the old
  name is kept as a hidden alias for backward compatibility
- op project inspect updated to accept [id|identifier] as its argument;
  the previous hard rejection of non-numeric input is removed
- op budget list --project flag updated to accept string values
- op work-package create --project flag updated to accept string values
- Project identifier field added to models, DTOs, and JSON output so it is
  visible when listing or inspecting projects
- Project display format updated to "#14 MyProject (my-project)" so users
  can discover the identifier to reuse in flags
- routes.ProjectUrl now builds the human-readable URL (projects/my-project)
  instead of the numeric one (projects/14)
- Input validated client-side: only alphanumeric characters, -, _, and +
  are accepted; invalid input is rejected before any API call with a clear
  error message pointing to the offending flag
- A friendly 404 error is shown when the project is not found instead of
  the raw API error response
OpenProject community now supports Jira-style identifiers introduced in
https://community.openproject.org/work_packages/72427. All commands that
accept a work package ID (inspect, update, list --parent-id, activities
--work-package, git start workpackage) now accept both numeric IDs for
backward compatibility and project-based identifiers (PROJECTID-NUMBER).

Validation enforces the identifier rules: uppercase letters, digits, or
underscores; max 10 characters; must start with a letter; followed by a
hyphen and a numeric sequence number.
…ayId field

The OpenProject API returns a displayId field alongside the numeric id.
When project-based identifiers are enabled, this is the semantic key
(e.g. SJF-13); on instances without the feature it matches the numeric
id and the display falls back to the conventional #N format.

Wire displayId through DTO → model → printer (text and JSON renderers).
Column alignment in list output handles mixed numeric/#N and semantic IDs.
@myabc myabc requested review from Copilot and myabc and removed request for Copilot June 4, 2026 14:41
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR is a broad refactor and feature expansion of the OpenProject CLI, primarily restructuring commands into a noun-first hierarchy while adding multi-profile configuration, semantic work package identifiers, JSON output, and several new resource commands (time entries, budgets, whoami).

Changes:

  • Reworked CLI command structure to op <noun> <verb> and introduced new command groups (work-package, time-entry, project, budget, status, notification, user, etc.).
  • Added multi-profile configuration support (INI-based), along with op whoami, op logout, and profile selection via --profile / OP_CLI_PROFILE.
  • Added semantic identifier handling for work packages/projects, JSON output rendering via a new renderer abstraction, and new resource capabilities (time entry create, budget list/inspect).

Reviewed changes

Copilot reviewed 95 out of 96 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
TODO.md Tracks follow-up items discovered during review.
README.md Updates documentation/examples to noun-first commands and multi-profile usage.
.gitignore Adds built binary and Claude local settings file.
CLAUDE.md Adds contributor/developer architecture and conventions documentation.
models/work_package.go Adds DisplayId to support semantic display identifiers.
models/project.go Adds Identifier to support slug/identifier-based project access.
models/time_entry_activity.go Adds time entry activity model.
models/budget.go Adds budget model.
dtos/work_package.go Maps API displayId and hardens link/description conversion against nils.
dtos/project.go Adds project identifier mapping.
dtos/time_entry.go Renames/exports links DTO and hardens conversion against nil links/comment.
dtos/time_entry_activity.go Adds DTOs and conversion for time entry activities collection.
dtos/budget.go Adds budget DTOs and collection conversion.
components/paths/paths.go Updates path helpers to accept string identifiers; adds budgets/time entry activities endpoints.
components/routes/routes.go Changes browser routes to use wp/<displayId> and projects/<identifier>.
components/resources/work_packages/validate.go Adds identifier validation for numeric vs semantic WP IDs.
components/resources/work_packages/validate_test.go Tests new WP identifier validation.
components/resources/work_packages/read.go Converts WP APIs to accept string IDs and adds parent filter handling.
components/resources/work_packages/filters.go Adds Parent filter option and filter builder.
components/resources/work_packages/create.go Allows project identifier string; adds assignee/description create options.
components/resources/work_packages/update.go Allows string WP id; adds description patching and improves assignee parsing errors.
components/resources/work_packages/activities.go Switches WP activities to accept string WP identifiers.
components/resources/time_entries/create.go Implements time entry creation plus activity lookup.
components/resources/projects/functions.go Updates project lookup to accept numeric ID or identifier and validates input.
components/resources/projects/versions.go Updates versions API to take string project identifiers/IDs.
components/resources/projects/validate.go Adds project identifier validation helper.
components/resources/projects/validate_test.go Tests new project identifier validation.
components/resources/budgets/functions.go Adds budgets lookup and listing for a project.
components/configuration/util.go Removes legacy single-profile read/write logic (replaced by profiles).
components/configuration/profiles.go Implements INI-based multi-profile config + migration from legacy format.
components/configuration/profiles_test.go Comprehensive tests for profile sanitization/validation/read/write/migration.
components/printer/renderer.go Introduces renderer abstraction and --format handling.
components/printer/renderer_test.go Tests renderer init behavior on unknown/known formats.
components/printer/text_renderer.go Centralizes existing text rendering logic behind renderer interface.
components/printer/json_renderer.go Adds JSON output rendering for list/inspect commands.
components/printer/work_packages.go Updates WP printing to use DisplayId via renderer and adds formatting helpers.
components/printer/work_packages_test.go Updates tests for DisplayId and adds semantic display ID alignment test.
components/printer/projects.go Routes project output via renderer.
components/printer/projects_test.go Updates expected project output to include identifier.
components/printer/users.go Routes user output via renderer.
components/printer/types.go Routes type output via renderer.
components/printer/status.go Routes status output via renderer.
components/printer/time_entries.go Routes time entry output via renderer.
components/printer/notifications.go Routes notifications output via renderer while keeping grouping helper.
components/printer/custom_actions.go Routes custom actions output via renderer.
components/printer/numbers.go Routes numeric output via renderer.
components/printer/budgets.go Adds budgets printer entry points routed through renderer.
components/printer/budgets_test.go Adds budgets output/alignment tests.
components/printer/whoami.go Adds whoami printer entry point routed through renderer.
components/printer/whoami_test.go Adds whoami output test.
components/printer/activities.go Routes activities output via renderer.
components/printer/activities_test.go Adds regression test for prior activities user-lookup bug.
cmd/root.go Registers new noun-first command groups; adds --format and --profile; initializes renderer/requests in pre-run.
cmd/login.go Adds profile selection/prompting and profile overwrite confirmation; stores credentials per profile.
cmd/logout.go Adds logout command to remove stored profiles with confirmation.
cmd/whoami.go Adds whoami command to display authenticated user for one/all profiles.
cmd/workpackage/workpackage.go Adds new work-package command group and wires flags/subcommands.
cmd/workpackage/list.go Implements work-package list with project/parent filters and improved errors.
cmd/workpackage/list_flags.go Defines flags for work-package list (project, parent-id, filters, total, etc.).
cmd/workpackage/create.go Implements work-package create supporting project identifier, assignee, description, open-in-browser.
cmd/workpackage/update.go Implements work-package update supporting semantic IDs and new --description.
cmd/workpackage/options_test.go Adds tests for description flag semantics in create/update.
cmd/workpackage/inspect.go Implements work-package inspect supporting semantic IDs and listing types.
cmd/workpackage/workpackage_test.go Adds test ensuring hyphenated command name usage.
cmd/timeentry/timeentry.go Adds time-entry command group and wires list/create.
cmd/timeentry/list.go Implements time-entry list as noun-first list subcommand.
cmd/timeentry/list_flags.go Wires filters for time-entry list.
cmd/timeentry/create.go Implements time-entry create including defaults and optional activity/user/comment.
cmd/timeentry/create_flags.go Defines flags and required inputs for time-entry create.
cmd/timeentry/timeentry_test.go Adds test ensuring hyphenated command name usage.
cmd/project/project.go Adds project command group and wires list/inspect with open flag.
cmd/project/list.go Implements project list as noun-first list subcommand.
cmd/project/inspect.go Implements project inspect accepting numeric ID or identifier.
cmd/budget/budget.go Adds budget command group and wires list/inspect.
cmd/budget/list.go Implements `budget list --project <id
cmd/budget/inspect.go Implements budget inspect <id>.
cmd/status/status.go Adds status command group.
cmd/status/list.go Implements status list.
cmd/notification/notification.go Adds notification command group.
cmd/notification/list.go Implements notification list with reason filter.
cmd/user/user.go Adds user command group.
cmd/user/search.go Implements user search (including me keyword behavior).
cmd/wptype/wptype.go Adds type command group for work package types.
cmd/wptype/list.go Implements type list.
cmd/activities/activities.go Adds activities command group.
cmd/activities/list.go Implements `activities list --work-package <id
cmd/git/start/work_package.go Updates git start command to accept semantic WP identifiers.
cmd/update/update.go Removes legacy verb-first update command group.
cmd/update/work_package.go Removes legacy verb-first update workpackage command.
cmd/list/list.go Removes legacy verb-first list command group.
cmd/list/activities.go Removes legacy verb-first activities list command.
cmd/list/status.go Removes legacy verb-first status list command.
cmd/inspect/inspect.go Removes legacy verb-first inspect command group.
cmd/create/create.go Removes legacy verb-first create command group.
cmd/create/work_package.go Removes legacy verb-first create workpackage command.
cmd/search/search.go Removes legacy verb-first search command group.
Comments suppressed due to low confidence (2)

cmd/workpackage/list.go:156

  • validatedVersionId() logs an error but continues, which can lead to a nil dereference (project may be nil, yet project.Identifier is accessed on the next lines). It should abort/return immediately when projects.Lookup fails.
    cmd/workpackage/list.go:161
  • validatedVersionId() logs an error but continues, which can lead to using an uninitialized versions slice (and inconsistent CLI behavior). Exit/return when AvailableVersions fails.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cmd/root.go Outdated
Comment thread components/printer/json_renderer.go
Comment thread cmd/login.go Outdated
Comment thread components/routes/routes.go
- Handle url.Parse error in PersistentPreRunE instead of silently ignoring it
- Route printJson output through activePrinter instead of fmt directly
- Use printer.Input for API token prompt in login instead of fmt.Printf
- Add comment explaining DisplayId is always non-empty (API guarantees identifier.presence || id)
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 95 out of 96 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

cmd/workpackage/list.go:156

  • In validatedVersionId(), errors from projects.Lookup() / AvailableVersions() are printed but the function continues, which can dereference a nil project (project.Identifier) and/or operate on a nil versions slice. Either return/exit immediately after these errors, or propagate the error to the caller so list can handle it cleanly.

Comment thread components/configuration/profiles.go Outdated
Comment on lines +190 to +200
// Old format: no section headers
if !strings.Contains(content, "[") && content != "" {
clean := common.SanitizeLineBreaks(content)
parts := strings.SplitN(clean, " ", 2)
if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
f := newIniFile()
f.set(DefaultProfile, "host", parts[0])
f.set(DefaultProfile, "token", parts[1])
return f, true
}
}
Comment thread dtos/budget.go
/////////////// MODEL CONVERSION ///////////////

func (dto *BudgetCollectionDto) Convert() []*models.Budget {
budgets := make([]*models.Budget, len(dto.Embedded.Elements))
Copy link
Copy Markdown
Contributor

@myabc myabc Jun 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ I don't think it's a guarantee that a response has an _embedded object - e.g. it is optional for soem error responses. As such this needs a nil guard.

Comment thread cmd/workpackage/list.go Outdated
printer.ErrorText(fmt.Sprintf("--parent-id: %s", err.Error()))
return
}
if _, err := work_packages.Lookup(listParentId); err != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this discards the numeric id

}
host, _ = f.get(profile, "host")
token, _ = f.get(profile, "token")
return host, token, nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude 🤖 findings:

Automated finding, not personally verified by the PR author.

The old ReadConfig surfaced an explicit "Invalid config file… please remove the file and run op login again" error. Here readConfigForProfile returns empty creds with a nil error for a malformed/un-resolvable config, so the user only sees the generic "not logged in" message and is never told the file is corrupt or how to fix it. Consider returning a diagnostic error when a config file exists but yields no usable profile.

if !strings.Contains(content, "[") && content != "" {
clean := common.SanitizeLineBreaks(content)
parts := strings.SplitN(clean, " ", 2)
if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude 🤖 findings:

Automated finding, not personally verified by the PR author.

Old-format migration requires both SplitN parts non-empty. A malformed old file (host-only, token-only, leading/trailing space) silently fails to migrate, falls through to parseIni which finds no section, and ReadConfig returns empty creds with no error — an existing logged-in user is silently treated as logged out after upgrade with no diagnostic.

sb.WriteString("\n")
}
sb.WriteString(fmt.Sprintf("[%s]\n", s.name))
for _, key := range []string{"host", "token"} {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude 🤖 findings: (cleanup)

Automated finding, not personally verified by the PR author.

marshal() only emits host/token, so any other key present in a profile section is silently discarded on rewrite — latent data loss if the config format ever grows additional keys. Consider iterating the section's actual keys.

func WorkPackageUrl(workPackage *models.WorkPackage) *url.URL {
routeUrl := *host
routeUrl.Path = fmt.Sprintf("work_packages/%d", workPackage.Id)
// DisplayId is always non-empty: the server code sets it to `identifier.presence || id`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude 🤖 findings:

Automated finding, not personally verified by the PR author.

op work-package create -o opens host/wp/ (no id) when the create POST response omits displayId (omitempty). Worth guarding against an empty DisplayId before building the URL.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@myabc Can displayId ever be empty? My current assumption is that it can't be empty.

Comment thread components/paths/paths.go
func Project(id uint64) string {
return Projects() + fmt.Sprintf("/%d", id)
func Project(id string) string {
return Projects() + "/" + id
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude 🤖 findings:

Automated finding, not personally verified by the PR author.

Project(id) / WorkPackage(id) now concatenate a caller-supplied string identifier directly with no escaping. The project identifier validator permits +, which is path-significant and read as a space by many servers, producing a wrong-resource lookup. Consider url.PathEscape on the identifier.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong, the project identifier cannot contain +. The problem is the validator, not the mssing escaping in the URL.

Project identifier rules are: "Only uppercase letters (A–Z), numbers or underscores. Max 10 characters. Must start with a letter. "

image


func hoursCreate(entry *dtos.TimeEntryDto, input string) error {
var hours float64
if _, err := fmt.Sscanf(input, "%f", &hours); err != nil {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude 🤖 findings:

Automated finding, not personally verified by the PR author.

fmt.Sscanf(input, "%f", …) (and "%d" at line 123) scans one item with a nil error for input like 1.5abc (→ 1.5) or 12x (→ user 12), silently accepting invalid input rather than rejecting it. Prefer strconv.ParseFloat / strconv.ParseUint, which fail on trailing characters.

return nil
}

func hoursToISO8601(hours float64) string {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude 🤖 findings: (cleanup)

Automated finding, not personally verified by the PR author.

hoursToISO8601 hand-rolls ISO-8601 duration formatting, while github.com/sosodev/duration is already a dependency used on the read path (dtos/time_entry.go:42). Reusing it keeps the write path in lockstep with parsing.

}

func (r *JsonRenderer) WorkPackage(wp *models.WorkPackage) {
printJson(struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude 🤖 findings: (cleanup)

Automated finding, not personally verified by the PR author.

Each resource declares its field shape twice (single + list anonymous structs); these have already diverged — WorkPackage includes Description while WorkPackages omits it. A single named view struct per resource, reused for both, keeps single/list output in sync.

Comment thread cmd/login.go
// - If OP_CLI_PROFILE is set (but --profile was not): display the value
// and use it without prompting.
// - Otherwise: prompt the user interactively.
func resolveLoginProfile(cmd *cobra.Command) (string, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude 🤖 findings: (cleanup)

Automated finding, not personally verified by the PR author.

resolveLoginProfile and resolvedProfile (cmd/root.go:103) both reimplement the flag > env > default precedence and can drift. Consider a single shared resolver.

@cbliard
Copy link
Copy Markdown
Member Author

cbliard commented Jun 4, 2026

@myabc 9 comments with

Claude 🤖 findings: (cleanup)

Automated finding, not personally verified by the PR author.

What makes you think I have more time available than you to actually verify them? I too have other things to do.
If you add a comment, please at least review what's in it.
I'm not a robot.
I started reviewing 2 of them but there are too many actually, I can't process them all.

From now on I'll ignore all non-human comments posted by humans.

@cbliard
Copy link
Copy Markdown
Member Author

cbliard commented Jun 4, 2026

@myabc And also feel free to send patches rather than comments. We all collaborate on this.

cbliard added 2 commits June 5, 2026 08:11
- Guard against nil _embedded in BudgetCollectionDto.Convert
- Resolve --parent-id semantic identifier to numeric ID before filtering
- Use strconv.ParseFloat/ParseUint instead of fmt.Sscanf to reject trailing garbage (e.g. "1.5abc")
- Fix IPv6 URL mis-detection in legacy config migration (HasPrefix instead of Contains)
- Add regression test for IPv6 host migration
Searches across subject, type, status, project name, and identifier
using the OpenProject API typeahead filter. Multiple words are ANDed.
Returns up to 100 results.
@cbliard cbliard force-pushed the cbl-improvements branch from b2c330e to 9ff0a5c Compare June 5, 2026 10:56
Scopes the search to a specific project when provided, using the
project-scoped API endpoint. Accepts a numeric ID or identifier.
@cbliard cbliard force-pushed the cbl-improvements branch from 9ff0a5c to c78815f Compare June 5, 2026 11:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants