silicon-cli 1.0.0__tar.gz → 1.0.4__tar.gz
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.
- silicon_cli-1.0.4/PKG-INFO +139 -0
- silicon_cli-1.0.4/README.md +122 -0
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/pyproject.toml +2 -2
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli/__init__.py +1 -1
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli/cli.py +10 -5
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli/config.py +20 -3
- silicon_cli-1.0.4/silicon_cli/interface_cli.py +182 -0
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli/stemcell.py +8 -30
- silicon_cli-1.0.4/silicon_cli/sync.py +466 -0
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli/update.py +1 -1
- silicon_cli-1.0.4/silicon_cli.egg-info/PKG-INFO +139 -0
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli.egg-info/SOURCES.txt +2 -0
- silicon_cli-1.0.4/silicon_cli.egg-info/requires.txt +1 -0
- silicon_cli-1.0.0/PKG-INFO +0 -74
- silicon_cli-1.0.0/README.md +0 -58
- silicon_cli-1.0.0/silicon_cli/sync.py +0 -216
- silicon_cli-1.0.0/silicon_cli.egg-info/PKG-INFO +0 -74
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/LICENSE +0 -0
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/setup.cfg +0 -0
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli/glassagent.py +0 -0
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli/process.py +0 -0
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli/registry.py +0 -0
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli/ui.py +0 -0
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli.egg-info/dependency_links.txt +0 -0
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli.egg-info/entry_points.txt +0 -0
- {silicon_cli-1.0.0 → silicon_cli-1.0.4}/silicon_cli.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: silicon-cli
|
|
3
|
+
Version: 1.0.4
|
|
4
|
+
Summary: Silicon CLI — create, run, and manage your silicon instances.
|
|
5
|
+
Author: Saket
|
|
6
|
+
Project-URL: Homepage, https://github.com/saket1225/silicon-cli
|
|
7
|
+
Project-URL: Repository, https://github.com/saket1225/silicon-cli
|
|
8
|
+
Keywords: silicon,cli,agents
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.9
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
Requires-Dist: certifi>=2024.2.2
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# silicon-cli
|
|
19
|
+
|
|
20
|
+
This is the single source for the installable **`silicon`** command. The PyPI
|
|
21
|
+
package is named `silicon-cli`, and the code lives here in this `silicon-cli`
|
|
22
|
+
repo.
|
|
23
|
+
|
|
24
|
+
The command manages silicon instances on a machine: create them from the
|
|
25
|
+
[silicon-stemcell](https://github.com/unlikefraction/silicon-stemcell) base,
|
|
26
|
+
start/stop them under an auto-restart watchdog, stream logs, and back them up to
|
|
27
|
+
Glass. It reads the same `~/.silicon/registry.json`, so existing installs carry
|
|
28
|
+
over unchanged.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install silicon-cli
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
(Zero runtime dependencies — stdlib only.)
|
|
37
|
+
|
|
38
|
+
## Commands
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
silicon Show status or list instances
|
|
42
|
+
silicon new [dir] Create a new Silicon (hydrate from stemcell)
|
|
43
|
+
silicon new . Hydrate the current folder into a runnable silicon
|
|
44
|
+
silicon start <target> Start silicon(s). target = name, index, 1,2,4, or all
|
|
45
|
+
silicon stop [--full] <target> Stop silicon(s) (--full also stops the glass agent)
|
|
46
|
+
silicon restart <target> Restart silicon(s)
|
|
47
|
+
silicon agent <start|stop|status> [name] Manage the per-silicon glass agent
|
|
48
|
+
silicon status [name] Show instance status
|
|
49
|
+
silicon browser [name] Open a headed browser for login
|
|
50
|
+
silicon debug [name] Tail a running instance's logs
|
|
51
|
+
silicon attach [path] Register an existing silicon directory
|
|
52
|
+
silicon pull [api_token] Pull a Glass silicon into a new local folder
|
|
53
|
+
silicon push [name] [now|stop] Daily 23:59 GMT backups to Glass (now = one-shot, stop = end loop)
|
|
54
|
+
silicon backup [name] [now|stop] Alias for silicon push
|
|
55
|
+
silicon update <target> Update silicon(s) from the latest stemcell
|
|
56
|
+
silicon list List all instances
|
|
57
|
+
silicon script update Update this CLI itself
|
|
58
|
+
silicon help Show help
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Configuration (env vars)
|
|
62
|
+
|
|
63
|
+
| Var | Default | Purpose |
|
|
64
|
+
| --- | --- | --- |
|
|
65
|
+
| `SILICON_HOME` | `~/.silicon` | registry + CLI state |
|
|
66
|
+
| `GLASS_SERVER_URL` | `https://glass.teamofsilicons.com` | Glass sync server (pull/push) |
|
|
67
|
+
| `SILICON_STEMCELL_REPO` | `unlikefraction/silicon-stemcell` | base for `new` |
|
|
68
|
+
| `SILICON_GLASS_CLI_REPO` | `unlikefraction/glass` | glass backup CLI |
|
|
69
|
+
| `SILICON_PYTHON` | `python3` | interpreter used to run a silicon's `main.py` |
|
|
70
|
+
| `SILICON_INTERFACE_CLI_PACKAGE` | `@teamofsilicons/silicon-interface-cli` | npm package used to install the Silicon Interface CLI |
|
|
71
|
+
| `SILICON_INTERFACE_CLI_TARBALL` | versioned npm tarball | fallback package URL if registry metadata is briefly unavailable |
|
|
72
|
+
| `SILICON_INTERFACE_CLI_SOURCE` | *(empty)* | local package dir or `silicon-interface.mjs` path for dev installs |
|
|
73
|
+
| `SILICON_INTERFACE_CLI_SKIP` | *(empty)* | set to `1` to skip interface CLI setup |
|
|
74
|
+
| `SILICON_INTERFACE_DAEMON_SKIP` | *(empty)* | set to `1` to install the CLI without starting its listener daemon |
|
|
75
|
+
|
|
76
|
+
## Silicon Interface CLI
|
|
77
|
+
|
|
78
|
+
`silicon new`, `silicon install`, and `silicon pull` also set up the
|
|
79
|
+
Silicon Interface CLI in the silicon folder when Node 22+ is available.
|
|
80
|
+
When a Glass `.glass.json` is present, setup also starts the background listener
|
|
81
|
+
daemon so the silicon receives live conversation frames without polling.
|
|
82
|
+
|
|
83
|
+
`silicon pull` is token-native. Generate an API token from the silicon detail
|
|
84
|
+
page in Glass, then run:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
silicon pull
|
|
88
|
+
# paste the token when prompted
|
|
89
|
+
|
|
90
|
+
# or, less private because it lands in shell history:
|
|
91
|
+
silicon pull scs_live_...
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The command validates the token with Glass, creates a folder named after the
|
|
95
|
+
silicon, hydrates the stemcell, writes `.glass.json`, `.env`, and `env.py`,
|
|
96
|
+
registers the instance, and starts the Silicon Interface daemon.
|
|
97
|
+
|
|
98
|
+
Provider API keys for voice, browser profiles, billing, and architecture
|
|
99
|
+
generation are configured on the Glass backend. A Glass-pulled silicon only
|
|
100
|
+
stores its Glass API token locally.
|
|
101
|
+
|
|
102
|
+
The local wrappers are written to:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
<silicon>/.silicon-interface/bin/si
|
|
106
|
+
<silicon>/.silicon-interface/bin/silicon-interface
|
|
107
|
+
<silicon>/.silicon-interface/inbox.jsonl
|
|
108
|
+
<silicon>/.silicon-interface/state.json
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
For Glass-pulled silicons, those wrappers automatically use the folder's
|
|
112
|
+
`.glass.json` (`server_url` + `api_key`) for conversation API auth.
|
|
113
|
+
|
|
114
|
+
During local development, point `SILICON_INTERFACE_CLI_SOURCE` at the package:
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
SILICON_INTERFACE_CLI_SOURCE=../silicon-interface/packages/silicon-interface-cli silicon new ./ada
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Backups
|
|
121
|
+
|
|
122
|
+
`silicon push <name> now` and `silicon backup <name> now` use the
|
|
123
|
+
`.backupsilicon` manifest when the instance has one. The CLI archives those
|
|
124
|
+
paths and uploads them to Glass via `/api/v1/silicon-backups/` with the
|
|
125
|
+
instance's `.glass.json` API key. If no manifest exists, the command falls back
|
|
126
|
+
to the legacy `glass push` snapshot flow.
|
|
127
|
+
|
|
128
|
+
`silicon push <name>` starts the background loop. Scheduled manifest backups run
|
|
129
|
+
daily at 23:59 GMT; `now` remains available for one-shot manual backups.
|
|
130
|
+
|
|
131
|
+
## How it differs from the bash version
|
|
132
|
+
|
|
133
|
+
- Pure Python package with a `silicon` console entry point (installed in an
|
|
134
|
+
isolated venv), instead of a single bash script.
|
|
135
|
+
- The auto-restart watchdog runs as `silicon _watchdog` (a detached supervisor
|
|
136
|
+
process) rather than a backgrounded bash function — same crash-loop detection,
|
|
137
|
+
`.silicon.stop` sentinel, and `.silicon.pid`/`.silicon.log` behavior.
|
|
138
|
+
- `silicon script update` reinstalls the package via pip from its recorded source.
|
|
139
|
+
- Everything (server, stemcell repo) is env-overridable.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# silicon-cli
|
|
2
|
+
|
|
3
|
+
This is the single source for the installable **`silicon`** command. The PyPI
|
|
4
|
+
package is named `silicon-cli`, and the code lives here in this `silicon-cli`
|
|
5
|
+
repo.
|
|
6
|
+
|
|
7
|
+
The command manages silicon instances on a machine: create them from the
|
|
8
|
+
[silicon-stemcell](https://github.com/unlikefraction/silicon-stemcell) base,
|
|
9
|
+
start/stop them under an auto-restart watchdog, stream logs, and back them up to
|
|
10
|
+
Glass. It reads the same `~/.silicon/registry.json`, so existing installs carry
|
|
11
|
+
over unchanged.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install silicon-cli
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
(Zero runtime dependencies — stdlib only.)
|
|
20
|
+
|
|
21
|
+
## Commands
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
silicon Show status or list instances
|
|
25
|
+
silicon new [dir] Create a new Silicon (hydrate from stemcell)
|
|
26
|
+
silicon new . Hydrate the current folder into a runnable silicon
|
|
27
|
+
silicon start <target> Start silicon(s). target = name, index, 1,2,4, or all
|
|
28
|
+
silicon stop [--full] <target> Stop silicon(s) (--full also stops the glass agent)
|
|
29
|
+
silicon restart <target> Restart silicon(s)
|
|
30
|
+
silicon agent <start|stop|status> [name] Manage the per-silicon glass agent
|
|
31
|
+
silicon status [name] Show instance status
|
|
32
|
+
silicon browser [name] Open a headed browser for login
|
|
33
|
+
silicon debug [name] Tail a running instance's logs
|
|
34
|
+
silicon attach [path] Register an existing silicon directory
|
|
35
|
+
silicon pull [api_token] Pull a Glass silicon into a new local folder
|
|
36
|
+
silicon push [name] [now|stop] Daily 23:59 GMT backups to Glass (now = one-shot, stop = end loop)
|
|
37
|
+
silicon backup [name] [now|stop] Alias for silicon push
|
|
38
|
+
silicon update <target> Update silicon(s) from the latest stemcell
|
|
39
|
+
silicon list List all instances
|
|
40
|
+
silicon script update Update this CLI itself
|
|
41
|
+
silicon help Show help
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Configuration (env vars)
|
|
45
|
+
|
|
46
|
+
| Var | Default | Purpose |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
| `SILICON_HOME` | `~/.silicon` | registry + CLI state |
|
|
49
|
+
| `GLASS_SERVER_URL` | `https://glass.teamofsilicons.com` | Glass sync server (pull/push) |
|
|
50
|
+
| `SILICON_STEMCELL_REPO` | `unlikefraction/silicon-stemcell` | base for `new` |
|
|
51
|
+
| `SILICON_GLASS_CLI_REPO` | `unlikefraction/glass` | glass backup CLI |
|
|
52
|
+
| `SILICON_PYTHON` | `python3` | interpreter used to run a silicon's `main.py` |
|
|
53
|
+
| `SILICON_INTERFACE_CLI_PACKAGE` | `@teamofsilicons/silicon-interface-cli` | npm package used to install the Silicon Interface CLI |
|
|
54
|
+
| `SILICON_INTERFACE_CLI_TARBALL` | versioned npm tarball | fallback package URL if registry metadata is briefly unavailable |
|
|
55
|
+
| `SILICON_INTERFACE_CLI_SOURCE` | *(empty)* | local package dir or `silicon-interface.mjs` path for dev installs |
|
|
56
|
+
| `SILICON_INTERFACE_CLI_SKIP` | *(empty)* | set to `1` to skip interface CLI setup |
|
|
57
|
+
| `SILICON_INTERFACE_DAEMON_SKIP` | *(empty)* | set to `1` to install the CLI without starting its listener daemon |
|
|
58
|
+
|
|
59
|
+
## Silicon Interface CLI
|
|
60
|
+
|
|
61
|
+
`silicon new`, `silicon install`, and `silicon pull` also set up the
|
|
62
|
+
Silicon Interface CLI in the silicon folder when Node 22+ is available.
|
|
63
|
+
When a Glass `.glass.json` is present, setup also starts the background listener
|
|
64
|
+
daemon so the silicon receives live conversation frames without polling.
|
|
65
|
+
|
|
66
|
+
`silicon pull` is token-native. Generate an API token from the silicon detail
|
|
67
|
+
page in Glass, then run:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
silicon pull
|
|
71
|
+
# paste the token when prompted
|
|
72
|
+
|
|
73
|
+
# or, less private because it lands in shell history:
|
|
74
|
+
silicon pull scs_live_...
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The command validates the token with Glass, creates a folder named after the
|
|
78
|
+
silicon, hydrates the stemcell, writes `.glass.json`, `.env`, and `env.py`,
|
|
79
|
+
registers the instance, and starts the Silicon Interface daemon.
|
|
80
|
+
|
|
81
|
+
Provider API keys for voice, browser profiles, billing, and architecture
|
|
82
|
+
generation are configured on the Glass backend. A Glass-pulled silicon only
|
|
83
|
+
stores its Glass API token locally.
|
|
84
|
+
|
|
85
|
+
The local wrappers are written to:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
<silicon>/.silicon-interface/bin/si
|
|
89
|
+
<silicon>/.silicon-interface/bin/silicon-interface
|
|
90
|
+
<silicon>/.silicon-interface/inbox.jsonl
|
|
91
|
+
<silicon>/.silicon-interface/state.json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For Glass-pulled silicons, those wrappers automatically use the folder's
|
|
95
|
+
`.glass.json` (`server_url` + `api_key`) for conversation API auth.
|
|
96
|
+
|
|
97
|
+
During local development, point `SILICON_INTERFACE_CLI_SOURCE` at the package:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
SILICON_INTERFACE_CLI_SOURCE=../silicon-interface/packages/silicon-interface-cli silicon new ./ada
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Backups
|
|
104
|
+
|
|
105
|
+
`silicon push <name> now` and `silicon backup <name> now` use the
|
|
106
|
+
`.backupsilicon` manifest when the instance has one. The CLI archives those
|
|
107
|
+
paths and uploads them to Glass via `/api/v1/silicon-backups/` with the
|
|
108
|
+
instance's `.glass.json` API key. If no manifest exists, the command falls back
|
|
109
|
+
to the legacy `glass push` snapshot flow.
|
|
110
|
+
|
|
111
|
+
`silicon push <name>` starts the background loop. Scheduled manifest backups run
|
|
112
|
+
daily at 23:59 GMT; `now` remains available for one-shot manual backups.
|
|
113
|
+
|
|
114
|
+
## How it differs from the bash version
|
|
115
|
+
|
|
116
|
+
- Pure Python package with a `silicon` console entry point (installed in an
|
|
117
|
+
isolated venv), instead of a single bash script.
|
|
118
|
+
- The auto-restart watchdog runs as `silicon _watchdog` (a detached supervisor
|
|
119
|
+
process) rather than a backgrounded bash function — same crash-loop detection,
|
|
120
|
+
`.silicon.stop` sentinel, and `.silicon.pid`/`.silicon.log` behavior.
|
|
121
|
+
- `silicon script update` reinstalls the package via pip from its recorded source.
|
|
122
|
+
- Everything (server, stemcell repo) is env-overridable.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "silicon-cli"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.4"
|
|
8
8
|
description = "Silicon CLI — create, run, and manage your silicon instances."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -15,7 +15,7 @@ classifiers = [
|
|
|
15
15
|
"License :: OSI Approved :: MIT License",
|
|
16
16
|
"Operating System :: OS Independent",
|
|
17
17
|
]
|
|
18
|
-
dependencies = []
|
|
18
|
+
dependencies = ["certifi>=2024.2.2"]
|
|
19
19
|
|
|
20
20
|
[project.urls]
|
|
21
21
|
Homepage = "https://github.com/saket1225/silicon-cli"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""Silicon CLI — manage silicon instances. Python port of the original bash manager."""
|
|
2
|
-
__version__ = "1.0.
|
|
2
|
+
__version__ = "1.0.1"
|
|
@@ -9,7 +9,7 @@ from . import glassagent, process, registry, stemcell, sync, ui, update
|
|
|
9
9
|
from .config import python_run_cmd
|
|
10
10
|
|
|
11
11
|
COMMANDS = ["start", "stop", "restart", "status", "browser", "debug", "attach",
|
|
12
|
-
"pull", "push", "update", "list", "install", "new", "help", "script", "agent"]
|
|
12
|
+
"pull", "push", "backup", "update", "list", "install", "new", "help", "script", "agent"]
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
# ----------------------------------------------------------------- commands
|
|
@@ -164,10 +164,11 @@ def cmd_help() -> None:
|
|
|
164
164
|
silicon browser [name] Open headed browser for login
|
|
165
165
|
silicon debug [name] Attach to running instance (live logs)
|
|
166
166
|
silicon attach [path] Register an existing silicon instance
|
|
167
|
-
silicon pull
|
|
168
|
-
silicon push [name] Start
|
|
167
|
+
silicon pull [api_token] Pull a Glass silicon into a new local folder
|
|
168
|
+
silicon push [name] Start daily 23:59 GMT backup loop to Glass
|
|
169
169
|
silicon push [name] now Push a one-time backup to Glass
|
|
170
|
-
silicon push [name] stop Stop the
|
|
170
|
+
silicon push [name] stop Stop the daily backup loop
|
|
171
|
+
silicon backup [name] [now|stop] Alias for silicon push
|
|
171
172
|
silicon update <target> Update silicon(s) to latest. target = name, index, 1,2,4, or all
|
|
172
173
|
silicon list List all instances
|
|
173
174
|
silicon script update Update the silicon CLI itself
|
|
@@ -199,6 +200,10 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
199
200
|
process.watchdog_loop(name=a2 or "silicon", path=a1, pid_file=argv[3] if len(argv) > 3 else "")
|
|
200
201
|
return
|
|
201
202
|
|
|
203
|
+
if cmd == "_backup_loop": # internal: the scheduled manifest backup loop
|
|
204
|
+
sync.backup_loop(a1 or ".", a2)
|
|
205
|
+
return
|
|
206
|
+
|
|
202
207
|
if cmd == "start":
|
|
203
208
|
process.start(a1)
|
|
204
209
|
elif cmd == "stop":
|
|
@@ -218,7 +223,7 @@ def main(argv: list[str] | None = None) -> None:
|
|
|
218
223
|
cmd_attach(a1)
|
|
219
224
|
elif cmd == "pull":
|
|
220
225
|
sync.pull(a1)
|
|
221
|
-
elif cmd
|
|
226
|
+
elif cmd in ("push", "backup"):
|
|
222
227
|
sync.push(a1, a2)
|
|
223
228
|
elif cmd == "update":
|
|
224
229
|
update.update_instance(a1)
|
|
@@ -11,9 +11,8 @@ REGISTRY_DIR = Path(os.environ.get("SILICON_HOME", HOME / ".silicon"))
|
|
|
11
11
|
REGISTRY_FILE = REGISTRY_DIR / "registry.json"
|
|
12
12
|
CLI_SOURCE_FILE = REGISTRY_DIR / "cli-source" # where `silicon script update` reinstalls from
|
|
13
13
|
|
|
14
|
-
# Glass sync server (pull/push).
|
|
15
|
-
|
|
16
|
-
GLASS_SERVER_URL = os.environ.get("GLASS_SERVER_URL", "https://glass.unlikefraction.com").rstrip("/")
|
|
14
|
+
# Glass sync server (pull/push). Override with GLASS_SERVER_URL to point elsewhere.
|
|
15
|
+
GLASS_SERVER_URL = os.environ.get("GLASS_SERVER_URL", "https://glass.teamofsilicons.com").rstrip("/")
|
|
17
16
|
|
|
18
17
|
# Stemcell — the base every new silicon is hydrated from.
|
|
19
18
|
STEMCELL_REPO = os.environ.get("SILICON_STEMCELL_REPO", "unlikefraction/silicon-stemcell")
|
|
@@ -23,6 +22,24 @@ STEMCELL_ZIP_URL = f"https://github.com/{STEMCELL_REPO}/archive/refs/heads/main.
|
|
|
23
22
|
# Glass CLI (used by pull/push for backups).
|
|
24
23
|
GLASS_CLI_REPO = os.environ.get("SILICON_GLASS_CLI_REPO", "unlikefraction/glass")
|
|
25
24
|
|
|
25
|
+
# Silicon Interface CLI. During local development, silicon-cli will auto-detect
|
|
26
|
+
# a sibling silicon-interface checkout; in production this package spec is used.
|
|
27
|
+
SILICON_INTERFACE_CLI_PACKAGE = os.environ.get(
|
|
28
|
+
"SILICON_INTERFACE_CLI_PACKAGE",
|
|
29
|
+
"@teamofsilicons/silicon-interface-cli",
|
|
30
|
+
)
|
|
31
|
+
SILICON_INTERFACE_CLI_TARBALL = os.environ.get(
|
|
32
|
+
"SILICON_INTERFACE_CLI_TARBALL",
|
|
33
|
+
"https://registry.npmjs.org/@teamofsilicons/silicon-interface-cli/-/silicon-interface-cli-0.1.2.tgz",
|
|
34
|
+
)
|
|
35
|
+
SILICON_INTERFACE_CLI_SOURCE = os.environ.get("SILICON_INTERFACE_CLI_SOURCE", "")
|
|
36
|
+
SILICON_INTERFACE_CLI_SKIP = os.environ.get("SILICON_INTERFACE_CLI_SKIP", "").lower() in {
|
|
37
|
+
"1", "true", "yes", "on",
|
|
38
|
+
}
|
|
39
|
+
SILICON_INTERFACE_DAEMON_SKIP = os.environ.get("SILICON_INTERFACE_DAEMON_SKIP", "").lower() in {
|
|
40
|
+
"1", "true", "yes", "on",
|
|
41
|
+
}
|
|
42
|
+
|
|
26
43
|
|
|
27
44
|
def python_run_cmd() -> str:
|
|
28
45
|
"""The interpreter used to RUN a silicon's main.py (not this CLI's venv)."""
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Install the Silicon Interface CLI into a silicon folder."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from . import ui
|
|
11
|
+
from .config import (
|
|
12
|
+
SILICON_INTERFACE_DAEMON_SKIP,
|
|
13
|
+
SILICON_INTERFACE_CLI_PACKAGE,
|
|
14
|
+
SILICON_INTERFACE_CLI_SKIP,
|
|
15
|
+
SILICON_INTERFACE_CLI_SOURCE,
|
|
16
|
+
SILICON_INTERFACE_CLI_TARBALL,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _node_major() -> int | None:
|
|
21
|
+
node = shutil.which("node")
|
|
22
|
+
if not node:
|
|
23
|
+
return None
|
|
24
|
+
try:
|
|
25
|
+
out = subprocess.run(
|
|
26
|
+
[node, "--version"],
|
|
27
|
+
capture_output=True,
|
|
28
|
+
text=True,
|
|
29
|
+
check=False,
|
|
30
|
+
).stdout.strip()
|
|
31
|
+
except Exception:
|
|
32
|
+
return None
|
|
33
|
+
m = re.match(r"^v?(\d+)", out)
|
|
34
|
+
return int(m.group(1)) if m else None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _source_script() -> Path | None:
|
|
38
|
+
if SILICON_INTERFACE_CLI_SOURCE:
|
|
39
|
+
source = Path(SILICON_INTERFACE_CLI_SOURCE).expanduser().resolve()
|
|
40
|
+
if source.is_file():
|
|
41
|
+
return source
|
|
42
|
+
candidate = source / "bin" / "silicon-interface.mjs"
|
|
43
|
+
if candidate.exists():
|
|
44
|
+
return candidate
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
# Local dev layout: ../silicon-interface/packages/silicon-interface-cli
|
|
48
|
+
repo_root = Path(__file__).resolve().parents[2]
|
|
49
|
+
candidate = (
|
|
50
|
+
repo_root
|
|
51
|
+
/ "silicon-interface"
|
|
52
|
+
/ "packages"
|
|
53
|
+
/ "silicon-interface-cli"
|
|
54
|
+
/ "bin"
|
|
55
|
+
/ "silicon-interface.mjs"
|
|
56
|
+
)
|
|
57
|
+
return candidate if candidate.exists() else None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _run(cmd: list[str], target: Path, *, warn: bool = True) -> bool:
|
|
61
|
+
try:
|
|
62
|
+
proc = subprocess.run(
|
|
63
|
+
cmd,
|
|
64
|
+
cwd=str(target),
|
|
65
|
+
stdout=subprocess.PIPE,
|
|
66
|
+
stderr=subprocess.PIPE,
|
|
67
|
+
text=True,
|
|
68
|
+
check=False,
|
|
69
|
+
)
|
|
70
|
+
except Exception as exc:
|
|
71
|
+
if warn:
|
|
72
|
+
ui.warn(f"Silicon Interface CLI setup skipped: {exc}")
|
|
73
|
+
return False
|
|
74
|
+
if proc.returncode == 0:
|
|
75
|
+
return True
|
|
76
|
+
if not warn:
|
|
77
|
+
return False
|
|
78
|
+
detail = (proc.stderr or proc.stdout or "").strip().splitlines()
|
|
79
|
+
suffix = f": {detail[-1]}" if detail else ""
|
|
80
|
+
ui.warn(f"Silicon Interface CLI setup skipped{suffix}")
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _npm_install_command(target: Path, package_spec: str) -> list[str] | None:
|
|
85
|
+
npm = shutil.which("npm")
|
|
86
|
+
if not npm:
|
|
87
|
+
return None
|
|
88
|
+
return [
|
|
89
|
+
npm,
|
|
90
|
+
"exec",
|
|
91
|
+
"--yes",
|
|
92
|
+
"--package",
|
|
93
|
+
package_spec,
|
|
94
|
+
"--",
|
|
95
|
+
"silicon-interface",
|
|
96
|
+
"install",
|
|
97
|
+
str(target),
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _npm_install_commands(target: Path) -> list[list[str]]:
|
|
102
|
+
package_specs = [SILICON_INTERFACE_CLI_PACKAGE]
|
|
103
|
+
if (
|
|
104
|
+
SILICON_INTERFACE_CLI_TARBALL
|
|
105
|
+
and SILICON_INTERFACE_CLI_TARBALL not in package_specs
|
|
106
|
+
):
|
|
107
|
+
package_specs.append(SILICON_INTERFACE_CLI_TARBALL)
|
|
108
|
+
|
|
109
|
+
commands: list[list[str]] = []
|
|
110
|
+
for package_spec in package_specs:
|
|
111
|
+
cmd = _npm_install_command(target, package_spec)
|
|
112
|
+
if cmd:
|
|
113
|
+
commands.append(cmd)
|
|
114
|
+
return commands
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _start_daemon(target: Path) -> bool:
|
|
118
|
+
if SILICON_INTERFACE_DAEMON_SKIP:
|
|
119
|
+
return False
|
|
120
|
+
if not (target / ".glass.json").exists():
|
|
121
|
+
return False
|
|
122
|
+
si = target / ".silicon-interface" / "bin" / "si"
|
|
123
|
+
if not si.exists():
|
|
124
|
+
return False
|
|
125
|
+
ok = _run([str(si), "daemon", "start"], target, warn=False)
|
|
126
|
+
if ok:
|
|
127
|
+
ui.success("Silicon Interface daemon running.")
|
|
128
|
+
else:
|
|
129
|
+
ui.warn("Silicon Interface daemon was not started; run '.silicon-interface/bin/si daemon start'.")
|
|
130
|
+
return ok
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def setup(target: str | Path) -> bool:
|
|
134
|
+
"""Install local si/silicon-interface wrappers into ``target``.
|
|
135
|
+
|
|
136
|
+
This is intentionally best-effort. A missing Node runtime or unpublished npm
|
|
137
|
+
package should not break `silicon new` or `silicon pull`; the user can rerun
|
|
138
|
+
the setup later after installing Node or setting SILICON_INTERFACE_CLI_SOURCE.
|
|
139
|
+
"""
|
|
140
|
+
if SILICON_INTERFACE_CLI_SKIP:
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
target_path = Path(target).resolve()
|
|
144
|
+
major = _node_major()
|
|
145
|
+
if major is None:
|
|
146
|
+
ui.warn("Silicon Interface CLI setup skipped: node not found.")
|
|
147
|
+
return False
|
|
148
|
+
if major < 22:
|
|
149
|
+
ui.warn(
|
|
150
|
+
"Silicon Interface CLI setup skipped: Node 22+ is required "
|
|
151
|
+
f"(found Node {major})."
|
|
152
|
+
)
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
script = _source_script()
|
|
156
|
+
ui.info("Setting up Silicon Interface CLI...")
|
|
157
|
+
if script:
|
|
158
|
+
ok = _run([shutil.which("node") or "node", str(script), "install", str(target_path)], target_path)
|
|
159
|
+
else:
|
|
160
|
+
commands = _npm_install_commands(target_path)
|
|
161
|
+
if not commands:
|
|
162
|
+
ui.warn("Silicon Interface CLI setup skipped: npm not found.")
|
|
163
|
+
return False
|
|
164
|
+
ok = False
|
|
165
|
+
for index, cmd in enumerate(commands):
|
|
166
|
+
final_attempt = index == len(commands) - 1
|
|
167
|
+
ok = _run(cmd, target_path, warn=final_attempt)
|
|
168
|
+
if ok:
|
|
169
|
+
break
|
|
170
|
+
if not final_attempt:
|
|
171
|
+
ui.warn(
|
|
172
|
+
"Silicon Interface CLI package lookup failed; "
|
|
173
|
+
"retrying with published tarball."
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
if ok:
|
|
177
|
+
ui.success(
|
|
178
|
+
"Silicon Interface CLI ready: "
|
|
179
|
+
f"{target_path}/.silicon-interface/bin/si"
|
|
180
|
+
)
|
|
181
|
+
_start_daemon(target_path)
|
|
182
|
+
return ok
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
`silicon new <dir>` downloads the stemcell, copies in any files the target is
|
|
4
4
|
missing (never clobbering env.py / silicon.json / .glass.json), seeds config +
|
|
5
|
-
env keys, prompts for
|
|
6
|
-
|
|
5
|
+
env keys, prompts for brain/worker providers, installs requirements, and
|
|
6
|
+
registers the instance.
|
|
7
7
|
"""
|
|
8
8
|
from __future__ import annotations
|
|
9
9
|
|
|
@@ -16,7 +16,7 @@ import sys
|
|
|
16
16
|
import tempfile
|
|
17
17
|
from pathlib import Path
|
|
18
18
|
|
|
19
|
-
from . import registry, ui
|
|
19
|
+
from . import interface_cli, registry, ui
|
|
20
20
|
from .config import STEMCELL_GIT_URL, STEMCELL_ZIP_URL, python_run_cmd
|
|
21
21
|
|
|
22
22
|
SKIP_NAMES = {".git", "__pycache__", ".DS_Store"}
|
|
@@ -144,9 +144,9 @@ def hydrate(target: str) -> None:
|
|
|
144
144
|
silicon.pop("version", None)
|
|
145
145
|
sj.write_text(json.dumps(silicon, indent=4) + "\n")
|
|
146
146
|
|
|
147
|
-
# Seed env.py required keys
|
|
147
|
+
# Seed env.py required keys used by the current stemcell.
|
|
148
148
|
env_path = dst / "env.py"
|
|
149
|
-
for key, default in {"
|
|
149
|
+
for key, default in {"GLASS_API_KEY": "", "BROWSER_PROFILE": name}.items():
|
|
150
150
|
if not _env_value(env_path, key):
|
|
151
151
|
_env_upsert(env_path, key, default)
|
|
152
152
|
|
|
@@ -158,7 +158,7 @@ def hydrate(target: str) -> None:
|
|
|
158
158
|
|
|
159
159
|
# Interactive setup
|
|
160
160
|
if ui.interactive():
|
|
161
|
-
_interactive_setup(
|
|
161
|
+
_interactive_setup(sj)
|
|
162
162
|
|
|
163
163
|
# Install dependencies
|
|
164
164
|
req = dst / "requirements.txt"
|
|
@@ -169,36 +169,14 @@ def hydrate(target: str) -> None:
|
|
|
169
169
|
subprocess.run([python_run_cmd(), "-m", "pip", "install", "-r", str(req), "--quiet", "--user"])
|
|
170
170
|
|
|
171
171
|
registry.register(name, abs_target)
|
|
172
|
+
interface_cli.setup(abs_target)
|
|
172
173
|
ui.success(f"Hydrated '{name}' at {abs_target}")
|
|
173
174
|
ui.info(f"Run 'silicon start {name}' when you're ready.")
|
|
174
175
|
finally:
|
|
175
176
|
shutil.rmtree(tmp_src, ignore_errors=True)
|
|
176
177
|
|
|
177
178
|
|
|
178
|
-
def _interactive_setup(
|
|
179
|
-
if not _env_value(env_path, "TELEGRAM_BOT_TOKEN"):
|
|
180
|
-
ui.info("You need a Telegram bot token to use Silicon.")
|
|
181
|
-
sys.stderr.write(f"{ui.DIM} 1. Open Telegram and search for @BotFather{ui.RESET}\n")
|
|
182
|
-
sys.stderr.write(f"{ui.DIM} 2. Send /newbot and follow the prompts{ui.RESET}\n")
|
|
183
|
-
sys.stderr.write(f"{ui.DIM} 3. Copy the token BotFather gives you{ui.RESET}\n")
|
|
184
|
-
token = ui.read_secret("Telegram bot token")
|
|
185
|
-
if not token:
|
|
186
|
-
ui.error("Telegram bot token is required.")
|
|
187
|
-
sys.exit(1)
|
|
188
|
-
_env_upsert(env_path, "TELEGRAM_BOT_TOKEN", token)
|
|
189
|
-
|
|
190
|
-
if not _env_value(env_path, "OPENAI_API_KEY"):
|
|
191
|
-
ui.info("OpenAI API key (for incoming voice transcription via Whisper). Enter to skip.")
|
|
192
|
-
v = ui.read_secret("OpenAI API key (optional)")
|
|
193
|
-
if v:
|
|
194
|
-
_env_upsert(env_path, "OPENAI_API_KEY", v)
|
|
195
|
-
|
|
196
|
-
if not _env_value(env_path, "GEMINI_API_KEY"):
|
|
197
|
-
ui.info("Gemini API key (for outgoing text-to-speech). Enter to skip.")
|
|
198
|
-
v = ui.read_secret("Gemini API key (optional)")
|
|
199
|
-
if v:
|
|
200
|
-
_env_upsert(env_path, "GEMINI_API_KEY", v)
|
|
201
|
-
|
|
179
|
+
def _interactive_setup(sj: Path) -> None:
|
|
202
180
|
# Brain / worker providers — only ask when both claude + codex are present
|
|
203
181
|
brain = "claude"
|
|
204
182
|
workers = {"browser": ["claude"], "terminal": ["claude"], "writer": ["claude"]}
|