Repository navigation
Conversation
'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>
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>
|
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:
Non-blocking: When CI is green on a head without the local |
|
@rayozzie I'll look at what it's actually asking for, and give you a human readable version. |
|
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 |
| ) | ||
|
|
||
| // notecardSwitches returns every switch | ||
| func notecardSwitches() []*cli.Switch { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
I needed to ensure backward compatibility as well as modernize it, so this is the direction i went.
A ground-up modernization of
notehub, modeled on theghCLI, plus a shared command line framework thatnotecardnow 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 helpnotehubshows short help;--helpshows everything; a mode with nothing to do shows its help--option;-optionworks tooSigning in
notehub login/logout, andnotehub auth login | logout | status | switch | token, modeled ongh auth.NOTEHUB_TOKEN: when set, its token is used for every request, no sign-in neededauth statusshows them all, which is active, and whether each hub still accepts its login;auth switchchanges the active hubnotehub login --name "DotBot" --json(URL, progress and result as JSON lines) orecho "$SECRET" | notehub login --with-token --jsonnotehub apito enable direct call of v1 APIs with saved credsgh apistyle:notehub api [METHOD] <path>, body to stdout, streamed as it arrives{project},{product}and{device}placeholders filled from--project/--product/--device--fieldand--raw-fieldwith gh typing,--input,--header,--include,--paginate,--specfor the hub's OpenAPI specnotehub firmwareto enable a major use case that was missinglist,show,set(version read from the binary or--version; same bytes skipped, same version replaced),get(md5-verified),edit,deleteupdate,cancel,status,historyover--scopedevices or fleets, narrowed by--tag/--sn/--sku;allneeds--force--dry-runand--jsonthroughoutnotehub skillsto enable inspection and management of skills (particularly backup & restore)show,list,get,set,rename,delete,backup,restore, with--dry-runand--forcewhere it mattersShared framework:
lib/cli--help, thehelpmode and the saved-settings printout come from the frameworkNotecard
--port (port), never(string)), ten examples and a detail paragraphnotecardshows short help, its own saved settings, and the ports it can see--versionand--binpackwork without a Notecard attached--watch, which has read nothing since the REPL took over watching in 2022, is goneHelp and README
gh-style examples, written for agents as much as for peopleTests
lib/cli, notecard help, and notehub modes, switches, help spellings, auth, api, firmware and files