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