Skip to content

feat: modernize CLIs and make 'notehub' work well in an agentic context - #78

Open
rayozzie wants to merge 29 commits into
masterfrom
ray/cli-modernization
Open

rayozzie wants to merge 29 commits into
masterfrom
ray/cli-modernization

Conversation

@rayozzie

@rayozzie rayozzie commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

A ground-up modernization of notehub, modeled on the gh CLI, plus a shared command line framework that notecard now also uses. Note that the CLI's are compatible with the previous versions in terms of switches accepted and 'modeless' use - so as not to break shell scripts - but the past syntax is undocumented.

NOTE: this cannot be merged yet because it requires features as-of-yet not pushed to the public note-go repo.

Modes

  • notehub <mode>: login, logout, auth, api, firmware, skills, each with its own switches, commands, examples and help
  • Bare notehub shows short help; --help shows everything; a mode with nothing to do shows its help
  • Options are documented as --option; -option works too

Signing in

  • CLI (only) terminology for signing in shifted to 'login' and 'logout' because of the vast acceptance of that terminology across CLI tools, even if sign in is far more widely accepted for apps and websites.
  • notehub login / logout, and notehub auth login | logout | status | switch | token, modeled on gh auth.
  • NOTEHUB_TOKEN: when set, its token is used for every request, no sign-in needed
  • One login per hub: auth status shows them all, which is active, and whether each hub still accepts its login; auth switch changes the active hub
  • Browser sign-in polls the hub to enable use in environments where localhost listens are not possible. (Localhost redirect code still available for note-go testing.)
  • Agents: notehub login --name "DotBot" --json (URL, progress and result as JSON lines) or echo "$SECRET" | notehub login --with-token --json
  • A login that is still good is kept: login says so and exits 0, so agents can run it just to check

notehub api to enable direct call of v1 APIs with saved creds

  • gh api style: notehub api [METHOD] <path>, body to stdout, streamed as it arrives
  • {project}, {product} and {device} placeholders filled from --project / --product / --device
  • --field and --raw-field with gh typing, --input, --header, --include, --paginate, --spec for the hub's OpenAPI spec

notehub firmware to enable a major use case that was missing

  • list, show, set (version read from the binary or --version; same bytes skipped, same version replaced), get (md5-verified), edit, delete
  • DFU: update, cancel, status, history over --scope devices or fleets, narrowed by --tag / --sn / --sku; all needs --force
  • --dry-run and --json throughout

notehub skills to enable inspection and management of skills (particularly backup & restore)

  • show, list, get, set, rename, delete, backup, restore, with --dry-run and --force where it matters

Shared framework: lib/cli

  • Modes, switches, groups, examples, help, dispatch and parsing in one package, used by both tools
  • Tables checked at startup: duplicate switches, unknown groups or modes, hidden general options, unnamed values
  • --help, the help mode and the saved-settings printout come from the framework
  • lib's module path is the path everyone imports

Notecard

  • A switch table with groups, named values (--port (port), never (string)), ten examples and a detail paragraph
  • Bare notecard shows short help, its own saved settings, and the ports it can see
  • Errors go to stderr; a request's failure is answered as JSON on stdout, as the Notecard answers
  • --version and --binpack work without a Notecard attached
  • --watch, which has read nothing since the REPL took over watching in 2022, is gone

Help and README

  • Every mode's help has gh-style examples, written for agents as much as for people
  • README is concise and human-targeted, one shape per section

Tests

  • Tests for lib/cli, notecard help, and notehub modes, switches, help spellings, auth, api, firmware and files
  • All three modules build, vet and test clean

rayozzie and others added 24 commits September 16, 2026 18:55
'notehub skills' emits a protocol that turns an AI harness into a teacher: it
interviews a developer about their product, then stores what it learns as tagged
Markdown skills inside the Notehub project itself, where any agent with read
access can later find it.

The problem it exists to solve is that a person asks about their product while
the data is named for the firmware.  Nothing inside a field called 't' reveals
that it is a temperature, that the product is a refrigerated container, or that
a value of zero means a disconnected probe.  More data does not close that gap.

