@withone/cli 2.14.1 → 2.14.4

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/dist/index.js CHANGED
@@ -40,16 +40,18 @@ import {
40
40
  validateActionInput,
41
41
  walkSteps,
42
42
  writeCache
43
- } from "./chunk-IM3OA7CU.js";
43
+ } from "./chunk-33KUKSRH.js";
44
44
  import {
45
45
  memSqlCommand
46
46
  } from "./chunk-TSMVFVQ3.js";
47
47
  import {
48
+ assertSafeSyncNames,
48
49
  collectIdentityKeys,
49
50
  countRecords,
50
51
  deleteDatabase,
51
52
  deleteRecords,
52
53
  dropTable,
54
+ ensureDirInsideProject,
53
55
  ensureTable,
54
56
  evolveSchema,
55
57
  extractSearchableFromPaths,
@@ -74,7 +76,7 @@ import {
74
76
  writeDraftProfile,
75
77
  writePageToMemory,
76
78
  writeProfile
77
- } from "./chunk-LEJKEGB6.js";
79
+ } from "./chunk-AFZIE2QJ.js";
78
80
  import {
79
81
  getByDotPath
80
82
  } from "./chunk-44CV5IMX.js";
@@ -156,7 +158,7 @@ import {
156
158
 
157
159
  // src/cli.ts
158
160
  import { createRequire } from "module";
159
- import path14 from "path";
161
+ import path15 from "path";
160
162
  import { Command } from "commander";
161
163
 
162
164
  // src/commands/init.ts
@@ -1288,13 +1290,13 @@ async function chooseConfigScope(options) {
1288
1290
  const resolved = resolveConfig();
1289
1291
  const hasGlobal = globalConfigExists();
1290
1292
  const hasProject = projectConfigExists();
1291
- const projectRoot = resolved.projectRoot;
1292
- const projectName = path2.basename(projectRoot);
1293
+ const projectRoot2 = resolved.projectRoot;
1294
+ const projectName = path2.basename(projectRoot2);
1293
1295
  const homeGlobal = tildify(getGlobalConfigPath());
1294
- const homeProject = tildify(getProjectConfigPath(projectRoot));
1296
+ const homeProject = tildify(getProjectConfigPath(projectRoot2));
1295
1297
  if (hasProject) {
1296
1298
  console.log();
1297
- console.log(` ${pc3.dim("Project:")} ${projectName} ${pc3.dim(projectRoot)}`);
1299
+ console.log(` ${pc3.dim("Project:")} ${projectName} ${pc3.dim(projectRoot2)}`);
1298
1300
  console.log(` ${pc3.bold("Active config:")} ${pc3.cyan("project")} ${pc3.dim("\xB7 " + homeProject)}`);
1299
1301
  console.log();
1300
1302
  if (hasGlobal) {
@@ -1314,7 +1316,7 @@ async function chooseConfigScope(options) {
1314
1316
  console.log();
1315
1317
  console.log(` ${pc3.bold("Initializing One")}`);
1316
1318
  console.log(` ${pc3.dim("\u2500".repeat(42))}`);
1317
- console.log(` ${pc3.dim("Project:")} ${projectName} ${pc3.dim(projectRoot)}`);
1319
+ console.log(` ${pc3.dim("Project:")} ${projectName} ${pc3.dim(projectRoot2)}`);
1318
1320
  console.log(` ${pc3.dim("Global:")} ${hasGlobal ? pc3.green("\u2713 configured") : pc3.yellow("\u2014 not set up")} ${pc3.dim(homeGlobal)}`);
1319
1321
  console.log(` ${pc3.dim("Project:")} ${pc3.yellow("\u2014 not set up")} ${pc3.dim(homeProject)}`);
1320
1322
  console.log();
@@ -3325,7 +3327,7 @@ var CUSTOM_ACTION_NOTE = '\n\nThis is a custom action: ALSO include "connectionK
3325
3327
  function codeGuideAction(action, knowledge, platform, tags, apiBase) {
3326
3328
  const base = apiBase.replace(/\/$/, "");
3327
3329
  const method = action.method.toUpperCase();
3328
- const path15 = action.path.startsWith("/") ? action.path : `/${action.path}`;
3330
+ const path16 = action.path.startsWith("/") ? action.path : `/${action.path}`;
3329
3331
  const connEnv = `ONE_${platformEnvSegment(platform)}_CONNECTION_KEY`;
3330
3332
  const customNote = tags.includes("custom") ? CUSTOM_ACTION_NOTE : "";
3331
3333
  return `### ${actionHeading(action)}
@@ -3334,7 +3336,7 @@ function codeGuideAction(action, knowledge, platform, tags, apiBase) {
3334
3336
  ${knowledge ?? NO_KNOWLEDGE}
3335
3337
 
3336
3338
  ## Calling this action from code
3337
- URL: ${base}/passthrough${path15}
3339
+ URL: ${base}/passthrough${path16}
3338
3340
  Method: ${method}
3339
3341
  Headers:
3340
3342
  - x-one-secret: the value of the ONE_SECRET env var
@@ -3345,7 +3347,7 @@ Headers:
3345
3347
  Environment variables: ONE_SECRET (their One API key) and ${connEnv} (the key of their ${platform} connection), both from the One dashboard.
3346
3348
 
3347
3349
  \`\`\`typescript
3348
- const response = await fetch("${base}/passthrough${path15}", {
3350
+ const response = await fetch("${base}/passthrough${path16}", {
3349
3351
  method: "${method}",
3350
3352
  headers: {
3351
3353
  "x-one-secret": process.env.ONE_SECRET,
@@ -4095,32 +4097,32 @@ function validateStepsArray(steps, pathPrefix, errors) {
4095
4097
  const validTypes = FLOW_SCHEMA.stepTypes.map((st) => st.type);
4096
4098
  for (let i = 0; i < steps.length; i++) {
4097
4099
  const step = steps[i];
4098
- const path15 = `${pathPrefix}[${i}]`;
4100
+ const path16 = `${pathPrefix}[${i}]`;
4099
4101
  if (!step || typeof step !== "object" || Array.isArray(step)) {
4100
- errors.push({ path: path15, message: "Step must be an object" });
4102
+ errors.push({ path: path16, message: "Step must be an object" });
4101
4103
  continue;
4102
4104
  }
4103
4105
  const s = step;
4104
4106
  if (!s.id || typeof s.id !== "string") {
4105
- errors.push({ path: `${path15}.id`, message: 'Step must have a string "id"' });
4107
+ errors.push({ path: `${path16}.id`, message: 'Step must have a string "id"' });
4106
4108
  }
4107
4109
  if (!s.name || typeof s.name !== "string") {
4108
- errors.push({ path: `${path15}.name`, message: 'Step must have a string "name"' });
4110
+ errors.push({ path: `${path16}.name`, message: 'Step must have a string "name"' });
4109
4111
  }
4110
4112
  if (!s.type || !validTypes.includes(s.type)) {
4111
- errors.push({ path: `${path15}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
4113
+ errors.push({ path: `${path16}.type`, message: `Step type must be one of: ${validTypes.join(", ")}` });
4112
4114
  continue;
4113
4115
  }
4114
4116
  if (s.requires !== void 0) {
4115
4117
  if (!Array.isArray(s.requires)) {
4116
- errors.push({ path: `${path15}.requires`, message: '"requires" must be an array of selector strings (e.g. ["$.steps.foo.output.bar"])' });
4118
+ errors.push({ path: `${path16}.requires`, message: '"requires" must be an array of selector strings (e.g. ["$.steps.foo.output.bar"])' });
4117
4119
  } else {
4118
4120
  for (let r = 0; r < s.requires.length; r++) {
4119
4121
  const sel = s.requires[r];
4120
4122
  if (typeof sel !== "string") {
4121
- errors.push({ path: `${path15}.requires[${r}]`, message: '"requires" entry must be a selector string' });
4123
+ errors.push({ path: `${path16}.requires[${r}]`, message: '"requires" entry must be a selector string' });
4122
4124
  } else if (!sel.startsWith("$.")) {
4123
- errors.push({ path: `${path15}.requires[${r}]`, message: `"requires" entry "${sel}" must be a selector starting with "$." (e.g. "$.steps.foo.output.bar")` });
4125
+ errors.push({ path: `${path16}.requires[${r}]`, message: `"requires" entry "${sel}" must be a selector starting with "$." (e.g. "$.steps.foo.output.bar")` });
4124
4126
  }
4125
4127
  }
4126
4128
  }
@@ -4128,7 +4130,7 @@ function validateStepsArray(steps, pathPrefix, errors) {
4128
4130
  if (s.onError && typeof s.onError === "object") {
4129
4131
  const oe = s.onError;
4130
4132
  if (!FLOW_SCHEMA.errorStrategies.includes(oe.strategy)) {
4131
- errors.push({ path: `${path15}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
4133
+ errors.push({ path: `${path16}.onError.strategy`, message: `Error strategy must be one of: ${FLOW_SCHEMA.errorStrategies.join(", ")}` });
4132
4134
  }
4133
4135
  }
4134
4136
  const descriptor = getStepTypeDescriptor(s.type);
@@ -4138,14 +4140,14 @@ function validateStepsArray(steps, pathPrefix, errors) {
4138
4140
  if (!configObj || typeof configObj !== "object") {
4139
4141
  const hint = detectFlatConfigHint(s, descriptor);
4140
4142
  errors.push({
4141
- path: `${path15}.${configKey}`,
4143
+ path: `${path16}.${configKey}`,
4142
4144
  message: `${capitalize(descriptor.type)} step must have a "${configKey}" config object${hint}`
4143
4145
  });
4144
4146
  continue;
4145
4147
  }
4146
4148
  const config2 = configObj;
4147
4149
  for (const [fieldName, fd] of Object.entries(descriptor.fields)) {
4148
- const fieldPath = `${path15}.${configKey}.${fieldName}`;
4150
+ const fieldPath = `${path16}.${configKey}.${fieldName}`;
4149
4151
  const value = config2[fieldName];
4150
4152
  if (fd.required && (value === void 0 || value === null || value === "")) {
4151
4153
  errors.push({ path: fieldPath, message: `${capitalize(descriptor.type)} must have ${fd.type === "string" ? "a string" : fd.type === "array" ? "a" : "a"} "${fieldName}"` });
@@ -4178,30 +4180,30 @@ function validateStepsArray(steps, pathPrefix, errors) {
4178
4180
  }
4179
4181
  }
4180
4182
  if (descriptor.type === "action") {
4181
- validateConnectionForm(config2, `${path15}.${configKey}`, errors);
4183
+ validateConnectionForm(config2, `${path16}.${configKey}`, errors);
4182
4184
  }
4183
4185
  if (descriptor.type === "code") {
4184
4186
  const hasSource = typeof config2.source === "string" && config2.source.length > 0;
4185
4187
  const hasModule = typeof config2.module === "string" && config2.module.length > 0;
4186
4188
  if (!hasSource && !hasModule) {
4187
- errors.push({ path: `${path15}.${configKey}`, message: 'Code step must define either "source" (inline JS) or "module" (path to .mjs file)' });
4189
+ errors.push({ path: `${path16}.${configKey}`, message: 'Code step must define either "source" (inline JS) or "module" (path to .mjs file)' });
4188
4190
  } else if (hasSource && hasModule) {
4189
- errors.push({ path: `${path15}.${configKey}`, message: 'Code step cannot define both "source" and "module" \u2014 pick one' });
4191
+ errors.push({ path: `${path16}.${configKey}`, message: 'Code step cannot define both "source" and "module" \u2014 pick one' });
4190
4192
  }
4191
4193
  if (hasModule) {
4192
4194
  const m = config2.module;
4193
4195
  if (m.startsWith("/") || /^[a-zA-Z]:[\\/]/.test(m)) {
4194
- errors.push({ path: `${path15}.${configKey}.module`, message: "Code module path must be relative to the flow folder (no absolute paths)" });
4196
+ errors.push({ path: `${path16}.${configKey}.module`, message: "Code module path must be relative to the flow folder (no absolute paths)" });
4195
4197
  } else if (m.split(/[\\/]/).includes("..")) {
4196
- errors.push({ path: `${path15}.${configKey}.module`, message: 'Code module path must not escape the flow folder ("..")' });
4198
+ errors.push({ path: `${path16}.${configKey}.module`, message: 'Code module path must not escape the flow folder ("..")' });
4197
4199
  } else if (!m.endsWith(".mjs")) {
4198
- errors.push({ path: `${path15}.${configKey}.module`, message: "Code module must be a .mjs file" });
4200
+ errors.push({ path: `${path16}.${configKey}.module`, message: "Code module must be a .mjs file" });
4199
4201
  }
4200
4202
  }
4201
4203
  if (hasSource) {
4202
4204
  const syntaxError = checkCodeSourceSyntax(config2.source);
4203
4205
  if (syntaxError) {
4204
- errors.push({ path: `${path15}.${configKey}.source`, message: `Syntax error in code step: ${syntaxError}` });
4206
+ errors.push({ path: `${path16}.${configKey}.source`, message: `Syntax error in code step: ${syntaxError}` });
4205
4207
  }
4206
4208
  }
4207
4209
  }
@@ -4273,16 +4275,16 @@ function validateStepIds(flow2) {
4273
4275
  function collectIds(steps, pathPrefix) {
4274
4276
  for (let i = 0; i < steps.length; i++) {
4275
4277
  const step = steps[i];
4276
- const path15 = `${pathPrefix}[${i}]`;
4278
+ const path16 = `${pathPrefix}[${i}]`;
4277
4279
  if (seen.has(step.id)) {
4278
- errors.push({ path: `${path15}.id`, message: `Duplicate step ID: "${step.id}"` });
4280
+ errors.push({ path: `${path16}.id`, message: `Duplicate step ID: "${step.id}"` });
4279
4281
  } else {
4280
4282
  seen.add(step.id);
4281
4283
  }
4282
4284
  for (const { configKey, fieldName } of nestedKeys) {
4283
4285
  const config2 = step[configKey];
4284
4286
  if (config2 && Array.isArray(config2[fieldName])) {
4285
- collectIds(config2[fieldName], `${path15}.${configKey}.${fieldName}`);
4287
+ collectIds(config2[fieldName], `${path16}.${configKey}.${fieldName}`);
4286
4288
  }
4287
4289
  }
4288
4290
  }
@@ -4330,7 +4332,7 @@ function validateSelectorReferences(flow2, rootDir) {
4330
4332
  }
4331
4333
  return selectors;
4332
4334
  }
4333
- function checkSelectors(selectors, path15, precedingStepIds) {
4335
+ function checkSelectors(selectors, path16, precedingStepIds) {
4334
4336
  for (const selector of selectors) {
4335
4337
  const parts = selector.split(".");
4336
4338
  if (parts.length < 3) continue;
@@ -4338,15 +4340,15 @@ function validateSelectorReferences(flow2, rootDir) {
4338
4340
  if (root === "input") {
4339
4341
  const inputName = parts[2];
4340
4342
  if (!inputNames.has(inputName)) {
4341
- errors.push({ path: path15, message: `Selector "${selector}" references undefined input "${inputName}"` });
4343
+ errors.push({ path: path16, message: `Selector "${selector}" references undefined input "${inputName}"` });
4342
4344
  }
4343
4345
  } else if (root === "steps") {
4344
4346
  const stepId = parts[2].replace(/[\[\]]/g, "").split(/[\[\]]/)[0];
4345
4347
  if (!allStepIds.has(stepId)) {
4346
- errors.push({ path: path15, message: `Selector "${selector}" references undefined step "${stepId}"` });
4348
+ errors.push({ path: path16, message: `Selector "${selector}" references undefined step "${stepId}"` });
4347
4349
  } else if (precedingStepIds && !precedingStepIds.has(stepId)) {
4348
4350
  errors.push({
4349
- path: path15,
4351
+ path: path16,
4350
4352
  message: `Selector "${selector}" references step "${stepId}" which is declared after the current step. Steps execute in declaration order, so this will always resolve to undefined at runtime \u2014 move the dependency earlier in the steps array.`
4351
4353
  });
4352
4354
  }
@@ -4354,20 +4356,20 @@ function validateSelectorReferences(flow2, rootDir) {
4354
4356
  }
4355
4357
  }
4356
4358
  const EXPRESSION_FIELDS = /* @__PURE__ */ new Set(["condition.expression", "while.condition"]);
4357
- function checkOperatorsInSelectorField(value, path15) {
4359
+ function checkOperatorsInSelectorField(value, path16) {
4358
4360
  if (typeof value === "string" && value.startsWith("$.")) {
4359
4361
  if (value.includes("||")) {
4360
- errors.push({ path: path15, message: `Selector "${value}" contains unsupported operator "||". Selectors in data fields use dot-path resolution, not JS evaluation. Use the "default" field on the input definition instead, or use a "code" step for complex expressions.` });
4362
+ errors.push({ path: path16, message: `Selector "${value}" contains unsupported operator "||". Selectors in data fields use dot-path resolution, not JS evaluation. Use the "default" field on the input definition instead, or use a "code" step for complex expressions.` });
4361
4363
  } else if (value.includes("&&")) {
4362
- errors.push({ path: path15, message: `Selector "${value}" contains unsupported operator "&&". Selectors in data fields use dot-path resolution, not JS evaluation. Use a "condition" step or "code" step for complex expressions.` });
4364
+ errors.push({ path: path16, message: `Selector "${value}" contains unsupported operator "&&". Selectors in data fields use dot-path resolution, not JS evaluation. Use a "condition" step or "code" step for complex expressions.` });
4363
4365
  }
4364
4366
  } else if (value && typeof value === "object" && !Array.isArray(value)) {
4365
4367
  for (const [k, v] of Object.entries(value)) {
4366
- checkOperatorsInSelectorField(v, `${path15}.${k}`);
4368
+ checkOperatorsInSelectorField(v, `${path16}.${k}`);
4367
4369
  }
4368
4370
  } else if (Array.isArray(value)) {
4369
4371
  for (let i = 0; i < value.length; i++) {
4370
- checkOperatorsInSelectorField(value[i], `${path15}[${i}]`);
4372
+ checkOperatorsInSelectorField(value[i], `${path16}[${i}]`);
4371
4373
  }
4372
4374
  }
4373
4375
  }
@@ -4452,10 +4454,10 @@ var VALID_OUTPUT_SCHEMA_TYPES = /* @__PURE__ */ new Set(["string", "number", "bo
4452
4454
  function isOutputSchemaObject(v) {
4453
4455
  return !!v && typeof v === "object" && !Array.isArray(v);
4454
4456
  }
4455
- function walkOutputSchema(schema, path15) {
4457
+ function walkOutputSchema(schema, path16) {
4456
4458
  let current = schema;
4457
- for (let i = 0; i < path15.length; i++) {
4458
- const seg = path15[i];
4459
+ for (let i = 0; i < path16.length; i++) {
4460
+ const seg = path16[i];
4459
4461
  if (typeof current === "string") {
4460
4462
  return current === "unknown" || current === "object" || current === "array" ? "opaque" : "opaque";
4461
4463
  }
@@ -6725,8 +6727,32 @@ import path8 from "path";
6725
6727
  var LOCK_DIR_REL = path8.join(".one", "sync", "locks");
6726
6728
  var STALE_MS = 30 * 60 * 1e3;
6727
6729
  function lockPath(platform, model) {
6730
+ try {
6731
+ assertSafeSyncNames(platform, model);
6732
+ } catch (err) {
6733
+ throw new SyncLockError(err instanceof Error ? err.message : String(err));
6734
+ }
6728
6735
  return path8.join(LOCK_DIR_REL, `${platform}_${model}`);
6729
6736
  }
6737
+ var PID_FILE = "pid";
6738
+ function looksLikeOurLock(dir) {
6739
+ try {
6740
+ if (!fs8.lstatSync(dir).isDirectory()) return false;
6741
+ const entries = fs8.readdirSync(dir);
6742
+ if (!entries.every((e) => e === PID_FILE)) return false;
6743
+ if (entries.length === 1 && !fs8.lstatSync(path8.join(dir, PID_FILE)).isFile()) return false;
6744
+ return true;
6745
+ } catch {
6746
+ return false;
6747
+ }
6748
+ }
6749
+ function removeLockDir(dir) {
6750
+ try {
6751
+ fs8.unlinkSync(path8.join(dir, PID_FILE));
6752
+ } catch {
6753
+ }
6754
+ fs8.rmdirSync(dir);
6755
+ }
6730
6756
  function isProcessAlive(pid) {
6731
6757
  try {
6732
6758
  process.kill(pid, 0);
@@ -6742,15 +6768,19 @@ var SyncLockError = class extends Error {
6742
6768
  }
6743
6769
  };
6744
6770
  function acquireSyncLock(platform, model) {
6745
- fs8.mkdirSync(LOCK_DIR_REL, { recursive: true });
6746
6771
  const dir = lockPath(platform, model);
6747
- const pidFile = path8.join(dir, "pid");
6772
+ try {
6773
+ ensureDirInsideProject(LOCK_DIR_REL);
6774
+ } catch (err) {
6775
+ throw new SyncLockError(err instanceof Error ? err.message : String(err));
6776
+ }
6777
+ const pidFile = path8.join(dir, PID_FILE);
6748
6778
  try {
6749
6779
  fs8.mkdirSync(dir);
6750
6780
  } catch (err) {
6751
6781
  const stat = (() => {
6752
6782
  try {
6753
- return fs8.statSync(dir);
6783
+ return fs8.lstatSync(dir);
6754
6784
  } catch {
6755
6785
  return null;
6756
6786
  }
@@ -6767,8 +6797,13 @@ function acquireSyncLock(platform, model) {
6767
6797
  const ownerDead = ownerPid !== null && !isProcessAlive(ownerPid);
6768
6798
  const veryOld = age > STALE_MS;
6769
6799
  if (ownerDead || veryOld) {
6800
+ if (!looksLikeOurLock(dir)) {
6801
+ throw new SyncLockError(
6802
+ `${dir} exists but is not a sync lock. Remove it manually if no sync is running.`
6803
+ );
6804
+ }
6770
6805
  try {
6771
- fs8.rmSync(dir, { recursive: true, force: true });
6806
+ removeLockDir(dir);
6772
6807
  fs8.mkdirSync(dir);
6773
6808
  } catch {
6774
6809
  throw new SyncLockError(
@@ -6792,7 +6827,7 @@ function acquireSyncLock(platform, model) {
6792
6827
  return {
6793
6828
  release() {
6794
6829
  try {
6795
- fs8.rmSync(dir, { recursive: true, force: true });
6830
+ if (looksLikeOurLock(dir)) removeLockDir(dir);
6796
6831
  } catch {
6797
6832
  }
6798
6833
  }
@@ -6849,10 +6884,24 @@ async function fireHooks(hookCommand, events) {
6849
6884
  function appendEventLog(events) {
6850
6885
  if (events.length === 0) return;
6851
6886
  const { platform, model } = events[0];
6852
- fs9.mkdirSync(EVENTS_DIR, { recursive: true });
6887
+ assertSafeSyncNames(platform, model);
6888
+ ensureDirInsideProject(EVENTS_DIR);
6853
6889
  const logPath = path9.join(EVENTS_DIR, `${platform}_${model}.jsonl`);
6854
6890
  const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
6855
- fs9.appendFileSync(logPath, lines);
6891
+ try {
6892
+ if (fs9.lstatSync(logPath).isSymbolicLink()) {
6893
+ throw new Error(`Refusing to append to ${logPath}: it is a symlink.`);
6894
+ }
6895
+ } catch (err) {
6896
+ if (err.code !== "ENOENT") throw err;
6897
+ }
6898
+ const noFollow = fs9.constants.O_NOFOLLOW ?? 0;
6899
+ const fd = fs9.openSync(logPath, fs9.constants.O_WRONLY | fs9.constants.O_APPEND | fs9.constants.O_CREAT | noFollow, 420);
6900
+ try {
6901
+ fs9.writeSync(fd, lines);
6902
+ } finally {
6903
+ fs9.closeSync(fd);
6904
+ }
6856
6905
  }
6857
6906
  function runShellHook(command, events) {
6858
6907
  return new Promise((resolve) => {
@@ -6956,8 +7005,8 @@ function sleep(ms) {
6956
7005
  return new Promise((resolve) => setTimeout(resolve, ms));
6957
7006
  }
6958
7007
  function interpolate(template, record) {
6959
- return template.replace(/\{?\{(\w+(?:\.\w+)*)\}\}?/g, (_, path15) => {
6960
- const parts = path15.split(".");
7008
+ return template.replace(/\{?\{(\w+(?:\.\w+)*)\}\}?/g, (_, path16) => {
7009
+ const parts = path16.split(".");
6961
7010
  let value = record;
6962
7011
  for (const part2 of parts) {
6963
7012
  if (typeof value !== "object" || value === null) return "";
@@ -6985,8 +7034,8 @@ function deepMerge(target, source) {
6985
7034
  }
6986
7035
  return result;
6987
7036
  }
6988
- function getByDotPath2(obj, path15) {
6989
- const parts = path15.split(".");
7037
+ function getByDotPath2(obj, path16) {
7038
+ const parts = path16.split(".");
6990
7039
  let current = obj;
6991
7040
  for (const part2 of parts) {
6992
7041
  if (current === null || current === void 0 || typeof current !== "object") return void 0;
@@ -6995,8 +7044,8 @@ function getByDotPath2(obj, path15) {
6995
7044
  return current;
6996
7045
  }
6997
7046
  function stripExcludedFields(obj, paths) {
6998
- for (const path15 of paths) {
6999
- stripOnePath(obj, path15.replace(/\[\]/g, ".*").split("."));
7047
+ for (const path16 of paths) {
7048
+ stripOnePath(obj, path16.replace(/\[\]/g, ".*").split("."));
7000
7049
  }
7001
7050
  }
7002
7051
  function stripOnePath(obj, parts) {
@@ -7353,6 +7402,88 @@ function detectColumnType(value) {
7353
7402
  return "TEXT";
7354
7403
  }
7355
7404
 
7405
+ // src/lib/memory/sync/approval.ts
7406
+ import crypto3 from "crypto";
7407
+ import fs10 from "fs";
7408
+ import path10 from "path";
7409
+ var SHELL_FIELDS = ["transform", "onInsert", "onUpdate", "onChange"];
7410
+ var STORE_FILE = () => path10.join(homeDir(), ".one", "sync", "shell-approvals.json");
7411
+ function shellCommandsOf(profile) {
7412
+ const out = {};
7413
+ for (const field of SHELL_FIELDS) {
7414
+ const value = profile[field];
7415
+ if (typeof value !== "string" || value.trim() === "") continue;
7416
+ if (field !== "transform" && value === "log") continue;
7417
+ out[field] = value;
7418
+ }
7419
+ return out;
7420
+ }
7421
+ function hashCommands(commands) {
7422
+ const ordered = SHELL_FIELDS.map((f) => [f, commands[f] ?? null]);
7423
+ return crypto3.createHash("sha256").update(JSON.stringify(ordered)).digest("hex");
7424
+ }
7425
+ function projectRoot() {
7426
+ try {
7427
+ return fs10.realpathSync(process.cwd());
7428
+ } catch {
7429
+ return path10.resolve(process.cwd());
7430
+ }
7431
+ }
7432
+ function keyFor(platform, model) {
7433
+ return `${projectRoot()}::${platform}/${model}`;
7434
+ }
7435
+ function readStore() {
7436
+ try {
7437
+ const parsed = JSON.parse(fs10.readFileSync(STORE_FILE(), "utf-8"));
7438
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
7439
+ } catch {
7440
+ return {};
7441
+ }
7442
+ }
7443
+ function writeStore(store) {
7444
+ const file = STORE_FILE();
7445
+ fs10.mkdirSync(path10.dirname(file), { recursive: true, mode: 448 });
7446
+ const tmp = `${file}.${process.pid}.tmp`;
7447
+ fs10.writeFileSync(tmp, JSON.stringify(store, null, 2), { mode: 384 });
7448
+ fs10.renameSync(tmp, file);
7449
+ }
7450
+ function isShellApproved(profile) {
7451
+ const commands = shellCommandsOf(profile);
7452
+ if (Object.keys(commands).length === 0) return true;
7453
+ const entry = readStore()[keyFor(profile.platform, profile.model)];
7454
+ return !!entry && entry.hash === hashCommands(commands);
7455
+ }
7456
+ function approveShellCommands(profile) {
7457
+ const store = readStore();
7458
+ const key = keyFor(profile.platform, profile.model);
7459
+ const commands = shellCommandsOf(profile);
7460
+ if (Object.keys(commands).length === 0) {
7461
+ if (!(key in store)) return;
7462
+ delete store[key];
7463
+ } else {
7464
+ store[key] = { hash: hashCommands(commands), commands, approvedAt: (/* @__PURE__ */ new Date()).toISOString() };
7465
+ }
7466
+ writeStore(store);
7467
+ }
7468
+ var SyncShellNotApprovedError = class extends Error {
7469
+ constructor(profile, commands) {
7470
+ const list = Object.entries(commands).map(([f, c]) => ` ${f}: ${c}`).join("\n");
7471
+ super(
7472
+ `Sync profile ${profile.platform}/${profile.model} runs shell commands that you haven't approved (or that changed since you approved them):
7473
+ ${list}
7474
+ Review them, then run: one sync approve ${profile.platform} --models ${profile.model}`
7475
+ );
7476
+ this.profile = profile;
7477
+ this.commands = commands;
7478
+ this.name = "SyncShellNotApprovedError";
7479
+ }
7480
+ };
7481
+ function assertShellApproved(profile) {
7482
+ if (!isShellApproved(profile)) {
7483
+ throw new SyncShellNotApprovedError(profile, shellCommandsOf(profile));
7484
+ }
7485
+ }
7486
+
7356
7487
  // src/lib/memory/sync/extract.ts
7357
7488
  var ROOT_PATH_TOKENS = /* @__PURE__ */ new Set(["", "$", "."]);
7358
7489
  function isRootPath(resultsPath) {
@@ -7417,8 +7548,8 @@ function sleep2(ms) {
7417
7548
  return new Promise((resolve) => setTimeout(resolve, ms));
7418
7549
  }
7419
7550
  function stripFields(record, paths) {
7420
- for (const path15 of paths) {
7421
- stripOnePath2(record, path15.split("."));
7551
+ for (const path16 of paths) {
7552
+ stripOnePath2(record, path16.split("."));
7422
7553
  }
7423
7554
  }
7424
7555
  function stripOnePath2(obj, parts) {
@@ -7479,6 +7610,7 @@ async function syncModel(api, profile, options) {
7479
7610
  "--full-refresh and --since cannot be used together. --full-refresh always fetches the whole collection."
7480
7611
  );
7481
7612
  }
7613
+ if (!options.dryRun) assertShellApproved(profile);
7482
7614
  const connectionKey = await resolveProfileConnectionKey(api, profile);
7483
7615
  const lock = options.dryRun ? null : acquireSyncLock(platform, model);
7484
7616
  const existingState = await getModelState(platform, model);
@@ -8511,19 +8643,19 @@ function inferProfileFromKnowledge(knowledge, modelName, platform) {
8511
8643
 
8512
8644
  // src/lib/memory/sync/schedule.ts
8513
8645
  import { spawnSync as spawnSync2 } from "child_process";
8514
- import fs11 from "fs";
8646
+ import fs12 from "fs";
8515
8647
  import os from "os";
8516
- import path11 from "path";
8648
+ import path12 from "path";
8517
8649
 
8518
8650
  // src/lib/memory/sync/schedule-registry.ts
8519
- import fs10 from "fs";
8520
- import path10 from "path";
8521
- var REGISTRY_DIR = () => path10.join(homeDir(), ".one", "sync");
8522
- var REGISTRY_FILE = () => path10.join(REGISTRY_DIR(), "schedules.json");
8651
+ import fs11 from "fs";
8652
+ import path11 from "path";
8653
+ var REGISTRY_DIR = () => path11.join(homeDir(), ".one", "sync");
8654
+ var REGISTRY_FILE = () => path11.join(REGISTRY_DIR(), "schedules.json");
8523
8655
  function readRaw() {
8524
8656
  try {
8525
- if (!fs10.existsSync(REGISTRY_FILE())) return { schedules: [] };
8526
- const raw = fs10.readFileSync(REGISTRY_FILE(), "utf-8");
8657
+ if (!fs11.existsSync(REGISTRY_FILE())) return { schedules: [] };
8658
+ const raw = fs11.readFileSync(REGISTRY_FILE(), "utf-8");
8527
8659
  const parsed = JSON.parse(raw);
8528
8660
  if (!parsed || !Array.isArray(parsed.schedules)) return { schedules: [] };
8529
8661
  return parsed;
@@ -8532,13 +8664,13 @@ function readRaw() {
8532
8664
  }
8533
8665
  }
8534
8666
  function writeRaw(file) {
8535
- fs10.mkdirSync(REGISTRY_DIR(), { recursive: true });
8667
+ fs11.mkdirSync(REGISTRY_DIR(), { recursive: true });
8536
8668
  const tmp = REGISTRY_FILE() + ".tmp";
8537
- fs10.writeFileSync(tmp, JSON.stringify(file, null, 2));
8538
- fs10.renameSync(tmp, REGISTRY_FILE());
8669
+ fs11.writeFileSync(tmp, JSON.stringify(file, null, 2));
8670
+ fs11.renameSync(tmp, REGISTRY_FILE());
8539
8671
  }
8540
8672
  function makeScheduleId(platform, cwd) {
8541
- const slug = path10.basename(cwd).replace(/[^a-zA-Z0-9_-]/g, "-").toLowerCase();
8673
+ const slug = path11.basename(cwd).replace(/[^a-zA-Z0-9_-]/g, "-").toLowerCase();
8542
8674
  return `${platform}-${slug}`;
8543
8675
  }
8544
8676
  function listRegistered() {
@@ -8572,7 +8704,7 @@ function removeRegistered(id) {
8572
8704
 
8573
8705
  // src/lib/memory/sync/schedule.ts
8574
8706
  var MARKER = "# one-sync";
8575
- var LOG_DIR_REL = path11.join(".one", "sync", "logs");
8707
+ var LOG_DIR_REL = path12.join(".one", "sync", "logs");
8576
8708
  function durationToCron(every) {
8577
8709
  const match = every.match(/^(\d+)([mhd])$/);
8578
8710
  if (!match) return null;
@@ -8610,8 +8742,8 @@ function isWindows() {
8610
8742
  function resolveOneBinary() {
8611
8743
  try {
8612
8744
  const entry = process.argv[1];
8613
- if (entry && fs11.existsSync(entry)) {
8614
- return fs11.realpathSync(entry);
8745
+ if (entry && fs12.existsSync(entry)) {
8746
+ return fs12.realpathSync(entry);
8615
8747
  }
8616
8748
  } catch {
8617
8749
  }
@@ -8689,7 +8821,7 @@ function migrateLegacyCronEntries() {
8689
8821
  const modelsMatch = command.match(/--models\s+(\S+)/);
8690
8822
  const models = modelsMatch ? modelsMatch[1].split(",") : void 0;
8691
8823
  const logMatch = command.match(/>>\s+"([^"]+)"/);
8692
- const logFile = logMatch ? logMatch[1] : path11.resolve(cwd, LOG_DIR_REL, `${platform}.log`);
8824
+ const logFile = logMatch ? logMatch[1] : path12.resolve(cwd, LOG_DIR_REL, `${platform}.log`);
8693
8825
  const id = makeScheduleId(platform, cwd);
8694
8826
  if (registeredIds.has(id)) continue;
8695
8827
  upsertRegistered({
@@ -8723,9 +8855,9 @@ function addSchedule(opts) {
8723
8855
  const cwd = process.cwd();
8724
8856
  const id = makeScheduleId(opts.platform, cwd);
8725
8857
  const replaced = getRegistered(id) !== void 0;
8726
- const logDir = path11.join(cwd, LOG_DIR_REL);
8727
- fs11.mkdirSync(logDir, { recursive: true });
8728
- const logFile = path11.join(logDir, `${opts.platform}.log`);
8858
+ const logDir = path12.join(cwd, LOG_DIR_REL);
8859
+ fs12.mkdirSync(logDir, { recursive: true });
8860
+ const logFile = path12.join(logDir, `${opts.platform}.log`);
8729
8861
  const entry = {
8730
8862
  id,
8731
8863
  platform: opts.platform,
@@ -8779,15 +8911,15 @@ function removeSchedule(idOrPlatform, options) {
8779
8911
  function scheduleStatus() {
8780
8912
  const entries = listSchedules();
8781
8913
  return entries.map((entry) => {
8782
- const logExists = fs11.existsSync(entry.logFile);
8783
- const logSize = logExists ? fs11.statSync(entry.logFile).size : 0;
8914
+ const logExists = fs12.existsSync(entry.logFile);
8915
+ const logSize = logExists ? fs12.statSync(entry.logFile).size : 0;
8784
8916
  let logTail = [];
8785
8917
  let lastRunAt = null;
8786
8918
  if (logExists) {
8787
8919
  try {
8788
- lastRunAt = fs11.statSync(entry.logFile).mtime.toISOString();
8920
+ lastRunAt = fs12.statSync(entry.logFile).mtime.toISOString();
8789
8921
  if (logSize > 0) {
8790
- const content = fs11.readFileSync(entry.logFile, "utf-8");
8922
+ const content = fs12.readFileSync(entry.logFile, "utf-8");
8791
8923
  logTail = content.trim().split("\n").slice(-10);
8792
8924
  }
8793
8925
  } catch {
@@ -8795,8 +8927,8 @@ function scheduleStatus() {
8795
8927
  }
8796
8928
  let drift = "ok";
8797
8929
  if (!entry.cronInstalled) drift = "missing-cron";
8798
- else if (!fs11.existsSync(entry.nodeBin)) drift = "stale-node-bin";
8799
- else if (!fs11.existsSync(entry.cliBin)) drift = "stale-cli-bin";
8930
+ else if (!fs12.existsSync(entry.nodeBin)) drift = "stale-node-bin";
8931
+ else if (!fs12.existsSync(entry.cliBin)) drift = "stale-cli-bin";
8800
8932
  return { entry, logExists, logSize, logTail, lastRunAt, drift };
8801
8933
  });
8802
8934
  }
@@ -9069,11 +9201,11 @@ function isNoise(s) {
9069
9201
  if (/^-?\d+(\.\d+)?([eE][-+]?\d+)?$/.test(s)) return true;
9070
9202
  return false;
9071
9203
  }
9072
- function recordSample(stats, path15, value, jsType, recordIndex, totalSamples) {
9073
- let s = stats.get(path15);
9204
+ function recordSample(stats, path16, value, jsType, recordIndex, totalSamples) {
9205
+ let s = stats.get(path16);
9074
9206
  if (!s) {
9075
9207
  s = {
9076
- path: path15,
9208
+ path: path16,
9077
9209
  total: totalSamples,
9078
9210
  recordIndices: /* @__PURE__ */ new Set(),
9079
9211
  lenSum: 0,
@@ -9082,7 +9214,7 @@ function recordSample(stats, path15, value, jsType, recordIndex, totalSamples) {
9082
9214
  primaryType: jsType,
9083
9215
  examples: []
9084
9216
  };
9085
- stats.set(path15, s);
9217
+ stats.set(path16, s);
9086
9218
  }
9087
9219
  s.recordIndices.add(recordIndex);
9088
9220
  s.lenSum += value.length;
@@ -9091,28 +9223,28 @@ function recordSample(stats, path15, value, jsType, recordIndex, totalSamples) {
9091
9223
  if (s.primaryType !== jsType) s.primaryType = "mixed";
9092
9224
  if (s.examples.length < 3 && value.length < 200) s.examples.push(value);
9093
9225
  }
9094
- function walkRecord(record, path15, stats, recordIndex, totalSamples) {
9226
+ function walkRecord(record, path16, stats, recordIndex, totalSamples) {
9095
9227
  if (record === null || record === void 0) return;
9096
9228
  if (typeof record === "string") {
9097
9229
  const trimmed = record.trim();
9098
- if (trimmed) recordSample(stats, path15, trimmed, "string", recordIndex, totalSamples);
9230
+ if (trimmed) recordSample(stats, path16, trimmed, "string", recordIndex, totalSamples);
9099
9231
  return;
9100
9232
  }
9101
9233
  if (typeof record === "number" || typeof record === "boolean") {
9102
9234
  const str = String(record);
9103
9235
  const kind = typeof record === "number" ? "number" : "boolean";
9104
- if (str) recordSample(stats, path15, str, kind, recordIndex, totalSamples);
9236
+ if (str) recordSample(stats, path16, str, kind, recordIndex, totalSamples);
9105
9237
  return;
9106
9238
  }
9107
9239
  if (Array.isArray(record)) {
9108
- const childPath = path15 ? `${path15}[]` : "[]";
9240
+ const childPath = path16 ? `${path16}[]` : "[]";
9109
9241
  for (const item of record) walkRecord(item, childPath, stats, recordIndex, totalSamples);
9110
9242
  return;
9111
9243
  }
9112
9244
  if (typeof record === "object") {
9113
9245
  for (const [key, value] of Object.entries(record)) {
9114
9246
  if (key.startsWith("_")) continue;
9115
- const childPath = path15 ? `${path15}.${key}` : key;
9247
+ const childPath = path16 ? `${path16}.${key}` : key;
9116
9248
  walkRecord(value, childPath, stats, recordIndex, totalSamples);
9117
9249
  }
9118
9250
  }
@@ -9443,17 +9575,85 @@ Run with --config to save:
9443
9575
  } else if (patch.connectionKey && !patch.connection) {
9444
9576
  delete profile.connection;
9445
9577
  }
9578
+ const existingApproved = existing ? isShellApproved(existing) : false;
9579
+ const mergedCommands = shellCommandsOf(profile);
9580
+ const userSupplied = SHELL_FIELDS.every((f) => {
9581
+ const value = mergedCommands[f];
9582
+ if (value === void 0) return true;
9583
+ if (patch[f] === value) return true;
9584
+ return existingApproved && existing[f] === value;
9585
+ });
9446
9586
  try {
9447
9587
  writeProfile(profile);
9588
+ const needsApproval = Object.keys(mergedCommands).length > 0 && !userSupplied;
9589
+ if (!needsApproval) approveShellCommands(profile);
9448
9590
  if (isAgentMode()) {
9449
- json({ status: "created", platform, model, profile });
9591
+ json({
9592
+ status: "created",
9593
+ platform,
9594
+ model,
9595
+ profile,
9596
+ ...needsApproval ? { shellApproval: "required", _hint: `Run: one sync approve ${platform} --models ${model}` } : {}
9597
+ });
9450
9598
  } else {
9451
9599
  outro(`Sync profile saved for ${platform}/${model}`);
9600
+ if (needsApproval) {
9601
+ console.log(
9602
+ ` ${pc11.yellow("!")} This profile runs shell commands you didn't supply here. Review them, then run ${pc11.bold(`one sync approve ${platform} --models ${model}`)}.`
9603
+ );
9604
+ }
9452
9605
  }
9453
9606
  } catch (err) {
9454
9607
  error(`Error saving profile: ${err instanceof Error ? err.message : String(err)}`);
9455
9608
  }
9456
9609
  }
9610
+ async function syncApproveCommand(platform, options) {
9611
+ const models = options.models?.split(",").map((m) => m.trim()).filter(Boolean);
9612
+ const profiles = listProfiles(platform).filter((pr) => !models || models.includes(pr.model));
9613
+ if (profiles.length === 0) {
9614
+ error(`No sync profiles found for ${platform}${models ? ` with models: ${models.join(", ")}` : ""}.`);
9615
+ }
9616
+ const pending = profiles.filter((pr) => !isShellApproved(pr));
9617
+ if (pending.length === 0) {
9618
+ if (isAgentMode()) {
9619
+ json({ status: "approved", platform, approved: [], alreadyApproved: profiles.map((pr) => pr.model) });
9620
+ } else {
9621
+ outro(`Nothing to approve: ${platform} profiles run no unapproved shell commands.`);
9622
+ }
9623
+ return;
9624
+ }
9625
+ if (!isAgentMode()) {
9626
+ for (const pr of pending) {
9627
+ const lines = Object.entries(shellCommandsOf(pr)).map(([f, c]) => `${f}: ${c}`).join("\n");
9628
+ note(lines, `${pr.platform}/${pr.model} runs these with sh -c`);
9629
+ }
9630
+ }
9631
+ if (!options.yes) {
9632
+ if (isAgentMode()) {
9633
+ json({
9634
+ status: "confirmation_required",
9635
+ platform,
9636
+ pending: pending.map((pr) => ({ model: pr.model, commands: shellCommandsOf(pr) })),
9637
+ _hint: "Show these commands to the user. Re-run with --yes only after they approve them."
9638
+ });
9639
+ return;
9640
+ }
9641
+ const confirmed = await p6.confirm({
9642
+ message: `Allow sync to run ${pending.length === 1 ? "this command set" : "these command sets"} in ${process.cwd()}?`,
9643
+ initialValue: false
9644
+ });
9645
+ if (p6.isCancel(confirmed) || !confirmed) {
9646
+ cancel("Not approved.");
9647
+ return;
9648
+ }
9649
+ }
9650
+ for (const pr of pending) approveShellCommands(pr);
9651
+ if (isAgentMode()) {
9652
+ json({ status: "approved", platform, approved: pending.map((pr) => pr.model) });
9653
+ } else {
9654
+ outro(`Approved shell commands for ${pending.map((pr) => `${platform}/${pr.model}`).join(", ")}. If they change, sync will ask again.`);
9655
+ }
9656
+ }
9457
9657
  async function syncTestCommand(platformModel, options = {}) {
9458
9658
  const [platform, model] = platformModel.split("/");
9459
9659
  if (!platform || !model) {
@@ -9547,7 +9747,7 @@ function buildSearchablePreview(profile, samples) {
9547
9747
  const first = samples[0];
9548
9748
  const paths = getSearchablePaths(profile);
9549
9749
  if (paths) {
9550
- const perPathAgg = paths.map((path15) => ({ path: path15, hits: 0, total: samples.length, sample: "" }));
9750
+ const perPathAgg = paths.map((path16) => ({ path: path16, hits: 0, total: samples.length, sample: "" }));
9551
9751
  for (const record of samples) {
9552
9752
  const { paths: perPath } = extractSearchableFromPaths(record, paths);
9553
9753
  perPath.forEach((p9, i) => {
@@ -9791,7 +9991,7 @@ async function maybeAutoMigrateLegacy(platform, models) {
9791
9991
  ` detected legacy .one/sync/data/${platform}.db (${dbSize}) \u2014 auto-migrating into memory before sync.
9792
9992
  `
9793
9993
  );
9794
- const { memMigrateCommand: memMigrateCommand3 } = await import("./migrate-RJ7F2LPT.js");
9994
+ const { memMigrateCommand: memMigrateCommand3 } = await import("./migrate-E5QDJRM4.js");
9795
9995
  await memMigrateCommand3({ platform, yes: true });
9796
9996
  return;
9797
9997
  }
@@ -9800,7 +10000,7 @@ async function maybeAutoMigrateLegacy(platform, models) {
9800
10000
  initialValue: true
9801
10001
  });
9802
10002
  if (p6.isCancel(shouldMigrate) || !shouldMigrate) return;
9803
- const { memMigrateCommand: memMigrateCommand2 } = await import("./migrate-RJ7F2LPT.js");
10003
+ const { memMigrateCommand: memMigrateCommand2 } = await import("./migrate-E5QDJRM4.js");
9804
10004
  await memMigrateCommand2({ platform, yes: true });
9805
10005
  }
9806
10006
  async function syncSuggestSearchableCommand(platformModel, options = {}) {
@@ -9966,8 +10166,14 @@ async function syncScheduleAddCommand(platform, options) {
9966
10166
  every: options.every,
9967
10167
  models: options.models?.split(",").map((m) => m.trim())
9968
10168
  });
10169
+ const scheduledModels = entry.models;
10170
+ const unapproved = listProfiles(platform).filter((pr) => !scheduledModels || scheduledModels.includes(pr.model)).filter((pr) => !isShellApproved(pr)).map((pr) => pr.model);
9969
10171
  if (isAgentMode()) {
9970
- json({ status: replaced ? "replaced" : "scheduled", ...entry });
10172
+ json({
10173
+ status: replaced ? "replaced" : "scheduled",
10174
+ ...entry,
10175
+ ...unapproved.length > 0 ? { unapprovedShellCommands: unapproved, _hint: `These models will fail until approved: one sync approve ${platform} --models ${unapproved.join(",")}` } : {}
10176
+ });
9971
10177
  return;
9972
10178
  }
9973
10179
  const verb = replaced ? "Replaced existing schedule" : "Scheduled sync";
@@ -9975,6 +10181,11 @@ async function syncScheduleAddCommand(platform, options) {
9975
10181
  `${verb} ${pc11.bold(entry.id)} \u2014 every ${pc11.bold(entry.every)} (cron: ${pc11.dim(entry.cronExpr)})
9976
10182
  Logs: ${pc11.dim(entry.logFile)}`
9977
10183
  );
10184
+ if (unapproved.length > 0) {
10185
+ console.log(
10186
+ ` ${pc11.yellow("!")} ${unapproved.map((m) => `${platform}/${m}`).join(", ")} run shell commands that aren't approved, so scheduled runs will skip ${unapproved.length === 1 ? "it" : "them"}. Run ${pc11.bold(`one sync approve ${platform} --models ${unapproved.join(",")}`)}.`
10187
+ );
10188
+ }
9978
10189
  } catch (err) {
9979
10190
  error(err instanceof Error ? err.message : String(err));
9980
10191
  }
@@ -10143,6 +10354,9 @@ function registerSyncSubcommands(sync) {
10143
10354
  sync.command("delete <platform/model>").description('Delete records from local sync data (e.g. one sync delete notion/pages --id "abc-123")').option("--id <value>", "Delete record by ID").option("--where <conditions>", 'Delete records matching conditions (e.g. "status=archived")').option("--where-sql <predicate>", `Delete using a raw SQL WHERE clause (e.g. "json_extract(data, '$.type') = 'promotion'")`).option("--yes", "Skip confirmation prompt").action(async (platformModel, options) => {
10144
10355
  await syncDeleteCommand(platformModel, options);
10145
10356
  });
10357
+ sync.command("approve <platform>").description("Approve the shell commands (transform, onInsert, onUpdate, onChange) in this project's sync profiles").option("--models <models>", "Only these models (comma-separated)").option("--yes", "Approve without the confirmation prompt").action(async (platform, options) => {
10358
+ await syncApproveCommand(platform, options);
10359
+ });
10146
10360
  sync.command("list [platform]").alias("ls").description("List all configured sync profiles and their status").action(async (platform) => {
10147
10361
  await syncListCommand(platform);
10148
10362
  });
@@ -10504,16 +10718,16 @@ function projectEmbeddingApiKey(cfg) {
10504
10718
  }
10505
10719
  function redactSecrets(cfg) {
10506
10720
  const copy = JSON.parse(JSON.stringify(cfg));
10507
- for (const path15 of SECRET_PATHS) {
10508
- const val = getPath(copy, path15);
10721
+ for (const path16 of SECRET_PATHS) {
10722
+ const val = getPath(copy, path16);
10509
10723
  if (typeof val === "string" && val.length > 0) {
10510
- setPath(copy, path15, `${val.slice(0, 6)}\u2026(redacted, use --show-secrets)`);
10724
+ setPath(copy, path16, `${val.slice(0, 6)}\u2026(redacted, use --show-secrets)`);
10511
10725
  }
10512
10726
  }
10513
10727
  return copy;
10514
10728
  }
10515
- function getPath(obj, path15) {
10516
- const parts = path15.split(".");
10729
+ function getPath(obj, path16) {
10730
+ const parts = path16.split(".");
10517
10731
  let cur = obj;
10518
10732
  for (const part2 of parts) {
10519
10733
  if (cur == null || typeof cur !== "object") return void 0;
@@ -10521,8 +10735,8 @@ function getPath(obj, path15) {
10521
10735
  }
10522
10736
  return cur;
10523
10737
  }
10524
- function setPath(obj, path15, value) {
10525
- const parts = path15.split(".");
10738
+ function setPath(obj, path16, value) {
10739
+ const parts = path16.split(".");
10526
10740
  let cur = obj;
10527
10741
  for (let i = 0; i < parts.length - 1; i++) {
10528
10742
  const part2 = parts[i];
@@ -10532,8 +10746,8 @@ function setPath(obj, path15, value) {
10532
10746
  cur[parts[parts.length - 1]] = value;
10533
10747
  return obj;
10534
10748
  }
10535
- function unsetPath(obj, path15) {
10536
- const parts = path15.split(".");
10749
+ function unsetPath(obj, path16) {
10750
+ const parts = path16.split(".");
10537
10751
  let cur = obj;
10538
10752
  for (let i = 0; i < parts.length - 1; i++) {
10539
10753
  const part2 = parts[i];
@@ -10972,7 +11186,7 @@ ${pc13.dim(line)}`);
10972
11186
  }
10973
11187
 
10974
11188
  // src/commands/mem/export.ts
10975
- import fs12 from "fs";
11189
+ import fs13 from "fs";
10976
11190
  import { once } from "events";
10977
11191
  async function writeLine(stream, line) {
10978
11192
  if (!stream.write(line)) {
@@ -10984,7 +11198,7 @@ async function memExportCommand(outfile) {
10984
11198
  const backend = await getBackend();
10985
11199
  const stats = await backend.stats();
10986
11200
  const toFile = !!outfile && outfile !== "-";
10987
- const stream = toFile ? fs12.createWriteStream(outfile, "utf-8") : process.stdout;
11201
+ const stream = toFile ? fs13.createWriteStream(outfile, "utf-8") : process.stdout;
10988
11202
  const all = await listAllTypes(backend);
10989
11203
  const pageSize = 500;
10990
11204
  let written = 0;
@@ -11015,9 +11229,9 @@ async function memExportCommand(outfile) {
11015
11229
  }
11016
11230
  async function memImportCommand(file) {
11017
11231
  requireMemoryInit();
11018
- if (!fs12.existsSync(file)) error(`File not found: ${file}`);
11232
+ if (!fs13.existsSync(file)) error(`File not found: ${file}`);
11019
11233
  const backend = await getBackend();
11020
- const raw = fs12.readFileSync(file, "utf-8");
11234
+ const raw = fs13.readFileSync(file, "utf-8");
11021
11235
  const lines = raw.split("\n").filter((l) => l.trim().length > 0);
11022
11236
  let inserted = 0;
11023
11237
  let updated = 0;
@@ -12455,6 +12669,15 @@ Hooks fire after each page (not end-of-sync) for real-time processing. Each even
12455
12669
 
12456
12670
  Every record has a \`_synced_at\` timestamp so you can track when it was last pulled.
12457
12671
 
12672
+ ### Approving shell commands
12673
+
12674
+ \`transform\` and every hook except \`"log"\` run through \`sh -c\`. Profiles live in the project, so a cloned repo can ship them. \`sync run\` (and scheduled runs) refuse a profile's shell commands until they are approved for that project directory:
12675
+
12676
+ - Commands you pass yourself in \`sync init --config\` are approved automatically.
12677
+ - Commands already in a profile on disk, e.g. from a cloned repo, need \`one sync approve <platform> [--models <m>]\`. It shows the exact commands and asks before approving. In agent mode it returns \`confirmation_required\` with the commands; show them to the user and pass \`--yes\` only once they agree.
12678
+ - The approval covers the exact commands. If they change, e.g. after a \`git pull\`, sync refuses them again until re-approved.
12679
+ - Approvals are stored in \`~/.one/sync/shell-approvals.json\`, never in the project. \`--dry-run\` stops before any command runs and needs no approval.
12680
+
12458
12681
  ## Full Refresh (deletion detection)
12459
12682
 
12460
12683
  \`\`\`bash
@@ -12907,7 +13130,7 @@ function buildWorkflowIdeas(connections) {
12907
13130
  }
12908
13131
 
12909
13132
  // src/commands/logout.ts
12910
- import fs13 from "fs";
13133
+ import fs14 from "fs";
12911
13134
  import * as p8 from "@clack/prompts";
12912
13135
  function formatWhoami(config2, apiKey, pc17) {
12913
13136
  const whoami = config2.whoami;
@@ -12942,12 +13165,12 @@ async function logoutCommand() {
12942
13165
  const globalPath = getGlobalConfigPath();
12943
13166
  const projectPath = getProjectConfigPath();
12944
13167
  let cleared = false;
12945
- if (fs13.existsSync(projectPath)) {
12946
- fs13.unlinkSync(projectPath);
13168
+ if (fs14.existsSync(projectPath)) {
13169
+ fs14.unlinkSync(projectPath);
12947
13170
  cleared = true;
12948
13171
  }
12949
- if (fs13.existsSync(globalPath)) {
12950
- fs13.unlinkSync(globalPath);
13172
+ if (fs14.existsSync(globalPath)) {
13173
+ fs14.unlinkSync(globalPath);
12951
13174
  cleared = true;
12952
13175
  }
12953
13176
  json({ status: cleared ? "logged_out" : "not_logged_in", message: cleared ? "Credentials cleared." : "No config found." });
@@ -13016,11 +13239,11 @@ async function logoutCommand() {
13016
13239
  }
13017
13240
  if (targetScope === "project" || targetScope === "both") {
13018
13241
  const projectPath = getProjectConfigPath();
13019
- if (fs13.existsSync(projectPath)) fs13.unlinkSync(projectPath);
13242
+ if (fs14.existsSync(projectPath)) fs14.unlinkSync(projectPath);
13020
13243
  }
13021
13244
  if (targetScope === "global" || targetScope === "both") {
13022
13245
  const globalPath = getGlobalConfigPath();
13023
- if (fs13.existsSync(globalPath)) fs13.unlinkSync(globalPath);
13246
+ if (fs14.existsSync(globalPath)) fs14.unlinkSync(globalPath);
13024
13247
  }
13025
13248
  p8.log.success("Credentials cleared.");
13026
13249
  p8.log.info("Your API key is still active. Manage keys at app.withone.ai/settings");
@@ -13028,10 +13251,10 @@ async function logoutCommand() {
13028
13251
  }
13029
13252
 
13030
13253
  // src/lib/project-skills.ts
13031
- import crypto3 from "crypto";
13032
- import fs14 from "fs";
13254
+ import crypto4 from "crypto";
13255
+ import fs15 from "fs";
13033
13256
  import os2 from "os";
13034
- import path12 from "path";
13257
+ import path13 from "path";
13035
13258
  import zlib2 from "zlib";
13036
13259
  var OPTIONAL_SKILLS = [
13037
13260
  {
@@ -13063,27 +13286,27 @@ function findAgentTarget(id) {
13063
13286
  return SKILL_AGENT_TARGETS.find((a) => a.id === id.trim().toLowerCase());
13064
13287
  }
13065
13288
  function findProjectRoot(start) {
13066
- let dir = path12.resolve(start);
13289
+ let dir = path13.resolve(start);
13067
13290
  for (; ; ) {
13068
- if (fs14.existsSync(path12.join(dir, "package.json")) || fs14.existsSync(path12.join(dir, ".git"))) return dir;
13069
- const parent = path12.dirname(dir);
13291
+ if (fs15.existsSync(path13.join(dir, "package.json")) || fs15.existsSync(path13.join(dir, ".git"))) return dir;
13292
+ const parent = path13.dirname(dir);
13070
13293
  if (parent === dir) return null;
13071
13294
  dir = parent;
13072
13295
  }
13073
13296
  }
13074
13297
  function findInstalledPackage(start, packageName) {
13075
- let dir = path12.resolve(start);
13298
+ let dir = path13.resolve(start);
13076
13299
  for (; ; ) {
13077
- const pkgDir = path12.join(dir, "node_modules", ...packageName.split("/"));
13078
- const manifest = path12.join(pkgDir, "package.json");
13079
- if (fs14.existsSync(manifest)) {
13300
+ const pkgDir = path13.join(dir, "node_modules", ...packageName.split("/"));
13301
+ const manifest = path13.join(pkgDir, "package.json");
13302
+ if (fs15.existsSync(manifest)) {
13080
13303
  try {
13081
- const { version: version2 } = JSON.parse(fs14.readFileSync(manifest, "utf-8"));
13304
+ const { version: version2 } = JSON.parse(fs15.readFileSync(manifest, "utf-8"));
13082
13305
  if (version2) return { dir: pkgDir, version: version2 };
13083
13306
  } catch {
13084
13307
  }
13085
13308
  }
13086
- const parent = path12.dirname(dir);
13309
+ const parent = path13.dirname(dir);
13087
13310
  if (parent === dir) return null;
13088
13311
  dir = parent;
13089
13312
  }
@@ -13097,8 +13320,8 @@ var SkillSourceError = class extends Error {
13097
13320
  function projectSkillSource(skill, projectDir) {
13098
13321
  const installed = findInstalledPackage(projectDir, skill.packageName);
13099
13322
  if (!installed) return null;
13100
- const dir = path12.join(installed.dir, ...skill.packagePath.split("/"));
13101
- if (!fs14.existsSync(path12.join(dir, "SKILL.md"))) {
13323
+ const dir = path13.join(installed.dir, ...skill.packagePath.split("/"));
13324
+ if (!fs15.existsSync(path13.join(dir, "SKILL.md"))) {
13102
13325
  throw new SkillSourceError(
13103
13326
  `${skill.packageName} ${installed.version} does not include the ${skill.title} skill. Upgrade it: npm install ${skill.packageName}@latest`,
13104
13327
  "skill-missing"
@@ -13141,11 +13364,11 @@ async function registrySkillSource(skill, fetchImpl = fetch) {
13141
13364
  if (!files.some((f) => f.path === "SKILL.md")) {
13142
13365
  throw new SkillSourceError(`${skill.packageName}@${version2} does not include the ${skill.title} skill.`, "not-in-package");
13143
13366
  }
13144
- const tmp = fs14.mkdtempSync(path12.join(os2.tmpdir(), "one-skill-"));
13367
+ const tmp = fs15.mkdtempSync(path13.join(os2.tmpdir(), "one-skill-"));
13145
13368
  for (const file of files) {
13146
- const dest = path12.join(tmp, ...file.path.split("/"));
13147
- fs14.mkdirSync(path12.dirname(dest), { recursive: true });
13148
- fs14.writeFileSync(dest, file.data);
13369
+ const dest = path13.join(tmp, ...file.path.split("/"));
13370
+ fs15.mkdirSync(path13.dirname(dest), { recursive: true });
13371
+ fs15.writeFileSync(dest, file.data);
13149
13372
  }
13150
13373
  return {
13151
13374
  kind: "registry",
@@ -13154,7 +13377,7 @@ async function registrySkillSource(skill, fetchImpl = fetch) {
13154
13377
  origin: tarball,
13155
13378
  cleanup: () => {
13156
13379
  try {
13157
- fs14.rmSync(tmp, { recursive: true, force: true });
13380
+ fs15.rmSync(tmp, { recursive: true, force: true });
13158
13381
  } catch {
13159
13382
  }
13160
13383
  }
@@ -13166,7 +13389,7 @@ function matchesIntegrity(bytes, integrity) {
13166
13389
  if (dash < 0) return false;
13167
13390
  const algorithm = entry.slice(0, dash);
13168
13391
  if (!["sha512", "sha384", "sha256"].includes(algorithm)) return false;
13169
- return crypto3.createHash(algorithm).update(bytes).digest("base64") === entry.slice(dash + 1);
13392
+ return crypto4.createHash(algorithm).update(bytes).digest("base64") === entry.slice(dash + 1);
13170
13393
  });
13171
13394
  }
13172
13395
  function readTarEntries(tar, prefix) {
@@ -13198,7 +13421,7 @@ function readTarEntries(tar, prefix) {
13198
13421
  if ((type === "0" || type === "\0") && full.startsWith(prefix)) {
13199
13422
  const rel = full.slice(prefix.length);
13200
13423
  const parts = rel.split("/");
13201
- if (rel && !path12.isAbsolute(rel) && !parts.includes("..") && !parts.includes("")) {
13424
+ if (rel && !path13.isAbsolute(rel) && !parts.includes("..") && !parts.includes("")) {
13202
13425
  out.push({ path: rel, data: Buffer.from(body) });
13203
13426
  }
13204
13427
  }
@@ -13209,11 +13432,11 @@ function readTarEntries(tar, prefix) {
13209
13432
  }
13210
13433
  var SKILL_STAMP = ".one-skill.json";
13211
13434
  function canonicalSkillDir(skill, plan) {
13212
- return path12.join(plan.root, ...UNIVERSAL_PROJECT_DIR.split("/"), skill.dirName);
13435
+ return path13.join(plan.root, ...UNIVERSAL_PROJECT_DIR.split("/"), skill.dirName);
13213
13436
  }
13214
13437
  function readStamp(dir) {
13215
13438
  try {
13216
- return JSON.parse(fs14.readFileSync(path12.join(dir, SKILL_STAMP), "utf-8"));
13439
+ return JSON.parse(fs15.readFileSync(path13.join(dir, SKILL_STAMP), "utf-8"));
13217
13440
  } catch {
13218
13441
  return null;
13219
13442
  }
@@ -13223,9 +13446,9 @@ function resolveAgentFolders(plan) {
13223
13446
  const chosen = SKILL_AGENT_TARGETS.filter((agent) => {
13224
13447
  if (explicit) return plan.agents.includes(agent.id);
13225
13448
  if (plan.scope === "global") {
13226
- return fs14.existsSync(path12.join(plan.root, path12.dirname(agent.homeDir)));
13449
+ return fs15.existsSync(path13.join(plan.root, path13.dirname(agent.homeDir)));
13227
13450
  }
13228
- return agent.projectMarker === null || fs14.existsSync(path12.join(plan.root, agent.projectMarker));
13451
+ return agent.projectMarker === null || fs15.existsSync(path13.join(plan.root, agent.projectMarker));
13229
13452
  });
13230
13453
  const readers = [];
13231
13454
  const byDir = /* @__PURE__ */ new Map();
@@ -13240,34 +13463,34 @@ function resolveAgentFolders(plan) {
13240
13463
  return { readers, folders: [...byDir].map(([dir, agents]) => ({ dir, agents })) };
13241
13464
  }
13242
13465
  function copyDir(src, dest) {
13243
- fs14.mkdirSync(dest, { recursive: true });
13244
- for (const entry of fs14.readdirSync(src, { withFileTypes: true })) {
13466
+ fs15.mkdirSync(dest, { recursive: true });
13467
+ for (const entry of fs15.readdirSync(src, { withFileTypes: true })) {
13245
13468
  if (entry.name === SKILL_STAMP) continue;
13246
- const from = path12.join(src, entry.name);
13247
- const to = path12.join(dest, entry.name);
13469
+ const from = path13.join(src, entry.name);
13470
+ const to = path13.join(dest, entry.name);
13248
13471
  if (entry.isSymbolicLink()) continue;
13249
13472
  if (entry.isDirectory()) copyDir(from, to);
13250
- else if (entry.isFile()) fs14.copyFileSync(from, to);
13473
+ else if (entry.isFile()) fs15.copyFileSync(from, to);
13251
13474
  }
13252
13475
  }
13253
13476
  function removePath(target) {
13254
13477
  try {
13255
- const stat = fs14.lstatSync(target);
13256
- if (stat.isSymbolicLink()) fs14.unlinkSync(target);
13257
- else fs14.rmSync(target, { recursive: true, force: true });
13478
+ const stat = fs15.lstatSync(target);
13479
+ if (stat.isSymbolicLink()) fs15.unlinkSync(target);
13480
+ else fs15.rmSync(target, { recursive: true, force: true });
13258
13481
  } catch {
13259
13482
  }
13260
13483
  }
13261
13484
  function linkDir(target, linkPath) {
13262
13485
  removePath(linkPath);
13263
- fs14.mkdirSync(path12.dirname(linkPath), { recursive: true });
13486
+ fs15.mkdirSync(path13.dirname(linkPath), { recursive: true });
13264
13487
  try {
13265
- fs14.symlinkSync(path12.relative(path12.dirname(linkPath), target), linkPath, "dir");
13488
+ fs15.symlinkSync(path13.relative(path13.dirname(linkPath), target), linkPath, "dir");
13266
13489
  return "symlink";
13267
13490
  } catch {
13268
13491
  }
13269
13492
  try {
13270
- fs14.symlinkSync(target, linkPath, "junction");
13493
+ fs15.symlinkSync(target, linkPath, "junction");
13271
13494
  return "junction";
13272
13495
  } catch {
13273
13496
  }
@@ -13285,15 +13508,15 @@ function stampLinkPath(skill, plan, rel) {
13285
13508
  }
13286
13509
  function assertInsideProject(plan, target) {
13287
13510
  if (plan.scope !== "project") return;
13288
- let dir = path12.dirname(target);
13289
- while (!fs14.existsSync(dir)) {
13290
- const parent = path12.dirname(dir);
13511
+ let dir = path13.dirname(target);
13512
+ while (!fs15.existsSync(dir)) {
13513
+ const parent = path13.dirname(dir);
13291
13514
  if (parent === dir) break;
13292
13515
  dir = parent;
13293
13516
  }
13294
- const realRoot = fs14.realpathSync(plan.root);
13295
- const rel = path12.relative(realRoot, fs14.realpathSync(dir));
13296
- if (rel.startsWith("..") || path12.isAbsolute(rel)) {
13517
+ const realRoot = fs15.realpathSync(plan.root);
13518
+ const rel = path13.relative(realRoot, fs15.realpathSync(dir));
13519
+ if (rel.startsWith("..") || path13.isAbsolute(rel)) {
13297
13520
  throw new Error(`Refusing to change ${target}: a folder on that path leads outside the project (${plan.root}).`);
13298
13521
  }
13299
13522
  }
@@ -13328,20 +13551,20 @@ function installSkill(skill, source, plan, cliVersion2) {
13328
13551
  installedBy: `@withone/cli@${cliVersion2}`,
13329
13552
  installedAt: (/* @__PURE__ */ new Date()).toISOString()
13330
13553
  };
13331
- fs14.writeFileSync(path12.join(canonical, SKILL_STAMP), JSON.stringify(stamp, null, 2) + "\n");
13554
+ fs15.writeFileSync(path13.join(canonical, SKILL_STAMP), JSON.stringify(stamp, null, 2) + "\n");
13332
13555
  return { skill, version: source.version, source: source.kind, origin: source.origin, scope: plan.scope, root: plan.root, canonical, readers, links };
13333
13556
  }
13334
13557
  function removeSkill(skill, plan) {
13335
13558
  const canonical = canonicalSkillDir(skill, plan);
13336
13559
  const stamp = readStamp(canonical);
13337
- if (!stamp && !fs14.existsSync(path12.join(canonical, "SKILL.md"))) return { removed: false, paths: [] };
13560
+ if (!stamp && !fs15.existsSync(path13.join(canonical, "SKILL.md"))) return { removed: false, paths: [] };
13338
13561
  const paths = [];
13339
13562
  assertInsideProject(plan, canonical);
13340
13563
  for (const link of stamp?.links ?? []) {
13341
13564
  const full = stampLinkPath(skill, plan, link.path);
13342
13565
  if (!full) continue;
13343
13566
  assertInsideProject(plan, full);
13344
- if (fs14.existsSync(full) || isDanglingLink(full)) {
13567
+ if (fs15.existsSync(full) || isDanglingLink(full)) {
13345
13568
  removePath(full);
13346
13569
  paths.push(full);
13347
13570
  }
@@ -13354,18 +13577,18 @@ function toPosix(p9) {
13354
13577
  return p9.split("\\").join("/");
13355
13578
  }
13356
13579
  function fromStamp(root, rel) {
13357
- return path12.join(root, ...toPosix(rel).split("/"));
13580
+ return path13.join(root, ...toPosix(rel).split("/"));
13358
13581
  }
13359
13582
  function isDanglingLink(p9) {
13360
13583
  try {
13361
- return fs14.lstatSync(p9).isSymbolicLink();
13584
+ return fs15.lstatSync(p9).isSymbolicLink();
13362
13585
  } catch {
13363
13586
  return false;
13364
13587
  }
13365
13588
  }
13366
13589
  function installedSkill(skill, plan) {
13367
13590
  const canonical = canonicalSkillDir(skill, plan);
13368
- if (!fs14.existsSync(path12.join(canonical, "SKILL.md"))) return null;
13591
+ if (!fs15.existsSync(path13.join(canonical, "SKILL.md"))) return null;
13369
13592
  const stamp = readStamp(canonical);
13370
13593
  return { scope: plan.scope, root: plan.root, canonical, version: stamp?.version ?? null, source: stamp?.source ?? null, links: stamp?.links ?? [] };
13371
13594
  }
@@ -13374,7 +13597,7 @@ function globalRoot() {
13374
13597
  }
13375
13598
  function refreshProjectSkills(cwd, cliVersion2) {
13376
13599
  const root = findProjectRoot(cwd);
13377
- if (!root || path12.resolve(root) === path12.resolve(homeDir())) return [];
13600
+ if (!root || path13.resolve(root) === path13.resolve(homeDir())) return [];
13378
13601
  const results = [];
13379
13602
  for (const skill of OPTIONAL_SKILLS) {
13380
13603
  const canonical = canonicalSkillDir(skill, { scope: "project", root });
@@ -13397,7 +13620,7 @@ function refreshProjectSkills(cwd, cliVersion2) {
13397
13620
  }
13398
13621
 
13399
13622
  // src/commands/skills.ts
13400
- import path13 from "path";
13623
+ import path14 from "path";
13401
13624
  import pc16 from "picocolors";
13402
13625
  function requireSkill(name) {
13403
13626
  const skill = findOptionalSkill(name);
@@ -13421,7 +13644,7 @@ function resolvePlan(options) {
13421
13644
  const agents = parseAgents(options.agents);
13422
13645
  if (options.global) return { scope: "global", root: globalRoot(), agents };
13423
13646
  const root = findProjectRoot(process.cwd());
13424
- if (!root || path13.resolve(root) === path13.resolve(homeDir())) {
13647
+ if (!root || path14.resolve(root) === path14.resolve(homeDir())) {
13425
13648
  error("No project here: run this inside your app (a folder with package.json or .git), or pass --global.");
13426
13649
  }
13427
13650
  return { scope: "project", root, agents };
@@ -13437,7 +13660,7 @@ async function resolveSource(skill, plan) {
13437
13660
  }
13438
13661
  }
13439
13662
  function display(p9, root) {
13440
- const rel = path13.relative(root, p9);
13663
+ const rel = path14.relative(root, p9);
13441
13664
  return rel && !rel.startsWith("..") ? rel : p9;
13442
13665
  }
13443
13666
  function report(command, result, packageInProject) {
@@ -13453,7 +13676,7 @@ function report(command, result, packageInProject) {
13453
13676
  root: result.root,
13454
13677
  installedTo: [
13455
13678
  { path: result.canonical, kind: "folder", agents: result.readers },
13456
- ...result.links.map((l) => ({ path: path13.join(result.root, ...l.path.split("/")), kind: l.kind, agents: l.agents }))
13679
+ ...result.links.map((l) => ({ path: path14.join(result.root, ...l.path.split("/")), kind: l.kind, agents: l.agents }))
13457
13680
  ],
13458
13681
  next: nextSteps(result, packageInProject)
13459
13682
  });
@@ -13555,8 +13778,8 @@ async function skillsRemoveCommand(name, options) {
13555
13778
  for (const p9 of result.paths) console.log(` ${pc16.dim(display(p9, plan.root))}`);
13556
13779
  }
13557
13780
  async function skillsListCommand() {
13558
- const projectRoot = findProjectRoot(process.cwd());
13559
- const inProject = projectRoot && path13.resolve(projectRoot) !== path13.resolve(homeDir()) ? projectRoot : null;
13781
+ const projectRoot2 = findProjectRoot(process.cwd());
13782
+ const inProject = projectRoot2 && path14.resolve(projectRoot2) !== path14.resolve(homeDir()) ? projectRoot2 : null;
13560
13783
  const optional = OPTIONAL_SKILLS.map((skill) => {
13561
13784
  const project = inProject ? installedSkill(skill, { scope: "project", root: inProject }) : null;
13562
13785
  const global = installedSkill(skill, { scope: "global", root: globalRoot() });
@@ -13838,8 +14061,8 @@ config.command("reset").description("Remove the project config for the current d
13838
14061
  return;
13839
14062
  }
13840
14063
  }
13841
- const fs16 = await import("fs");
13842
- fs16.unlinkSync(globalPath);
14064
+ const fs17 = await import("fs");
14065
+ fs17.unlinkSync(globalPath);
13843
14066
  if (isAgentMode()) {
13844
14067
  json({ deleted: true, scope: "global" });
13845
14068
  } else {
@@ -13856,15 +14079,15 @@ config.command("reset").description("Remove the project config for the current d
13856
14079
  }
13857
14080
  return;
13858
14081
  }
13859
- const fs15 = await import("fs");
13860
- const configContent = fs15.readFileSync(resolved.path, "utf-8");
13861
- fs15.unlinkSync(resolved.path);
14082
+ const fs16 = await import("fs");
14083
+ const configContent = fs16.readFileSync(resolved.path, "utf-8");
14084
+ fs16.unlinkSync(resolved.path);
13862
14085
  const next = resolveConfig();
13863
- fs15.mkdirSync(path14.dirname(resolved.path), { recursive: true });
13864
- fs15.writeFileSync(resolved.path, configContent);
14086
+ fs16.mkdirSync(path15.dirname(resolved.path), { recursive: true });
14087
+ fs16.writeFileSync(resolved.path, configContent);
13865
14088
  let fallbackLabel;
13866
14089
  if (next.scope === "project") {
13867
- fallbackLabel = `parent project config (${path14.basename(next.projectRoot)})`;
14090
+ fallbackLabel = `parent project config (${path15.basename(next.projectRoot)})`;
13868
14091
  } else if (next.scope === "global") {
13869
14092
  fallbackLabel = "global config";
13870
14093
  } else {
@@ -13873,7 +14096,7 @@ config.command("reset").description("Remove the project config for the current d
13873
14096
  if (!isAgentMode()) {
13874
14097
  const p9 = await import("@clack/prompts");
13875
14098
  const confirmed = await p9.confirm({
13876
- message: `Delete project config for ${path14.basename(resolved.projectRoot)}? Will fall back to ${fallbackLabel}.`,
14099
+ message: `Delete project config for ${path15.basename(resolved.projectRoot)}? Will fall back to ${fallbackLabel}.`,
13877
14100
  initialValue: false
13878
14101
  });
13879
14102
  if (p9.isCancel(confirmed) || !confirmed) {
@@ -13881,9 +14104,9 @@ config.command("reset").description("Remove the project config for the current d
13881
14104
  return;
13882
14105
  }
13883
14106
  }
13884
- fs15.unlinkSync(resolved.path);
14107
+ fs16.unlinkSync(resolved.path);
13885
14108
  try {
13886
- fs15.rmdirSync(path14.dirname(resolved.path));
14109
+ fs16.rmdirSync(path15.dirname(resolved.path));
13887
14110
  } catch {
13888
14111
  }
13889
14112
  if (isAgentMode()) {