@withone/cli 1.27.1 → 1.29.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
@@ -124,15 +124,22 @@ Set up your API key and install the MCP server into your AI agents.
124
124
  one init
125
125
  ```
126
126
 
127
- Supports Claude Code, Claude Desktop, Cursor, Windsurf, Codex, and Kiro. Installs globally by default, or per-project with `-p` so your team can share configs (each person uses their own API key).
127
+ Supports Claude Code, Claude Desktop, Cursor, Windsurf, Codex, and Kiro.
128
128
 
129
- If you've already set up, `one init` shows your current status and lets you update your key, install to more agents, or reconfigure.
129
+ **Global vs. project scope.** `one init` is interactive and asks where the setup should live:
130
+
131
+ - **Global** (`~/.one/config.json`) — applies to every folder. Best when you only need one workspace / API key.
132
+ - **Project** (`~/.one/projects/<slug>/config.json`) — scoped to the current project, stored under your home directory so secrets never land in git. Use this when different projects need different API keys, connections, or access control.
133
+
134
+ When you run `one` in a project, it uses the project config if one exists and falls back to the global config otherwise. Use `one config path` to see which config is active and the full resolution order.
135
+
136
+ If you've already set up, `one init` shows your current status for the active scope and lets you update your key, install to more agents, or reconfigure.
130
137
 
131
138
  | Flag | What it does |
132
139
  |------|-------------|
133
140
  | `-y` | Skip confirmations |
134
- | `-g` | Install globally (default) |
135
- | `-p` | Install for current project only |
141
+ | `-g` | Non-interactive: write the One config globally (`~/.one/config.json`) |
142
+ | `-p` | Non-interactive: write the One config for this project (`~/.one/projects/<slug>/config.json`) |
136
143
 
137
144
  ### `one add <platform>`
138
145
 
@@ -391,6 +398,30 @@ Settings propagate automatically to all installed agent configs.
391
398
 
392
399
  Auto-sync refuses to resurrect skills if you opted out of skill installation during `one init` — the canonical dir has to already exist.
393
400
 
401
+ ### Project config (`.onerc`)
402
+
403
+ Drop a `.onerc` file in your project root to override global settings per-project. Simple `KEY=VALUE` format; `#` for comments. Read from the current working directory (no parent lookup).
404
+
405
+ | Key | Purpose |
406
+ |-----|---------|
407
+ | `ONE_SECRET` | API key (also honored as env var) |
408
+ | `ONE_API_BASE` | API base URL (also honored as env var) |
409
+ | `ONE_PERMISSIONS` | `admin` / `write` / `read` |
410
+ | `ONE_CONNECTION_KEYS` | Comma-separated connection-key allowlist |
411
+ | `ONE_ACTION_IDS` | Comma-separated action-ID allowlist |
412
+ | `ONE_KNOWLEDGE_AGENT` | `true` / `false` — knowledge-only mode |
413
+
414
+ Precedence: env var > `.onerc` > `~/.one/config.json`.
415
+
416
+ ```bash
417
+ # .onerc
418
+ ONE_SECRET=sk_live_xxx
419
+ ONE_API_BASE=https://development-api.withone.ai
420
+ ONE_PERMISSIONS=read
421
+ ```
422
+
423
+ > ⚠️ **Add `.onerc` to your `.gitignore`.** If you put `ONE_SECRET` in it, committing the file will leak your API key. Treat `.onerc` like `.env` — never check it in.
424
+
394
425
  ## The workflow
395
426
 
396
427
  The power of One is in the workflow. Every interaction follows the same pattern:
package/dist/index.js CHANGED
@@ -36,24 +36,88 @@ import path from "path";
36
36
  import os from "os";
37
37
  var CONFIG_DIR = path.join(os.homedir(), ".one");
38
38
  var CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
