Compare commits

...

1 Commits

Author SHA1 Message Date
Jiang Bohan
8b24f628a1 refactor(cli): tidy workspace subtree (MUL-2386)
- Drop `workspace current`; `workspace get` (no args) already prints the
  current default workspace, so the two were doing the same thing.
- Rename `workspace members` to `workspace member list` to free up the
  `member` namespace for future `add` / `remove` subcommands and align
  with the rest of the CLI's `<resource> <verb>` shape.
- Add `--full-id` to `workspace list`, matching `project list`,
  `autopilot list`, and friends.

Docs and the daemon prompt are updated to match.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-19 17:44:55 +08:00
15 changed files with 38 additions and 79 deletions

View File

@@ -285,19 +285,11 @@ If you need full isolation between organizations or accounts — separate tokens
```bash
multica workspace list
multica workspace list --full-id
multica workspace list --output json
```
The current default workspace is marked with `*`.
### Show Current Workspace
```bash
multica workspace current
multica workspace current --output json
```
Prints the workspace that commands without `--workspace-id` and `MULTICA_WORKSPACE_ID` would target.
The current default workspace is marked with `*`. Table output shows short UUID prefixes — pass `--full-id` when you need the canonical UUIDs.
### Switch Default Workspace
@@ -315,10 +307,12 @@ multica workspace get <workspace-id>
multica workspace get <workspace-id> --output json
```
Passing no `<workspace-id>` resolves to the current default workspace, so `multica workspace get` doubles as "what workspace am I on?".
### List Members
```bash
multica workspace members <workspace-id>
multica workspace member list <workspace-id>
```
## Issues
@@ -350,7 +344,7 @@ multica issue create --title "Fix login bug" --description "..." --priority high
multica issue create --title "Fix login bug" --assignee-id 5fb87ac7-23b5-4a7a-81fa-ed295a54545d
```
Flags: `--title` (required), `--description`, `--status`, `--priority`, `--assignee` / `--assignee-id`, `--parent`, `--project`, `--due-date`. Pass `--assignee-id <uuid>` (mutually exclusive with `--assignee`) when scripting against the IDs returned by `multica workspace members --output json` / `multica agent list --output json`.
Flags: `--title` (required), `--description`, `--status`, `--priority`, `--assignee` / `--assignee-id`, `--parent`, `--project`, `--due-date`. Pass `--assignee-id <uuid>` (mutually exclusive with `--assignee`) when scripting against the IDs returned by `multica workspace member list --output json` / `multica agent list --output json`.
### Update Issue

View File

@@ -35,7 +35,7 @@ multica issue assign MUL-42 --to alice
multica issue assign MUL-42 --to-id 5fb87ac7-23b5-4a7a-81fa-ed295a54545d
```
`--to` takes a member username or an agent name (fuzzy match). When names overlap — e.g. an agent `J` alongside `Cursor - J` — pass `--to-id <uuid>` instead, using the `user_id` (member) or `id` (agent) from `multica workspace members --output json` / `multica agent list --output json`. UUID matching is strict and unambiguous, which is what you want from scripts and from agents driving the CLI. `--to` and `--to-id` are mutually exclusive.
`--to` takes a member username or an agent name (fuzzy match). When names overlap — e.g. an agent `J` alongside `Cursor - J` — pass `--to-id <uuid>` instead, using the `user_id` (member) or `id` (agent) from `multica workspace member list --output json` / `multica agent list --output json`. UUID matching is strict and unambiguous, which is what you want from scripts and from agents driving the CLI. `--to` and `--to-id` are mutually exclusive.
Unassign:

View File

@@ -35,7 +35,7 @@ multica issue assign MUL-42 --to alice
multica issue assign MUL-42 --to-id 5fb87ac7-23b5-4a7a-81fa-ed295a54545d
```
`--to` 后跟成员用户名或智能体名字(模糊匹配)。如果工作区里有同名 / 互相含子串的成员或智能体(例如 agent `J` 旁边还有 `Cursor - J`),改用 `--to-id <uuid>`UUID 来自 `multica workspace members --output json` 的 `user_id` 或 `multica agent list --output json` 的 `id`,是唯一精确的方式,特别适合脚本和驱动 CLI 的智能体。`--to` 和 `--to-id` 互斥。
`--to` 后跟成员用户名或智能体名字(模糊匹配)。如果工作区里有同名 / 互相含子串的成员或智能体(例如 agent `J` 旁边还有 `Cursor - J`),改用 `--to-id <uuid>`UUID 来自 `multica workspace member list --output json` 的 `user_id` 或 `multica agent list --output json` 的 `id`,是唯一精确的方式,特别适合脚本和驱动 CLI 的智能体。`--to` 和 `--to-id` 互斥。
取消分配:

View File

@@ -39,7 +39,7 @@ For the difference between token types, see [Authentication and tokens](/auth-to
|---|---|
| `multica workspace list` | List every workspace you can access |
| `multica workspace get <slug>` | Show details for one workspace |
| `multica workspace members` | List members of the current workspace |
| `multica workspace member list` | List members of the current workspace |
| `multica workspace update <id> --name "..." [--description "..."] [--context "..."] [--issue-prefix "..."]` | Update workspace metadata (admin/owner). Long fields accept `--description-stdin` / `--context-stdin`. |
## Issues and projects

View File

@@ -39,7 +39,7 @@ Token 类型的详细区分见 [认证与令牌](/auth-tokens)。
|---|---|
| `multica workspace list` | 列出你有权访问的所有工作区 |
| `multica workspace get <slug>` | 查看一个工作区的详情 |
| `multica workspace members` | 列出当前工作区的成员 |
| `multica workspace member list` | 列出当前工作区的成员 |
| `multica workspace update <id> --name "..." [--description "..."] [--context "..."] [--issue-prefix "..."]` | 修改 workspace 元数据admin/owner 权限)。长文本可用 `--description-stdin` / `--context-stdin`。 |
## Issue 和 Project

View File

@@ -210,7 +210,7 @@ multica workspace get <workspace-id> --output json
### List Members
```bash
multica workspace members <workspace-id>
multica workspace member list <workspace-id>
```
### Update Workspace
@@ -267,7 +267,7 @@ multica issue create --title "Fix login bug" --description "..." --priority high
multica issue create --title "Fix login bug" --assignee-id 5fb87ac7-23b5-4a7a-81fa-ed295a54545d
```
Flags: `--title` (required), `--description`, `--status`, `--priority`, `--assignee` / `--assignee-id`, `--parent`, `--project`, `--due-date`. 脚本里如果已经拿到了 UUID例如来自 `multica workspace members --output json`),传 `--assignee-id <uuid>`(与 `--assignee` 互斥)以精确锁定。
Flags: `--title` (required), `--description`, `--status`, `--priority`, `--assignee` / `--assignee-id`, `--parent`, `--project`, `--due-date`. 脚本里如果已经拿到了 UUID例如来自 `multica workspace member list --output json`),传 `--assignee-id <uuid>`(与 `--assignee` 互斥)以精确锁定。
### Update Issue

View File

@@ -99,7 +99,7 @@ Assign the issue to the agent you just created — click its avatar in the web U
multica issue assign MUL-1 --to my-agent-name
```
`--to` takes the **name** of an agent or member. A substring match works — if the agent is called `my-code-reviewer`, `reviewer` resolves to it. If your workspace has overlapping names, pass `--to-id <uuid>` instead (mutually exclusive with `--to`); look up the UUID via `multica agent list --output json` or `multica workspace members --output json`.
`--to` takes the **name** of an agent or member. A substring match works — if the agent is called `my-code-reviewer`, `reviewer` resolves to it. If your workspace has overlapping names, pass `--to-id <uuid>` instead (mutually exclusive with `--to`); look up the UUID via `multica agent list --output json` or `multica workspace member list --output json`.
**What happens next from the daemon**:

View File

