@withone/cli 1.49.0 → 1.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -179,7 +179,7 @@ Opens your browser, you authorize, done. The CLI polls until the connection is l
179
179
 
180
180
  ### `one list`
181
181
 
182
- List your active connections with their status and connection keys.
182
+ List your active connections with their status, connection keys, and what your access config lets you run on each.
183
183
 
184
184
  ```bash
185
185
  one list
@@ -193,6 +193,37 @@ one list
193
193
 
194
194
  You need the connection key (rightmost column) when executing actions.
195
195
 
196
+ **Access reporting.** Every connection carries an `access` field describing what the current [access control](#one-config) config permits — so an agent knows its reach up front instead of discovering it as a failure mid-workflow. Three policies:
197
+
198
+ | Policy | Means | Shown when |
199
+ |--------|-------|-----------|
200
+ | `{"policy": "full"}` | Every action on the connection | Default (admin, no action allowlist) |
201
+ | `{"policy": "methods", "methods": ["GET"]}` | Only actions with those HTTP methods | Permission level is `read` or `write` |
202
+ | `{"policy": "actions", "actions": [{"actionId": "...", "title": "...", "method": "..."}]}` | Only these specific actions | An action allowlist is configured |
203
+
204
+ An action allowlist wins over the permission level (and is then method-filtered by it). When the policy is `actions`, those actions are exactly what may run — no `actions search` needed.
205
+
206
+ ```jsonc
207
+ // one --agent list
208
+ {
209
+ "total": 2,
210
+ "showing": 2,
211
+ "connections": [
212
+ {
213
+ "platform": "gmail",
214
+ "state": "operational",
215
+ "key": "live::gmail::default::abc123",
216
+ "access": { "policy": "methods", "methods": ["GET"] }
217
+ }
218
+ ],
219
+ "accessHint": "Permission level \"read\": only GET actions will execute."
220
+ }
221
+ ```
222
+
223
+ `knowledgeOnly: true` appears when knowledge-only mode is on (execution disabled), and `unresolvedActionIds` lists any allowlisted action ids that could not be looked up. In human output, an `Access` column and a summary note appear only when access is actually scoped.
224
+
225
+ This mirrors the `access` field on the One MCP server's `list_one_integrations` tool, so both surfaces report access the same way.
226
+
196
227
  ### `one connection delete <connection-key>`
197
228
 
198
229
  Remove a connection by its key.
@@ -576,6 +607,8 @@ one config
576
607
 
577
608
  Settings propagate automatically to all installed agent configs.
578
609
 
610
+ Run [`one list`](#one-list) to see the effect: each connection reports an `access` field describing exactly what these settings let you run on it.
611
+
579
612
  #### `one config skills status` / `one config skills sync`
580
613
 
581
614
  `one init` copies the packaged skill files (`SKILL.md`, `references/`) into `~/.agents/skills/one/` and symlinks per-agent paths to that canonical directory. When the CLI self-updates, the skill files in the canonical dir would normally stay frozen at the version that was installed. To prevent stale docs, every CLI command checks a `.one-cli-version` marker in the canonical dir and silently refreshes the skill files if they don't match the running CLI version. No user action required.
@@ -2319,7 +2319,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
2319
2319
  if (flowStack.includes(resolvedKey)) {
2320
2320
  throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
2321
2321
  }
2322
- const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-IBB42ED2.js");
2322
+ const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-YDKAYGZW.js");
2323
2323
  const { flow: subFlow, rootDir: subRootDir } = loadFlowWithMeta2(resolvedKey);
2324
2324
  const subContext = await executeFlow(
2325
2325
  subFlow,
@@ -3238,11 +3238,11 @@ export {
3238
3238
  ApiError,
3239
3239
  OneApi,
3240
3240
  TimeoutError,
3241
+ PERMISSION_METHODS,
3241
3242
  filterByPermissions,
3242
3243
  isMethodAllowed,
3243
3244
  isActionAllowed,
3244
3245
  buildActionKnowledgeWithGuidance,
3245
- validateActionInput,
3246
3246
  knowledgeCachePath,
3247
3247
  searchCachePath,
3248
3248
  readCache,
@@ -3256,6 +3256,7 @@ export {
3256
3256
  clearEntry,
3257
3257
  makeCacheEntry,
3258
3258
  resolveActionDetails,
3259
+ validateActionInput,
3259
3260
  FLOW_SCHEMA,
3260
3261
  getStepTypeDescriptor,
3261
3262
  getNestedStepsKeys,
@@ -12,7 +12,7 @@ import {
12
12
  stripStepsAlias,
13
13
  summarizeFlowInputs,
14
14
  walkSteps
15
- } from "./chunk-2TWFL3CS.js";
15
+ } from "./chunk-D6W756DN.js";
16
16
  import "./chunk-44CV5IMX.js";
17
17
  import "./chunk-SO323PZP.js";
18
18
  export {
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  FLOW_SCHEMA,
4
4
  FlowRunner,
5
5
  OneApi,
6
+ PERMISSION_METHODS,
6
7
  TimeoutError,
7
8
  buildActionKnowledgeWithGuidance,
8
9
  buildCacheMeta,
@@ -31,7 +32,7 @@ import {
31
32
  validateActionInput,
32
33
  walkSteps,
33
34
  writeCache
34
- } from "./chunk-2TWFL3CS.js";
35
+ } from "./chunk-D6W756DN.js";
35
36
  import {
36
37
  memSqlCommand
37
38
  } from "./chunk-4WWK3GO5.js";
@@ -1942,6 +1943,54 @@ function maskApiKey(key) {
1942
1943
  import * as p4 from "@clack/prompts";
1943
1944
  import pc4 from "picocolors";
1944
1945
 
1946
+ // src/lib/access.ts
1947
+ async function resolveAllowedActions(api, actionIds) {
1948
+ if (actionIds.includes("*")) return [];
1949
+ const resolved = await Promise.all(
1950
+ actionIds.map(async (actionId) => {
1951
+ try {
1952
+ const { details } = await resolveActionDetails(api, actionId, { warn: () => {
1953
+ } });
1954
+ if (!details.connectionPlatform) return null;
1955
+ return {
1956
+ actionId,
1957
+ title: details.title,
1958
+ method: details.method,
1959
+ platform: details.connectionPlatform
1960
+ };
1961
+ } catch {
1962
+ return null;
1963
+ }
1964
+ })
1965
+ );
1966
+ return resolved.filter((a) => a !== null);
1967
+ }
1968
+ function computeConnectionAccess(platform, permissions, allowedActionIds, grantedActions) {
1969
+ if (!allowedActionIds.includes("*")) {
1970
+ const actions2 = grantedActions.filter((a) => a.platform === platform).map(({ actionId, title, method }) => ({ actionId, title, method }));
1971
+ return { policy: "actions", actions: actions2 };
1972
+ }
1973
+ const methods = PERMISSION_METHODS[permissions];
1974
+ if (methods !== null) {
1975
+ return { policy: "methods", methods };
1976
+ }
1977
+ return { policy: "full" };
1978
+ }
1979
+ function formatAccess(access, maxActions = 2) {
1980
+ switch (access.policy) {
1981
+ case "full":
1982
+ return "full";
1983
+ case "methods":
1984
+ return access.methods.join(", ");
1985
+ case "actions": {
1986
+ if (access.actions.length === 0) return "none";
1987
+ const shown = access.actions.slice(0, maxActions).map((a) => `${a.title} (${a.method})`);
1988
+ const rest = access.actions.length - shown.length;
1989
+ return rest > 0 ? `${shown.join(", ")} +${rest} more` : shown.join(", ");
1990
+ }
1991
+ }
1992
+ }
1993
+
1945
1994
  // src/lib/platforms.ts
1946
1995
  function findPlatform(platforms, query) {
1947
1996
  const normalizedQuery = query.toLowerCase().trim();
@@ -2155,8 +2204,16 @@ async function connectionListCommand(options) {
2155
2204
  try {
2156
2205
  const allConnections = await api.listConnections();
2157
2206
  const ac = getAccessControlFromAllSources();
2207
+ const permissions = ac.permissions || "admin";
2158
2208
  const allowedKeys = ac.connectionKeys || ["*"];
2209
+ const actionIds = ac.actionIds || ["*"];
2210
+ const knowledgeOnly = ac.knowledgeAgent || false;
2159
2211
  const accessFiltered = allowedKeys.includes("*") ? allConnections : allConnections.filter((conn) => allowedKeys.includes(conn.key));
2212
+ const resolvedActions = await resolveAllowedActions(api, actionIds);
2213
+ const unresolvedActionIds = actionIds.includes("*") ? [] : actionIds.filter((id) => !resolvedActions.some((a) => a.actionId === id));
2214
+ const grantedActions = resolvedActions.filter((a) => isMethodAllowed(a.method, permissions));
2215
+ const accessFor = (platform) => computeConnectionAccess(platform, permissions, actionIds, grantedActions);
2216
+ const hintText = accessHint(permissions, actionIds, knowledgeOnly);
2160
2217
  const searchQuery = options?.search?.toLowerCase();
2161
2218
  const filtered = searchQuery ? accessFiltered.filter((conn) => conn.platform.toLowerCase().includes(searchQuery)) : accessFiltered;
2162
2219
  const limitArg = options?.limit ? parseInt(options.limit, 10) : void 0;
@@ -2172,8 +2229,12 @@ async function connectionListCommand(options) {
2172
2229
  state: conn.state,
2173
2230
  key: conn.key,
2174
2231
  ...conn.name && { name: conn.name },
2175
- ...conn.tags?.length && { tags: conn.tags }
2232
+ ...conn.tags?.length && { tags: conn.tags },
2233
+ access: accessFor(conn.platform)
2176
2234
  })),
2235
+ ...knowledgeOnly && { knowledgeOnly: true },
2236
+ ...unresolvedActionIds.length > 0 && { unresolvedActionIds },
2237
+ ...hintText && { accessHint: hintText },
2177
2238
  ...limited.length < filtered.length && {
2178
2239
  hint: `Showing ${limited.length} of ${filtered.length} connections. Use --search <query> to filter by platform or --limit <n> to see more.`
2179
2240
  }
@@ -2206,20 +2267,34 @@ Add one with: ${pc4.cyan("one connection add gmail")}`,
2206
2267
  platform: conn.platform,