The CLI is deliberately thin.  It emits the protocol and moves Markdown in and
out of Skills Storage; everything else a harness needs it already has.  Teaching
happens in a working copy under the config directory, so nothing reaches the
project until it is pushed, and 'status' says what would change.  Push never
deletes, pull refuses to run over unpushed work, and 'backup'/'restore' make a
session safe to experiment with.

Storage rides on the v0 upload API as type 'data', with the relative path as the
source and the kind of knowledge as the tags, so a reader can load only what a
question needs.  'full:true' is not deployed everywhere yet, so reads fall back
to a range read and will become a single transaction when it ships.

Also adds mode-aware switch parsing: an optional leading keyword selects which
switches exist, with '-help' per mode.  Default mode is unchanged, and a switch
used in the wrong mode is diagnosed by name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Expired credentials are as good as none, because the hub rejects them, so a
hub we are no longer signed in to is not a saved value worth printing. Adds
ConfigCreds.ExpiredAt and skips those entries in Print, emitting the "creds:"
header only when at least one live credential remains.
`notehub train` prints skills/train.md verbatim, and that text is the whole
briefing an agent gets before it interviews a human about their product. An
independent review ran the previous text as a live protocol against fictional
fixtures and found gates that read well and were never followed, plus three
substantive errors. This reworks it, and re-runs the same kind of rehearsals
against the result.

Stored skills carry no access facts
  Two passages still routed access state into uploaded files: the opening
  asked whether Notehub IQ was on and wrote the answer into `config` and
  `index`, and the closing prohibition then allowed IQ findings into
  `anomalies` or `questions`. All four are uploaded, so all four published it.
  The IQ investigation now lives in the trainer's own exploration section and
  its findings stay in session notes; the prohibition names `anomalies` and
  `questions` explicitly as not a loophole. Skills state no credential, no
  authentication method, and no IQ enablement - these objects are public
  within the project, and enablement is a dated fact that would silently age
  the durable knowledge beside it. An analytical-documentation URL may appear
  only as a conditional. The project identity now includes the hub, described
  as an address rather than an access fact.

Interview gates that agents actually follow
  The confidentiality promise was a forward reference from the invitation, so
  a linear reader asked for firmware first; the promise now precedes the
  invitation in the text, with source classification asked at the same moment.
  The two-part read-back was paraphrased in every reviewed run, always by
  swapping the outsider test for a colleague, so that exact substitution is
  now named as the failure. The anomaly loop no longer promotes an explanation
  straight to `stated`, leaving exactly one path to `stated`. The stop rule no
  longer forbids recording a confirmation the person already gave. Turns are
  capped at three closely related questions, and the lesson lists are named as
  a session-wide coverage checklist rather than one questionnaire.

Aggregation and inference semantics
  A recipe must name two reductions, within-note and across-period, because a
  counting unit and a denominator do not choose between "any exceedance in the
  day", "the last observation", "the day's mean" and "time spent above". The
  previous text claimed a maximum above a limit and any value above it were
  different rules; over a non-empty valid set they are identical, and the text
  now says so explicitly so no reader assumes otherwise. The
  latest-before-validity gate is scoped to current-state questions and no
  longer travels to period questions, with the worked case showing that a
  device valid above the limit in the morning and faulted at noon is unknown
  now and did exceed today. "The device integrates" is gone as a justification
  for continuous duration: only an interval-bounding extreme supports it, and
  a mean does not, since 9 then 9 has the same mean as 7 then 11 and a
  different time above 8.

Notefile inventory is now a forcing structure
  A minimal-effort rehearsal inventoried four of seven Notefiles, dropping the
  system files and a firmware-only one, and substituted a coverage warning for
  the entries. Step 6d now requires a roster - every Notefile name from every
  source, built mechanically before any meaning is known - and the file is not
  finished until every roster name has an entry. A coverage warning and an
  open question are explicitly not entries. The closing sweep walks the roster
  and reports the count aloud. The same rehearsal then produced seven of seven.