@@ -99,7 +99,7 @@ multica issue create --title "给 README 加一段 ASCII 架构图"
multica issue assign MUL-1 --to my-agent-name
```
`--to` 后面填智能体或成员的**名字**,子串就行——如果智能体叫 `my-code-reviewer`,填 `reviewer` 也能命中。如果工作区里名字相互重叠或冲突,改用 `--to-id <uuid>`(与 `--to` 互斥UUID 来自 `multica agent list --output json` 或 `multica workspace members --output json`。
`--to` 后面填智能体或成员的**名字**,子串就行——如果智能体叫 `my-code-reviewer`,填 `reviewer` 也能命中。如果工作区里名字相互重叠或冲突,改用 `--to-id <uuid>`(与 `--to` 互斥UUID 来自 `multica agent list --output json` 或 `multica workspace member list --output json`。
**接下来守护进程会**

View File

@@ -126,7 +126,7 @@ There is currently no unarchive command; create a new squad if you need the rout
| `multica squad member remove <id> --member-id <uuid> --type agent\|member` | Remove a member (the leader cannot be removed — change leader first) |
| `multica squad activity <issue-id> <action\|no_action\|failed> --reason "..."` | Recorded by the leader agent at the end of every turn |
`--leader` accepts an agent name or UUID; for everything else, IDs come from `multica agent list --output json`, `multica workspace members --output json`, and `multica squad list --output json`.
`--leader` accepts an agent name or UUID; for everything else, IDs come from `multica agent list --output json`, `multica workspace member list --output json`, and `multica squad list --output json`.
## Next

View File

@@ -126,7 +126,7 @@ multica squad member add <squad-id> --member-id <agent-or-user-uuid> --type agen
| `multica squad member remove <id> --member-id <uuid> --type agent\|member` | 移除成员(**不能移除队长**——先换队长)|
| `multica squad activity <issue-id> <action\|no_action\|failed> --reason "..."` | 队长每次结束前由它自己调用 |
`--leader` 接受智能体名字或 UUID其它 ID 从 `multica agent list --output json`、`multica workspace members --output json`、`multica squad list --output json` 拿。
`--leader` 接受智能体名字或 UUID其它 ID 从 `multica agent list --output json`、`multica workspace member list --output json`、`multica squad list --output json` 拿。
## 下一步

View File

@@ -13,9 +13,9 @@ func TestLegacyCompatibilityCommandsRemainAvailable(t *testing.T) {
}
})
t.Run("workspace members remains available", func(t *testing.T) {
if _, _, err := workspaceCmd.Find([]string{"members"}); err != nil {
t.Fatalf("expected workspace members command to exist: %v", err)
t.Run("workspace member list remains available", func(t *testing.T) {
if _, _, err := workspaceCmd.Find([]string{"member", "list"}); err != nil {
t.Fatalf("expected workspace member list command to exist: %v", err)
}
})

View File

@@ -1664,7 +1664,7 @@ func ambiguousAssigneeError(input string, matches []assigneeMatch) error {
// assignee_id) by looking it up against the workspace's members, agents, and
// (when allowed) squads. It is the deterministic counterpart to
// resolveAssignee: callers that already hold a UUID (e.g. agents reading IDs
// from `multica workspace members --output json`) should use this instead of
// from `multica workspace member list --output json`) should use this instead of
// round-tripping through name matching, which can be ambiguous in workspaces
// with overlapping names.
func resolveAssigneeByID(ctx context.Context, client *cli.APIClient, id string, kinds assigneeKinds) (string, string, error) {

View File

@@ -32,8 +32,13 @@ var workspaceGetCmd = &cobra.Command{
RunE: runWorkspaceGet,
}
var workspaceMembersCmd = &cobra.Command{
Use: "members [workspace-id]",
var workspaceMemberCmd = &cobra.Command{
Use: "member",
Short: "Manage workspace members",
}
var workspaceMemberListCmd = &cobra.Command{
Use: "list [workspace-id]",
Short: "List workspace members",
Args: cobra.MaximumNArgs(1),
RunE: runWorkspaceMembers,
@@ -60,24 +65,18 @@ var workspaceSwitchCmd = &cobra.Command{
RunE: runWorkspaceSwitch,
}
var workspaceCurrentCmd = &cobra.Command{
Use: "current",
Short: "Show the current default workspace",
RunE: runWorkspaceCurrent,
}
func init() {
workspaceCmd.AddCommand(workspaceListCmd)
workspaceCmd.AddCommand(workspaceGetCmd)
workspaceCmd.AddCommand(workspaceMembersCmd)
workspaceCmd.AddCommand(workspaceMemberCmd)
workspaceMemberCmd.AddCommand(workspaceMemberListCmd)
workspaceCmd.AddCommand(workspaceUpdateCmd)
workspaceCmd.AddCommand(workspaceSwitchCmd)
workspaceCmd.AddCommand(workspaceCurrentCmd)
workspaceListCmd.Flags().String("output", "table", "Output format: table or json")
workspaceListCmd.Flags().Bool("full-id", false, "Show full UUIDs in table output")
workspaceGetCmd.Flags().String("output", "json", "Output format: table or json")
workspaceMembersCmd.Flags().String("output", "table", "Output format: table or json")
workspaceCurrentCmd.Flags().String("output", "table", "Output format: table or json")
workspaceMemberListCmd.Flags().String("output", "table", "Output format: table or json")
workspaceUpdateCmd.Flags().String("name", "", "New workspace name")
workspaceUpdateCmd.Flags().String("description", "", "New description (decodes \\n, \\r, \\t, \\\\; pipe via --description-stdin to preserve literal backslashes)")
@@ -89,7 +88,7 @@ func init() {
}
// workspaceSummary is the subset of fields the CLI needs from /api/workspaces
// to drive list/switch/current. Keeping it here (instead of using the full
// to drive list and switch. Keeping it here (instead of using the full
// WorkspaceResponse) avoids a dependency on the handler package.
type workspaceSummary struct {
ID string `json:"id"`
@@ -98,8 +97,8 @@ type workspaceSummary struct {
}
// fetchWorkspaces lists all workspaces the authenticated user belongs to. It
// is shared by `list`, `switch`, and `current` so all three see the same
// access-controlled view of workspaces.
// is shared by `list` and `switch` so both see the same access-controlled view
// of workspaces.
func fetchWorkspaces(ctx context.Context, cmd *cobra.Command) ([]workspaceSummary, error) {
serverURL := resolveServerURL(cmd)
token := resolveToken(cmd)
@@ -135,6 +134,7 @@ func runWorkspaceList(cmd *cobra.Command, _ []string) error {
}
currentID := resolveWorkspaceID(cmd)
fullID, _ := cmd.Flags().GetBool("full-id")
w := tabwriter.NewWriter(os.Stdout, 0, 4, 2, ' ', 0)
fmt.Fprintln(w, "\tID\tNAME\tSLUG")
for _, ws := range workspaces {
@@ -142,7 +142,7 @@ func runWorkspaceList(cmd *cobra.Command, _ []string) error {
if ws.ID == currentID {
marker = "*"
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", marker, ws.ID, ws.Name, ws.Slug)
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", marker, displayID(ws.ID, fullID), ws.Name, ws.Slug)
}
if err := w.Flush(); err != nil {
return err
@@ -209,41 +209,6 @@ func runWorkspaceSwitch(cmd *cobra.Command, args []string) error {
return nil
}
func runWorkspaceCurrent(cmd *cobra.Command, _ []string) error {
currentID := resolveWorkspaceID(cmd)
if currentID == "" {
return fmt.Errorf("no default workspace set: use 'multica workspace switch <id|slug>' to pick one")
}
client, err := newAPIClient(cmd)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
var ws map[string]any
if err := client.GetJSON(ctx, "/api/workspaces/"+currentID, &ws); err != nil {
return fmt.Errorf("get workspace: %w", err)
}
output, _ := cmd.Flags().GetString("output")
if output == "json" {
return cli.PrintJSON(os.Stdout, ws)
}
headers := []string{"ID", "NAME", "SLUG", "ISSUE PREFIX"}
rows := [][]string{{
strVal(ws, "id"),
strVal(ws, "name"),
strVal(ws, "slug"),
strVal(ws, "issue_prefix"),
}}
cli.PrintTable(os.Stdout, headers, rows)
return nil
}
func workspaceIDFromArgs(cmd *cobra.Command, args []string) string {
if len(args) > 0 {
return args[0]

View File

@@ -624,7 +624,7 @@ func TestInjectRuntimeConfigAvailableCommandsCoreOnly(t *testing.T) {
"multica issue label list",
"multica issue subscriber list",
"multica label list",
"multica workspace members",
"multica workspace member list",
"multica agent list",
"multica squad list",
"multica issue runs",

View File

@@ -65,7 +65,7 @@ func buildQuickCreatePrompt(task Task) string {
// assignee
b.WriteString("- **assignee**:\n")
b.WriteString(" - When the user names someone (\"assign to X\" / \"@X\"), call `multica workspace members --output json`, `multica agent list --output json`, and `multica squad list --output json` and find the matching entity by display name. Squads are first-class assignees too — a squad name (e.g. \"Super Human\") routes work to the squad leader, who then delegates. On a clean unambiguous match, prefer `--assignee-id <uuid>` using the `user_id` (member) or `id` (agent or squad) from that JSON — UUID matching is exact and robust to name collisions in workspaces with overlapping names. `--assignee <name>` (fuzzy) is acceptable as a fallback when names are unambiguous. On no match or ambiguous match, do NOT pass either flag — instead append a final line to the description: `Unrecognized assignee: X`.\n")
b.WriteString(" - When the user names someone (\"assign to X\" / \"@X\"), call `multica workspace member list --output json`, `multica agent list --output json`, and `multica squad list --output json` and find the matching entity by display name. Squads are first-class assignees too — a squad name (e.g. \"Super Human\") routes work to the squad leader, who then delegates. On a clean unambiguous match, prefer `--assignee-id <uuid>` using the `user_id` (member) or `id` (agent or squad) from that JSON — UUID matching is exact and robust to name collisions in workspaces with overlapping names. `--assignee <name>` (fuzzy) is acceptable as a fallback when names are unambiguous. On no match or ambiguous match, do NOT pass either flag — instead append a final line to the description: `Unrecognized assignee: X`.\n")
b.WriteString(" - Treat bare @-routing as an assignee directive even when the user did not write the English word \"assign\". This includes Chinese imperatives like `让 @独立团 review 这个 PR`, `给 @X 处理`, or `交给 @X`; strip the leading `@`/`` before matching display names. Do not keep that routing wrapper or `@Name` in the description unless it is a true CC-style notification rather than ownership. If the matched entity is a squad, pass the squad's `id` as `--assignee-id`, not the leader agent's id.\n")
agentID := ""
agentName := ""