2207
2268
  state: conn.state,
2208
2269
  key: conn.key,
2209
- tags: conn.tags?.length ? conn.tags.join(", ") : ""
2270
+ tags: conn.tags?.length ? conn.tags.join(", ") : "",
2271
+ access: formatAccess(accessFor(conn.platform))
2210
2272
  }));
2211
2273
  const hasTags = rows.some((r) => r.tags);
2274
+ const hasScopedAccess = rows.some((r) => r.access !== "full");
2212
2275
  printTable(
2213
2276
  [
2214
2277
  { key: "status", label: "" },
2215
2278
  { key: "platform", label: "Platform" },
2216
2279
  { key: "state", label: "Status" },
2217
2280
  { key: "key", label: "Connection Key", color: pc4.dim },
2218
- ...hasTags ? [{ key: "tags", label: "Tags", color: pc4.dim }] : []
2281
+ ...hasTags ? [{ key: "tags", label: "Tags", color: pc4.dim }] : [],
2282
+ ...hasScopedAccess ? [{ key: "access", label: "Access", color: pc4.yellow }] : []
2219
2283
  ],
2220
2284
  rows
2221
2285
  );
2222
2286
  console.log();
2287
+ if (hintText) {
2288
+ const lines = [hintText];
2289
+ if (unresolvedActionIds.length > 0) {
2290
+ lines.push(
2291
+ `Could not resolve ${unresolvedActionIds.length} allowlisted action id(s): ${unresolvedActionIds.join(", ")}`
2292
+ );
2293
+ }
2294
+ p4.note(`${wrapText(lines.join("\n"))}
2295
+
2296
+ Change it with: ${pc4.cyan("one config")}`, "Access");
2297
+ }
2223
2298
  if (displayed.length < filtered.length) {
2224
2299
  p4.note(
2225
2300
  `Showing ${displayed.length} of ${filtered.length}. Increase --limit or run without it to see all.`,
@@ -2292,6 +2367,37 @@ async function connectionDeleteCommand(connectionKey, options) {
2292
2367
  error(`Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
2293
2368
  }
2294
2369
  }
2370
+ function accessHint(permissions, actionIds, knowledgeOnly) {
2371
+ const parts = [];
2372
+ if (!actionIds.includes("*")) {
2373
+ parts.push(
2374
+ "Action-scoped: each connection's `access.actions` are the only actions you may run \u2014 use them directly, no `actions search` needed."
2375
+ );
2376
+ } else if (permissions !== "admin") {
2377
+ const methods = PERMISSION_METHODS[permissions]?.join(", ") ?? "";
2378
+ parts.push(`Permission level "${permissions}": only ${methods} actions will execute.`);
2379
+ }
2380
+ if (knowledgeOnly) {
2381
+ parts.push("Knowledge-only mode: `actions execute` is disabled \u2014 read knowledge and write code instead.");
2382
+ }
2383
+ return parts.length > 0 ? parts.join(" ") : null;
2384
+ }
2385
+ function wrapText(text5, width = 72) {
2386
+ return text5.split("\n").map((paragraph) => {
2387
+ const lines = [];
2388
+ let line = "";
2389
+ for (const word of paragraph.split(/\s+/)) {
2390
+ if (line && line.length + 1 + word.length > width) {
2391
+ lines.push(line);
2392
+ line = word;
2393
+ } else {
2394
+ line = line ? `${line} ${word}` : word;
2395
+ }
2396
+ }
2397
+ if (line) lines.push(line);
2398
+ return lines.join("\n");
2399
+ }).join("\n");
2400
+ }
2295
2401
  function getStatusIndicator(state) {
2296
2402
  switch (state) {
2297
2403
  case "operational":
@@ -9777,7 +9883,7 @@ Search for actions, read their docs, and execute them. This is the core workflow
9777
9883
 
9778
9884
  **Quick start:**
9779
9885
  \`\`\`bash
9780
- one --agent connection list # See connected platforms
9886
+ one --agent connection list # See connected platforms + your access on each
9781
9887
  one --agent connection delete <connection-key> # Remove a connection
9782
9888
  one --agent actions search <platform> "<query>" -t execute # Find an action
9783
9889
  one --agent actions knowledge <platform> <actionId> # Read docs (REQUIRED)
@@ -9866,6 +9972,7 @@ Request specific sections:
9866
9972
  - Always use the **exact action ID** from search results \u2014 don't guess
9867
9973
  - Always read **knowledge** before executing any action
9868
9974
  - Connection keys come from \`one connection list\` \u2014 don't hardcode them
9975
+ - \`connection list\` also reports an \`access\` field per connection (\`full\` / \`methods\` / \`actions\`) \u2014 read it before planning so you don't propose an action the access config will reject
9869
9976
  - Skills stay in lockstep with the CLI version automatically \u2014 every command checks a \`.one-cli-version\` marker in the canonical skill dir and refreshes the files if the CLI has been upgraded. Check manually with \`one config skills status\`; force a resync with \`one config skills sync\`
9870
9977
  `;
9871
9978
  var GUIDE_ACTIONS = `# One Actions \u2014 Reference
@@ -9880,7 +9987,17 @@ Always follow this sequence. Never skip the knowledge step.
9880
9987
  one --agent connection list
9881
9988
  \`\`\`
9882
9989
 
9883
- Returns platforms, status, connection keys, and tags.
9990
+ Returns platforms, status, connection keys, tags, and an \`access\` field per connection describing what the current access config lets you run there:
9991
+
9992
+ | \`access\` | Meaning |
9993
+ |----------|---------|
9994
+ | \`{"policy": "full"}\` | Every action on the connection |
9995
+ | \`{"policy": "methods", "methods": ["GET"]}\` | Only actions with these HTTP methods will execute |
9996
+ | \`{"policy": "actions", "actions": [{"actionId", "title", "method"}]}\` | Only these exact actions \u2014 use them directly, skip \`actions search\` |
9997
+
9998
+ Also present when relevant: \`knowledgeOnly: true\` (execution disabled \u2014 read knowledge and write code instead), \`unresolvedActionIds\` (allowlisted ids that could not be looked up), and \`accessHint\` (a one-line summary of the restriction).
9999
+
10000
+ Read \`access\` before planning \u2014 it prevents proposing an action the config will reject. Change it with \`one config\`.
9884
10001
 
9885
10002
  ### 1b. Delete a Connection
9886
10003
 
@@ -11456,7 +11573,7 @@ program.name("one").option("--agent", "Machine-readable JSON output (no colors,
11456
11573
  one whoami Show current user, organization, and project
11457
11574
 
11458
11575
  Workflow (use these in order):
11459
- 1. one list List your connected platforms and connection keys
11576
+ 1. one list List connected platforms, keys, and your access on each
11460
11577
  2. one actions search <platform> <q> Search for actions using natural language
11461
11578
  3. one actions knowledge <plat> <id> Get full docs for an action (ALWAYS do this before execute)
11462
11579
  4. one actions execute <p> <id> <key> Execute the action
@@ -11714,7 +11831,7 @@ var connection = program.command("connection").description("Manage connections")
11714
11831
  connection.command("add [platform]").alias("a").description("Add a new connection").option("--tag <name>", "Tag the new connection (disambiguates multiple connections per platform in sync/flow profiles)").action(async (platform, options) => {
11715
11832
  await connectionAddCommand(platform, { tag: options.tag });
11716
11833
  });
11717
- connection.command("list").alias("ls").description("List your connections").option("-s, --search <query>", "Filter connections by platform name").option("-l, --limit <n>", "Max connections to return (agent mode default: 20)").action(async (options) => {
11834
+ connection.command("list").alias("ls").description("List your connections and what your access config lets you run on each").option("-s, --search <query>", "Filter connections by platform name").option("-l, --limit <n>", "Max connections to return (agent mode default: 20)").action(async (options) => {
11718
11835
  await connectionListCommand(options);
11719
11836
  });
11720
11837
  connection.command("delete <connection-key>").alias("rm").description("Delete a connection").option("-f, --force", "Skip confirmation prompt").action(async (connectionKey, options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/cli",
3
- "version": "1.49.0",
3
+ "version": "1.50.0",
4
4
  "description": "CLI for managing One",
5
5
  "type": "module",
6
6
  "files": [
@@ -45,7 +45,21 @@ Always follow this sequence when the user wants to do something on a connected p
45
45
  one --agent connection list
46
46
  ```
47
47
 
48
- Returns connected platforms with their connection keys (needed for execution) and platform names in kebab-case (needed for searching).
48
+ Returns connected platforms with their connection keys (needed for execution), platform names in kebab-case (needed for searching), and an `access` field per connection telling you what you may run there.
49
+
50
+ **Read `access` before you plan a workflow** — it saves you from discovering a restriction as a 403 halfway through:
51
+
52
+ | `access` | What it means |
53
+ |----------|---------------|
54
+ | `{"policy": "full"}` | Every action on this connection is available |
55
+ | `{"policy": "methods", "methods": ["GET"]}` | Only actions with these HTTP methods will execute — don't propose writes |
56
+ | `{"policy": "actions", "actions": [...]}` | Only these exact actions may run. Each has `actionId`, `title`, `method` — **use them directly and skip `actions search`** |
57
+
58
+ Two more fields appear only when relevant:
59
+ - `"knowledgeOnly": true` — `actions execute` is disabled. Read knowledge and write integration code instead of executing.
60
+ - `"unresolvedActionIds": [...]` — allowlisted ids that couldn't be looked up; treat them as unavailable and tell the user.
61
+
62
+ An empty `actions` array means the allowlist grants nothing on that connection — say so rather than searching for alternatives.
49
63
 
50
64
  ### 1b. Delete a connection
51
65