slidemind 0.2.1 → 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/.agents/plugins/marketplace.json +29 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +9 -1
- package/{CLAUDE.md → AGENTS.md} +88 -17
- package/README.md +87 -39
- package/assets/app-icon.png +0 -0
- package/bin/slidemind.mjs +335 -100
- package/package.json +4 -2
- 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/builders/build.mjs +3 -3
- package/src/builders/resolve-skill.mjs +66 -6
- 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/src/pptx/fill/engine.mjs +6 -3
- package/src/pptx/fill/freeform.mjs +36 -22
- package/src/pptx/fill/parse-slides.mjs +56 -22
- package/src/pptx/fill/xml.mjs +1 -1
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` +
|
|
@@ -468,6 +479,13 @@ async function cmdBuildPptx(rest) {
|
|
|
468
479
|
deckDir,
|
|
469
480
|
locale: skill.locale ?? "en",
|
|
470
481
|
srcDir: SRC_DIR,
|
|
482
|
+
// Both are the PACK's contract, read from its skill.json — this CLI
|
|
483
|
+
// knows no layout names and no template-fixed titles of its own.
|
|
484
|
+
components: {
|
|
485
|
+
...(skill.manifest?.components ?? {}),
|
|
486
|
+
skillDir: skill.skillDir,
|
|
487
|
+
},
|
|
488
|
+
policy: skill.manifest?.policy ?? {},
|
|
471
489
|
onLog: (line) => process.stdout.write(` ${line}\n`),
|
|
472
490
|
});
|
|
473
491
|
for (const w of ff.warnings) process.stderr.write(` warning: ${w}\n`);
|
|
@@ -1799,7 +1817,7 @@ function connectionBlocker(pub) {
|
|
|
1799
1817
|
code: "not_paired",
|
|
1800
1818
|
actor: "user",
|
|
1801
1819
|
message:
|
|
1802
|
-
"Not
|
|
1820
|
+
"Not connected. In PowerPoint open slidemind → Settings → General → Connect CLI, press the link button, and run the command it shows.",
|
|
1803
1821
|
};
|
|
1804
1822
|
}
|
|
1805
1823
|
if ((pub.backends ?? []).every((b) => !b.ok)) {
|
|
@@ -1869,6 +1887,226 @@ function connectionNextAction(pub) {
|
|
|
1869
1887
|
return connectionBlocker(pub)?.message ?? "Ready.";
|
|
1870
1888
|
}
|
|
1871
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
|
+
|
|
1872
2110
|
// The one-command agent preflight. Collapses the old 4-call session start
|
|
1873
2111
|
// (presentations → pick → download-bundle → download-bundle-to-bind) into a
|
|
1874
2112
|
// single verb: check pairing/reachability/CLI-version/windows, resolve the OPEN
|
|
@@ -1947,10 +2185,20 @@ async function cmdReady(rest) {
|
|
|
1947
2185
|
fail(blocker.message);
|
|
1948
2186
|
};
|
|
1949
2187
|
|
|
1950
|
-
// ---
|
|
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. ---
|
|
1951
2192
|
const cfg = await loadBridgeModule("config");
|
|
1952
2193
|
if (cfg.readSessions().length === 0) {
|
|
1953
|
-
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
|
+
});
|
|
1954
2202
|
return;
|
|
1955
2203
|
}
|
|
1956
2204
|
|
|
@@ -2449,93 +2697,23 @@ async function loadBridgeModule(name) {
|
|
|
2449
2697
|
return import(pathToFileURL(path.join(SRC_DIR, "bridge", `${name}.mjs`)).href);
|
|
2450
2698
|
}
|
|
2451
2699
|
|
|
2452
|
-
async function cmdPair(rest) {
|
|
2453
|
-
let code = null;
|
|
2454
|
-
let label = null;
|
|
2455
|
-
for (let i = 0; i < rest.length; i++) {
|
|
2456
|
-
const a = rest[i];
|
|
2457
|
-
if (a === "--label") {
|
|
2458
|
-
label = rest[++i];
|
|
2459
|
-
if (!label) fail("--label expects a name");
|
|
2460
|
-
} else if (!a.startsWith("--") && !code) {
|
|
2461
|
-
code = a;
|
|
2462
|
-
}
|
|
2463
|
-
}
|
|
2464
|
-
if (!code) {
|
|
2465
|
-
fail('usage: slidemind pair <code> (copy the command from the add-in\'s "Connect CLI")');
|
|
2466
|
-
}
|
|
2467
|
-
const pairMod = await loadBridgeModule("pair");
|
|
2468
|
-
const decoded = pairMod.decodePairingCode(code);
|
|
2469
|
-
if (!decoded) {
|
|
2470
|
-
fail("invalid pairing code — copy the exact `slidemind pair …` command shown in the add-in");
|
|
2471
|
-
}
|
|
2472
|
-
const res = await pairMod.httpJson(
|
|
2473
|
-
"POST",
|
|
2474
|
-
`${decoded.backendURL}/api/cli/pair/redeem`,
|
|
2475
|
-
{ body: { secret: decoded.secret } },
|
|
2476
|
-
);
|
|
2477
|
-
if (res.status !== 200 || !res.json?.sessionToken) {
|
|
2478
|
-
fail(
|
|
2479
|
-
`pairing failed (${res.status}): ${res.json?.error || "no session returned"}\n` +
|
|
2480
|
-
"the code may have expired — generate a fresh one in the add-in's \"Connect CLI\"",
|
|
2481
|
-
);
|
|
2482
|
-
}
|
|
2483
|
-
const cfg = await loadBridgeModule("config");
|
|
2484
|
-
const sessions = cfg.upsertSession({
|
|
2485
|
-
backendURL: decoded.backendURL,
|
|
2486
|
-
sessionToken: res.json.sessionToken,
|
|
2487
|
-
label,
|
|
2488
|
-
});
|
|
2489
|
-
const env = cfg.deriveEnvLabel(decoded.backendURL);
|
|
2490
|
-
const others = sessions.length - 1;
|
|
2491
|
-
const suffix =
|
|
2492
|
-
others > 0
|
|
2493
|
-
? ` (${sessions.length} environments connected — commands now fan out across all of them)`
|
|
2494
|
-
: "";
|
|
2495
|
-
let verify = "";
|
|
2496
|
-
try {
|
|
2497
|
-
const client = await import(
|
|
2498
|
-
pathToFileURL(path.join(SRC_DIR, "bridge", "client.mjs")).href
|
|
2499
|
-
);
|
|
2500
|
-
const deadline = Date.now() + 8000;
|
|
2501
|
-
for (;;) {
|
|
2502
|
-
const status = await client.connectionStatus();
|
|
2503
|
-
if ((status.presentations ?? []).length > 0) {
|
|
2504
|
-
verify = " Pane is registered.";
|
|
2505
|
-
break;
|
|
2506
|
-
}
|
|
2507
|
-
if (Date.now() >= deadline) {
|
|
2508
|
-
verify =
|
|
2509
|
-
" No pane is registered yet; if PowerPoint is already open, reopen or click the slidemind task pane, then run 'slidemind doctor'.";
|
|
2510
|
-
break;
|
|
2511
|
-
}
|
|
2512
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
2513
|
-
}
|
|
2514
|
-
} catch {
|
|
2515
|
-
// Pairing itself succeeded; diagnostics remain available via doctor.
|
|
2516
|
-
}
|
|
2517
|
-
process.stdout.write(
|
|
2518
|
-
`Paired with ${label || env}${suffix}.${verify} Run 'slidemind doctor' to verify.\n`,
|
|
2519
|
-
);
|
|
2520
|
-
}
|
|
2521
|
-
|
|
2522
2700
|
async function cmdWhoami() {
|
|
2523
2701
|
const cfg = await loadBridgeModule("config");
|
|
2524
2702
|
const sessions = cfg.readSessions();
|
|
2525
2703
|
if (sessions.length === 0) {
|
|
2526
2704
|
process.stderr.write(
|
|
2527
|
-
|
|
2705
|
+
"Not connected. In PowerPoint open slidemind → Settings → General → Connect CLI, press the link button, and run the command it shows.\n",
|
|
2528
2706
|
);
|
|
2529
2707
|
return;
|
|
2530
2708
|
}
|
|
2531
|
-
const
|
|
2709
|
+
const { httpJson } = await loadBridgeModule("http-json");
|
|
2532
2710
|
const def = cfg.getDefaultBackend();
|
|
2533
2711
|
const lines = await Promise.all(
|
|
2534
2712
|
sessions.map(async (s) => {
|
|
2535
2713
|
const marker = s.backendURL === def ? "*" : " ";
|
|
2536
2714
|
let detail;
|
|
2537
2715
|
try {
|
|
2538
|
-
const res = await
|
|
2716
|
+
const res = await httpJson(
|
|
2539
2717
|
"GET",
|
|
2540
2718
|
`${s.backendURL}/api/cli/whoami`,
|
|
2541
2719
|
{ token: s.sessionToken },
|
|
@@ -2543,15 +2721,15 @@ async function cmdWhoami() {
|
|
|
2543
2721
|
detail =
|
|
2544
2722
|
res.status === 200 && res.json
|
|
2545
2723
|
? `user ${res.json.oid} · tenant ${res.json.tid ?? "(n/a)"}`
|
|
2546
|
-
: `session invalid (${res.status}) —
|
|
2724
|
+
: `session invalid (${res.status}) — authorize again`;
|
|
2547
2725
|
} catch {
|
|
2548
|
-
detail = "unreachable — check your connection or
|
|
2726
|
+
detail = "unreachable — check your connection or authorize again";
|
|
2549
2727
|
}
|
|
2550
2728
|
return `${marker} ${s.label}\n ${detail}`;
|
|
2551
2729
|
}),
|
|
2552
2730
|
);
|
|
2553
2731
|
process.stdout.write(
|
|
2554
|
-
`
|
|
2732
|
+
`Connected environments (${sessions.length}):\n${lines.join("\n")}\n`,
|
|
2555
2733
|
);
|
|
2556
2734
|
}
|
|
2557
2735
|
|
|
@@ -2586,10 +2764,10 @@ async function cmdLogout(rest) {
|
|
|
2586
2764
|
toRemove = sessions.filter((s) => s.backendURL === def);
|
|
2587
2765
|
}
|
|
2588
2766
|
|
|
2589
|
-
const
|
|
2767
|
+
const { httpJson } = await loadBridgeModule("http-json");
|
|
2590
2768
|
for (const s of toRemove) {
|
|
2591
2769
|
try {
|
|
2592
|
-
await
|
|
2770
|
+
await httpJson("POST", `${s.backendURL}/api/cli/logout`, {
|
|
2593
2771
|
token: s.sessionToken,
|
|
2594
2772
|
});
|
|
2595
2773
|
} catch {
|
|
@@ -2600,7 +2778,7 @@ async function cmdLogout(rest) {
|
|
|
2600
2778
|
const remaining = cfg.readSessions().length;
|
|
2601
2779
|
process.stdout.write(
|
|
2602
2780
|
`Logged out of ${toRemove.map((s) => s.label).join(", ")}.` +
|
|
2603
|
-
(remaining > 0 ? ` ${remaining} environment(s) still
|
|
2781
|
+
(remaining > 0 ? ` ${remaining} environment(s) still connected.\n` : "\n"),
|
|
2604
2782
|
);
|
|
2605
2783
|
}
|
|
2606
2784
|
|
|
@@ -2778,20 +2956,56 @@ async function cmdNew(rest) {
|
|
|
2778
2956
|
);
|
|
2779
2957
|
}
|
|
2780
2958
|
|
|
2781
|
-
|
|
2959
|
+
const HARNESS_TARGETS = ["claude", "codex", "opencode"];
|
|
2960
|
+
|
|
2961
|
+
function parseTargetFlag(rest, { allowAuto = false } = {}) {
|
|
2782
2962
|
const idx = rest.findIndex((a) => a === "--for" || a === "-f");
|
|
2783
2963
|
if (idx < 0 || !rest[idx + 1]) {
|
|
2784
|
-
fail(
|
|
2964
|
+
fail(
|
|
2965
|
+
`missing --for <claude|codex|opencode|all${allowAuto ? "|auto" : ""}>`,
|
|
2966
|
+
);
|
|
2785
2967
|
}
|
|
2786
2968
|
const target = rest[idx + 1];
|
|
2787
|
-
|
|
2969
|
+
const allowed = allowAuto
|
|
2970
|
+
? [...HARNESS_TARGETS, "all", "auto"]
|
|
2971
|
+
: [...HARNESS_TARGETS, "all"];
|
|
2972
|
+
if (!allowed.includes(target)) {
|
|
2788
2973
|
fail(`unknown install target: ${target}`);
|
|
2789
2974
|
}
|
|
2790
2975
|
return target;
|
|
2791
2976
|
}
|
|
2792
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
|
+
|
|
2793
3007
|
async function runInstaller(target, action, rest) {
|
|
2794
|
-
const targets =
|
|
3008
|
+
const targets = await resolveInstallTargets(target);
|
|
2795
3009
|
for (const t of targets) {
|
|
2796
3010
|
const mod = await import(
|
|
2797
3011
|
pathToFileURL(path.join(INSTALLERS_DIR, `${t}.mjs`)).href
|
|
@@ -2805,6 +3019,7 @@ async function runInstaller(target, action, rest) {
|
|
|
2805
3019
|
flags: rest,
|
|
2806
3020
|
});
|
|
2807
3021
|
}
|
|
3022
|
+
return targets;
|
|
2808
3023
|
}
|
|
2809
3024
|
|
|
2810
3025
|
async function loadInstallerUi() {
|
|
@@ -2815,10 +3030,18 @@ async function loadInstallerUi() {
|
|
|
2815
3030
|
}
|
|
2816
3031
|
|
|
2817
3032
|
async function cmdInstall(rest) {
|
|
2818
|
-
const target = parseTargetFlag(rest);
|
|
3033
|
+
const target = parseTargetFlag(rest, { allowAuto: true });
|
|
2819
3034
|
const ui = await loadInstallerUi();
|
|
2820
3035
|
ui.banner(readPackageJson().version, "init");
|
|
2821
|
-
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
|
+
}
|
|
2822
3045
|
|
|
2823
3046
|
// Force every design bundle to re-download from the add-in on the next
|
|
2824
3047
|
// build/push/download by wiping the materialized cache. init is the
|
|
@@ -2836,9 +3059,11 @@ async function cmdInstall(rest) {
|
|
|
2836
3059
|
});
|
|
2837
3060
|
|
|
2838
3061
|
ui.done(
|
|
2839
|
-
|
|
2840
|
-
? "
|
|
2841
|
-
:
|
|
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`,
|
|
2842
3067
|
);
|
|
2843
3068
|
}
|
|
2844
3069
|
|
|
@@ -2893,6 +3118,16 @@ function spawnInherit(file, args) {
|
|
|
2893
3118
|
});
|
|
2894
3119
|
}
|
|
2895
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
|
+
|
|
2896
3131
|
// ---------- distribution (public npm) ----------
|
|
2897
3132
|
//
|
|
2898
3133
|
// slidemind is published to public npm as `slidemind`. First install is
|
|
@@ -3063,12 +3298,12 @@ async function main(rawArgv) {
|
|
|
3063
3298
|
case "ready":
|
|
3064
3299
|
await cmdReady(rest);
|
|
3065
3300
|
return 0;
|
|
3301
|
+
case "setup":
|
|
3302
|
+
await cmdSetup(rest);
|
|
3303
|
+
return 0;
|
|
3066
3304
|
case "new":
|
|
3067
3305
|
await cmdNew(rest);
|
|
3068
3306
|
return 0;
|
|
3069
|
-
case "pair":
|
|
3070
|
-
await cmdPair(rest);
|
|
3071
|
-
break;
|
|
3072
3307
|
case "whoami":
|
|
3073
3308
|
await cmdWhoami(rest);
|
|
3074
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": {
|
|
@@ -20,7 +20,9 @@
|
|
|
20
20
|
"skills/",
|
|
21
21
|
".claude-plugin/",
|
|
22
22
|
".codex-plugin/",
|
|
23
|
-
"
|
|
23
|
+
".agents/plugins/",
|
|
24
|
+
"assets/",
|
|
25
|
+
"AGENTS.md",
|
|
24
26
|
"README.md"
|
|
25
27
|
],
|
|
26
28
|
"scripts": {
|