samagotchi 0.133.0 → 0.134.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +68 -1
- data/docs/cli.md +11 -0
- data/docs/configuration.md +8 -4
- data/docs/guardrails.md +102 -13
- data/docs/internals/background-tasks.md +1 -0
- data/docs/internals/participants.md +6 -5
- data/docs/internals/tool-guardrails.md +2 -0
- data/docs/plugins.md +2 -1
- data/docs/sessions.md +3 -2
- data/docs/testing.md +7 -2
- data/lib/samagotchi/bundles/system/manifest.yml +1 -1
- data/lib/samagotchi/chi_home.rb +12 -2
- data/lib/samagotchi/desktop/macos.rb +19 -4
- data/lib/samagotchi/engine.rb +13 -0
- data/lib/samagotchi/guardrails/implicit_writers.rb +143 -0
- data/lib/samagotchi/guardrails/pair_boundary.rb +6 -4
- data/lib/samagotchi/guardrails/pair_command.rb +493 -60
- data/lib/samagotchi/guardrails/parent_approvals.rb +1 -1
- data/lib/samagotchi/guardrails/read_only_shell.rb +34 -2
- data/lib/samagotchi/guardrails/shell_git_dirs.rb +6 -1
- data/lib/samagotchi/guardrails/shell_lex.rb +23 -0
- data/lib/samagotchi/guardrails/shell_paths.rb +3 -2
- data/lib/samagotchi/guardrails/targets.rb +8 -3
- data/lib/samagotchi/home_command.rb +22 -6
- data/lib/samagotchi/home_doctor.rb +36 -3
- data/lib/samagotchi/home_notice.rb +8 -1
- data/lib/samagotchi/hooks/loader.rb +5 -0
- data/lib/samagotchi/log_line.rb +12 -0
- data/lib/samagotchi/project_env.rb +3 -11
- data/lib/samagotchi/self_report.rb +3 -1
- data/lib/samagotchi/system_one/self_protect.rb +58 -2
- data/lib/samagotchi/testing/waiting.rb +13 -0
- data/lib/samagotchi/toolkit_binding.rb +16 -0
- data/lib/samagotchi/update_command.rb +7 -3
- data/lib/samagotchi/update_hint.rb +6 -1
- data/lib/samagotchi/version.rb +1 -1
- data/lib/samagotchi/web/public/answer_fold.js +262 -0
- data/lib/samagotchi/web/public/answer_refs.js +2 -0
- data/lib/samagotchi/web/public/app.js +18 -1
- data/lib/samagotchi/web/public/cards_place.js +5 -2
- data/lib/samagotchi/web/public/chip_popover.js +73 -11
- data/lib/samagotchi/web/public/claim_link.js +4 -2
- data/lib/samagotchi/web/public/composer.css +3 -1
- data/lib/samagotchi/web/public/history_reveal.js +23 -0
- data/lib/samagotchi/web/public/index.html +44 -28
- data/lib/samagotchi/web/public/llm_context_chip.js +5 -13
- data/lib/samagotchi/web/public/refs.js +6 -7
- data/lib/samagotchi/web/public/review_start.js +1 -1
- data/lib/samagotchi/web/public/start_page.js +4 -4
- data/lib/samagotchi/web/public/start_settings.js +29 -18
- data/lib/samagotchi/web/public/start_toolkit.js +5 -4
- data/lib/samagotchi/web/public/timing.js +5 -1
- data/lib/samagotchi/web/public/turn_html.js +12 -6
- data/lib/samagotchi/web/public/turn_range.js +32 -0
- data/lib/samagotchi/web/public/turn_view.js +21 -11
- data/lib/samagotchi/web/public/verdict_card.js +5 -13
- data/lib/samagotchi/web/public/verify_target.js +7 -10
- metadata +5 -1
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Samagotchi
|
|
4
|
+
module Guardrails
|
|
5
|
+
# Programs that write outside the folder they run in with no word of
|
|
6
|
+
# the command naming where: a global install (npm -g, gem install, brew,
|
|
7
|
+
# cargo/go install, pipx, uv tool), a user config file (npm config set
|
|
8
|
+
# writes ~/.npmrc, gh config set ~/.config/gh, bundle config set), the
|
|
9
|
+
# system's defaults, a launchd job, a tool manager's global version, git
|
|
10
|
+
# maintenance's schedule and git lfs's global filters. A pair turn's "a
|
|
11
|
+
# write outside the project always asks" counts them as outside writes
|
|
12
|
+
# (PairCommand#outside_writes?). The table holds examples, not a closed
|
|
13
|
+
# set: a program it doesn't know runs unasked.
|
|
14
|
+
#
|
|
15
|
+
# pip counts with or without --user: it writes into its interpreter's
|
|
16
|
+
# site-packages, which no word names and which a plain `pip` (the
|
|
17
|
+
# worker's PATH, any VIRTUAL_ENV it inherited) may place anywhere. A pip
|
|
18
|
+
# or python named by a path into the project (.venv/bin/pip) installs
|
|
19
|
+
# into the project, and -t/--target/--prefix/--root name where it
|
|
20
|
+
# writes (their value is checked as any word): those don't count
|
|
21
|
+
# (PairCommand checks the program's path: LOCAL_INSTALLERS).
|
|
22
|
+
#
|
|
23
|
+
# Not counted: a project install (npm install, bundle install, uv sync)
|
|
24
|
+
# and the caches every build writes (~/.npm, ~/.cache, go's module
|
|
25
|
+
# cache): they hold downloads, not what runs as the owner.
|
|
26
|
+
module ImplicitWriters
|
|
27
|
+
NPM_CHANGES = %w[install i in add isntall uninstall un unlink remove rm r update up upgrade link ln].freeze
|
|
28
|
+
# program => the word groups of a call that writes outside: its
|
|
29
|
+
# arguments hold a word of every group of one entry (:global: npm's
|
|
30
|
+
# global flag in any spelling, .global?; [] alone: any call).
|
|
31
|
+
BY_VERB = {
|
|
32
|
+
"npm" => [[NPM_CHANGES, :global], [%w[link ln]], [%w[config c], %w[set delete rm edit fix]],
|
|
33
|
+
[%w[set login adduser add-user logout]]],
|
|
34
|
+
"yarn" => [[%w[global]], [%w[link]], [%w[config], %w[set delete unset]]],
|
|
35
|
+
"pnpm" => [[NPM_CHANGES, :global], [%w[config], %w[set delete]], [%w[setup]]],
|
|
36
|
+
"bun" => [[NPM_CHANGES, :global], [%w[link]]],
|
|
37
|
+
"pip" => [[%w[install uninstall]], [%w[config], %w[set unset edit]]],
|
|
38
|
+
"ensurepip" => [[]],
|
|
39
|
+
"easy_install" => [[]],
|
|
40
|
+
"pipenv" => [[%w[install uninstall sync update]]],
|
|
41
|
+
"poetry" => [[%w[self]]],
|
|
42
|
+
"conda" => [[%w[install remove uninstall update upgrade create]]],
|
|
43
|
+
"gem" => [[%w[install i update uninstall cleanup pristine]],
|
|
44
|
+
[%w[sources], %w[-a --add -r --remove -c --clear-all]]],
|
|
45
|
+
"bundle" => [[%w[install], %w[--system]]],
|
|
46
|
+
"defaults" => [[%w[write delete rename import]]],
|
|
47
|
+
"cargo" => [[%w[install uninstall]]],
|
|
48
|
+
"go" => [[%w[install]], [%w[env], %w[-w -u]]],
|
|
49
|
+
"uv" => [[%w[tool], %w[install uninstall upgrade update-shell]], [%w[python], %w[install uninstall]],
|
|
50
|
+
[%w[self], %w[update]], [%w[pip], %w[install uninstall sync], %w[--system]]],
|
|
51
|
+
"deno" => [[%w[install uninstall], :global]],
|
|
52
|
+
"dotnet" => [[%w[tool], %w[install update uninstall], %w[-g --global --tool-path]]],
|
|
53
|
+
"corepack" => [[%w[enable disable]], [%w[install], :global]],
|
|
54
|
+
"pyenv" => [[%w[global install uninstall rehash]]],
|
|
55
|
+
"rbenv" => [[%w[global install uninstall rehash]]],
|
|
56
|
+
"nodenv" => [[%w[global install uninstall rehash]]],
|
|
57
|
+
"asdf" => [[%w[install uninstall global plugin reshim]]],
|
|
58
|
+
"volta" => [[%w[install uninstall setup fetch]]],
|
|
59
|
+
"nvm" => [[%w[install uninstall alias unalias]]],
|
|
60
|
+
"code" => [[%w[--install-extension --uninstall-extension]]],
|
|
61
|
+
"gh" => [[%w[auth], %w[login logout refresh setup-git switch]], [%w[config], %w[set]],
|
|
62
|
+
[%w[extension extensions ext], %w[install upgrade remove]], [%w[alias], %w[set delete import]]],
|
|
63
|
+
# the global config and a launchd/cron schedule; git lfs's global filters
|
|
64
|
+
"git" => [[%w[maintenance], %w[register start unregister stop]], [%w[lfs], %w[install uninstall]]],
|
|
65
|
+
"git-maintenance" => [[%w[register start unregister stop]]],
|
|
66
|
+
"git-lfs" => [[%w[install uninstall]]],
|
|
67
|
+
"scalar" => [[%w[register unregister clone reconfigure delete]]]
|
|
68
|
+
}.freeze
|
|
69
|
+
# program => the verbs (its first argument) that only read: every
|
|
70
|
+
# other one writes outside.
|
|
71
|
+
READ_VERBS = {
|
|
72
|
+
"brew" => %w[list ls info search desc deps uses outdated leaves config doctor --prefix --cellar --repository
|
|
73
|
+
--version -v --help -h help],
|
|
74
|
+
"pipx" => %w[list environment --version --help -h],
|
|
75
|
+
"launchctl" => %w[list print print-cache print-disabled blame procinfo hostinfo examine version help],
|
|
76
|
+
"rustup" => %w[show which --version -V help --help -h completions]
|
|
77
|
+
}.freeze
|
|
78
|
+
# Installers whose program named by a path into the project (a
|
|
79
|
+
# project venv's pip) install there, and the options that say where.
|
|
80
|
+
LOCAL_INSTALLERS = %w[pip ensurepip easy_install].freeze
|
|
81
|
+
PLACE_OPTIONS = /\A(?:-t|--target|--prefix|--root)(?:=|\z)/
|
|
82
|
+
# pip3, pip3.12 → pip.
|
|
83
|
+
ALIASES = { /\Apip\d*(?:\.\d+)?\z/ => "pip" }.freeze
|
|
84
|
+
# npm's global flag: -g, --global, --global=true, --location=global
|
|
85
|
+
# (and --location global, two words: .global?).
|
|
86
|
+
GLOBAL = /\A(?:-g|--global|--global=(?!false\z).*|--location=global)\z/
|
|
87
|
+
# npm reads its config from the environment too.
|
|
88
|
+
NPM_ENV_GLOBAL = /\Anpm_config_(?:global|location)\z/i
|
|
89
|
+
|
|
90
|
+
module_function
|
|
91
|
+
|
|
92
|
+
# The table's name for +program+ (a word's basename).
|
|
93
|
+
def name(program)
|
|
94
|
+
base = File.basename(program.to_s)
|
|
95
|
+
ALIASES.each { |pattern, to| return to if base.match?(pattern) }
|
|
96
|
+
base
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Whether +program+ (its word) run with +args+ writes outside the
|
|
100
|
+
# folder it runs in with no word naming where. +local+ says the
|
|
101
|
+
# program's word is a path into the project (LOCAL_INSTALLERS);
|
|
102
|
+
# +assigned+ the variables the command sets before it.
|
|
103
|
+
def outside?(program, args, local: false, assigned: {})
|
|
104
|
+
program = name(program)
|
|
105
|
+
if (verbs = READ_VERBS[program])
|
|
106
|
+
return !(args.empty? || verbs.include?(args.first))
|
|
107
|
+
end
|
|
108
|
+
return bundle_config_outside?(args) if program == "bundle" && args.first == "config"
|
|
109
|
+
|
|
110
|
+
entries = BY_VERB[program] or return false
|
|
111
|
+
return false unless entries.any? { |groups| groups.all? { |group| group?(group, args, assigned) } }
|
|
112
|
+
return true unless LOCAL_INSTALLERS.include?(program)
|
|
113
|
+
return true if args.include?("--user")
|
|
114
|
+
|
|
115
|
+
!(local || args.any? { |arg| arg.match?(PLACE_OPTIONS) })
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def group?(group, args, assigned)
|
|
119
|
+
group == :global ? global?(args, assigned) : args.intersect?(group)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# npm's (pnpm's, bun's, deno's, corepack's) global flag, in any of
|
|
123
|
+
# its spellings or the environment's (npm_config_global=true).
|
|
124
|
+
def global?(args, assigned = {})
|
|
125
|
+
args.any? { |arg| arg.match?(GLOBAL) } || args.each_cons(2).include?(%w[--location global]) ||
|
|
126
|
+
assigned.any? { |key, value| key.match?(NPM_ENV_GLOBAL) && !%w[false local project].include?(value.to_s) }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# bundle config set/unset writes ~/.bundle/config unless --local, and
|
|
130
|
+
# so does the old form `bundle config NAME VALUE`; a list or a get
|
|
131
|
+
# (or a NAME alone) only reads.
|
|
132
|
+
def bundle_config_outside?(args)
|
|
133
|
+
rest = args.drop(1)
|
|
134
|
+
return false if rest.include?("--local")
|
|
135
|
+
|
|
136
|
+
words = rest.reject { |arg| arg.start_with?("-") }
|
|
137
|
+
return false if words.empty? || %w[list get].include?(words.first)
|
|
138
|
+
|
|
139
|
+
%w[set unset].include?(words.first) || words.size > 1
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -28,7 +28,7 @@ module Samagotchi
|
|
|
28
28
|
# loopback name, URL openers (open, xdg-open, a browser) and
|
|
29
29
|
# listeners (nc -l, socat …LISTEN, a static HTTP server), background
|
|
30
30
|
# and scheduled jobs, the environment and
|
|
31
|
-
# credential printers, git
|
|
31
|
+
# credential printers, git config writes (reads only), home sweeps
|
|
32
32
|
# and links to them, and a program named by a $var or a glob); a
|
|
33
33
|
# write or edit of git's hooks (GIT_HOOKS);
|
|
34
34
|
# - owner-voiced tools (OWNER_VOICED, and any delegate_* tool): what
|
|
@@ -77,13 +77,15 @@ module Samagotchi
|
|
|
77
77
|
HOME_SWEEP = "a participant's turn can't search, copy or link a folder that holds secrets or chi's own files"
|
|
78
78
|
LINK = "a participant's turn makes a link: a path through it can lead out of the project, so the owner " \
|
|
79
79
|
"approves it"
|
|
80
|
-
GIT_CONFIG = "a participant's turn
|
|
81
|
-
"
|
|
80
|
+
GIT_CONFIG = "a participant's turn writes no git config (git config but its reads, git -c, --config-env, a " \
|
|
81
|
+
"clone's or init's -c/--config, GIT_CONFIG_* and GIT_TEMPLATE_DIR): settings can make git run a " \
|
|
82
|
+
"command in the owner's name"
|
|
82
83
|
ENV_LOADER = "a participant's turn runs mise or direnv: they load the project's env files (a mise.local.toml, " \
|
|
83
84
|
"an .envrc), which may hold the owner's secrets, so the owner approves it"
|
|
84
85
|
MISE_CONFIG = "a participant's turn changes mise's config: the owner's next worker start runs it (its [env] " \
|
|
85
86
|
"can run commands), so the owner approves it"
|
|
86
|
-
GIT_HOOKS = "a participant's turn can't write git's hooks (.git/hooks
|
|
87
|
+
GIT_HOOKS = "a participant's turn can't write or install git's hooks (.git/hooks, .git/modules/*/hooks, a " \
|
|
88
|
+
"template, pre-commit install, husky): git runs them in the owner's name"
|
|
87
89
|
# chi web on loopback asks the owner's token too (Web::App#token_gate,
|
|
88
90
|
# LAN mode); this text check stays as defense in depth: a loopback-only
|
|
89
91
|
# chi web still answers loopback as the owner.
|