39
- function getConfigPath() {
40
- return CONFIG_FILE;
39
+ var PROJECTS_DIR = path.join(CONFIG_DIR, "projects");
40
+ function getProjectRoot(cwd = process.cwd()) {
41
+ let dir = path.resolve(cwd);
42
+ const root = path.parse(dir).root;
43
+ while (dir !== root) {
44
+ if (fs.existsSync(path.join(dir, ".git")) || fs.existsSync(path.join(dir, "package.json"))) {
45
+ return dir;
46
+ }
47
+ dir = path.dirname(dir);
48
+ }
49
+ return path.resolve(cwd);
41
50
  }
42
- function configExists() {
43
- return fs.existsSync(CONFIG_FILE);
51
+ function getProjectSlug(projectRoot = getProjectRoot()) {
52
+ return projectRoot.replace(/[\\/]/g, "-");
44
53
  }
45
- function readConfig() {
46
- if (!configExists()) {
47
- return null;
54
+ function getProjectConfigDir(projectRoot = getProjectRoot()) {
55
+ return path.join(PROJECTS_DIR, getProjectSlug(projectRoot));
56
+ }
57
+ function getProjectConfigPath(projectRoot = getProjectRoot()) {
58
+ return path.join(getProjectConfigDir(projectRoot), "config.json");
59
+ }
60
+ function getGlobalConfigPath() {
61
+ return CONFIG_FILE;
62
+ }
63
+ function resolveConfig() {
64
+ const projectRoot = getProjectRoot();
65
+ const projectSlug = getProjectSlug(projectRoot);
66
+ const projectPath = getProjectConfigPath(projectRoot);
67
+ if (fs.existsSync(projectPath)) {
68
+ const config2 = readConfigFile(projectPath);
69
+ if (config2) {
70
+ return { config: config2, scope: "project", path: projectPath, projectRoot, projectSlug };
71
+ }
48
72
  }
73
+ if (fs.existsSync(CONFIG_FILE)) {
74
+ const config2 = readConfigFile(CONFIG_FILE);
75
+ if (config2) {
76
+ return { config: config2, scope: "global", path: CONFIG_FILE, projectRoot, projectSlug };
77
+ }
78
+ }
79
+ return { config: null, scope: null, path: CONFIG_FILE, projectRoot, projectSlug };
80
+ }
81
+ function readConfigFile(filePath) {
49
82
  try {
50
- const content = fs.readFileSync(CONFIG_FILE, "utf-8");
83
+ const content = fs.readFileSync(filePath, "utf-8");
51
84
  return JSON.parse(content);
52
85
  } catch {
53
86
  return null;
54
87
  }
55
88
  }
56
- function writeConfig(config2) {
89
+ function configExists() {
90
+ return resolveConfig().config !== null;
91
+ }
92
+ function globalConfigExists() {
93
+ return fs.existsSync(CONFIG_FILE);
94
+ }
95
+ function projectConfigExists(projectRoot = getProjectRoot()) {
96
+ return fs.existsSync(getProjectConfigPath(projectRoot));
97
+ }
98
+ function readConfig() {
99
+ return resolveConfig().config;
100
+ }
101
+ function readGlobalConfig() {
102
+ if (!fs.existsSync(CONFIG_FILE)) return null;
103
+ return readConfigFile(CONFIG_FILE);
104
+ }
105
+ function readProjectConfig() {
106
+ const projectPath = getProjectConfigPath();
107
+ if (!fs.existsSync(projectPath)) return null;
108
+ return readConfigFile(projectPath);
109
+ }
110
+ function writeConfig(config2, scope) {
111
+ const targetScope = scope ?? resolveConfig().scope ?? "global";
112
+ if (targetScope === "project") {
113
+ const dir = getProjectConfigDir();
114
+ if (!fs.existsSync(dir)) {
115
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
116
+ }
117
+ const filePath = getProjectConfigPath();
118
+ fs.writeFileSync(filePath, JSON.stringify(config2, null, 2), { mode: 384 });
119
+ return;
120
+ }
57
121
  if (!fs.existsSync(CONFIG_DIR)) {
58
122
  fs.mkdirSync(CONFIG_DIR, { mode: 448 });
59
123
  }
@@ -879,24 +943,98 @@ async function initCommand(options) {
879
943
  if (isAgentMode()) {
880
944
  error("This command requires interactive input. Run without --agent.");
881
945
  }
882
- const existingConfig = readConfig();
883
946
  printBanner();
947
+ const scope = await chooseConfigScope(options);
948
+ if (scope === null) {
949
+ p3.cancel("Setup cancelled.");
950
+ return;
951
+ }
952
+ const existingConfig = scope === "project" ? readProjectConfig() : readGlobalConfig();
884
953
  if (existingConfig) {
885
- await handleExistingConfig(existingConfig.apiKey, options);
954
+ await handleExistingConfig(existingConfig.apiKey, scope, options);
886
955
  return;
887
956
  }
888
- await freshSetup(options);
957
+ await freshSetup(scope, options);
958
+ }
959
+ async function chooseConfigScope(options) {
960
+ if (options.global) return "global";
961
+ if (options.project) return "project";
962
+ const resolved = resolveConfig();
963
+ const hasGlobal = globalConfigExists();
964
+ const hasProject = projectConfigExists();
965
+ const projectRoot = resolved.projectRoot;
966
+ const projectName = path4.basename(projectRoot);
967
+ const homeGlobal = tildify(getGlobalConfigPath());
968
+ const homeProject = tildify(getProjectConfigPath(projectRoot));
969
+ if (hasProject) {
970
+ console.log();
971
+ console.log(` ${pc2.dim("Project:")} ${projectName} ${pc2.dim(projectRoot)}`);
972
+ console.log(` ${pc2.bold("Active config:")} ${pc2.cyan("project")} ${pc2.dim("\xB7 " + homeProject)}`);
973
+ console.log();
974
+ if (hasGlobal) {
975
+ const which2 = await p3.select({
976
+ message: "Which config do you want to edit?",
977
+ options: [
978
+ { value: "project", label: `This project (${projectName})`, hint: homeProject },
979
+ { value: "global", label: "Global (all folders)", hint: homeGlobal }
980
+ ],
981
+ initialValue: "project"
982
+ });
983
+ if (p3.isCancel(which2)) return null;
984
+ return which2;
985
+ }
986
+ return "project";
987
+ }
988
+ console.log();
989
+ console.log(` ${pc2.bold("Initializing One")}`);
990
+ console.log(` ${pc2.dim("\u2500".repeat(42))}`);
991
+ console.log(` ${pc2.dim("Project:")} ${projectName} ${pc2.dim(projectRoot)}`);
992
+ console.log(` ${pc2.dim("Global:")} ${hasGlobal ? pc2.green("\u2713 configured") : pc2.yellow("\u2014 not set up")} ${pc2.dim(homeGlobal)}`);
993
+ console.log(` ${pc2.dim("Project:")} ${pc2.yellow("\u2014 not set up")} ${pc2.dim(homeProject)}`);
994
+ console.log();
995
+ const defaultScope = hasGlobal ? "project" : "global";
996
+ const hint = hasGlobal ? "Your global config stays as-is. This folder gets its own setup." : "No global config yet \u2014 this becomes your default for every folder.";
997
+ p3.note(hint, defaultScope === "project" ? "Recommended: project" : "Recommended: global");
998
+ const which = await p3.select({
999
+ message: "Where should this setup live?",
1000
+ options: [
1001
+ {
1002
+ value: "project",
1003
+ label: `This project only (${projectName})`,
1004
+ hint: "different API key / connections just for this folder"
1005
+ },
1006
+ {
1007
+ value: "global",
1008
+ label: "Globally (all folders)",
1009
+ hint: "applies everywhere you run `one`"
1010
+ }
1011
+ ],
1012
+ initialValue: defaultScope
1013
+ });
1014
+ if (p3.isCancel(which)) return null;
1015
+ return which;
1016
+ }
1017
+ function tildify(filePath) {
1018
+ const home = os4.homedir();
1019
+ return filePath.startsWith(home) ? "~" + filePath.slice(home.length) : filePath;
1020
+ }
1021
+ function scopeLabel(scope) {
1022
+ return scope === "project" ? pc2.cyan("[project]") : pc2.magenta("[global]");
889
1023
  }
890
- async function handleExistingConfig(apiKey, options) {
1024
+ function scopedMessage(scope, message) {
1025
+ return `${scopeLabel(scope)} ${message}`;
1026
+ }
1027
+ async function handleExistingConfig(apiKey, scope, options) {
891
1028
  const statuses = getAgentStatuses();
892
1029
  const masked = maskApiKey(apiKey);
893
1030
  const skillInstalled = isSkillInstalled2();
1031
+ const activeConfigPath = scope === "project" ? getProjectConfigPath() : getGlobalConfigPath();
894
1032
  console.log();
895
- console.log(` ${pc2.bold("Current Setup")}`);
1033
+ console.log(` ${pc2.bold("Current Setup")} ${scopeLabel(scope)}`);
896
1034
  console.log(` ${pc2.dim("\u2500".repeat(42))}`);
897
1035
  console.log(` ${pc2.dim("API Key:")} ${masked}`);
898
1036
  console.log(` ${pc2.dim("Skill:")} ${skillInstalled ? pc2.green("installed") : pc2.yellow("not installed")}`);
899
- console.log(` ${pc2.dim("Config:")} ${getConfigPath()}`);
1037
+ console.log(` ${pc2.dim("Config:")} ${tildify(activeConfigPath)}`);
900
1038
  const ac = getAccessControl();
901
1039
  if (Object.keys(ac).length > 0) {
902
1040
  console.log();
@@ -938,7 +1076,7 @@ async function handleExistingConfig(apiKey, options) {
938
1076
  label: "Start fresh (reconfigure everything)"
939
1077
  });
940
1078
  const action = await p3.select({
941
- message: "What would you like to do?",
1079
+ message: scopedMessage(scope, "What would you like to do?"),
942
1080
  options: actionOptions
943
1081
  });
944
1082
  if (p3.isCancel(action)) {
@@ -965,21 +1103,21 @@ async function handleExistingConfig(apiKey, options) {
965
1103
  p3.outro("Done.");
966
1104
  break;
967
1105
  case "update-key":
968
- await handleUpdateKey(statuses);
1106
+ await handleUpdateKey(statuses, scope);
969
1107
  break;
970
1108
  case "access-control":
971
1109
  await configCommand();
972
1110
  break;
973
1111
  case "start-fresh":
974
- await freshSetup({ yes: true });
1112
+ await freshSetup(scope, { yes: true });
975
1113
  break;
976
1114
  }
977
1115
  }
978
- async function handleUpdateKey(statuses) {
1116
+ async function handleUpdateKey(statuses, scope) {
979
1117
  p3.note(`Get your API key at:
980
- ${pc2.cyan(getApiKeyUrl())}`, "API Key");
1118
+ ${pc2.cyan(getApiKeyUrl())}`, `API Key ${scopeLabel(scope)}`);
981
1119
  const openBrowser = await p3.confirm({
982
- message: "Open browser to get API key?",
1120
+ message: scopedMessage(scope, "Open browser to get API key?"),
983
1121
  initialValue: true
984
1122
  });
985
1123
  if (p3.isCancel(openBrowser)) {
@@ -990,7 +1128,7 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
990
1128
  await openApiKeyPage();
991
1129
  }
992
1130
  const newKey = await p3.text({
993
- message: "Enter your new One API key:",
1131
+ message: scopedMessage(scope, "Enter your new One API key:"),
994
1132
  placeholder: "sk_live_...",
995
1133
  validate: (value) => {
996
1134
  if (!value) return "API key is required";
@@ -1026,13 +1164,18 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
1026
1164
  reinstalled.push(`${s.agent.name} (project)`);
1027
1165
  }
1028
1166
  }
1029
- const config2 = readConfig();
1030
- writeConfig({
1031
- apiKey: newKey,
1032
- installedAgents: config2?.installedAgents ?? [],
1033
- createdAt: config2?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1034
- accessControl: config2?.accessControl
1035
- });
1167
+ const current = scope === "project" ? readProjectConfig() : readGlobalConfig();
1168
+ writeConfig(
1169
+ {
1170
+ apiKey: newKey,
1171
+ installedAgents: current?.installedAgents ?? [],
1172
+ createdAt: current?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1173
+ accessControl: current?.accessControl,
1174
+ apiBase: current?.apiBase,
1175
+ cacheTtl: current?.cacheTtl
1176
+ },
1177
+ scope
1178
+ );
1036
1179
  if (reinstalled.length > 0) {
1037
1180
  p3.log.success(`Updated MCP configs: ${reinstalled.join(", ")}`);
1038
1181
  }
@@ -1195,11 +1338,11 @@ function printOnboardingPrompt() {
1195
1338
  console.log(pc2.cyan(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
1196
1339
  console.log();
1197
1340
  }
1198
- async function freshSetup(options) {
1341
+ async function freshSetup(scope, options) {
1199
1342
  p3.note(`Get your API key at:
1200
- ${pc2.cyan(getApiKeyUrl())}`, "API Key");
1343
+ ${pc2.cyan(getApiKeyUrl())}`, `API Key ${scopeLabel(scope)}`);
1201
1344
  const openBrowser = await p3.confirm({
1202
- message: "Open browser to get API key?",
1345
+ message: scopedMessage(scope, "Open browser to get API key?"),
1203
1346
  initialValue: true
1204
1347
  });
1205
1348
  if (p3.isCancel(openBrowser)) {
@@ -1210,7 +1353,7 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
1210
1353
  await openApiKeyPage();
1211
1354
  }
1212
1355
  const apiKey = await p3.text({
1213
- message: "Enter your One API key:",
1356
+ message: scopedMessage(scope, "Enter your One API key:"),
1214
1357
  placeholder: "sk_live_...",
1215
1358
  validate: (value) => {
1216
1359
  if (!value) return "API key is required";
@@ -1234,15 +1377,24 @@ ${pc2.cyan(getApiKeyUrl())}`, "API Key");
1234
1377
  process.exit(1);
1235
1378
  }
1236
1379
  spinner5.stop("API key validated");
1237
- writeConfig({
1238
- apiKey,
1239
- installedAgents: [],
1240
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
1241
- });
1380
+ writeConfig(
1381
+ {
1382
+ apiKey,
1383
+ installedAgents: [],
1384
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1385
+ },
1386
+ scope
1387
+ );
1242
1388
  await promptSkillInstall();
1243
1389
  await promptConnectIntegrations(apiKey);
1390
+ const savedPath = scope === "project" ? getProjectConfigPath() : getGlobalConfigPath();
1391
+ const resolutionHint = scope === "project" ? `When you run ${pc2.cyan("one")} from ${pc2.bold(path4.basename(getProjectRoot()))}, it uses this project config.
1392
+ From anywhere else, it falls back to your global config.` : `This config applies to every folder unless a project config is set.`;
1244
1393
  p3.note(
1245
- `Config saved to: ${pc2.dim(getConfigPath())}`,
1394
+ `${scopeLabel(scope)} Config saved to:
1395
+ ${pc2.dim(tildify(savedPath))}
1396
+
1397
+ ${resolutionHint}`,
1246
1398
  "Setup Complete"
1247
1399
  );
1248
1400
  printOnboardingPrompt();
@@ -3870,7 +4022,7 @@ var GUIDE_OVERVIEW = `# One CLI \u2014 Agent Guide
3870
4022
 
3871
4023
  ## Setup
3872
4024
 
3873
- 1. Run \`one init\` to configure your API key
4025
+ 1. Run \`one init\` to configure your API key (interactive \u2014 can be global or per-project)
3874
4026
  2. Run \`one add <platform>\` to connect platforms via OAuth
3875
4027
  3. Run \`one --agent connection list\` to verify connections
3876
4028
 
@@ -4650,12 +4802,46 @@ program.hook("postAction", async () => {
4650
4802
  if (!isNewerVersion(info.version, current)) return;
4651
4803
  autoUpdate(info.version, info.publishedAt);
4652
4804
  });
4653
- program.command("init").description("Set up One and install MCP to your AI agents").option("-y, --yes", "Skip confirmations").option("-g, --global", "Install MCP globally (available in all projects)").option("-p, --project", "Install MCP for this project only (creates .mcp.json)").action(async (options) => {
4805
+ program.command("init").description("Set up One and install MCP to your AI agents (interactive: picks global or project scope)").option("-y, --yes", "Skip confirmations").option("-g, --global", "Write the One config globally (~/.one/config.json) \u2014 skips the scope picker").option("-p, --project", "Write the One config for this project only (~/.one/projects/<slug>/) \u2014 skips the scope picker").action(async (options) => {
4654
4806
  await initCommand(options);
4655
4807
  });
4656
4808
  var config = program.command("config").description("Configure the CLI (access control, skills, ...)").action(async () => {
4657
4809
  await configCommand();
4658
4810
  });
4811
+ config.command("path").description("Show the active config path, scope, and the fallback chain (project \u2192 global)").action(() => {
4812
+ const resolved = resolveConfig();
4813
+ const globalPath = getGlobalConfigPath();
4814
+ const projectPath = getProjectConfigPath(resolved.projectRoot);
4815
+ const hasGlobal = globalConfigExists();
4816
+ const hasProject = projectConfigExists(resolved.projectRoot);
4817
+ if (isAgentMode()) {
4818
+ json({
4819
+ command: "config path",
4820
+ scope: resolved.scope,
4821
+ path: resolved.path,
4822
+ projectRoot: resolved.projectRoot,
4823
+ projectSlug: resolved.projectSlug,
4824
+ fallback: {
4825
+ project: { path: projectPath, exists: hasProject },
4826
+ global: { path: globalPath, exists: hasGlobal }
4827
+ }
4828
+ });
4829
+ return;
4830
+ }
4831
+ if (!resolved.scope) {
4832
+ console.log("No One config found.");
4833
+ console.log(` project: ${projectPath} (not set up)`);
4834
+ console.log(` global: ${globalPath} (not set up)`);
4835
+ console.log("\nRun 'one init' to get started.");
4836
+ return;
4837
+ }
4838
+ console.log(`Active: ${resolved.scope}`);
4839
+ console.log(`Path: ${resolved.path}`);
4840
+ console.log(`
4841
+ Resolution order (first match wins):`);
4842
+ console.log(` 1. project ${projectPath} ${hasProject ? "\u2713" : "\u2014"}`);
4843
+ console.log(` 2. global ${globalPath} ${hasGlobal ? "\u2713" : "\u2014"}`);
4844
+ });
4659
4845
  var configSkills = config.command("skills").description("Manage locally-installed skill files");
4660
4846
  configSkills.command("sync").description("Re-copy packaged skill files over the local install (runs automatically after CLI upgrades)").action(async () => {
4661
4847
  const result = forceSyncSkills();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/cli",
3
- "version": "1.27.1",
3
+ "version": "1.29.0",
4
4
  "description": "CLI for managing One",
5
5
  "type": "module",
6
6
  "files": [
@@ -22,6 +22,8 @@ description: |
22
22
 
23
23
  You have access to the One CLI which lets you interact with 250+ third-party platforms through their APIs. Always include the `--agent` flag right after `one` for structured JSON output.
24
24
 
25
+ If the user wants a separate API key / connections for a specific project (vs. their default), walk them through running `one init` from that project folder and picking the "project" scope — see `references/scoping.md`.
26
+
25
27
  ## Core Workflow: search -> knowledge -> execute
26
28
 
27
29
  Always follow this sequence when the user wants to do something on a connected platform:
@@ -0,0 +1,35 @@
1
+ # Project vs. global config
2
+
3
+ The One CLI can be configured at two scopes:
4
+
5
+ - **Global** — `~/.one/config.json`. Applies everywhere the user runs `one`.
6
+ - **Project** — `~/.one/projects/<slug>/config.json`, where `<slug>` is the project root path with slashes replaced by dashes (e.g. `/Users/jane/acme` → `-Users-jane-acme`). Only applies when running `one` from inside that project folder.
7
+
8
+ **Resolution order:** env vars → `.onerc` in cwd → project config → global config. Project config wins when present; otherwise the CLI falls back to the global config.
9
+
10
+ ## When to suggest project scope
11
+
12
+ Suggest project scope when the user wants any of the following for a specific folder only, without changing their default setup:
13
+
14
+ - A different One API key (e.g. sandbox workspace for a client project)
15
+ - A different set of connections / connection keys
16
+ - Different access control (permissions, scoped connections, knowledge-only mode)
17
+
18
+ ## How to set it up
19
+
20
+ Do **not** hand-edit `.onerc` or config files. Walk the user through the interactive init:
21
+
22
+ ```bash
23
+ cd /path/to/the/project
24
+ one init
25
+ ```
26
+
27
+ When `init` asks "Where should this setup live?", pick **"This project only"**. Init will write the config to `~/.one/projects/<slug>/config.json` and everything else (skill install, MCP) stays untouched.
28
+
29
+ To see which config is currently active and the full fallback chain:
30
+
31
+ ```bash
32
+ one --agent config path
33
+ ```
34
+
35
+ To switch an existing project back to using the global config, delete its project config file — the CLI will automatically fall back to global on the next run.