autotouch-cli 0.2.45__tar.gz → 0.2.47__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.
Files changed (48) hide show
  1. {autotouch_cli-0.2.45/autotouch_cli.egg-info → autotouch_cli-0.2.47}/PKG-INFO +37 -10
  2. autotouch_cli-0.2.45/PKG-INFO → autotouch_cli-0.2.47/README.md +20 -18
  3. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/cli.py +13 -5
  4. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/cli_contracts.py +121 -4
  5. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/auth.py +9 -8
  6. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/cells.py +5 -1
  7. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/columns.py +15 -6
  8. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/core/auth.py +4 -3
  9. autotouch_cli-0.2.47/autotouch_cli/data/CLI_REFERENCE.md +5130 -0
  10. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/data/cli-manifest.json +5517 -494
  11. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/templates.py +9 -6
  12. autotouch_cli-0.2.45/README.md → autotouch_cli-0.2.47/autotouch_cli.egg-info/PKG-INFO +45 -9
  13. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli.egg-info/requires.txt +3 -0
  14. autotouch_cli-0.2.47/pyproject.toml +46 -0
  15. autotouch_cli-0.2.45/autotouch_cli/data/CLI_REFERENCE.md +0 -5118
  16. autotouch_cli-0.2.45/pyproject.toml +0 -25
  17. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/MANIFEST.in +0 -0
  18. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/__init__.py +0 -0
  19. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/__init__.py +0 -0
  20. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/jobs.py +0 -0
  21. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/leads.py +0 -0
  22. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/linkedin.py +0 -0
  23. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/prompts.py +0 -0
  24. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/rows.py +0 -0
  25. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/search.py +0 -0
  26. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/sequences.py +0 -0
  27. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/tables.py +0 -0
  28. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/tasks.py +0 -0
  29. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/webhooks.py +0 -0
  30. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/commands/workspace_secrets.py +0 -0
  31. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/core/__init__.py +0 -0
  32. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/core/config.py +0 -0
  33. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/core/http.py +0 -0
  34. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/core/io.py +0 -0
  35. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/core/output.py +0 -0
  36. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/core/polling.py +0 -0
  37. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/mongo_status.py +0 -0
  38. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/parser_groups.py +0 -0
  39. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli/sequence_support.py +0 -0
  40. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli.egg-info/SOURCES.txt +0 -0
  41. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli.egg-info/dependency_links.txt +0 -0
  42. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli.egg-info/entry_points.txt +0 -0
  43. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_cli.egg-info/top_level.txt +0 -0
  44. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_shared/__init__.py +0 -0
  45. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_shared/linkedin_contract.py +0 -0
  46. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_shared/provider_registry.py +0 -0
  47. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/autotouch_shared/search_contract.py +0 -0
  48. {autotouch_cli-0.2.45 → autotouch_cli-0.2.47}/setup.cfg +0 -0
@@ -1,11 +1,27 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autotouch-cli
3
- Version: 0.2.45
3
+ Version: 0.2.47
4
4
  Summary: Autotouch Smart Table CLI
5
+ Project-URL: Homepage, https://app.autotouch.ai
6
+ Project-URL: Documentation, https://github.com/nicolonic/autotouch_main/tree/main/docs/research-table/reference
7
+ Project-URL: Source, https://github.com/nicolonic/autotouch_main
8
+ Keywords: autotouch,smart-table,cli,automation,api
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.9
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Topic :: Utilities
5
19
  Requires-Python: >=3.9
6
20
  Description-Content-Type: text/markdown
7
21
  Requires-Dist: requests>=2.31.0
8
22
  Requires-Dist: python-dotenv>=1.0.0
23
+ Provides-Extra: mongo
24
+ Requires-Dist: pymongo>=4.7; extra == "mongo"
9
25
 
10
26
  # Autotouch CLI
11
27
 
@@ -31,13 +47,15 @@ pip install autotouch-cli
31
47
  Existing developer key:
32
48
 
