Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ on:
pull_request:
branches: [main]

# sphere-cli consumes @unicitylabs/sphere-sdk via `file:../../sphere-sdk`
# because the CLI-extraction commit in sphere-sdk (which promoted types
# like CreateInvoiceRequest, PayInvoiceParams to the public exports)
# landed AFTER the sphere-sdk v0.7.0 npm release. Until a new sphere-sdk
# version is published, CI clones the sibling repo into the expected
# `../../sphere-sdk` location and builds it in-place so the `file:`
# dependency resolves correctly.
#
# Swap to an npm-published version once sphere-sdk v0.7.1+ ships.

jobs:
lint-typecheck-test:
name: lint + typecheck + test
Expand All @@ -15,16 +25,49 @@ jobs:
node-version: [20.x, 22.x]
steps:
- uses: actions/checkout@v4
# Default path: $GITHUB_WORKSPACE/
# = /home/runner/work/sphere-cli/sphere-cli/
# package.json's `file:../../sphere-sdk` resolves from there to
# /home/runner/work/sphere-sdk/ — that's where we clone below.

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm

- name: Clone sphere-sdk sibling
# Pin to a specific commit SHA (not a branch name) for supply-chain
# integrity — a branch pointer can be force-pushed or rebased,
# silently changing the code CI builds against. This SHA points at
# the tip of `refactor/extract-cli-to-sphere-cli` at the time of
# this commit; that branch contains `bc07e89 feat(cli-extraction)`
# which promoted CLI-consumed types (CreateInvoiceRequest,
# PayInvoiceParams, encrypt/decrypt helpers, etc.) to the public
# module surface. Those exports have not yet landed on `main` or
# in any published npm version.
#
# Bump this SHA when a new sphere-sdk commit is required; remove
# this whole workaround once sphere-sdk publishes v0.7.1+ to npm
# with the post-extraction exports.
env:
SPHERE_SDK_SHA: 86468103ac25271b96a338f64349dd0eb472689f
run: |
git clone https://github.com/unicity-sphere/sphere-sdk.git ../../sphere-sdk
git -C ../../sphere-sdk checkout --detach "$SPHERE_SDK_SHA"

- name: "Build sphere-sdk (required for file: dependency to resolve types)"
run: |
cd ../../sphere-sdk
npm ci
npm run build

- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm run test
- run: npm run build

- name: Verify bin shim is executable
run: |
chmod +x bin/sphere.mjs
Expand Down
30 changes: 25 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,29 @@ The unified CLI for the Sphere SDK and agentic-hosting control — DM-native.

## Status

**Phase 1 scaffold.** Real commands are not wired yet. `sphere --help` shows
the full command topology from the migration plan so you can see what's coming.
**Phase 2 — legacy bridge + live host commands.** The full wallet / balance /
payments / dm / market / swap / invoice / nametag / crypto / util / faucet /
daemon / config / completions surface is wired via the legacy sphere-sdk
dispatcher. The DM-native `sphere host` namespace (HMCP-0 over Sphere DMs) is
live and production-ready. Phase 4 (`sphere tenant` — ACP over DMs) remains
stubbed and exits with a pointer to the migration schedule.

See [`docs/SPHERE-CLI-EXTRACTION-PLAN.md`](https://github.com/unicity-sphere/sphere-sdk/blob/refactor/extract-cli-to-sphere-cli/docs/SPHERE-CLI-EXTRACTION-PLAN.md)
for the full migration plan (same doc lives in `agentic-hosting` under the
parallel refactor branch).

### What works today

| Namespace | Status | Notes |
|---|---|---|
| `sphere wallet` | legacy bridge | list, use, create, current, delete, init, status |
| `sphere balance` / `payments` / `dm` / `group` | legacy bridge | |
| `sphere market` / `swap` / `invoice` | legacy bridge | |
| `sphere nametag` / `crypto` / `util` / `faucet` | legacy bridge | |
| `sphere daemon` / `config` / `completions` | legacy bridge | |
| `sphere host` | **DM-native (live)** | HMCP-0: spawn, list, stop, start, inspect, remove, pause, resume, help, cmd |
| `sphere tenant` | Phase 4 (stub) | Exits with scheduled message |

## Install

```bash
Expand All @@ -22,10 +38,14 @@ npm install -g @unicity-sphere/cli
```bash
sphere --help
sphere --version
```

Phase 1 provides only scaffolding. Invoking any namespace (e.g. `sphere wallet`)
prints a pointer to the migration schedule and exits non-zero.
# Legacy bridge example
sphere wallet init --network testnet

# DM-native host example
sphere host list --manager @myhostmanager
sphere host spawn --manager @myhostmanager --template tpl-1 mybot
```

## Development

Expand Down
19 changes: 14 additions & 5 deletions bin/sphere.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,24 @@ const srcEntry = resolve(__dirname, '../src/index.ts');
if (existsSync(distEntry)) {
const mod = await import(distEntry);
const code = await mod.main(process.argv);
if (code !== 0) process.exit(code);
// Always exit explicitly so open handles (Nostr sockets, timers) don't keep
// the process alive after the command finishes. Daemon commands manage their
// own keep-alive via event loop references and never return from main().
process.exit(code);
} else if (existsSync(srcEntry)) {
// Dev mode: delegate to tsx (must be installed).
// Dev mode: resolve tsx from local node_modules to avoid PATH injection.
const { spawn } = await import('node:child_process');
const child = spawn('npx', ['tsx', srcEntry, ...process.argv.slice(2)], {
const tsxBin = resolve(__dirname, '../node_modules/.bin/tsx');
const tsxEntry = existsSync(tsxBin) ? tsxBin : 'npx tsx';
const [cmd, ...cmdArgs] = tsxEntry.includes(' ')
? ['npx', 'tsx']
: [tsxBin];
const child = spawn(cmd, [...cmdArgs, srcEntry, ...process.argv.slice(2)], {
stdio: 'inherit',
});
child.on('exit', (code) => process.exit(code ?? 0));
// Use 'close' (not 'exit') so all child stdio flushes before we exit.
child.on('close', (code) => process.exit(code ?? 0));
} else {
console.error('sphere: no entry found. Did you run `npm run build`?');
process.stderr.write('sphere: no entry found. Did you run `npm run build`?\n');
process.exit(1);
}
9 changes: 8 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@ import globals from 'globals';

export default tseslint.config(
{
ignores: ['dist/**', 'node_modules/**', 'coverage/**', '**/*.d.ts'],
ignores: [
'dist/**',
'node_modules/**',
'coverage/**',
'**/*.d.ts',
// Agent-worktree scratch directories from Claude Code — not source.
'.claude/**',
],
},
js.configs.recommended,
...tseslint.configs.recommended,
Expand Down
77 changes: 77 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"prepublishOnly": "npm run check && npm run build"
},
"dependencies": {
"@unicitylabs/sphere-sdk": "file:../../sphere-sdk",
"commander": "^12.1.0"
},
"devDependencies": {
Expand Down
Loading
Loading