Install and set up¶
For the Puppet Master¶
- Download the file for your computer from the
releases page:
puppet-strings-windows.exe,puppet-strings-macosorpuppet-strings-linux.tar.gz. - Put it somewhere you will find it again, and open it. On macOS you may have to mark it
runnable first (
chmod +x puppet-strings-macos). On Linux, unpack the archive (tar xzf puppet-strings-linux.tar.gz) into the folder you want to keep it in and run./puppet-strings-linux/install.sh, which marks the program runnable and puts Puppet Strings in your applications menu with its icon. Keep the folder where it is: the menu entry points at it. - Sign in with the Google account that can open camp's sheets, and choose the Puppet Strings folder in Configure. Puppet Strings remembers both.
There is no config file to put anywhere and no OAuth client JSON to go and find: everything that is the same for everyone — the Google client, the update feed — is built into the file you downloaded, and what differs from one person to the next is your sign-in and your folder, which it remembers for you.
Check for updates at the right-hand end of the toolbar asks GitHub whether a newer version is out and replaces the file you are running if you say yes; restart it afterwards. A downloaded copy also asks quietly the first time it loads a day, and says nothing unless there is something newer.
On Linux the update is the same archive the releases page has, so it is unpacked and the
program inside it replaces the one you are running, in the folder it is already in. The
icon beside it is refreshed at the same time, and your menu entry is left alone — it names
that same folder and goes on working. There is no need to run install.sh again.
Building the releases, once¶
Puppet Strings signs in as the person using it, which needs a Google OAuth client made
once and built into the releases. Steps 3 and 4 below make one; then, in the GitHub
repository under Settings → Secrets and variables → Actions, add GOOGLE_CLIENT_ID
and GOOGLE_CLIENT_SECRET. RELEASES_URL is optional and only needed to point a build at
a different repository's releases.
Push a tag and the release is built for all three platforms with camp's settings inside:
git tag v0.2.0 && git push --tags
Note
A Google client secret for a desktop app is not confidential in the usual sense — it ships inside every copy of every desktop program that signs in to Google, and Google says as much. It is kept in a repository secret rather than in the code anyway, because a published one lets someone put camp's name on a consent screen of their own.
Running from source¶
The rest of this page is for working on Puppet Strings rather than using it. A checkout
has no built-in client, so it reads config.toml and the OAuth client JSON as it always
has, and it never checks for updates on its own — git pull is the update.
1. Python¶
Puppet Strings needs Python 3.12 or newer. Check with:
python3 --version
2. Install the package¶
From a copy of this repository:
python3 -m venv .venv
source .venv/bin/activate
pip install -e .
puppet-strings --help
On Windows use .venv\Scripts\activate.
3. Google OAuth client¶
Puppet Strings reads and writes the sheets as you, signed in to your Google account, so a sheet you can open is a sheet it can open. Signing in needs an OAuth client, which is made once and handed to the next Puppet Master along with this repository.
- Open console.cloud.google.com and create a project
named
Puppet Strings. - APIs & Services → Library: enable Google Sheets API and Google Drive API.
- APIs & Services → OAuth consent screen: set it up as an Internal app if camp has a Google Workspace, or External otherwise, and add yourself as a test user.
- APIs & Services → Credentials → Create credentials → OAuth client ID, application
type Desktop app. Download the JSON and save it as
~/.config/puppet_strings/oauth_client.json.
Nothing needs sharing with anybody: you already have the sheets.
4. Create the config spreadsheet and the schedules folder¶
Create a config spreadsheet with tabs named Blocks, Calendar, Mappings and, when
you want it, Adjustments, with the exact columns in The sheets. Each mapping
you add later gets its own extra tab of rows, as that page explains. The Blocks tab
needs a cabin_act row, which is the slot the cabin act sheets are scheduled into.
Requests need no spreadsheet: they are kept on this computer. See Requests, which also says how to hand them over.
Put the config spreadsheet, and the other spreadsheets, in a Puppet Strings folder holding a folder per
year. Puppet Strings builds the programme and span folders inside a year as it needs them;
you put a Staff Categories spreadsheet in each span's folder, and Reload makes the
day spreadsheets. See the schedules tree.
5. Sign in and choose the sheets¶
puppet-strings app
The first run has nobody signed in, so the Configure pane opens before the window does. Sign in — a browser opens and asks for consent once — and then use Browse Drive… beside each row to pick that sheet. The browser shows My Drive, Shared with me and every shared drive, the same three places Drive itself offers.
| Row | What to pick |
|---|---|
| Root | The directory holding a folder per year; see the schedules tree |
| Cabin Acts | The directory holding one cabin act sheet per session and week, which somebody else keeps |
Two directories is the whole of it. Inside the root everything is found by name — a spreadsheet called Clinic_Data, Clinic_Schedule, Skills or Config is that
sheet, a folder called 2027 is that year — so renaming a sheet is how you move it, and
nothing has to be re-chosen when you add next season.
What you choose is kept in ~/.config/puppet_strings/settings.json. Configure is on
the right of the toolbar whenever you want to change a sheet, or sign in as a different
account.
6. config.toml, for everything else¶
Everything else lives in ~/.config/puppet_strings/config.toml, and every part of it has a
default, so the file is only worth writing when you want to change something:
# Tab names inside each spreadsheet. Change these to match, or rename the tabs.
[tabs]
clinics = "Clinics" # Clinic_Data: the combined tab with a Category column
offerings = "Offerings" # Clinic_Schedule
skills = "Skills" # Skills: the main tab
position_skills = "Positions" # Skills: Clinic_Name | 1st | 2nd | 3rd
cabin_act_board = "Board" # the tab of a cabin act sheet that is read
[auth]
client_secrets = "~/.config/puppet_strings/oauth_client.json"
token = "~/.config/puppet_strings/token.json"
# A release has camp's OAuth client built in; these override it, and are how a checkout
# signs in without a client JSON file.
# client_id = "..."
# client_secret = "..."
[updates]
releases_url = "https://api.github.com/repos/Dylan-Puppet-Master/Puppet-Strings/releases/latest"
[solver]
tier_seconds_limit = 15 # each pass, not the whole solve
tidy_seconds = 2 # the cosmetic pass, which only neatens a working schedule
workers = 8
[views]
remainder = "DYOW/WPs" # label for the unused part of a partly used block
[day]
midday = "12:00" # where morning ends, for a half-day rest; 12:00 or 12 PM
date_order = "mdy" # how to read 6/7/2026 on the Calendar: "mdy" or "dmy"
A [sheets] table of spreadsheet ids still works, for an install made before the
Configure pane existed, and what is chosen in the app wins over it.
An older install may instead have a service account key at
~/.config/puppet_strings/service_account.json. That still works, and is used whenever
nobody is signed in, but it cannot browse Drive, so moving over to signing in is worth
doing.
7. Check¶
puppet-strings names
puppet-strings validate
puppet-strings --date 2026-06-15 solve
names lists every name you can use in a request. validate checks every request.
solve prints the schedule for a date without publishing it, with a clinic request for
each offering. If any command reports a load error, it names the sheet, tab and row to fix.
Working offline¶
puppet-strings export-fixtures some-folder downloads every tab as CSV, and copies the
requests in beside them as requests.sqlite. Every command then accepts
--fixtures some-folder to run without Google access, which is also how the test suite
works. Running on the folder reads and saves its own requests, not this computer's.
The Google Sheets cache¶
Each sheet read from Google is kept in ~/.config/puppet_strings/sheets-cache.sqlite
until Drive says it has changed, so a reload reads only what somebody has edited since.
Drive can take a moment to notice an edit; if a sheet you have just changed comes back as
it was, Configure → Google Sheets cache → Clear reads everything afresh. From the
command line, --no-cache does the same for one run. To turn the cache off for good, set
cache = "" under [storage] in config.toml.