33
49
  ```bash
34
- autotouch setup --api-key stk_... --base-url https://app.autotouch.ai
50
+ read -s AUTOTOUCH_API_KEY
51
+ export AUTOTOUCH_API_KEY
52
+ autotouch setup --api-key "$AUTOTOUCH_API_KEY" --base-url https://app.autotouch.ai
35
53
  ```
36
54
 
37
55
  Equivalent manual steps:
38
56
 
39
57
  ```bash
40
- autotouch auth set-key --api-key stk_... --base-url https://app.autotouch.ai
58
+ autotouch auth set-key --api-key "$AUTOTOUCH_API_KEY" --base-url https://app.autotouch.ai
41
59
  autotouch auth check --base-url https://app.autotouch.ai
42
60
  ```
43
61
 
@@ -45,23 +63,31 @@ Fresh account:
45
63
 
46
64
  ```bash
47
65
  export AUTOTOUCH_CONFIG_PATH=/tmp/autotouch-audit.json
66
+ read -s AUTOTOUCH_BOOTSTRAP_PASSWORD
67
+ export AUTOTOUCH_BOOTSTRAP_PASSWORD
48
68
 
49
69
  autotouch auth bootstrap \
50
70
  --first-name Ada \
51
71
  --last-name Lovelace \
52
72
  --email ada+audit@example.com \
53
- --password 'use-a-strong-random-password' \
73
+ --password "$AUTOTOUCH_BOOTSTRAP_PASSWORD" \
54
74
  --organization-name "Audit Org" \
55
75
  --save-key
56
76
 
57
77
  autotouch auth check
58
78
  ```
59
79
 
80
+ Config precedence:
81
+ - Explicit flags win over environment variables.
82
+ - Environment variables win over saved config.
83
+ - Saved config wins over built-in defaults.
84
+ - For JSON payload inputs, pass either `--data-json` or `--data-file`, not both.
85
+
60
86
  ## 5-Minute Flow
61
87
 
62
88
  ```bash
63
89
  # 0) First-run setup
64
- autotouch setup --api-key stk_... --base-url https://app.autotouch.ai
90
+ autotouch setup --api-key "$AUTOTOUCH_API_KEY" --base-url https://app.autotouch.ai
65
91
 
66
92
  # 1) Inspect machine-readable contract
67
93
  autotouch capabilities --output json
@@ -85,7 +111,7 @@ COLUMN_ID=$(autotouch columns create --table-id "$TABLE_ID" --data-file column.j
85
111
  JOB_ID=$(autotouch columns run-next \
86
112
  --table-id "$TABLE_ID" \
87
113
  --column-id "$COLUMN_ID" \
88
- --count 10 \
114
+ --count 2 \
89
115
  --show-estimate \
90
116
  --wait \
91
117
  --output json --select job_id)
@@ -118,10 +144,11 @@ For automation or agent-driven setup, use:
118
144
  - `autotouch capabilities --output json` for provider/workflow contracts
119
145
  - `autotouch rows list` / `autotouch rows get` / `autotouch cells get` for read-back and audit
120
146
  - `autotouch sequences ...` and `autotouch tasks ...` for sequence/task workflows
147
+ - `pip install 'autotouch-cli[mongo]'` if you need the Mongo-backed `status` / `watch` commands
121
148
 
122
149
  ## Docs
123
150
 
124
- - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.45/docs/research-table/reference/autotouch-cli.md
125
- - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.45/docs/research-table/guides/autotouch-cli-agent-playbook.md
126
- - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.45/docs/research-table/reference/tables-api.md
127
- - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.45/docs/platform/authentication.md
151
+ - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/reference/autotouch-cli.md
152
+ - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/guides/autotouch-cli-agent-playbook.md
153
+ - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/reference/tables-api.md
154
+ - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/platform/authentication.md
@@ -1,12 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: autotouch-cli
3
- Version: 0.2.45
4
- Summary: Autotouch Smart Table CLI
5
- Requires-Python: >=3.9
6
- Description-Content-Type: text/markdown
7
- Requires-Dist: requests>=2.31.0
8
- Requires-Dist: python-dotenv>=1.0.0
9
-
10
1
  # Autotouch CLI
11
2
 
12
3
  Installable CLI for the Smart Table developer API.
@@ -31,13 +22,15 @@ pip install autotouch-cli
31
22
  Existing developer key:
32
23
 
33
24
  ```bash
