Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,15 @@ replaceable workers without losing institutional state.

> An agent should be replaceable without losing organizational knowledge.

Install the `opencontext` command (brings its own Node if yours is older than 20; remove with
`sh ~/.local/share/opencontext/uninstall.sh`):

```bash
curl -fsSL https://logicsrc.com/opencontext/install.sh | sh
```

Or run it without installing:

```bash
npx @logicsrc/opencontext init my-context
npx @logicsrc/opencontext validate --strict
Expand Down
159 changes: 159 additions & 0 deletions apps/logicsrc-web/public/opencontext/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/bin/sh
# opencontext installer.
#
# curl -fsSL https://logicsrc.com/opencontext/install.sh | sh
#
# Installs @logicsrc/opencontext (commands: opencontext) under your home directory. No root, no system
# package manager, no system files touched. If this machine has no Node 20+,
# an official Node build is fetched (checksum verified) into the same private directory
# and used from there. Run this again to update. Remove with:
# sh ~/.local/share/opencontext/uninstall.sh
#
# sh -s -- --version X install a specific release
# sh -s -- --prefix DIR install root (default: ~/.local)
#
# Listed in the W3BS catalog (https://w3bs.org/packages): `w3bs install opencontext` runs
# this same script and `w3bs update` re-runs it.
#
# Generated from the W3BS npm CLI installer template; keep the variables below in sync
# with package.json. Nothing runs until the last line, so a truncated download is inert.

main() {
set -eu
PKG="@logicsrc/opencontext"
TOOL="opencontext"
URL="https://logicsrc.com/opencontext/install.sh"
NODE_MAJOR="20"
PREFIX="${INSTALL_PREFIX:-$HOME/.local}"
VERSION=""
NODE_CHOICE="${INSTALL_NODE:-auto}"

while [ $# -gt 0 ]; do
case "$1" in
--version) VERSION="${2:?--version needs a value}"; shift ;;
--prefix) PREFIX="${2:?--prefix needs a value}"; shift ;;
-h | --help) echo "Usage: curl -fsSL $URL | sh [-s -- --version X --prefix DIR]"; return 0 ;;
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
*) echo "$TOOL installer: unknown option $1" >&2; return 64 ;;
esac
shift
done

say() { printf '%s\n' "$*"; }
fail() { printf '%s installer: %s\n' "$TOOL" "$*" >&2; exit 1; }

case "$(uname -s)" in
Linux) OS=linux ;;
Darwin) OS=darwin ;;
*) fail "unsupported operating system $(uname -s); Linux and macOS are supported" ;;
esac
case "$(uname -m)" in
x86_64 | amd64) ARCH=x64 ;;
aarch64 | arm64) ARCH=arm64 ;;
*) fail "unsupported CPU $(uname -m)" ;;
esac
if command -v curl >/dev/null 2>&1; then
fetch() { curl --proto '=https' --tlsv1.2 -fsSL "$1" -o "$2"; }
elif command -v wget >/dev/null 2>&1; then
fetch() { wget --https-only -q "$1" -O "$2"; }
else
fail "curl or wget is required"
fi

SHARE="$PREFIX/share/$TOOL"
BIN="$PREFIX/bin"
mkdir -p "$SHARE" "$BIN"

node_major() { "$1" -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0; }
NODE=""
NODE_KIND=""
if [ "$NODE_CHOICE" != private ] && command -v node >/dev/null 2>&1 && [ "$(node_major node)" -ge "$NODE_MAJOR" ]; then
NODE="$(command -v node)"
NODE_KIND=system
elif [ -x "$SHARE/node/bin/node" ] && [ "$(node_major "$SHARE/node/bin/node")" -ge "$NODE_MAJOR" ]; then
NODE="$SHARE/node/bin/node"
NODE_KIND=private
fi
if [ -z "$NODE" ]; then
say "No Node $NODE_MAJOR+ here; fetching an official Node 24 into $SHARE/node ..."
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
fetch "https://nodejs.org/dist/latest-v24.x/SHASUMS256.txt" "$TMP/SHASUMS256.txt" || fail "cannot reach nodejs.org"
TARBALL="$(grep -o "node-v24\.[0-9.]*-$OS-$ARCH\.tar\.gz" "$TMP/SHASUMS256.txt" | head -n 1 || true)"
[ -n "$TARBALL" ] || fail "nodejs.org has no Node 24 build for $OS-$ARCH"
fetch "https://nodejs.org/dist/latest-v24.x/$TARBALL" "$TMP/$TARBALL" || fail "cannot download $TARBALL"
if command -v sha256sum >/dev/null 2>&1; then SUM="$(sha256sum "$TMP/$TARBALL" | cut -d ' ' -f 1)"
else SUM="$(shasum -a 256 "$TMP/$TARBALL" | cut -d ' ' -f 1)"; fi
grep -q "^$SUM $TARBALL\$" "$TMP/SHASUMS256.txt" || fail "checksum mismatch for $TARBALL"
rm -rf "$SHARE/node" && mkdir -p "$SHARE/node"
tar -xzf "$TMP/$TARBALL" -C "$SHARE/node" --strip-components=1 || fail "cannot unpack $TARBALL"
NODE="$SHARE/node/bin/node"
NODE_KIND=private
fi
if [ "$NODE_KIND" = private ]; then NPM="$SHARE/node/bin/npm"; else NPM="$(dirname "$NODE")/npm"; [ -x "$NPM" ] || NPM=npm; fi
# npm runs package scripts with the Node in PATH; make that the chosen one.
PATH="$(dirname "$NODE"):$PATH"
export PATH

SPEC="$PKG"
[ -n "$VERSION" ] && SPEC="$PKG@$VERSION"
say "Installing $SPEC into $SHARE ..."
rm -rf "$SHARE/node_modules" "$SHARE/package.json" "$SHARE/package-lock.json"
cd "$SHARE"
printf '{ "name": "%s-install", "private": true }\n' "$TOOL" > package.json
"$NPM" install --silent --no-audit --no-fund --omit=dev "$SPEC" >/dev/null 2>&1 ||
"$NPM" install --no-audit --no-fund --omit=dev "$SPEC" || fail "npm could not install $SPEC"
PKG_DIR="$SHARE/node_modules/$PKG"
[ -f "$PKG_DIR/package.json" ] || fail "$SPEC installed but $PKG_DIR is missing"

# One shim per entry in the package's own `bin` map: a script, not a symlink, so the
# command always runs this copy with the chosen Node.
BINS="$("$NODE" -e '
const p = require(process.argv[1] + "/package.json");
const b = typeof p.bin === "string" ? { [p.name.split("/").pop()]: p.bin } : (p.bin || {});
for (const [k, v] of Object.entries(b)) if (/^[A-Za-z0-9._-]+$/.test(k)) console.log(k + "\t" + v);
' "$PKG_DIR")"
[ -n "$BINS" ] || fail "$SPEC declares no commands"
PATHS="$SHARE"
NAMES=""
OLDIFS="$IFS"
IFS='
'
for line in $BINS; do
name="${line%% *}"
rel="${line#* }"
entry="$PKG_DIR/$rel"
[ -f "$entry" ] || fail "$SPEC is missing $rel"
printf '#!/bin/sh\n# %s, installed by the %s installer (%s).\nexec "%s" "%s" "$@"\n' "$name" "$TOOL" "$URL" "$NODE" "$entry" > "$BIN/$name"
chmod 0755 "$BIN/$name"
PATHS="$PATHS
$BIN/$name"
NAMES="$NAMES $name"
done
IFS="$OLDIFS"

INSTALLED="$("$NODE" -p "require('$PKG_DIR/package.json').version" 2>/dev/null || echo unknown)"
{
printf '{\n "package": "%s",\n "version": "%s",\n "installer": "%s",\n' "$PKG" "$INSTALLED" "$URL"
printf ' "installedAt": "%s",\n "node": "%s",\n "paths": [\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$NODE_KIND"
printf '%s\n' "$PATHS" | sed 's/.*/ "&",/' | sed '$ s/,$//'
printf ' ]\n}\n'
} > "$SHARE/manifest.json"
{
echo '#!/bin/sh'
echo "# Removes $TOOL. Written by its installer."
echo 'set -eu'
printf '%s\n' "$PATHS" | sed 's|.*|rm -rf "&"|'
echo "echo '$TOOL removed.'"
} > "$SHARE/uninstall.sh"
chmod 0755 "$SHARE/uninstall.sh"

say ""
say "Installed $PKG $INSTALLED:$NAMES (in $BIN)"
case ":$PATH:" in
*":$BIN:"*) ;;
*) say "Add $BIN to your PATH: echo 'export PATH=\"$BIN:\$PATH\"' >> ~/.profile" ;;
esac
say "Update by running the installer again; remove with: sh $SHARE/uninstall.sh"
}

main "$@"
4 changes: 3 additions & 1 deletion docs/opencontext/cli.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# CLI reference

```bash
npx @logicsrc/opencontext <command> # standalone
curl -fsSL https://logicsrc.com/opencontext/install.sh | sh # install the `opencontext` command
opencontext <command> # standalone
npx @logicsrc/opencontext <command> # standalone, without installing
logicsrc context <command> # inside the LogicSRC CLI
```

Expand Down
159 changes: 159 additions & 0 deletions packages/opencontext/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/bin/sh
# opencontext installer.
#
# curl -fsSL https://logicsrc.com/opencontext/install.sh | sh
#
# Installs @logicsrc/opencontext (commands: opencontext) under your home directory. No root, no system
# package manager, no system files touched. If this machine has no Node 20+,
# an official Node build is fetched (checksum verified) into the same private directory
# and used from there. Run this again to update. Remove with:
# sh ~/.local/share/opencontext/uninstall.sh
#
# sh -s -- --version X install a specific release
# sh -s -- --prefix DIR install root (default: ~/.local)
#
# Listed in the W3BS catalog (https://w3bs.org/packages): `w3bs install opencontext` runs
# this same script and `w3bs update` re-runs it.
#
# Generated from the W3BS npm CLI installer template; keep the variables below in sync
# with package.json. Nothing runs until the last line, so a truncated download is inert.

main() {
set -eu
PKG="@logicsrc/opencontext"
TOOL="opencontext"
URL="https://logicsrc.com/opencontext/install.sh"
NODE_MAJOR="20"
PREFIX="${INSTALL_PREFIX:-$HOME/.local}"
VERSION=""
NODE_CHOICE="${INSTALL_NODE:-auto}"

while [ $# -gt 0 ]; do
case "$1" in
--version) VERSION="${2:?--version needs a value}"; shift ;;
--prefix) PREFIX="${2:?--prefix needs a value}"; shift ;;
-h | --help) echo "Usage: curl -fsSL $URL | sh [-s -- --version X --prefix DIR]"; return 0 ;;
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
*) echo "$TOOL installer: unknown option $1" >&2; return 64 ;;
esac
shift
done

say() { printf '%s\n' "$*"; }
fail() { printf '%s installer: %s\n' "$TOOL" "$*" >&2; exit 1; }

case "$(uname -s)" in
Linux) OS=linux ;;
Darwin) OS=darwin ;;
*) fail "unsupported operating system $(uname -s); Linux and macOS are supported" ;;
esac
case "$(uname -m)" in
x86_64 | amd64) ARCH=x64 ;;
aarch64 | arm64) ARCH=arm64 ;;
*) fail "unsupported CPU $(uname -m)" ;;
esac
if command -v curl >/dev/null 2>&1; then
fetch() { curl --proto '=https' --tlsv1.2 -fsSL "$1" -o "$2"; }
elif command -v wget >/dev/null 2>&1; then
fetch() { wget --https-only -q "$1" -O "$2"; }
else
fail "curl or wget is required"
fi

SHARE="$PREFIX/share/$TOOL"
BIN="$PREFIX/bin"
mkdir -p "$SHARE" "$BIN"

node_major() { "$1" -p 'process.versions.node.split(".")[0]' 2>/dev/null || echo 0; }
NODE=""
NODE_KIND=""
if [ "$NODE_CHOICE" != private ] && command -v node >/dev/null 2>&1 && [ "$(node_major node)" -ge "$NODE_MAJOR" ]; then
NODE="$(command -v node)"
NODE_KIND=system
elif [ -x "$SHARE/node/bin/node" ] && [ "$(node_major "$SHARE/node/bin/node")" -ge "$NODE_MAJOR" ]; then
NODE="$SHARE/node/bin/node"
NODE_KIND=private
fi
if [ -z "$NODE" ]; then
say "No Node $NODE_MAJOR+ here; fetching an official Node 24 into $SHARE/node ..."
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
fetch "https://nodejs.org/dist/latest-v24.x/SHASUMS256.txt" "$TMP/SHASUMS256.txt" || fail "cannot reach nodejs.org"
TARBALL="$(grep -o "node-v24\.[0-9.]*-$OS-$ARCH\.tar\.gz" "$TMP/SHASUMS256.txt" | head -n 1 || true)"
[ -n "$TARBALL" ] || fail "nodejs.org has no Node 24 build for $OS-$ARCH"
fetch "https://nodejs.org/dist/latest-v24.x/$TARBALL" "$TMP/$TARBALL" || fail "cannot download $TARBALL"
if command -v sha256sum >/dev/null 2>&1; then SUM="$(sha256sum "$TMP/$TARBALL" | cut -d ' ' -f 1)"
else SUM="$(shasum -a 256 "$TMP/$TARBALL" | cut -d ' ' -f 1)"; fi
grep -q "^$SUM $TARBALL\$" "$TMP/SHASUMS256.txt" || fail "checksum mismatch for $TARBALL"
rm -rf "$SHARE/node" && mkdir -p "$SHARE/node"
tar -xzf "$TMP/$TARBALL" -C "$SHARE/node" --strip-components=1 || fail "cannot unpack $TARBALL"
NODE="$SHARE/node/bin/node"
NODE_KIND=private
fi
if [ "$NODE_KIND" = private ]; then NPM="$SHARE/node/bin/npm"; else NPM="$(dirname "$NODE")/npm"; [ -x "$NPM" ] || NPM=npm; fi
# npm runs package scripts with the Node in PATH; make that the chosen one.
PATH="$(dirname "$NODE"):$PATH"
export PATH

SPEC="$PKG"
[ -n "$VERSION" ] && SPEC="$PKG@$VERSION"
say "Installing $SPEC into $SHARE ..."
rm -rf "$SHARE/node_modules" "$SHARE/package.json" "$SHARE/package-lock.json"
cd "$SHARE"
printf '{ "name": "%s-install", "private": true }\n' "$TOOL" > package.json
"$NPM" install --silent --no-audit --no-fund --omit=dev "$SPEC" >/dev/null 2>&1 ||
"$NPM" install --no-audit --no-fund --omit=dev "$SPEC" || fail "npm could not install $SPEC"
PKG_DIR="$SHARE/node_modules/$PKG"
[ -f "$PKG_DIR/package.json" ] || fail "$SPEC installed but $PKG_DIR is missing"

# One shim per entry in the package's own `bin` map: a script, not a symlink, so the
# command always runs this copy with the chosen Node.
BINS="$("$NODE" -e '
const p = require(process.argv[1] + "/package.json");
const b = typeof p.bin === "string" ? { [p.name.split("/").pop()]: p.bin } : (p.bin || {});
for (const [k, v] of Object.entries(b)) if (/^[A-Za-z0-9._-]+$/.test(k)) console.log(k + "\t" + v);
' "$PKG_DIR")"
[ -n "$BINS" ] || fail "$SPEC declares no commands"
PATHS="$SHARE"
NAMES=""
OLDIFS="$IFS"
IFS='
'
for line in $BINS; do
name="${line%% *}"
rel="${line#* }"
entry="$PKG_DIR/$rel"
[ -f "$entry" ] || fail "$SPEC is missing $rel"
printf '#!/bin/sh\n# %s, installed by the %s installer (%s).\nexec "%s" "%s" "$@"\n' "$name" "$TOOL" "$URL" "$NODE" "$entry" > "$BIN/$name"
chmod 0755 "$BIN/$name"
PATHS="$PATHS
$BIN/$name"
NAMES="$NAMES $name"
done
IFS="$OLDIFS"

INSTALLED="$("$NODE" -p "require('$PKG_DIR/package.json').version" 2>/dev/null || echo unknown)"
{
printf '{\n "package": "%s",\n "version": "%s",\n "installer": "%s",\n' "$PKG" "$INSTALLED" "$URL"
printf ' "installedAt": "%s",\n "node": "%s",\n "paths": [\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$NODE_KIND"
printf '%s\n' "$PATHS" | sed 's/.*/ "&",/' | sed '$ s/,$//'
printf ' ]\n}\n'
} > "$SHARE/manifest.json"
{
echo '#!/bin/sh'
echo "# Removes $TOOL. Written by its installer."
echo 'set -eu'
printf '%s\n' "$PATHS" | sed 's|.*|rm -rf "&"|'
echo "echo '$TOOL removed.'"
} > "$SHARE/uninstall.sh"
chmod 0755 "$SHARE/uninstall.sh"

say ""
say "Installed $PKG $INSTALLED:$NAMES (in $BIN)"
case ":$PATH:" in
*":$BIN:"*) ;;
*) say "Add $BIN to your PATH: echo 'export PATH=\"$BIN:\$PATH\"' >> ~/.profile" ;;
esac
say "Update by running the installer again; remove with: sh $SHARE/uninstall.sh"
}

main "$@"
Loading