@naswerks/periscope 1.0.0 → 1.0.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/CHANGELOG.md +6 -0
- package/README.md +2 -2
- package/contracts/wire-vectors/bulk_delivered.full.json +21 -0
- package/contracts/wire-vectors/bulk_delivered.minimal.json +21 -0
- package/contracts/wire-vectors/bulk_delivered.without-stat-pair.json +22 -0
- package/contracts/wire-vectors/bulk_failed.declared-reason.json +22 -0
- package/contracts/wire-vectors/bulk_failed.unknown-reason.json +23 -0
- package/contracts/wire-vectors/bulk_request.full.json +21 -0
- package/contracts/wire-vectors/host_configure.set-and-remove.json +28 -0
- package/contracts/wire-vectors/host_configure_result.applied.json +34 -0
- package/contracts/wire-vectors/host_configure_result.refused.json +33 -0
- package/contracts/wire-vectors/link_ack.cursors.json +25 -0
- package/contracts/wire-vectors/link_ack.empty.json +16 -0
- package/contracts/wire-vectors/link_bye.cause.json +16 -0
- package/contracts/wire-vectors/link_hello.empty.json +33 -0
- package/contracts/wire-vectors/link_hello.full.json +44 -0
- package/contracts/wire-vectors/link_ping.nonce.json +16 -0
- package/contracts/wire-vectors/link_ping.unknown-payload-key.json +18 -0
- package/contracts/wire-vectors/link_pong.nonce.json +16 -0
- package/contracts/wire-vectors/link_welcome.empty.json +18 -0
- package/contracts/wire-vectors/link_welcome.full.json +25 -0
- package/contracts/wire-vectors/refused.empty.json +10 -0
- package/contracts/wire-vectors/refused.encode.empty-at.json +18 -0
- package/contracts/wire-vectors/refused.encode.empty-session-id.json +18 -0
- package/contracts/wire-vectors/refused.encode.missing-member.json +22 -0
- package/contracts/wire-vectors/refused.encode.seq-zero.json +18 -0
- package/contracts/wire-vectors/refused.encode.undeclared-configure-refusal.json +34 -0
- package/contracts/wire-vectors/refused.encode.undeclared-wire-refusal.json +27 -0
- package/contracts/wire-vectors/refused.missing-member.json +10 -0
- package/contracts/wire-vectors/refused.not-json.json +10 -0
- package/contracts/wire-vectors/refused.over-limit.json +10 -0
- package/contracts/wire-vectors/refused.seq-zero.json +10 -0
- package/contracts/wire-vectors/refused.unknown-frame.json +10 -0
- package/contracts/wire-vectors/refused.unknown-kind.json +10 -0
- package/contracts/wire-vectors/repository_list.root.json +19 -0
- package/contracts/wire-vectors/repository_list.subdirectory.json +19 -0
- package/contracts/wire-vectors/repository_list_result.entries.json +34 -0
- package/contracts/wire-vectors/repository_list_result.refused.json +24 -0
- package/contracts/wire-vectors/repository_read.head.json +20 -0
- package/contracts/wire-vectors/repository_read_result.refused.json +25 -0
- package/contracts/wire-vectors/repository_read_result.truncated.json +22 -0
- package/contracts/wire-vectors/session_cancel.bare.json +17 -0
- package/contracts/wire-vectors/session_configure.full.json +22 -0
- package/contracts/wire-vectors/session_configure.minimal.json +20 -0
- package/contracts/wire-vectors/session_delta.agent-message.json +27 -0
- package/contracts/wire-vectors/session_list.request.json +18 -0
- package/contracts/wire-vectors/session_list_result.empty.json +21 -0
- package/contracts/wire-vectors/session_list_result.full.json +36 -0
- package/contracts/wire-vectors/session_new.full.json +72 -0
- package/contracts/wire-vectors/session_new.minimal.json +22 -0
- package/contracts/wire-vectors/session_new.request-all-null.json +41 -0
- package/contracts/wire-vectors/session_prompt.empty.json +18 -0
- package/contracts/wire-vectors/session_prompt.multibyte.json +18 -0
- package/contracts/wire-vectors/session_prompt.text.json +18 -0
- package/contracts/wire-vectors/session_prompt.unknown-frame-key.json +20 -0
- package/contracts/wire-vectors/session_prompt.unknown-payload-key.json +20 -0
- package/contracts/wire-vectors/session_update.agent-message.json +26 -0
- package/contracts/wire-vectors/session_update.state-transition-with-activity.json +44 -0
- package/contracts/wire-vectors/session_update.state-transition.json +41 -0
- package/contracts/wire-vectors/session_update.wire-refusal.json +26 -0
- package/contracts/wire-vectors/transcript_failed.declared-reason.json +22 -0
- package/contracts/wire-vectors/transcript_list.from-start.json +19 -0
- package/contracts/wire-vectors/transcript_list.paged.json +19 -0
- package/contracts/wire-vectors/transcript_list_result.entry-without-cwd.json +30 -0
- package/contracts/wire-vectors/transcript_list_result.full.json +29 -0
- package/contracts/wire-vectors/transcript_list_result.last-page.json +29 -0
- package/contracts/wire-vectors/transcript_tail.any-user-entry.json +22 -0
- package/contracts/wire-vectors/transcript_tail.with-needle.json +22 -0
- package/contracts/wire-vectors/transcript_tail_result.absent.json +23 -0
- package/contracts/wire-vectors/transcript_tail_result.found.json +23 -0
- package/contracts/wire-vectors/workspace_list.from-start.json +19 -0
- package/contracts/wire-vectors/workspace_list_result.page.json +48 -0
- package/contracts/wire-vectors/workspace_list_result.refused.json +26 -0
- package/contracts/wire-vectors/workspace_release.by-path-delete-branch.json +22 -0
- package/contracts/wire-vectors/workspace_release.request.json +22 -0
- package/contracts/wire-vectors/workspace_release_bulk.two-entries.json +32 -0
- package/contracts/wire-vectors/workspace_release_bulk_result.mixed.json +37 -0
- package/contracts/wire-vectors/workspace_release_result.partial.json +26 -0
- package/contracts/wire-vectors/workspace_release_result.refused.json +26 -0
- package/contracts/wire-vectors/workspace_release_result.released.json +23 -0
- package/package.json +6 -1
- package/src/bin/command.ts +198 -0
- package/src/bin/config.ts +104 -0
- package/src/bin/login.ts +149 -0
- package/src/bin/main.ts +204 -0
- package/src/bin/pair.ts +246 -0
- package/src/bin/periscope.ts +3 -0
- package/src/bin/posture.ts +232 -0
- package/src/bin/reconfigure.ts +266 -0
- package/src/bin/serve.ts +615 -0
- package/src/bin/status.ts +61 -0
- package/src/bin/workspaces.ts +280 -0
- package/src/control/backoff.ts +40 -0
- package/src/control/codec.ts +557 -0
- package/src/control/credential.ts +33 -0
- package/src/control/frames.ts +1586 -0
- package/src/control/link-state.ts +67 -0
- package/src/control/link.ts +894 -0
- package/src/control/queue.ts +248 -0
- package/src/control/seq.ts +76 -0
- package/src/control/stream-routing.ts +216 -0
- package/src/control/stream.ts +149 -0
- package/src/core/async-queue.ts +63 -0
- package/src/core/failure.ts +61 -0
- package/src/core/index.ts +22 -0
- package/src/core/keyed-turns.ts +47 -0
- package/src/core/paths.ts +84 -0
- package/src/core/refusal.ts +430 -0
- package/src/core/result.ts +27 -0
- package/src/core/time.ts +34 -0
- package/src/core/vocab.ts +44 -0
- package/src/core/workspace-id.ts +11 -0
- package/src/gate/command.ts +617 -0
- package/src/gate/decision.ts +173 -0
- package/src/gate/escalate.ts +164 -0
- package/src/gate/gate.ts +635 -0
- package/src/gate/index.ts +53 -0
- package/src/gate/jail.ts +210 -0
- package/src/gate/local.ts +115 -0
- package/src/gate/outcome.ts +164 -0
- package/src/gate/shell.ts +295 -0
- package/src/host/agent-process.ts +694 -0
- package/src/host/bulk-post.ts +166 -0
- package/src/host/claude-transcripts.ts +404 -0
- package/src/host/config-file.ts +246 -0
- package/src/host/git-facts.ts +119 -0
- package/src/host/hooks.ts +102 -0
- package/src/host/host.ts +1901 -0
- package/src/host/index.ts +92 -0
- package/src/host/link-state-file.ts +79 -0
- package/src/host/loopback.ts +177 -0
- package/src/host/machine.ts +27 -0
- package/src/host/mcp-server.ts +66 -0
- package/src/host/package-facts.ts +25 -0
- package/src/host/paired-credential-store.ts +140 -0
- package/src/host/paths.ts +175 -0
- package/src/host/repository-read.ts +245 -0
- package/src/host/session-store.ts +133 -0
- package/src/host/sign-in.ts +352 -0
- package/src/host/telemetry.ts +105 -0
- package/src/host/token-cache.ts +214 -0
- package/src/host/transcript-fs.ts +110 -0
- package/src/host/wire-request.ts +285 -0
- package/src/host/workspace-fs.ts +90 -0
- package/src/host/workspace-trust.ts +92 -0
- package/src/identity/authorize.ts +127 -0
- package/src/identity/config.ts +227 -0
- package/src/identity/credential.ts +176 -0
- package/src/identity/device-code.ts +217 -0
- package/src/identity/index.ts +84 -0
- package/src/identity/mode.ts +161 -0
- package/src/identity/paired-credential.ts +89 -0
- package/src/identity/pkce.ts +113 -0
- package/src/identity/store.ts +98 -0
- package/src/identity/token.ts +203 -0
- package/src/index.ts +140 -0
- package/src/mcp/descriptor.ts +75 -0
- package/src/mcp/index.ts +23 -0
- package/src/mcp/schema.ts +214 -0
- package/src/mcp/server.ts +180 -0
- package/src/persistence/entry.ts +142 -0
- package/src/persistence/index.ts +47 -0
- package/src/persistence/key.ts +77 -0
- package/src/persistence/mirror.ts +118 -0
- package/src/persistence/receipt.ts +182 -0
- package/src/persistence/retention.ts +118 -0
- package/src/persistence/store.ts +183 -0
- package/src/persistence/transition-log.ts +176 -0
- package/src/protocol.ts +215 -0
- package/src/sessions/index.ts +27 -0
- package/src/sessions/registry.ts +392 -0
- package/src/sessions/session.ts +440 -0
- package/src/sessions/spawn-env.ts +318 -0
- package/src/state/coverage.ts +360 -0
- package/src/state/index.ts +58 -0
- package/src/state/machine.ts +359 -0
- package/src/state/model.ts +417 -0
- package/src/state/observer.ts +499 -0
- package/src/state/reporter.ts +60 -0
- package/src/state/store.ts +157 -0
- package/src/telemetry/index.ts +10 -0
- package/src/telemetry/usage.ts +117 -0
- package/src/workspace/git-worktree.ts +551 -0
- package/src/workspace/index.ts +17 -0
- package/src/workspace/plain-dir.ts +132 -0
- package/src/workspace/provider.ts +159 -0
- package/src/workspace/worktree-porcelain.ts +118 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expect": {
|
|
3
|
+
"decode": "ok"
|
|
4
|
+
},
|
|
5
|
+
"frame": {
|
|
6
|
+
"frame": "session",
|
|
7
|
+
"sessionId": "session-1",
|
|
8
|
+
"seq": 1,
|
|
9
|
+
"at": "2000-01-01T00:00:00.000Z",
|
|
10
|
+
"payload": {
|
|
11
|
+
"kind": "workspace_list",
|
|
12
|
+
"requestId": "request-7",
|
|
13
|
+
"fromIndex": 0
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"name": "workspace_list.from-start",
|
|
17
|
+
"protocolVersion": 9,
|
|
18
|
+
"wire": "{\"frame\":\"session\",\"sessionId\":\"session-1\",\"seq\":1,\"at\":\"2000-01-01T00:00:00.000Z\",\"payload\":{\"kind\":\"workspace_list\",\"requestId\":\"request-7\",\"fromIndex\":0}}"
|
|
19
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expect": {
|
|
3
|
+
"decode": "ok"
|
|
4
|
+
},
|
|
5
|
+
"frame": {
|
|
6
|
+
"frame": "session",
|
|
7
|
+
"sessionId": "session-1",
|
|
8
|
+
"seq": 1,
|
|
9
|
+
"at": "2000-01-01T00:00:00.000Z",
|
|
10
|
+
"payload": {
|
|
11
|
+
"kind": "workspace_list_result",
|
|
12
|
+
"requestId": "request-7",
|
|
13
|
+
"entries": [
|
|
14
|
+
{
|
|
15
|
+
"key": "session-150",
|
|
16
|
+
"path": "/srv/repo-workspaces/session-150",
|
|
17
|
+
"branch": "repo/session-150",
|
|
18
|
+
"head": "2222222222222222222222222222222222222222",
|
|
19
|
+
"detached": false,
|
|
20
|
+
"locked": false,
|
|
21
|
+
"prunable": false,
|
|
22
|
+
"merged": true,
|
|
23
|
+
"aheadCount": 0,
|
|
24
|
+
"lastCommitAt": "2026-09-09T08:30:00+00:00"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"key": "solo-detached",
|
|
28
|
+
"path": "/srv/repo-workspaces/solo-detached",
|
|
29
|
+
"branch": null,
|
|
30
|
+
"head": null,
|
|
31
|
+
"detached": true,
|
|
32
|
+
"locked": true,
|
|
33
|
+
"prunable": true,
|
|
34
|
+
"merged": null,
|
|
35
|
+
"aheadCount": null,
|
|
36
|
+
"lastCommitAt": null
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"totalCount": 27,
|
|
40
|
+
"nextIndex": 25,
|
|
41
|
+
"defaultBranch": "main",
|
|
42
|
+
"refusal": null
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"name": "workspace_list_result.page",
|
|
46
|
+
"protocolVersion": 9,
|
|
47
|
+
"wire": "{\"frame\":\"session\",\"sessionId\":\"session-1\",\"seq\":1,\"at\":\"2000-01-01T00:00:00.000Z\",\"payload\":{\"kind\":\"workspace_list_result\",\"requestId\":\"request-7\",\"entries\":[{\"key\":\"session-150\",\"path\":\"/srv/repo-workspaces/session-150\",\"branch\":\"repo/session-150\",\"head\":\"2222222222222222222222222222222222222222\",\"detached\":false,\"locked\":false,\"prunable\":false,\"merged\":true,\"aheadCount\":0,\"lastCommitAt\":\"2026-09-09T08:30:00+00:00\"},{\"key\":\"solo-detached\",\"path\":\"/srv/repo-workspaces/solo-detached\",\"branch\":null,\"head\":null,\"detached\":true,\"locked\":true,\"prunable\":true,\"merged\":null,\"aheadCount\":null,\"lastCommitAt\":null}],\"totalCount\":27,\"nextIndex\":25,\"defaultBranch\":\"main\",\"refusal\":null}}"
|
|
48
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expect": {
|
|
3
|
+
"decode": "ok"
|
|
4
|
+
},
|
|
5
|
+
"frame": {
|
|
6
|
+
"frame": "session",
|
|
7
|
+
"sessionId": "session-1",
|
|
8
|
+
"seq": 1,
|
|
9
|
+
"at": "2000-01-01T00:00:00.000Z",
|
|
10
|
+
"payload": {
|
|
11
|
+
"kind": "workspace_list_result",
|
|
12
|
+
"requestId": "request-7",
|
|
13
|
+
"entries": [],
|
|
14
|
+
"totalCount": 0,
|
|
15
|
+
"nextIndex": null,
|
|
16
|
+
"defaultBranch": null,
|
|
17
|
+
"refusal": {
|
|
18
|
+
"reason": "workspace-list-failed",
|
|
19
|
+
"detail": "no provider"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"name": "workspace_list_result.refused",
|
|
24
|
+
"protocolVersion": 9,
|
|
25
|
+
"wire": "{\"frame\":\"session\",\"sessionId\":\"session-1\",\"seq\":1,\"at\":\"2000-01-01T00:00:00.000Z\",\"payload\":{\"kind\":\"workspace_list_result\",\"requestId\":\"request-7\",\"entries\":[],\"totalCount\":0,\"nextIndex\":null,\"defaultBranch\":null,\"refusal\":{\"reason\":\"workspace-list-failed\",\"detail\":\"no provider\"}}}"
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expect": {
|
|
3
|
+
"decode": "ok"
|
|
4
|
+
},
|
|
5
|
+
"frame": {
|
|
6
|
+
"frame": "session",
|
|
7
|
+
"sessionId": "session-1",
|
|
8
|
+
"seq": 1,
|
|
9
|
+
"at": "2000-01-01T00:00:00.000Z",
|
|
10
|
+
"payload": {
|
|
11
|
+
"kind": "workspace_release",
|
|
12
|
+
"requestId": "request-5",
|
|
13
|
+
"workspaceKey": null,
|
|
14
|
+
"path": "C:/work/workspace-1",
|
|
15
|
+
"deleteBranch": true,
|
|
16
|
+
"force": true
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"name": "workspace_release.by-path-delete-branch",
|
|
20
|
+
"protocolVersion": 9,
|
|
21
|
+
"wire": "{\"frame\":\"session\",\"sessionId\":\"session-1\",\"seq\":1,\"at\":\"2000-01-01T00:00:00.000Z\",\"payload\":{\"kind\":\"workspace_release\",\"requestId\":\"request-5\",\"workspaceKey\":null,\"path\":\"C:/work/workspace-1\",\"deleteBranch\":true,\"force\":true}}"
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expect": {
|
|
3
|
+
"decode": "ok"
|
|
4
|
+
},
|
|
5
|
+
"frame": {
|
|
6
|
+
"frame": "session",
|
|
7
|
+
"sessionId": "session-1",
|
|
8
|
+
"seq": 1,
|
|
9
|
+
"at": "2000-01-01T00:00:00.000Z",
|
|
10
|
+
"payload": {
|
|
11
|
+
"kind": "workspace_release",
|
|
12
|
+
"requestId": "request-5",
|
|
13
|
+
"workspaceKey": "workspace-1",
|
|
14
|
+
"path": null,
|
|
15
|
+
"deleteBranch": false,
|
|
16
|
+
"force": false
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"name": "workspace_release.request",
|
|
20
|
+
"protocolVersion": 9,
|
|
21
|
+
"wire": "{\"frame\":\"session\",\"sessionId\":\"session-1\",\"seq\":1,\"at\":\"2000-01-01T00:00:00.000Z\",\"payload\":{\"kind\":\"workspace_release\",\"requestId\":\"request-5\",\"workspaceKey\":\"workspace-1\",\"path\":null,\"deleteBranch\":false,\"force\":false}}"
|
|
22
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expect": {
|
|
3
|
+
"decode": "ok"
|
|
4
|
+
},
|
|
5
|
+
"frame": {
|
|
6
|
+
"frame": "session",
|
|
7
|
+
"sessionId": "session-1",
|
|
8
|
+
"seq": 1,
|
|
9
|
+
"at": "2000-01-01T00:00:00.000Z",
|
|
10
|
+
"payload": {
|
|
11
|
+
"kind": "workspace_release_bulk",
|
|
12
|
+
"requestId": "request-8",
|
|
13
|
+
"releases": [
|
|
14
|
+
{
|
|
15
|
+
"workspaceKey": "workspace-1",
|
|
16
|
+
"path": null,
|
|
17
|
+
"deleteBranch": true,
|
|
18
|
+
"force": false
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"workspaceKey": null,
|
|
22
|
+
"path": "C:/work/workspace-2",
|
|
23
|
+
"deleteBranch": false,
|
|
24
|
+
"force": false
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"name": "workspace_release_bulk.two-entries",
|
|
30
|
+
"protocolVersion": 9,
|
|
31
|
+
"wire": "{\"frame\":\"session\",\"sessionId\":\"session-1\",\"seq\":1,\"at\":\"2000-01-01T00:00:00.000Z\",\"payload\":{\"kind\":\"workspace_release_bulk\",\"requestId\":\"request-8\",\"releases\":[{\"workspaceKey\":\"workspace-1\",\"path\":null,\"deleteBranch\":true,\"force\":false},{\"workspaceKey\":null,\"path\":\"C:/work/workspace-2\",\"deleteBranch\":false,\"force\":false}]}}"
|
|
32
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expect": {
|
|
3
|
+
"decode": "ok"
|
|
4
|
+
},
|
|
5
|
+
"frame": {
|
|
6
|
+
"frame": "session",
|
|
7
|
+
"sessionId": "session-1",
|
|
8
|
+
"seq": 1,
|
|
9
|
+
"at": "2000-01-01T00:00:00.000Z",
|
|
10
|
+
"payload": {
|
|
11
|
+
"kind": "workspace_release_bulk_result",
|
|
12
|
+
"requestId": "request-8",
|
|
13
|
+
"results": [
|
|
14
|
+
{
|
|
15
|
+
"workspaceKey": "workspace-1",
|
|
16
|
+
"path": "C:/work/workspace-1",
|
|
17
|
+
"directoryRemoved": true,
|
|
18
|
+
"branchDeleted": true,
|
|
19
|
+
"refusal": null
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"workspaceKey": "workspace-2",
|
|
23
|
+
"path": "C:/work/workspace-2",
|
|
24
|
+
"directoryRemoved": false,
|
|
25
|
+
"branchDeleted": false,
|
|
26
|
+
"refusal": {
|
|
27
|
+
"reason": "branch-not-merged",
|
|
28
|
+
"detail": "not merged into main"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"name": "workspace_release_bulk_result.mixed",
|
|
35
|
+
"protocolVersion": 9,
|
|
36
|
+
"wire": "{\"frame\":\"session\",\"sessionId\":\"session-1\",\"seq\":1,\"at\":\"2000-01-01T00:00:00.000Z\",\"payload\":{\"kind\":\"workspace_release_bulk_result\",\"requestId\":\"request-8\",\"results\":[{\"workspaceKey\":\"workspace-1\",\"path\":\"C:/work/workspace-1\",\"directoryRemoved\":true,\"branchDeleted\":true,\"refusal\":null},{\"workspaceKey\":\"workspace-2\",\"path\":\"C:/work/workspace-2\",\"directoryRemoved\":false,\"branchDeleted\":false,\"refusal\":{\"reason\":\"branch-not-merged\",\"detail\":\"not merged into main\"}}]}}"
|
|
37
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expect": {
|
|
3
|
+
"decode": "ok"
|
|
4
|
+
},
|
|
5
|
+
"frame": {
|
|
6
|
+
"frame": "session",
|
|
7
|
+
"sessionId": "session-1",
|
|
8
|
+
"seq": 1,
|
|
9
|
+
"at": "2000-01-01T00:00:00.000Z",
|
|
10
|
+
"payload": {
|
|
11
|
+
"kind": "workspace_release_result",
|
|
12
|
+
"requestId": "request-5",
|
|
13
|
+
"workspaceKey": "workspace-1",
|
|
14
|
+
"path": "C:/work/workspace-1",
|
|
15
|
+
"directoryRemoved": true,
|
|
16
|
+
"branchDeleted": false,
|
|
17
|
+
"refusal": {
|
|
18
|
+
"reason": "workspace-release-failed",
|
|
19
|
+
"detail": "removed the worktree but could not delete the branch"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"name": "workspace_release_result.partial",
|
|
24
|
+
"protocolVersion": 9,
|
|
25
|
+
"wire": "{\"frame\":\"session\",\"sessionId\":\"session-1\",\"seq\":1,\"at\":\"2000-01-01T00:00:00.000Z\",\"payload\":{\"kind\":\"workspace_release_result\",\"requestId\":\"request-5\",\"workspaceKey\":\"workspace-1\",\"path\":\"C:/work/workspace-1\",\"directoryRemoved\":true,\"branchDeleted\":false,\"refusal\":{\"reason\":\"workspace-release-failed\",\"detail\":\"removed the worktree but could not delete the branch\"}}}"
|
|
26
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expect": {
|
|
3
|
+
"decode": "ok"
|
|
4
|
+
},
|
|
5
|
+
"frame": {
|
|
6
|
+
"frame": "session",
|
|
7
|
+
"sessionId": "session-1",
|
|
8
|
+
"seq": 1,
|
|
9
|
+
"at": "2000-01-01T00:00:00.000Z",
|
|
10
|
+
"payload": {
|
|
11
|
+
"kind": "workspace_release_result",
|
|
12
|
+
"requestId": "request-5",
|
|
13
|
+
"workspaceKey": "workspace-1",
|
|
14
|
+
"path": null,
|
|
15
|
+
"directoryRemoved": false,
|
|
16
|
+
"branchDeleted": false,
|
|
17
|
+
"refusal": {
|
|
18
|
+
"reason": "workspace-release-failed",
|
|
19
|
+
"detail": "still backing a live session"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"name": "workspace_release_result.refused",
|
|
24
|
+
"protocolVersion": 9,
|
|
25
|
+
"wire": "{\"frame\":\"session\",\"sessionId\":\"session-1\",\"seq\":1,\"at\":\"2000-01-01T00:00:00.000Z\",\"payload\":{\"kind\":\"workspace_release_result\",\"requestId\":\"request-5\",\"workspaceKey\":\"workspace-1\",\"path\":null,\"directoryRemoved\":false,\"branchDeleted\":false,\"refusal\":{\"reason\":\"workspace-release-failed\",\"detail\":\"still backing a live session\"}}}"
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"expect": {
|
|
3
|
+
"decode": "ok"
|
|
4
|
+
},
|
|
5
|
+
"frame": {
|
|
6
|
+
"frame": "session",
|
|
7
|
+
"sessionId": "session-1",
|
|
8
|
+
"seq": 1,
|
|
9
|
+
"at": "2000-01-01T00:00:00.000Z",
|
|
10
|
+
"payload": {
|
|
11
|
+
"kind": "workspace_release_result",
|
|
12
|
+
"requestId": "request-5",
|
|
13
|
+
"workspaceKey": "workspace-1",
|
|
14
|
+
"path": "C:/work/workspace-1",
|
|
15
|
+
"directoryRemoved": true,
|
|
16
|
+
"branchDeleted": true,
|
|
17
|
+
"refusal": null
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"name": "workspace_release_result.released",
|
|
21
|
+
"protocolVersion": 9,
|
|
22
|
+
"wire": "{\"frame\":\"session\",\"sessionId\":\"session-1\",\"seq\":1,\"at\":\"2000-01-01T00:00:00.000Z\",\"payload\":{\"kind\":\"workspace_release_result\",\"requestId\":\"request-5\",\"workspaceKey\":\"workspace-1\",\"path\":\"C:/work/workspace-1\",\"directoryRemoved\":true,\"branchDeleted\":true,\"refusal\":null}}"
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naswerks/periscope",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Self-hosted runner for agent sessions: dials out to your controller, then spawns, gates, observes, prompts and streams.",
|
|
6
6
|
"keywords": [
|
|
@@ -36,6 +36,11 @@
|
|
|
36
36
|
"!dist/**/*.map",
|
|
37
37
|
"!dist/pins/",
|
|
38
38
|
"!dist/test-support/",
|
|
39
|
+
"src/",
|
|
40
|
+
"!src/**/*.test.ts",
|
|
41
|
+
"!src/pins/",
|
|
42
|
+
"!src/test-support/",
|
|
43
|
+
"contracts/wire-vectors/",
|
|
39
44
|
"docs/",
|
|
40
45
|
"examples/",
|
|
41
46
|
"README.md",
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which command the binary was asked for.
|
|
3
|
+
*
|
|
4
|
+
* Extracted out of the composition root so it can be called without starting a host, the same
|
|
5
|
+
* reason, and the same shape, as `workspaces.ts`. `bin/periscope.ts` calls `main()` on load, so
|
|
6
|
+
* importing it to test one function starts a host, opens a socket and spawns nothing useful. The
|
|
7
|
+
* fix is to split the function out; it is not to add a startup guard to the entry point, because
|
|
8
|
+
* changing production behaviour to make a test possible is how a suite starts describing a system
|
|
9
|
+
* nobody ships.
|
|
10
|
+
*
|
|
11
|
+
* The parser is deliberately tiny and not an option library. This package's direct dependencies
|
|
12
|
+
* are the agent SDK with its peer closure, `ws` and `zod`; a CLI framework for a handful of verbs
|
|
13
|
+
* would be a new dependency in a package whose boundary claims are auditable from one directory.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/** The verbs this binary answers to. Anything else is named rather than guessed at. */
|
|
17
|
+
export type Command =
|
|
18
|
+
/** Run the host: dial the controller, serve sessions. The default, and what a supervisor starts. */
|
|
19
|
+
| { readonly kind: 'serve' }
|
|
20
|
+
/**
|
|
21
|
+
* Acquire a token interactively and write it to the cache the daemon reads.
|
|
22
|
+
*
|
|
23
|
+
* Why the verb exists: `bin/serve.ts` never calls `signIn`. It builds a credential over the
|
|
24
|
+
* file token cache and a refresher, so it presents a token that is already there, and nothing
|
|
25
|
+
* else in this package ever writes one. An `npx`-installed host on someone's laptop with no way
|
|
26
|
+
* to sign in is not a host; it is a process that reads an empty cache, refuses by name, and
|
|
27
|
+
* (because the link is fail-open) connects with no headers at all.
|
|
28
|
+
*/
|
|
29
|
+
| { readonly kind: 'login' }
|
|
30
|
+
/**
|
|
31
|
+
* Trade a pair code for this machine's durable credential and write it beside the token cache.
|
|
32
|
+
*
|
|
33
|
+
* An OIDC refresh token can expire after a period of inactivity, so an unattended host on the
|
|
34
|
+
* `login` credential can quietly stop being able to dial. A paired credential has no clock; it
|
|
35
|
+
* dies only when the controller revokes it, and that refusal is loud (the upgrade 401 is terminal
|
|
36
|
+
* and the process exits naming it). `code: null` is the operator forgetting the argument, named
|
|
37
|
+
* at parse so the message can say what to type.
|
|
38
|
+
*/
|
|
39
|
+
| {
|
|
40
|
+
readonly kind: 'pair';
|
|
41
|
+
readonly code: string | null;
|
|
42
|
+
/** `--controller <origin>`: where to redeem, and what the controller names its routes from. */
|
|
43
|
+
readonly controller: string | null;
|
|
44
|
+
/** `--label <name>`: what the controller lists this machine as; overrides the environment. */
|
|
45
|
+
readonly label: string | null;
|
|
46
|
+
/** An argument the parser could not place, named so the usage line can say what to type. */
|
|
47
|
+
readonly problem: string | null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Read or write the config file the daemon falls back to.
|
|
51
|
+
*
|
|
52
|
+
* Why the verb exists: with every setting readable from the environment only, a user with no UI
|
|
53
|
+
* and no supervisor has no way to configure a repository short of exporting variables into every
|
|
54
|
+
* shell that starts the host. The file is a fallback (the environment still wins per key) and
|
|
55
|
+
* nothing else in this package writes it. `key: null` lists the file; `value: null` reads one
|
|
56
|
+
* key; both stated is a write.
|
|
57
|
+
*/
|
|
58
|
+
| {
|
|
59
|
+
readonly kind: 'config';
|
|
60
|
+
readonly key: string | null;
|
|
61
|
+
readonly value: string | null;
|
|
62
|
+
/** `--unset <key>`: remove the key from the file. */
|
|
63
|
+
readonly unset: boolean;
|
|
64
|
+
}
|
|
65
|
+
/** Print the package version. */
|
|
66
|
+
| { readonly kind: 'version' }
|
|
67
|
+
/** Say what the verbs are. */
|
|
68
|
+
| { readonly kind: 'status' }
|
|
69
|
+
| { readonly kind: 'help' }
|
|
70
|
+
/** A verb this binary does not have. Named, never silently treated as `serve`. */
|
|
71
|
+
| { readonly kind: 'unknown'; readonly name: string };
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Read the command from an argv tail (`process.argv.slice(2)`).
|
|
75
|
+
*
|
|
76
|
+
* No arguments means `serve`, and that is a contract rather than a preference: a supervisor or
|
|
77
|
+
* container starts this binary with no arguments, so a bare invocation is the daemon.
|
|
78
|
+
*
|
|
79
|
+
* An unrecognised first argument is `unknown`, never `serve`. Falling back to the default would
|
|
80
|
+
* mean `periscope logn` silently starts a host: the operator believes they are signing in, the
|
|
81
|
+
* process dials out and starts accepting sessions, and the mistake surfaces much later as an empty
|
|
82
|
+
* token cache. A typo must fail loudly at the only moment it is cheap to fix.
|
|
83
|
+
*/
|
|
84
|
+
export function readCommand(argv: readonly string[]): Command {
|
|
85
|
+
const first = argv[0];
|
|
86
|
+
|
|
87
|
+
if (first === undefined || first === '') return { kind: 'serve' };
|
|
88
|
+
|
|
89
|
+
switch (first) {
|
|
90
|
+
case 'serve':
|
|
91
|
+
return { kind: 'serve' };
|
|
92
|
+
case 'login':
|
|
93
|
+
return { kind: 'login' };
|
|
94
|
+
case 'pair':
|
|
95
|
+
return readPairArguments(argv.slice(1));
|
|
96
|
+
case 'config': {
|
|
97
|
+
if (argv[1] === '--unset') {
|
|
98
|
+
const key = argv[2];
|
|
99
|
+
return {
|
|
100
|
+
kind: 'config',
|
|
101
|
+
key: key === undefined || key === '' ? null : key,
|
|
102
|
+
value: null,
|
|
103
|
+
unset: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const key = argv[1];
|
|
107
|
+
const value = argv[2];
|
|
108
|
+
return {
|
|
109
|
+
kind: 'config',
|
|
110
|
+
key: key === undefined || key === '' ? null : key,
|
|
111
|
+
value: value === undefined || value === '' ? null : value,
|
|
112
|
+
unset: false,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
case 'version':
|
|
116
|
+
case '--version':
|
|
117
|
+
case '-v':
|
|
118
|
+
return { kind: 'version' };
|
|
119
|
+
case 'status':
|
|
120
|
+
return { kind: 'status' };
|
|
121
|
+
case 'help':
|
|
122
|
+
case '--help':
|
|
123
|
+
case '-h':
|
|
124
|
+
return { kind: 'help' };
|
|
125
|
+
default:
|
|
126
|
+
return { kind: 'unknown', name: first };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** The two flags `pair` takes. A closed set: anything else starting with `--` is a problem, never the code. */
|
|
131
|
+
const PAIR_FLAGS = ['--controller', '--label'] as const;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* `pair`'s tail: one positional (the code) and two optional flags, each as `--flag value` or
|
|
135
|
+
* `--flag=value`. Hand-rolled for the same reason the whole parser is: two flags do not earn an
|
|
136
|
+
* option library. An unknown `--x` is a problem rather than the code, so a typo'd flag cannot be
|
|
137
|
+
* sent to the controller as a pair code and refused there with the wrong message.
|
|
138
|
+
*/
|
|
139
|
+
function readPairArguments(rest: readonly string[]): Command {
|
|
140
|
+
let code: string | null = null;
|
|
141
|
+
let controller: string | null = null;
|
|
142
|
+
let label: string | null = null;
|
|
143
|
+
let problem: string | null = null;
|
|
144
|
+
|
|
145
|
+
for (let index = 0; index < rest.length; index += 1) {
|
|
146
|
+
const argument = rest[index] ?? '';
|
|
147
|
+
if (argument.startsWith('--')) {
|
|
148
|
+
const equals = argument.indexOf('=');
|
|
149
|
+
const name = equals === -1 ? argument : argument.slice(0, equals);
|
|
150
|
+
if (!(PAIR_FLAGS as readonly string[]).includes(name)) {
|
|
151
|
+
problem ??= `unknown option '${name}'`;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
let value: string | undefined;
|
|
155
|
+
if (equals === -1) {
|
|
156
|
+
value = rest[index + 1];
|
|
157
|
+
index += 1;
|
|
158
|
+
} else {
|
|
159
|
+
value = argument.slice(equals + 1);
|
|
160
|
+
}
|
|
161
|
+
if (value === undefined || value === '') {
|
|
162
|
+
problem ??= `${name} needs a value`;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (name === '--controller') controller = value;
|
|
166
|
+
else label = value;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (argument === '') continue;
|
|
170
|
+
if (code === null) code = argument;
|
|
171
|
+
else problem ??= `unexpected argument '${argument}'`;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return { kind: 'pair', code, controller, label, problem };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** What `help` prints. Kept beside the parser so a new verb cannot be added without a line here. */
|
|
178
|
+
export const USAGE = [
|
|
179
|
+
'periscope — hosts Claude Code sessions for a remote controller.',
|
|
180
|
+
'',
|
|
181
|
+
'Usage:',
|
|
182
|
+
' periscope [serve] Dial the controller and serve sessions (the default).',
|
|
183
|
+
' periscope login Sign in and write the token cache this host presents.',
|
|
184
|
+
' periscope pair <code> [--controller <origin>] [--label <name>]',
|
|
185
|
+
" Trade a pair code for this machine's durable credential. The controller",
|
|
186
|
+
' origin names where to redeem and writes the link URLs to the config file;',
|
|
187
|
+
' the label is what the controller lists this machine as.',
|
|
188
|
+
' periscope config Show the config file and every value in it.',
|
|
189
|
+
' periscope config <key> [value] Read one config value, or write it.',
|
|
190
|
+
' periscope config --unset <key> Remove one config value from the file.',
|
|
191
|
+
' periscope status Say what this host is: its link, credential, ids, workspace and where',
|
|
192
|
+
' each setting came from. Reads what serve left behind; never dials.',
|
|
193
|
+
' periscope version Print the package version.',
|
|
194
|
+
' periscope help Show this.',
|
|
195
|
+
'',
|
|
196
|
+
'Configuration is read from the environment first, then the config file (the environment wins);',
|
|
197
|
+
'see README.md.',
|
|
198
|
+
].join('\n');
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `periscope config` — read or write the config file the daemon falls back to.
|
|
3
|
+
*
|
|
4
|
+
* Why this is a command: the binary reads its configuration from the environment first and the
|
|
5
|
+
* file second (every consuming verb: `serve`, `login`, `pair`), and nothing else in this package
|
|
6
|
+
* ever writes that file. Without it, a user installing the host on a machine with no UI has
|
|
7
|
+
* exactly one configuration surface, env vars at process start, which is a supervisor's surface,
|
|
8
|
+
* not a person's. This verb is the person's.
|
|
9
|
+
*
|
|
10
|
+
* It orchestrates only. The filesystem work lives in `host/config-file.ts`, because `src/host/`
|
|
11
|
+
* is the one directory allowed to touch the machine; this module composes that seam and formats
|
|
12
|
+
* lines, exactly the split `login` and `pair` use.
|
|
13
|
+
*
|
|
14
|
+
* The output says what wins. Every listing line marks a value the environment currently
|
|
15
|
+
* overrides, because "set it and nothing changed" with a stale env var standing in front of the
|
|
16
|
+
* file is otherwise confusing; the rule is env-wins and the listing repeats it where the operator
|
|
17
|
+
* is looking.
|
|
18
|
+
*/
|
|
19
|
+
import {
|
|
20
|
+
CONFIG_KEYS,
|
|
21
|
+
configFilePath,
|
|
22
|
+
readConfigFile,
|
|
23
|
+
writeConfigEntries,
|
|
24
|
+
writeConfigEntry,
|
|
25
|
+
} from '../host/config-file.js';
|
|
26
|
+
import { configValueProblem } from './reconfigure.js';
|
|
27
|
+
|
|
28
|
+
/** What one invocation produced: the lines to print, and whether it succeeded. */
|
|
29
|
+
export interface ConfigOutcome {
|
|
30
|
+
readonly ok: boolean;
|
|
31
|
+
readonly lines: readonly string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function runConfig(
|
|
35
|
+
key: string | null,
|
|
36
|
+
value: string | null,
|
|
37
|
+
env: NodeJS.ProcessEnv,
|
|
38
|
+
unset = false,
|
|
39
|
+
): ConfigOutcome {
|
|
40
|
+
const path = configFilePath(env);
|
|
41
|
+
if (path === null) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
lines: ['there is nowhere to keep a config file — no home directory and no PERISCOPE_CONFIG_DIR'],
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (unset) {
|
|
49
|
+
if (key === null) return { ok: false, lines: ['usage: periscope config --unset <key>'] };
|
|
50
|
+
const reading = readConfigFile(env);
|
|
51
|
+
if (reading.problem !== null) return { ok: false, lines: [reading.problem] };
|
|
52
|
+
if (reading.values[key] === undefined) return { ok: true, lines: [`${key} is not set in ${path}`] };
|
|
53
|
+
const problem = writeConfigEntries(env, [{ key, value: null }]);
|
|
54
|
+
if (problem !== null) return { ok: false, lines: [problem] };
|
|
55
|
+
return { ok: true, lines: [`${key} removed from ${path}`] };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (key === null) {
|
|
59
|
+
const reading = readConfigFile(env);
|
|
60
|
+
if (reading.problem !== null) return { ok: false, lines: [reading.problem] };
|
|
61
|
+
const entries = Object.entries(reading.values);
|
|
62
|
+
const lines = [
|
|
63
|
+
`config file: ${path}`,
|
|
64
|
+
...(entries.length === 0 ? ['(empty — set a value with: periscope config <key> <value>)'] : []),
|
|
65
|
+
...entries.map(([name, stored]) => {
|
|
66
|
+
const fromEnv = env[name];
|
|
67
|
+
const overridden =
|
|
68
|
+
fromEnv !== undefined && fromEnv !== '' ? ' (currently overridden by the environment)' : '';
|
|
69
|
+
return ` ${name} = ${stored}${overridden}`;
|
|
70
|
+
}),
|
|
71
|
+
`keys this host reads: ${CONFIG_KEYS.join(', ')}`,
|
|
72
|
+
];
|
|
73
|
+
return { ok: true, lines };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (value === null) {
|
|
77
|
+
const reading = readConfigFile(env);
|
|
78
|
+
if (reading.problem !== null) return { ok: false, lines: [reading.problem] };
|
|
79
|
+
const stored = reading.values[key];
|
|
80
|
+
if (stored === undefined) {
|
|
81
|
+
return { ok: false, lines: [`${key} is not set in ${path}`] };
|
|
82
|
+
}
|
|
83
|
+
return { ok: true, lines: [stored] };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The same screen the wire runs on `host_configure`, before anything is written: a value the
|
|
87
|
+
// daemon would refuse at its next start is refused here, where the person typing it is looking.
|
|
88
|
+
const screened = configValueProblem(key, value);
|
|
89
|
+
if (screened !== null) return { ok: false, lines: [screened] };
|
|
90
|
+
const problem = writeConfigEntry(env, key, value);
|
|
91
|
+
if (problem !== null) return { ok: false, lines: [problem] };
|
|
92
|
+
const fromEnv = env[key];
|
|
93
|
+
return {
|
|
94
|
+
ok: true,
|
|
95
|
+
lines: [
|
|
96
|
+
`${key} written to ${path}`,
|
|
97
|
+
...(fromEnv !== undefined && fromEnv !== ''
|
|
98
|
+
? [
|
|
99
|
+
`note: ${key} is currently set in the environment, and the environment wins — the file value is a fallback`,
|
|
100
|
+
]
|
|
101
|
+
: []),
|
|
102
|
+
],
|
|
103
|
+
};
|
|
104
|
+
}
|