34
- autotouch setup --api-key stk_... --base-url https://app.autotouch.ai
25
+ read -s AUTOTOUCH_API_KEY
26
+ export AUTOTOUCH_API_KEY
27
+ autotouch setup --api-key "$AUTOTOUCH_API_KEY" --base-url https://app.autotouch.ai
35
28
  ```
36
29
 
37
30
  Equivalent manual steps:
38
31
 
39
32
  ```bash
40
- autotouch auth set-key --api-key stk_... --base-url https://app.autotouch.ai
33
+ autotouch auth set-key --api-key "$AUTOTOUCH_API_KEY" --base-url https://app.autotouch.ai
41
34
  autotouch auth check --base-url https://app.autotouch.ai
42
35
  ```
43
36
 
@@ -45,23 +38,31 @@ Fresh account:
45
38
 
46
39
  ```bash
47
40
  export AUTOTOUCH_CONFIG_PATH=/tmp/autotouch-audit.json
41
+ read -s AUTOTOUCH_BOOTSTRAP_PASSWORD
42
+ export AUTOTOUCH_BOOTSTRAP_PASSWORD
48
43
 
49
44
  autotouch auth bootstrap \
50
45
  --first-name Ada \
51
46
  --last-name Lovelace \
52
47
  --email ada+audit@example.com \
53
- --password 'use-a-strong-random-password' \
48
+ --password "$AUTOTOUCH_BOOTSTRAP_PASSWORD" \
54
49
  --organization-name "Audit Org" \
55
50
  --save-key
56
51
 
57
52
  autotouch auth check
58
53
  ```
59
54
 
55
+ Config precedence:
56
+ - Explicit flags win over environment variables.
57
+ - Environment variables win over saved config.
58
+ - Saved config wins over built-in defaults.
59
+ - For JSON payload inputs, pass either `--data-json` or `--data-file`, not both.
60
+
60
61
  ## 5-Minute Flow
61
62
 
62
63
  ```bash
63
64
  # 0) First-run setup
64
- autotouch setup --api-key stk_... --base-url https://app.autotouch.ai
65
+ autotouch setup --api-key "$AUTOTOUCH_API_KEY" --base-url https://app.autotouch.ai
65
66
 
66
67
  # 1) Inspect machine-readable contract
67
68
  autotouch capabilities --output json
