slidemind 0.2.2 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +8 -1
- package/AGENTS.md +57 -11
- package/README.md +78 -34
- package/assets/app-icon.png +0 -0
- package/bin/slidemind.mjs +328 -100
- package/package.json +2 -1
- package/skills/create-slides/SKILL.md +15 -15
- package/skills/search/SKILL.md +6 -6
- package/src/bridge/client.mjs +11 -10
- package/src/bridge/config.mjs +2 -2
- package/src/bridge/{pair.mjs → http-json.mjs} +9 -24
- package/src/bridge/setup-claim.mjs +127 -0
- package/src/installers/claude.mjs +24 -0
- package/src/installers/codex.mjs +60 -4
- package/src/installers/lib.mjs +49 -19
- package/src/installers/opencode.mjs +16 -0
- package/src/installers/tui.mjs +21 -10
package/bin/slidemind.mjs
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
// slidemind — single CLI entry point for the slidemind npm package.
|
|
3
3
|
//
|
|
4
4
|
// Subcommands:
|
|
5
|
+
// slidemind setup <claim> one-command onboarding from the add-in's
|
|
6
|
+
// "Connect CLI" button: connect + global
|
|
7
|
+
// install + skills + ready (run via npx)
|
|
5
8
|
// slidemind init [--for <target>] set up slidemind in a coding harness
|
|
6
9
|
// (deploy the skill);
|
|
7
10
|
// targets: claude | codex | opencode | all
|
|
@@ -52,8 +55,17 @@ function printHelp() {
|
|
|
52
55
|
`slidemind ${pkg.version} — drive the slidemind PowerPoint add-in to build native, on-brand decks\n` +
|
|
53
56
|
`\n` +
|
|
54
57
|
`Usage:\n` +
|
|
58
|
+
` slidemind setup <claim> One-command onboarding. Copy the command from PowerPoint →\n` +
|
|
59
|
+
` slidemind → Settings → General → Connect CLI and paste it:\n` +
|
|
60
|
+
` it connects this machine, installs slidemind globally, deploys\n` +
|
|
61
|
+
` the skills, and runs 'ready'. Designed for 'npx slidemind@latest'.\n` +
|
|
62
|
+
` --for <target> harness to deploy skills into (default: auto — every\n` +
|
|
63
|
+
` coding agent found on this machine; none to skip)\n` +
|
|
64
|
+
` --no-install skip the global npm install (already installed)\n` +
|
|
65
|
+
` --no-ready skip the closing readiness check\n` +
|
|
66
|
+
` --json machine-readable result\n` +
|
|
55
67
|
` slidemind ready [deck] One-command session setup — run this FIRST each session.\n` +
|
|
56
|
-
` Verifies
|
|
68
|
+
` Verifies authorization/connection/CLI version, resolves the open\n` +
|
|
57
69
|
` design, materializes its bundle, and (with a deck) binds it.\n` +
|
|
58
70
|
` Exit 0 = ready; non-zero prints one actionable blocker.\n` +
|
|
59
71
|
` --to <presId> bind to a specific open window\n` +
|
|
@@ -132,7 +144,7 @@ function printHelp() {
|
|
|
132
144
|
` --force re-download even if the cached bundle is current\n` +
|
|
133
145
|
` slidemind presentations List active PowerPoint panes registered with the add-in\n` +
|
|
134
146
|
` --all include retained suspended entries\n` +
|
|
135
|
-
` slidemind doctor Diagnose CLI/add-in
|
|
147
|
+
` slidemind doctor Diagnose CLI/add-in authorization, backend, and pane readiness\n` +
|
|
136
148
|
` --json print machine-readable status\n` +
|
|
137
149
|
` slidemind read [presId|file.pptx] Pull the content of an open presentation (requires a host),\n` +
|
|
138
150
|
` or of a local .pptx path (read host-free from disk)\n` +
|
|
@@ -144,13 +156,12 @@ function printHelp() {
|
|
|
144
156
|
` --at <index> insert after slide N (0-based); default = append\n` +
|
|
145
157
|
` --replace delete existing slides first (destructive)\n` +
|
|
146
158
|
` --slides <ids> comma-separated source slide ids to push\n` +
|
|
147
|
-
` slidemind
|
|
148
|
-
` --label <name> name this environment (default: derived from URL)\n` +
|
|
149
|
-
` Pair once per environment; commands fan out across all of them.\n` +
|
|
150
|
-
` slidemind whoami Show every paired environment (* = default)\n` +
|
|
159
|
+
` slidemind whoami Show every connected environment (* = default)\n` +
|
|
151
160
|
` slidemind use <url|env> Set the default environment\n` +
|
|
152
161
|
` slidemind logout [url|env] Unpair one environment (--all to unpair every one)\n` +
|
|
153
162
|
` slidemind init --for <t> Set up slidemind in a harness (deploys the skill; clears the bundle cache)\n` +
|
|
163
|
+
` <t> = claude | codex | opencode | all | auto (every harness found here)\n` +
|
|
164
|
+
` 'setup' already does this — only needed after installing a new agent\n` +
|
|
154
165
|
` slidemind uninstall --for <t> Remove a previously-installed skill (clears the bundle cache)\n` +
|
|
155
166
|
` slidemind update [--for <t>] Fetch the latest release and redeploy\n` +
|
|
156
167
|
` --for <t> harness to redeploy into (default: all)\n` +
|
|
@@ -1806,7 +1817,7 @@ function connectionBlocker(pub) {
|
|
|
1806
1817
|
code: "not_paired",
|
|
1807
1818
|
actor: "user",
|
|
1808
1819
|
message:
|
|
1809
|
-
"Not
|
|
1820
|
+
"Not connected. In PowerPoint open slidemind → Settings → General → Connect CLI, press the link button, and run the command it shows.",
|
|
1810
1821
|
};
|
|
1811
1822
|
}
|
|
1812
1823
|
if ((pub.backends ?? []).every((b) => !b.ok)) {
|
|
@@ -1876,6 +1887,226 @@ function connectionNextAction(pub) {
|
|
|
1876
1887
|
return connectionBlocker(pub)?.message ?? "Ready.";
|
|
1877
1888
|
}
|
|
1878
1889
|
|
|
1890
|
+
// ---------- one-command setup (npx bootstrap) ----------
|
|
1891
|
+
//
|
|
1892
|
+
// `npx --yes slidemind@latest setup <claim>` is the whole onboarding: the user
|
|
1893
|
+
// clicks "Connect CLI" in the add-in, pastes ONE command, and ends up with a
|
|
1894
|
+
// connected, globally-installed CLI. npx only runs the package from its cache —
|
|
1895
|
+
// it never installs it — so this command performs the persistent
|
|
1896
|
+
// `npm install -g` itself.
|
|
1897
|
+
//
|
|
1898
|
+
// Order matters: redeem FIRST. The claim is short-lived and one-time, while a
|
|
1899
|
+
// global npm install can take a while or hit an EACCES prefix problem. Spending
|
|
1900
|
+
// the claim up front means a failed install leaves a connected CLI the user can
|
|
1901
|
+
// fix at their leisure, instead of forcing them back to PowerPoint for a fresh
|
|
1902
|
+
// command.
|
|
1903
|
+
async function cmdSetup(rest) {
|
|
1904
|
+
let claimArg = null;
|
|
1905
|
+
// `auto` is the whole point: setup finishes onboarding by itself, so it
|
|
1906
|
+
// deploys into the coding agents this machine actually has instead of asking
|
|
1907
|
+
// the user to run `init` afterwards.
|
|
1908
|
+
let target = "auto";
|
|
1909
|
+
let doInstall = true;
|
|
1910
|
+
let doReady = true;
|
|
1911
|
+
let asJson = false;
|
|
1912
|
+
for (let i = 0; i < rest.length; i++) {
|
|
1913
|
+
const a = rest[i];
|
|
1914
|
+
if (a === "--for" || a === "-f") {
|
|
1915
|
+
target = rest[++i];
|
|
1916
|
+
if (!target) fail("--for expects a target");
|
|
1917
|
+
if (![...HARNESS_TARGETS, "all", "auto", "none"].includes(target)) {
|
|
1918
|
+
fail(`unknown install target: ${target}`);
|
|
1919
|
+
}
|
|
1920
|
+
} else if (a === "--no-install") {
|
|
1921
|
+
doInstall = false;
|
|
1922
|
+
} else if (a === "--no-ready") {
|
|
1923
|
+
doReady = false;
|
|
1924
|
+
} else if (a === "--json") {
|
|
1925
|
+
asJson = true;
|
|
1926
|
+
} else if (a.startsWith("-")) {
|
|
1927
|
+
fail(`unknown flag: ${a}`);
|
|
1928
|
+
} else if (claimArg === null) {
|
|
1929
|
+
claimArg = a;
|
|
1930
|
+
} else {
|
|
1931
|
+
fail("setup takes exactly one setup claim");
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
if (!claimArg) {
|
|
1936
|
+
fail(
|
|
1937
|
+
"setup needs the connection command from the add-in.\n" +
|
|
1938
|
+
" Open PowerPoint → slidemind → Settings → General → Connect CLI, copy the command, and paste it here.\n" +
|
|
1939
|
+
" Already have the CLI? The panel's `CLI already installed?` toggle gives the short `slidemind setup …` form.",
|
|
1940
|
+
);
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
const pkg = readPackageJson();
|
|
1944
|
+
const claims = await loadBridgeModule("setup-claim");
|
|
1945
|
+
// Decode before any UI so a mistyped/partial paste fails in one clean line.
|
|
1946
|
+
const { backendURL, secret } = claims.decodeSetupClaim(claimArg);
|
|
1947
|
+
const cfg = await loadBridgeModule("config");
|
|
1948
|
+
const envLabel = cfg.deriveEnvLabel(backendURL);
|
|
1949
|
+
|
|
1950
|
+
// The installers paint their own progress rail. In --json mode stdout is
|
|
1951
|
+
// reserved for the payload, so silence the whole UI rather than letting it
|
|
1952
|
+
// corrupt what the caller parses.
|
|
1953
|
+
const ui = await loadInstallerUi();
|
|
1954
|
+
if (asJson) ui.silence();
|
|
1955
|
+
ui.banner(pkg.version, "setup");
|
|
1956
|
+
|
|
1957
|
+
// --- 1. spend the claim -------------------------------------------------
|
|
1958
|
+
await ui.section("connect", envLabel);
|
|
1959
|
+
const sessionToken = await claims.redeemSetupClaim({
|
|
1960
|
+
backendURL,
|
|
1961
|
+
secret,
|
|
1962
|
+
cliVersion: pkg.version,
|
|
1963
|
+
});
|
|
1964
|
+
cfg.upsertSession({ backendURL, sessionToken, label: envLabel });
|
|
1965
|
+
ui.step({ label: "authorize", value: envLabel, note: "session saved" });
|
|
1966
|
+
|
|
1967
|
+
// --- 2. make this CLI permanent ----------------------------------------
|
|
1968
|
+
// Without this the whole thing evaporates with npx's cache: the user would be
|
|
1969
|
+
// connected but have no `slidemind` on PATH.
|
|
1970
|
+
let installed = false;
|
|
1971
|
+
let installError = null;
|
|
1972
|
+
const pluginInstall = isClaudePluginInstall();
|
|
1973
|
+
if (doInstall && pluginInstall) {
|
|
1974
|
+
ui.step({
|
|
1975
|
+
status: "warn",
|
|
1976
|
+
label: "install",
|
|
1977
|
+
value: "skipped",
|
|
1978
|
+
note: "this slidemind came from the Claude Code plugin — update it with /plugin",
|
|
1979
|
+
});
|
|
1980
|
+
} else if (doInstall) {
|
|
1981
|
+
await ui.section("install", "npm global");
|
|
1982
|
+
ui.step({ label: "install", value: "slidemind@latest", note: "global (npm)" });
|
|
1983
|
+
try {
|
|
1984
|
+
await spawnInherit(npmBin(), [
|
|
1985
|
+
"install",
|
|
1986
|
+
"-g",
|
|
1987
|
+
"--loglevel=error",
|
|
1988
|
+
"slidemind@latest",
|
|
1989
|
+
]);
|
|
1990
|
+
installed = true;
|
|
1991
|
+
} catch (err) {
|
|
1992
|
+
installError = err?.message || String(err);
|
|
1993
|
+
ui.step({
|
|
1994
|
+
status: "warn",
|
|
1995
|
+
label: "install",
|
|
1996
|
+
value: "failed",
|
|
1997
|
+
note: "the connection is saved — see the note below",
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// --- 3. deploy the skills ----------------------------------------------
|
|
2003
|
+
// In-process, from THIS package copy: the npx copy and the global install are
|
|
2004
|
+
// the same published version, and going through PATH here would fail on a
|
|
2005
|
+
// shell that has not rehashed yet.
|
|
2006
|
+
//
|
|
2007
|
+
// With the default `auto` this is what removes `slidemind init` from
|
|
2008
|
+
// onboarding: the skills land in every coding agent present on the machine,
|
|
2009
|
+
// and skip any whose plugin already supplies them (copying over a plugin's
|
|
2010
|
+
// skills plants a second set that drifts on the next plugin update).
|
|
2011
|
+
let skills = [];
|
|
2012
|
+
let skippedPlugins = [];
|
|
2013
|
+
if (target !== "none") {
|
|
2014
|
+
if (target === "auto") {
|
|
2015
|
+
const harnesses = await detectHarnesses();
|
|
2016
|
+
skippedPlugins = harnesses
|
|
2017
|
+
.filter((h) => h.present && h.pluginInstalled)
|
|
2018
|
+
.map((h) => h.target);
|
|
2019
|
+
}
|
|
2020
|
+
await ui.section(
|
|
2021
|
+
"skills",
|
|
2022
|
+
target === "auto"
|
|
2023
|
+
? "detected harnesses"
|
|
2024
|
+
: target === "all"
|
|
2025
|
+
? "all harnesses"
|
|
2026
|
+
: target,
|
|
2027
|
+
);
|
|
2028
|
+
skills = await runInstaller(target, "install", []);
|
|
2029
|
+
for (const t of skippedPlugins) {
|
|
2030
|
+
ui.step({
|
|
2031
|
+
status: "warn",
|
|
2032
|
+
label: t,
|
|
2033
|
+
value: "skipped",
|
|
2034
|
+
note: "its slidemind plugin already supplies the skills",
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
2037
|
+
if (skills.length === 0 && skippedPlugins.length === 0) {
|
|
2038
|
+
ui.step({
|
|
2039
|
+
status: "warn",
|
|
2040
|
+
label: "harnesses",
|
|
2041
|
+
value: "none detected",
|
|
2042
|
+
note: "install a coding agent, then run `slidemind init --for auto`",
|
|
2043
|
+
});
|
|
2044
|
+
}
|
|
2045
|
+
const bundleMod = await loadBridgeModule("bundle");
|
|
2046
|
+
bundleMod.clearBundleCache();
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
if (asJson) {
|
|
2050
|
+
const globalInstall =
|
|
2051
|
+
doInstall && !pluginInstall
|
|
2052
|
+
? installed
|
|
2053
|
+
? "installed"
|
|
2054
|
+
: "failed"
|
|
2055
|
+
: "skipped";
|
|
2056
|
+
process.stdout.write(
|
|
2057
|
+
`${JSON.stringify(
|
|
2058
|
+
{
|
|
2059
|
+
ok: true,
|
|
2060
|
+
connected: true,
|
|
2061
|
+
env: envLabel,
|
|
2062
|
+
globalInstall,
|
|
2063
|
+
skills,
|
|
2064
|
+
// Present-but-plugin-served harnesses, so an agent can tell "nothing
|
|
2065
|
+
// deployed because nothing is installed" from "nothing needed".
|
|
2066
|
+
skippedPlugins,
|
|
2067
|
+
nextAction: "slidemind ready",
|
|
2068
|
+
// Only on the unhappy path: the session IS saved, so `ok` stays true,
|
|
2069
|
+
// but an agent needs to know the binary did not land.
|
|
2070
|
+
...(installError ? { installError } : {}),
|
|
2071
|
+
},
|
|
2072
|
+
null,
|
|
2073
|
+
2,
|
|
2074
|
+
)}\n`,
|
|
2075
|
+
);
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
|
|
2079
|
+
ui.done(
|
|
2080
|
+
target === "none" || (skills.length === 0 && skippedPlugins.length === 0)
|
|
2081
|
+
? `connected to ${envLabel} — run 'slidemind ready' to start`
|
|
2082
|
+
: `connected to ${envLabel} and ready to use`,
|
|
2083
|
+
);
|
|
2084
|
+
|
|
2085
|
+
if (installError) {
|
|
2086
|
+
process.stderr.write(
|
|
2087
|
+
"\nslidemind is connected, but the global install could not write to npm's global package directory.\n" +
|
|
2088
|
+
"This usually happens on macOS/Linux when Node was installed with a system-owned npm prefix.\n\n" +
|
|
2089
|
+
"Avoid `sudo npm`. Use Homebrew/nvm, or move npm's global prefix into your home directory:\n\n" +
|
|
2090
|
+
" mkdir -p ~/.npm-global\n" +
|
|
2091
|
+
" npm config set prefix ~/.npm-global\n" +
|
|
2092
|
+
" echo 'export PATH=\"$HOME/.npm-global/bin:$PATH\"' >> ~/.zshrc\n" +
|
|
2093
|
+
" source ~/.zshrc\n" +
|
|
2094
|
+
" npm install -g slidemind\n\n" +
|
|
2095
|
+
`(details: ${installError})\n`,
|
|
2096
|
+
);
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2100
|
+
// --- 4. finish the session the user actually came for -------------------
|
|
2101
|
+
// `ready` exits non-zero for every legitimate not-yet blocker (PowerPoint
|
|
2102
|
+
// closed, no design activated). Setup already succeeded at that point, so its
|
|
2103
|
+
// blocker is information, not a failure — run it for the message and the
|
|
2104
|
+
// warmed bundle, and never inherit its exit code.
|
|
2105
|
+
if (doReady) {
|
|
2106
|
+
await spawnStatus(process.execPath, [__filename, "ready"]);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
|
|
1879
2110
|
// The one-command agent preflight. Collapses the old 4-call session start
|
|
1880
2111
|
// (presentations → pick → download-bundle → download-bundle-to-bind) into a
|
|
1881
2112
|
// single verb: check pairing/reachability/CLI-version/windows, resolve the OPEN
|
|
@@ -1954,10 +2185,20 @@ async function cmdReady(rest) {
|
|
|
1954
2185
|
fail(blocker.message);
|
|
1955
2186
|
};
|
|
1956
2187
|
|
|
1957
|
-
// ---
|
|
2188
|
+
// --- First use: there is nothing this process can do on its own. Connecting
|
|
2189
|
+
// starts in the add-in because the claim it mints carries the deployment's
|
|
2190
|
+
// address, and an unconnected CLI has no way to learn which backend its user
|
|
2191
|
+
// belongs to. So name the one action that works and stop. ---
|
|
1958
2192
|
const cfg = await loadBridgeModule("config");
|
|
1959
2193
|
if (cfg.readSessions().length === 0) {
|
|
1960
|
-
finishBlocker({
|
|
2194
|
+
finishBlocker({
|
|
2195
|
+
blocker: {
|
|
2196
|
+
code: "not_connected",
|
|
2197
|
+
actor: "user",
|
|
2198
|
+
message:
|
|
2199
|
+
"Connect this CLI first: in PowerPoint open slidemind → Settings → General → Connect CLI, press the link button, copy the command it shows, and run it here.",
|
|
2200
|
+
},
|
|
2201
|
+
});
|
|
1961
2202
|
return;
|
|
1962
2203
|
}
|
|
1963
2204
|
|
|
@@ -2456,93 +2697,23 @@ async function loadBridgeModule(name) {
|
|
|
2456
2697
|
return import(pathToFileURL(path.join(SRC_DIR, "bridge", `${name}.mjs`)).href);
|
|
2457
2698
|
}
|
|
2458
2699
|
|
|
2459
|
-
async function cmdPair(rest) {
|
|
2460
|
-
let code = null;
|
|
2461
|
-
let label = null;
|
|
2462
|
-
for (let i = 0; i < rest.length; i++) {
|
|
2463
|
-
const a = rest[i];
|
|
2464
|
-
if (a === "--label") {
|
|
2465
|
-
label = rest[++i];
|
|
2466
|
-
if (!label) fail("--label expects a name");
|
|
2467
|
-
} else if (!a.startsWith("--") && !code) {
|
|
2468
|
-
code = a;
|
|
2469
|
-
}
|
|
2470
|
-
}
|
|
2471
|
-
if (!code) {
|
|
2472
|
-
fail('usage: slidemind pair <code> (copy the command from the add-in\'s "Connect CLI")');
|
|
2473
|
-
}
|
|
2474
|
-
const pairMod = await loadBridgeModule("pair");
|
|
2475
|
-
const decoded = pairMod.decodePairingCode(code);
|
|
2476
|
-
if (!decoded) {
|
|
2477
|
-
fail("invalid pairing code — copy the exact `slidemind pair …` command shown in the add-in");
|
|
2478
|
-
}
|
|
2479
|
-
const res = await pairMod.httpJson(
|
|
2480
|
-
"POST",
|
|
2481
|
-
`${decoded.backendURL}/api/cli/pair/redeem`,
|
|
2482
|
-
{ body: { secret: decoded.secret } },
|
|
2483
|
-
);
|
|
2484
|
-
if (res.status !== 200 || !res.json?.sessionToken) {
|
|
2485
|
-
fail(
|
|
2486
|
-
`pairing failed (${res.status}): ${res.json?.error || "no session returned"}\n` +
|
|
2487
|
-
"the code may have expired — generate a fresh one in the add-in's \"Connect CLI\"",
|
|
2488
|
-
);
|
|
2489
|
-
}
|
|
2490
|
-
const cfg = await loadBridgeModule("config");
|
|
2491
|
-
const sessions = cfg.upsertSession({
|
|
2492
|
-
backendURL: decoded.backendURL,
|
|
2493
|
-
sessionToken: res.json.sessionToken,
|
|
2494
|
-
label,
|
|
2495
|
-
});
|
|
2496
|
-
const env = cfg.deriveEnvLabel(decoded.backendURL);
|
|
2497
|
-
const others = sessions.length - 1;
|
|
2498
|
-
const suffix =
|
|
2499
|
-
others > 0
|
|
2500
|
-
? ` (${sessions.length} environments connected — commands now fan out across all of them)`
|
|
2501
|
-
: "";
|
|
2502
|
-
let verify = "";
|
|
2503
|
-
try {
|
|
2504
|
-
const client = await import(
|
|
2505
|
-
pathToFileURL(path.join(SRC_DIR, "bridge", "client.mjs")).href
|
|
2506
|
-
);
|
|
2507
|
-
const deadline = Date.now() + 8000;
|
|
2508
|
-
for (;;) {
|
|
2509
|
-
const status = await client.connectionStatus();
|
|
2510
|
-
if ((status.presentations ?? []).length > 0) {
|
|
2511
|
-
verify = " Pane is registered.";
|
|
2512
|
-
break;
|
|
2513
|
-
}
|
|
2514
|
-
if (Date.now() >= deadline) {
|
|
2515
|
-
verify =
|
|
2516
|
-
" No pane is registered yet; if PowerPoint is already open, reopen or click the slidemind task pane, then run 'slidemind doctor'.";
|
|
2517
|
-
break;
|
|
2518
|
-
}
|
|
2519
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2520
|
-
}
|
|
2521
|
-
} catch {
|
|
2522
|
-
// Pairing itself succeeded; diagnostics remain available via doctor.
|
|
2523
|
-
}
|
|
2524
|
-
process.stdout.write(
|
|
2525
|
-
`Paired with ${label || env}${suffix}.${verify} Run 'slidemind doctor' to verify.\n`,
|
|
2526
|
-
);
|
|
2527
|
-
}
|
|
2528
|
-
|
|
2529
2700
|
async function cmdWhoami() {
|
|
2530
2701
|
const cfg = await loadBridgeModule("config");
|
|
2531
2702
|
const sessions = cfg.readSessions();
|
|
2532
2703
|
if (sessions.length === 0) {
|
|
2533
2704
|
process.stderr.write(
|
|
2534
|
-
|
|
2705
|
+
"Not connected. In PowerPoint open slidemind → Settings → General → Connect CLI, press the link button, and run the command it shows.\n",
|
|
2535
2706
|
);
|
|
2536
2707
|
return;
|
|
2537
2708
|
}
|
|
2538
|
-
const
|
|
2709
|
+
const { httpJson } = await loadBridgeModule("http-json");
|
|
2539
2710
|
const def = cfg.getDefaultBackend();
|
|
2540
2711
|
const lines = await Promise.all(
|
|
2541
2712
|
sessions.map(async (s) => {
|
|
2542
2713
|
const marker = s.backendURL === def ? "*" : " ";
|
|
2543
2714
|
let detail;
|
|
2544
2715
|
try {
|
|
2545
|
-
const res = await
|
|
2716
|
+
const res = await httpJson(
|
|
2546
2717
|
"GET",
|
|
2547
2718
|
`${s.backendURL}/api/cli/whoami`,
|
|
2548
2719
|
{ token: s.sessionToken },
|
|
@@ -2550,15 +2721,15 @@ async function cmdWhoami() {
|
|
|
2550
2721
|
detail =
|
|
2551
2722
|
res.status === 200 && res.json
|
|
2552
2723
|
? `user ${res.json.oid} · tenant ${res.json.tid ?? "(n/a)"}`
|
|
2553
|
-
: `session invalid (${res.status}) —
|
|
2724
|
+
: `session invalid (${res.status}) — authorize again`;
|
|
2554
2725
|
} catch {
|
|
2555
|
-
detail = "unreachable — check your connection or
|
|
2726
|
+
detail = "unreachable — check your connection or authorize again";
|
|
2556
2727
|
}
|
|
2557
2728
|
return `${marker} ${s.label}\n ${detail}`;
|
|
2558
2729
|
}),
|
|
2559
2730
|
);
|
|
2560
2731
|
process.stdout.write(
|
|
2561
|
-
`
|
|
2732
|
+
`Connected environments (${sessions.length}):\n${lines.join("\n")}\n`,
|
|
2562
2733
|
);
|
|
2563
2734
|
}
|
|
2564
2735
|
|
|
@@ -2593,10 +2764,10 @@ async function cmdLogout(rest) {
|
|
|
2593
2764
|
toRemove = sessions.filter((s) => s.backendURL === def);
|
|
2594
2765
|
}
|
|
2595
2766
|
|
|
2596
|
-
const
|
|
2767
|
+
const { httpJson } = await loadBridgeModule("http-json");
|
|
2597
2768
|
for (const s of toRemove) {
|
|
2598
2769
|
try {
|
|
2599
|
-
await
|
|
2770
|
+
await httpJson("POST", `${s.backendURL}/api/cli/logout`, {
|
|
2600
2771
|
token: s.sessionToken,
|
|
2601
2772
|
});
|
|
2602
2773
|
} catch {
|
|
@@ -2607,7 +2778,7 @@ async function cmdLogout(rest) {
|
|
|
2607
2778
|
const remaining = cfg.readSessions().length;
|
|
2608
2779
|
process.stdout.write(
|
|
2609
2780
|
`Logged out of ${toRemove.map((s) => s.label).join(", ")}.` +
|
|
2610
|
-
(remaining > 0 ? ` ${remaining} environment(s) still
|
|
2781
|
+
(remaining > 0 ? ` ${remaining} environment(s) still connected.\n` : "\n"),
|
|
2611
2782
|
);
|
|
2612
2783
|
}
|
|
2613
2784
|
|
|
@@ -2785,20 +2956,56 @@ async function cmdNew(rest) {
|
|
|
2785
2956
|
);
|
|
2786
2957
|
}
|
|
2787
2958
|
|
|
2788
|
-
|
|
2959
|
+
const HARNESS_TARGETS = ["claude", "codex", "opencode"];
|
|
2960
|
+
|
|
2961
|
+
function parseTargetFlag(rest, { allowAuto = false } = {}) {
|
|
2789
2962
|
const idx = rest.findIndex((a) => a === "--for" || a === "-f");
|
|
2790
2963
|
if (idx < 0 || !rest[idx + 1]) {
|
|
2791
|
-
fail(
|
|
2964
|
+
fail(
|
|
2965
|
+
`missing --for <claude|codex|opencode|all${allowAuto ? "|auto" : ""}>`,
|
|
2966
|
+
);
|
|
2792
2967
|
}
|
|
2793
2968
|
const target = rest[idx + 1];
|
|
2794
|
-
|
|
2969
|
+
const allowed = allowAuto
|
|
2970
|
+
? [...HARNESS_TARGETS, "all", "auto"]
|
|
2971
|
+
: [...HARNESS_TARGETS, "all"];
|
|
2972
|
+
if (!allowed.includes(target)) {
|
|
2795
2973
|
fail(`unknown install target: ${target}`);
|
|
2796
2974
|
}
|
|
2797
2975
|
return target;
|
|
2798
2976
|
}
|
|
2799
2977
|
|
|
2978
|
+
// Which coding agents are actually on this machine, and which of them already
|
|
2979
|
+
// get their skills from a plugin. This is what lets `slidemind setup` finish the
|
|
2980
|
+
// job on its own: it deploys where a harness exists and stays out of the way
|
|
2981
|
+
// where a plugin already supplies the skills, so a separate `init` is no longer
|
|
2982
|
+
// part of onboarding.
|
|
2983
|
+
async function detectHarnesses() {
|
|
2984
|
+
const found = [];
|
|
2985
|
+
for (const target of HARNESS_TARGETS) {
|
|
2986
|
+
const mod = await import(
|
|
2987
|
+
pathToFileURL(path.join(INSTALLERS_DIR, `${target}.mjs`)).href
|
|
2988
|
+
);
|
|
2989
|
+
const info =
|
|
2990
|
+
typeof mod.detect === "function"
|
|
2991
|
+
? mod.detect()
|
|
2992
|
+
: { present: false, pluginInstalled: false, root: null };
|
|
2993
|
+
found.push({ target, ...info });
|
|
2994
|
+
}
|
|
2995
|
+
return found;
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
/** Expand a `--for` value into the concrete harness list to act on. */
|
|
2999
|
+
async function resolveInstallTargets(target) {
|
|
3000
|
+
if (target === "all") return [...HARNESS_TARGETS];
|
|
3001
|
+
if (target !== "auto") return [target];
|
|
3002
|
+
return (await detectHarnesses())
|
|
3003
|
+
.filter((h) => h.present && !h.pluginInstalled)
|
|
3004
|
+
.map((h) => h.target);
|
|
3005
|
+
}
|
|
3006
|
+
|
|
2800
3007
|
async function runInstaller(target, action, rest) {
|
|
2801
|
-
const targets =
|
|
3008
|
+
const targets = await resolveInstallTargets(target);
|
|
2802
3009
|
for (const t of targets) {
|
|
2803
3010
|
const mod = await import(
|
|
2804
3011
|
pathToFileURL(path.join(INSTALLERS_DIR, `${t}.mjs`)).href
|
|
@@ -2812,6 +3019,7 @@ async function runInstaller(target, action, rest) {
|
|
|
2812
3019
|
flags: rest,
|
|
2813
3020
|
});
|
|
2814
3021
|
}
|
|
3022
|
+
return targets;
|
|
2815
3023
|
}
|
|
2816
3024
|
|
|
2817
3025
|
async function loadInstallerUi() {
|
|
@@ -2822,10 +3030,18 @@ async function loadInstallerUi() {
|
|
|
2822
3030
|
}
|
|
2823
3031
|
|
|
2824
3032
|
async function cmdInstall(rest) {
|
|
2825
|
-
const target = parseTargetFlag(rest);
|
|
3033
|
+
const target = parseTargetFlag(rest, { allowAuto: true });
|
|
2826
3034
|
const ui = await loadInstallerUi();
|
|
2827
3035
|
ui.banner(readPackageJson().version, "init");
|
|
2828
|
-
await runInstaller(target, "install", rest);
|
|
3036
|
+
const deployed = await runInstaller(target, "install", rest);
|
|
3037
|
+
if (deployed.length === 0) {
|
|
3038
|
+
ui.step({
|
|
3039
|
+
status: "warn",
|
|
3040
|
+
label: "harnesses",
|
|
3041
|
+
value: "none detected",
|
|
3042
|
+
note: "install a coding agent, then rerun with --for <claude|codex|opencode>",
|
|
3043
|
+
});
|
|
3044
|
+
}
|
|
2829
3045
|
|
|
2830
3046
|
// Force every design bundle to re-download from the add-in on the next
|
|
2831
3047
|
// build/push/download by wiping the materialized cache. init is the
|
|
@@ -2843,9 +3059,11 @@ async function cmdInstall(rest) {
|
|
|
2843
3059
|
});
|
|
2844
3060
|
|
|
2845
3061
|
ui.done(
|
|
2846
|
-
|
|
2847
|
-
? "
|
|
2848
|
-
:
|
|
3062
|
+
deployed.length === 0
|
|
3063
|
+
? "no coding agent found — the CLI is installed, skills are not"
|
|
3064
|
+
: deployed.length === HARNESS_TARGETS.length
|
|
3065
|
+
? "ready — ask your agent to plan, create, or edit a deck"
|
|
3066
|
+
: `ready — ${deployed.join(", ")} can now plan, create, or edit decks`,
|
|
2849
3067
|
);
|
|
2850
3068
|
}
|
|
2851
3069
|
|
|
@@ -2900,6 +3118,16 @@ function spawnInherit(file, args) {
|
|
|
2900
3118
|
});
|
|
2901
3119
|
}
|
|
2902
3120
|
|
|
3121
|
+
// Like spawnInherit, but resolves with the child's exit code instead of
|
|
3122
|
+
// rejecting — for a step whose failure is a message to the user rather than a
|
|
3123
|
+
// failure of the command that ran it (see `setup` → `ready`).
|
|
3124
|
+
function spawnStatus(file, args) {
|
|
3125
|
+
return spawnInherit(file, args).then(
|
|
3126
|
+
() => 0,
|
|
3127
|
+
() => 1,
|
|
3128
|
+
);
|
|
3129
|
+
}
|
|
3130
|
+
|
|
2903
3131
|
// ---------- distribution (public npm) ----------
|
|
2904
3132
|
//
|
|
2905
3133
|
// slidemind is published to public npm as `slidemind`. First install is
|
|
@@ -3070,12 +3298,12 @@ async function main(rawArgv) {
|
|
|
3070
3298
|
case "ready":
|
|
3071
3299
|
await cmdReady(rest);
|
|
3072
3300
|
return 0;
|
|
3301
|
+
case "setup":
|
|
3302
|
+
await cmdSetup(rest);
|
|
3303
|
+
return 0;
|
|
3073
3304
|
case "new":
|
|
3074
3305
|
await cmdNew(rest);
|
|
3075
3306
|
return 0;
|
|
3076
|
-
case "pair":
|
|
3077
|
-
await cmdPair(rest);
|
|
3078
|
-
break;
|
|
3079
3307
|
case "whoami":
|
|
3080
3308
|
await cmdWhoami(rest);
|
|
3081
3309
|
break;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "slidemind",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Connect Claude Code, Codex & opencode to the slidemind PowerPoint add-in — let your coding agent build and edit native, on-brand PowerPoint decks against your active corporate design, from the terminal.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
".claude-plugin/",
|
|
22
22
|
".codex-plugin/",
|
|
23
23
|
".agents/plugins/",
|
|
24
|
+
"assets/",
|
|
24
25
|
"AGENTS.md",
|
|
25
26
|
"README.md"
|
|
26
27
|
],
|