@floomhq/floom 3.0.3 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +293 -205
- package/dist/version.js +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3067,11 +3067,38 @@ init_src();
|
|
|
3067
3067
|
import { homedir as homedir2 } from "node:os";
|
|
3068
3068
|
import { join as join3 } from "node:path";
|
|
3069
3069
|
import { mkdir as mkdir2, readFile as readFile3, writeFile, chmod } from "node:fs/promises";
|
|
3070
|
+
|
|
3071
|
+
// src/lib/invocation.ts
|
|
3072
|
+
var NPX_HINTS = ["_npx", "npm-cache/_npx", "npm-cache\\_npx", ".npm/_npx"];
|
|
3073
|
+
var NPX_INVOCATION = "npx -y @floomhq/floom";
|
|
3074
|
+
var GLOBAL_INVOCATION = "floom";
|
|
3075
|
+
var cached = null;
|
|
3076
|
+
function detect() {
|
|
3077
|
+
const override = process.env.FLOOM_INVOKED_AS;
|
|
3078
|
+
if (override && (override === NPX_INVOCATION || override === GLOBAL_INVOCATION)) {
|
|
3079
|
+
return override;
|
|
3080
|
+
}
|
|
3081
|
+
const argv1 = process.argv[1] ?? "";
|
|
3082
|
+
for (const hint of NPX_HINTS) {
|
|
3083
|
+
if (argv1.includes(hint)) return NPX_INVOCATION;
|
|
3084
|
+
}
|
|
3085
|
+
return GLOBAL_INVOCATION;
|
|
3086
|
+
}
|
|
3087
|
+
function cliInvocation() {
|
|
3088
|
+
if (cached === null) cached = detect();
|
|
3089
|
+
return cached;
|
|
3090
|
+
}
|
|
3091
|
+
function cliCmd(cmd) {
|
|
3092
|
+
const stripped = cmd.startsWith("floom ") ? cmd.slice("floom ".length) : cmd;
|
|
3093
|
+
return `${cliInvocation()} ${stripped}`;
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
// src/config.ts
|
|
3070
3097
|
var CONFIG_DIR = join3(homedir2(), ".floom");
|
|
3071
3098
|
var AUTH_FILE = join3(CONFIG_DIR, "auth.json");
|
|
3072
|
-
var DEFAULT_APP_URL = "https://floom.dev";
|
|
3073
|
-
var DEFAULT_API_URL = "https://floom.dev/api/v1";
|
|
3074
|
-
var TRUSTED_API_HOSTS = /* @__PURE__ */ new Set(["floom.dev", "try.floom.dev"]);
|
|
3099
|
+
var DEFAULT_APP_URL = "https://skills.floom.dev";
|
|
3100
|
+
var DEFAULT_API_URL = "https://skills.floom.dev/api/v1";
|
|
3101
|
+
var TRUSTED_API_HOSTS = /* @__PURE__ */ new Set(["floom.dev", "skills.floom.dev", "try.floom.dev"]);
|
|
3075
3102
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1"]);
|
|
3076
3103
|
function devModeEnabled() {
|
|
3077
3104
|
const v = process.env.FLOOM_DEV;
|
|
@@ -3097,7 +3124,9 @@ function trustedApiUrlOrDefault(apiUrl) {
|
|
|
3097
3124
|
const trimmed = apiUrl.replace(/\/$/, "");
|
|
3098
3125
|
if (!isTrustedApiUrl(trimmed)) return DEFAULT_API_URL;
|
|
3099
3126
|
try {
|
|
3100
|
-
|
|
3127
|
+
const hostname2 = new URL(trimmed).hostname;
|
|
3128
|
+
if (hostname2 === "floom.dev" || hostname2 === "try.floom.dev") return DEFAULT_API_URL;
|
|
3129
|
+
if (hostname2 === "skills.floom.dev") return `${new URL(trimmed).origin}/api/v1`;
|
|
3101
3130
|
} catch {
|
|
3102
3131
|
return DEFAULT_API_URL;
|
|
3103
3132
|
}
|
|
@@ -3119,18 +3148,25 @@ async function readRawAuth() {
|
|
|
3119
3148
|
if (code === "ENOENT") return null;
|
|
3120
3149
|
if (code === "EACCES" || code === "EPERM") {
|
|
3121
3150
|
throw new Error(
|
|
3122
|
-
|
|
3151
|
+
`Cannot read your Floom auth file (permission denied).
|
|
3152
|
+
Fix: chmod 600 ~/.floom/auth.json
|
|
3153
|
+
Or re-authenticate: ${cliCmd("login")}
|
|
3154
|
+
More help: https://floom.dev/docs#troubleshooting`
|
|
3123
3155
|
);
|
|
3124
3156
|
}
|
|
3125
3157
|
throw new Error(
|
|
3126
|
-
|
|
3158
|
+
`Cannot read your Floom auth file.
|
|
3159
|
+
Run: ${cliCmd("login")}
|
|
3160
|
+
More help: https://floom.dev/docs#troubleshooting`
|
|
3127
3161
|
);
|
|
3128
3162
|
}
|
|
3129
3163
|
try {
|
|
3130
3164
|
return JSON.parse(raw);
|
|
3131
3165
|
} catch {
|
|
3132
3166
|
throw new Error(
|
|
3133
|
-
|
|
3167
|
+
`Your Floom auth file is corrupted.
|
|
3168
|
+
Run: ${cliCmd("login")} to generate a fresh one.
|
|
3169
|
+
More help: https://floom.dev/docs#troubleshooting`
|
|
3134
3170
|
);
|
|
3135
3171
|
}
|
|
3136
3172
|
}
|
|
@@ -3253,7 +3289,7 @@ async function getMachineIdentity() {
|
|
|
3253
3289
|
}
|
|
3254
3290
|
|
|
3255
3291
|
// src/version.ts
|
|
3256
|
-
var VERSION = "3.0.
|
|
3292
|
+
var VERSION = "3.0.4";
|
|
3257
3293
|
|
|
3258
3294
|
// src/api-client.ts
|
|
3259
3295
|
var DEFAULT_TIMEOUT_MS = 2e4;
|
|
@@ -3304,12 +3340,12 @@ var HELP = "https://floom.dev/docs#troubleshooting";
|
|
|
3304
3340
|
function friendlyApiErrorMessage(code, raw, status) {
|
|
3305
3341
|
if (code === "AUTH_REQUIRED" || code === "TOKEN_INVALID" || status === 401) {
|
|
3306
3342
|
return `Not signed in.
|
|
3307
|
-
Run:
|
|
3343
|
+
Run: ${cliCmd("login")}
|
|
3308
3344
|
More help: ${HELP}`;
|
|
3309
3345
|
}
|
|
3310
3346
|
if (code === "TOKEN_EXPIRED") {
|
|
3311
3347
|
return `Your session has expired.
|
|
3312
|
-
Run:
|
|
3348
|
+
Run: ${cliCmd("login")}
|
|
3313
3349
|
More help: ${HELP}`;
|
|
3314
3350
|
}
|
|
3315
3351
|
if (code === "SKILL_ACCESS_DENIED" || status === 403) {
|
|
@@ -3318,7 +3354,7 @@ function friendlyApiErrorMessage(code, raw, status) {
|
|
|
3318
3354
|
}
|
|
3319
3355
|
if (code === "SKILL_NOT_FOUND" || status === 404) {
|
|
3320
3356
|
return `Skill not found. It may have been deleted or the slug is wrong.
|
|
3321
|
-
Run:
|
|
3357
|
+
Run: ${cliCmd("list")}
|
|
3322
3358
|
More help: ${HELP}`;
|
|
3323
3359
|
}
|
|
3324
3360
|
if (code === "RATE_LIMITED" || status === 429) {
|
|
@@ -3346,7 +3382,9 @@ async function api(path, opts = {}) {
|
|
|
3346
3382
|
if (opts.authRequired && !token) {
|
|
3347
3383
|
throw new FloomError(
|
|
3348
3384
|
"AUTH_REQUIRED",
|
|
3349
|
-
|
|
3385
|
+
`Not signed in.
|
|
3386
|
+
Run: ${cliCmd("login")}
|
|
3387
|
+
More help: https://floom.dev/docs#troubleshooting`
|
|
3350
3388
|
);
|
|
3351
3389
|
}
|
|
3352
3390
|
let lastError = null;
|
|
@@ -3425,7 +3463,7 @@ async function api(path, opts = {}) {
|
|
|
3425
3463
|
// src/lib/agents.ts
|
|
3426
3464
|
init_src();
|
|
3427
3465
|
import { createHash as createHash3 } from "node:crypto";
|
|
3428
|
-
import { readdir as readdir2, readFile as readFile5, stat as stat3, lstat as lstat2 } from "node:fs/promises";
|
|
3466
|
+
import { readdir as readdir2, readFile as readFile5, readlink, stat as stat3, lstat as lstat2 } from "node:fs/promises";
|
|
3429
3467
|
import { homedir as homedir4 } from "node:os";
|
|
3430
3468
|
import { join as join5, posix as posix2, relative as relative3, resolve as resolve2, sep as sep3 } from "node:path";
|
|
3431
3469
|
var AGENT_LABELS = {
|
|
@@ -3550,7 +3588,28 @@ async function collectHashFiles(root) {
|
|
|
3550
3588
|
const abs = join5(dir, entry.name);
|
|
3551
3589
|
const info = await lstat2(abs);
|
|
3552
3590
|
if (info.isSymbolicLink()) {
|
|
3553
|
-
|
|
3591
|
+
let target = "(unreadable)";
|
|
3592
|
+
try {
|
|
3593
|
+
target = await readlink(abs);
|
|
3594
|
+
} catch {
|
|
3595
|
+
}
|
|
3596
|
+
throw new Error(
|
|
3597
|
+
`Skill folder contains a symlink (not allowed for security):
|
|
3598
|
+
link: ${abs}
|
|
3599
|
+
target: ${target}
|
|
3600
|
+
|
|
3601
|
+
Why: a symlink could point outside the skill folder and quietly leak files
|
|
3602
|
+
(SSH keys, .env, cookie jars) into a public Library. Floom refuses to read
|
|
3603
|
+
through symlinks for that reason.
|
|
3604
|
+
|
|
3605
|
+
Fix: replace the link with the real file contents:
|
|
3606
|
+
rm "${abs}"
|
|
3607
|
+
cp "${target}" "${abs}"
|
|
3608
|
+
|
|
3609
|
+
(or move the target file under the skill folder if it lives elsewhere)
|
|
3610
|
+
|
|
3611
|
+
More help: https://floom.dev/docs#skill-folder-rules`
|
|
3612
|
+
);
|
|
3554
3613
|
}
|
|
3555
3614
|
if (entry.isDirectory()) {
|
|
3556
3615
|
await walk2(abs, childRel);
|
|
@@ -3622,8 +3681,16 @@ function tildePath(path, homeDir = homedir4()) {
|
|
|
3622
3681
|
}
|
|
3623
3682
|
|
|
3624
3683
|
// src/commands/login.ts
|
|
3684
|
+
function isHeadlessLinuxSession(env = process.env, platform3 = process.platform) {
|
|
3685
|
+
if (env.FLOOM_NO_OPEN === "1") return true;
|
|
3686
|
+
if (platform3 !== "linux") return false;
|
|
3687
|
+
const isSsh = Boolean(env.SSH_CONNECTION ?? env.SSH_CLIENT ?? env.SSH_TTY);
|
|
3688
|
+
const hasDisplay = Boolean(env.DISPLAY ?? env.WAYLAND_DISPLAY);
|
|
3689
|
+
return isSsh || !hasDisplay;
|
|
3690
|
+
}
|
|
3625
3691
|
function tryOpenBrowser(url) {
|
|
3626
3692
|
if (process.env.FLOOM_NO_OPEN === "1") return;
|
|
3693
|
+
if (isHeadlessLinuxSession()) return;
|
|
3627
3694
|
const platform3 = process.platform;
|
|
3628
3695
|
let cmd;
|
|
3629
3696
|
let args;
|
|
@@ -3666,8 +3733,17 @@ async function loginCommand() {
|
|
|
3666
3733
|
});
|
|
3667
3734
|
log.heading("Sign in to Floom");
|
|
3668
3735
|
log.blank();
|
|
3669
|
-
|
|
3670
|
-
|
|
3736
|
+
const headless = isHeadlessLinuxSession();
|
|
3737
|
+
if (headless) {
|
|
3738
|
+
log.info(" Open this URL in any browser to approve:");
|
|
3739
|
+
log.info(` ${session.verification_uri}`);
|
|
3740
|
+
log.info(` Code: ${chalk2.bold.cyan(session.user_code)}`);
|
|
3741
|
+
log.blank();
|
|
3742
|
+
log.info(" You can open it on your phone or laptop \u2014 the same URL works from anywhere.");
|
|
3743
|
+
} else {
|
|
3744
|
+
log.info(` Open: ${session.verification_uri}`);
|
|
3745
|
+
log.info(` Code: ${chalk2.bold.cyan(session.user_code)}`);
|
|
3746
|
+
}
|
|
3671
3747
|
log.blank();
|
|
3672
3748
|
process.stdout.write("Waiting for browser approval...");
|
|
3673
3749
|
tryOpenBrowser(session.verification_uri);
|
|
@@ -3693,9 +3769,13 @@ async function loginCommand() {
|
|
|
3693
3769
|
if (token.workspace?.name) log.info(` Workspace: ${token.workspace.name}`);
|
|
3694
3770
|
await printAgentsFound();
|
|
3695
3771
|
printNext([
|
|
3696
|
-
{ command: "
|
|
3697
|
-
{ command: "
|
|
3772
|
+
{ command: cliCmd("pull"), description: "install your team's skills into these agents" },
|
|
3773
|
+
{ command: cliCmd("push"), description: "if you have skills here to publish first" }
|
|
3698
3774
|
]);
|
|
3775
|
+
if (cliCmd("login").startsWith("npx ")) {
|
|
3776
|
+
log.blank();
|
|
3777
|
+
log.info(" Tip: install once with npm i -g @floomhq/floom and just type `floom` after that.");
|
|
3778
|
+
}
|
|
3699
3779
|
return;
|
|
3700
3780
|
}
|
|
3701
3781
|
} catch (error) {
|
|
@@ -3714,7 +3794,7 @@ async function loginCommand() {
|
|
|
3714
3794
|
process.stdout.write("\n");
|
|
3715
3795
|
log.err("Login timed out \u2014 the browser window expired.");
|
|
3716
3796
|
printNext([
|
|
3717
|
-
{ command: "
|
|
3797
|
+
{ command: cliCmd("login"), description: "try again" },
|
|
3718
3798
|
{ command: "More help: https://floom.dev/docs#troubleshooting" }
|
|
3719
3799
|
]);
|
|
3720
3800
|
process.exitCode = 1;
|
|
@@ -3752,7 +3832,7 @@ async function logoutCommand(opts = {}, deps = {}) {
|
|
|
3752
3832
|
);
|
|
3753
3833
|
log.blank();
|
|
3754
3834
|
log.info("Your local skill files in ~/.claude/skills, ~/.codex/skills, etc. were not changed.");
|
|
3755
|
-
printNext([{ command: "
|
|
3835
|
+
printNext([{ command: cliCmd("login"), description: "sign in again" }]);
|
|
3756
3836
|
}
|
|
3757
3837
|
|
|
3758
3838
|
// src/commands/account.ts
|
|
@@ -3807,18 +3887,18 @@ async function accountCommand(opts = {}) {
|
|
|
3807
3887
|
const device = await deviceLabel();
|
|
3808
3888
|
if (!auth) {
|
|
3809
3889
|
if (json) {
|
|
3810
|
-
emitJson({ signedIn: false, email: null, workspace: null, device: device ? { label: device } : null, next: ["
|
|
3890
|
+
emitJson({ signedIn: false, email: null, workspace: null, device: device ? { label: device } : null, next: [cliCmd("login")] });
|
|
3811
3891
|
return;
|
|
3812
3892
|
}
|
|
3813
3893
|
log.info("Not signed in to Floom.");
|
|
3814
|
-
printNext([{ command: "
|
|
3894
|
+
printNext([{ command: cliCmd("login") }]);
|
|
3815
3895
|
return;
|
|
3816
3896
|
}
|
|
3817
3897
|
if (!json) {
|
|
3818
3898
|
const rawAuth = await readRawAuth();
|
|
3819
3899
|
if (rawAuth && !isTrustedApiUrl(rawAuth.apiUrl)) {
|
|
3820
3900
|
log.warn(
|
|
3821
|
-
`Your saved Floom credentials point at ${rawAuth.apiUrl}, which is not a trusted Floom API. Run '
|
|
3901
|
+
`Your saved Floom credentials point at ${rawAuth.apiUrl}, which is not a trusted Floom API. Run '${cliCmd("login")}' to re-authenticate.`
|
|
3822
3902
|
);
|
|
3823
3903
|
}
|
|
3824
3904
|
}
|
|
@@ -3830,7 +3910,7 @@ async function accountCommand(opts = {}) {
|
|
|
3830
3910
|
email: me.user.email ?? auth.email,
|
|
3831
3911
|
workspace: me.workspace ? { name: me.workspace.name } : null,
|
|
3832
3912
|
device: { label: device },
|
|
3833
|
-
next: ["
|
|
3913
|
+
next: [cliCmd("status"), cliCmd("logout")]
|
|
3834
3914
|
});
|
|
3835
3915
|
return;
|
|
3836
3916
|
}
|
|
@@ -3840,19 +3920,19 @@ async function accountCommand(opts = {}) {
|
|
|
3840
3920
|
if (me.workspace) log.kv("Workspace:", me.workspace.name);
|
|
3841
3921
|
log.kv("This machine:", device ?? "not named yet");
|
|
3842
3922
|
printNext([
|
|
3843
|
-
{ command: "
|
|
3844
|
-
{ command: "
|
|
3923
|
+
{ command: cliCmd("status"), description: "see Library and local agent copies" },
|
|
3924
|
+
{ command: cliCmd("logout"), description: "sign out on this machine" }
|
|
3845
3925
|
]);
|
|
3846
3926
|
} catch (error) {
|
|
3847
3927
|
if (json) {
|
|
3848
|
-
emitJson({ signedIn: false, email: auth.email, workspace: null, device: { label: device }, next: ["
|
|
3928
|
+
emitJson({ signedIn: false, email: auth.email, workspace: null, device: { label: device }, next: [cliCmd("login")] });
|
|
3849
3929
|
process.exitCode = 1;
|
|
3850
3930
|
return;
|
|
3851
3931
|
}
|
|
3852
3932
|
log.err(error.message);
|
|
3853
3933
|
log.blank();
|
|
3854
3934
|
log.info("Session expired. Sign in again to continue.");
|
|
3855
|
-
printNext([{ command: "
|
|
3935
|
+
printNext([{ command: cliCmd("login") }]);
|
|
3856
3936
|
process.exitCode = 1;
|
|
3857
3937
|
}
|
|
3858
3938
|
void isJsonMode;
|
|
@@ -4720,7 +4800,7 @@ async function pushCommand(pathArg, options = {}, deps = {}) {
|
|
|
4720
4800
|
syncedCount: 0,
|
|
4721
4801
|
plan: [],
|
|
4722
4802
|
error: { code: "AUTH_REQUIRED", message: "Not signed in \u2014 log in first to publish." },
|
|
4723
|
-
next: ["
|
|
4803
|
+
next: [cliCmd("login")]
|
|
4724
4804
|
});
|
|
4725
4805
|
process.exitCode = 1;
|
|
4726
4806
|
return;
|
|
@@ -4730,7 +4810,7 @@ async function pushCommand(pathArg, options = {}, deps = {}) {
|
|
|
4730
4810
|
log.blank();
|
|
4731
4811
|
if (skills.length === 0) {
|
|
4732
4812
|
log.info("No local skill folders found on this machine.");
|
|
4733
|
-
printNext([{ command: "
|
|
4813
|
+
printNext([{ command: cliCmd("login") }]);
|
|
4734
4814
|
process.exitCode = 1;
|
|
4735
4815
|
return;
|
|
4736
4816
|
}
|
|
@@ -4738,10 +4818,10 @@ async function pushCommand(pathArg, options = {}, deps = {}) {
|
|
|
4738
4818
|
for (const skill of skills) log.info(` ${tildePath(skill.path)}`);
|
|
4739
4819
|
log.blank();
|
|
4740
4820
|
log.info("To compare and publish them, sign in to Floom.");
|
|
4741
|
-
printNext([{ command: "
|
|
4821
|
+
printNext([{ command: cliCmd("login") }]);
|
|
4742
4822
|
log.blank();
|
|
4743
4823
|
log.info("After login, run:");
|
|
4744
|
-
log.command("
|
|
4824
|
+
log.command(cliCmd("push"));
|
|
4745
4825
|
process.exitCode = 1;
|
|
4746
4826
|
return;
|
|
4747
4827
|
}
|
|
@@ -4759,7 +4839,7 @@ async function pushCommand(pathArg, options = {}, deps = {}) {
|
|
|
4759
4839
|
wouldMutate: false,
|
|
4760
4840
|
syncedCount: 0,
|
|
4761
4841
|
plan: [],
|
|
4762
|
-
next: libCount > 0 ? ["
|
|
4842
|
+
next: libCount > 0 ? [cliCmd("pull")] : [cliCmd("new")]
|
|
4763
4843
|
});
|
|
4764
4844
|
return;
|
|
4765
4845
|
}
|
|
@@ -4768,11 +4848,11 @@ async function pushCommand(pathArg, options = {}, deps = {}) {
|
|
|
4768
4848
|
log.blank();
|
|
4769
4849
|
if (libCount > 0) {
|
|
4770
4850
|
log.info(`Your Library has ${libCount} skill${libCount === 1 ? "" : "s"}. To install them here, run:`);
|
|
4771
|
-
log.command("
|
|
4851
|
+
log.command(cliCmd("pull"));
|
|
4772
4852
|
}
|
|
4773
4853
|
printNext([
|
|
4774
|
-
{ command: "
|
|
4775
|
-
{ command: "
|
|
4854
|
+
{ command: cliCmd("pull"), description: "install your Library on this machine" },
|
|
4855
|
+
{ command: cliCmd("new research-helper"), description: "create a ready-to-edit skill" }
|
|
4776
4856
|
]);
|
|
4777
4857
|
return;
|
|
4778
4858
|
}
|
|
@@ -4815,7 +4895,7 @@ async function pushCommand(pathArg, options = {}, deps = {}) {
|
|
|
4815
4895
|
lastSyncedLibraryVersion: r.lastSyncedLibraryVersion,
|
|
4816
4896
|
publishVersion: r.publishVersion
|
|
4817
4897
|
})),
|
|
4818
|
-
next: ["
|
|
4898
|
+
next: [cliCmd("push --yes"), cliCmd("push")]
|
|
4819
4899
|
});
|
|
4820
4900
|
process.exitCode = wouldMutate && !options.exitZero ? 1 : 0;
|
|
4821
4901
|
return;
|
|
@@ -4837,7 +4917,7 @@ async function pushCommand(pathArg, options = {}, deps = {}) {
|
|
|
4837
4917
|
if (!wouldMutate && refusedRows.length === 0) {
|
|
4838
4918
|
log.blank();
|
|
4839
4919
|
log.info("Everything is already in sync with the Library. Nothing to publish.");
|
|
4840
|
-
printNext([{ command: "
|
|
4920
|
+
printNext([{ command: cliCmd("status"), description: "see every copy" }]);
|
|
4841
4921
|
return;
|
|
4842
4922
|
}
|
|
4843
4923
|
let toPublish = [];
|
|
@@ -4845,12 +4925,12 @@ async function pushCommand(pathArg, options = {}, deps = {}) {
|
|
|
4845
4925
|
toPublish = safeRows;
|
|
4846
4926
|
} else if (!isInteractive()) {
|
|
4847
4927
|
log.blank();
|
|
4848
|
-
log.err("
|
|
4928
|
+
log.err(`${cliCmd("push")} needs a choice but is not running in an interactive terminal.`);
|
|
4849
4929
|
log.blank();
|
|
4850
4930
|
log.info("Re-run with:");
|
|
4851
|
-
log.command("
|
|
4852
|
-
log.command("
|
|
4853
|
-
log.command("
|
|
4931
|
+
log.command(cliCmd("push --dry-run") + " preview what would happen");
|
|
4932
|
+
log.command(cliCmd("push --yes") + " proceed with all safe changes");
|
|
4933
|
+
log.command(cliCmd("push --json") + " get the plan as JSON");
|
|
4854
4934
|
process.exitCode = 2;
|
|
4855
4935
|
return;
|
|
4856
4936
|
} else {
|
|
@@ -4900,9 +4980,9 @@ async function pushCommand(pathArg, options = {}, deps = {}) {
|
|
|
4900
4980
|
}
|
|
4901
4981
|
const conflictSlug = skipped.find((r) => r.action === "conflict")?.slug;
|
|
4902
4982
|
printNext([
|
|
4903
|
-
...conflictSlug ? [{ command: `
|
|
4904
|
-
{ command: "
|
|
4905
|
-
{ command: "
|
|
4983
|
+
...conflictSlug ? [{ command: cliCmd(`diff ${conflictSlug}`), description: "review the conflict before publishing" }] : [],
|
|
4984
|
+
{ command: cliCmd("pull"), description: "update your other agents with these new versions" },
|
|
4985
|
+
{ command: cliCmd("status"), description: "see every copy" }
|
|
4906
4986
|
]);
|
|
4907
4987
|
if (failures.length > 0 || skipped.length > 0) process.exitCode = 1;
|
|
4908
4988
|
} finally {
|
|
@@ -4915,10 +4995,10 @@ async function pushExplicitPath(pathArg, options, pushApi, authed) {
|
|
|
4915
4995
|
const info = await stat5(folder).catch(() => null);
|
|
4916
4996
|
if (!info || !info.isDirectory()) {
|
|
4917
4997
|
if (json) {
|
|
4918
|
-
emitJson({ workspace: { name: "Library", signedIn: authed }, mode: "plan", applied: false, wouldMutate: false, syncedCount: 0, plan: [], next: ["
|
|
4998
|
+
emitJson({ workspace: { name: "Library", signedIn: authed }, mode: "plan", applied: false, wouldMutate: false, syncedCount: 0, plan: [], next: [cliCmd("push")] });
|
|
4919
4999
|
} else {
|
|
4920
5000
|
log.err(`Folder not found: ${pathArg}`);
|
|
4921
|
-
log.info(
|
|
5001
|
+
log.info(` Run ${cliCmd("push")} with no arguments to auto-detect your skills, or pass a valid folder path.`);
|
|
4922
5002
|
}
|
|
4923
5003
|
process.exitCode = 2;
|
|
4924
5004
|
return;
|
|
@@ -4941,13 +5021,13 @@ async function pushExplicitPath(pathArg, options, pushApi, authed) {
|
|
|
4941
5021
|
syncedCount: 0,
|
|
4942
5022
|
plan: [],
|
|
4943
5023
|
error: { code: "AUTH_REQUIRED", message: "Not signed in \u2014 log in first to publish." },
|
|
4944
|
-
next: ["
|
|
5024
|
+
next: [cliCmd("login")]
|
|
4945
5025
|
});
|
|
4946
5026
|
} else {
|
|
4947
5027
|
log.blank();
|
|
4948
5028
|
log.err("Not signed in \u2014 log in first to publish.");
|
|
4949
5029
|
log.info(`Found local skill at ${tildePath(folder)}.`);
|
|
4950
|
-
printNext([{ command: "
|
|
5030
|
+
printNext([{ command: cliCmd("login") }]);
|
|
4951
5031
|
}
|
|
4952
5032
|
process.exitCode = 1;
|
|
4953
5033
|
return;
|
|
@@ -4964,7 +5044,7 @@ async function pushExplicitPath(pathArg, options, pushApi, authed) {
|
|
|
4964
5044
|
wouldMutate: false,
|
|
4965
5045
|
syncedCount: 0,
|
|
4966
5046
|
plan: [{ slug, scope: "global", path: folder, action: "changed", status: "refused", refusedReason: "secret_scan_failed", message: `${slug} was refused because the secret scan could not run`, localHash: "", lastSyncedHash: null, libraryVersion: null, lastSyncedLibraryVersion: null, publishVersion: null }],
|
|
4967
|
-
next: ["
|
|
5047
|
+
next: [cliCmd("push --no-secret-check")]
|
|
4968
5048
|
});
|
|
4969
5049
|
process.exitCode = 1;
|
|
4970
5050
|
return;
|
|
@@ -4986,7 +5066,7 @@ async function pushExplicitPath(pathArg, options, pushApi, authed) {
|
|
|
4986
5066
|
wouldMutate: false,
|
|
4987
5067
|
syncedCount: 0,
|
|
4988
5068
|
plan: [{ slug, scope: "global", path: folder, action: "changed", status: "refused", refusedReason: "secret_detected", message: `${slug} was refused because a file looks like it contains a secret`, localHash: "", lastSyncedHash: null, libraryVersion: null, lastSyncedLibraryVersion: null, publishVersion: null }],
|
|
4989
|
-
next: ["
|
|
5069
|
+
next: [cliCmd("push --no-secret-check")]
|
|
4990
5070
|
});
|
|
4991
5071
|
process.exitCode = 1;
|
|
4992
5072
|
return;
|
|
@@ -5011,7 +5091,7 @@ async function pushExplicitPath(pathArg, options, pushApi, authed) {
|
|
|
5011
5091
|
log.info("Nothing published.");
|
|
5012
5092
|
printNext([
|
|
5013
5093
|
{ command: "Edit .floomignore to exclude secrets or generated files" },
|
|
5014
|
-
{ command: `
|
|
5094
|
+
{ command: cliCmd(`push ${pathArg}`) }
|
|
5015
5095
|
]);
|
|
5016
5096
|
return;
|
|
5017
5097
|
}
|
|
@@ -5026,7 +5106,7 @@ async function pushExplicitPath(pathArg, options, pushApi, authed) {
|
|
|
5026
5106
|
wouldMutate: true,
|
|
5027
5107
|
syncedCount: 0,
|
|
5028
5108
|
plan: [{ slug, scope: "global", path: folder, action: "changed", status: "ok", refusedReason: null, message: `${slug} would be published`, localHash: await skillContentHash(folder) ?? "", lastSyncedHash: null, libraryVersion: null, lastSyncedLibraryVersion: null, publishVersion: null }],
|
|
5029
|
-
next: [
|
|
5109
|
+
next: [cliCmd(`push ${pathArg} --yes`)]
|
|
5030
5110
|
});
|
|
5031
5111
|
} else {
|
|
5032
5112
|
log.heading("Push plan");
|
|
@@ -5046,15 +5126,15 @@ async function pushExplicitPath(pathArg, options, pushApi, authed) {
|
|
|
5046
5126
|
mode: "apply",
|
|
5047
5127
|
applied: true,
|
|
5048
5128
|
results: [{ slug: result.skill.slug, scope: "global", path: folder, action: "changed", result: "published", message: `${result.skill.slug} published as ${result.skill.latest.display}`, libraryVersion: result.skill.latest.version_seq ?? null, publishVersion: result.skill.latest.version_seq ?? null, backupPath: null }],
|
|
5049
|
-
next: ["
|
|
5129
|
+
next: [cliCmd("pull")]
|
|
5050
5130
|
});
|
|
5051
5131
|
} else {
|
|
5052
5132
|
log.ok(`Published ${result.skill.slug} ${result.skill.latest.display}`);
|
|
5053
|
-
printNext([{ command: "
|
|
5133
|
+
printNext([{ command: cliCmd("pull"), description: `update your other agents with ${result.skill.slug}` }]);
|
|
5054
5134
|
}
|
|
5055
5135
|
} catch (error) {
|
|
5056
5136
|
if (json) {
|
|
5057
|
-
emitJson({ workspace: { name: "Library", signedIn: true }, mode: "apply", applied: true, results: [{ slug, scope: "global", path: folder, action: "changed", result: "failed", message: error.message, libraryVersion: null, publishVersion: null, backupPath: null }], next: [
|
|
5137
|
+
emitJson({ workspace: { name: "Library", signedIn: true }, mode: "apply", applied: true, results: [{ slug, scope: "global", path: folder, action: "changed", result: "failed", message: error.message, libraryVersion: null, publishVersion: null, backupPath: null }], next: [cliCmd(`push ${pathArg}`)] });
|
|
5058
5138
|
} else {
|
|
5059
5139
|
log.err(error.message);
|
|
5060
5140
|
}
|
|
@@ -5095,11 +5175,11 @@ async function deleteCommand(slug, opts = {}, deps = { api }) {
|
|
|
5095
5175
|
skill: { slug: "", version: null, exists: false },
|
|
5096
5176
|
wouldMutate: false,
|
|
5097
5177
|
action: "missing_argument",
|
|
5098
|
-
message: "
|
|
5099
|
-
next: ["
|
|
5178
|
+
message: `${cliCmd("delete")} needs a skill name.`,
|
|
5179
|
+
next: [cliCmd("list")]
|
|
5100
5180
|
});
|
|
5101
5181
|
} else {
|
|
5102
|
-
log.err("
|
|
5182
|
+
log.err(`${cliCmd("delete")} needs a skill name. Run \`${cliCmd("list")}\` to see skills in the Library.`);
|
|
5103
5183
|
}
|
|
5104
5184
|
process.exitCode = 2;
|
|
5105
5185
|
return;
|
|
@@ -5116,10 +5196,10 @@ async function deleteCommand(slug, opts = {}, deps = { api }) {
|
|
|
5116
5196
|
wouldMutate: false,
|
|
5117
5197
|
action: "not_found",
|
|
5118
5198
|
message: `No skill named '${slug}' in ${ws}.`,
|
|
5119
|
-
next: ["
|
|
5199
|
+
next: [cliCmd("list")]
|
|
5120
5200
|
});
|
|
5121
5201
|
} else {
|
|
5122
|
-
log.err(`No skill named '${slug}' in ${ws}. Run
|
|
5202
|
+
log.err(`No skill named '${slug}' in ${ws}. Run \`${cliCmd("list")}\` to see what is in the Library.`);
|
|
5123
5203
|
}
|
|
5124
5204
|
process.exitCode = 2;
|
|
5125
5205
|
return;
|
|
@@ -5134,7 +5214,7 @@ async function deleteCommand(slug, opts = {}, deps = { api }) {
|
|
|
5134
5214
|
wouldMutate: true,
|
|
5135
5215
|
action: "delete",
|
|
5136
5216
|
message: `${slug} would be deleted from the Library.`,
|
|
5137
|
-
next: [
|
|
5217
|
+
next: [cliCmd(`delete ${slug} --yes`)]
|
|
5138
5218
|
});
|
|
5139
5219
|
} else {
|
|
5140
5220
|
log.heading(`Delete plan for ${ws} Library`);
|
|
@@ -5160,17 +5240,17 @@ async function deleteCommand(slug, opts = {}, deps = { api }) {
|
|
|
5160
5240
|
wouldMutate: true,
|
|
5161
5241
|
action: "delete",
|
|
5162
5242
|
message: `${slug} would be deleted from the Library.`,
|
|
5163
|
-
next: [
|
|
5243
|
+
next: [cliCmd(`delete ${slug} --yes`)]
|
|
5164
5244
|
});
|
|
5165
5245
|
process.exitCode = opts.exitZero ? 0 : 1;
|
|
5166
5246
|
return;
|
|
5167
5247
|
}
|
|
5168
5248
|
if (!opts.yes) {
|
|
5169
5249
|
if (!isInteractive()) {
|
|
5170
|
-
log.err("
|
|
5250
|
+
log.err(`${cliCmd("delete")} needs confirmation but is not running in an interactive terminal.`);
|
|
5171
5251
|
log.blank();
|
|
5172
5252
|
log.info("Re-run with:");
|
|
5173
|
-
log.command(`
|
|
5253
|
+
log.command(cliCmd(`delete ${slug} --yes`));
|
|
5174
5254
|
process.exitCode = 2;
|
|
5175
5255
|
return;
|
|
5176
5256
|
}
|
|
@@ -5199,7 +5279,7 @@ async function deleteCommand(slug, opts = {}, deps = { api }) {
|
|
|
5199
5279
|
skill: { slug, version: info.versionSeq, exists: true },
|
|
5200
5280
|
result: "failed",
|
|
5201
5281
|
message: error.message,
|
|
5202
|
-
next: [
|
|
5282
|
+
next: [cliCmd(`delete ${slug} --yes`)]
|
|
5203
5283
|
});
|
|
5204
5284
|
} else {
|
|
5205
5285
|
log.err(error.message);
|
|
@@ -5215,14 +5295,14 @@ async function deleteCommand(slug, opts = {}, deps = { api }) {
|
|
|
5215
5295
|
skill: { slug, version: info.versionSeq, exists: false },
|
|
5216
5296
|
result: "deleted",
|
|
5217
5297
|
message: `Deleted ${slug} from the Library.`,
|
|
5218
|
-
next: ["
|
|
5298
|
+
next: [cliCmd("status")]
|
|
5219
5299
|
});
|
|
5220
5300
|
return;
|
|
5221
5301
|
}
|
|
5222
5302
|
log.ok(`Deleted ${slug} from the Library.`);
|
|
5223
5303
|
log.blank();
|
|
5224
5304
|
log.info("Local copies in your agents still have the skill. Floom will not delete them automatically.");
|
|
5225
|
-
printNext([{ command: "
|
|
5305
|
+
printNext([{ command: cliCmd("status"), description: "see which local copies are no longer in the Library" }]);
|
|
5226
5306
|
}
|
|
5227
5307
|
|
|
5228
5308
|
// src/commands/list.ts
|
|
@@ -5236,11 +5316,11 @@ async function listCommand(opts = {}) {
|
|
|
5236
5316
|
workspace: { name: "Library", signedIn: false },
|
|
5237
5317
|
error: { code: "AUTH_REQUIRED", message: "Not signed in." },
|
|
5238
5318
|
skills: [],
|
|
5239
|
-
next: ["
|
|
5319
|
+
next: [cliCmd("login")]
|
|
5240
5320
|
});
|
|
5241
5321
|
} else {
|
|
5242
5322
|
log.err("Not signed in.");
|
|
5243
|
-
printNext([{ command: "
|
|
5323
|
+
printNext([{ command: cliCmd("login") }]);
|
|
5244
5324
|
}
|
|
5245
5325
|
process.exitCode = 1;
|
|
5246
5326
|
return;
|
|
@@ -5255,7 +5335,7 @@ async function listCommand(opts = {}) {
|
|
|
5255
5335
|
workspace: { name: "Library", signedIn: true },
|
|
5256
5336
|
error: { code: fe?.code ?? "INTERNAL_ERROR", message: e.message },
|
|
5257
5337
|
skills: [],
|
|
5258
|
-
next: ["
|
|
5338
|
+
next: [cliCmd("login")]
|
|
5259
5339
|
});
|
|
5260
5340
|
process.exitCode = 1;
|
|
5261
5341
|
return;
|
|
@@ -5289,7 +5369,7 @@ async function listCommand(opts = {}) {
|
|
|
5289
5369
|
}))
|
|
5290
5370
|
};
|
|
5291
5371
|
}),
|
|
5292
|
-
next: result.total === 0 ? ["
|
|
5372
|
+
next: result.total === 0 ? [cliCmd("push")] : [cliCmd("pull"), cliCmd("status")]
|
|
5293
5373
|
});
|
|
5294
5374
|
return;
|
|
5295
5375
|
}
|
|
@@ -5304,7 +5384,7 @@ async function listCommand(opts = {}) {
|
|
|
5304
5384
|
log.info(` ${tildePath(skill.path)}`);
|
|
5305
5385
|
}
|
|
5306
5386
|
}
|
|
5307
|
-
printNext([{ command: "
|
|
5387
|
+
printNext([{ command: cliCmd("push"), description: "publish your first skills to the Library" }]);
|
|
5308
5388
|
return;
|
|
5309
5389
|
}
|
|
5310
5390
|
log.heading(`Library \xB7 ${result.total} skill${result.total === 1 ? "" : "s"}`);
|
|
@@ -5322,8 +5402,8 @@ async function listCommand(opts = {}) {
|
|
|
5322
5402
|
log.info(` ... and ${result.total - rows.length} more`);
|
|
5323
5403
|
}
|
|
5324
5404
|
printNext([
|
|
5325
|
-
{ command: "
|
|
5326
|
-
{ command: "
|
|
5405
|
+
{ command: cliCmd("pull"), description: "install missing skills into local agents" },
|
|
5406
|
+
{ command: cliCmd("status"), description: "compare every local copy in detail" }
|
|
5327
5407
|
]);
|
|
5328
5408
|
}
|
|
5329
5409
|
|
|
@@ -5388,10 +5468,10 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5388
5468
|
const planMode = isPlanMode(flags);
|
|
5389
5469
|
if (!await readAuth()) {
|
|
5390
5470
|
if (json) {
|
|
5391
|
-
emitJson({ workspace: { name: "Library", signedIn: false }, mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], next: ["
|
|
5471
|
+
emitJson({ workspace: { name: "Library", signedIn: false }, mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], next: [cliCmd("login")] });
|
|
5392
5472
|
} else {
|
|
5393
5473
|
log.info("Not signed in to Floom.");
|
|
5394
|
-
printNext([{ command: "
|
|
5474
|
+
printNext([{ command: cliCmd("login") }]);
|
|
5395
5475
|
}
|
|
5396
5476
|
process.exitCode = 2;
|
|
5397
5477
|
return;
|
|
@@ -5399,14 +5479,14 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5399
5479
|
const detected = await detectAgents();
|
|
5400
5480
|
if (detected.length === 0) {
|
|
5401
5481
|
if (json) {
|
|
5402
|
-
emitJson({ workspace: { name: "Library", signedIn: true }, mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], next: ["
|
|
5482
|
+
emitJson({ workspace: { name: "Library", signedIn: true }, mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], next: [cliCmd("pull --agent claude")] });
|
|
5403
5483
|
} else {
|
|
5404
5484
|
log.info("No AI agents found on this machine.");
|
|
5405
5485
|
log.blank();
|
|
5406
5486
|
log.info("Floom looks for Claude, Codex, Cursor, Gemini, or OpenCode.");
|
|
5407
|
-
log.info(
|
|
5487
|
+
log.info(`Make sure one of these is installed, then run ${cliCmd("pull")} again.`);
|
|
5408
5488
|
log.blank();
|
|
5409
|
-
log.info(
|
|
5489
|
+
log.info(`Or use: ${cliCmd("pull --agent claude")}`);
|
|
5410
5490
|
log.blank();
|
|
5411
5491
|
log.info("More help: https://floom.dev/docs#agents");
|
|
5412
5492
|
}
|
|
@@ -5418,19 +5498,19 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5418
5498
|
if (skillArg) {
|
|
5419
5499
|
if (flags.allScopes) {
|
|
5420
5500
|
if (json) {
|
|
5421
|
-
emitJson({ workspace: { name: "Library", signedIn: true }, error: "
|
|
5501
|
+
emitJson({ workspace: { name: "Library", signedIn: true }, error: `${cliCmd("pull")} <skill> targets one copy \u2014 pass --global or --project, not --all-scopes.`, next: [cliCmd(`pull ${skillArg} --agent claude --global`)] });
|
|
5422
5502
|
} else {
|
|
5423
|
-
log.err("
|
|
5503
|
+
log.err(`${cliCmd("pull")} <skill> targets one copy \u2014 pass --global or --project, not --all-scopes.`);
|
|
5424
5504
|
}
|
|
5425
5505
|
process.exitCode = 2;
|
|
5426
5506
|
return;
|
|
5427
5507
|
}
|
|
5428
5508
|
if (flags.agents.length !== 1) {
|
|
5429
5509
|
if (json) {
|
|
5430
|
-
emitJson({ workspace: { name: "Library", signedIn: true }, error: "
|
|
5510
|
+
emitJson({ workspace: { name: "Library", signedIn: true }, error: `${cliCmd("pull")} <skill> needs exactly one --agent`, next: [cliCmd(`pull ${skillArg} --agent claude`)] });
|
|
5431
5511
|
} else {
|
|
5432
|
-
log.err(
|
|
5433
|
-
log.command(`
|
|
5512
|
+
log.err(`${cliCmd("pull")} ${skillArg} needs exactly one --agent <name>.`);
|
|
5513
|
+
log.command(cliCmd(`pull ${skillArg} --agent claude`));
|
|
5434
5514
|
}
|
|
5435
5515
|
process.exitCode = 2;
|
|
5436
5516
|
return;
|
|
@@ -5439,7 +5519,7 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5439
5519
|
const hasProjectSkillsDir = await projectSkillsDirExists(process.cwd());
|
|
5440
5520
|
if (flags.scope === "project" && !hasProjectSkillsDir) {
|
|
5441
5521
|
if (json) {
|
|
5442
|
-
emitJson({ workspace: { name: "Library", signedIn: true }, error: "No project agent skill directory found. Pass --global or run from a project with agent skills.", next: [`
|
|
5522
|
+
emitJson({ workspace: { name: "Library", signedIn: true }, error: "No project agent skill directory found. Pass --global or run from a project with agent skills.", next: [cliCmd(`pull ${skillArg} --agent ${agent} --global`)] });
|
|
5443
5523
|
} else {
|
|
5444
5524
|
log.err("No project agent skill directory found. Pass --global or run from a project with agent skills.");
|
|
5445
5525
|
}
|
|
@@ -5448,7 +5528,7 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5448
5528
|
}
|
|
5449
5529
|
if (flags.scope === null && hasProjectSkillsDir && !isInteractive()) {
|
|
5450
5530
|
if (json) {
|
|
5451
|
-
emitJson({ workspace: { name: "Library", signedIn: true }, error: "This project has its own agent skill copies. Pass --global or --project.", next: [`
|
|
5531
|
+
emitJson({ workspace: { name: "Library", signedIn: true }, error: "This project has its own agent skill copies. Pass --global or --project.", next: [cliCmd(`pull ${skillArg} --agent ${agent} --global`), cliCmd(`pull ${skillArg} --agent ${agent} --project`)] });
|
|
5452
5532
|
} else {
|
|
5453
5533
|
log.err("This project has its own agent skill copies. Pass --global or --project.");
|
|
5454
5534
|
}
|
|
@@ -5457,7 +5537,7 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5457
5537
|
}
|
|
5458
5538
|
const resolvedScope = flags.scope === "project" ? "project" : "global";
|
|
5459
5539
|
const dir = resolve5(resolvedScope === "project" ? projectSkillsDir(agent) : globalSkillsDir(agent));
|
|
5460
|
-
const scopedPullCommand = `
|
|
5540
|
+
const scopedPullCommand = cliCmd(`pull ${skillArg} --agent ${agent}${resolvedScope === "project" ? " --project" : ""}`);
|
|
5461
5541
|
if (flags.dryRun || json && !flags.yes) {
|
|
5462
5542
|
if (json) {
|
|
5463
5543
|
emitJson({ workspace: { name: "Library", signedIn: true }, mode: "plan", applied: false, wouldMutate: true, hasSkipped: false, agents: [{ name: agent, scope: resolvedScope, skillsDir: dir, actions: [{ slug: skillArg, scope: resolvedScope, action: "update", fromVersion: null, toVersion: null, path: join9(dir, skillArg), backupPath: null }] }], next: [`${scopedPullCommand} --yes`] });
|
|
@@ -5473,10 +5553,10 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5473
5553
|
cleanup.trackDir(join9(dir, ".floom", "tmp"));
|
|
5474
5554
|
const result = await pullOneSkill(agent, skillArg, { installDir: dir });
|
|
5475
5555
|
if (json) {
|
|
5476
|
-
emitJson({ workspace: { name: "Library", signedIn: true }, mode: "apply", applied: true, hasFailures: false, agents: [{ name: agent, scope: resolvedScope, skillsDir: dir, results: [{ slug: result.slug, scope: resolvedScope, action: "update", result: "updated", fromVersion: null, toVersion: result.versionSeq, path: join9(dir, result.slug), backupPath: null, message: `updated to v${result.versionSeq}` }] }], next: [`
|
|
5556
|
+
emitJson({ workspace: { name: "Library", signedIn: true }, mode: "apply", applied: true, hasFailures: false, agents: [{ name: agent, scope: resolvedScope, skillsDir: dir, results: [{ slug: result.slug, scope: resolvedScope, action: "update", result: "updated", fromVersion: null, toVersion: result.versionSeq, path: join9(dir, result.slug), backupPath: null, message: `updated to v${result.versionSeq}` }] }], next: [cliCmd(`status --agent ${agent}`)] });
|
|
5477
5557
|
} else {
|
|
5478
5558
|
log.ok(`Backed up your local copy and updated ${result.slug} to Library v${result.versionSeq}`);
|
|
5479
|
-
printNext([{ command: `
|
|
5559
|
+
printNext([{ command: cliCmd(`status --agent ${agent}`) }]);
|
|
5480
5560
|
}
|
|
5481
5561
|
} catch (error) {
|
|
5482
5562
|
if (json) {
|
|
@@ -5490,16 +5570,16 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5490
5570
|
}
|
|
5491
5571
|
if (!planMode && flags.agents.length === 0 && !wantAll && !isInteractive()) {
|
|
5492
5572
|
if (json) {
|
|
5493
|
-
emitJson({ workspace: { name: "Library", signedIn: true }, error: "
|
|
5573
|
+
emitJson({ workspace: { name: "Library", signedIn: true }, error: `${cliCmd("pull")} needs to know which agents to update`, next: [cliCmd("pull --all-agents --yes")] });
|
|
5494
5574
|
} else {
|
|
5495
|
-
log.err("
|
|
5575
|
+
log.err(`${cliCmd("pull")} needs to know which agents to update.`);
|
|
5496
5576
|
log.blank();
|
|
5497
5577
|
log.info("Pass --agent <name>, repeat --agent, or use --all-agents.");
|
|
5498
5578
|
log.blank();
|
|
5499
5579
|
log.info("Examples:");
|
|
5500
|
-
log.command("
|
|
5501
|
-
log.command("
|
|
5502
|
-
log.command("
|
|
5580
|
+
log.command(cliCmd("pull --agent claude --yes"));
|
|
5581
|
+
log.command(cliCmd("pull --agent claude,codex --yes"));
|
|
5582
|
+
log.command(cliCmd("pull --all-agents --yes"));
|
|
5503
5583
|
}
|
|
5504
5584
|
process.exitCode = 2;
|
|
5505
5585
|
return;
|
|
@@ -5544,7 +5624,7 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5544
5624
|
backupPath: null
|
|
5545
5625
|
}))
|
|
5546
5626
|
})),
|
|
5547
|
-
next: ["
|
|
5627
|
+
next: [cliCmd("pull --all-agents --yes")]
|
|
5548
5628
|
});
|
|
5549
5629
|
process.exitCode = (wouldMutate || hasSkipped) && !flags.exitZero ? 1 : 0;
|
|
5550
5630
|
return;
|
|
@@ -5582,7 +5662,7 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5582
5662
|
}
|
|
5583
5663
|
log.blank();
|
|
5584
5664
|
const firstConflict = conflicted[0];
|
|
5585
|
-
printNext(firstConflict ? [{ command: `
|
|
5665
|
+
printNext(firstConflict ? [{ command: cliCmd(`diff ${firstConflict.slug}`), description: "review the conflict" }] : [{ command: cliCmd("status") }]);
|
|
5586
5666
|
process.exitCode = flags.exitZero ? 0 : 1;
|
|
5587
5667
|
return;
|
|
5588
5668
|
}
|
|
@@ -5591,7 +5671,7 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5591
5671
|
for (const p of plans) {
|
|
5592
5672
|
log.row([AGENT_LABELS[p.agent], "up to date"], [10]);
|
|
5593
5673
|
}
|
|
5594
|
-
printNext([{ command: "
|
|
5674
|
+
printNext([{ command: cliCmd("status") }]);
|
|
5595
5675
|
return;
|
|
5596
5676
|
}
|
|
5597
5677
|
log.heading(`Update agent copies from ${workspaceName}`);
|
|
@@ -5608,7 +5688,7 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5608
5688
|
let toUpdate = actionable;
|
|
5609
5689
|
if (!flags.yes) {
|
|
5610
5690
|
if (!isInteractive()) {
|
|
5611
|
-
log.err("
|
|
5691
|
+
log.err(`${cliCmd("pull")} needs a choice but is not running in an interactive terminal.`);
|
|
5612
5692
|
log.info("Re-run with --agent <name> --yes, or --all-agents --yes.");
|
|
5613
5693
|
process.exitCode = 2;
|
|
5614
5694
|
return;
|
|
@@ -5668,7 +5748,7 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5668
5748
|
message: a.message
|
|
5669
5749
|
}]
|
|
5670
5750
|
})),
|
|
5671
|
-
next: ["
|
|
5751
|
+
next: [cliCmd("status")]
|
|
5672
5752
|
});
|
|
5673
5753
|
if (failed || hasSkipped) process.exitCode = flags.exitZero ? 0 : 1;
|
|
5674
5754
|
return;
|
|
@@ -5681,8 +5761,8 @@ async function pullCommand(skillArg, rawOpts = {}) {
|
|
|
5681
5761
|
}
|
|
5682
5762
|
}
|
|
5683
5763
|
log.blank();
|
|
5684
|
-
log.info(
|
|
5685
|
-
printNext([{ command: "
|
|
5764
|
+
log.info(`Backups of any replaced skills are in each agent's .floom/backups/ folder (run ${cliCmd("restore --list")} to see them).`);
|
|
5765
|
+
printNext([{ command: cliCmd("status") }]);
|
|
5686
5766
|
if (failed || hasSkipped) process.exitCode = flags.exitZero ? 0 : 1;
|
|
5687
5767
|
void skillArg;
|
|
5688
5768
|
void INSTALL_TARGETS;
|
|
@@ -5735,8 +5815,8 @@ function printPreview(plan) {
|
|
|
5735
5815
|
log.err(`${count} ${noun} both locally and on the server: Floom won't guess which wins.`);
|
|
5736
5816
|
for (const skill of plan.conflicts) {
|
|
5737
5817
|
log.err(` - ${skill.slug} (${skill.version})`);
|
|
5738
|
-
log.err(` Keep the server version:
|
|
5739
|
-
log.err(` Keep your local version:
|
|
5818
|
+
log.err(` Keep the server version: ${cliCmd(`pull --target ${plan.target}`)}`);
|
|
5819
|
+
log.err(` Keep your local version: ${cliCmd(`push <${skill.slug}-dir>`)}`);
|
|
5740
5820
|
}
|
|
5741
5821
|
log.err("Your local copy is always backed up to .floom/backups/ first.");
|
|
5742
5822
|
log.err("More: https://floom.dev/docs#conflicts");
|
|
@@ -5862,7 +5942,7 @@ async function runSyncForTarget(options = {}, deps = {}) {
|
|
|
5862
5942
|
log.err(` snapshot at: ${failure.snapshotDir}`);
|
|
5863
5943
|
}
|
|
5864
5944
|
log.err("");
|
|
5865
|
-
log.err(`Re-run
|
|
5945
|
+
log.err(`Re-run \`${cliCmd(`sync --target ${plan.target}`)}\` after the network recovers.`);
|
|
5866
5946
|
process.exitCode = 1;
|
|
5867
5947
|
return { ok: false, pushFailures, hasConflicts: false };
|
|
5868
5948
|
}
|
|
@@ -5876,6 +5956,13 @@ async function runSyncForTarget(options = {}, deps = {}) {
|
|
|
5876
5956
|
init_runtime();
|
|
5877
5957
|
init_prompt();
|
|
5878
5958
|
import { resolve as resolve6 } from "node:path";
|
|
5959
|
+
async function runTestSyncDelay(cleanup) {
|
|
5960
|
+
const delayMs = Number.parseInt(process.env.FLOOM_TEST_SYNC_DELAY_MS ?? "", 10);
|
|
5961
|
+
if (!Number.isFinite(delayMs) || delayMs <= 0) return;
|
|
5962
|
+
const trackDir = process.env.FLOOM_TEST_SYNC_TRACK_DIR;
|
|
5963
|
+
if (trackDir) cleanup.trackDir(trackDir);
|
|
5964
|
+
await new Promise((resolve14) => setTimeout(resolve14, delayMs));
|
|
5965
|
+
}
|
|
5879
5966
|
function appliedFromResult(plan, result) {
|
|
5880
5967
|
if (result.ok) return { plan, ok: true, message: "synced" };
|
|
5881
5968
|
const message = result.pushFailures.length > 0 ? `push failed for ${result.pushFailures.map((f) => f.slug).join(", ")}` : result.hasConflicts ? "changed in two places" : "sync did not complete";
|
|
@@ -5897,7 +5984,7 @@ function buildApplyJson(workspaceName, applied, opts) {
|
|
|
5897
5984
|
...a.plan.conflicts.map((slug) => ({ slug, scope: a.plan.scope, direction: "pull", result: "skipped", fromVersion: null, toVersion: null, path: a.plan.skillsDir, backupPath: null, message: "changed in two places" }))
|
|
5898
5985
|
]
|
|
5899
5986
|
})),
|
|
5900
|
-
next: ["
|
|
5987
|
+
next: [cliCmd("status")]
|
|
5901
5988
|
};
|
|
5902
5989
|
}
|
|
5903
5990
|
function formatAgentLabel(plan) {
|
|
@@ -5926,6 +6013,7 @@ async function syncCommand(rawOpts = {}, deps = {}) {
|
|
|
5926
6013
|
const readAuthFn = deps.readAuth ?? readAuth;
|
|
5927
6014
|
const cleanup = installCancellationHandler();
|
|
5928
6015
|
try {
|
|
6016
|
+
await runTestSyncDelay(cleanup);
|
|
5929
6017
|
let flags;
|
|
5930
6018
|
try {
|
|
5931
6019
|
flags = parseCommonFlags(rawOpts);
|
|
@@ -5941,10 +6029,10 @@ async function syncCommand(rawOpts = {}, deps = {}) {
|
|
|
5941
6029
|
const planMode = isPlanMode(flags);
|
|
5942
6030
|
if (!await readAuthFn()) {
|
|
5943
6031
|
if (json) {
|
|
5944
|
-
emitJson({ workspace: { name: "Library", signedIn: false }, mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], next: ["
|
|
6032
|
+
emitJson({ workspace: { name: "Library", signedIn: false }, mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], next: [cliCmd("login")] });
|
|
5945
6033
|
} else {
|
|
5946
6034
|
log.info("Not signed in to Floom.");
|
|
5947
|
-
printNext([{ command: "
|
|
6035
|
+
printNext([{ command: cliCmd("login") }]);
|
|
5948
6036
|
}
|
|
5949
6037
|
process.exitCode = 2;
|
|
5950
6038
|
return;
|
|
@@ -5952,7 +6040,7 @@ async function syncCommand(rawOpts = {}, deps = {}) {
|
|
|
5952
6040
|
const detected = await detectAgentsFn();
|
|
5953
6041
|
if (detected.length === 0) {
|
|
5954
6042
|
if (json) {
|
|
5955
|
-
emitJson({ workspace: { name: "Library", signedIn: true }, mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], error: "no agents detected \u2014 install Claude, Codex, Cursor, Gemini, or OpenCode", next: ["
|
|
6043
|
+
emitJson({ workspace: { name: "Library", signedIn: true }, mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], error: "no agents detected \u2014 install Claude, Codex, Cursor, Gemini, or OpenCode", next: [cliCmd("sync --agent claude")] });
|
|
5956
6044
|
} else {
|
|
5957
6045
|
log.err("No agents detected \u2014 install Claude, Codex, Cursor, Gemini, or OpenCode, then re-run.");
|
|
5958
6046
|
log.info("Floom syncs into one of: Claude, Codex, Cursor, Gemini, OpenCode.");
|
|
@@ -5965,14 +6053,14 @@ async function syncCommand(rawOpts = {}, deps = {}) {
|
|
|
5965
6053
|
const selectedAgents = flags.agents.length > 0 ? flags.agents : detected;
|
|
5966
6054
|
if (!planMode && !flags.yes && !isInteractive()) {
|
|
5967
6055
|
if (json) {
|
|
5968
|
-
emitJson({ workspace: { name: "Library", signedIn: true }, error: "
|
|
6056
|
+
emitJson({ workspace: { name: "Library", signedIn: true }, error: `${cliCmd("sync")} needs --yes to apply changes in a non-interactive shell`, next: [cliCmd("sync --yes")] });
|
|
5969
6057
|
} else {
|
|
5970
|
-
log.err("
|
|
6058
|
+
log.err(`${cliCmd("sync")} needs --yes to apply changes in a non-interactive shell.`);
|
|
5971
6059
|
log.blank();
|
|
5972
6060
|
log.info("Examples:");
|
|
5973
|
-
log.command("
|
|
5974
|
-
log.command("
|
|
5975
|
-
log.command("
|
|
6061
|
+
log.command(cliCmd("sync --yes") + " # sync every detected agent");
|
|
6062
|
+
log.command(cliCmd("sync --agent claude --yes"));
|
|
6063
|
+
log.command(cliCmd("sync --agent claude,codex --yes"));
|
|
5976
6064
|
}
|
|
5977
6065
|
process.exitCode = 2;
|
|
5978
6066
|
return;
|
|
@@ -6016,12 +6104,12 @@ async function syncCommand(rawOpts = {}, deps = {}) {
|
|
|
6016
6104
|
reason: "changed_in_two_places",
|
|
6017
6105
|
libraryVersion: 0,
|
|
6018
6106
|
localPath: p.skillsDir,
|
|
6019
|
-
diffCommand: `
|
|
6020
|
-
pullCommand: `
|
|
6021
|
-
pushCommand:
|
|
6107
|
+
diffCommand: cliCmd(`diff ${slug}`),
|
|
6108
|
+
pullCommand: cliCmd(`pull ${slug} --agent ${p.agent}${p.scope === "project" ? " --project" : ""}`),
|
|
6109
|
+
pushCommand: cliCmd("push")
|
|
6022
6110
|
}))
|
|
6023
6111
|
})),
|
|
6024
|
-
next: ["
|
|
6112
|
+
next: [cliCmd("sync --all-agents --yes")]
|
|
6025
6113
|
});
|
|
6026
6114
|
process.exitCode = (wouldMutate || hasSkipped) && !flags.exitZero ? 1 : 0;
|
|
6027
6115
|
return;
|
|
@@ -6055,7 +6143,7 @@ async function syncCommand(rawOpts = {}, deps = {}) {
|
|
|
6055
6143
|
log.info("Everything is in sync.");
|
|
6056
6144
|
log.blank();
|
|
6057
6145
|
for (const p of plans) log.row([AGENT_LABELS[p.agent], "up to date"], [10]);
|
|
6058
|
-
printNext([{ command: "
|
|
6146
|
+
printNext([{ command: cliCmd("status") }]);
|
|
6059
6147
|
return;
|
|
6060
6148
|
}
|
|
6061
6149
|
if (defaultedToAll && detected.length > 1) {
|
|
@@ -6074,7 +6162,7 @@ async function syncCommand(rawOpts = {}, deps = {}) {
|
|
|
6074
6162
|
let toSync = actionable;
|
|
6075
6163
|
if (!flags.yes) {
|
|
6076
6164
|
if (!isInteractive()) {
|
|
6077
|
-
log.err("
|
|
6165
|
+
log.err(`${cliCmd("sync")} needs a choice but is not running in an interactive terminal.`);
|
|
6078
6166
|
log.info("Re-run with --agent <name> --yes, or --all-agents --yes.");
|
|
6079
6167
|
process.exitCode = 2;
|
|
6080
6168
|
return;
|
|
@@ -6124,7 +6212,7 @@ async function syncCommand(rawOpts = {}, deps = {}) {
|
|
|
6124
6212
|
log.info(` ${marker} ${formatAgentLabel(entry.plan)} ${entry.message}`);
|
|
6125
6213
|
}
|
|
6126
6214
|
log.blank();
|
|
6127
|
-
printNext([{ command: "
|
|
6215
|
+
printNext([{ command: cliCmd("status") }]);
|
|
6128
6216
|
if (hasSkipped) {
|
|
6129
6217
|
for (const p of plans) {
|
|
6130
6218
|
for (const slug of p.conflicts) {
|
|
@@ -6148,13 +6236,13 @@ async function syncCommand(rawOpts = {}, deps = {}) {
|
|
|
6148
6236
|
if (hasSkipped) {
|
|
6149
6237
|
const conflictSlug = plans.flatMap((p) => p.conflicts)[0];
|
|
6150
6238
|
printNext([
|
|
6151
|
-
...conflictSlug ? [{ command: `
|
|
6152
|
-
{ command: "
|
|
6239
|
+
...conflictSlug ? [{ command: cliCmd(`diff ${conflictSlug}`), description: "show what changed before choosing" }] : [],
|
|
6240
|
+
{ command: cliCmd("status") }
|
|
6153
6241
|
]);
|
|
6154
6242
|
} else {
|
|
6155
6243
|
log.blank();
|
|
6156
|
-
log.info(
|
|
6157
|
-
printNext([{ command: "
|
|
6244
|
+
log.info(`Backups of any replaced skills are in each agent's .floom/backups/ folder (run ${cliCmd("restore --list")} to see them).`);
|
|
6245
|
+
printNext([{ command: cliCmd("status") }]);
|
|
6158
6246
|
}
|
|
6159
6247
|
if (hasSkipped) process.exitCode = 1;
|
|
6160
6248
|
} finally {
|
|
@@ -6233,15 +6321,15 @@ async function statusCommand(rawOpts = {}) {
|
|
|
6233
6321
|
const detected = await detectAgents();
|
|
6234
6322
|
if (detected.length === 0) {
|
|
6235
6323
|
if (json) {
|
|
6236
|
-
emitJson({ workspace: { name: "Library", signedIn: false, librarySkillCount: null }, complete: true, agents: [], skillsNeedingAttention: [], next: ["
|
|
6324
|
+
emitJson({ workspace: { name: "Library", signedIn: false, librarySkillCount: null }, complete: true, agents: [], skillsNeedingAttention: [], next: [cliCmd("install <link>")] });
|
|
6237
6325
|
} else {
|
|
6238
6326
|
log.info("No AI agents found on this machine.");
|
|
6239
6327
|
log.blank();
|
|
6240
6328
|
log.info("Floom looks for Claude, Codex, Cursor, Gemini, or OpenCode.");
|
|
6241
|
-
log.info(
|
|
6329
|
+
log.info(`Install one of those agents, then run ${cliCmd("status")} again.`);
|
|
6242
6330
|
log.blank();
|
|
6243
6331
|
log.info("If someone sent you a shared skill:");
|
|
6244
|
-
log.command("
|
|
6332
|
+
log.command(cliCmd("install <link>"));
|
|
6245
6333
|
log.blank();
|
|
6246
6334
|
log.info("More help: https://floom.dev/docs#agents");
|
|
6247
6335
|
}
|
|
@@ -6257,7 +6345,7 @@ async function statusCommand(rawOpts = {}) {
|
|
|
6257
6345
|
complete: false,
|
|
6258
6346
|
agents: detected.map((agent) => ({ name: agent, detected: true, state: null, error: null, summary: emptySummary(), copies: [] })),
|
|
6259
6347
|
skillsNeedingAttention: [],
|
|
6260
|
-
next: ["
|
|
6348
|
+
next: [cliCmd("login")]
|
|
6261
6349
|
});
|
|
6262
6350
|
process.exitCode = 1;
|
|
6263
6351
|
return;
|
|
@@ -6271,7 +6359,7 @@ async function statusCommand(rawOpts = {}) {
|
|
|
6271
6359
|
log.blank();
|
|
6272
6360
|
log.info("Floom Library");
|
|
6273
6361
|
log.info(" Not signed in \u2014 can't compare with your team Library.");
|
|
6274
|
-
printNext([{ command: "
|
|
6362
|
+
printNext([{ command: cliCmd("login"), description: "see and sync your Library" }]);
|
|
6275
6363
|
return;
|
|
6276
6364
|
}
|
|
6277
6365
|
const me = await fetchMe().catch(() => null);
|
|
@@ -6327,7 +6415,7 @@ async function statusCommand(rawOpts = {}) {
|
|
|
6327
6415
|
};
|
|
6328
6416
|
}),
|
|
6329
6417
|
skillsNeedingAttention: buildAttention(checks),
|
|
6330
|
-
next: ["
|
|
6418
|
+
next: [cliCmd("sync"), cliCmd("status --verbose")]
|
|
6331
6419
|
});
|
|
6332
6420
|
process.exitCode = complete ? 0 : 1;
|
|
6333
6421
|
return;
|
|
@@ -6343,8 +6431,8 @@ async function statusCommand(rawOpts = {}) {
|
|
|
6343
6431
|
}
|
|
6344
6432
|
}
|
|
6345
6433
|
printNext([
|
|
6346
|
-
{ command: "
|
|
6347
|
-
{ command: "
|
|
6434
|
+
{ command: cliCmd("push"), description: "publish local changes" },
|
|
6435
|
+
{ command: cliCmd("pull"), description: "install Library updates" }
|
|
6348
6436
|
]);
|
|
6349
6437
|
return;
|
|
6350
6438
|
}
|
|
@@ -6353,7 +6441,7 @@ async function statusCommand(rawOpts = {}) {
|
|
|
6353
6441
|
if (attention.length === 0 && timedOut.length === 0) {
|
|
6354
6442
|
log.blank();
|
|
6355
6443
|
log.info(`All ${skillCount} skill${skillCount === 1 ? "" : "s"} are up to date in ${selectedAgents.map((a) => AGENT_LABELS[a]).join(", ")}.`);
|
|
6356
|
-
printNext([{ command: "
|
|
6444
|
+
printNext([{ command: cliCmd("push"), description: "if you just created a new skill locally" }]);
|
|
6357
6445
|
return;
|
|
6358
6446
|
}
|
|
6359
6447
|
log.heading("Skills needing attention");
|
|
@@ -6368,7 +6456,7 @@ async function statusCommand(rawOpts = {}) {
|
|
|
6368
6456
|
}
|
|
6369
6457
|
if (attention.length > shown.length) {
|
|
6370
6458
|
log.blank();
|
|
6371
|
-
log.info(`...and ${attention.length - shown.length} more \u2014 run
|
|
6459
|
+
log.info(`...and ${attention.length - shown.length} more \u2014 run ${cliCmd("status --verbose")}.`);
|
|
6372
6460
|
}
|
|
6373
6461
|
const upToDate = checks.flatMap((c) => c.skills).filter((s) => s.state === "up_to_date").length;
|
|
6374
6462
|
if (upToDate > 0) {
|
|
@@ -6379,15 +6467,15 @@ async function statusCommand(rawOpts = {}) {
|
|
|
6379
6467
|
if (timedOut.length > 0) {
|
|
6380
6468
|
log.blank();
|
|
6381
6469
|
log.info("Status summary");
|
|
6382
|
-
log.info(` ${timedOut.length} agent${timedOut.length === 1 ? "" : "s"} timed out \u2014 run
|
|
6470
|
+
log.info(` ${timedOut.length} agent${timedOut.length === 1 ? "" : "s"} timed out \u2014 run ${cliCmd(`status --agent ${timedOut[0].agent}`)} to retry.`);
|
|
6383
6471
|
}
|
|
6384
6472
|
const hasLocalUnpublished = attention.some((a) => a.locations.some((l) => l.state === "not_in_library_never_published" || l.state === "local_changes"));
|
|
6385
6473
|
const hasUpdates = attention.some((a) => a.locations.some((l) => l.state === "update_available"));
|
|
6386
6474
|
const next = [];
|
|
6387
|
-
if (hasLocalUnpublished && hasUpdates) next.push({ command: "
|
|
6388
|
-
if (hasLocalUnpublished) next.push({ command: "
|
|
6389
|
-
if (hasUpdates) next.push({ command: "
|
|
6390
|
-
next.push({ command: "
|
|
6475
|
+
if (hasLocalUnpublished && hasUpdates) next.push({ command: cliCmd("sync"), description: "review local changes and Library updates together" });
|
|
6476
|
+
if (hasLocalUnpublished) next.push({ command: cliCmd("push"), description: "publish local skills to the Library" });
|
|
6477
|
+
if (hasUpdates) next.push({ command: cliCmd("pull"), description: "install Library updates" });
|
|
6478
|
+
next.push({ command: cliCmd("status --verbose"), description: "see every skill" });
|
|
6391
6479
|
printNext(next);
|
|
6392
6480
|
if (timedOut.length > 0) process.exitCode = 1;
|
|
6393
6481
|
}
|
|
@@ -6482,7 +6570,7 @@ var MACHINE_FILE3 = join11(CONFIG_DIR3, "machine.json");
|
|
|
6482
6570
|
async function renameDeviceCommand(newLabel) {
|
|
6483
6571
|
const trimmed = (newLabel ?? "").trim().slice(0, 80);
|
|
6484
6572
|
if (!trimmed) {
|
|
6485
|
-
log.err(
|
|
6573
|
+
log.err(`Device name cannot be empty. Use: ${cliCmd('rename-device "Work Laptop"')}`);
|
|
6486
6574
|
process.exitCode = 2;
|
|
6487
6575
|
return;
|
|
6488
6576
|
}
|
|
@@ -6503,8 +6591,8 @@ async function renameDeviceCommand(newLabel) {
|
|
|
6503
6591
|
log.blank();
|
|
6504
6592
|
log.info("Server sync will happen after your next login.");
|
|
6505
6593
|
printNext([
|
|
6506
|
-
{ command: "
|
|
6507
|
-
{ command: "
|
|
6594
|
+
{ command: cliCmd("login"), description: "sync the device name to your workspace" },
|
|
6595
|
+
{ command: cliCmd("account"), description: "see account details including device name" }
|
|
6508
6596
|
]);
|
|
6509
6597
|
return;
|
|
6510
6598
|
}
|
|
@@ -6521,7 +6609,7 @@ async function renameDeviceCommand(newLabel) {
|
|
|
6521
6609
|
}
|
|
6522
6610
|
log.blank();
|
|
6523
6611
|
log.info("This name appears in your workspace when teammates look at which machine synced last.");
|
|
6524
|
-
printNext([{ command: "
|
|
6612
|
+
printNext([{ command: cliCmd("account"), description: "see account details including device name" }]);
|
|
6525
6613
|
}
|
|
6526
6614
|
|
|
6527
6615
|
// src/commands/install.ts
|
|
@@ -6698,7 +6786,7 @@ async function installCommand(input, rawOpts = {}) {
|
|
|
6698
6786
|
const detected = await detectAgents();
|
|
6699
6787
|
if (detected.length === 0) {
|
|
6700
6788
|
if (json) {
|
|
6701
|
-
emitJson({ share: shareInfo(shareData), mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], next: ["
|
|
6789
|
+
emitJson({ share: shareInfo(shareData), mode: "plan", applied: false, wouldMutate: false, hasSkipped: false, agents: [], next: [cliCmd("install <link> --agent claude")] });
|
|
6702
6790
|
} else {
|
|
6703
6791
|
log.heading("Shared skill");
|
|
6704
6792
|
log.info(` ${shareData.skill.title} ${shareData.skill.version.display}`);
|
|
@@ -6719,16 +6807,16 @@ async function installCommand(input, rawOpts = {}) {
|
|
|
6719
6807
|
if (!planMode && selectedAgents.length === 0) {
|
|
6720
6808
|
if (!isInteractive()) {
|
|
6721
6809
|
if (json) {
|
|
6722
|
-
emitJson({ share: shareInfo(shareData), error: "
|
|
6810
|
+
emitJson({ share: shareInfo(shareData), error: `${cliCmd("install")} needs to know where to install`, next: [cliCmd("install <link> --all-agents --yes")] });
|
|
6723
6811
|
} else {
|
|
6724
|
-
log.err("
|
|
6812
|
+
log.err(`${cliCmd("install")} needs to know where to install.`);
|
|
6725
6813
|
log.blank();
|
|
6726
6814
|
log.info("Pass --agent <name>, repeat --agent, or use --all-agents.");
|
|
6727
6815
|
log.blank();
|
|
6728
6816
|
log.info("Examples:");
|
|
6729
|
-
log.command("
|
|
6730
|
-
log.command("
|
|
6731
|
-
log.command("
|
|
6817
|
+
log.command(cliCmd("install <link> --agent claude --yes"));
|
|
6818
|
+
log.command(cliCmd("install <link> --agent claude,codex --yes"));
|
|
6819
|
+
log.command(cliCmd("install <link> --all-agents --yes"));
|
|
6732
6820
|
}
|
|
6733
6821
|
process.exitCode = 2;
|
|
6734
6822
|
return;
|
|
@@ -6797,7 +6885,7 @@ async function installCommand(input, rawOpts = {}) {
|
|
|
6797
6885
|
wouldMutate: planAgents.length > 0,
|
|
6798
6886
|
hasSkipped: false,
|
|
6799
6887
|
agents: planAgents,
|
|
6800
|
-
next: ["
|
|
6888
|
+
next: [cliCmd("install <link> --all-agents --yes")]
|
|
6801
6889
|
});
|
|
6802
6890
|
} else {
|
|
6803
6891
|
log.heading("Install plan");
|
|
@@ -6858,7 +6946,7 @@ async function installCommand(input, rawOpts = {}) {
|
|
|
6858
6946
|
backupPath: null,
|
|
6859
6947
|
message: r.message
|
|
6860
6948
|
})),
|
|
6861
|
-
next: ["
|
|
6949
|
+
next: [cliCmd("status")]
|
|
6862
6950
|
});
|
|
6863
6951
|
} else {
|
|
6864
6952
|
printNext([{ command: `Open your agent and ask it to use "${shareData.skill.title}".` }]);
|
|
@@ -6999,10 +7087,10 @@ async function newCommand(name, rawOpts = {}) {
|
|
|
6999
7087
|
const json = flags.json;
|
|
7000
7088
|
if (flags.allScopes) {
|
|
7001
7089
|
if (json) {
|
|
7002
|
-
emitJson({ skill: { slug: name, name: deriveTitle(name) }, mode: "plan", applied: false, wouldMutate: false, scope: null, path: null, action: "refused", reason: "needs_agent_choice", message: "
|
|
7090
|
+
emitJson({ skill: { slug: name, name: deriveTitle(name) }, mode: "plan", applied: false, wouldMutate: false, scope: null, path: null, action: "refused", reason: "needs_agent_choice", message: `${cliCmd("new")} creates one skill in one location; --all-scopes does not apply.`, next: [cliCmd(`new ${name} --agent claude`)] });
|
|
7003
7091
|
} else {
|
|
7004
|
-
log.err("
|
|
7005
|
-
printNext([{ command: `
|
|
7092
|
+
log.err(`${cliCmd("new")} creates one skill in one location; --all-scopes does not apply.`);
|
|
7093
|
+
printNext([{ command: cliCmd(`new ${name} --agent claude`) }]);
|
|
7006
7094
|
}
|
|
7007
7095
|
process.exitCode = 2;
|
|
7008
7096
|
return;
|
|
@@ -7015,7 +7103,7 @@ async function newCommand(name, rawOpts = {}) {
|
|
|
7015
7103
|
log.blank();
|
|
7016
7104
|
log.info("Skill names use lowercase letters, numbers, and hyphens only.");
|
|
7017
7105
|
log.info("Use:");
|
|
7018
|
-
log.command(`
|
|
7106
|
+
log.command(cliCmd(`new ${name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "")}`));
|
|
7019
7107
|
}
|
|
7020
7108
|
process.exitCode = 2;
|
|
7021
7109
|
return;
|
|
@@ -7032,8 +7120,8 @@ async function newCommand(name, rawOpts = {}) {
|
|
|
7032
7120
|
log.info(`Floom will not create a nested skill inside ${tildePath(cwd)}.`);
|
|
7033
7121
|
printNext([
|
|
7034
7122
|
{ command: "cd ~/.claude/skills" },
|
|
7035
|
-
{ command: `
|
|
7036
|
-
{ command: `
|
|
7123
|
+
{ command: cliCmd(`new ${name}`) },
|
|
7124
|
+
{ command: cliCmd(`new ${name} --agent claude`) }
|
|
7037
7125
|
]);
|
|
7038
7126
|
}
|
|
7039
7127
|
process.exitCode = 2;
|
|
@@ -7066,7 +7154,7 @@ async function newCommand(name, rawOpts = {}) {
|
|
|
7066
7154
|
usesScannedDir = false;
|
|
7067
7155
|
} else if (!isInteractive() || json) {
|
|
7068
7156
|
if (json) {
|
|
7069
|
-
emitJson({ skill: { slug: name, name: title }, mode: "plan", applied: false, wouldMutate: true, scope: null, path: null, action: "refused", reason: "needs_agent_choice", message: "
|
|
7157
|
+
emitJson({ skill: { slug: name, name: title }, mode: "plan", applied: false, wouldMutate: true, scope: null, path: null, action: "refused", reason: "needs_agent_choice", message: `${cliCmd("new")} needs to know where to create the skill. Pass --agent <name>.`, next: [cliCmd(`new ${name} --agent ${detected[0]}`)] });
|
|
7070
7158
|
process.exitCode = 2;
|
|
7071
7159
|
return;
|
|
7072
7160
|
}
|
|
@@ -7075,8 +7163,8 @@ async function newCommand(name, rawOpts = {}) {
|
|
|
7075
7163
|
scope = "global";
|
|
7076
7164
|
usesScannedDir = true;
|
|
7077
7165
|
} else {
|
|
7078
|
-
log.err("
|
|
7079
|
-
printNext([{ command: `
|
|
7166
|
+
log.err(`${cliCmd("new")} needs to know where to create the skill.`);
|
|
7167
|
+
printNext([{ command: cliCmd(`new ${name} --agent ${detected[0]}`) }]);
|
|
7080
7168
|
process.exitCode = 2;
|
|
7081
7169
|
return;
|
|
7082
7170
|
}
|
|
@@ -7109,7 +7197,7 @@ async function newCommand(name, rawOpts = {}) {
|
|
|
7109
7197
|
}
|
|
7110
7198
|
if (flags.dryRun || json && !flags.yes) {
|
|
7111
7199
|
if (json) {
|
|
7112
|
-
emitJson({ skill: { slug: name, name: title }, mode: "plan", applied: false, wouldMutate: true, scope, path: skillFolder, action: "create", reason: null, message: `${name} would be created`, next: [usesScannedDir ? "
|
|
7200
|
+
emitJson({ skill: { slug: name, name: title }, mode: "plan", applied: false, wouldMutate: true, scope, path: skillFolder, action: "create", reason: null, message: `${name} would be created`, next: [usesScannedDir ? cliCmd("push") : cliCmd(`push ${skillFolder}`)] });
|
|
7113
7201
|
} else {
|
|
7114
7202
|
log.heading("Create plan");
|
|
7115
7203
|
log.blank();
|
|
@@ -7130,7 +7218,7 @@ async function newCommand(name, rawOpts = {}) {
|
|
|
7130
7218
|
await mkdir7(skillFolder, { recursive: true });
|
|
7131
7219
|
await writeFile6(skillMd, skillTemplate(title));
|
|
7132
7220
|
await writeFile6(join13(skillFolder, ".floomignore"), FLOOMIGNORE);
|
|
7133
|
-
const pushCmd = usesScannedDir ? "
|
|
7221
|
+
const pushCmd = usesScannedDir ? cliCmd("push") : cliCmd(`push ${tildePath(skillFolder)}`);
|
|
7134
7222
|
if (json) {
|
|
7135
7223
|
emitJson({ skill: { slug: name, name: title }, mode: "apply", applied: true, scope, path: skillFolder, result: "created", reason: null, message: `Created ${skillFolder}`, next: [pushCmd] });
|
|
7136
7224
|
return;
|
|
@@ -7141,7 +7229,7 @@ async function newCommand(name, rawOpts = {}) {
|
|
|
7141
7229
|
if (!usesScannedDir) {
|
|
7142
7230
|
log.blank();
|
|
7143
7231
|
log.info("No AI agents were detected, so Floom created the skill in this folder.");
|
|
7144
|
-
log.info(
|
|
7232
|
+
log.info(`Zero-arg \`${cliCmd("push")}\` will not find this folder until an agent is installed.`);
|
|
7145
7233
|
next.push({ command: `Edit ${tildePath(skillMd)}` });
|
|
7146
7234
|
} else {
|
|
7147
7235
|
next.push({ command: `Edit ${tildePath(skillMd)}` });
|
|
@@ -7202,20 +7290,20 @@ async function doctorCommand(opts = {}) {
|
|
|
7202
7290
|
const auth = await readAuth();
|
|
7203
7291
|
let authOk = false;
|
|
7204
7292
|
if (!auth) {
|
|
7205
|
-
checks.push({ name: "auth", status: "fail", message: "not signed in", fix: "
|
|
7293
|
+
checks.push({ name: "auth", status: "fail", message: "not signed in", fix: cliCmd("login") });
|
|
7206
7294
|
} else {
|
|
7207
7295
|
try {
|
|
7208
7296
|
const me = await fetchMe();
|
|
7209
7297
|
authOk = true;
|
|
7210
7298
|
checks.push({ name: "auth", status: "pass", message: `signed in as ${me.user.email}`, fix: null });
|
|
7211
7299
|
} catch {
|
|
7212
|
-
checks.push({ name: "auth", status: "fail", message: "session expired", fix: "
|
|
7300
|
+
checks.push({ name: "auth", status: "fail", message: "session expired", fix: cliCmd("login") });
|
|
7213
7301
|
}
|
|
7214
7302
|
}
|
|
7215
7303
|
if (authOk) {
|
|
7216
7304
|
checks.push({ name: "api", status: "pass", message: "https://floom.dev reachable", fix: null });
|
|
7217
7305
|
} else {
|
|
7218
|
-
checks.push({ name: "api", status: "warn", message: "skipped \u2014 sign in first", fix: "
|
|
7306
|
+
checks.push({ name: "api", status: "warn", message: "skipped \u2014 sign in first", fix: cliCmd("login") });
|
|
7219
7307
|
}
|
|
7220
7308
|
const agents = await detectAgents();
|
|
7221
7309
|
if (agents.length === 0) {
|
|
@@ -7238,7 +7326,7 @@ async function doctorCommand(opts = {}) {
|
|
|
7238
7326
|
}
|
|
7239
7327
|
}
|
|
7240
7328
|
if (manifestCorrupt && corruptAgent) {
|
|
7241
|
-
const fix = authOk ? `
|
|
7329
|
+
const fix = authOk ? cliCmd(`sync --dry-run --agent ${corruptAgent}`) : cliCmd("login");
|
|
7242
7330
|
checks.push({
|
|
7243
7331
|
name: "manifests",
|
|
7244
7332
|
status: "fail",
|
|
@@ -7273,7 +7361,7 @@ async function doctorCommand(opts = {}) {
|
|
|
7273
7361
|
name: "backups",
|
|
7274
7362
|
status: "warn",
|
|
7275
7363
|
message: `${formatBytes(backupBytes)} across detected agents`,
|
|
7276
|
-
fix: "
|
|
7364
|
+
fix: cliCmd("restore --list")
|
|
7277
7365
|
});
|
|
7278
7366
|
} else {
|
|
7279
7367
|
checks.push({ name: "backups", status: "pass", message: `${formatBytes(backupBytes)} across ${agents.length} agent${agents.length === 1 ? "" : "s"}`, fix: null });
|
|
@@ -7293,7 +7381,7 @@ async function doctorCommand(opts = {}) {
|
|
|
7293
7381
|
emitJson({
|
|
7294
7382
|
ok,
|
|
7295
7383
|
checks,
|
|
7296
|
-
next: failures.length > 0 ? failures.map((c) => c.fix).filter((f) => Boolean(f)) : ["
|
|
7384
|
+
next: failures.length > 0 ? failures.map((c) => c.fix).filter((f) => Boolean(f)) : [cliCmd("status")]
|
|
7297
7385
|
});
|
|
7298
7386
|
process.exitCode = ok ? 0 : 1;
|
|
7299
7387
|
return;
|
|
@@ -7323,13 +7411,13 @@ async function doctorCommand(opts = {}) {
|
|
|
7323
7411
|
if (authFail && manifestCorrupt) {
|
|
7324
7412
|
log.blank();
|
|
7325
7413
|
log.info("Fix in this order:");
|
|
7326
|
-
log.command("
|
|
7327
|
-
if (corruptAgent) log.command(`
|
|
7414
|
+
log.command(cliCmd("login"));
|
|
7415
|
+
if (corruptAgent) log.command(cliCmd(`sync --dry-run --agent ${corruptAgent}`));
|
|
7328
7416
|
} else {
|
|
7329
|
-
printNext(failures.map((c) => ({ command: c.fix ?? "
|
|
7417
|
+
printNext(failures.map((c) => ({ command: c.fix ?? cliCmd("status") })));
|
|
7330
7418
|
}
|
|
7331
7419
|
} else {
|
|
7332
|
-
printNext([{ command: "
|
|
7420
|
+
printNext([{ command: cliCmd("status") }]);
|
|
7333
7421
|
}
|
|
7334
7422
|
process.exitCode = ok ? 0 : 1;
|
|
7335
7423
|
}
|
|
@@ -7438,7 +7526,7 @@ async function diffCommand(skill, opts = {}) {
|
|
|
7438
7526
|
lastSyncedHash: null,
|
|
7439
7527
|
files: files.map((f) => ({ path: f, status: "added", binary: false }))
|
|
7440
7528
|
}],
|
|
7441
|
-
next: [`
|
|
7529
|
+
next: [cliCmd(`push ${copy.path}`)]
|
|
7442
7530
|
});
|
|
7443
7531
|
return;
|
|
7444
7532
|
}
|
|
@@ -7450,7 +7538,7 @@ async function diffCommand(skill, opts = {}) {
|
|
|
7450
7538
|
log.blank();
|
|
7451
7539
|
log.info("Baseline: empty");
|
|
7452
7540
|
log.info(`Local copy: ${tildePath(copy.path)}`);
|
|
7453
|
-
printNext([{ command: `
|
|
7541
|
+
printNext([{ command: cliCmd(`push ${tildePath(copy.path)}`), description: `publish ${skill} as v1` }]);
|
|
7454
7542
|
return;
|
|
7455
7543
|
}
|
|
7456
7544
|
const localHash = await skillContentHash(copy.path) ?? "";
|
|
@@ -7469,7 +7557,7 @@ async function diffCommand(skill, opts = {}) {
|
|
|
7469
7557
|
lastSyncedHash: entry.hash,
|
|
7470
7558
|
files: []
|
|
7471
7559
|
}],
|
|
7472
|
-
next: ["
|
|
7560
|
+
next: [cliCmd("status")]
|
|
7473
7561
|
});
|
|
7474
7562
|
return;
|
|
7475
7563
|
}
|
|
@@ -7479,17 +7567,17 @@ async function diffCommand(skill, opts = {}) {
|
|
|
7479
7567
|
log.info(`Last synced at v${entry.version_seq}.`);
|
|
7480
7568
|
log.info(`Local copy: ${tildePath(copy.path)}`);
|
|
7481
7569
|
log.blank();
|
|
7482
|
-
log.info(
|
|
7483
|
-
printNext([{ command: "
|
|
7570
|
+
log.info(`Run ${cliCmd("status")} when back online for a full file-by-file comparison.`);
|
|
7571
|
+
printNext([{ command: cliCmd("status") }]);
|
|
7484
7572
|
return;
|
|
7485
7573
|
}
|
|
7486
7574
|
if (copies.length === 0) {
|
|
7487
7575
|
if (json) {
|
|
7488
|
-
emitJson({ skill, agent: agentArg ?? null, source: "library", library: { available: libraryAvailable, version: libraryVersion, hash: null }, agents: [], next: libraryAvailable ? ["
|
|
7576
|
+
emitJson({ skill, agent: agentArg ?? null, source: "library", library: { available: libraryAvailable, version: libraryVersion, hash: null }, agents: [], next: libraryAvailable ? [cliCmd("pull")] : [] });
|
|
7489
7577
|
} else {
|
|
7490
7578
|
log.info(`No local copy of ${skill} found on this machine.`);
|
|
7491
7579
|
if (libraryAvailable) {
|
|
7492
|
-
printNext([{ command: "
|
|
7580
|
+
printNext([{ command: cliCmd("pull"), description: `install ${skill} from the Library` }]);
|
|
7493
7581
|
} else {
|
|
7494
7582
|
log.err(`${skill} is not in the Library and has no local copy.`);
|
|
7495
7583
|
}
|
|
@@ -7545,7 +7633,7 @@ async function diffCommand(skill, opts = {}) {
|
|
|
7545
7633
|
source: libraryAvailable ? "library" : "last_synced_manifest",
|
|
7546
7634
|
library: { available: libraryAvailable, version: libraryVersion, hash: null },
|
|
7547
7635
|
agents: agentResults,
|
|
7548
|
-
next: ["
|
|
7636
|
+
next: [cliCmd("status")]
|
|
7549
7637
|
});
|
|
7550
7638
|
return;
|
|
7551
7639
|
}
|
|
@@ -7574,10 +7662,10 @@ async function diffCommand(skill, opts = {}) {
|
|
|
7574
7662
|
const conflict = agentResults.find((r) => r.state === "changed_in_two_places");
|
|
7575
7663
|
const next = [];
|
|
7576
7664
|
if (conflict) {
|
|
7577
|
-
next.push({ command: `
|
|
7578
|
-
next.push({ command: `
|
|
7665
|
+
next.push({ command: cliCmd(`pull ${skill} --agent ${conflict.agent}${conflict.scope === "project" ? " --project" : ""}`), description: "use the Library version (your local copy is backed up first)" });
|
|
7666
|
+
next.push({ command: cliCmd(`push ${tildePath(conflict.path)}`), description: "publish your local version" });
|
|
7579
7667
|
} else {
|
|
7580
|
-
next.push({ command: "
|
|
7668
|
+
next.push({ command: cliCmd("status") });
|
|
7581
7669
|
}
|
|
7582
7670
|
printNext(next);
|
|
7583
7671
|
void sep6;
|
|
@@ -7671,7 +7759,7 @@ async function restoreCommand(skill, opts = {}) {
|
|
|
7671
7759
|
wouldMutate: false,
|
|
7672
7760
|
selectedBackup: null,
|
|
7673
7761
|
message: `${all.length} backups available`,
|
|
7674
|
-
next: skill ? [`
|
|
7762
|
+
next: skill ? [cliCmd(`restore ${skill} --agent ${detected[0] ?? "claude"}`)] : []
|
|
7675
7763
|
});
|
|
7676
7764
|
return;
|
|
7677
7765
|
}
|
|
@@ -7693,12 +7781,12 @@ async function restoreCommand(skill, opts = {}) {
|
|
|
7693
7781
|
log.info(` ${b.skill.padEnd(16)}${b.timestamp.padEnd(24)}${tildePath(b.path)}`);
|
|
7694
7782
|
}
|
|
7695
7783
|
if (all[0]) {
|
|
7696
|
-
printNext([{ command: `
|
|
7784
|
+
printNext([{ command: cliCmd(`restore ${all[0].skill} --agent ${all[0].agent}`) }]);
|
|
7697
7785
|
}
|
|
7698
7786
|
return;
|
|
7699
7787
|
}
|
|
7700
7788
|
if (!skill) {
|
|
7701
|
-
if (!json) log.err("
|
|
7789
|
+
if (!json) log.err(`${cliCmd("restore")} needs a skill name, or use ${cliCmd("restore --list")} to see backups.`);
|
|
7702
7790
|
process.exitCode = 2;
|
|
7703
7791
|
return;
|
|
7704
7792
|
}
|
|
@@ -7716,10 +7804,10 @@ async function restoreCommand(skill, opts = {}) {
|
|
|
7716
7804
|
const backups = (await listBackups(searchAgents)).filter((b) => b.skill === skill);
|
|
7717
7805
|
if (backups.length === 0) {
|
|
7718
7806
|
if (json) {
|
|
7719
|
-
emitJson({ mode: "plan", applied: false, skill: { slug: skill }, agent, scope: null, availableBackups: [], wouldMutate: false, selectedBackup: null, message: `No backups found for ${skill}.`, next: ["
|
|
7807
|
+
emitJson({ mode: "plan", applied: false, skill: { slug: skill }, agent, scope: null, availableBackups: [], wouldMutate: false, selectedBackup: null, message: `No backups found for ${skill}.`, next: [cliCmd("restore --list")] });
|
|
7720
7808
|
} else {
|
|
7721
7809
|
log.err(`No backups found for ${skill}.`);
|
|
7722
|
-
printNext([{ command: "
|
|
7810
|
+
printNext([{ command: cliCmd("restore --list") }]);
|
|
7723
7811
|
}
|
|
7724
7812
|
process.exitCode = 2;
|
|
7725
7813
|
return;
|
|
@@ -7744,7 +7832,7 @@ async function restoreCommand(skill, opts = {}) {
|
|
|
7744
7832
|
}
|
|
7745
7833
|
}
|
|
7746
7834
|
if (opts.yes && !opts.dryRun && !agentArg && !isInteractive()) {
|
|
7747
|
-
if (!json) log.err("
|
|
7835
|
+
if (!json) log.err(`${cliCmd("restore")} needs --agent <name> --backup <timestamp> --yes for non-interactive restore.`);
|
|
7748
7836
|
process.exitCode = 2;
|
|
7749
7837
|
return;
|
|
7750
7838
|
}
|
|
@@ -7764,7 +7852,7 @@ async function restoreCommand(skill, opts = {}) {
|
|
|
7764
7852
|
wouldMutate: true,
|
|
7765
7853
|
selectedBackup: selected?.timestamp ?? null,
|
|
7766
7854
|
message: `${skill} would be restored from ${chosen.timestamp}`,
|
|
7767
|
-
next: [`
|
|
7855
|
+
next: [cliCmd(`restore ${skill} --agent ${agent} --backup ${chosen.timestamp} --yes`)]
|
|
7768
7856
|
});
|
|
7769
7857
|
} else {
|
|
7770
7858
|
log.heading("Restore plan");
|
|
@@ -7794,8 +7882,8 @@ async function restoreCommand(skill, opts = {}) {
|
|
|
7794
7882
|
}
|
|
7795
7883
|
selected = agentBackups[choice - 1];
|
|
7796
7884
|
} else {
|
|
7797
|
-
log.err(
|
|
7798
|
-
log.info(
|
|
7885
|
+
log.err(`${cliCmd("restore")} needs a backup timestamp. Pass --backup <timestamp>, or run interactively.`);
|
|
7886
|
+
log.info(`Run ${cliCmd("restore --list")} to see available backups.`);
|
|
7799
7887
|
process.exitCode = 2;
|
|
7800
7888
|
return;
|
|
7801
7889
|
}
|
|
@@ -7839,15 +7927,15 @@ async function restoreCommand(skill, opts = {}) {
|
|
|
7839
7927
|
preRestoreBackupPath: preRestoreBackup,
|
|
7840
7928
|
result: "restored",
|
|
7841
7929
|
message: `Restored ${skill} from ${selected.timestamp}`,
|
|
7842
|
-
next: [`
|
|
7930
|
+
next: [cliCmd(`status --agent ${agent}`)]
|
|
7843
7931
|
});
|
|
7844
7932
|
return;
|
|
7845
7933
|
}
|
|
7846
7934
|
log.ok(`Restored ${skill} from ${selected.timestamp}`);
|
|
7847
|
-
printNext([{ command: `
|
|
7935
|
+
printNext([{ command: cliCmd(`status --agent ${agent}`) }]);
|
|
7848
7936
|
} catch (error) {
|
|
7849
7937
|
if (json) {
|
|
7850
|
-
emitJson({ mode: "apply", applied: true, skill: { slug: skill }, agent, scope: selected.scope, backup: selected.timestamp, preRestoreBackupPath: null, result: "failed", message: error.message, next: ["
|
|
7938
|
+
emitJson({ mode: "apply", applied: true, skill: { slug: skill }, agent, scope: selected.scope, backup: selected.timestamp, preRestoreBackupPath: null, result: "failed", message: error.message, next: [cliCmd("restore --list")] });
|
|
7851
7939
|
} else {
|
|
7852
7940
|
log.err(error.message);
|
|
7853
7941
|
}
|
|
@@ -7873,7 +7961,7 @@ async function dashboardCommand() {
|
|
|
7873
7961
|
}
|
|
7874
7962
|
log.blank();
|
|
7875
7963
|
log.info("Not signed in \u2014 can't compare with your team Library.");
|
|
7876
|
-
printNext([{ command: "
|
|
7964
|
+
printNext([{ command: cliCmd("login"), description: "sign in to see and sync your Library" }]);
|
|
7877
7965
|
return;
|
|
7878
7966
|
}
|
|
7879
7967
|
const me = await fetchMe().catch(() => null);
|
|
@@ -7943,17 +8031,17 @@ async function dashboardCommand() {
|
|
|
7943
8031
|
}
|
|
7944
8032
|
const next = [];
|
|
7945
8033
|
if (anyLocalChanges && anyUpdates) {
|
|
7946
|
-
next.push({ command: "
|
|
7947
|
-
next.push({ command: "
|
|
7948
|
-
next.push({ command: "
|
|
8034
|
+
next.push({ command: cliCmd("sync"), description: "review local changes and Library updates together" });
|
|
8035
|
+
next.push({ command: cliCmd("push"), description: "publish local skills to the Library" });
|
|
8036
|
+
next.push({ command: cliCmd("pull"), description: "update agent copies from the Library" });
|
|
7949
8037
|
} else if (anyUpdates) {
|
|
7950
|
-
next.push({ command: "
|
|
8038
|
+
next.push({ command: cliCmd("pull"), description: "update agent copies from the Library" });
|
|
7951
8039
|
} else if (anyLocalChanges) {
|
|
7952
|
-
next.push({ command: "
|
|
8040
|
+
next.push({ command: cliCmd("push"), description: "publish local skills to the Library" });
|
|
7953
8041
|
} else if (!libResult.ok) {
|
|
7954
|
-
next.push({ command: "
|
|
8042
|
+
next.push({ command: cliCmd("status"), description: "retry full comparison" });
|
|
7955
8043
|
}
|
|
7956
|
-
next.push({ command: "
|
|
8044
|
+
next.push({ command: cliCmd("status"), description: "see full detail" });
|
|
7957
8045
|
printNext(next);
|
|
7958
8046
|
void [];
|
|
7959
8047
|
}
|