@@ -85,7 +86,7 @@ COLUMN_ID=$(autotouch columns create --table-id "$TABLE_ID" --data-file column.j
85
86
  JOB_ID=$(autotouch columns run-next \
86
87
  --table-id "$TABLE_ID" \
87
88
  --column-id "$COLUMN_ID" \
88
- --count 10 \
89
+ --count 2 \
89
90
  --show-estimate \
90
91
  --wait \
91
92
  --output json --select job_id)
@@ -118,10 +119,11 @@ For automation or agent-driven setup, use:
118
119
  - `autotouch capabilities --output json` for provider/workflow contracts
119
120
  - `autotouch rows list` / `autotouch rows get` / `autotouch cells get` for read-back and audit
120
121
  - `autotouch sequences ...` and `autotouch tasks ...` for sequence/task workflows
122
+ - `pip install 'autotouch-cli[mongo]'` if you need the Mongo-backed `status` / `watch` commands
121
123
 
122
124
  ## Docs
123
125
 
124
- - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.45/docs/research-table/reference/autotouch-cli.md
125
- - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.45/docs/research-table/guides/autotouch-cli-agent-playbook.md
126
- - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.45/docs/research-table/reference/tables-api.md
127
- - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.45/docs/platform/authentication.md
126
+ - Full CLI reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/reference/autotouch-cli.md
127
+ - Agent playbook: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/guides/autotouch-cli-agent-playbook.md
128
+ - Tables/API reference: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/research-table/reference/tables-api.md
129
+ - Authentication/scopes: https://github.com/nicolonic/autotouch_main/blob/autotouch-cli-v0.2.47/docs/platform/authentication.md
@@ -1091,8 +1091,7 @@ def _normalize_run_payload(args: argparse.Namespace) -> Dict[str, Any]:
1091
1091
  if filters is not None:
1092
1092
  payload["filters"] = filters
1093
1093
 
1094
- if getattr(args, "unprocessed_only", False):
1095
- payload["unprocessedOnly"] = True
1094
+ payload["unprocessedOnly"] = bool(getattr(args, "unprocessed_only", True))
1096
1095
 
1097
1096
  return payload
1098
1097
 
@@ -3559,11 +3558,20 @@ def _add_run_scope_arguments(parser: argparse.ArgumentParser) -> None:
3559
3558
  type=int,
3560
3559
  help="First N rows (for scope=firstN). Use with --unprocessed-only to process the next rows without reruns",
3561
3560
  )