Two corrected facts about the service
  "Everything that lists is paginated" was false, verified against the current
  OpenAPI specification: devices, events and the fleet variants take
  pageSize/pageNum, while fleets, routes, schemas, products and monitors take
  no paging parameters at all. And command substitution does not hide a
  credential - `$(notehub -token)` expands into curl's argv, confirmed by
  experiment - so the example is replaced with an argv-free form that reads
  the header from stdin.

Gaps found by reading the skills the protocol produced
  Four fresh readers were given only a generated skill set and a new wording
  of a caller's question, and an adversarial pass then tried to refute each
  answer against those files. That found six defects in the protocol rather
  than in the readers. The floor demanded a fleet-or-period recipe and never a
  current-state one, although the text calls that "most phone calls" - so the
  latest-before-validity gate reached the skills as a note about its own scope
  with no recipe using it, and one set told a reader to "skip" an invalid
  latest note, which on a "right now" question means the walk-back to an older
  good reading that the gate exists to forbid. The floor now requires both
  recipes, and the current-state one must say that an invalid latest note
  means unknown.

  A retraction that removes a decision rule now has to supply the replacement
  or mark every dependent recipe blocked: one run struck out "use the mean of
  the three probes", wrote that the reduction must be re-chosen, and left the
  dependents reading as verified, so every reader would invent a different
  replacement. An observed-coverage denominator must be written as a test a
  reader can evaluate, because "the devices that reported" does not say which
  side a device that reported hourly with a dead sensor falls on. A trend is
  not a forecast, and every set now names a forward-looking question its
  callers ask and says whether a mechanism supports it. And `index.md` now
  says what to do with a question no recipe matches, and may not call a
  question answerable today if it cannot survive the gates the recipes
  impose - a complete field inventory does not make a single-device question
  answerable when nobody established how a caller names a device.

Rehearsals
  Four fresh-context sessions against fictional fixtures, each given only this
  document and a fixture, each consuming three scripted human replies: a
  deliberately minimal-effort initial run, a normal-effort presentation probe,
  an update run with five planted contradictions plus a local hand-edit, a
  remote edit and a partial 403 publication, and a minimal-effort retest of
  the roster change. Promise ordering and the verbatim read-back passed in
  every run, both having failed in every reviewed run beforehand. The update
  run found seven contradictions, stopped and asked before touching a `stated`
  claim, preserved the hand-edit, merged the remote recipe rather than
  clobbering it, and reported exactly which file was saved and which were only
  local. The inventory went from four of seven to seven of seven.

  A minimal-effort validation run against the six fixes above confirmed five
  of them - roster, current-state recipe refusing the walk-back, evaluable
  denominator, unmatched-question routing, and a named forward-looking
  question - and exposed two more, now also fixed: the three-question cap was
  being met by bundling several asks into one numbered bullet, and the index's
  answerable list was built from recipe titles rather than recipe statuses, so
  it advertised a question as answerable while the recipe itself was
  conditional on an unresolved identity rule.

  Not verified: those last two fixes have not yet had a rehearsal of their
  own. A cross-harness run on a smaller model was cut short by an account
  limit and never completed.

Declined: the review proposed reaching a 750-850 line budget partly by
deleting the provenance and retraction examples and compressing the inventory
checklist by 44%. Those are the passages that demonstrably produce compliance,
so the file is longer than the target instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Five more minimal-effort rehearsals against fictional fixtures, each given
only skills/train.md and a fixture. The document's own text was the cause in
every case, so these are protocol fixes rather than fixture artifacts. All
five are verified by a further rehearsal, described at the end.

The document contradicted itself on turn size
  Step 3 prescribed an opening that asks who the person is, what they own,
  what people phone about, for every document, whether each is public, and
  the caller time zone - six asks - while Step 6 capped a turn at three. A
  rehearsal duly produced a seven-ask opening, and the scripted human's reply
  did not answer the last of them. The warning to count asks rather than
  bullets was powerless because the volume was mandated upstream. The opening
  is now explicitly two turns, and the cap starts at the opener rather than
  after the pleasantries.

