@lanes-sh/link 0.2.2 → 0.3.1
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.
- package/README.md +22 -8
- package/instructions/skills/lanes-link/SKILL.md +42 -14
- package/package.json +1 -1
- package/src/cli/argv.ts +50 -0
- package/src/cli/brand.ts +178 -0
- package/src/cli/callback-page.ts +108 -128
- package/src/cli/commands/connect/accounts.ts +5 -0
- package/src/cli/commands/connect/assertion.ts +187 -0
- package/src/cli/commands/connect/authorise.ts +61 -17
- package/src/cli/commands/connect/client.ts +37 -9
- package/src/cli/commands/connect/discover.ts +94 -0
- package/src/cli/commands/connect/family.ts +72 -0
- package/src/cli/commands/connect/index.ts +113 -115
- package/src/cli/commands/connect/method.ts +237 -0
- package/src/cli/commands/connect/outcome.ts +42 -1
- package/src/cli/commands/connect/pasted-token.ts +66 -0
- package/src/cli/commands/connect/requirements.ts +60 -8
- package/src/cli/commands/connect/setup.ts +16 -5
- package/src/cli/commands/connect/target-note.ts +34 -0
- package/src/cli/commands/identity.ts +258 -0
- package/src/cli/commands/knowledge/index.ts +390 -0
- package/src/cli/commands/knowledge/migrate.ts +180 -0
- package/src/cli/commands/knowledge/setup.ts +144 -0
- package/src/cli/commands/knowledge.ts +10 -0
- package/src/cli/commands/mcp/harnesses.ts +16 -2
- package/src/cli/commands/mcp/register.ts +9 -1
- package/src/cli/commands/mcp/stdio.ts +21 -0
- package/src/cli/commands/operate/dashboard.ts +107 -0
- package/src/cli/commands/operate/findings.ts +151 -0
- package/src/cli/commands/operate/inspect.ts +63 -164
- package/src/cli/commands/operate/outputs.ts +38 -11
- package/src/cli/commands/operate/policy.ts +7 -7
- package/src/cli/commands/operate/serve.ts +3 -0
- package/src/cli/commands/operate/status.ts +108 -1
- package/src/cli/commands/operate/token.ts +1 -1
- package/src/cli/commands/operate.ts +2 -0
- package/src/cli/commands/profile/declare.ts +154 -0
- package/src/cli/commands/profile/removal.ts +17 -0
- package/src/cli/commands/profile/remove.ts +5 -5
- package/src/cli/commands/profile.ts +83 -35
- package/src/cli/commands/secrets.ts +6 -6
- package/src/cli/commands/setup.ts +22 -6
- package/src/cli/commands/sync.ts +262 -0
- package/src/cli/commands/target.ts +65 -83
- package/src/cli/config-edit.ts +53 -144
- package/src/cli/config-repair.ts +186 -0
- package/src/cli/dashboard-page.ts +284 -0
- package/src/cli/dashboard-shell.ts +125 -0
- package/src/cli/dispatch-owner.ts +93 -0
- package/src/cli/identity.ts +12 -1
- package/src/cli/main.ts +90 -61
- package/src/cli/nearest.ts +45 -0
- package/src/cli/oauth-callback.ts +187 -0
- package/src/cli/oauth-exchange.ts +57 -15
- package/src/cli/oauth.ts +67 -177
- package/src/cli/output.ts +21 -5
- package/src/cli/provider-marks.ts +45 -0
- package/src/cli/runtime/open.ts +74 -51
- package/src/cli/runtime/registry.ts +60 -2
- package/src/cli/runtime/select.ts +26 -13
- package/src/cli/runtime/vault.ts +61 -0
- package/src/cli/runtime.ts +2 -1
- package/src/cli/selection.ts +365 -0
- package/src/cli/usage.ts +42 -11
- package/src/connectivity/auth/README.md +7 -1
- package/src/connectivity/auth/basic/index.ts +1 -1
- package/src/connectivity/auth/index.ts +14 -0
- package/src/connectivity/auth/oauth-authcode/broker.ts +26 -0
- package/src/connectivity/auth/oauth-authcode/index.ts +16 -2
- package/src/connectivity/auth/oauth-authcode/provider.ts +1 -1
- package/src/connectivity/auth/oauth-authcode/refresh.ts +3 -3
- package/src/connectivity/auth/oauth-jwt/README.md +33 -0
- package/src/connectivity/auth/oauth-jwt/index.ts +237 -0
- package/src/connectivity/auth/oauth-jwt/key.ts +148 -0
- package/src/connectivity/auth/resolve.ts +1 -1
- package/src/connectivity/auth/token.ts +11 -0
- package/src/connectivity/index.ts +2 -0
- package/src/connectivity/manifest/auth.ts +99 -2
- package/src/connectivity/manifest/identity.ts +12 -0
- package/src/connectivity/manifest/index.ts +3 -1
- package/src/connectivity/manifest/provider.ts +37 -8
- package/src/connectivity/manifest/requirements.ts +109 -6
- package/src/deployments/adapters/filesystem.ts +10 -1
- package/src/deployments/adapters/github-api.ts +106 -0
- package/src/deployments/adapters/github-commit.ts +103 -0
- package/src/deployments/adapters/github-repo.ts +356 -0
- package/src/deployments/adapters/github-testing.ts +258 -0
- package/src/deployments/adapters/github.ts +125 -0
- package/src/deployments/deploy.ts +94 -114
- package/src/deployments/discover.ts +103 -0
- package/src/deployments/driver.ts +8 -1
- package/src/deployments/gcp/driver.ts +3 -1
- package/src/deployments/knowledge.ts +119 -0
- package/src/deployments/prepare.ts +12 -6
- package/src/deployments/report.ts +117 -0
- package/src/deployments/servable.ts +82 -0
- package/src/deployments/serving.ts +165 -0
- package/src/deployments/sync-apply.ts +276 -0
- package/src/deployments/sync.ts +136 -0
- package/src/deployments/target.ts +3 -2
- package/src/deployments/upload.ts +19 -12
- package/src/dispatch/dispatch.ts +1 -1
- package/src/profile/deployments.ts +80 -0
- package/src/profile/identity.ts +60 -0
- package/src/profile/index.ts +23 -5
- package/src/profile/knowledge.ts +124 -0
- package/src/profile/load.ts +17 -5
- package/src/profile/primitives.ts +24 -1
- package/src/profile/schema.ts +81 -3
- package/src/profile/targets.ts +122 -109
- package/src/profile/workspace.ts +139 -79
- package/src/providers/google/calendar/index.ts +2 -0
- package/src/providers/google/contacts/index.ts +2 -0
- package/src/providers/google/docs/index.ts +2 -0
- package/src/providers/google/drive/index.ts +2 -0
- package/src/providers/google/gmail/index.ts +2 -0
- package/src/providers/google/gmail-imap/index.ts +125 -0
- package/src/providers/google/index.ts +2 -1
- package/src/providers/google/shared/oauth.ts +18 -6
- package/src/providers/google/shared/service-account.ts +110 -0
- package/src/providers/google/shared/setup.ts +21 -3
- package/src/providers/google/sheets/index.ts +2 -0
- package/src/providers/google/tasks/index.ts +2 -0
- package/src/providers/identity/provider.ts +166 -0
- package/src/providers/index.ts +3 -0
- package/src/providers/owner.ts +10 -2
- package/src/providers/scopes.ts +2 -0
- package/src/providers/setup/plan.ts +31 -9
- package/src/providers/setup/provider.ts +23 -0
- package/src/providers/slack/index.ts +81 -33
- package/src/providers/slack/oauth.ts +103 -0
- package/src/providers/slack/scopes.ts +37 -0
- package/src/server/container.ts +18 -1
- package/src/server/cors.ts +252 -0
- package/src/server/dashboard.ts +208 -0
- package/src/server/endpoint.ts +45 -1
- package/src/server/generations.ts +11 -2
- package/src/server/harness.ts +7 -0
- package/src/server/index.ts +45 -6
- package/src/server/mcp/index.ts +1 -0
- package/src/server/mcp/instructions.ts +28 -1
- package/src/server/mcp/visibility.ts +33 -0
- package/src/stores/blobs/route.ts +123 -0
package/README.md
CHANGED
|
@@ -48,8 +48,8 @@ Needs [Bun](https://bun.com) 1.3.11+. Nothing else — no account anywhere.
|
|
|
48
48
|
|
|
49
49
|
```console
|
|
50
50
|
$ bun install -g @lanes-sh/link # puts `lanes` on your PATH
|
|
51
|
-
$ lanes link profile add personal --
|
|
52
|
-
$ lanes link start
|
|
51
|
+
$ lanes link profile add personal --target local
|
|
52
|
+
$ lanes link start --profile personal --target local
|
|
53
53
|
ok serving http://127.0.0.1:7337/mcp
|
|
54
54
|
profiles: personal
|
|
55
55
|
```
|
|
@@ -57,7 +57,7 @@ ok serving http://127.0.0.1:7337/mcp
|
|
|
57
57
|
Then, in another shell:
|
|
58
58
|
|
|
59
59
|
```console
|
|
60
|
-
$ lanes link mcp add # every agent installed; or name one: claude, codex
|
|
60
|
+
$ lanes link mcp add --profile personal --target local # every agent installed; or name one: claude, codex
|
|
61
61
|
ok registered lanes-link with Claude Code (user scope)
|
|
62
62
|
ok registered lanes-link with Codex
|
|
63
63
|
```
|
|
@@ -78,6 +78,17 @@ calendar are the next step. **[Full quickstart →](docs/quickstart.md)**
|
|
|
78
78
|
Memory and skills are plain Markdown files, so a text editor and an agent reach the same bytes. All
|
|
79
79
|
four belong to one profile: what you add under `work` is invisible under `personal`.
|
|
80
80
|
|
|
81
|
+
Keep those two in a private GitHub repository instead of on this machine, and get history, diffs,
|
|
82
|
+
and the same notes from anywhere you run this:
|
|
83
|
+
|
|
84
|
+
```console
|
|
85
|
+
$ lanes link knowledge use github --repo <owner/name> --migrate
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
It moves what you have already stored, in one commit, and `lanes link knowledge use local
|
|
89
|
+
--migrate` brings it back. Nothing else moves — your credentials and your vault stay where they
|
|
90
|
+
are, and there is no setting that would put them in a repository.
|
|
91
|
+
|
|
81
92
|
## Connect an account
|
|
82
93
|
|
|
83
94
|
One command per account. Run it again to add a second mailbox, a second calendar, a second anything.
|
|
@@ -99,15 +110,17 @@ One command per account. Run it again to add a second mailbox, a second calendar
|
|
|
99
110
|
| Linear | `lanes link connect linear` |
|
|
100
111
|
| GitHub | `lanes link connect github` |
|
|
101
112
|
| Slack | `lanes link connect slack` |
|
|
113
|
+
| Gmail (IMAP, app password) | `lanes link connect gmail_imap` |
|
|
102
114
|
| Gmail (Google MCP) | `lanes link connect gmail_mcp` |
|
|
103
115
|
| Drive (Google MCP) | `lanes link connect drive_mcp` |
|
|
104
116
|
|
|
105
117
|
Three things worth knowing up front. `lanes link connect icloud` sets up Mail, Calendar, and
|
|
106
|
-
Contacts together, because one app-specific password covers all three. Google
|
|
107
|
-
of your own:
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
118
|
+
Contacts together, because one app-specific password covers all three. Google and Slack need no
|
|
119
|
+
OAuth client of your own: both authorise against the one Lanes operates, so there is no console to
|
|
120
|
+
visit — for Google, add `--own-client` if you would rather register your own, or take a service
|
|
121
|
+
account key or an app password over IMAP where you would rather nothing expired. And GitHub takes a
|
|
122
|
+
token you paste rather than a browser sign-in, because it will not register a client for us; that
|
|
123
|
+
is the one console visit left here.
|
|
111
124
|
|
|
112
125
|
Full guide — what each one gives your agent, what it needs, and adding your own:
|
|
113
126
|
**[docs/connect.md](docs/connect.md)**.
|
|
@@ -137,6 +150,7 @@ than being rebuilt.
|
|
|
137
150
|
- **[Connect your accounts](docs/connect.md)** — every provider, and what each one needs
|
|
138
151
|
- **[Add it to your agent](docs/clients.md)** — Claude Code, Codex, Claude Desktop, claude.ai, ChatGPT
|
|
139
152
|
- **[Deploy to your own cloud](docs/deploy.md)** — five commands to a URL
|
|
153
|
+
- **[Every command](docs/detailed/commands.md)** — arguments and flags, one entry each
|
|
140
154
|
- **[Full reference](docs/detailed/)** — architecture, configuration, the CLI, the security model,
|
|
141
155
|
writing a provider, and the decision records
|
|
142
156
|
|
|
@@ -24,6 +24,13 @@ is listed first.** Quietly picking one crosses the line the profile exists to
|
|
|
24
24
|
draw. There is no "current profile" to switch — the choice is made per call, and
|
|
25
25
|
`lanes link profile list` shows what exists.
|
|
26
26
|
|
|
27
|
+
**Every `lanes link` command names its profile and its target.** Both are required
|
|
28
|
+
flags with no default, no environment variable, and nothing in a config file
|
|
29
|
+
behind them, so a command missing either refuses rather than acting somewhere
|
|
30
|
+
else. When you write one out for the owner, either fill both in or leave them as
|
|
31
|
+
`<name>` for them to complete — never drop them. `lanes link target list
|
|
32
|
+
--profile <name>` shows what a profile declares.
|
|
33
|
+
|
|
27
34
|
A `connection` names an account within that profile. One profile may hold
|
|
28
35
|
several of the same kind, and naming a connection belonging to a *different*
|
|
29
36
|
profile is refused rather than guessed at.
|
|
@@ -37,7 +44,7 @@ try more than one wording before deciding it is not there.
|
|
|
37
44
|
Writing is a separate grant, and it should be. What you write is served back to
|
|
38
45
|
every later session, including to a different agent, so **write when you are
|
|
39
46
|
asked to remember something, not as a habit.** The owner reaches the same
|
|
40
|
-
entries with `lanes link memory list
|
|
47
|
+
entries with `lanes link memory list --profile <name> --target <name>` and a text editor.
|
|
41
48
|
|
|
42
49
|
## Skills are theirs, not yours
|
|
43
50
|
|
|
@@ -47,7 +54,7 @@ cannot read a skill's body; that is deliberate, not a gap to work around.
|
|
|
47
54
|
|
|
48
55
|
So when a task has a skill for it, **say the skill exists and let them invoke
|
|
49
56
|
it** rather than improvising your own version of their procedure. They manage
|
|
50
|
-
these with `lanes link skills list
|
|
57
|
+
these with `lanes link skills list --profile <name> --target <name>` and `lanes link skills show <skill> --profile <name> --target <name>`.
|
|
51
58
|
|
|
52
59
|
## Vault values are credentials
|
|
53
60
|
|
|
@@ -55,6 +62,27 @@ Use one to do the thing that needs it. Never quote it back, summarise it, echo
|
|
|
55
62
|
it into a file, or paste it into a command whose output you will show. If a
|
|
56
63
|
value has been printed by accident, say so.
|
|
57
64
|
|
|
65
|
+
## Who you are writing as is declared, not inferred
|
|
66
|
+
|
|
67
|
+
A profile may declare the names, addresses and handles its owner wants used when
|
|
68
|
+
something is written as them. Call `identity_list` for the profile in play
|
|
69
|
+
before signing a message, choosing an address to send from, or attributing work
|
|
70
|
+
to a handle — **do not** read a name off the conversation, off a previous
|
|
71
|
+
message's signature, or off the account label on a connection. That label is the
|
|
72
|
+
identity a provider reports for a mailbox; it is not necessarily what they sign
|
|
73
|
+
with.
|
|
74
|
+
|
|
75
|
+
A profile may declare several of a kind on purpose. The first is the default and
|
|
76
|
+
each carries a note saying when to prefer it, so read the notes rather than
|
|
77
|
+
picking the first unconditionally. If none of them fits what you are doing, ask
|
|
78
|
+
— do not combine two, and do not carry one profile's name into another. That
|
|
79
|
+
crossing is the specific mistake this exists to prevent.
|
|
80
|
+
|
|
81
|
+
If the tool is not there, the profile has declared nothing. Ask rather than
|
|
82
|
+
inventing something; they add one with `lanes link identity add <kind> <value> --profile <profile>
|
|
83
|
+
--target <target>` — both flags, because neither has a fallback.
|
|
84
|
+
Nothing you can call writes here, deliberately.
|
|
85
|
+
|
|
58
86
|
## Attachments are named, not carried
|
|
59
87
|
|
|
60
88
|
Where a tool takes `attachments`, each entry names **one** source and the endpoint
|
|
@@ -84,7 +112,7 @@ its content.
|
|
|
84
112
|
|
|
85
113
|
**If the endpoint is not on the same machine as the file**, `path` names the
|
|
86
114
|
*server's* filesystem rather than theirs, and will not find it. Ask them to run
|
|
87
|
-
`lanes link attach <file> --connection <provider>.<account>`, which prints a
|
|
115
|
+
`lanes link attach <file> --profile <name> --target <name> --connection <provider>.<account>`, which prints a
|
|
88
116
|
handle to use instead.
|
|
89
117
|
|
|
90
118
|
**`draft_only: true`** saves instead of sending, where they should see it before
|
|
@@ -104,9 +132,9 @@ granted, and retrying will not reveal it. A call that *is* refused was refused b
|
|
|
104
132
|
policy on purpose.
|
|
105
133
|
|
|
106
134
|
Report it plainly and let the owner decide whether to widen the grant —
|
|
107
|
-
`lanes link policy list
|
|
135
|
+
`lanes link policy list --profile <name>` shows the rules, `lanes link policy allow <capability> --profile <name> --target <name>`
|
|
108
136
|
changes them, and that is their call, not yours. **Do not look for another route
|
|
109
|
-
to the same data.** Every call is audited either way; `lanes link audit tail
|
|
137
|
+
to the same data.** Every call is audited either way; `lanes link audit tail --profile <name> --target <name>`
|
|
110
138
|
shows what was attempted, refusals included.
|
|
111
139
|
|
|
112
140
|
## Setting something up
|
|
@@ -125,9 +153,9 @@ credential — those are `lanes link` in a terminal, deliberately. What you can
|
|
|
125
153
|
know exactly what to hand over, and say what it will ask for before they start.
|
|
126
154
|
|
|
127
155
|
If you have a shell, you can run it yourself for anything that does *not* need a
|
|
128
|
-
browser: `lanes link setup plan <provider> --json` lists what to store,
|
|
129
|
-
`lanes link secrets set <ref>` takes the value on stdin, and
|
|
130
|
-
`lanes link connect <provider> --
|
|
156
|
+
browser: `lanes link setup plan <provider> --profile <name> --target <name> --json` lists what to store,
|
|
157
|
+
`lanes link secrets set <ref> --profile <name> --target <name>` takes the value on stdin, and
|
|
158
|
+
`lanes link connect <provider> --profile <name> --target <name> --id <id> --non-interactive --json` finishes.
|
|
131
159
|
Anything with a browser sign-in belongs to whoever owns the account — give them
|
|
132
160
|
the line.
|
|
133
161
|
|
|
@@ -139,10 +167,10 @@ straight after connecting will still not show it. Say so rather than retrying.
|
|
|
139
167
|
|
|
140
168
|
## Registering it, and re-registering it
|
|
141
169
|
|
|
142
|
-
`lanes link mcp add
|
|
170
|
+
`lanes link mcp add --profile <name> --target <name>` runs each harness's own registration command and installs
|
|
143
171
|
this skill where that harness keeps them. With no argument it does every harness
|
|
144
172
|
installed; name one (`claude`, `codex`) to be specific. Run it again after
|
|
145
|
-
`lanes link token rotate
|
|
173
|
+
`lanes link token rotate --profile <name> --target <name>` — add `--force`, since Claude Code stores the token as
|
|
146
174
|
a value rather than a command.
|
|
147
175
|
|
|
148
176
|
**Never paste the token.** There is a right way and a wrong way, and the
|
|
@@ -151,7 +179,7 @@ difference matters:
|
|
|
151
179
|
```bash
|
|
152
180
|
# RIGHT — the token goes from the CLI to the harness. You never see it.
|
|
153
181
|
claude mcp add --transport http lanes-link http://127.0.0.1:7337/mcp \
|
|
154
|
-
--header "Authorization: Bearer $(lanes link token show --raw)"
|
|
182
|
+
--header "Authorization: Bearer $(lanes link token show --raw --profile <name> --target <name>)"
|
|
155
183
|
|
|
156
184
|
# WRONG — the token is now in your context, and in the transcript, forever.
|
|
157
185
|
lanes link token show --show # then copying the value into the command
|
|
@@ -159,9 +187,9 @@ lanes link token show --show # then copying the value into the command
|
|
|
159
187
|
|
|
160
188
|
The token reaches every account of every profile the endpoint serves. Use the
|
|
161
189
|
substitution form. If you have already printed one by accident, say so and offer
|
|
162
|
-
`lanes link token rotate
|
|
190
|
+
`lanes link token rotate --profile <name> --target <name>`.
|
|
163
191
|
|
|
164
|
-
Prefer `lanes link mcp add
|
|
192
|
+
Prefer `lanes link mcp add --profile <name> --target <name>` to writing the command yourself: it checks the
|
|
165
193
|
endpoint is reachable, refuses to silently shadow an existing registration, and
|
|
166
194
|
cannot mistype the token. For a harness it does not know, take the command from
|
|
167
195
|
`lanes link outputs` rather than writing it blind — that command checks whether
|
|
@@ -173,7 +201,7 @@ If you register Codex, tell the user to export the token — Codex stores only t
|
|
|
173
201
|
variable name, so nothing works until it is set:
|
|
174
202
|
|
|
175
203
|
```bash
|
|
176
|
-
export LANES_LINK_TOKEN="$(lanes link token show --raw)"
|
|
204
|
+
export LANES_LINK_TOKEN="$(lanes link token show --raw --profile <name> --target <name>)"
|
|
177
205
|
```
|
|
178
206
|
|
|
179
207
|
One registration covers every profile. Do not add one per profile; they share a
|
package/package.json
CHANGED
package/src/cli/argv.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { GlobalFlags } from './runtime.ts';
|
|
2
2
|
import type { OwnerFlags } from './commands/owner.ts';
|
|
3
|
+
import type { KnowledgeFlags } from './commands/knowledge.ts';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Turning argv into a command path and a flag bag.
|
|
@@ -63,6 +64,32 @@ export function text(flags: Flags, name: string): string | undefined {
|
|
|
63
64
|
return typeof value === 'string' ? value : undefined;
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Every value given for a flag, in order.
|
|
69
|
+
*
|
|
70
|
+
* `profile add --target local --target cloud` is the one command where a flag
|
|
71
|
+
* legitimately repeats: it is declaring a list, not selecting one thing. The
|
|
72
|
+
* parser keeps the last value in `flags`, so this re-reads argv rather than
|
|
73
|
+
* changing that shape for every other flag's sake.
|
|
74
|
+
*/
|
|
75
|
+
export function all(argv: readonly string[], name: string): string[] {
|
|
76
|
+
const values: string[] = [];
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < argv.length; i++) {
|
|
79
|
+
const token = argv[i]!;
|
|
80
|
+
if (token === `--${name}`) {
|
|
81
|
+
const next = argv[i + 1];
|
|
82
|
+
if (next !== undefined && !next.startsWith('--')) values.push(next);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (token.startsWith(`--${name}=`)) values.push(token.slice(name.length + 3));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Comma-separated is the other spelling people reach for, and refusing it
|
|
89
|
+
// would be a refusal with no reason behind it.
|
|
90
|
+
return values.flatMap((value) => value.split(',').map((part) => part.trim())).filter(Boolean);
|
|
91
|
+
}
|
|
92
|
+
|
|
66
93
|
/** The flags every command accepts. */
|
|
67
94
|
export function globalFlags(flags: Flags): GlobalFlags {
|
|
68
95
|
return {
|
|
@@ -92,3 +119,26 @@ export function ownerFlags(flags: Flags): OwnerFlags {
|
|
|
92
119
|
yes: flags['yes'] === true,
|
|
93
120
|
};
|
|
94
121
|
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* `lanes link knowledge`'s flags.
|
|
125
|
+
*
|
|
126
|
+
* Here for the reason `ownerFlags` is: the kebab-case spellings belong in the
|
|
127
|
+
* one place that parses argv, so `--allow-public` is written once rather than
|
|
128
|
+
* once per reader.
|
|
129
|
+
*/
|
|
130
|
+
export function knowledgeFlags(flags: Flags): KnowledgeFlags {
|
|
131
|
+
return {
|
|
132
|
+
...globalFlags(flags),
|
|
133
|
+
repo: text(flags, 'repo'),
|
|
134
|
+
branch: text(flags, 'branch'),
|
|
135
|
+
path: text(flags, 'path'),
|
|
136
|
+
// `--migrate`, `--no-migrate`, or neither — see `KnowledgeFlags.migrate`.
|
|
137
|
+
migrate: flags['migrate'] === true ? true : flags['no-migrate'] === true ? false : undefined,
|
|
138
|
+
keep: flags['keep'] === true,
|
|
139
|
+
allowPublic: flags['allow-public'] === true,
|
|
140
|
+
replace: flags['replace'] === true,
|
|
141
|
+
yes: flags['yes'] === true,
|
|
142
|
+
json: flags['json'] === true,
|
|
143
|
+
};
|
|
144
|
+
}
|
package/src/cli/brand.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Lanes design tokens, and the pieces every page this repository serves is
|
|
3
|
+
* built from.
|
|
4
|
+
*
|
|
5
|
+
* Three surfaces render HTML — the authorization consent screen, the page a
|
|
6
|
+
* connect flow lands on, and the dashboard — and until this file existed each
|
|
7
|
+
* carried its own approximation of the brand. They had drifted: two greens and
|
|
8
|
+
* two ambers that are not tokens at all, nine different alphas standing in for
|
|
9
|
+
* one border colour, a destructive red with a dark variant the token does not
|
|
10
|
+
* have, and the system font stack where Geist belongs.
|
|
11
|
+
*
|
|
12
|
+
* Values are transcribed from https://lanes.sh/design/foundations. The rules
|
|
13
|
+
* they express, which the CSS below encodes rather than restates:
|
|
14
|
+
*
|
|
15
|
+
* - **Gold is the only accent.** "Gold for positive, neutral tokens otherwise."
|
|
16
|
+
* That is why there is no green here: a connection that works is `positive`,
|
|
17
|
+
* which is gold, and everything else is one of two neutrals. `--destructive`
|
|
18
|
+
* is for errors, not for a state that merely needs attention.
|
|
19
|
+
* - **Two weights, 400 and 500.** Nothing else is available in the loaded faces,
|
|
20
|
+
* so a heavier rule would silently synthesise.
|
|
21
|
+
* - **Lora heads, Geist speaks, Geist Mono for code and eyebrow labels.**
|
|
22
|
+
*
|
|
23
|
+
* `--border`, `--accent-gold` and `--destructive` are constant across themes.
|
|
24
|
+
* Only the five neutrals swap, which is why the dark block below is five lines
|
|
25
|
+
* rather than a second stylesheet.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Escape text for an HTML body.
|
|
30
|
+
*
|
|
31
|
+
* `escapeXml` in `connectivity/transports/dav/xml.ts` would do, and `cli` is
|
|
32
|
+
* allowed to import `connectivity` — but a page renderer reaching into a DAV
|
|
33
|
+
* transport for a string function is a dependency nobody would defend. Five
|
|
34
|
+
* lines is cheaper than the coupling.
|
|
35
|
+
*
|
|
36
|
+
* Load-bearing: these pages render a provider's `manifest.name`, and a custom
|
|
37
|
+
* provider supplies that from a YAML file of its own.
|
|
38
|
+
*/
|
|
39
|
+
export function escapeHtml(value: string): string {
|
|
40
|
+
return value
|
|
41
|
+
.replace(/&/g, '&')
|
|
42
|
+
.replace(/</g, '<')
|
|
43
|
+
.replace(/>/g, '>')
|
|
44
|
+
.replace(/"/g, '"')
|
|
45
|
+
.replace(/'/g, ''');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* One request for all three faces.
|
|
50
|
+
*
|
|
51
|
+
* `preconnect` to both hosts because the stylesheet and the font files it names
|
|
52
|
+
* are served from different origins, and the second lookup is otherwise not
|
|
53
|
+
* started until the first has parsed.
|
|
54
|
+
*/
|
|
55
|
+
export const FONTS =
|
|
56
|
+
'<link rel="preconnect" href="https://fonts.googleapis.com">\n' +
|
|
57
|
+
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>\n' +
|
|
58
|
+
'<link href="https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500' +
|
|
59
|
+
'&family=Geist:wght@400;500&family=Lora:wght@400;500&display=swap" rel="stylesheet">';
|
|
60
|
+
|
|
61
|
+
/** The one-row footer, identical to the one the Lanes API's own pages carry. */
|
|
62
|
+
export const FOOTER =
|
|
63
|
+
'<div class="footer"><p>Your unfair advantage in parallel AI coding. ' +
|
|
64
|
+
'<a href="https://lanes.sh/">lanes.sh</a></p></div>';
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The tokens, and the base every page shares.
|
|
68
|
+
*
|
|
69
|
+
* The background is **painted**, which reverses what these pages used to do.
|
|
70
|
+
* The old note read "nothing is painted" — a transparent body under
|
|
71
|
+
* `color-scheme: light dark` so the browser supplied its own canvas — and it
|
|
72
|
+
* was written to fix a hardcoded `#0d1117` that showed as a black rectangle on
|
|
73
|
+
* a light machine. The design system settles that differently: it names a
|
|
74
|
+
* background for each mode, so the fix is to paint the right one rather than
|
|
75
|
+
* none. `color-scheme` stays, because form controls and scrollbars still need
|
|
76
|
+
* to know which mode they are in.
|
|
77
|
+
*/
|
|
78
|
+
export const TOKENS = `
|
|
79
|
+
:root {
|
|
80
|
+
color-scheme: light dark;
|
|
81
|
+
--background: #EBEAE7;
|
|
82
|
+
--card: #F0EFEC;
|
|
83
|
+
--muted: #E2E1DD;
|
|
84
|
+
--foreground: #171717;
|
|
85
|
+
--muted-foreground: #202329;
|
|
86
|
+
--border: rgba(120,113,108,0.2);
|
|
87
|
+
--accent-gold: #A1845A;
|
|
88
|
+
--destructive: #A06060;
|
|
89
|
+
/* The two tints the badge variants need. Spelled as rgba rather than
|
|
90
|
+
color-mix because the consent screen opens in whatever browser a phone
|
|
91
|
+
happens to use, and both accents are constant across themes anyway. */
|
|
92
|
+
--gold-fill: rgba(161,132,90,0.1);
|
|
93
|
+
--gold-ring: rgba(161,132,90,0.25);
|
|
94
|
+
--destructive-fill: rgba(160,96,96,0.1);
|
|
95
|
+
--destructive-ring: rgba(160,96,96,0.25);
|
|
96
|
+
--sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
97
|
+
--serif: "Lora", Georgia, "Times New Roman", serif;
|
|
98
|
+
--mono: "Geist Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
99
|
+
}
|
|
100
|
+
@media (prefers-color-scheme: dark) {
|
|
101
|
+
:root {
|
|
102
|
+
--background: #171717;
|
|
103
|
+
--card: #292524;
|
|
104
|
+
--muted: #202329;
|
|
105
|
+
--foreground: #EBEAE7;
|
|
106
|
+
--muted-foreground: #A8A29E;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
* { box-sizing: border-box; }
|
|
110
|
+
body { margin: 0; font-family: var(--sans); font-weight: 400;
|
|
111
|
+
background: var(--background); color: var(--foreground); }
|
|
112
|
+
h1, h2, h3 { font-family: var(--serif); font-weight: 500; letter-spacing: -0.01em; margin: 0; }
|
|
113
|
+
h1 { font-size: 28px; line-height: 1.3; }
|
|
114
|
+
code, .mono { font-family: var(--mono); }
|
|
115
|
+
a { color: inherit; }
|
|
116
|
+
|
|
117
|
+
/* Eyebrow: 11px, uppercase, wide tracking, muted — and mono, which is where the
|
|
118
|
+
design system puts label type. */
|
|
119
|
+
.eyebrow { font-family: var(--mono); font-size: 11px; font-weight: 400;
|
|
120
|
+
letter-spacing: 0.09em; text-transform: uppercase; color: var(--muted-foreground);
|
|
121
|
+
opacity: 0.75; }
|
|
122
|
+
|
|
123
|
+
/* Surfaces are dashed and 10px; controls are 6px; pills are round. */
|
|
124
|
+
.surface { background: var(--card); border: 1px dashed var(--border); border-radius: 10px; }
|
|
125
|
+
|
|
126
|
+
/* The four badge variants, as the design system defines them: gold for
|
|
127
|
+
positive, neutral tokens otherwise, destructive for an error only. The ring
|
|
128
|
+
is an inset shadow so it costs no layout, exactly as \`ring-inset\` does. */
|
|
129
|
+
.pill { display: inline-flex; align-items: center; flex: none; white-space: nowrap;
|
|
130
|
+
font-size: 11px; font-weight: 500; letter-spacing: 0.02em;
|
|
131
|
+
padding: 2px 8px; border-radius: 999px; box-shadow: inset 0 0 0 1px var(--border); }
|
|
132
|
+
.pill.positive { background: var(--gold-fill); color: var(--accent-gold);
|
|
133
|
+
box-shadow: inset 0 0 0 1px var(--gold-ring); }
|
|
134
|
+
.pill.neutral { background: var(--muted); color: var(--muted-foreground); }
|
|
135
|
+
.pill.quiet { background: transparent; color: var(--muted-foreground); opacity: 0.7; }
|
|
136
|
+
.pill.negative { background: var(--destructive-fill); color: var(--destructive);
|
|
137
|
+
box-shadow: inset 0 0 0 1px var(--destructive-ring); }
|
|
138
|
+
|
|
139
|
+
/* Ghost is the variant every button on these pages is: muted until it is
|
|
140
|
+
wanted, and never gold, because gold is reserved for saying a thing is good. */
|
|
141
|
+
.btn { font: inherit; font-size: 12px; font-weight: 500; line-height: 1;
|
|
142
|
+
padding: 6px 9px; border-radius: 6px; cursor: pointer;
|
|
143
|
+
background: transparent; color: var(--muted-foreground);
|
|
144
|
+
border: 1px solid var(--border); }
|
|
145
|
+
.btn:hover, .btn:focus-visible { color: var(--foreground); background: var(--muted); }
|
|
146
|
+
|
|
147
|
+
.footer { margin: 40px 0 0; text-align: center; }
|
|
148
|
+
.footer p { margin: 0; font-size: 12px; line-height: 1.5; color: var(--muted-foreground); opacity: 0.75; }
|
|
149
|
+
.footer a { text-decoration: underline; }
|
|
150
|
+
`.trim();
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* What every page these modules serve loads, and nothing else.
|
|
154
|
+
*
|
|
155
|
+
* The font stylesheet and the faces it names are the only two origins any of
|
|
156
|
+
* them reaches; `default-src 'none'` closes the rest. No `script-src`, because
|
|
157
|
+
* these pages have no script — the dashboard, which has one listener, extends
|
|
158
|
+
* this rather than replacing it.
|
|
159
|
+
*/
|
|
160
|
+
export const PAGE_CSP =
|
|
161
|
+
"frame-ancestors 'none'; default-src 'none'; " +
|
|
162
|
+
"style-src 'unsafe-inline' https://fonts.googleapis.com; " +
|
|
163
|
+
'font-src https://fonts.gstatic.com';
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Headers every page here answers with.
|
|
167
|
+
*
|
|
168
|
+
* `frame-ancestors` in both spellings: one of these pages asks for the endpoint
|
|
169
|
+
* token and another lists the owner's accounts, and framing either is the cheap
|
|
170
|
+
* half of a UI-redress attack. The URLs carry a `client_id`, a `redirect_uri`,
|
|
171
|
+
* or a one-time key, none of which belongs in a Referer.
|
|
172
|
+
*/
|
|
173
|
+
export const PAGE_HEADERS: Readonly<Record<string, string>> = {
|
|
174
|
+
'content-type': 'text/html; charset=utf-8',
|
|
175
|
+
'content-security-policy': PAGE_CSP,
|
|
176
|
+
'x-frame-options': 'DENY',
|
|
177
|
+
'referrer-policy': 'no-referrer',
|
|
178
|
+
};
|