3562
- parser.add_argument(
3561
+ run_mode = parser.add_mutually_exclusive_group()
3562
+ run_mode.add_argument(
3563
3563
  "--unprocessed-only",
3564
+ dest="unprocessed_only",
3564
3565
  action="store_true",
3565
- help="Only process rows without values (recommended with scope=firstN)",
3566
+ help="Only process rows without values (default)",
3567
+ )
3568
+ run_mode.add_argument(
3569
+ "--include-processed",
3570
+ dest="unprocessed_only",
3571
+ action="store_false",
3572
+ help="Include rows that already have output",
3566
3573
  )
3574
+ parser.set_defaults(unprocessed_only=True)
3567
3575
  parser.add_argument("--filters-json", help="JSON object for scope=filtered")
3568
3576
  parser.add_argument("--filters-file", help="Path to JSON file for scope=filtered")
3569
3577
  parser.add_argument("--data-json", help="Explicit RunRequest payload JSON (overrides scope flags)")
@@ -4218,7 +4226,7 @@ def build_parser() -> argparse.ArgumentParser:
4218
4226
  palias_run_next = sub.add_parser("run-next", help="Alias for: columns run-next")
4219
4227
  palias_run_next.add_argument("--table-id", required=True)
4220
4228
  palias_run_next.add_argument("--column-id", required=True)
4221
- palias_run_next.add_argument("--count", type=int, required=True, help="Exact number of rows to queue")
4229
+ palias_run_next.add_argument("--count", type=int, required=True, help="Maximum number of rows to queue")
4222
4230
  palias_run_next.add_argument("--filters-json", help="Optional JSON object to select from filtered rows")
4223
4231
  palias_run_next.add_argument("--filters-file", help="Optional JSON file to select from filtered rows")
4224
4232
  palias_run_next.add_argument("--page-size", type=int, default=200, help="Rows page size while selecting candidates (max 1000)")
@@ -1,9 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import argparse
4
+ import json
4
5
  from typing import Any, Dict, List, Optional, Tuple
5
6
 
6
7
 
8
+ _CLI_MANIFEST_SCHEMA_VERSION = 2
9
+
10
+
7
11
  _AUTH_MODE_EXACT: Dict[str, str] = {
8
12
  "setup": "none",
9
13
  "auth.bootstrap": "none",
@@ -79,6 +83,80 @@ _OPTIONAL_DEPENDENCY_COMMANDS: Dict[str, Dict[str, Any]] = {
79
83
  },
80
84
  }
81
85
 
86
+ _SENSITIVE_OPTION_TERMS = ("token", "api-key", "api_key", "password", "secret")
87
+
88
+
89
+ def _json_safe_value(value: Any) -> Any:
90
+ try:
91
+ json.dumps(value)
92
+ except (TypeError, ValueError):
93
+ return str(value)
94
+ return value
95
+
96
+
97
+ def _option_input_kind(action: argparse.Action) -> Optional[str]:
98
+ flags = [flag.lower() for flag in getattr(action, "option_strings", []) or []]
99
+ dest = str(getattr(action, "dest", "") or "").lower()
100
+ if any(flag.endswith("-file") or flag == "--file" for flag in flags) or dest.endswith("_file"):
101
+ return "file"
102
+ if any(flag.endswith("-json") for flag in flags) or dest.endswith("_json"):
103
+ return "json"
104
+ if dest in {"out_file", "checkpoint_file"}:
105
+ return "file"
106
+ return None
107
+
108
+
109
+ def _option_kind(action: argparse.Action, *, input_kind: Optional[str]) -> str:
110
+ if isinstance(action, (argparse._StoreTrueAction, argparse._StoreFalseAction)):
111
+ return "boolean"
112
+ if isinstance(action, argparse._CountAction):
113
+ return "integer"
114
+ if input_kind:
115
+ return input_kind
116
+ if getattr(action, "type", None) is int:
117
+ return "integer"
118
+ if getattr(action, "type", None) is float:
119
+ return "number"
120
+ if getattr(action, "nargs", None) in ("*", "+"):
121
+ return "list"
122
+ return "string"
123
+
124
+
125
+ def _option_action_kind(action: argparse.Action) -> str:
126
+ if isinstance(action, argparse._StoreTrueAction):
127
+ return "store_true"
128
+ if isinstance(action, argparse._StoreFalseAction):
129
+ return "store_false"
130
+ if isinstance(action, argparse._AppendAction):
131
+ return "append"
132
+ if isinstance(action, argparse._AppendConstAction):
133
+ return "append_const"
134
+ if isinstance(action, argparse._CountAction):
135
+ return "count"
136
+ if isinstance(action, argparse._StoreConstAction):
137
+ return "store_const"
138
+ return "store"
139
+
140
+
141
+ def _option_is_sensitive(action: argparse.Action) -> bool:
142
+ option_strings = [flag.lower() for flag in getattr(action, "option_strings", []) or []]
143
+ dest = str(getattr(action, "dest", "") or "").lower()
144
+ if isinstance(action, (argparse._StoreTrueAction, argparse._StoreFalseAction, argparse._StoreConstAction, argparse._AppendConstAction)):
145
+ return False
146
+ if dest == "value" or "--value" in option_strings:
147
+ return True
148
+ searchable = " ".join([dest, *option_strings])
149
+ return any(term in searchable for term in _SENSITIVE_OPTION_TERMS)
150
+
151
+
152
+ def _option_accepts_comma_separated(action: argparse.Action) -> bool:
153
+ help_text = str(getattr(action, "help", "") or "").lower()
154
+ return "comma-separated" in help_text
155
+
156
+
157
+ def _option_repeatable(action: argparse.Action) -> bool:
158
+ return isinstance(action, (argparse._AppendAction, argparse._AppendConstAction, argparse._CountAction))
159
+
82
160
 
83
161
  def canonical_subparser_entries(
84
162
  action: argparse._SubParsersAction,
@@ -123,10 +201,14 @@ def action_descriptor(action: argparse.Action) -> Optional[Dict[str, Any]]:
123
201
  if isinstance(action, argparse._SubParsersAction):
124
202
  return None
125
203
 
204
+ input_kind = _option_input_kind(action)
205
+ action_kind = _option_action_kind(action)
126
206
  payload: Dict[str, Any] = {
127
207
  "dest": action.dest,
128
208
  "required": bool(getattr(action, "required", False)),
129
209
  "help": action.help,
210
+ "kind": _option_kind(action, input_kind=input_kind),
211
+ "action": action_kind,
130
212
  }
131
213
  if getattr(action, "choices", None) is not None:
132
214
  payload["choices"] = list(action.choices)
@@ -135,11 +217,29 @@ def action_descriptor(action: argparse.Action) -> Optional[Dict[str, Any]]:
135
217
  if getattr(action, "metavar", None) is not None:
136
218
  payload["metavar"] = action.metavar
137
219
  default_value = getattr(action, "default", argparse.SUPPRESS)
138
- if default_value is not argparse.SUPPRESS and default_value is not None:
139
- payload["default"] = default_value
220
+ if default_value is not argparse.SUPPRESS:
221
+ if default_value is not None or isinstance(action, (argparse._StoreTrueAction, argparse._StoreFalseAction)):
222
+ payload["default_when_omitted"] = _json_safe_value(default_value)
223
+ if default_value is not None and not isinstance(action, (argparse._StoreTrueAction, argparse._StoreFalseAction)):
224
+ payload["default"] = _json_safe_value(default_value)
225
+ if isinstance(action, argparse._StoreTrueAction):
226
+ payload["value_when_present"] = True
227
+ elif isinstance(action, argparse._StoreFalseAction):
228
+ payload["value_when_present"] = False
229
+ if input_kind:
230
+ payload["input_kind"] = input_kind
231
+ if _option_repeatable(action):
232
+ payload["repeatable"] = True
233
+ if _option_accepts_comma_separated(action):
234
+ payload["accepts_comma_separated"] = True
235
+ if _option_is_sensitive(action):
236
+ payload["sensitive"] = True
140
237
  if action.option_strings:
141
238
  payload["flags"] = list(action.option_strings)
142
- payload["takes_value"] = getattr(action, "nargs", None) != 0
239
+ payload["takes_value"] = not isinstance(
240
+ action,
241
+ (argparse._StoreTrueAction, argparse._StoreFalseAction, argparse._StoreConstAction, argparse._AppendConstAction),
242
+ ) and getattr(action, "nargs", None) != 0
143
243
  else:
144
244
  payload["name"] = action.dest
145
245
  payload["positional"] = True
@@ -301,6 +401,7 @@ def collect_cli_command_manifest(
301
401
  def build_cli_manifest(version: str, parser: argparse.ArgumentParser) -> Dict[str, Any]:
302
402
  return {
303
403
  "version": version,
404
+ "manifest_schema_version": _CLI_MANIFEST_SCHEMA_VERSION,
304
405
  "entry_points": {
305
406
  "autotouch": "autotouch_cli.cli:main",
306
407
  "smart-table": "autotouch_cli.cli:main",
@@ -311,11 +412,13 @@ def build_cli_manifest(version: str, parser: argparse.ArgumentParser) -> Dict[st
311
412
 
312
413
  def build_cli_reference_markdown(manifest: Dict[str, Any]) -> str:
313
414
  version = str(manifest.get("version") or "unknown")
415
+ manifest_schema_version = str(manifest.get("manifest_schema_version") or "unknown")
314
416
  commands = manifest.get("commands") if isinstance(manifest.get("commands"), dict) else {}
315
417
  lines: List[str] = [
316
418
  "# Autotouch CLI Reference",
317
419
  "",
318
420
  f"Generated from the installed parser for `autotouch-cli` `{version}`.",
421
+ f"Manifest schema version: `{manifest_schema_version}`.",
319
422
  "",
320
423
  "## Output Modes",
321
424
  "",
@@ -394,9 +497,23 @@ def build_cli_reference_markdown(manifest: Dict[str, Any]) -> str:
394
497
  note_parts: List[str] = []
395
498
  if option.get("required"):
396
499
  note_parts.append("required")
500
+ if option.get("kind"):
501
+ note_parts.append(f"kind={option.get('kind')}")
502
+ if option.get("input_kind"):
503
+ note_parts.append(f"input={option.get('input_kind')}")
504
+ if option.get("repeatable"):
505
+ note_parts.append("repeatable")
506
+ if option.get("accepts_comma_separated"):
507
+ note_parts.append("comma-separated")
508
+ if option.get("sensitive"):
509
+ note_parts.append("sensitive")
510
+ if option.get("action") in {"store_true", "store_false"}:
511
+ note_parts.append(
512
+ f"when omitted={option.get('default_when_omitted')}; when present={option.get('value_when_present')}"
513
+ )
397
514
  if option.get("choices"):
398
515
  note_parts.append(f"choices={','.join(str(v) for v in option.get('choices') or [])}")
399
- if option.get("default") is not None:
516
+ if option.get("default") is not None and option.get("action") not in {"store_true", "store_false"}:
400
517
  note_parts.append(f"default={option.get('default')}")
401
518
  help_value = str(option.get("help") or "").strip()
402
519
  note_text = f" ({'; '.join(note_parts)})" if note_parts else ""
@@ -30,19 +30,16 @@ def cmd_auth_check(args: argparse.Namespace, *, runtime: AuthCommandRuntime) ->
30
30
  token = runtime.resolve_token(args.token, required=True)
31
31
  body = runtime.request_api(
32
32
  "GET",
33
- "/api/capabilities",
33
+ "/api/auth/check",
34
34
  base_url=args.base_url,
35
35
  token=token,
36
36
  use_x_api_key=args.use_x_api_key,
37
37
  timeout=args.timeout,
38
38
  verbose=args.verbose,
39
39
  )
40
- output = {
41
- "ok": True,
42
- "base_url": runtime.api_url(args.base_url),
43
- "api_version": body.get("api_version") if isinstance(body, dict) else None,
44
- "auth_header_mode": "x-api-key" if args.use_x_api_key else "authorization",
45
- }
40
+ output = dict(body) if isinstance(body, dict) else {"ok": True}
41
+ output["base_url"] = runtime.api_url(args.base_url)
42
+ output["auth_header_mode"] = "x-api-key" if args.use_x_api_key else "authorization"
46
43
  runtime.print_json(output, args.compact)
47
44
 
48
45
 
@@ -363,7 +360,11 @@ def register_auth_subcommands(
363
360
  auth = subparsers.add_parser("auth", help="Auth helpers")
364
361
  auth_sub = auth.add_subparsers(dest="auth_cmd", required=True)
365
362
 
366
- pa = auth_sub.add_parser("check", help="Validate token against /api/capabilities")
363
+ pa = auth_sub.add_parser(
364
+ "check",
365
+ help="Validate the current token/session and show granted scopes",
366
+ description="Validate the current token/session against the API and show granted scopes for developer API keys.",
367
+ )
367
368
  add_api_common_arguments(pa)
368
369
  pa.set_defaults(func=handlers["check"])
369
370
 
@@ -85,7 +85,11 @@ def register_cells_subcommands(
85
85
  add_api_common_arguments(get_parser)
86
86
  get_parser.set_defaults(func=handlers["get"])
87
87
 
88
- patch_parser = cells_sub.add_parser("patch", help="Patch cells in batch")
88
+ patch_parser = cells_sub.add_parser(
89
+ "patch",
90
+ help="Patch cells in batch",
91
+ description="Patch cells in batch. Use column key strings in updates[].key; this payload does not accept columnId.",
92
+ )
89
93
  patch_parser.add_argument("--table-id", required=True)
90
94
  patch_parser.add_argument("--updates-json", help="JSON list or {updates:[...]} object")
91
95
  patch_parser.add_argument("--updates-file", help="Path to JSON file with updates payload")
@@ -273,9 +273,11 @@ def cmd_columns_run_next(args: argparse.Namespace, *, runtime: ColumnCommandRunt
273
273
  sys.exit(3)
274
274
  return
275
275
 
276
- payload: Dict[str, Any] = {"scope": "subset", "rowIds": row_ids}
277
- if args.unprocessed_only:
278
- payload["unprocessedOnly"] = True
276
+ payload: Dict[str, Any] = {
277
+ "scope": "subset",
278
+ "rowIds": row_ids,
279
+ "unprocessedOnly": bool(args.unprocessed_only),
280
+ }
279
281
 
280
282
  context = {
281
283
  "mode": "run-next",
@@ -327,7 +329,14 @@ def register_columns_subcommands(
327
329
  add_api_common_arguments(pcc)
328
330
  pcc.set_defaults(func=handlers["create"])
329
331
 
330
- pcu = col_sub.add_parser("update", help="Update a column")
332
+ pcu = col_sub.add_parser(
333
+ "update",
334
+ help="Update a column",
335
+ description=(
336
+ "Update a column definition. Nested config keys merge into the existing config, "
337
+ "so partial config patches are supported."
338
+ ),
339
+ )
331
340
  pcu.add_argument("--table-id", required=True)
332
341
  pcu.add_argument("--column-id", required=True)
333
342
  pcu.add_argument("--data-json", help="ColumnUpdate payload JSON")
@@ -415,10 +424,10 @@ def register_columns_subcommands(
415
424
  add_api_common_arguments(pcs)
416
425
  pcs.set_defaults(func=handlers["stop"])
417
426
 
418
- pcrn = col_sub.add_parser("run-next", help="Run exactly N selected rows using subset scope")
427
+ pcrn = col_sub.add_parser("run-next", help="Run up to N selected rows using subset scope")
419
428
  pcrn.add_argument("--table-id", required=True)
420
429
  pcrn.add_argument("--column-id", required=True)
421
- pcrn.add_argument("--count", type=int, required=True, help="Exact number of rows to queue")
430
+ pcrn.add_argument("--count", type=int, required=True, help="Maximum number of rows to queue")
422
431
  pcrn.add_argument("--filters-json", help="Optional JSON object to select from filtered rows")
423
432
  pcrn.add_argument("--filters-file", help="Optional JSON file to select from filtered rows")
424
433
  pcrn.add_argument("--page-size", type=int, default=200, help="Rows page size while selecting candidates (max 1000)")
@@ -68,8 +68,9 @@ def print_missing_developer_auth_help() -> None:
68
68
  print(
69
69
  "Fresh-account / isolated audit pattern: "
70
70
  "`export AUTOTOUCH_CONFIG_PATH=/tmp/autotouch-audit.json && "
71
+ "read -s AUTOTOUCH_BOOTSTRAP_PASSWORD && export AUTOTOUCH_BOOTSTRAP_PASSWORD && "
71
72
  "autotouch auth bootstrap --first-name Ada --last-name Lovelace "
72
- "--email ada+audit@example.com --password 'use-a-strong-random-password' "
73
+ "--email ada+audit@example.com --password \"$AUTOTOUCH_BOOTSTRAP_PASSWORD\" "
73
74
  "--organization-name \"Audit Org\" --save-key`",
74
75
  file=sys.stderr,
75
76
  )
@@ -89,8 +90,9 @@ def print_missing_user_session_help() -> None:
89
90
  print(
90
91
  "Fresh-account / isolated audit pattern: "
91
92
  "`export AUTOTOUCH_CONFIG_PATH=/tmp/autotouch-audit.json && "
93
+ "read -s AUTOTOUCH_BOOTSTRAP_PASSWORD && export AUTOTOUCH_BOOTSTRAP_PASSWORD && "
92
94
  "autotouch auth bootstrap --first-name Ada --last-name Lovelace "
93
- "--email ada+audit@example.com --password 'use-a-strong-random-password' "
95
+ "--email ada+audit@example.com --password \"$AUTOTOUCH_BOOTSTRAP_PASSWORD\" "
94
96
  "--organization-name \"Audit Org\" --save-key`",
95
97
  file=sys.stderr,
96
98
  )
@@ -157,4 +159,3 @@ def auth_headers(token: Optional[str], *, use_x_api_key: bool = False) -> Dict[s
157
159
  if use_x_api_key:
158
160
  return {"X-API-Key": token}
159
161
  return {"Authorization": f"Bearer {token}"}
160
-