The roster reconciled perfectly while a Notefile was missing
  The previous change made Step 6d build a roster before any entry, which
  took a minimal-effort run from four Notefiles of seven to seven of seven.
  The next run reported "6 on the roster, 6 entries" - self-consistent, and
  short by one: it built the roster from the schemas and the event sample,
  never walked the firmware back against it, and lost the Notefile that
  exists only in firmware and never arrives. Reconciling the roster against
  its own entries cannot catch a name that never reached the roster. The
  roster is now built source by source with each source named as it is
  exhausted, a source read after the roster sends you back to it first, and
  the closing sweep reconciles in both directions and reports both counts.

The index contradicted the recipes for a mechanical reason
  index.md is listed first in the file set, so it gets drafted first, the
  recipes are written afterwards, and it is never reopened - a run advertised
  a blocked recipe as answerable, and it was the question the human had
  opened the session with. Appending a closing status to the same file does
  not make anybody re-read the lines above it. index.md is now written last,
  every time, and its answerable list is copied from recipe status lines
  rather than recipe titles.

Read-back drifted in both directions at once
  One run left a sentence the person had agreed to at `heard` while writing
  up a passing remark as `stated`. Step 5's instruction to count promotions
  against read-backs read as an intention and was not performed. It is now a
  written step - list what was read back, list what was promoted, check the
  lists match - and read-back happens at the end of every lesson rather than
  once at the end, because deferring it means promoting hour-old sentences
  against someone already leaving.

The closing dropped the lessons it never ran
  The rule requiring an unasked lesson to be named in the closing was
  crowded out by the four-bullet template, so it survived only as a question
  ID. The "what you never reached" bullet now names lessons A to G by letter.

Verification
  The confirming rehearsal, deliberately minimal-effort and with the
  scripted human's overflow rule enforced so that an oversized turn actually
  costs answers: turns of two, three and two distinct asks, overflow never
  triggered; roster built across four named sources, the firmware-only
  Notefile present with its own entry, "4 sources walked; 7 names on the
  roster; 7 entries"; index written last by file timestamp, and its four
  status verdicts agreeing with the four recipes including the open-question
  IDs; the promotion list written out, and it caught a real error - the
  liability constraint arrived after the read-back, so the run demoted it
  from `stated` to `heard` and opened a question; lessons B, F and D2 named
  as never or barely run; promise before invitation; nothing about
  authentication or IQ in any produced file.

  Still not guaranteed: coverage. A lazy trainer satisfies every structural
  gate in three turns and can still never run lessons B and F, never read a
  route or a fleet, and never obtain a population rule. The document reports
  that honestly rather than preventing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fresh-train.sh reset nothing
  It listed published skills with type "data", but storage moved to the
  "skill" type in 9628142, so it reported "published skills: (none)",
  deleted nothing, and the next run became an update run - the one outcome
  the script exists to prevent. It now queries the skill store. A listing
  that fails no longer reads as an empty project either: the CLI prints a
  service error as a reply and exits 0, so the reply itself is checked, and
  any failure stops the script before it does anything.

  Testing it turned up a worse defect: when archiving the working copy
  failed, the script said "continuing" and then deleted the working copy
  anyway, with no copy of the hand edits it held. A failed archive now
  stops the script before anything is deleted. It also checks first that
  the notehub on PATH has the train and skills modes, because an older
  build fails every step after the listing.

Push listed superseded uploads twice
  The list was built by skillsStorageCurrent and then every upload was
  appended again, so an older duplicate was deleted twice. Only redundant
  requests, but each upload a push supersedes is now listed once.

.project is gone
  It was to hold the project's readable name for naming backups, and was
  never written, deliberately: the name comes from hub.app.get, which also
  returns route credentials. Removed along with the parameters that only
  carried it, and the reasoning now sits on skillsBackupPath.

Kinds are read as YAML
  The kind field was read by a line parser, so `kind: [a, b]` was stored
  as the tag "[a,b]", a block list of "- a" lines stored no tags, a line
  wrapped onto the next lost its second half, and a nested kind: matched.
  Only the top-level kind field is now parsed, with gopkg.in/yaml.v3,
  which was already in go.sum, so the comma-separated line, both YAML list
  forms, wrapped lines and comments all work, and a slip elsewhere in the
  front matter can't block a push. Anything that is not a word or a list
  of words is refused with a readable message rather than stored mangled.
  A real trained project's six stored skills give identical tags.

