@lanes-sh/link 0.7.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -10
- package/instructions/agents/lanes-link-scout.md +2 -2
- package/instructions/skills/lanes-link/SKILL.md +148 -73
- package/package.json +2 -1
- package/src/audit/index.ts +8 -1
- package/src/auth/index.ts +58 -2
- package/src/auth/lanes/assertion.ts +256 -0
- package/src/auth/lanes/callback.ts +135 -0
- package/src/auth/lanes/federation.ts +50 -0
- package/src/auth/lanes/login.ts +294 -0
- package/src/auth/lanes/members.ts +103 -0
- package/src/auth/lanes/session.ts +97 -0
- package/src/auth/oauth/grant.ts +183 -0
- package/src/auth/oauth/result.ts +27 -0
- package/src/auth/oauth/server.ts +176 -203
- package/src/auth/oauth/store.ts +85 -4
- package/src/auth/remote.ts +32 -9
- package/src/cli/accepts.ts +109 -0
- package/src/cli/argv.ts +57 -3
- package/src/cli/audit-change.ts +140 -0
- package/src/cli/callback-page.ts +36 -115
- package/src/cli/commands/auth-dispatch.ts +48 -0
- package/src/cli/commands/auth.ts +229 -0
- package/src/cli/commands/connect/accounts.ts +4 -4
- package/src/cli/commands/connect/authorise.ts +4 -4
- package/src/cli/commands/connect/bind-credential.ts +2 -1
- package/src/cli/commands/connect/custom/index.ts +1 -1
- package/src/cli/commands/connect/custom/write.ts +2 -2
- package/src/cli/commands/connect/grant.ts +29 -14
- package/src/cli/commands/connect/index.ts +90 -88
- package/src/cli/commands/connect/options.ts +83 -0
- package/src/cli/commands/connect/registration.ts +50 -0
- package/src/cli/commands/connect/requirements.ts +1 -1
- package/src/cli/commands/connect/settle.ts +16 -6
- package/src/cli/commands/connect/target-note.ts +7 -2
- package/src/cli/commands/connect/unknown.ts +1 -1
- package/src/cli/commands/connect/variables.ts +3 -2
- package/src/cli/commands/connection-list.ts +116 -0
- package/src/cli/commands/connection.ts +183 -165
- package/src/cli/commands/grant.ts +140 -0
- package/src/cli/commands/identity.ts +24 -12
- package/src/cli/commands/knowledge/index.ts +49 -89
- package/src/cli/commands/knowledge/migrate.ts +79 -13
- package/src/cli/commands/knowledge/show.ts +97 -0
- package/src/cli/commands/knowledge.ts +2 -1
- package/src/cli/commands/mcp/harnesses.ts +30 -8
- package/src/cli/commands/mcp/onboarding.ts +86 -0
- package/src/cli/commands/mcp/register.ts +16 -2
- package/src/cli/commands/mcp.ts +1 -0
- package/src/cli/commands/members.ts +288 -0
- package/src/cli/commands/operate/attach.ts +3 -3
- package/src/cli/commands/operate/audit.ts +11 -7
- package/src/cli/commands/operate/auth.ts +28 -11
- package/src/cli/commands/operate/findings.ts +2 -1
- package/src/cli/commands/operate/inspect.ts +37 -19
- package/src/cli/commands/operate/migrate.ts +33 -13
- package/src/cli/commands/operate/outputs.ts +3 -3
- package/src/cli/commands/operate/pair-certificate.ts +141 -0
- package/src/cli/commands/operate/pair.ts +324 -0
- package/src/cli/commands/operate/policy.ts +73 -22
- package/src/cli/commands/operate/serve.ts +53 -5
- package/src/cli/commands/operate/status.ts +18 -10
- package/src/cli/commands/operate/tools.ts +2 -2
- package/src/cli/commands/operate.ts +2 -0
- package/src/cli/commands/owner/assets.ts +2 -2
- package/src/cli/commands/owner/entities.ts +2 -2
- package/src/cli/commands/owner/memory.ts +2 -2
- package/src/cli/commands/owner/shared.ts +13 -2
- package/src/cli/commands/owner/skills.ts +28 -8
- package/src/cli/commands/owner/tasks.ts +2 -2
- package/src/cli/commands/owner/vault.ts +3 -3
- package/src/cli/commands/profile/disposition.ts +236 -0
- package/src/cli/commands/profile/removal.ts +154 -64
- package/src/cli/commands/profile/remove.ts +83 -7
- package/src/cli/commands/profile.ts +79 -16
- package/src/cli/commands/relabel.ts +112 -0
- package/src/cli/commands/secrets.ts +39 -17
- package/src/cli/commands/set-workspace.ts +96 -0
- package/src/cli/commands/setup.ts +2 -2
- package/src/cli/commands/sync.ts +8 -8
- package/src/cli/commands/target.ts +9 -7
- package/src/cli/commands/update-migration.ts +54 -0
- package/src/cli/commands/update.ts +78 -24
- package/src/cli/config-edit.ts +99 -143
- package/src/cli/config-migrate.ts +82 -64
- package/src/cli/config-repair-sweep.ts +119 -0
- package/src/cli/config-repair.ts +131 -125
- package/src/cli/config-templates.ts +200 -0
- package/src/cli/contract3-credentials.ts +294 -0
- package/src/cli/contract3-data.ts +262 -0
- package/src/cli/contract3-layout.ts +46 -0
- package/src/cli/contract3-shape.ts +212 -0
- package/src/cli/contract3.ts +399 -0
- package/src/cli/contract4-credentials.ts +207 -0
- package/src/cli/contract4-data.ts +399 -0
- package/src/cli/contract4-rename.ts +73 -0
- package/src/cli/contract4-yaml.ts +223 -0
- package/src/cli/contract4.ts +342 -0
- package/src/cli/endpoint-url.ts +1 -1
- package/src/cli/identity.ts +44 -26
- package/src/cli/lanes.ts +25 -1
- package/src/cli/main.ts +94 -14
- package/src/cli/migrate-move.ts +166 -0
- package/src/cli/migrate-plan.ts +12 -6
- package/src/cli/output.ts +34 -1
- package/src/cli/publish.ts +6 -7
- package/src/cli/runtime/open.ts +64 -99
- package/src/cli/runtime/registry.ts +6 -7
- package/src/cli/runtime/select.ts +2 -11
- package/src/cli/runtime/stores.ts +58 -0
- package/src/cli/runtime/types.ts +106 -0
- package/src/cli/runtime/vault.ts +19 -4
- package/src/cli/runtime/workspace.ts +60 -0
- package/src/cli/runtime.ts +2 -1
- package/src/cli/selection-require.ts +44 -13
- package/src/cli/selection.ts +127 -145
- package/src/cli/usage.ts +40 -20
- package/src/cli/workspace-migrate.ts +152 -22
- package/src/connectivity/manifest/provider.ts +34 -13
- package/src/connectivity/manifest/requirements.ts +1 -1
- package/src/connectivity/transports/imap/parser.ts +70 -9
- package/src/deployments/adapters/filesystem.ts +18 -3
- package/src/deployments/bind.ts +1 -1
- package/src/deployments/deploy.ts +38 -29
- package/src/deployments/gcp/bucket.ts +58 -11
- package/src/deployments/gcp/provision.ts +7 -7
- package/src/deployments/knowledge.ts +9 -4
- package/src/deployments/prepare.ts +72 -24
- package/src/deployments/record.ts +1 -1
- package/src/deployments/report.ts +2 -2
- package/src/deployments/serving.ts +15 -74
- package/src/deployments/target.ts +34 -13
- package/src/deployments/upload.ts +60 -27
- package/src/dispatch/deps.ts +88 -0
- package/src/dispatch/dispatch.ts +21 -62
- package/src/policy/index.ts +47 -15
- package/src/profile/connections.ts +195 -0
- package/src/profile/deployments.ts +86 -8
- package/src/profile/index.ts +35 -6
- package/src/profile/knowledge.ts +18 -5
- package/src/profile/layout.ts +163 -90
- package/src/profile/load.ts +133 -64
- package/src/profile/pairing.ts +32 -0
- package/src/profile/primitives.ts +35 -1
- package/src/profile/registry.ts +6 -6
- package/src/profile/schema.ts +181 -21
- package/src/profile/targets.ts +21 -9
- package/src/profile/testing.ts +104 -2
- package/src/profile/workspace.ts +124 -33
- package/src/providers/assets/provider.ts +6 -6
- package/src/providers/custom/index.ts +1 -1
- package/src/providers/custom/load.ts +2 -3
- package/src/providers/entities/provider.ts +6 -6
- package/src/providers/entities/writes.ts +1 -1
- package/src/providers/identity/provider.ts +2 -2
- package/src/providers/memory/provider.ts +26 -8
- package/src/providers/setup/plan.ts +1 -1
- package/src/providers/setup/provider.ts +3 -3
- package/src/providers/skills/provider.ts +2 -2
- package/src/providers/slack/index.ts +2 -2
- package/src/providers/tasks/provider.ts +6 -6
- package/src/providers/vault/provider.ts +1 -1
- package/src/registry/policy-bridge.ts +33 -11
- package/src/registry/reconcile.ts +4 -4
- package/src/registry/registry.ts +1 -1
- package/src/server/authorization.ts +94 -0
- package/src/server/edge.ts +14 -1
- package/src/server/endpoint.ts +89 -104
- package/src/server/generation.ts +10 -1
- package/src/server/harness.ts +71 -13
- package/src/server/index.ts +31 -0
- package/src/server/mcp/build.ts +20 -1
- package/src/server/mcp/client-info.ts +54 -0
- package/src/server/mcp/guide.ts +120 -0
- package/src/server/mcp/instructions.ts +22 -22
- package/src/server/mcp/prompts.ts +7 -3
- package/src/server/mcp/resources.ts +16 -8
- package/src/server/mcp/routing.ts +3 -3
- package/src/server/mcp/tools.ts +25 -6
- package/src/server/mcp/visibility.ts +74 -7
- package/src/server/oauth.ts +29 -109
- package/src/server/read/credential.ts +134 -0
- package/src/server/read/deployed.ts +56 -0
- package/src/server/read/listener.ts +54 -0
- package/src/server/read/open.ts +101 -0
- package/src/server/read/routes.ts +247 -0
- package/src/server/read/state.ts +171 -0
- package/src/stores/blobs/conformance.ts +19 -0
- package/src/stores/state/index.ts +76 -10
- package/src/stores/state/testing.ts +5 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { CONNECTIONS_FILE, type IdentityEntry, type ProfileSelection, type Resolution } from '#profile';
|
|
2
2
|
import { ConfigDocument } from '../config-edit.ts';
|
|
3
3
|
import { ensureIdentityConnection, repairLines, repaired } from '../config-repair.ts';
|
|
4
4
|
import { announce, announceProfile, emit, ok, print, style, table, warn } from '../output.ts';
|
|
@@ -37,9 +37,9 @@ export interface IdentityListing {
|
|
|
37
37
|
readonly reachable: boolean;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/** Whether a rule list puts `
|
|
40
|
+
/** Whether a rule list puts `lanes_identity.*` in force, in either spelling. */
|
|
41
41
|
function covers(rules: ReadonlyArray<{ capability: string }>): boolean {
|
|
42
|
-
return rules.some((rule) => rule.capability === '*' || rule.capability === '
|
|
42
|
+
return rules.some((rule) => rule.capability === '*' || rule.capability === 'lanes_identity.*');
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
/**
|
|
@@ -51,14 +51,20 @@ function covers(rules: ReadonlyArray<{ capability: string }>): boolean {
|
|
|
51
51
|
* misleading thing this command could print.
|
|
52
52
|
*/
|
|
53
53
|
function readable(config: {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
grants: ReadonlyArray<{
|
|
55
|
+
connection: string;
|
|
56
|
+
allow: ReadonlyArray<{ capability: string }>;
|
|
57
|
+
deny: ReadonlyArray<{ capability: string }>;
|
|
58
|
+
}>;
|
|
56
59
|
}): boolean {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
);
|
|
60
|
+
// One row now rather than a connection and a rule that had to agree
|
|
61
|
+
// (ADR-058). The old shape could say "declared but not granted" and
|
|
62
|
+
// "granted but not declared", and both served nothing while reading like
|
|
63
|
+
// configuration that worked; neither is expressible here.
|
|
64
|
+
const grant = config.grants.find((row) => row.connection.startsWith('lanes_identity.'));
|
|
65
|
+
if (grant === undefined) return false;
|
|
66
|
+
|
|
67
|
+
return covers(grant.allow) && !covers(grant.deny);
|
|
62
68
|
}
|
|
63
69
|
|
|
64
70
|
/**
|
|
@@ -96,7 +102,13 @@ export async function addIdentity(
|
|
|
96
102
|
const entry: IdentityEntry = { kind, value, ...(options.note ? { note: options.note } : {}) };
|
|
97
103
|
document.addTo(['identity'], entry, { inline: true });
|
|
98
104
|
|
|
99
|
-
|
|
105
|
+
// The identity *connection* is the workspace's, like every other (ADR-057),
|
|
106
|
+
// so provisioning the surface on first use touches two files. Both saves, or
|
|
107
|
+
// neither: a grant naming a connection that does not exist is refused at load,
|
|
108
|
+
// so writing the profile alone would leave one that no longer opens.
|
|
109
|
+
const connections = await ConfigDocument.openKey(resolution.workspaceRoot, CONNECTIONS_FILE);
|
|
110
|
+
const repair = ensureIdentityConnection(connections, document);
|
|
111
|
+
await connections.save();
|
|
100
112
|
await document.save();
|
|
101
113
|
|
|
102
114
|
return {
|
|
@@ -232,7 +244,7 @@ export async function identityList(options: { json?: boolean } & GlobalFlags): P
|
|
|
232
244
|
print(warn('declared, but no agent can read it'));
|
|
233
245
|
print(
|
|
234
246
|
style.dim(
|
|
235
|
-
' the identity surface needs a connection row and
|
|
247
|
+
' the identity surface needs a connection row and a lanes_identity.* allow rule;',
|
|
236
248
|
),
|
|
237
249
|
);
|
|
238
250
|
print(style.dim(' adding an entry with this command writes both.'));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
soleGrantFor,
|
|
2
3
|
ConfigError,
|
|
3
4
|
KNOWLEDGE_LAYOUT,
|
|
4
5
|
knowledgeTargetSchema,
|
|
@@ -23,7 +24,6 @@ import {
|
|
|
23
24
|
removeLocal,
|
|
24
25
|
summarise,
|
|
25
26
|
} from './migrate.ts';
|
|
26
|
-
|
|
27
27
|
/**
|
|
28
28
|
* `lanes link knowledge` — where this profile's memory and skills are kept.
|
|
29
29
|
*
|
|
@@ -65,78 +65,6 @@ export interface KnowledgeFlags extends GlobalFlags {
|
|
|
65
65
|
readonly fetch?: FetchLike | undefined;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
export async function knowledgeShow(flags: KnowledgeFlags): Promise<void> {
|
|
69
|
-
const runtime = await openRuntime(flags, { fetch: flags.fetch });
|
|
70
|
-
try {
|
|
71
|
-
if (!flags.json) announce(runtime.resolution);
|
|
72
|
-
|
|
73
|
-
const profile = runtime.config.instance.profile;
|
|
74
|
-
const selection = ` --profile ${runtime.resolution.profile} --target ${runtime.target}`;
|
|
75
|
-
const skills = (await runtime.skills.list()).length;
|
|
76
|
-
const memory = (await runtime.storage.list(`${KNOWLEDGE_LAYOUT.memory}/`)).length;
|
|
77
|
-
const entities = (await runtime.storage.list(`${KNOWLEDGE_LAYOUT.entities}/`)).length;
|
|
78
|
-
const where = runtime.knowledge?.describe;
|
|
79
|
-
|
|
80
|
-
if (flags.json) {
|
|
81
|
-
print(
|
|
82
|
-
JSON.stringify(
|
|
83
|
-
{ target: runtime.target, where: where ?? 'local', memory, skills, entities },
|
|
84
|
-
null,
|
|
85
|
-
2,
|
|
86
|
-
),
|
|
87
|
-
);
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
heading('Knowledge');
|
|
92
|
-
table([
|
|
93
|
-
// The memory *directory*, not the blob root it sits in. `layout.blobs`
|
|
94
|
-
// is `data/<profile>`, which is where every provider's namespace lives —
|
|
95
|
-
// printing it here would name a directory that is mostly not memory.
|
|
96
|
-
[
|
|
97
|
-
' memory',
|
|
98
|
-
where ? `${where}/${KNOWLEDGE_LAYOUT.memory}` : `${layout.blobs(profile)}/${KNOWLEDGE_LAYOUT.memory}`,
|
|
99
|
-
style.dim(`${memory} file${memory === 1 ? '' : 's'}`),
|
|
100
|
-
],
|
|
101
|
-
[
|
|
102
|
-
' skills',
|
|
103
|
-
where ? `${where}/${KNOWLEDGE_LAYOUT.skills}` : layout.skills(profile),
|
|
104
|
-
style.dim(`${skills} file${skills === 1 ? '' : 's'}`),
|
|
105
|
-
],
|
|
106
|
-
// The count includes the derived `_index.json`, deliberately: it is a
|
|
107
|
-
// file in that directory and it is committed with the rest, so a number
|
|
108
|
-
// that quietly excluded it would not match what a person sees there.
|
|
109
|
-
[
|
|
110
|
-
' entities',
|
|
111
|
-
where
|
|
112
|
-
? `${where}/${KNOWLEDGE_LAYOUT.entities}`
|
|
113
|
-
: `${layout.blobs(profile)}/${KNOWLEDGE_LAYOUT.entities}`,
|
|
114
|
-
style.dim(`${entities} file${entities === 1 ? '' : 's'}`),
|
|
115
|
-
],
|
|
116
|
-
]);
|
|
117
|
-
|
|
118
|
-
print('');
|
|
119
|
-
print(
|
|
120
|
-
style.dim(
|
|
121
|
-
` The vault, the credential store, runtime state and the audit log stay in target "${runtime.target}".`,
|
|
122
|
-
),
|
|
123
|
-
);
|
|
124
|
-
// Complete commands, not shapes. Every one of these is pasted, and with
|
|
125
|
-
// nothing left to fall back on (ADR-037) a line missing either flag is a
|
|
126
|
-
// line that refuses — `emitted.test.ts` states the rule for the templates
|
|
127
|
-
// reachable without a runtime, and this is the same rule where there is one.
|
|
128
|
-
print(
|
|
129
|
-
style.dim(
|
|
130
|
-
where
|
|
131
|
-
? ` Bring them back with: lanes link knowledge use local --migrate${selection}`
|
|
132
|
-
: ` Keep them in a repository with: lanes link knowledge use github --repo <owner/name>${selection}`,
|
|
133
|
-
),
|
|
134
|
-
);
|
|
135
|
-
} finally {
|
|
136
|
-
await runtime.close();
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
68
|
export async function knowledgeUse(where: string | undefined, flags: KnowledgeFlags): Promise<void> {
|
|
141
69
|
if (where === 'local') return useLocal(flags);
|
|
142
70
|
if (where === 'github') return useGithub(flags);
|
|
@@ -172,7 +100,7 @@ async function useGithub(flags: KnowledgeFlags): Promise<void> {
|
|
|
172
100
|
const runtime = await openRuntime(flags, { fetch: flags.fetch });
|
|
173
101
|
try {
|
|
174
102
|
announce(runtime.resolution);
|
|
175
|
-
const selection = ` --profile ${runtime.resolution.profile} --
|
|
103
|
+
const selection = ` --profile ${runtime.resolution.profile} --workspace ${runtime.target}`;
|
|
176
104
|
|
|
177
105
|
if (runtime.knowledge) {
|
|
178
106
|
print(style.dim(` This profile already reads ${runtime.knowledge.describe}.`));
|
|
@@ -187,11 +115,7 @@ async function useGithub(flags: KnowledgeFlags): Promise<void> {
|
|
|
187
115
|
|
|
188
116
|
// Nothing is written until this returns: the token is asked for, the
|
|
189
117
|
// repository is probed, and either can refuse while the profile is intact.
|
|
190
|
-
const secrets = await openSecretStoreFor(
|
|
191
|
-
runtime.config,
|
|
192
|
-
runtime.resolution.workspaceRoot,
|
|
193
|
-
runtime.target,
|
|
194
|
-
);
|
|
118
|
+
const secrets = await openSecretStoreFor(runtime.resolution.workspaceRoot, runtime.target);
|
|
195
119
|
const token = await resolveToken(secrets, knowledge.token_ref, knowledge.repo, selection, {
|
|
196
120
|
replace: flags.replace,
|
|
197
121
|
});
|
|
@@ -206,7 +130,16 @@ async function useGithub(flags: KnowledgeFlags): Promise<void> {
|
|
|
206
130
|
[' token', viewer, style.dim('can write')],
|
|
207
131
|
]);
|
|
208
132
|
|
|
209
|
-
|
|
133
|
+
// Which instances this profile grants, so `memory/` and `entities/` reach
|
|
134
|
+
// only its own and not every profile's (ADR-059).
|
|
135
|
+
const instances = grantedInstances(runtime.config);
|
|
136
|
+
|
|
137
|
+
const movable = await localContents(
|
|
138
|
+
runtime.storage,
|
|
139
|
+
runtime.skills ?? null,
|
|
140
|
+
knowledge,
|
|
141
|
+
instances,
|
|
142
|
+
);
|
|
210
143
|
const moving = await decideMigration(movable.length > 0, flags);
|
|
211
144
|
|
|
212
145
|
if (moving) {
|
|
@@ -225,7 +158,7 @@ async function useGithub(flags: KnowledgeFlags): Promise<void> {
|
|
|
225
158
|
if (flags.keep) {
|
|
226
159
|
print(style.dim(' --keep: the local copies are still there, and are no longer read.'));
|
|
227
160
|
} else {
|
|
228
|
-
await removeLocal(runtime.storage, runtime.skills, movable);
|
|
161
|
+
await removeLocal(runtime.storage, runtime.skills ?? null, movable, instances);
|
|
229
162
|
print(ok('removed the local copies'));
|
|
230
163
|
}
|
|
231
164
|
} else if (movable.length > 0) {
|
|
@@ -268,11 +201,7 @@ async function useLocal(flags: KnowledgeFlags): Promise<void> {
|
|
|
268
201
|
|
|
269
202
|
// Built here rather than taken from `runtime.knowledge`, so both directions
|
|
270
203
|
// of this command reach the repository through one constructor.
|
|
271
|
-
const secrets = await openSecretStoreFor(
|
|
272
|
-
runtime.config,
|
|
273
|
-
runtime.resolution.workspaceRoot,
|
|
274
|
-
runtime.target,
|
|
275
|
-
);
|
|
204
|
+
const secrets = await openSecretStoreFor(runtime.resolution.workspaceRoot, runtime.target);
|
|
276
205
|
const token = await resolveToken(secrets, knowledge.token_ref, knowledge.repo, selection);
|
|
277
206
|
const repository = repositoryFor(knowledge, token, flags.fetch);
|
|
278
207
|
|
|
@@ -280,7 +209,13 @@ async function useLocal(flags: KnowledgeFlags): Promise<void> {
|
|
|
280
209
|
flags.migrate ??
|
|
281
210
|
(await agreedTo(flags, `Copy everything in ${knowledge.repo} back onto this target's storage?`))
|
|
282
211
|
) {
|
|
283
|
-
const moved = await moveOut(
|
|
212
|
+
const moved = await moveOut(
|
|
213
|
+
repository,
|
|
214
|
+
knowledge,
|
|
215
|
+
local.storage,
|
|
216
|
+
local.skills,
|
|
217
|
+
grantedInstances(runtime.config),
|
|
218
|
+
);
|
|
284
219
|
print(
|
|
285
220
|
ok(
|
|
286
221
|
`wrote back ${moved.memory} memory entr${moved.memory === 1 ? 'y' : 'ies'}, ` +
|
|
@@ -314,7 +249,7 @@ async function useLocal(flags: KnowledgeFlags): Promise<void> {
|
|
|
314
249
|
*/
|
|
315
250
|
async function openLocalStores(
|
|
316
251
|
runtime: Runtime,
|
|
317
|
-
): Promise<{ storage: BlobStore; skills: BlobStore }> {
|
|
252
|
+
): Promise<{ storage: BlobStore; skills: BlobStore | null }> {
|
|
318
253
|
const { openStorage } = await import('#deployments/target.ts');
|
|
319
254
|
const declared = runtime.declared;
|
|
320
255
|
|
|
@@ -327,7 +262,14 @@ async function openLocalStores(
|
|
|
327
262
|
},
|
|
328
263
|
runtime.credentials,
|
|
329
264
|
);
|
|
330
|
-
|
|
265
|
+
// The *granted* connection, beside the profile. `layout.skills` changed
|
|
266
|
+
// meaning without changing arity at ADR-059, so the compiler was silent;
|
|
267
|
+
// ADR-066 put the profile back in front of it, and it is an argument now.
|
|
268
|
+
const skillsConnection = soleGrantFor(runtime.config, 'lanes_skills');
|
|
269
|
+
return {
|
|
270
|
+
storage: factory(),
|
|
271
|
+
skills: skillsConnection === undefined ? null : factory(layout.skills(runtime.config.instance.profile, skillsConnection)),
|
|
272
|
+
};
|
|
331
273
|
}
|
|
332
274
|
|
|
333
275
|
/**
|
|
@@ -386,3 +328,21 @@ async function agreedTo(flags: KnowledgeFlags, question: string): Promise<boolea
|
|
|
386
328
|
if (!yes) print(style.dim(' cancelled'));
|
|
387
329
|
return yes;
|
|
388
330
|
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* The memory and entities instances one profile grants.
|
|
334
|
+
*
|
|
335
|
+
* `main` where a profile grants none, which is what a profile that predates the
|
|
336
|
+
* owner layer looks like and is also where a fresh one puts them. The point is
|
|
337
|
+
* that this is never the *surface* — `memory/` alone matches every profile's
|
|
338
|
+
* notes now that the blob root is the workspace's.
|
|
339
|
+
*/
|
|
340
|
+
function grantedInstances(config: Parameters<typeof soleGrantFor>[0]): {
|
|
341
|
+
memory: string;
|
|
342
|
+
entities: string;
|
|
343
|
+
} {
|
|
344
|
+
return {
|
|
345
|
+
memory: soleGrantFor(config, 'lanes_memory') ?? 'main',
|
|
346
|
+
entities: soleGrantFor(config, 'lanes_entities') ?? 'main',
|
|
347
|
+
};
|
|
348
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ConfigError,
|
|
3
3
|
KNOWLEDGE_LAYOUT,
|
|
4
|
+
KNOWLEDGE_PREFIX,
|
|
4
5
|
knowledgeRoot,
|
|
5
6
|
type KnowledgeArea,
|
|
6
7
|
type KnowledgeConfig,
|
|
@@ -43,16 +44,75 @@ export interface Movable {
|
|
|
43
44
|
* it once means `localContents`, `removeLocal` and `moveOut` each became a loop
|
|
44
45
|
* over this rather than a branch on a literal.
|
|
45
46
|
*/
|
|
46
|
-
|
|
47
|
+
/**
|
|
48
|
+
* One area's local home: where to look, and what of that key is the repository's.
|
|
49
|
+
*
|
|
50
|
+
* Two prefixes rather than one, and the difference is the whole fix. `scope` is
|
|
51
|
+
* what is listed — `memory/<instance>/`, so a profile sees only the instance it
|
|
52
|
+
* grants. `prefix` is what is stripped to get the repository path, which stays
|
|
53
|
+
* `memory/<instance>/<file>` because that is the layout ADR-041 documents and
|
|
54
|
+
* what makes two profiles' notes distinguishable in one repository.
|
|
55
|
+
*
|
|
56
|
+
* Collapsing them listed every profile's memory and then wrote it flat.
|
|
57
|
+
*/
|
|
58
|
+
type LocalArea = {
|
|
59
|
+
readonly store: BlobStore;
|
|
60
|
+
readonly scope: string;
|
|
61
|
+
readonly prefix: string;
|
|
62
|
+
};
|
|
47
63
|
|
|
48
|
-
|
|
64
|
+
/** The connection ids this profile's memory and entities live under. */
|
|
65
|
+
export interface Instances {
|
|
66
|
+
readonly memory: string;
|
|
67
|
+
readonly entities: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function localAreas(
|
|
71
|
+
storage: BlobStore,
|
|
72
|
+
skills: BlobStore | null,
|
|
73
|
+
/** Which instance of each surface this profile grants (ADR-059). */
|
|
74
|
+
instances: Instances,
|
|
75
|
+
): Record<KnowledgeArea, LocalArea> {
|
|
49
76
|
return {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
77
|
+
// Scoped to the instance this profile grants, not to the surface.
|
|
78
|
+
//
|
|
79
|
+
// `layout.blobs()` is the whole workspace since contract 3, so a prefix of
|
|
80
|
+
// `memory/` matched every profile's memory — and `knowledge use github
|
|
81
|
+
// --migrate --profile personal` therefore committed `work`'s notes to
|
|
82
|
+
// personal's repository and then deleted them locally, leaving `work`
|
|
83
|
+
// reading an empty store with no knowledge block of its own. `skills` was
|
|
84
|
+
// already connection-scoped, which is why it alone was correct.
|
|
85
|
+
// `scope` reads the *local* store and `prefix` names the repository
|
|
86
|
+
// directory, and since contract 4 those are different strings:
|
|
87
|
+
// `lanes_memory/lan1/` on disk becomes `memory/lan1/` in the repository.
|
|
88
|
+
// Reading one where the other belongs finds nothing and reports success.
|
|
89
|
+
memory: {
|
|
90
|
+
store: storage,
|
|
91
|
+
scope: `${KNOWLEDGE_PREFIX.memory}/${instances.memory}/`,
|
|
92
|
+
prefix: `${KNOWLEDGE_PREFIX.memory}/`,
|
|
93
|
+
},
|
|
94
|
+
// Null becomes an empty area rather than a refusal: a profile granting no
|
|
95
|
+
// skills connection has none to move, and `knowledge use` should still move
|
|
96
|
+
// its memory and entities rather than failing on the one area that is empty
|
|
97
|
+
// by construction (ADR-059).
|
|
98
|
+
skills: { store: skills ?? EMPTY_AREA, scope: '', prefix: '' },
|
|
99
|
+
entities: {
|
|
100
|
+
store: storage,
|
|
101
|
+
scope: `${KNOWLEDGE_PREFIX.entities}/${instances.entities}/`,
|
|
102
|
+
prefix: `${KNOWLEDGE_PREFIX.entities}/`,
|
|
103
|
+
},
|
|
53
104
|
};
|
|
54
105
|
}
|
|
55
106
|
|
|
107
|
+
/** Nothing to list, nothing to delete. See `localAreas`. */
|
|
108
|
+
const EMPTY_AREA: BlobStore = {
|
|
109
|
+
get: async () => null,
|
|
110
|
+
put: async () => {},
|
|
111
|
+
has: async () => false,
|
|
112
|
+
delete: async () => {},
|
|
113
|
+
list: async () => [],
|
|
114
|
+
};
|
|
115
|
+
|
|
56
116
|
const AREAS = ['memory', 'skills', 'entities'] as const;
|
|
57
117
|
|
|
58
118
|
/**
|
|
@@ -66,16 +126,18 @@ const AREAS = ['memory', 'skills', 'entities'] as const;
|
|
|
66
126
|
*/
|
|
67
127
|
export async function localContents(
|
|
68
128
|
storage: BlobStore,
|
|
69
|
-
skills
|
|
129
|
+
/** Null when the profile grants no skills connection (ADR-059). */
|
|
130
|
+
skills: BlobStore | null,
|
|
70
131
|
knowledge: KnowledgeConfig,
|
|
132
|
+
instances: Instances,
|
|
71
133
|
): Promise<Movable[]> {
|
|
72
|
-
const areas = localAreas(storage, skills);
|
|
134
|
+
const areas = localAreas(storage, skills, instances);
|
|
73
135
|
const found: Movable[] = [];
|
|
74
136
|
|
|
75
137
|
for (const area of AREAS) {
|
|
76
|
-
const { store, prefix } = areas[area];
|
|
138
|
+
const { store, scope, prefix } = areas[area];
|
|
77
139
|
|
|
78
|
-
for (const entry of await store.list(
|
|
140
|
+
for (const entry of await store.list(scope)) {
|
|
79
141
|
const data = await store.get(entry.key);
|
|
80
142
|
if (data === null) continue; // Listed then deleted; not worth failing over.
|
|
81
143
|
|
|
@@ -128,10 +190,12 @@ export async function moveIn(
|
|
|
128
190
|
/** Remove what has been verified elsewhere. Never called before `moveIn`. */
|
|
129
191
|
export async function removeLocal(
|
|
130
192
|
storage: BlobStore,
|
|
131
|
-
skills
|
|
193
|
+
/** Null when the profile grants no skills connection (ADR-059). */
|
|
194
|
+
skills: BlobStore | null,
|
|
132
195
|
movable: readonly Movable[],
|
|
196
|
+
instances: Instances,
|
|
133
197
|
): Promise<void> {
|
|
134
|
-
const areas = localAreas(storage, skills);
|
|
198
|
+
const areas = localAreas(storage, skills, instances);
|
|
135
199
|
|
|
136
200
|
for (const item of movable) {
|
|
137
201
|
const { store, prefix } = areas[item.area];
|
|
@@ -152,10 +216,12 @@ export async function moveOut(
|
|
|
152
216
|
repository: GithubRepository,
|
|
153
217
|
knowledge: KnowledgeConfig,
|
|
154
218
|
storage: BlobStore,
|
|
155
|
-
skills
|
|
219
|
+
/** Null when the profile grants no skills connection (ADR-059). */
|
|
220
|
+
skills: BlobStore | null,
|
|
221
|
+
instances: Instances,
|
|
156
222
|
): Promise<Record<KnowledgeArea, number>> {
|
|
157
223
|
const { entries } = await repository.entries();
|
|
158
|
-
const areas = localAreas(storage, skills);
|
|
224
|
+
const areas = localAreas(storage, skills, instances);
|
|
159
225
|
const counts: Record<KnowledgeArea, number> = { memory: 0, skills: 0, entities: 0 };
|
|
160
226
|
|
|
161
227
|
for (const entry of entries.values()) {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { KNOWLEDGE_LAYOUT, KNOWLEDGE_PREFIX, layout, soleGrantFor } from '#profile';
|
|
2
|
+
import { announce, emit, heading, print, style, table } from '../../output.ts';
|
|
3
|
+
import { openRuntime } from '../../runtime.ts';
|
|
4
|
+
import type { KnowledgeFlags } from './index.ts';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* `lanes link knowledge show` — where memory, skills and entities actually are.
|
|
8
|
+
*
|
|
9
|
+
* Split from `knowledge use` so `index.ts` stays inside the size budget, on the
|
|
10
|
+
* seam the two commands already have: this one opens stores and counts, and
|
|
11
|
+
* never writes. That difference is why `show` can answer for a repository `use`
|
|
12
|
+
* would refuse to touch.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export async function knowledgeShow(flags: KnowledgeFlags): Promise<void> {
|
|
16
|
+
const runtime = await openRuntime(flags, { fetch: flags.fetch });
|
|
17
|
+
try {
|
|
18
|
+
if (!flags.json) announce(runtime.resolution);
|
|
19
|
+
|
|
20
|
+
const profile = runtime.config.instance.profile;
|
|
21
|
+
const selection = ` --profile ${runtime.resolution.profile} --target ${runtime.target}`;
|
|
22
|
+
const skills = runtime.skills ? (await runtime.skills.list()).length : 0;
|
|
23
|
+
// Counted through the *provider's* prefix, which is what the local store is
|
|
24
|
+
// scoped into; `KNOWLEDGE_LAYOUT` is the repository's directory name and
|
|
25
|
+
// since contract 4 the two are different strings. Counting by the wrong one
|
|
26
|
+
// reports zero for a profile whose memory is perfectly well populated.
|
|
27
|
+
const memory = (await runtime.storage.list(`${KNOWLEDGE_PREFIX.memory}/`)).length;
|
|
28
|
+
const entities = (await runtime.storage.list(`${KNOWLEDGE_PREFIX.entities}/`)).length;
|
|
29
|
+
const where = runtime.knowledge?.describe;
|
|
30
|
+
// Null when no skills connection is granted (ADR-059). The row still prints,
|
|
31
|
+
// saying so: a missing row reads as "there are none", not "not granted".
|
|
32
|
+
const skillsConnection = soleGrantFor(runtime.config, 'lanes_skills');
|
|
33
|
+
|
|
34
|
+
if (flags.json) {
|
|
35
|
+
print(
|
|
36
|
+
JSON.stringify(
|
|
37
|
+
{ target: runtime.target, where: where ?? 'local', memory, skills, entities },
|
|
38
|
+
null,
|
|
39
|
+
2,
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
heading('Knowledge');
|
|
46
|
+
table([
|
|
47
|
+
// The memory *directory*, not the blob root it sits in. `layout.blobs`
|
|
48
|
+
// is the profile's own directory, which is where every provider's
|
|
49
|
+
// namespace lives — printing it here would name a directory that is
|
|
50
|
+
// mostly not memory.
|
|
51
|
+
[
|
|
52
|
+
' memory',
|
|
53
|
+
where ? `${where}/${KNOWLEDGE_LAYOUT.memory}` : `${layout.blobs(profile)}/${KNOWLEDGE_PREFIX.memory}`,
|
|
54
|
+
style.dim(`${memory} file${memory === 1 ? '' : 's'}`),
|
|
55
|
+
],
|
|
56
|
+
[
|
|
57
|
+
' skills',
|
|
58
|
+
where
|
|
59
|
+
? `${where}/${KNOWLEDGE_LAYOUT.skills}`
|
|
60
|
+
: skillsConnection
|
|
61
|
+
? layout.skills(profile, skillsConnection)
|
|
62
|
+
: style.dim('not granted'),
|
|
63
|
+
style.dim(`${skills} file${skills === 1 ? '' : 's'}`),
|
|
64
|
+
],
|
|
65
|
+
// The count includes the derived `_index.json`, deliberately: it is a
|
|
66
|
+
// file in that directory and it is committed with the rest, so a number
|
|
67
|
+
// that quietly excluded it would not match what a person sees there.
|
|
68
|
+
[
|
|
69
|
+
' entities',
|
|
70
|
+
where
|
|
71
|
+
? `${where}/${KNOWLEDGE_LAYOUT.entities}`
|
|
72
|
+
: `${layout.blobs(profile)}/${KNOWLEDGE_PREFIX.entities}`,
|
|
73
|
+
style.dim(`${entities} file${entities === 1 ? '' : 's'}`),
|
|
74
|
+
],
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
print('');
|
|
78
|
+
print(
|
|
79
|
+
style.dim(
|
|
80
|
+
` The vault, the credential store, runtime state and the audit log stay in workspace "${runtime.target}".`,
|
|
81
|
+
),
|
|
82
|
+
);
|
|
83
|
+
// Complete commands, not shapes. Every one of these is pasted, and with
|
|
84
|
+
// nothing left to fall back on (ADR-037) a line missing either flag is a
|
|
85
|
+
// line that refuses — `emitted.test.ts` states the rule for the templates
|
|
86
|
+
// reachable without a runtime, and this is the same rule where there is one.
|
|
87
|
+
print(
|
|
88
|
+
style.dim(
|
|
89
|
+
where
|
|
90
|
+
? ` Bring them back with: lanes link knowledge use local --migrate${selection}`
|
|
91
|
+
: ` Keep them in a repository with: lanes link knowledge use github --repo <owner/name>${selection}`,
|
|
92
|
+
),
|
|
93
|
+
);
|
|
94
|
+
} finally {
|
|
95
|
+
await runtime.close();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -7,4 +7,5 @@
|
|
|
7
7
|
* the grammar that puts them in order.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
export {
|
|
10
|
+
export { knowledgeUse, type KnowledgeFlags } from './knowledge/index.ts';
|
|
11
|
+
export { knowledgeShow } from './knowledge/show.ts';
|
|
@@ -17,12 +17,31 @@ import { join } from 'node:path';
|
|
|
17
17
|
* command, write the file where it does not. A skill is content in a documented
|
|
18
18
|
* location, not a config format we would be guessing at, and the directory
|
|
19
19
|
* written is named after this project.
|
|
20
|
+
*
|
|
21
|
+
* **No registration carries a token any more** (ADR-062). Every endpoint runs
|
|
22
|
+
* the authorization flow, discovery is served ahead of the auth gate on
|
|
23
|
+
* loopback as well as deployed, and a client pointed at a bare URL finds
|
|
24
|
+
* `/.well-known/oauth-protected-resource`, signs its owner in at lanes.sh, and
|
|
25
|
+
* comes back with a token of its own. What that removes is worth stating: the
|
|
26
|
+
* registration no longer contains a credential, so a config file synced to a
|
|
27
|
+
* dotfiles repository is no longer a leak, and a rotation does not invalidate
|
|
28
|
+
* every harness at once.
|
|
29
|
+
*
|
|
30
|
+
* The exception is `tokenEnv`, which survives for the one caller that has no
|
|
31
|
+
* browser — see `token show`, which is now documented as a CI command.
|
|
20
32
|
*/
|
|
21
33
|
|
|
22
34
|
export interface AddInput {
|
|
23
35
|
readonly name: string;
|
|
24
36
|
readonly url: string;
|
|
25
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The endpoint's static token, for a harness that cannot run a browser.
|
|
39
|
+
*
|
|
40
|
+
* Nothing passes this in the ordinary path. It is here because
|
|
41
|
+
* `--headless` exists for CI, where there is no browser to complete an
|
|
42
|
+
* authorization in and a pasted credential is the only thing that works.
|
|
43
|
+
*/
|
|
44
|
+
readonly token?: string | undefined;
|
|
26
45
|
readonly tokenEnv: string;
|
|
27
46
|
readonly scope: string;
|
|
28
47
|
/**
|
|
@@ -44,11 +63,12 @@ export interface Harness {
|
|
|
44
63
|
/** Whether `--scope` means anything here. Codex config is global. */
|
|
45
64
|
readonly scoped: boolean;
|
|
46
65
|
/**
|
|
47
|
-
* Whether
|
|
66
|
+
* Whether a credential reaches the harness's config at all.
|
|
48
67
|
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
68
|
+
* Both are `false` in the ordinary path now — the client authorises itself.
|
|
69
|
+
* This still distinguishes what a `--headless` registration would write:
|
|
70
|
+
* Claude Code would hold the header value, and Codex stores only the *name*
|
|
71
|
+
* of an environment variable it reads at launch.
|
|
52
72
|
*/
|
|
53
73
|
readonly storesToken: boolean;
|
|
54
74
|
/**
|
|
@@ -99,8 +119,10 @@ export const HARNESSES: readonly Harness[] = [
|
|
|
99
119
|
'http',
|
|
100
120
|
name,
|
|
101
121
|
url,
|
|
102
|
-
|
|
103
|
-
|
|
122
|
+
// Only when there is no browser to sign in with. Registering the bare URL
|
|
123
|
+
// is the ordinary path: Claude Code discovers the protected-resource
|
|
124
|
+
// document and runs the authorization itself.
|
|
125
|
+
...(token ? ['--header', `Authorization: Bearer ${token}`] : []),
|
|
104
126
|
'--scope',
|
|
105
127
|
scope,
|
|
106
128
|
],
|
|
@@ -142,7 +164,7 @@ export const HARNESSES: readonly Harness[] = [
|
|
|
142
164
|
// anywhere else: an unresolvable substitution yields the empty string, the
|
|
143
165
|
// header becomes "Bearer ", and the only symptom is a 401 that reads as a
|
|
144
166
|
// bad token rather than a command that refused.
|
|
145
|
-
` export ${tokenEnv}="$(lanes link token show --raw --profile ${profile} --
|
|
167
|
+
` export ${tokenEnv}="$(lanes link token show --raw --profile ${profile} --workspace ${target})"`,
|
|
146
168
|
'',
|
|
147
169
|
'Add that to your shell profile. This is the better half of the bargain: the token never',
|
|
148
170
|
'reaches ~/.codex/config.toml, and a "lanes link token rotate" is picked up on next launch',
|