project-agent-client 0.1.0__tar.gz

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.
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: project-agent-client
3
+ Version: 0.1.0
4
+ Summary: Remote Project Agent command-line client
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+
8
+ # Project Agent CLI
9
+
10
+ Install the client with `python -m pip install project-agent-client`, or install
11
+ from this repository with `python -m pip install ./apps/cli`. Build a wheel
12
+ with `python -m build apps/cli` for distribution. The installed
13
+ entry point is `project-agent`; do not install it into the same environment as
14
+ the legacy backend package, which exposes the same command name.
15
+
16
+ This separately packaged client forwards Task, Query, Sprint and Proposal scene
17
+ commands, Quest Task handoffs, and explicitly supported Context, Doc Hub,
18
+ Meeting, Bootstrap, Attribute, Report, Monitoring and Scheduled commands.
19
+ The service must implement and authorize each operation; this client cannot
20
+ override a service-side rejection. Scene receipts and the stdout, stderr and
21
+ exit code of bridged commands are preserved. Administrative database and
22
+ upgrade operations are not exposed through this client. The server's database
23
+ location is never selected by a client-supplied path.
24
+
25
+ Configure `PROJECT_SERVICE_URL` and set `PROJECT_SERVICE_TOKEN` to a Magic
26
+ Campus API Key (access token), or to an existing service grant. The client sends
27
+ it as `Authorization: Bearer <token>`; it does not need Magic Campus account
28
+ API settings or service database credentials. HTTPS is required
29
+ except for a local loopback service. The client does not automatically retry a
30
+ write because a network failure can leave its outcome unknown. Legacy Doc Hub
31
+ JSON-file arguments are read on the client and sent as inline JSON; paths are
32
+ never read on the server. A Magic Campus API Key acts only for its verified
33
+ user; it cannot delegate another actor. Commands that mutate data require an
34
+ active user and the applicable project membership. Existing trusted Agent
35
+ grants can still supply a verified per-request actor.
36
+
37
+ With a personal Magic Campus API Key, Task and Sprint scene writes continue to
38
+ enforce their actor and business rules, and Task Quest commands remain available
39
+ with their own authorization checks. Proposal `list` is readable, while
40
+ `approve` and `reject` require a trusted service grant because the existing
41
+ policy does not yet enforce approval eligibility. For Story/People Quest
42
+ sources, personal keys may use status, query and detail; private query scopes
43
+ must match the caller's active Magic Campus identity. The source operations
44
+ ingest, publish-source, reconcile and history require a trusted service grant
45
+ and an active actor: history also updates the shared review journal. The grant
46
+ must bind the actor or explicitly allow per-request delegation. The CLI bridge supports the
47
+ read-only context commands, Doc Hub reads (`get-root`, `index`, `access target`,
48
+ `access plan`), `meeting parse-time`, `meeting tasks pending`, `mind read`,
49
+ `loop list`, and `monitoring evaluate-followups`; it also supports
50
+ `attribute apply` and `meeting save`, which check project membership.
51
+
52
+ Other bridge commands require a configured trusted service grant: context
53
+ member/identity management, onboarding, all bootstrap commands (including
54
+ `checkpoint` and `loop-state`, which update state), Doc Hub writes, report,
55
+ monitoring writes, scheduled runs, meeting task plan/placement/run/resume, and
56
+ loop writes. These commands do not yet all enforce caller authorization. The
57
+ service grant must have a bound actor or explicitly trust per-request delegation
58
+ for writes. A personal API Key cannot be used to bypass this restriction.
59
+
60
+ With a trusted service grant, meeting `tasks run` and `tasks resume` execute on the service, using the bound
61
+ actor. Existing plans must resolve to that actor; a resume batch with a plan
62
+ for another actor is rejected. Task writes use the service-side Quest handoff.
63
+ Meeting notifications are sent by the service's `magic-campus` executable, so
64
+ that executable and its account configuration must be available in the service
65
+ runtime; an absent executable leaves the notification pending for retry.
66
+ The meeting-assistant skill uses the same `PROJECT_SERVICE_URL` and
67
+ `PROJECT_SERVICE_TOKEN` for meeting state when those are set, and does not
68
+ fall back to a local database if the remote service is unavailable.
69
+
70
+ When an explicitly trusted Agent credential uses `trustActorRefs: true`, the
71
+ runtime must set `PROJECT_SERVICE_ACTOR_REF` to the current verified actor JSON
72
+ (e.g. `{"userId":"..."}`) for CLI bridge operations which need a user. A bound
73
+ actor credential does not need this variable. The service validates that actor
74
+ is active and rejects a conflicting actor in the command input.
@@ -0,0 +1,67 @@
1
+ # Project Agent CLI
2
+
3
+ Install the client with `python -m pip install project-agent-client`, or install
4
+ from this repository with `python -m pip install ./apps/cli`. Build a wheel
5
+ with `python -m build apps/cli` for distribution. The installed
6
+ entry point is `project-agent`; do not install it into the same environment as
7
+ the legacy backend package, which exposes the same command name.
8
+
9
+ This separately packaged client forwards Task, Query, Sprint and Proposal scene
10
+ commands, Quest Task handoffs, and explicitly supported Context, Doc Hub,
11
+ Meeting, Bootstrap, Attribute, Report, Monitoring and Scheduled commands.
12
+ The service must implement and authorize each operation; this client cannot
13
+ override a service-side rejection. Scene receipts and the stdout, stderr and
14
+ exit code of bridged commands are preserved. Administrative database and
15
+ upgrade operations are not exposed through this client. The server's database
16
+ location is never selected by a client-supplied path.
17
+
18
+ Configure `PROJECT_SERVICE_URL` and set `PROJECT_SERVICE_TOKEN` to a Magic
19
+ Campus API Key (access token), or to an existing service grant. The client sends
20
+ it as `Authorization: Bearer <token>`; it does not need Magic Campus account
21
+ API settings or service database credentials. HTTPS is required
22
+ except for a local loopback service. The client does not automatically retry a
23
+ write because a network failure can leave its outcome unknown. Legacy Doc Hub
24
+ JSON-file arguments are read on the client and sent as inline JSON; paths are
25
+ never read on the server. A Magic Campus API Key acts only for its verified
26
+ user; it cannot delegate another actor. Commands that mutate data require an
27
+ active user and the applicable project membership. Existing trusted Agent
28
+ grants can still supply a verified per-request actor.
29
+
30
+ With a personal Magic Campus API Key, Task and Sprint scene writes continue to
31
+ enforce their actor and business rules, and Task Quest commands remain available
32
+ with their own authorization checks. Proposal `list` is readable, while
33
+ `approve` and `reject` require a trusted service grant because the existing
34
+ policy does not yet enforce approval eligibility. For Story/People Quest
35
+ sources, personal keys may use status, query and detail; private query scopes
36
+ must match the caller's active Magic Campus identity. The source operations
37
+ ingest, publish-source, reconcile and history require a trusted service grant
38
+ and an active actor: history also updates the shared review journal. The grant
39
+ must bind the actor or explicitly allow per-request delegation. The CLI bridge supports the
40
+ read-only context commands, Doc Hub reads (`get-root`, `index`, `access target`,
41
+ `access plan`), `meeting parse-time`, `meeting tasks pending`, `mind read`,
42
+ `loop list`, and `monitoring evaluate-followups`; it also supports
43
+ `attribute apply` and `meeting save`, which check project membership.
44
+
45
+ Other bridge commands require a configured trusted service grant: context
46
+ member/identity management, onboarding, all bootstrap commands (including
47
+ `checkpoint` and `loop-state`, which update state), Doc Hub writes, report,
48
+ monitoring writes, scheduled runs, meeting task plan/placement/run/resume, and
49
+ loop writes. These commands do not yet all enforce caller authorization. The
50
+ service grant must have a bound actor or explicitly trust per-request delegation
51
+ for writes. A personal API Key cannot be used to bypass this restriction.
52
+
53
+ With a trusted service grant, meeting `tasks run` and `tasks resume` execute on the service, using the bound
54
+ actor. Existing plans must resolve to that actor; a resume batch with a plan
55
+ for another actor is rejected. Task writes use the service-side Quest handoff.
56
+ Meeting notifications are sent by the service's `magic-campus` executable, so
57
+ that executable and its account configuration must be available in the service
58
+ runtime; an absent executable leaves the notification pending for retry.
59
+ The meeting-assistant skill uses the same `PROJECT_SERVICE_URL` and
60
+ `PROJECT_SERVICE_TOKEN` for meeting state when those are set, and does not
61
+ fall back to a local database if the remote service is unavailable.
62
+
63
+ When an explicitly trusted Agent credential uses `trustActorRefs: true`, the
64
+ runtime must set `PROJECT_SERVICE_ACTOR_REF` to the current verified actor JSON
65
+ (e.g. `{"userId":"..."}`) for CLI bridge operations which need a user. A bound
66
+ actor credential does not need this variable. The service validates that actor
67
+ is active and rejects a conflicting actor in the command input.
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "project-agent-client"
7
+ version = "0.1.0"
8
+ description = "Remote Project Agent command-line client"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ dependencies = []
12
+
13
+ [project.scripts]
14
+ project-agent = "project_agent_client.cli:main"
15
+
16
+ [tool.setuptools.packages.find]
17
+ where = ["src"]
18
+
19
+ [tool.setuptools.package-data]
20
+ project_agent_client = ["help.json"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1 @@
1
+ """Client for the Project Backend Service."""
@@ -0,0 +1,170 @@
1
+ """Forward supported Project Agent commands to the backend service."""
2
+
3
+ import argparse
4
+ from importlib import metadata, resources
5
+ import json
6
+ import os
7
+ from pathlib import Path
8
+ import sys
9
+ from urllib.error import HTTPError, URLError
10
+ from urllib.parse import urlsplit
11
+ from urllib.request import HTTPRedirectHandler, Request, build_opener
12
+
13
+
14
+ class _NoRedirect(HTTPRedirectHandler):
15
+ def redirect_request(self, req, fp, code, msg, headers, newurl):
16
+ return None
17
+
18
+
19
+ def _open_url(request, timeout):
20
+ return build_opener(_NoRedirect()).open(request, timeout=timeout)
21
+
22
+
23
+ def _failure(code: str, message: str) -> dict:
24
+ return {"status": "failed", "results": [{"operationId": "request", "action": "transport", "status": "failed", "error": {"code": code, "message": message}}]}
25
+
26
+
27
+ _SCENES = {"task": ("apply", "maintain"), "query": ("apply",),
28
+ "sprint": ("apply", "rollover"), "proposal": ("apply",),
29
+ "quest": ("publish-task", "sync-task", "status", "pending", "reconcile-task-create")}
30
+ _ENDPOINTS = {"task": "tasks", "query": "query", "sprint": "sprints", "proposal": "proposals", "quest": "quest"}
31
+ _BRIDGE = {
32
+ ("attribute", "apply"), ("onboarding", "apply"),
33
+ *(("bootstrap", name) for name in ("apply", "init", "record-loop-result", "checkpoint", "loop-state", "mark")),
34
+ *(("context", name) for name in ("resolve-group", "resolve-binding", "project-binding", "list-relationships", "resolve-user", "search-user", "get-user", "add-project-member", "set-project-owner", "upsert-user-identity")),
35
+ *(("doc-hub", name) for name in ("set-provider", "bind-space", "bind-root", "get-root", "register", "sync-manifest", "remove", "classify", "relate", "read", "propose-updates", "index")),
36
+ *(("doc-hub", "access", name) for name in ("target", "record", "plan")),
37
+ *(("meeting", name) for name in ("save", "parse-time")),
38
+ *(("meeting", "tasks", name) for name in ("pending", "plan", "placement", "run", "resume")),
39
+ *(("report", name) for name in ("apply", "build")),
40
+ *(("monitoring", name) for name in ("plan", "run", "evaluate-followups")),
41
+ ("scheduled", "run"), ("mind", "read"),
42
+ *(("loop", name) for name in ("list", "record-result", "enqueue-bootstrap")),
43
+ }
44
+ _INLINE_JSON_FLAGS = {"--payload-json", "--default-policy-json", "--manifest-json", "--content-json"}
45
+ _TIMEOUT_SECONDS = 200 # The service allows up to 180 seconds; never auto-retry a write.
46
+ _MAX_INLINE_BYTES = 4 * 1024 * 1024
47
+
48
+
49
+ def _command(argv):
50
+ # Longest matching fixed path wins (e.g. meeting tasks plan).
51
+ for command in sorted(_BRIDGE, key=len, reverse=True):
52
+ if tuple(argv[:len(command)]) == command:
53
+ return command, argv[len(command):]
54
+ return None, []
55
+
56
+
57
+ def _inline_file_arguments(arguments):
58
+ """Read legacy JSON-file options on the client, never on the server."""
59
+ output = list(arguments)
60
+ for index, token in enumerate(output):
61
+ option, sep, value = token.partition('=')
62
+ if option not in _INLINE_JSON_FLAGS:
63
+ continue
64
+ target = index if sep else index + 1
65
+ if target >= len(output):
66
+ continue
67
+ raw = value if sep else output[target]
68
+ if raw.lstrip().startswith(('{', '[')):
69
+ continue
70
+ path = Path(raw).expanduser()
71
+ if path.is_file():
72
+ if path.stat().st_size > _MAX_INLINE_BYTES:
73
+ raise ValueError('JSON input file is too large')
74
+ content = path.read_text(encoding='utf-8')
75
+ output[target] = option + '=' + content if sep else content
76
+ return output
77
+
78
+
79
+ def run(argv: list[str], stdin, stdout, environ, open_url=_open_url, stderr=None) -> int:
80
+ stderr = stderr if stderr is not None else sys.stderr
81
+ if argv == ['--version']:
82
+ try:
83
+ version = metadata.version('project-agent-client')
84
+ except metadata.PackageNotFoundError:
85
+ version = '0.1.0'
86
+ stdout.write(version + '\n')
87
+ return 0
88
+ if argv and argv[-1] == '--help':
89
+ command_name = ' '.join(argv[:-1])
90
+ if not command_name:
91
+ names = sorted(set(_SCENES) | {command[0] for command in _BRIDGE})
92
+ stdout.write('Usage: project-agent [OPTIONS] COMMAND [ARGS]...\n\nCommands: ' + ', '.join(names) + '\n')
93
+ return 0
94
+ if command_name == 'quest':
95
+ stdout.write('Usage: project-agent quest COMMAND [ARGS]...\n\nCommands: publish-task, sync-task, status, pending, reconcile-task-create\n')
96
+ return 0
97
+ if command_name in {'quest publish-task', 'quest sync-task', 'quest status', 'quest pending', 'quest reconcile-task-create'}:
98
+ stdout.write('Usage: project-agent ' + command_name + ' --input @-\n')
99
+ return 0
100
+ catalog = json.loads(resources.files('project_agent_client').joinpath('help.json').read_text(encoding='utf-8'))
101
+ if command_name in catalog:
102
+ stdout.write(catalog[command_name])
103
+ return 0
104
+ command, arguments = _command(argv)
105
+ is_bridge = command is not None
106
+ if not is_bridge:
107
+ parser = argparse.ArgumentParser(prog="project-agent")
108
+ commands = parser.add_subparsers(dest="command", required=True)
109
+ for name, supported_actions in _SCENES.items():
110
+ subparser = commands.add_parser(name)
111
+ actions = subparser.add_subparsers(dest="action", required=True)
112
+ for action in supported_actions:
113
+ subcommand = actions.add_parser(action)
114
+ subcommand.add_argument("--input", required=True, choices=["@-"])
115
+ args = parser.parse_args(argv)
116
+
117
+ base_url = environ.get("PROJECT_SERVICE_URL", "").strip().rstrip("/")
118
+ token = environ.get("PROJECT_SERVICE_TOKEN", "").strip()
119
+ try:
120
+ parsed = urlsplit(base_url)
121
+ valid_url = (parsed.scheme == "https" or (parsed.scheme == "http" and parsed.hostname in {"127.0.0.1", "localhost", "::1"})) and bool(parsed.netloc) and not (parsed.username or parsed.password or parsed.query or parsed.fragment)
122
+ except ValueError:
123
+ valid_url = False
124
+ if not valid_url or not token:
125
+ receipt = _failure("service_configuration_error", "Project service URL or token is not configured.")
126
+ else:
127
+ try:
128
+ if is_bridge:
129
+ arguments = _inline_file_arguments(arguments)
130
+ # Most legacy commands do not read stdin. Avoid blocking the caller.
131
+ wants_stdin = any(item == '--input' or item.startswith('--input=') for item in arguments)
132
+ body = {"command": list(command), "arguments": arguments, "stdin": stdin.read() if wants_stdin else ''}
133
+ if environ.get('PROJECT_SERVICE_ACTOR_REF', '').strip():
134
+ body['actorRef'] = json.loads(environ['PROJECT_SERVICE_ACTOR_REF'])
135
+ path = '/v1/cli/execute'
136
+ else:
137
+ body = json.loads(stdin.read())
138
+ path = f"/v1/{_ENDPOINTS[args.command]}/{args.action}"
139
+ payload = json.dumps(body, ensure_ascii=False).encode("utf-8")
140
+ except (ValueError, UnicodeError, OSError):
141
+ receipt = _failure("cli_contract_mismatch", "Input is invalid or a local input file could not be read.")
142
+ else:
143
+ request = Request(base_url + path, data=payload, method="POST", headers={"Authorization": "Bearer " + token, "Content-Type": "application/json", "Accept": "application/json"})
144
+ try:
145
+ with open_url(request, timeout=_TIMEOUT_SECONDS) as response:
146
+ receipt = json.load(response)
147
+ if is_bridge:
148
+ if not isinstance(receipt, dict) or not isinstance(receipt.get('stdout'), str) or not isinstance(receipt.get('stderr'), str) or type(receipt.get('exitCode')) is not int:
149
+ receipt = _failure("invalid_service_response", "Project service returned an invalid command result.")
150
+ else:
151
+ stdout.write(receipt['stdout'])
152
+ stderr.write(receipt['stderr'])
153
+ return receipt['exitCode']
154
+ else:
155
+ valid_statuses = {"ok", "clarification_required", "failed", "partial_success"}
156
+ if not isinstance(receipt, dict) or (args.action != "maintain" and receipt.get("status") not in valid_statuses) or (args.action == "maintain" and not isinstance(receipt.get("status"), str)):
157
+ receipt = _failure("invalid_service_response", "Project service returned an invalid receipt.")
158
+ except HTTPError as exc:
159
+ receipt = _failure("service_request_rejected", f"Project service rejected the request (HTTP {exc.code}).")
160
+ except (URLError, TimeoutError, OSError, ValueError):
161
+ receipt = _failure("service_unavailable", "Project service could not be reached or returned an invalid response; the write outcome may be unknown.")
162
+
163
+ stdout.write(json.dumps(receipt, ensure_ascii=False) + "\n")
164
+ if not is_bridge and args.action == "maintain" and receipt.get("status") != "failed":
165
+ return 0
166
+ return {"ok": 0, "clarification_required": 2}.get(receipt.get("status"), 1)
167
+
168
+
169
+ def main() -> None:
170
+ raise SystemExit(run(sys.argv[1:], sys.stdin, sys.stdout, os.environ))
@@ -0,0 +1,75 @@
1
+ {
2
+ "attribute": " \n Usage: project-agent attribute [OPTIONS] COMMAND [ARGS]... \n \n 发现、读取和更新所有业务实体的扩展属性。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ apply │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
3
+ "attribute apply": " \n Usage: project-agent attribute apply [OPTIONS] \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --input <str> [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
4
+ "bootstrap": " \n Usage: project-agent bootstrap [OPTIONS] COMMAND [ARGS]... \n \n 初始化项目、Owner、项目群 binding 和参与人等基础数据。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ apply 一次完成 bootstrap init/ack、ledger 写入和下一 │\n│ checkpoint 读取。 │\n│ init 创建项目基础记录,并可同时写入项目群 binding │\n│ 与初始参与人。 │\n│ record-loop-result 记录一次 bootstrap loop 动作的 success/failed/skipped │\n│ 结果;仅作复盘和下次 loop-state 诊断,不决定业务下一步。 │\n│ checkpoint 读取并校准 bootstrap │\n│ 阶段状态:返回群绑定、对齐字段缺口、引导/确认消息落账和… │\n│ loop-state 读取 bootstrap checkpoint,登记本轮 loop attempt,并返 … │\n│ stuck/blocker 诊断;下一步动作仍由 │\n│ project-lifecycle-manager skill 发起。 │\n│ mark 登记 bootstrap 里程碑消息已发出(alignment_intro_sent / │\n│ execution_confirmation_sent / kickoff_sent),并返回最 … │\n│ checkpoint。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
5
+ "bootstrap apply": " \n Usage: project-agent bootstrap apply [OPTIONS] \n \n 一次完成 bootstrap init/ack、ledger 写入和下一 checkpoint 读取。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --input <str> 固定为 @-,从 stdin 读取 JSON。 [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
6
+ "bootstrap checkpoint": " \n Usage: project-agent bootstrap checkpoint [OPTIONS] \n \n 读取并校准 bootstrap \n 阶段状态:返回群绑定、对齐字段缺口、引导/确认消息落账和下一步动作。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 ID │\n│ 或外部文档 ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
7
+ "bootstrap init": " \n Usage: project-agent bootstrap init [OPTIONS] \n \n 创建项目基础记录,并可同时写入项目群 binding 与初始参与人。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical │\n│ projectId。不要直接传群 ID 或外部文 … │\n│ ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ * --project-name <str> 项目展示名。写入 project 基础信息。 │\n│ [required] │\n│ * --owner-user-id <str> 项目 Owner 的内部 userId。 [required] │\n│ * --primary-goal <str> 项目当前主目标,用于 Project Mind │\n│ 和文档初始内容。 │\n│ [required] │\n│ --target-release-date <str> 目标发布日期,格式 YYYY-MM-DD。 │\n│ --timezone <str> 项目默认时区,未显式传入时默认 │\n│ Asia/Shanghai。 │\n│ [default: Asia/Shanghai] │\n│ --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 │\n│ --provider。 │\n│ --project-group-id <str> 要绑定的项目群 ID。传入后会按 channe… │\n│ 规则写入 project_group binding。 │\n│ --participant-user-id <str> 额外参与人 userId。可重复传多次。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
8
+ "bootstrap loop-state": " \n Usage: project-agent bootstrap loop-state [OPTIONS] \n \n 读取 bootstrap checkpoint,登记本轮 loop attempt,并返回 stuck/blocker \n 诊断;下一步动作仍由 project-lifecycle-manager skill 发起。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical │\n│ projectId。不要直接传群 ID 或外部文档 │\n│ ID;只有外部信号时先用 context 命令解析。 │\n│ [required] │\n│ --stuck-threshold <int> 同一 nextAction 被 loop-state │\n│ 看到多少次后判定 stuck。 │\n│ [default: 3] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
9
+ "bootstrap mark": " \n Usage: project-agent bootstrap mark [OPTIONS] \n \n 登记 bootstrap 里程碑消息已发出(alignment_intro_sent / \n execution_confirmation_sent / kickoff_sent),并返回最新 checkpoint。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 ID │\n│ 或外部文档 ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ * --milestone <str> 已发出的里程碑消息:alignment_intro_sent / │\n│ execution_confirmation_sent / kickoff_sent。 │\n│ [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
10
+ "bootstrap record-loop-result": " \n Usage: project-agent bootstrap record-loop-result [OPTIONS] \n \n 记录一次 bootstrap loop 动作的 success/failed/skipped 结果;仅作复盘和下次 \n loop-state 诊断,不决定业务下一步。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 │\n│ ID 或外部文档 ID;只有外部信号时先用 contex… │\n│ 命令解析。 │\n│ [required] │\n│ * --next-action <str> 本次执行对应的 checkpoint nextAction。 │\n│ [required] │\n│ * --outcome <str> 执行结果:success / failed / skipped。 │\n│ [required] │\n│ --stage <str> 执行阶段,例如 provider_write / fetch_verif… │\n│ / mark。 │\n│ --reason <str> 执行结果的简短原因。 │\n│ --details-json <str> 可选结构化细节 JSON。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
11
+ "context": " \n Usage: project-agent context [OPTIONS] COMMAND [ARGS]... \n \n 做群绑定、成员/身份管理和项目上下文解析;项目事实读取走 query apply。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ resolve-group 按群 ID │\n│ 查询绑定到哪个项目,常用于把外部群信号解析成内部 │\n│ projectId。 │\n│ resolve-binding 按来源信号组合解析 binding,适合已有 chat-id 或 │\n│ subject 的场景。 │\n│ project-binding 按项目 ID 读取项目绑定的群/会话信息。 │\n│ list-relationships 读取项目当前关系;传 --include-closed │\n│ 可查询历史关闭关系。 │\n│ resolve-user 把外部用户标识解析成内部 userId。 │\n│ search-user 按本地展示名或已保存身份资料模糊搜索内部用户;只查本 … │\n│ get-user 读取内部用户及其外部身份映射。 │\n│ add-project-member 把一个内部 userId 添加为项目 active member。 │\n│ set-project-owner 设置项目 Owner;无现任 Owner 时可接受历史提名。 │\n│ upsert-user-identity 写入或更新外部身份与内部 userId 的映射。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
12
+ "context add-project-member": " \n Usage: project-agent context add-project-member [OPTIONS] \n \n 把一个内部 userId 添加为项目 active member。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --project-id <str> 目标项目的 canonical projectId。 │\n│ --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 --provider。 │\n│ --group-id <str> 群聊 ID 信号。用于 resolve-group 或 │\n│ project_group 边界校验,不是内部 projectId。 │\n│ * --user-id <str> 要加入项目的内部 canonical userId。 [required] │\n│ --attributes <str> 扩展属性 JSON 数组;未知属性会自动创建。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
13
+ "context get-user": " \n Usage: project-agent context get-user [OPTIONS] \n \n 读取内部用户及其外部身份映射。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --user-id <str> 内部 canonical userId。 [required] │\n│ --project-id <str> 提供项目作用域时返回该项目内的用户扩展属性。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
14
+ "context list-relationships": " \n Usage: project-agent context list-relationships [OPTIONS] \n \n 读取项目当前关系;传 --include-closed 可查询历史关闭关系。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传 … │\n│ ID 或外部文档 ID;只有外部信号时先用 │\n│ context 命令解析。 │\n│ [required] │\n│ --include-closed 同时返回已关闭的历史关系。 │\n│ --source-kind <str> 调用来源类型。传 project_group │\n│ 时会启用来源群与 projectId 的边界校验。 │\n│ --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 │\n│ --provider。 │\n│ --delivery-to <str> 来源投递目标标识。和 chat-id / subject / │\n│ group-id 一起参与 project_group binding │\n│ 解析。 │\n│ --chat-id <str> 聊天会话 ID。Feishu 项目群常见值为 oc_xxx │\n│ 或 chat:oc_xxx;CLI 会按既有规则做规范化。 │\n│ --subject <str> 来源主体 ID,例如会话主体或用户主体。仅在 │\n│ binding 解析需要该信号时传入。 │\n│ --group-id <str> 群聊 ID 信号。用于 resolve-group 或 │\n│ project_group 边界校验,不是内部 │\n│ projectId。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
15
+ "context project-binding": " \n Usage: project-agent context project-binding [OPTIONS] \n \n 按项目 ID 读取项目绑定的群/会话信息。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 I… │\n│ 或外部文档 ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ --source-kind <str> 调用来源类型。传 project_group │\n│ 时会启用来源群与 projectId 的边界校验。 │\n│ --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 --provider。 │\n│ --delivery-to <str> 来源投递目标标识。和 chat-id / subject / │\n│ group-id 一起参与 project_group binding │\n│ 解析。 │\n│ --chat-id <str> 聊天会话 ID。Feishu 项目群常见值为 oc_xxx 或 │\n│ chat:oc_xxx;CLI 会按既有规则做规范化。 │\n│ --subject <str> 来源主体 ID,例如会话主体或用户主体。仅在 │\n│ binding 解析需要该信号时传入。 │\n│ --group-id <str> 群聊 ID 信号。用于 resolve-group 或 │\n│ project_group 边界校验,不是内部 projectId。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
16
+ "context resolve-binding": " \n Usage: project-agent context resolve-binding [OPTIONS] \n \n 按来源信号组合解析 binding,适合已有 chat-id 或 subject 的场景。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 --provider。 │\n│ [default: feishu] │\n│ --binding-kind <str> binding 类型。常见值为 project_group。 │\n│ --delivery-to <str> 来源投递目标标识。和 chat-id / subject / │\n│ group-id 一起参与 project_group binding 解析。 │\n│ --chat-id <str> 聊天会话 ID。Feishu 项目群常见值为 oc_xxx 或 │\n│ chat:oc_xxx;CLI 会按既有规则做规范化。 │\n│ --subject <str> 来源主体 ID,例如会话主体或用户主体。仅在 │\n│ binding 解析需要该信号时传入。 │\n│ --group-id <str> 群聊 ID 信号。用于 resolve-group 或 │\n│ project_group 边界校验,不是内部 projectId。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
17
+ "context resolve-group": " \n Usage: project-agent context resolve-group [OPTIONS] \n \n 按群 ID 查询绑定到哪个项目,常用于把外部群信号解析成内部 projectId。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --group-id <str> 外部群 ID,例如 Feishu 的 oc_xxx 或 │\n│ chat:oc_xxx。 │\n│ [required] │\n│ --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 │\n│ --provider。 │\n│ [default: feishu] │\n│ --binding-kind <str> binding 类型。留空时按命令默认解析,常见为 │\n│ project_group。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
18
+ "context resolve-user": " \n Usage: project-agent context resolve-user [OPTIONS] \n \n 把外部用户标识解析成内部 userId。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 │\n│ --provider。 │\n│ [required] │\n│ * --external-subject-id <str> 外部平台上的用户主体 ID,例如 │\n│ open_id。 │\n│ [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
19
+ "context search-user": " \n Usage: project-agent context search-user [OPTIONS] \n \n 按本地展示名或已保存身份资料模糊搜索内部用户;只查本地数据,只读,不写库。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 --provider。 │\n│ [required] │\n│ * --query <str> 本地搜索文本,例如姓名、昵称、别名、邮箱或手机号… │\n│ [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
20
+ "context set-project-owner": " \n Usage: project-agent context set-project-owner [OPTIONS] \n \n 设置项目 Owner;无现任 Owner 时可接受历史提名。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 │\n│ ID 或外部文档 ID;只有外部信号时先用 │\n│ context 命令解析。 │\n│ [required] │\n│ * --user-id <str> 目标 Owner 的 canonical userId。 [required] │\n│ * --actor-user-id <str> 执行这次正式写操作的内部 userId。需要审计 … │\n│ owner 校验的命令应显式传入。 │\n│ [required] │\n│ --accept 以被提名用户身份接受无现任 Owner │\n│ 时创建的提名。 │\n│ --source-kind <str> 调用来源类型。传 project_group │\n│ 时会启用来源群与 projectId 的边界校验。 │\n│ --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 │\n│ --provider。 │\n│ --delivery-to <str> 来源投递目标标识。和 chat-id / subject / │\n│ group-id 一起参与 project_group binding │\n│ 解析。 │\n│ --chat-id <str> 聊天会话 ID。Feishu 项目群常见值为 oc_xxx │\n│ 或 chat:oc_xxx;CLI 会按既有规则做规范化。 │\n│ --subject <str> 来源主体 ID,例如会话主体或用户主体。仅在 │\n│ binding 解析需要该信号时传入。 │\n│ --group-id <str> 群聊 ID 信号。用于 resolve-group 或 │\n│ project_group 边界校验,不是内部 │\n│ projectId。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
21
+ "context upsert-user-identity": " \n Usage: project-agent context upsert-user-identity [OPTIONS] \n \n 写入或更新外部身份与内部 userId 的映射。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 │\n│ --provider。 │\n│ [required] │\n│ * --external-subject-id <str> 外部平台上的用户主体 ID,例如 │\n│ open_id。 │\n│ [required] │\n│ * --display-name <str> 展示名。 [required] │\n│ --user-id <str> 已有内部 │\n│ userId。留空时由后端分配或复用。 │\n│ --profile-json <str> 额外身份资料 JSON 字符串。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
22
+ "doc-hub": " \n Usage: project-agent doc-hub [OPTIONS] COMMAND [ARGS]... \n \n 管理 Doc Hub 根节点、空间和文档条目。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ set-provider 经用户二次确认后切换新文档默认平台;不迁移已有文档。 │\n│ bind-space 把项目绑定到某个外部文档空间。 │\n│ bind-root 配置某个 provider 的 Doc Hub 根节点。 │\n│ get-root 读取某个 provider 当前配置的 Doc Hub 根节点。 │\n│ register 注册一条外部文档记录到 Doc Hub。 │\n│ remove 从 Doc Hub 索引移除文档条目,不删除外部文件。 │\n│ classify 给 Doc Hub 条目设置逻辑类型。 │\n│ relate 为两个 Doc Hub 条目建立关系。 │\n│ index 构建项目的 Doc Hub 索引视图。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
23
+ "doc-hub access": " \n Usage: project-agent doc-hub access [OPTIONS] COMMAND [ARGS]... \n \n 管理项目文档空间访问授权记录。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ target 解析项目成员对应的文档空间授权目标。 │\n│ record 记录一次远端文档空间权限检查或授权结果。 │\n│ plan 列出需要运行时逐个检查 Drive 权限的项目成员。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
24
+ "doc-hub access plan": " \n Usage: project-agent doc-hub access plan [OPTIONS] \n \n 列出需要运行时逐个检查 Drive 权限的项目成员。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 ID │\n│ 或外部文档 ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ --provider <str> 外部文档平台 provider,例如 feishu 或 │\n│ google-drive。用于文档与 doc-hub 归属;doc-hu… │\n│ register/sync-manifest │\n│ 未传时沿用已登记同一文档的 provider,否则与 │\n│ bind-space │\n│ 一样使用项目默认平台;其他命令按各自默认值。 │\n│ [default: feishu] │\n│ * --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 --provider。 │\n│ [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
25
+ "doc-hub access record": " \n Usage: project-agent doc-hub access record [OPTIONS] \n \n 记录一次远端文档空间权限检查或授权结果。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical │\n│ projectId。不要直接传群 ID 或外部文 … │\n│ ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ --provider <str> 外部文档平台 provider,例如 feishu … │\n│ google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifest │\n│ 未传时沿用已登记同一文档的 │\n│ provider,否则与 bind-space │\n│ 一样使用项目默认平台;其他命令按各自… │\n│ [default: feishu] │\n│ * --external-space-id <str> provider 侧空间或目录 ID。 [required] │\n│ * --principal-email <str> Google Drive 权限目标 email。 │\n│ [required] │\n│ --role <str> 目标权限角色,默认 writer。 │\n│ [default: writer] │\n│ * --status <str> granted、already_writer、upgraded 或 │\n│ failed。 │\n│ [required] │\n│ * --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 │\n│ --provider。 │\n│ [required] │\n│ * --external-subject-id <str> 渠道侧用户唯一 ID。 [required] │\n│ --user-id <str> Project Agent 内部 userId。 │\n│ --principal-type <str> 授权主体类型,默认 user。 │\n│ [default: user] │\n│ --permission-id <str> provider 侧 permission ID。 │\n│ --last-error <str> 失败原因。 │\n│ --payload-json <str> provider-neutral 授权细节 JSON │\n│ 或文件路径。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
26
+ "doc-hub access target": " \n Usage: project-agent doc-hub access target [OPTIONS] \n \n 解析项目成员对应的文档空间授权目标。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical │\n│ projectId。不要直接传群 ID 或外部文 … │\n│ ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ --provider <str> 外部文档平台 provider,例如 feishu … │\n│ google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifest │\n│ 未传时沿用已登记同一文档的 │\n│ provider,否则与 bind-space │\n│ 一样使用项目默认平台;其他命令按各自… │\n│ [default: feishu] │\n│ * --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 │\n│ --provider。 │\n│ [required] │\n│ * --external-subject-id <str> 渠道侧用户唯一 ID。 [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
27
+ "doc-hub bind-root": " \n Usage: project-agent doc-hub bind-root [OPTIONS] \n \n 配置某个 provider 的 Doc Hub 根节点。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --provider <str> 外部文档平台 provider,例如 feishu 或 │\n│ google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifest │\n│ 未传时沿用已登记同一文档的 │\n│ provider,否则与 bind-space │\n│ 一样使用项目默认平台;其他命令按各自默 … │\n│ [default: feishu] │\n│ --external-root-id <str> 外部平台上的根节点 ID。 │\n│ --url <str> 根节点链接。 │\n│ --status <str> 根节点绑定状态,默认 active。 │\n│ [default: active] │\n│ --default-policy-json <str> 根节点默认策略 JSON 或 JSON 文件路径。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
28
+ "doc-hub bind-space": " \n Usage: project-agent doc-hub bind-space [OPTIONS] \n \n 把项目绑定到某个外部文档空间。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical │\n│ projectId。不要直接传群 ID 或外部文档 │\n│ ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ --provider <str> 外部文档平台 provider,例如 feishu 或 │\n│ google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifest │\n│ 未传时沿用已登记同一文档的 │\n│ provider,否则与 bind-space │\n│ 一样使用项目默认平台;其他命令按各自默… │\n│ --external-space-id <str> 外部平台上的空间 ID。 │\n│ --url <str> 外部空间链接。 │\n│ --status <str> 绑定状态,默认 active。 │\n│ [default: active] │\n│ --payload-json <str> provider-neutral 空间扩展信息 JSON │\n│ 或文件路径,例如 logicalTypeFolders │\n│ 子目录映射;省略时保留已有 payload。 │\n│ --attributes <str> 扩展属性 JSON │\n│ 数组;未知属性会自动创建。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
29
+ "doc-hub classify": " \n Usage: project-agent doc-hub classify [OPTIONS] \n \n 给 Doc Hub 条目设置逻辑类型。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 │\n│ ID 或外部文档 ID;只有外部信号时先用 contex… │\n│ 命令解析。 │\n│ [required] │\n│ * --entry-id <str> Doc Hub entryId。 [required] │\n│ * --logical-type <str> 目标逻辑分类,例如 project_planning / │\n│ decision_meeting。 │\n│ [required] │\n│ --provider <str> 外部文档平台 provider,例如 feishu 或 │\n│ google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifest │\n│ 未传时沿用已登记同一文档的 provider,否则与 │\n│ bind-space │\n│ 一样使用项目默认平台;其他命令按各自默认值。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
30
+ "doc-hub get-root": " \n Usage: project-agent doc-hub get-root [OPTIONS] \n \n 读取某个 provider 当前配置的 Doc Hub 根节点。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --provider <str> 外部文档平台 provider,例如 feishu 或 │\n│ google-drive。用于文档与 doc-hub 归属;doc-hub │\n│ register/sync-manifest 未传时沿用已登记同一文档的 │\n│ provider,否则与 bind-space │\n│ 一样使用项目默认平台;其他命令按各自默认值。 │\n│ [default: feishu] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
31
+ "doc-hub index": " \n Usage: project-agent doc-hub index [OPTIONS] \n \n 构建项目的 Doc Hub 索引视图。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --project-id <str> 项目内部 canonical projectId。不要直接传群 │\n│ ID 或外部文档 ID;只有外部信号时先用 │\n│ context 命令解析。 │\n│ --channel-group-id <str> MagicCampus 项目群 ID。未传 --project-id │\n│ 时用它反查项目;聊天渠道默认 magic-campus。 │\n│ --provider <str> 外部文档平台 provider,例如 feishu 或 │\n│ google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifest │\n│ 未传时沿用已登记同一文档的 provider,否则 … │\n│ bind-space │\n│ 一样使用项目默认平台;其他命令按各自默认值… │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
32
+ "doc-hub propose-updates": " \n Usage: project-agent doc-hub propose-updates [OPTIONS] \n \n 根据某次文档阅读结果生成待审批更新 Proposal。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 │\n│ ID 或外部文档 ID;只有外部信号时先用 │\n│ context 命令解析。 │\n│ [required] │\n│ * --read-id <str> doc-hub read 返回的 readId。 [required] │\n│ --actor-user-id <str> 执行这次正式写操作的内部 userId。需要审计 … │\n│ owner 校验的命令应显式传入。 │\n│ --provider <str> 外部文档平台 provider,例如 feishu 或 │\n│ google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifest │\n│ 未传时沿用已登记同一文档的 provider,否则 … │\n│ bind-space │\n│ 一样使用项目默认平台;其他命令按各自默认值… │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
33
+ "doc-hub read": " \n Usage: project-agent doc-hub read [OPTIONS] \n \n 记录一次外部文档阅读结果,供后续生成 Proposal。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical │\n│ projectId。不要直接传群 ID 或外部文档 │\n│ ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ * --entry-id <str> 要记录读取结果的 Doc Hub entryId。 │\n│ [required] │\n│ * --content-json <str> JSON 字符串或文件路径;必须是对象。 │\n│ [required] │\n│ --provider-revision <str> 外部平台的文档版本号或 revision 标识。 │\n│ --provider <str> 外部文档平台 provider,例如 feishu 或 │\n│ google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifest │\n│ 未传时沿用已登记同一文档的 │\n│ provider,否则与 bind-space │\n│ 一样使用项目默认平台;其他命令按各自默… │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
34
+ "doc-hub register": " \n Usage: project-agent doc-hub register [OPTIONS] \n \n 注册一条外部文档记录到 Doc Hub。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical │\n│ projectId。不要直接传群 ID │\n│ 或外部文档 ID;只有外部信号时先用 │\n│ context 命令解析。 │\n│ [required] │\n│ * --title <str> 文档标题,用于 Doc Hub 展示和索引。 │\n│ [required] │\n│ * --doc-kind <str> 文档类型,例如 user_document。 │\n│ [required] │\n│ --provider <str> 外部文档平台 provider,例如 feishu │\n│ 或 google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifes… │\n│ 未传时沿用已登记同一文档的 │\n│ provider,否则与 bind-space │\n│ 一样使用项目默认平台;其他命令按各 … │\n│ --logical-type <str> 逻辑分类,例如 project_planning / │\n│ decision_meeting。留空时由后端按 │\n│ doc_kind/title 推断。 │\n│ --source-logical-type <str> 由 provider │\n│ 目录位置解析出的逻辑分类;优先级高 … │\n│ --parent-folder-id <str> 文档所在 provider 父目录 ID,用于 │\n│ Doc Hub 目录同步和排障。 │\n│ --folder-path <str> 文档所在 provider 目录路径,用于 Do… │\n│ Hub 目录同步和排障。 │\n│ --document-source-kind <str> 文档来源类型,例如 user_provided 或 │\n│ agent_generated。 │\n│ [default: user_provided] │\n│ --external-doc-id <str> 外部平台文档 ID。 │\n│ --url <str> 外部文档访问链接。 │\n│ --document-id <str> 本地 │\n│ project_documents.documentId;系统 … │\n│ --sprint-id <str> 目标 Sprint 的 canonical │\n│ sprintId。留空时通常表示当前 │\n│ Sprint,具体以命令契约为准。 │\n│ --entry-key <str> 调用方提供的幂等 │\n│ key。留空时由后端生成。 │\n│ --status <str> 条目状态,默认 active。 │\n│ [default: active] │\n│ --summary <str> 文档摘要,供索引和后续 proposal │\n│ 判断使用。 │\n│ --attributes <str> 扩展属性 JSON │\n│ 数组;未知属性会自动创建。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
35
+ "doc-hub relate": " \n Usage: project-agent doc-hub relate [OPTIONS] \n \n 为两个 Doc Hub 条目建立关系。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 │\n│ ID 或外部文档 ID;只有外部信号时先用 │\n│ context 命令解析。 │\n│ [required] │\n│ * --from-entry-id <str> 关系起点 Doc Hub entryId。 [required] │\n│ * --to-entry-id <str> 关系终点 Doc Hub entryId。 [required] │\n│ * --relation-kind <str> 关系类型,例如 references / supersedes。 │\n│ [required] │\n│ --status <str> 关系状态,默认 active。 [default: active] │\n│ --provider <str> 外部文档平台 provider,例如 feishu 或 │\n│ google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifest │\n│ 未传时沿用已登记同一文档的 provider,否则 … │\n│ bind-space │\n│ 一样使用项目默认平台;其他命令按各自默认值… │\n│ --attributes <str> 扩展属性 JSON 数组;未知属性会自动创建。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
36
+ "doc-hub remove": " \n Usage: project-agent doc-hub remove [OPTIONS] \n \n 从 Doc Hub 索引移除文档条目,不删除外部文件。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 ID │\n│ 或外部文档 ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ * --entry-id <str> 要移除的 Doc Hub entryId。 [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
37
+ "doc-hub set-provider": " \n Usage: project-agent doc-hub set-provider [OPTIONS] \n \n 经用户二次确认后切换新文档默认平台;不迁移已有文档。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 │\n│ ID 或外部文档 ID;只有外部信号时先用 │\n│ context 命令解析。 │\n│ [required] │\n│ * --provider <str> 外部文档平台 provider,例如 feishu 或 │\n│ google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifest │\n│ 未传时沿用已登记同一文档的 provider,否则 … │\n│ bind-space │\n│ 一样使用项目默认平台;其他命令按各自默认值… │\n│ [required] │\n│ * --actor-user-id <str> 执行这次正式写操作的内部 userId。需要审计 … │\n│ owner 校验的命令应显式传入。 │\n│ [required] │\n│ --confirmed 仅在用户二次确认本次切换后传入。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
38
+ "doc-hub sync-manifest": " \n Usage: project-agent doc-hub sync-manifest [OPTIONS] \n \n 按 provider/host 扫描到的文档清单批量同步 Doc Hub registry。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 │\n│ ID 或外部文档 ID;只有外部信号时先用 │\n│ context 命令解析。 │\n│ [required] │\n│ --provider <str> 外部文档平台 provider,例如 feishu 或 │\n│ google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifest │\n│ 未传时沿用已登记同一文档的 provider,否则 … │\n│ bind-space │\n│ 一样使用项目默认平台;其他命令按各自默认值… │\n│ * --manifest-json <str> provider-neutral 文档清单 JSON 或 JSON │\n│ 文件路径。 │\n│ [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
39
+ "loop": " \n Usage: project-agent loop [OPTIONS] COMMAND [ARGS]... \n \n 运行和检查 backend-owned durable loop jobs。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ run-once Claim and run one due loop job. │\n│ list List loop jobs for a project. │\n│ record-result Record a runtime/provider execution result for a claimed │\n│ loop job. │\n│ enqueue-bootstrap Debug helper: enqueue bootstrap_continue for a project. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
40
+ "loop enqueue-bootstrap": " \n Usage: project-agent loop enqueue-bootstrap [OPTIONS] \n \n Debug helper: enqueue bootstrap_continue for a project. \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 ID │\n│ 或外部文档 ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ --reason <str> Debug enqueue reason. [default: manual_debug] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
41
+ "loop list": " \n Usage: project-agent loop list [OPTIONS] \n \n List loop jobs for a project. \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 ID │\n│ 或外部文档 ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
42
+ "loop record-result": " \n Usage: project-agent loop record-result [OPTIONS] \n \n Record a runtime/provider execution result for a claimed loop job. \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --loop-job-id <str> Loop job id returned by run-once/list. │\n│ [required] │\n│ * --outcome <str> success / retryable_failed / blocked. │\n│ [required] │\n│ --result-json <str> Optional result object JSON. │\n│ --reason <str> Failure or blocker reason. │\n│ --next-run-at <str> Retry timestamp for retryable_failed. │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
43
+ "meeting": " \n Usage: project-agent meeting [OPTIONS] COMMAND [ARGS]... \n \n 把录音转录和 Fireflies 会议写入 Project Agent 数据库。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ save 从 stdin 读取会议 JSON,在 Project Agent 数据库中建表并写入。 │\n│ parse-time 校验会议实际时间。可解析时输出 UTC 时间。 │\n│ tasks 已保存会议纪要的独立 Task 后处理与恢复。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
44
+ "meeting parse-time": " \n Usage: project-agent meeting parse-time [OPTIONS] \n \n 校验会议实际时间。可解析时输出 UTC 时间。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --value <str> ISO 8601 或 epoch。 [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
45
+ "meeting save": " \n Usage: project-agent meeting save [OPTIONS] \n \n 从 stdin 读取会议 JSON,在 Project Agent 数据库中建表并写入。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --input <str> 固定为 @-,从 stdin 读取 JSON。 [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
46
+ "meeting tasks": " \n Usage: project-agent meeting tasks [OPTIONS] COMMAND [ARGS]... \n \n 已保存会议纪要的独立 Task 后处理与恢复。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ pending 读取已保存纪要的待办后处理;包括 summarized,不访问 Fireflies。 │\n│ plan 从已保存纪要生成的 JSON 固化稳定计划;同一会议不可覆盖。 │\n│ run 单遍执行一场会议的持久化计划;逐项记录并汇总通知。 │\n│ placement 仅补充尚未创建的单项任务归属;保留原计划与幂等键。 │\n│ resume 周期入口:单遍续跑已有计划,并返回需要从已保存纪要提取的会议。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
47
+ "meeting tasks pending": " \n Usage: project-agent meeting tasks pending [OPTIONS] \n \n 读取已保存纪要的待办后处理;包括 summarized,不访问 Fireflies。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
48
+ "meeting tasks placement": " \n Usage: project-agent meeting tasks placement [OPTIONS] \n \n 仅补充尚未创建的单项任务归属;保留原计划与幂等键。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --meeting-id <str> [required] │\n│ * --input <str> [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
49
+ "meeting tasks plan": " \n Usage: project-agent meeting tasks plan [OPTIONS] \n \n 从已保存纪要生成的 JSON 固化稳定计划;同一会议不可覆盖。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --meeting-id <str> [required] │\n│ * --input <str> [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
50
+ "meeting tasks resume": " \n Usage: project-agent meeting tasks resume [OPTIONS] \n \n 周期入口:单遍续跑已有计划,并返回需要从已保存纪要提取的会议。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
51
+ "meeting tasks run": " \n Usage: project-agent meeting tasks run [OPTIONS] \n \n 单遍执行一场会议的持久化计划;逐项记录并汇总通知。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --meeting-id <str> [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
52
+ "mind": " \n Usage: project-agent mind [OPTIONS] COMMAND [ARGS]... \n \n 读取 Project Mind;正式写入通过 Task、Sprint、Proposal 场景命令执行。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ migrate-tasks 将全部未映射的 Local Task 注册到 Task Center;包含历史 │\n│ Sprint。 │\n│ read 读取 Project Mind 的 summary 或 full 视图;窄读走 query │\n│ apply。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
53
+ "mind read": " \n Usage: project-agent mind read [OPTIONS] COMMAND [ARGS]... \n \n 读取 Project Mind 的 summary 或 full 视图;窄读走 query apply。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --project-id <str> 项目内部 canonical projectId。不要直接传群 │\n│ ID 或外部文档 ID;只有外部信号时先用 │\n│ context 命令解析。 │\n│ --view <str> 读取视图:summary|full;summary │\n│ 返回当前摘要,full 返回完整 snapshot。 │\n│ [default: summary] │\n│ --source-kind <str> 调用来源类型。传 project_group │\n│ 时会启用来源群与 projectId 的边界校验。 │\n│ --channel <str> 聊天/消息渠道名,例如 feishu 或 │\n│ magic-campus。用于 binding │\n│ 解析和来源边界校验;文档平台请用 │\n│ --provider。 │\n│ --provider <str> 外部文档平台 provider,例如 feishu 或 │\n│ google-drive。用于文档与 doc-hub │\n│ 归属;doc-hub register/sync-manifest │\n│ 未传时沿用已登记同一文档的 provider,否则 … │\n│ bind-space │\n│ 一样使用项目默认平台;其他命令按各自默认值… │\n│ --channel-group-id <str> 已写入 project_bindings.external_channel_i… │\n│ 的群聊 ID。常用于直接按绑定项目群反查 │\n│ projectId 并读取 Project Mind。 │\n│ --delivery-to <str> 来源投递目标标识。和 chat-id / subject / │\n│ group-id 一起参与 project_group binding │\n│ 解析。 │\n│ --chat-id <str> 聊天会话 ID。Feishu 项目群常见值为 oc_xxx │\n│ 或 chat:oc_xxx;CLI 会按既有规则做规范化。 │\n│ --subject <str> 来源主体 ID,例如会话主体或用户主体。仅在 │\n│ binding 解析需要该信号时传入。 │\n│ --group-id <str> 群聊 ID 信号。用于 resolve-group 或 │\n│ project_group 边界校验,不是内部 │\n│ projectId。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
54
+ "monitoring": " \n Usage: project-agent monitoring [OPTIONS] COMMAND [ARGS]... \n \n 配置监控策略;内部评估仅供 scheduled/report 消费。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ plan 开启或关闭项目的监控策略。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
55
+ "monitoring evaluate-followups": " \n Usage: project-agent monitoring evaluate-followups [OPTIONS] \n \n 只计算 follow-up 结果,不写入 monitoring run。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 ID │\n│ 或外部文档 ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ * --at <str> 评估时点,使用 ISO-8601 时间字符串。 │\n│ [required] │\n│ --task-id <str> 目标 Task 的 canonical │\n│ taskId。只在命令支持单任务上下文时传入。 │\n│ --format <str> 输出格式。目前只支持 json。 [default: json] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
56
+ "monitoring plan": " \n Usage: project-agent monitoring plan [OPTIONS] \n \n 开启或关闭项目的监控策略。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical │\n│ projectId。不要直接传群 ID │\n│ 或外部文档 ID;只有外部信号时先用 │\n│ context 命令解析。 │\n│ [required] │\n│ --enabled --disabled 监控开关。传 --disabled │\n│ 可关闭该项目的监控计划。 │\n│ [default: enabled] │\n│ --attributes <str> 扩展属性 JSON │\n│ 数组;未知属性会自动创建。 │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
57
+ "monitoring run": " \n Usage: project-agent monitoring run [OPTIONS] \n \n 执行一次监控评估,并落库存档的 monitoring run。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical projectId。不要直接传群 ID │\n│ 或外部文档 ID;只有外部信号时先用 context │\n│ 命令解析。 │\n│ [required] │\n│ * --at <str> 评估时点,使用 ISO-8601 时间字符串。 │\n│ [required] │\n│ --task-id <str> 目标 Task 的 canonical │\n│ taskId。只在命令支持单任务上下文时传入。 │\n│ --format <str> 输出格式。目前只支持 json。 [default: json] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
58
+ "onboarding": " \n Usage: project-agent onboarding [OPTIONS] COMMAND [ARGS]... \n \n 一次处理项目群入群成员 onboarding。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ apply 从 stdin 接收入群事件 JSON,并返回 terminal receipt。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
59
+ "onboarding apply": " \n Usage: project-agent onboarding apply [OPTIONS] \n \n 从 stdin 接收入群事件 JSON,并返回 terminal receipt。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --input <str> 固定为 @-,从 stdin 读取 JSON。 [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
60
+ "proposal": " \n Usage: project-agent proposal [OPTIONS] COMMAND [ARGS]... \n \n 一次查询、批准或拒绝一个或多个 Proposal。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ apply 从 stdin 接收 Proposal scene JSON,并返回 terminal receipt。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
61
+ "proposal apply": " \n Usage: project-agent proposal apply [OPTIONS] \n \n 从 stdin 接收 Proposal scene JSON,并返回 terminal receipt。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --input <str> 固定为 @-,从 stdin 读取 JSON。 [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
62
+ "query": " \n Usage: project-agent query [OPTIONS] COMMAND [ARGS]... \n \n 一次读取项目、用户、Task、Sprint 或 roster 事实。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ apply 从 stdin 接收一个 Query scene JSON document,并返回 compact receipt。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
63
+ "query apply": " \n Usage: project-agent query apply [OPTIONS] \n \n 从 stdin 接收一个 Query scene JSON document,并返回 compact receipt。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --input <str> 固定为 @-,从 stdin 读取 JSON。 [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
64
+ "report": " \n Usage: project-agent report [OPTIONS] COMMAND [ARGS]... \n \n 基于当前项目状态生成结构化报告;Agent 热路径走 report apply。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ apply 从 stdin 接收 Reporting scene JSON,并返回 terminal receipt。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
65
+ "report apply": " \n Usage: project-agent report apply [OPTIONS] \n \n 从 stdin 接收 Reporting scene JSON,并返回 terminal receipt。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --input <str> 固定为 @-,从 stdin 读取 JSON。 [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
66
+ "report build": " \n Usage: project-agent report build [OPTIONS] \n \n 生成日报、follow-up 或 kickoff 报告,并返回结构化 JSON。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical │\n│ projectId。不要直接传 … │\n│ ID 或外部文档 │\n│ ID;只有外部信号时先用 │\n│ context 命令解析。 │\n│ [required] │\n│ * --report-kind <daily_status|followu 报告类型。常见值包括 │\n│ p|kickoff> daily_status、followup… │\n│ [required] │\n│ * --at <str> 报告生成时点,使用 │\n│ ISO-8601 时间字符串。 │\n│ [required] │\n│ --task-id <str> 目标 Task 的 canonical │\n│ taskId。只在命令支持单… │\n│ --monitoring-run-id <str> 复用 monitoring run │\n│ ID。 │\n│ --format <str> 输出格式。目前只支持 │\n│ json。 │\n│ [default: json] │\n│ --help Show this message and │\n│ exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
67
+ "scheduled": " \n Usage: project-agent scheduled [OPTIONS] COMMAND [ARGS]... \n \n 执行定时监控入口,输出适合调度器消费的标准结果。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ run 按给定时间窗执行一次定时监控,并输出 text 契约结果。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
68
+ "scheduled run": " \n Usage: project-agent scheduled run [OPTIONS] \n \n 按给定时间窗执行一次定时监控,并输出 text 契约结果。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --project-id <str> 项目内部 canonical │\n│ projectId。不要直接传群 I… │\n│ 或外部文档 │\n│ ID;只有外部信号时先用 │\n│ context 命令解析。 │\n│ [required] │\n│ * --window <09:00|10:00|11:00|15:00| 调度时间窗,取值为 09:00 … │\n│ 18:00> 10:00 / 11:00 / 15:00 / │\n│ 18:00。由上游 cron 决定。 │\n│ [required] │\n│ * --at <str> 本次调度触发时点,使用 │\n│ ISO-8601 时间字符串。 │\n│ [required] │\n│ --format <str> 输出格式。目前只支持 │\n│ text。 │\n│ [default: text] │\n│ --help Show this message and │\n│ exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
69
+ "sprint": " \n Usage: project-agent sprint [OPTIONS] COMMAND [ARGS]... \n \n 一次完成 Sprint、Workstream 与 tracking 治理。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ apply 从 stdin 接收 Sprint scene JSON document,并返回 terminal │\n│ receipt。 │\n│ rollover 规划或执行 Sprint 收口,并在命令内处理未完成 Task 去向。 │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
70
+ "sprint apply": " \n Usage: project-agent sprint apply [OPTIONS] \n \n 从 stdin 接收 Sprint scene JSON document,并返回 terminal receipt。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --input <str> 固定为 @-,从 stdin 读取 JSON。 [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
71
+ "sprint rollover": " \n Usage: project-agent sprint rollover [OPTIONS] \n \n 规划或执行 Sprint 收口,并在命令内处理未完成 Task 去向。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --input <str> 固定为 @-,从 stdin 读取 JSON。 [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
72
+ "task": " \n Usage: project-agent task [OPTIONS] COMMAND [ARGS]... \n \n 一次完成 Task create/update/complete/cancel/move/snooze。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n╭─ Commands ───────────────────────────────────────────────────────────────────╮\n│ apply 从 stdin 接收一个 Task scene JSON document,并返回 terminal │\n│ receipt。 │\n│ maintain Prepare current evidence, apply Agent synthesis (append or amend │\n│ WorkNodes), or reconcile an uncertain write. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
73
+ "task apply": " \n Usage: project-agent task apply [OPTIONS] \n \n 从 stdin 接收一个 Task scene JSON document,并返回 terminal receipt。 \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --input <str> 固定为 @-,从 stdin 读取 JSON。 [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n",
74
+ "task maintain": " \n Usage: project-agent task maintain [OPTIONS] \n \n Prepare current evidence, apply Agent synthesis (append or amend WorkNodes), \n or reconcile an uncertain write. \n \n╭─ Options ────────────────────────────────────────────────────────────────────╮\n│ * --input <str> [required] │\n│ --help Show this message and exit. │\n╰──────────────────────────────────────────────────────────────────────────────╯\n\n"
75
+ }
@@ -0,0 +1,74 @@
1
+ Metadata-Version: 2.4
2
+ Name: project-agent-client
3
+ Version: 0.1.0
4
+ Summary: Remote Project Agent command-line client
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+
8
+ # Project Agent CLI
9
+
10
+ Install the client with `python -m pip install project-agent-client`, or install
11
+ from this repository with `python -m pip install ./apps/cli`. Build a wheel
12
+ with `python -m build apps/cli` for distribution. The installed
13
+ entry point is `project-agent`; do not install it into the same environment as
14
+ the legacy backend package, which exposes the same command name.
15
+
16
+ This separately packaged client forwards Task, Query, Sprint and Proposal scene
17
+ commands, Quest Task handoffs, and explicitly supported Context, Doc Hub,
18
+ Meeting, Bootstrap, Attribute, Report, Monitoring and Scheduled commands.
19
+ The service must implement and authorize each operation; this client cannot
20
+ override a service-side rejection. Scene receipts and the stdout, stderr and
21
+ exit code of bridged commands are preserved. Administrative database and
22
+ upgrade operations are not exposed through this client. The server's database
23
+ location is never selected by a client-supplied path.
24
+
25
+ Configure `PROJECT_SERVICE_URL` and set `PROJECT_SERVICE_TOKEN` to a Magic
26
+ Campus API Key (access token), or to an existing service grant. The client sends
27
+ it as `Authorization: Bearer <token>`; it does not need Magic Campus account
28
+ API settings or service database credentials. HTTPS is required
29
+ except for a local loopback service. The client does not automatically retry a
30
+ write because a network failure can leave its outcome unknown. Legacy Doc Hub
31
+ JSON-file arguments are read on the client and sent as inline JSON; paths are
32
+ never read on the server. A Magic Campus API Key acts only for its verified
33
+ user; it cannot delegate another actor. Commands that mutate data require an
34
+ active user and the applicable project membership. Existing trusted Agent
35
+ grants can still supply a verified per-request actor.
36
+
37
+ With a personal Magic Campus API Key, Task and Sprint scene writes continue to
38
+ enforce their actor and business rules, and Task Quest commands remain available
39
+ with their own authorization checks. Proposal `list` is readable, while
40
+ `approve` and `reject` require a trusted service grant because the existing
41
+ policy does not yet enforce approval eligibility. For Story/People Quest
42
+ sources, personal keys may use status, query and detail; private query scopes
43
+ must match the caller's active Magic Campus identity. The source operations
44
+ ingest, publish-source, reconcile and history require a trusted service grant
45
+ and an active actor: history also updates the shared review journal. The grant
46
+ must bind the actor or explicitly allow per-request delegation. The CLI bridge supports the
47
+ read-only context commands, Doc Hub reads (`get-root`, `index`, `access target`,
48
+ `access plan`), `meeting parse-time`, `meeting tasks pending`, `mind read`,
49
+ `loop list`, and `monitoring evaluate-followups`; it also supports
50
+ `attribute apply` and `meeting save`, which check project membership.
51
+
52
+ Other bridge commands require a configured trusted service grant: context
53
+ member/identity management, onboarding, all bootstrap commands (including
54
+ `checkpoint` and `loop-state`, which update state), Doc Hub writes, report,
55
+ monitoring writes, scheduled runs, meeting task plan/placement/run/resume, and
56
+ loop writes. These commands do not yet all enforce caller authorization. The
57
+ service grant must have a bound actor or explicitly trust per-request delegation
58
+ for writes. A personal API Key cannot be used to bypass this restriction.
59
+
60
+ With a trusted service grant, meeting `tasks run` and `tasks resume` execute on the service, using the bound
61
+ actor. Existing plans must resolve to that actor; a resume batch with a plan
62
+ for another actor is rejected. Task writes use the service-side Quest handoff.
63
+ Meeting notifications are sent by the service's `magic-campus` executable, so
64
+ that executable and its account configuration must be available in the service
65
+ runtime; an absent executable leaves the notification pending for retry.
66
+ The meeting-assistant skill uses the same `PROJECT_SERVICE_URL` and
67
+ `PROJECT_SERVICE_TOKEN` for meeting state when those are set, and does not
68
+ fall back to a local database if the remote service is unavailable.
69
+
70
+ When an explicitly trusted Agent credential uses `trustActorRefs: true`, the
71
+ runtime must set `PROJECT_SERVICE_ACTOR_REF` to the current verified actor JSON
72
+ (e.g. `{"userId":"..."}`) for CLI bridge operations which need a user. A bound
73
+ actor credential does not need this variable. The service validates that actor
74
+ is active and rejects a conflicting actor in the command input.
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/project_agent_client/__init__.py
4
+ src/project_agent_client/cli.py
5
+ src/project_agent_client/help.json
6
+ src/project_agent_client.egg-info/PKG-INFO
7
+ src/project_agent_client.egg-info/SOURCES.txt
8
+ src/project_agent_client.egg-info/dependency_links.txt
9
+ src/project_agent_client.egg-info/entry_points.txt
10
+ src/project_agent_client.egg-info/top_level.txt
11
+ tests/test_cli.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ project-agent = project_agent_client.cli:main
@@ -0,0 +1,146 @@
1
+ import io
2
+ import json
3
+ from urllib.error import URLError
4
+
5
+ from project_agent_client.cli import run
6
+
7
+
8
+ def test_forwards_task_request_and_preserves_receipt():
9
+ output = io.StringIO()
10
+ expected = {"status": "partial_success", "results": [{"taskId": "t1"}]}
11
+ calls = []
12
+
13
+ def send(request, timeout):
14
+ calls.append((request, timeout))
15
+ return io.BytesIO(json.dumps(expected).encode())
16
+
17
+ result = run(["task", "apply", "--input", "@-"], io.StringIO('{"operations": []}'), output,
18
+ {"PROJECT_SERVICE_URL": "https://project.example", "PROJECT_SERVICE_TOKEN": "secret"}, send)
19
+ assert result == 1
20
+ assert json.loads(output.getvalue()) == expected
21
+ assert calls[0][0].full_url == "https://project.example/v1/tasks/apply"
22
+ assert calls[0][0].get_header("Authorization") == "Bearer secret"
23
+ assert json.loads(calls[0][0].data) == {"operations": []}
24
+
25
+
26
+ def test_network_failure_is_not_retried_or_reported_as_safe_to_repeat():
27
+ output = io.StringIO()
28
+ calls = []
29
+
30
+ def send(request, timeout):
31
+ calls.append(request)
32
+ raise URLError("unavailable")
33
+
34
+ result = run(["task", "apply", "--input", "@-"], io.StringIO("{}"), output,
35
+ {"PROJECT_SERVICE_URL": "https://project.example", "PROJECT_SERVICE_TOKEN": "secret"}, send)
36
+ assert result == 1
37
+ assert len(calls) == 1
38
+ assert "outcome may be unknown" in json.loads(output.getvalue())["results"][0]["error"]["message"]
39
+
40
+
41
+ def test_maintenance_forwards_its_original_result():
42
+ output = io.StringIO()
43
+ result_body = {"status": "prepared", "snapshotToken": "snapshot"}
44
+ calls = []
45
+
46
+ def send(request, timeout):
47
+ calls.append(request.full_url)
48
+ return io.BytesIO(json.dumps(result_body).encode())
49
+
50
+ result = run(["task", "maintain", "--input", "@-"], io.StringIO("{}"), output,
51
+ {"PROJECT_SERVICE_URL": "https://project.example", "PROJECT_SERVICE_TOKEN": "secret"}, send)
52
+ assert result == 0
53
+ assert calls == ["https://project.example/v1/tasks/maintain"]
54
+ assert json.loads(output.getvalue()) == result_body
55
+
56
+
57
+ def test_rejects_plain_http_to_remote_hosts():
58
+ output = io.StringIO()
59
+ result = run(["task", "apply", "--input", "@-"], io.StringIO("{}"), output,
60
+ {"PROJECT_SERVICE_URL": "http://project.example", "PROJECT_SERVICE_TOKEN": "secret"})
61
+ assert result == 1
62
+ assert json.loads(output.getvalue())["results"][0]["error"]["code"] == "service_configuration_error"
63
+
64
+
65
+ def test_legacy_command_forwards_arguments_and_preserves_streams():
66
+ output, error, calls = io.StringIO(), io.StringIO(), []
67
+ def send(request, timeout):
68
+ calls.append(request)
69
+ return io.BytesIO(json.dumps({'stdout': 'result\n', 'stderr': 'diagnostic\n', 'exitCode': 2}).encode())
70
+ result = run(['context', 'get-user', '--user-id', 'u'], io.StringIO(''), output,
71
+ {'PROJECT_SERVICE_URL': 'https://project.example', 'PROJECT_SERVICE_TOKEN': 'secret'}, send, error)
72
+ assert result == 2 and output.getvalue() == 'result\n' and error.getvalue() == 'diagnostic\n'
73
+ assert json.loads(calls[0].data) == {'command': ['context', 'get-user'], 'arguments': ['--user-id', 'u'], 'stdin': ''}
74
+
75
+
76
+ def test_inline_document_json_file_is_read_only_by_client(tmp_path):
77
+ source = tmp_path / 'doc.json'
78
+ source.write_text('{"title":"hello"}')
79
+ captured = []
80
+ def send(request, timeout):
81
+ captured.append(json.loads(request.data))
82
+ return io.BytesIO(b'{"stdout":"ok","stderr":"","exitCode":0}')
83
+ assert run(['doc-hub', 'read', '--project-id', 'p', '--entry-id', 'e', '--content-json', str(source)], io.StringIO(), io.StringIO(),
84
+ {'PROJECT_SERVICE_URL': 'https://project.example', 'PROJECT_SERVICE_TOKEN': 'secret'}, send) == 0
85
+ assert captured[0]['arguments'][-1] == '{"title":"hello"}'
86
+
87
+
88
+ def test_quest_handoff_uses_service_and_preserves_uncertain_outcome():
89
+ captured, output = [], io.StringIO()
90
+ def send(request, timeout):
91
+ captured.append(request)
92
+ return io.BytesIO(b'{"status":"partial_success","result":{"status":"unknown_write"}}')
93
+ result = run(['quest', 'publish-task', '--input', '@-'], io.StringIO('{"projectId":"p","taskId":"t"}'), output,
94
+ {'PROJECT_SERVICE_URL': 'https://project.example', 'PROJECT_SERVICE_TOKEN': 'secret'}, send)
95
+ assert result == 1
96
+ assert captured[0].full_url == 'https://project.example/v1/quest/publish-task'
97
+ assert json.loads(output.getvalue())['result']['status'] == 'unknown_write'
98
+
99
+
100
+ def test_quest_status_forwards_request_unchanged():
101
+ captured, output = [], io.StringIO()
102
+ def send(request, timeout):
103
+ captured.append(request)
104
+ return io.BytesIO(b'{"status":"ok","publication":{"state":"pending"}}')
105
+ body = '{"projectId":"p","taskId":"t","actorRef":{"userId":"u"}}'
106
+ assert run(['quest', 'status', '--input', '@-'], io.StringIO(body), output,
107
+ {'PROJECT_SERVICE_URL':'https://project.example', 'PROJECT_SERVICE_TOKEN':'secret'}, send) == 0
108
+ assert captured[0].full_url == 'https://project.example/v1/quest/status'
109
+ assert json.loads(captured[0].data) == json.loads(body)
110
+
111
+
112
+ def test_offline_version_and_help_never_contact_service():
113
+ def never_send(*args, **kwargs):
114
+ raise AssertionError('help must not contact service')
115
+ for args, expected in [(['--version'], '0.'), (['--help'], 'Commands:'),
116
+ (['task', 'apply', '--help'], '--input'),
117
+ (['quest', 'status', '--help'], '--input'),
118
+ (['quest', 'reconcile-task-create', '--help'], '--input')]:
119
+ out = io.StringIO()
120
+ assert run(args, io.StringIO(), out, {}, never_send) == 0
121
+ assert expected in out.getvalue()
122
+
123
+
124
+ def test_quest_pending_forwards_request_unchanged():
125
+ captured, output = [], io.StringIO()
126
+ def send(request, timeout):
127
+ captured.append(request)
128
+ return io.BytesIO(b'{"status":"ok","result":{"items":[],"nextCursor":null}}')
129
+ body = '{"projectId":"p","actorRef":{"userId":"u"},"limit":10}'
130
+ assert run(['quest', 'pending', '--input', '@-'], io.StringIO(body), output,
131
+ {'PROJECT_SERVICE_URL':'https://project.example', 'PROJECT_SERVICE_TOKEN':'secret'}, send) == 0
132
+ assert captured[0].full_url == 'https://project.example/v1/quest/pending'
133
+ assert json.loads(captured[0].data) == json.loads(body)
134
+
135
+
136
+ def test_quest_reconcile_task_create_forwards_request_and_receipt():
137
+ captured, output = [], io.StringIO()
138
+ def send(request, timeout):
139
+ captured.append(request)
140
+ return io.BytesIO(b'{"status":"ok","result":{"status":"recovered","taskId":"t"}}')
141
+ body = '{"projectId":"p","taskId":"t","remoteId":"r","actorRef":{"userId":"u"}}'
142
+ assert run(['quest', 'reconcile-task-create', '--input', '@-'], io.StringIO(body), output,
143
+ {'PROJECT_SERVICE_URL':'https://project.example', 'PROJECT_SERVICE_TOKEN':'secret'}, send) == 0
144
+ assert captured[0].full_url == 'https://project.example/v1/quest/reconcile-task-create'
145
+ assert json.loads(captured[0].data) == json.loads(body)
146
+ assert json.loads(output.getvalue())['result']['status'] == 'recovered'