@withone/cli 2.14.1 → 2.14.3
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
|
@@ -45,11 +45,13 @@ 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-
|
|
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
|
|
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
|
|
1292
|
-
const projectName = path2.basename(
|
|
1293
|
+
const projectRoot2 = resolved.projectRoot;
|
|
1294
|
+
const projectName = path2.basename(projectRoot2);
|
|
1293
1295
|
const homeGlobal = tildify(getGlobalConfigPath());
|
|
1294
|
-
const homeProject = tildify(getProjectConfigPath(
|
|
1296
|
+
const homeProject = tildify(getProjectConfigPath(projectRoot2));
|
|
1295
1297
|
if (hasProject) {
|
|
1296
1298
|
console.log();
|
|
1297
|
-
console.log(` ${pc3.dim("Project:")} ${projectName} ${pc3.dim(
|
|
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(
|
|
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
|
|
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${
|
|
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${
|
|
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
|
|
4100
|
+
const path16 = `${pathPrefix}[${i}]`;
|
|
4099
4101
|
if (!step || typeof step !== "object" || Array.isArray(step)) {
|
|
4100
|
-
errors.push({ path:
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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 = `${
|
|
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, `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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: `${
|
|
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
|
|
4278
|
+
const path16 = `${pathPrefix}[${i}]`;
|
|
4277
4279
|
if (seen.has(step.id)) {
|
|
4278
|
-
errors.push({ path: `${
|
|
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], `${
|
|
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,
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
4359
|
+
function checkOperatorsInSelectorField(value, path16) {
|
|
4358
4360
|
if (typeof value === "string" && value.startsWith("$.")) {
|
|
4359
4361
|
if (value.includes("||")) {
|
|
4360
|
-
errors.push({ path:
|
|
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:
|
|
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, `${
|
|
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], `${
|
|
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,
|
|
4457
|
+
function walkOutputSchema(schema, path16) {
|
|
4456
4458
|
let current = schema;
|
|
4457
|
-
for (let i = 0; i <
|
|
4458
|
-
const seg =
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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, (_,
|
|
6960
|
-
const parts =
|
|
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,
|
|
6989
|
-
const parts =
|
|
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
|
|
6999
|
-
stripOnePath(obj,
|
|
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
|
|
7421
|
-
stripOnePath2(record,
|
|
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
|
|
8646
|
+
import fs12 from "fs";
|
|
8515
8647
|
import os from "os";
|
|
8516
|
-
import
|
|
8648
|
+
import path12 from "path";
|
|
8517
8649
|
|
|
8518
8650
|
// src/lib/memory/sync/schedule-registry.ts
|
|
8519
|
-
import
|
|
8520
|
-
import
|
|
8521
|
-
var REGISTRY_DIR = () =>
|
|
8522
|
-
var REGISTRY_FILE = () =>
|
|
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 (!
|
|
8526
|
-
const raw =
|
|
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
|
-
|
|
8667
|
+
fs11.mkdirSync(REGISTRY_DIR(), { recursive: true });
|
|
8536
8668
|
const tmp = REGISTRY_FILE() + ".tmp";
|
|
8537
|
-
|
|
8538
|
-
|
|
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 =
|
|
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 =
|
|
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 &&
|
|
8614
|
-
return
|
|
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] :
|
|
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 =
|
|
8727
|
-
|
|
8728
|
-
const logFile =
|
|
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 =
|
|
8783
|
-
const logSize = logExists ?
|
|
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 =
|
|
8920
|
+
lastRunAt = fs12.statSync(entry.logFile).mtime.toISOString();
|
|
8789
8921
|
if (logSize > 0) {
|
|
8790
|
-
const content =
|
|
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 (!
|
|
8799
|
-
else if (!
|
|
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,
|
|
9073
|
-
let s = stats.get(
|
|
9204
|
+
function recordSample(stats, path16, value, jsType, recordIndex, totalSamples) {
|
|
9205
|
+
let s = stats.get(path16);
|
|
9074
9206
|
if (!s) {
|
|
9075
9207
|
s = {
|
|
9076
|
-
path:
|
|
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(
|
|
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,
|
|
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,
|
|
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,
|
|
9236
|
+
if (str) recordSample(stats, path16, str, kind, recordIndex, totalSamples);
|
|
9105
9237
|
return;
|
|
9106
9238
|
}
|
|
9107
9239
|
if (Array.isArray(record)) {
|
|
9108
|
-
const childPath =
|
|
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 =
|
|
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({
|
|
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((
|
|
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-
|
|
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-
|
|
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({
|
|
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
|
|
10508
|
-
const val = getPath(copy,
|
|
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,
|
|
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,
|
|
10516
|
-
const parts =
|
|
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,
|
|
10525
|
-
const parts =
|
|
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,
|
|
10536
|
-
const parts =
|
|
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
|
|
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 ?
|
|
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 (!
|
|
11232
|
+
if (!fs13.existsSync(file)) error(`File not found: ${file}`);
|
|
11019
11233
|
const backend = await getBackend();
|
|
11020
|
-
const raw =
|
|
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
|
|
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 (
|
|
12946
|
-
|
|
13168
|
+
if (fs14.existsSync(projectPath)) {
|
|
13169
|
+
fs14.unlinkSync(projectPath);
|
|
12947
13170
|
cleared = true;
|
|
12948
13171
|
}
|
|
12949
|
-
if (
|
|
12950
|
-
|
|
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 (
|
|
13242
|
+
if (fs14.existsSync(projectPath)) fs14.unlinkSync(projectPath);
|
|
13020
13243
|
}
|
|
13021
13244
|
if (targetScope === "global" || targetScope === "both") {
|
|
13022
13245
|
const globalPath = getGlobalConfigPath();
|
|
13023
|
-
if (
|
|
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
|
|
13032
|
-
import
|
|
13254
|
+
import crypto4 from "crypto";
|
|
13255
|
+
import fs15 from "fs";
|
|
13033
13256
|
import os2 from "os";
|
|
13034
|
-
import
|
|
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 =
|
|
13289
|
+
let dir = path13.resolve(start);
|
|
13067
13290
|
for (; ; ) {
|
|
13068
|
-
if (
|
|
13069
|
-
const parent =
|
|
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 =
|
|
13298
|
+
let dir = path13.resolve(start);
|
|
13076
13299
|
for (; ; ) {
|
|
13077
|
-
const pkgDir =
|
|
13078
|
-
const manifest =
|
|
13079
|
-
if (
|
|
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(
|
|
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 =
|
|
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 =
|
|
13101
|
-
if (!
|
|
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 =
|
|
13367
|
+
const tmp = fs15.mkdtempSync(path13.join(os2.tmpdir(), "one-skill-"));
|
|
13145
13368
|
for (const file of files) {
|
|
13146
|
-
const dest =
|
|
13147
|
-
|
|
13148
|
-
|
|
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
|
-
|
|
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
|
|
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 && !
|
|
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
|
|
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(
|
|
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
|
|
13449
|
+
return fs15.existsSync(path13.join(plan.root, path13.dirname(agent.homeDir)));
|
|
13227
13450
|
}
|
|
13228
|
-
return agent.projectMarker === null ||
|
|
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
|
-
|
|
13244
|
-
for (const entry of
|
|
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 =
|
|
13247
|
-
const to =
|
|
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())
|
|
13473
|
+
else if (entry.isFile()) fs15.copyFileSync(from, to);
|
|
13251
13474
|
}
|
|
13252
13475
|
}
|
|
13253
13476
|
function removePath(target) {
|
|
13254
13477
|
try {
|
|
13255
|
-
const stat =
|
|
13256
|
-
if (stat.isSymbolicLink())
|
|
13257
|
-
else
|
|
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
|
-
|
|
13486
|
+
fs15.mkdirSync(path13.dirname(linkPath), { recursive: true });
|
|
13264
13487
|
try {
|
|
13265
|
-
|
|
13488
|
+
fs15.symlinkSync(path13.relative(path13.dirname(linkPath), target), linkPath, "dir");
|
|
13266
13489
|
return "symlink";
|
|
13267
13490
|
} catch {
|
|
13268
13491
|
}
|
|
13269
13492
|
try {
|
|
13270
|
-
|
|
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 =
|
|
13289
|
-
while (!
|
|
13290
|
-
const parent =
|
|
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 =
|
|
13295
|
-
const rel =
|
|
13296
|
-
if (rel.startsWith("..") ||
|
|
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
|
-
|
|
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 && !
|
|
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 (
|
|
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
|
|
13580
|
+
return path13.join(root, ...toPosix(rel).split("/"));
|
|
13358
13581
|
}
|
|
13359
13582
|
function isDanglingLink(p9) {
|
|
13360
13583
|
try {
|
|
13361
|
-
return
|
|
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 (!
|
|
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 ||
|
|
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
|
|
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 ||
|
|
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 =
|
|
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:
|
|
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
|
|
13559
|
-
const inProject =
|
|
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
|
|
13842
|
-
|
|
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
|
|
13860
|
-
const configContent =
|
|
13861
|
-
|
|
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
|
-
|
|
13864
|
-
|
|
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 (${
|
|
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 ${
|
|
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
|
-
|
|
14107
|
+
fs16.unlinkSync(resolved.path);
|
|
13885
14108
|
try {
|
|
13886
|
-
|
|
14109
|
+
fs16.rmdirSync(path15.dirname(resolved.path));
|
|
13887
14110
|
} catch {
|
|
13888
14111
|
}
|
|
13889
14112
|
if (isAgentMode()) {
|