Docs
  skills.go still said 'notehub skills' emits the protocol, which is
  'notehub train' now. The leftover teach/taught wording says train,
  including one error message, the README's mode table gains train, and
  the testing README matches the script.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Training now lives at https://notehub.md as the notehub-project-train
skill, which any agent can follow with nothing installed: it interviews
the person, drafts in its own session, and stores what it learns in the
project over plain HTTPS.  So the CLI no longer trains anything.  The
train mode and its embedded protocol are gone, and so is everything that
existed to serve them: the working copy and its baseline, status, pull,
push, backup and restore, and the rehearsal reset script.  The help for
skills mode says where training lives now.

What remains is a tool for a person to read and manage the skills a
project holds, and it keeps nothing between runs:

  show [name|all]      one skill as stored, or the whole set as one
                       linked document (unchanged)
  list                 what the project holds (unchanged)
  get <name|all> [p]   save a skill to a file, to stdout with -, or
                       every skill into a directory
  set <path> [name]    store a file, or every skill in a directory
  delete <name|all>    remove one skill, or every one

get never overwrites a local file that differs from the project's copy
without --force, since it may hold edits nobody has stored.  set stores
only what differs from what the project holds, never removes anything,
and --dry-run says what it would do.  delete all lists what it would
remove and refuses without --force.

Testing set live found that storing the same skill again within a second
of its last upload fails: the service names an upload by its source and
the second it arrived, so the two collide.  The add comes before any
removal, so nothing was lost; set now waits a second and adds again when
a name the project already holds is refused.

notehub --project/--product now includes the account's role in the
project, from GET /v1/projects/{projectUID}.  It is informational: on old
projects it can read unknown:fleetmanager, a deprecated role the API does
not recognize, which still permits writing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…omments

- A skill's kinds may also come from a tags: line, since kinds are stored
  as the upload's tags.  The first of kind:, kinds: and tags: that holds
  any is used.
- list, show and set no longer print "no kind" for an untagged skill.
- Remove the last training remnants from tests, examples and comments.
  Help and the README keep one pointer to https://notehub.md.
- Drop the deprecated rand.Seed calls from lib/config.go, a no-op since
  Go 1.24, and show the first saved credential on the creds: line.
- Shorten comments throughout the branch.  Apart from the above, the code
  is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rename <name> <newname> stores the skill under the new name with its
existing kinds and then removes the old name, in that order so a failure
leaves it in place.  It won't replace another skill without --force, and
afterward it names any skills whose text still mentions the old name,
since those cross-references break.

backup <path.zip> writes every skill into a zip at its path.  A skill
whose front matter doesn't already declare the kinds it is stored under
gets a kind: line written in, so the zip is self-describing and a
restore keeps the kinds.  The zip is written to a temporary file and
renamed into place, and an existing one isn't overwritten without
--force.

restore <path.zip> stores every skill the zip holds, taking kinds from
its front matter as set does, then removes every skill the zip does not
hold.  All names and kinds are validated first, and storing precedes
removing, so a failure part-way loses nothing.  This is enough to put a
project's skills back as they were: training stores only the file's
path, its bytes, and its kinds as tags, all of which a zip carries; only
the service-assigned upload name and stored date are regenerated.

A zip is a lossless round-trip because set and restore share one storing
routine, and the front-matter parser was split so the same field-finding
logic both reads a kind and writes one.

Help and dry-run:

  - get now documents - for stdout, and get all - is refused rather than
    writing to a file named "-".
  - the mode help and README say kinds come from kind:, kinds: or tags:,
    matching what the code accepts.
  - --dry-run now covers set, rename, delete, backup and restore, each
    printing what it would do; a dry-run delete all no longer needs
    --force.

Two smaller changes:

  - a --project value that isn't a projectUID (doesn't begin with app:)
    is taken as --product, and a --product value that is a projectUID as
    --project, so a UID given to the wrong switch still works.
  - --whoami now asks the hub whenever there are saved credentials,
    rather than trusting a recorded expiration.  It reports when the hub
    still accepts a token whose recorded expiration has passed, and when
    the hub rejects one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…amework, notecard cleanup

- NOTEHUB_TOKEN supplies the token for every request when set
- login, logout and auth modes (login, logout, status, switch, token)
  replace --signin and --signout, which stay as hidden aliases; agents
  sign in with --json, --name, --with-token and --expires-in, and a
  login already in place is reported without opening anything
- api mode, modeled on gh api: placeholders, --field typing, --paginate,
  streaming responses and --spec; netcat and examples.txt removed
- firmware mode: list, show, get, set, edit, delete, and DFU update,
  cancel, status and history; a hidden files mode
- the mode, switch and help framework moves to lib/cli, shared by both
  tools and checked at startup; lib's module path now matches its
  import path, and its old help printer is gone
- notecard: a switch table with groups, named values and examples;
  errors to stderr; --version and --binpack without a Notecard; the
  unimplemented --watch removed; each tool shows only its own settings
- help and README rewritten

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@rayozzie rayozzie changed the title Modernize the Notehub and Notecard CLIs feat: modernize the CLIs and make it useful in an agentic context Oct 9, 2026
@rayozzie rayozzie added the do not merge Leave, perhaps review, but don't merge (yet) label Oct 9, 2026
@rayozzie rayozzie changed the title feat: modernize the CLIs and make it useful in an agentic context feat: modernize CLIs and make 'notehub' work well in an agentic context Oct 9, 2026
rayozzie and others added 2 commits October 8, 2026 20:31
The CLI uses three generated types from the SDK, all in v0.0.10, so the
local replace that came in with the agent login work isn't needed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@zakoverflow

Copy link
Copy Markdown
Contributor

Comment, not a full review yet: you've flagged this as blocked on note-go, so I'm holding the line-by-line pass until it can build in CI. Two things worth fixing before that pass so it's reviewable:

  1. go.mod: replace github.com/blues/note-go => ../hub/note-go points at a sibling checkout that only exists on your machine. The audit job fails on it (reading ../hub/note-go/go.mod: no such file or directory), and because that's the only check, nothing in this 10k-line change has been built, vetted, or tested in CI yet. Once the note-go features are public, please drop the replace and bump the require to the tagged note-go version so audit (and the new lib/cli, notehub, and notecard tests) actually run on this head. A pseudo-version from a pushed note-go branch would also unblock CI earlier.

  2. Credential file permissions. With notehub login now storing OAuth sign-ins and PATs per hub, the config is still written with os.OpenFile(..., 0666) in lib/config.go (pre-existing, not introduced here), and the new MkdirAll(..., 0777) / WriteFile(..., 0666) helpers follow the same pattern. Under a permissive umask that leaves tokens group/world-readable. Suggest 0600 for the config file and 0700 for its directory; the skills backup/firmware output files can stay as they are.

Non-blocking: lib/go.mod's module path change to github.com/blues/note-cli/lib is the right fix (the root require already used that path), but it's a breaking import-path change for anyone importing the old github.com/note-cli/lib. Worth a line in the release notes.

When CI is green on a head without the local replace, I'll do the full pass, with priority on notehub firmware update scope/--force handling for all, auth logout revocation ordering, and api --paginate.

@zfields

zfields commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

@rayozzie I'll look at what it's actually asking for, and give you a human readable version.
#developerEmpathy

@rayozzie

rayozzie commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor Author

We can't build it until note-go is merged and pushed to the public repo, hence the replace.

And who cares about world readable. Yes, they are in my computer's file system. Are you concerned that another account on my computer will access it? Hmm

Comment thread go.mod
@zfields
zfields requested a review from Bucknalla October 15, 2026 00:45
Comment thread notecard/cli.go
)

// notecardSwitches returns every switch
func notecardSwitches() []*cli.Switch {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Scott and Alex have both previously used a library called Cobra to "modernize" our CLIs (parameter handling, etc.).

Is there a reason to not use a library to manage the parameters and help, or is it just something you were previously unaware of?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I needed to ensure backward compatibility as well as modernize it, so this is the direction i went.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do not merge Leave, perhaps review, but don't merge (yet)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants