ticlawk 0.1.17-dev.19 → 0.1.17-dev.20
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 +23 -42
- package/bin/ticlawk.mjs +10 -46
- package/package.json +4 -2
- package/scripts/publish-dev.sh +77 -0
- package/src/adapters/ticlawk/api.mjs +0 -28
- package/src/adapters/ticlawk/credentials.mjs +0 -2
- package/src/adapters/ticlawk/index.mjs +35 -317
- package/src/core/adapter-registry.mjs +1 -19
- package/src/core/profiles.mjs +0 -1
- package/src/core/runtime-env.mjs +0 -1
- package/ticlawk.mjs +2 -5
package/README.md
CHANGED
|
@@ -13,57 +13,42 @@ from your phone.
|
|
|
13
13
|
|
|
14
14
|
## Quickstart — 2 minutes
|
|
15
15
|
|
|
16
|
-
You need a
|
|
17
|
-
|
|
16
|
+
You need a machine with at least one supported runtime installed and the
|
|
17
|
+
Ticlawk app installed on your phone.
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
# 1. install
|
|
21
21
|
curl -fsSL https://raw.githubusercontent.com/darthjaja6/ticlawk/main/install.sh | bash
|
|
22
22
|
|
|
23
23
|
# 2. connect this machine to your Ticlawk account by scanning the QR code
|
|
24
|
-
cd /path/to/project
|
|
25
24
|
ticlawk connect
|
|
26
25
|
```
|
|
27
26
|
|
|
28
|
-
`ticlawk connect`
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
directory when you want to bind a new project.
|
|
27
|
+
`ticlawk connect` authorizes the current machine as a Ticlawk host. After the
|
|
28
|
+
host is connected, create agents in the app by choosing that host and one of
|
|
29
|
+
the runtimes the daemon reports as available.
|
|
32
30
|
|
|
33
31
|
> ⚠️ **Single user, by design.** The connector binds to your Ticlawk account;
|
|
34
32
|
> agents run with your local user's permissions and can edit files / run
|
|
35
|
-
> commands
|
|
36
|
-
>
|
|
33
|
+
> commands on that host. Don't share your connector API key and don't connect
|
|
34
|
+
> a host you wouldn't trust a shell to touch.
|
|
37
35
|
|
|
38
36
|
---
|
|
39
37
|
|
|
40
|
-
##
|
|
38
|
+
## Creating Agents
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|-------------|----------------|------------------------|---------------------------------------------------------------|
|
|
47
|
-
| Claude Code | `--workdir` | `--session-id` | filename of `~/.claude/projects/**/*.jsonl` (without `.jsonl`)|
|
|
48
|
-
| Codex | `--workdir` | `--session-id` | `payload.id` in the first `session_meta` record of `~/.codex/sessions/**/*.jsonl` |
|
|
49
|
-
| OpenClaw | `--agent-id` | none | logical agent name (`main` by default) |
|
|
50
|
-
| opencode | `--workdir` | `--session-id` (with `--workdir`) | `.id` from `opencode session list --format json` (run inside the project dir) |
|
|
40
|
+
`ticlawk connect` does not create an agent. It connects a host. The daemon
|
|
41
|
+
then reports runtime health for Codex, Claude Code, opencode, OpenClaw, and pi.
|
|
42
|
+
In the mobile app, tap `+`, choose a connected host, choose an available
|
|
43
|
+
runtime, and create the agent there.
|
|
51
44
|
|
|
52
45
|
[OpenClaw](https://github.com/openclaw/openclaw) is an open-source local-first
|
|
53
|
-
agent runtime; skip
|
|
54
|
-
|
|
55
|
-
[opencode](https://opencode.ai) is provider-agnostic — it doesn't carry its own
|
|
56
|
-
account system. Before connecting through `ticlawk`, install the CLI
|
|
57
|
-
(`npm i -g opencode-ai`) and authenticate it once against the LLM provider
|
|
58
|
-
you want it to use (`opencode auth login`, then pick Anthropic / OpenAI /
|
|
59
|
-
Google / OpenRouter / etc. and paste the corresponding API key). Model
|
|
60
|
-
selection lives in opencode's own config (`opencode.json` in the project
|
|
61
|
-
root, or user-level config) — `ticlawk` does not pass `--model`.
|
|
46
|
+
agent runtime; skip it if you don't use it.
|
|
62
47
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
48
|
+
[opencode](https://opencode.ai) is provider-agnostic. Before creating an
|
|
49
|
+
opencode-backed agent through Ticlawk, install the CLI (`npm i -g opencode-ai`)
|
|
50
|
+
and authenticate it once against the LLM provider you want it to use
|
|
51
|
+
(`opencode auth login`).
|
|
67
52
|
|
|
68
53
|
---
|
|
69
54
|
|
|
@@ -74,11 +59,10 @@ After setup:
|
|
|
74
59
|
- Keep `ticlawk` running as a user service. If Linux prints an
|
|
75
60
|
`Action required` message during install or connect, run the command it
|
|
76
61
|
shows so the daemon keeps running after logout and reboot.
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
again or reset the session from the app.
|
|
62
|
+
- Connect another host: run `ticlawk connect` on that machine.
|
|
63
|
+
- Create a new agent from the app by choosing a connected host and runtime.
|
|
64
|
+
- If replies stop because the local runtime session ended, reset the agent
|
|
65
|
+
session from the app.
|
|
82
66
|
- `ticlawk health` is the first thing to check when something feels off.
|
|
83
67
|
|
|
84
68
|
---
|
|
@@ -201,7 +185,6 @@ Usage:
|
|
|
201
185
|
ticlawk config
|
|
202
186
|
ticlawk config get <streaming...|runtimes.claude_code.path|runtimes.codex.path|runtimes.opencode.path|runtimes.pi.path|ticlawk.connector-api-key|ticlawk.api-url|ticlawk.connector-ws-url>
|
|
203
187
|
ticlawk config set <streaming...|runtimes.claude_code.path|runtimes.codex.path|runtimes.opencode.path|runtimes.pi.path|ticlawk.connector-api-key|ticlawk.api-url|ticlawk.connector-ws-url> <value>
|
|
204
|
-
ticlawk auth --code <6-digit-code> [--api-url <url>]
|
|
205
188
|
ticlawk connect
|
|
206
189
|
ticlawk profile list
|
|
207
190
|
ticlawk profile current
|
|
@@ -221,8 +204,7 @@ Agent CLI (run inside an agent runtime; requires TICLAWK_RUNTIME_AGENT_ID):
|
|
|
221
204
|
ticlawk server info [--refresh]
|
|
222
205
|
|
|
223
206
|
Commands:
|
|
224
|
-
|
|
225
|
-
connect connect ticlawk to a local runtime
|
|
207
|
+
connect connect this host to Ticlawk
|
|
226
208
|
profile list or switch saved local identities
|
|
227
209
|
message send/read chat messages (agent CLI surface)
|
|
228
210
|
task claim/update/list tasks (agent CLI surface)
|
|
@@ -245,7 +227,6 @@ Config examples:
|
|
|
245
227
|
|
|
246
228
|
Examples:
|
|
247
229
|
ticlawk connect
|
|
248
|
-
ticlawk auth --code <6-digit-code>
|
|
249
230
|
```
|
|
250
231
|
<!-- usage:end -->
|
|
251
232
|
|
|
@@ -254,7 +235,7 @@ Examples:
|
|
|
254
235
|
## Security model
|
|
255
236
|
|
|
256
237
|
- **Agents can run code on your machine.** Whatever Claude Code / Codex /
|
|
257
|
-
OpenClaw / opencode / pi can do
|
|
238
|
+
OpenClaw / opencode / pi can do on the connected host, the Ticlawk app can
|
|
258
239
|
ask it to do. Treat the chat as you'd treat a shell.
|
|
259
240
|
- **Single user.** The connector binds to one Ticlawk account. Don't share
|
|
260
241
|
your connector API key.
|
package/bin/ticlawk.mjs
CHANGED
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
getActiveProfile,
|
|
19
19
|
listProfiles,
|
|
20
20
|
} from '../src/core/profiles.mjs';
|
|
21
|
-
import { runAdapterAuth } from '../src/core/adapter-registry.mjs';
|
|
22
21
|
import { runTiclawkConnect } from '../src/core/ticlawk-control.mjs';
|
|
23
22
|
import {
|
|
24
23
|
RUNTIME_DEFINITIONS,
|
|
@@ -81,7 +80,6 @@ Usage:
|
|
|
81
80
|
ticlawk config
|
|
82
81
|
ticlawk config get <${configKeys}>
|
|
83
82
|
ticlawk config set <${configKeys}> <value>
|
|
84
|
-
ticlawk auth --code <6-digit-code> [--api-url <url>]
|
|
85
83
|
ticlawk connect
|
|
86
84
|
ticlawk profile list
|
|
87
85
|
ticlawk profile current
|
|
@@ -101,8 +99,7 @@ Agent CLI (run inside an agent runtime; requires TICLAWK_RUNTIME_AGENT_ID):
|
|
|
101
99
|
ticlawk server info [--refresh]
|
|
102
100
|
|
|
103
101
|
Commands:
|
|
104
|
-
|
|
105
|
-
connect connect ticlawk to a local runtime
|
|
102
|
+
connect connect this host to Ticlawk
|
|
106
103
|
profile list or switch saved local identities
|
|
107
104
|
message send/read chat messages (agent CLI surface)
|
|
108
105
|
task claim/update/list tasks (agent CLI surface)
|
|
@@ -122,30 +119,15 @@ ${getRuntimeConfigExamples()}
|
|
|
122
119
|
|
|
123
120
|
Examples:
|
|
124
121
|
ticlawk connect
|
|
125
|
-
ticlawk auth --code <6-digit-code>
|
|
126
|
-
`;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function getAuthHelp() {
|
|
130
|
-
return `ticlawk auth --code <6-digit-code> [--api-url <url>]
|
|
131
|
-
|
|
132
|
-
Store the ticlawk app pairing code locally. The daemon picks it up on the
|
|
133
|
-
next start and exchanges it for an API key.
|
|
134
|
-
|
|
135
|
-
Options:
|
|
136
|
-
--code <code> 6-digit setup code from the ticlawk app
|
|
137
|
-
--api-url <url> optional ticlawk API base URL override
|
|
138
122
|
`;
|
|
139
123
|
}
|
|
140
124
|
|
|
141
125
|
function getConnectHelp() {
|
|
142
126
|
return `ticlawk connect
|
|
143
127
|
|
|
144
|
-
Use \`connect\` to
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
Codex, Claude Code, opencode, and pi, then choose the runtime in ticlawk.
|
|
148
|
-
No pairing QR code is shown when no local agent harness is detected.
|
|
128
|
+
Use \`connect\` to authorize this machine as a Ticlawk host.
|
|
129
|
+
After the host is connected, create agents from the app by choosing this host
|
|
130
|
+
and one of the detected local runtimes.
|
|
149
131
|
|
|
150
132
|
Examples:
|
|
151
133
|
ticlawk connect
|
|
@@ -162,10 +144,6 @@ function printHelp(helpPath, args = {}) {
|
|
|
162
144
|
printUsage();
|
|
163
145
|
return;
|
|
164
146
|
}
|
|
165
|
-
if (head === 'auth') {
|
|
166
|
-
console.log(getAuthHelp());
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
147
|
if (head === 'connect') {
|
|
170
148
|
console.log(getConnectHelp());
|
|
171
149
|
return;
|
|
@@ -242,7 +220,7 @@ function formatSecretConfigValue(configKey, value) {
|
|
|
242
220
|
}
|
|
243
221
|
|
|
244
222
|
function buildConnectPayload(args) {
|
|
245
|
-
const
|
|
223
|
+
const rejectedArgs = [
|
|
246
224
|
'adapter',
|
|
247
225
|
'type',
|
|
248
226
|
'session-id',
|
|
@@ -252,21 +230,19 @@ function buildConnectPayload(args) {
|
|
|
252
230
|
'agent-id',
|
|
253
231
|
'runtime-path',
|
|
254
232
|
'code',
|
|
233
|
+
'name',
|
|
234
|
+
'switch-user',
|
|
255
235
|
].filter((key) => args[key] !== undefined);
|
|
256
236
|
const positionalRuntime = args._?.[1];
|
|
257
|
-
if (positionalRuntime ||
|
|
237
|
+
if (positionalRuntime || rejectedArgs.length > 0) {
|
|
258
238
|
const suffix = positionalRuntime ? ` ${positionalRuntime}` : '';
|
|
259
239
|
console.error(`ticlawk connect${suffix} no longer accepts explicit runtime, adapter, session, or workdir arguments.`);
|
|
260
|
-
console.error('Run `ticlawk connect`
|
|
261
|
-
console.error('
|
|
240
|
+
console.error('Run `ticlawk connect` with no runtime arguments to authorize this host.');
|
|
241
|
+
console.error('Create agents from the app after the host is connected.');
|
|
262
242
|
process.exit(1);
|
|
263
243
|
}
|
|
264
244
|
return {
|
|
265
245
|
adapter: 'ticlawk',
|
|
266
|
-
autoRuntime: true,
|
|
267
|
-
switchUser: Boolean(args['switch-user']),
|
|
268
|
-
workdir: process.cwd(),
|
|
269
|
-
...(args.name ? { name: args.name } : {}),
|
|
270
246
|
};
|
|
271
247
|
}
|
|
272
248
|
|
|
@@ -637,18 +613,6 @@ async function main() {
|
|
|
637
613
|
return;
|
|
638
614
|
}
|
|
639
615
|
|
|
640
|
-
if (command === 'auth') {
|
|
641
|
-
if (args.help || args.h) {
|
|
642
|
-
printHelp(['auth'], args);
|
|
643
|
-
return;
|
|
644
|
-
}
|
|
645
|
-
const adapterArgv = rawArgv.slice(1);
|
|
646
|
-
const res = await runAdapterAuth('ticlawk', adapterArgv);
|
|
647
|
-
printCommandResult(res);
|
|
648
|
-
process.exitCode = res.statusCode >= 400 ? 1 : 0;
|
|
649
|
-
return;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
616
|
if (command === 'connect') {
|
|
653
617
|
if (args.help || args.h) {
|
|
654
618
|
console.log(getConnectHelp());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ticlawk",
|
|
3
|
-
"version": "0.1.17-dev.
|
|
3
|
+
"version": "0.1.17-dev.20",
|
|
4
4
|
"description": "Local connector that links agent harnesses (Claude Code, Codex, OpenClaw, opencode, Pi) to the Ticlawk mobile app.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "ticlawk.mjs",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"assets/",
|
|
16
16
|
"bin/",
|
|
17
17
|
"scripts/postinstall.mjs",
|
|
18
|
+
"scripts/publish-dev.sh",
|
|
18
19
|
"src/",
|
|
19
20
|
"cc-watcher.mjs",
|
|
20
21
|
"ticlawk.mjs",
|
|
@@ -33,7 +34,8 @@
|
|
|
33
34
|
"health": "node ./bin/ticlawk.mjs health",
|
|
34
35
|
"dev": "echo 'Configure ~/.ticlawk/.config, then: npm run start'",
|
|
35
36
|
"generate:usage-docs": "node scripts/generate-usage-docs.mjs",
|
|
36
|
-
"verify:public-package": "bash scripts/verify-ticlawk-public.sh"
|
|
37
|
+
"verify:public-package": "bash scripts/verify-ticlawk-public.sh",
|
|
38
|
+
"publish:dev": "bash scripts/publish-dev.sh"
|
|
37
39
|
},
|
|
38
40
|
"repository": {
|
|
39
41
|
"type": "git",
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
cd "$ROOT_DIR"
|
|
6
|
+
|
|
7
|
+
EXPECTED_VERSION=""
|
|
8
|
+
DRY_RUN=false
|
|
9
|
+
|
|
10
|
+
while [[ $# -gt 0 ]]; do
|
|
11
|
+
case "$1" in
|
|
12
|
+
--expected-version)
|
|
13
|
+
EXPECTED_VERSION="${2:-}"
|
|
14
|
+
if [[ -z "$EXPECTED_VERSION" ]]; then
|
|
15
|
+
echo "--expected-version requires a value" >&2
|
|
16
|
+
exit 2
|
|
17
|
+
fi
|
|
18
|
+
shift 2
|
|
19
|
+
;;
|
|
20
|
+
--dry-run)
|
|
21
|
+
DRY_RUN=true
|
|
22
|
+
shift
|
|
23
|
+
;;
|
|
24
|
+
*)
|
|
25
|
+
echo "unknown argument: $1" >&2
|
|
26
|
+
echo "usage: npm run publish:dev -- [--expected-version X.Y.Z-dev.N] [--dry-run]" >&2
|
|
27
|
+
exit 2
|
|
28
|
+
;;
|
|
29
|
+
esac
|
|
30
|
+
done
|
|
31
|
+
|
|
32
|
+
VERSION="$(node -p "JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version")"
|
|
33
|
+
|
|
34
|
+
if [[ -n "$EXPECTED_VERSION" && "$EXPECTED_VERSION" != "$VERSION" ]]; then
|
|
35
|
+
echo "expected version ($EXPECTED_VERSION) does not match cli/package.json ($VERSION)" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
if [[ "$VERSION" != *-* ]]; then
|
|
40
|
+
echo "refusing to publish stable version $VERSION with the dev script" >&2
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
NPM_ARGS=(--registry https://registry.npmjs.org/)
|
|
45
|
+
TMP_NPMRC=""
|
|
46
|
+
if [[ -n "${NPM_TOKEN:-}" ]]; then
|
|
47
|
+
TMP_NPMRC="$(mktemp)"
|
|
48
|
+
chmod 0600 "$TMP_NPMRC"
|
|
49
|
+
printf '//registry.npmjs.org/:_authToken=%s\n' "$NPM_TOKEN" > "$TMP_NPMRC"
|
|
50
|
+
NPM_ARGS+=(--userconfig "$TMP_NPMRC")
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
cleanup() {
|
|
54
|
+
if [[ -n "$TMP_NPMRC" ]]; then
|
|
55
|
+
rm -f "$TMP_NPMRC"
|
|
56
|
+
fi
|
|
57
|
+
}
|
|
58
|
+
trap cleanup EXIT
|
|
59
|
+
|
|
60
|
+
if npm "${NPM_ARGS[@]}" view "ticlawk@$VERSION" version >/dev/null 2>&1; then
|
|
61
|
+
echo "ticlawk@$VERSION is already published" >&2
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
if ! npm "${NPM_ARGS[@]}" whoami >/dev/null; then
|
|
66
|
+
echo "npm auth failed; run npm login or set a valid NPM_TOKEN with publish access to ticlawk" >&2
|
|
67
|
+
exit 1
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
npm run verify:public-package
|
|
71
|
+
|
|
72
|
+
PUBLISH_ARGS=(publish --access public --tag dev)
|
|
73
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
74
|
+
PUBLISH_ARGS+=(--dry-run)
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
npm "${NPM_ARGS[@]}" "${PUBLISH_ARGS[@]}"
|
|
@@ -552,34 +552,6 @@ export async function postEvent({ agent, agent_id, runtime_host_id, session_id,
|
|
|
552
552
|
return queued;
|
|
553
553
|
}
|
|
554
554
|
|
|
555
|
-
// ── Pair (no auth needed) ──
|
|
556
|
-
|
|
557
|
-
export async function pair(payload) {
|
|
558
|
-
const url = `${getApiUrl()}/dispatch`;
|
|
559
|
-
const res = await fetch(url, {
|
|
560
|
-
method: 'POST',
|
|
561
|
-
headers: { 'Content-Type': 'application/json' },
|
|
562
|
-
body: JSON.stringify({ action: 'pair', ...payload }),
|
|
563
|
-
signal: AbortSignal.timeout(15000),
|
|
564
|
-
});
|
|
565
|
-
const body = await res.json().catch(() => ({}));
|
|
566
|
-
return {
|
|
567
|
-
statusCode: res.status,
|
|
568
|
-
...body,
|
|
569
|
-
};
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
export async function pairPreview(payload) {
|
|
573
|
-
const url = `${getApiUrl()}/dispatch`;
|
|
574
|
-
const res = await fetch(url, {
|
|
575
|
-
method: 'POST',
|
|
576
|
-
headers: { 'Content-Type': 'application/json' },
|
|
577
|
-
body: JSON.stringify({ action: 'pair-preview', ...payload }),
|
|
578
|
-
signal: AbortSignal.timeout(15000),
|
|
579
|
-
});
|
|
580
|
-
return res.json();
|
|
581
|
-
}
|
|
582
|
-
|
|
583
555
|
async function pairingRequest(payload, timeout = 15000) {
|
|
584
556
|
const url = `${getApiUrl()}/api/agent-pairings`;
|
|
585
557
|
const res = await fetch(url, {
|
|
@@ -13,9 +13,7 @@ export function persistApiCredential(apiKey) {
|
|
|
13
13
|
|
|
14
14
|
persistConfig({
|
|
15
15
|
[TICLAWK_CONNECTOR_API_KEY]: apiKey,
|
|
16
|
-
TICLAWK_SETUP_CODE: '',
|
|
17
16
|
});
|
|
18
17
|
process.env[TICLAWK_CONNECTOR_API_KEY] = apiKey;
|
|
19
|
-
delete process.env.TICLAWK_SETUP_CODE;
|
|
20
18
|
console.log(`[connect] saved ${TICLAWK_CONNECTOR_API_KEY} to ${AF_CONFIG_PATH}`);
|
|
21
19
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import { parseOptionArgs } from '../../core/argv.mjs';
|
|
2
1
|
import { createHash, randomBytes } from 'node:crypto';
|
|
3
2
|
import { createRequire } from 'node:module';
|
|
4
|
-
import { basename } from 'node:path';
|
|
5
3
|
import { loadPersistentConfig, persistConfig, TICLAWK_CONNECTOR_API_KEY, TICLAWK_CONNECTOR_WS_URL } from '../../core/config.mjs';
|
|
6
4
|
import { belongsToRuntimeHost, getBindingRuntimeHostId, getHostId, getHostLabel } from '../../core/host-id.mjs';
|
|
7
5
|
import { debugError, debugLog } from '../../core/logger.mjs';
|
|
8
|
-
import { getActiveProfile,
|
|
6
|
+
import { getActiveProfile, saveAndActivateProfile } from '../../core/profiles.mjs';
|
|
9
7
|
import { isTerminalRuntimeFailure } from '../../core/runtime-support.mjs';
|
|
10
8
|
import { clearUpdateRequiredState, readUpdateState, setUpdateRequiredState } from '../../core/update-state.mjs';
|
|
11
9
|
import { isManagedInstall, startDetachedSelfUpdate } from '../../core/update.mjs';
|
|
@@ -71,9 +69,6 @@ function normalizeInboundMediaAssets(msg) {
|
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
export function normalizeInboundMessage(msg) {
|
|
74
|
-
// Claimed delivery rows carry the recipient agent id; legacy messages
|
|
75
|
-
// (history sync, manual inserts) may still use plain agent_id. Prefer
|
|
76
|
-
// the new field but fall back so the same normalizer works for both.
|
|
77
72
|
const recipientAgentId = msg.recipient_agent_id || '';
|
|
78
73
|
const messageId = msg.id || msg.message_id || null;
|
|
79
74
|
const deliveryId = msg.delivery_id || null;
|
|
@@ -123,16 +118,12 @@ function getRuntimeHostLabelFromPayload(payload) {
|
|
|
123
118
|
|
|
124
119
|
function getAgentIdFromPayload(payload) {
|
|
125
120
|
// claim_pending_deliveries is the canonical source and returns
|
|
126
|
-
// `recipient_agent_id`.
|
|
127
|
-
// `agentId`) were left in by an earlier partial cleanup but they
|
|
128
|
-
// never fire against the current schema — dead branches.
|
|
121
|
+
// `recipient_agent_id`.
|
|
129
122
|
return String(payload?.recipient_agent_id || '').trim();
|
|
130
123
|
}
|
|
131
124
|
|
|
132
125
|
// Whitelist of meta keys runtimes actually consume. Anything else in
|
|
133
|
-
// the source row's meta blob is dropped
|
|
134
|
-
// (like the post-Y2 workdir/cwd/projectDir leftovers) can't sneak
|
|
135
|
-
// back into runtimeMeta and confuse downstream code.
|
|
126
|
+
// the source row's meta blob is dropped before reaching runtimeMeta.
|
|
136
127
|
const RUNTIME_META_KEYS = [
|
|
137
128
|
'sessionId',
|
|
138
129
|
'path',
|
|
@@ -225,24 +216,6 @@ function maskIdentity(identity = {}) {
|
|
|
225
216
|
};
|
|
226
217
|
}
|
|
227
218
|
|
|
228
|
-
function formatIdentityLines(identity = {}) {
|
|
229
|
-
const normalized = maskIdentity(identity);
|
|
230
|
-
return [
|
|
231
|
-
` user: ${normalized.userId || 'unknown'}`,
|
|
232
|
-
normalized.emailMasked ? ` email: ${normalized.emailMasked}` : null,
|
|
233
|
-
normalized.phoneMasked ? ` phone: ${normalized.phoneMasked}` : null,
|
|
234
|
-
].filter(Boolean);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function formatAffectedAgents(agents = []) {
|
|
238
|
-
if (!agents.length) return [' - none found for this host'];
|
|
239
|
-
return agents.map((agent) => {
|
|
240
|
-
const display = agent.display_name || agent.name || agent.id;
|
|
241
|
-
const runtime = agent.service_type || 'unknown';
|
|
242
|
-
return ` - ${display} (${runtime}) ${agent.id}`;
|
|
243
|
-
});
|
|
244
|
-
}
|
|
245
|
-
|
|
246
219
|
function sha256Hex(value) {
|
|
247
220
|
return createHash('sha256').update(String(value)).digest('hex');
|
|
248
221
|
}
|
|
@@ -251,63 +224,6 @@ function makeClientSecret() {
|
|
|
251
224
|
return randomBytes(32).toString('base64url');
|
|
252
225
|
}
|
|
253
226
|
|
|
254
|
-
function getRuntimeWorkdir(runtimeMeta = {}) {
|
|
255
|
-
return String(runtimeMeta.workdir || runtimeMeta.cwd || runtimeMeta.projectDir || '').trim();
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
function runtimeLabel(runtime) {
|
|
259
|
-
if (runtime === 'claude_code') return 'Claude Code';
|
|
260
|
-
if (runtime === 'opencode') return 'OpenCode';
|
|
261
|
-
if (runtime === 'openclaw') return 'OpenClaw';
|
|
262
|
-
if (runtime === 'codex') return 'Codex';
|
|
263
|
-
if (runtime === 'pi') return 'Pi';
|
|
264
|
-
return runtime || 'Agent';
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function printDetectedRuntimeOptions(runtimeOptions = []) {
|
|
268
|
-
console.log('Detected agent harness:');
|
|
269
|
-
if (!runtimeOptions.length) {
|
|
270
|
-
console.log(' none');
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
for (const option of runtimeOptions) {
|
|
274
|
-
const label = option.runtime_label || runtimeLabel(option.runtime);
|
|
275
|
-
const workdir = option.workdir ? ` (${option.workdir})` : '';
|
|
276
|
-
console.log(` - ${label}${workdir}`);
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
async function buildAutoRuntimeOptions(ctx, payload = {}) {
|
|
281
|
-
const workdir = getRuntimeWorkdir(payload) || process.cwd();
|
|
282
|
-
const candidates = ['codex', 'claude_code', 'opencode', 'pi'];
|
|
283
|
-
const options = [];
|
|
284
|
-
for (const serviceType of candidates) {
|
|
285
|
-
try {
|
|
286
|
-
const resolved = await ctx.resolveRuntimeBinding({
|
|
287
|
-
...payload,
|
|
288
|
-
serviceType,
|
|
289
|
-
workdir,
|
|
290
|
-
});
|
|
291
|
-
const runtimeMeta = resolved.runtimeMeta || {};
|
|
292
|
-
const optionWorkdir = getRuntimeWorkdir(runtimeMeta) || workdir;
|
|
293
|
-
options.push({
|
|
294
|
-
runtime: resolved.runtime,
|
|
295
|
-
runtime_label: runtimeLabel(resolved.runtime),
|
|
296
|
-
display_name: resolved.displayName || basename(optionWorkdir) || runtimeLabel(resolved.runtime),
|
|
297
|
-
workdir: optionWorkdir,
|
|
298
|
-
binding_key: optionWorkdir,
|
|
299
|
-
runtime_meta: runtimeMeta,
|
|
300
|
-
});
|
|
301
|
-
} catch (err) {
|
|
302
|
-
debugLog('ticlawk-pairing', 'auto-runtime.skip', {
|
|
303
|
-
runtime: serviceType,
|
|
304
|
-
reason: err?.message || String(err),
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
return options;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
227
|
function printPairingChallenge(session) {
|
|
312
228
|
console.log();
|
|
313
229
|
console.log('Open Ticlawk and scan the QR code below, or enter the pairing code.');
|
|
@@ -372,6 +288,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
372
288
|
let lastBindingsWakeAt = 0;
|
|
373
289
|
let updateRequired = null;
|
|
374
290
|
let lastUpdateRequiredLogAt = 0;
|
|
291
|
+
let startupSyncing = false;
|
|
375
292
|
|
|
376
293
|
function clearDebounce(timer) {
|
|
377
294
|
if (timer) clearTimeout(timer);
|
|
@@ -851,10 +768,11 @@ export function createTiclawkAdapter(ctx) {
|
|
|
851
768
|
function handleWakeEvent(event) {
|
|
852
769
|
wakeState.lastEventAt = new Date().toISOString();
|
|
853
770
|
if (event?.type === 'hello') {
|
|
771
|
+
void reportHostCapabilitiesNow();
|
|
772
|
+
if (startupSyncing) return;
|
|
854
773
|
void refreshBindings('wake.hello')
|
|
855
774
|
.then(() => requestDrain('wake.hello'))
|
|
856
775
|
.catch(() => {});
|
|
857
|
-
void reportHostCapabilitiesNow();
|
|
858
776
|
return;
|
|
859
777
|
}
|
|
860
778
|
if (event?.type === 'jobs.available') {
|
|
@@ -907,9 +825,21 @@ export function createTiclawkAdapter(ctx) {
|
|
|
907
825
|
});
|
|
908
826
|
}
|
|
909
827
|
|
|
828
|
+
function getConnectorWakeUrlForHost() {
|
|
829
|
+
const raw = api.getConnectorWsUrl();
|
|
830
|
+
try {
|
|
831
|
+
const url = new URL(raw);
|
|
832
|
+
url.searchParams.set('runtime_host_id', hostId);
|
|
833
|
+
return url.toString();
|
|
834
|
+
} catch {
|
|
835
|
+
const separator = raw.includes('?') ? '&' : '?';
|
|
836
|
+
return `${raw}${separator}runtime_host_id=${encodeURIComponent(hostId)}`;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
|
|
910
840
|
function connectWakeSocket() {
|
|
911
841
|
connectorSocket = new TiclawkWakeClient({
|
|
912
|
-
getUrl:
|
|
842
|
+
getUrl: getConnectorWakeUrlForHost,
|
|
913
843
|
getApiKey: api.getApiKey,
|
|
914
844
|
onEvent: handleWakeEvent,
|
|
915
845
|
onStatus: handleWakeStatus,
|
|
@@ -937,7 +867,7 @@ export function createTiclawkAdapter(ctx) {
|
|
|
937
867
|
bindingAuditTimer.unref?.();
|
|
938
868
|
}
|
|
939
869
|
|
|
940
|
-
async function
|
|
870
|
+
async function finishHostPairing(pairData) {
|
|
941
871
|
const apiKey = pairData.connector_api_key || pairData.apiKey || pairData.api_key;
|
|
942
872
|
persistApiCredential(apiKey);
|
|
943
873
|
if (pairData.connector_ws_url) {
|
|
@@ -956,61 +886,26 @@ export function createTiclawkAdapter(ctx) {
|
|
|
956
886
|
restartWakeSocket('connect.paired');
|
|
957
887
|
}
|
|
958
888
|
|
|
959
|
-
|
|
960
|
-
if (!bindingId) {
|
|
961
|
-
throw new Error('pairing did not return agent_id');
|
|
962
|
-
}
|
|
963
|
-
const runtimeMeta = pairData.runtime_meta || resolved.runtimeMeta;
|
|
964
|
-
const binding = await ctx.upsertBinding({
|
|
965
|
-
id: bindingId,
|
|
966
|
-
adapter: 'ticlawk',
|
|
967
|
-
targetKey: bindingId,
|
|
968
|
-
targetMeta: {
|
|
969
|
-
agentId: bindingId,
|
|
970
|
-
runtime_host_id: hostId,
|
|
971
|
-
binding_key: pairData.binding_key || null,
|
|
972
|
-
},
|
|
973
|
-
runtime_host_id: hostId,
|
|
974
|
-
runtime_host_label: hostLabel,
|
|
975
|
-
runtime: resolved.runtime,
|
|
976
|
-
runtimeMeta,
|
|
977
|
-
displayName: resolved.displayName,
|
|
978
|
-
status: 'connected',
|
|
979
|
-
});
|
|
889
|
+
await reportHostCapabilitiesNow();
|
|
980
890
|
return {
|
|
981
891
|
statusCode: 200,
|
|
982
892
|
body: {
|
|
983
893
|
ok: true,
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
name: binding.displayName,
|
|
987
|
-
bindingKey: pairData.binding_key || null,
|
|
894
|
+
hostId,
|
|
895
|
+
hostLabel,
|
|
988
896
|
user: pairedIdentity,
|
|
989
897
|
},
|
|
990
898
|
};
|
|
991
899
|
}
|
|
992
900
|
|
|
993
|
-
async function connectWithQrPairing(
|
|
994
|
-
const autoRuntime = Boolean(payload?.autoRuntime);
|
|
995
|
-
const runtimeOptions = autoRuntime ? await buildAutoRuntimeOptions(ctx, payload) : [];
|
|
996
|
-
if (autoRuntime) {
|
|
997
|
-
printDetectedRuntimeOptions(runtimeOptions);
|
|
998
|
-
if (runtimeOptions.length === 0) {
|
|
999
|
-
return connectError(400, 'No supported local agent harness detected in this terminal. Install or sign in to Codex, Claude Code, OpenCode, or pi, then run `ticlawk connect` again.');
|
|
1000
|
-
}
|
|
1001
|
-
}
|
|
1002
|
-
const resolved = autoRuntime ? null : await ctx.resolveRuntimeBinding(payload);
|
|
1003
|
-
const runtimeMeta = resolved?.runtimeMeta || {};
|
|
1004
|
-
const workdir = getRuntimeWorkdir(runtimeMeta) || getRuntimeWorkdir(payload) || process.cwd();
|
|
901
|
+
async function connectWithQrPairing() {
|
|
1005
902
|
const clientSecret = makeClientSecret();
|
|
1006
903
|
const created = await api.createPairingSession({
|
|
1007
904
|
client: 'ticlawk',
|
|
1008
905
|
client_version: api.getTiclawkVersion(),
|
|
1009
906
|
host_id: hostId,
|
|
1010
907
|
host_label: hostLabel,
|
|
1011
|
-
|
|
1012
|
-
workdir,
|
|
1013
|
-
display_name: resolved?.displayName || basename(workdir) || 'Agent',
|
|
908
|
+
scope: 'host',
|
|
1014
909
|
challenge_hash: sha256Hex(clientSecret),
|
|
1015
910
|
});
|
|
1016
911
|
if (!created?.ok) {
|
|
@@ -1031,20 +926,9 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1031
926
|
pollAfterMs: Number(created.poll_after_ms || 1500),
|
|
1032
927
|
expiresAt: created.expires_at,
|
|
1033
928
|
});
|
|
1034
|
-
|
|
1035
|
-
const approvedOption = runtimeOptions.find((option) => option.runtime === approvedRuntime) || null;
|
|
1036
|
-
const finalResolved = resolved || {
|
|
1037
|
-
runtime: approvedRuntime || approvedOption?.runtime,
|
|
1038
|
-
displayName: approved.display_name || approvedOption?.display_name || runtimeLabel(approvedRuntime),
|
|
1039
|
-
runtimeMeta: approved.runtime_meta || approvedOption?.runtime_meta || {},
|
|
1040
|
-
};
|
|
1041
|
-
if (!finalResolved.runtime) {
|
|
1042
|
-
throw new Error('ticlawk pairing did not return a selected runtime');
|
|
1043
|
-
}
|
|
1044
|
-
return await finishPairing.call(this, finalResolved, {
|
|
929
|
+
return await finishHostPairing({
|
|
1045
930
|
...created,
|
|
1046
931
|
...approved,
|
|
1047
|
-
binding_key: approved.binding_key || created.binding_key || null,
|
|
1048
932
|
});
|
|
1049
933
|
} catch (err) {
|
|
1050
934
|
await api.cancelPairingSession({
|
|
@@ -1063,9 +947,14 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1063
947
|
// mid-claim, the row stays `claimed` forever. lease_expires_at
|
|
1064
948
|
// was dropped in X1; no cron has replaced it. Rare in practice;
|
|
1065
949
|
// when it happens the fix is a one-row UPDATE in supabase.
|
|
1066
|
-
|
|
1067
|
-
await requestDrain('startup');
|
|
950
|
+
startupSyncing = true;
|
|
1068
951
|
connectWakeSocket();
|
|
952
|
+
try {
|
|
953
|
+
await refreshBindings('startup');
|
|
954
|
+
await requestDrain('startup');
|
|
955
|
+
} finally {
|
|
956
|
+
startupSyncing = false;
|
|
957
|
+
}
|
|
1069
958
|
startAuditTimers();
|
|
1070
959
|
},
|
|
1071
960
|
|
|
@@ -1102,131 +991,9 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1102
991
|
},
|
|
1103
992
|
|
|
1104
993
|
async connect(payload) {
|
|
1105
|
-
const config = loadPersistentConfig();
|
|
1106
|
-
if (!payload?.__legacyConnect) {
|
|
1107
|
-
try {
|
|
1108
|
-
return await connectWithQrPairing.call(this, {
|
|
1109
|
-
...payload,
|
|
1110
|
-
autoRuntime: true,
|
|
1111
|
-
});
|
|
1112
|
-
} catch (err) {
|
|
1113
|
-
return connectError(err?.status || 500, err.message);
|
|
1114
|
-
}
|
|
1115
|
-
}
|
|
1116
|
-
|
|
1117
|
-
// Archived setup-code connect path. It is intentionally not reachable
|
|
1118
|
-
// from the public CLI; keep it here only until the old flow is deleted.
|
|
1119
|
-
const connectCode = String(payload?.code || config.TICLAWK_SETUP_CODE || '').trim();
|
|
1120
|
-
if (!connectCode) {
|
|
1121
|
-
return connectError(400, 'legacy ticlawk setup-code connect requires code');
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
994
|
try {
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
let currentIdentity = null;
|
|
1128
|
-
const activeProfile = getActiveProfile();
|
|
1129
|
-
if (activeProfile?.adapter === 'ticlawk') {
|
|
1130
|
-
currentIdentity = readProfileMeta(activeProfile.adapter, activeProfile.userId) || {
|
|
1131
|
-
userId: activeProfile.userId,
|
|
1132
|
-
};
|
|
1133
|
-
} else if (config[TICLAWK_CONNECTOR_API_KEY]) {
|
|
1134
|
-
try {
|
|
1135
|
-
const me = await api.getMe();
|
|
1136
|
-
if (me?.userId || me?.user_id) {
|
|
1137
|
-
currentIdentity = maskIdentity(me);
|
|
1138
|
-
ensureLegacyProfile({
|
|
1139
|
-
adapter: 'ticlawk',
|
|
1140
|
-
userId: currentIdentity.userId,
|
|
1141
|
-
meta: currentIdentity,
|
|
1142
|
-
});
|
|
1143
|
-
}
|
|
1144
|
-
} catch {}
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
let previewIdentity = null;
|
|
1148
|
-
try {
|
|
1149
|
-
const preview = await api.pairPreview({ code: connectCode });
|
|
1150
|
-
if (preview?.ok) {
|
|
1151
|
-
previewIdentity = maskIdentity(preview);
|
|
1152
|
-
} else if (preview?.userId || preview?.user_id) {
|
|
1153
|
-
previewIdentity = maskIdentity(preview);
|
|
1154
|
-
}
|
|
1155
|
-
} catch {}
|
|
1156
|
-
|
|
1157
|
-
if (currentIdentity?.userId && !previewIdentity?.userId && !payload.switchUser) {
|
|
1158
|
-
return {
|
|
1159
|
-
statusCode: 409,
|
|
1160
|
-
body: {
|
|
1161
|
-
ok: false,
|
|
1162
|
-
code: 'ticlawk_pairing_user_unverified',
|
|
1163
|
-
currentUser: currentIdentity,
|
|
1164
|
-
error: [
|
|
1165
|
-
'This ticlawk home is already paired to ticlawk user:',
|
|
1166
|
-
...formatIdentityLines(currentIdentity),
|
|
1167
|
-
'',
|
|
1168
|
-
'Could not verify which ticlawk user owns the new pairing code.',
|
|
1169
|
-
'Refusing to switch users because existing agents may stop processing messages.',
|
|
1170
|
-
'',
|
|
1171
|
-
'If you want to switch user, please rerun this command with --switch-user.',
|
|
1172
|
-
].join('\n'),
|
|
1173
|
-
},
|
|
1174
|
-
};
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
if (
|
|
1178
|
-
currentIdentity?.userId
|
|
1179
|
-
&& previewIdentity?.userId
|
|
1180
|
-
&& currentIdentity.userId !== previewIdentity.userId
|
|
1181
|
-
&& !payload.switchUser
|
|
1182
|
-
) {
|
|
1183
|
-
let affectedAgents = [];
|
|
1184
|
-
try {
|
|
1185
|
-
affectedAgents = await api.getAgents({ hostId });
|
|
1186
|
-
} catch {}
|
|
1187
|
-
const message = [
|
|
1188
|
-
'This ticlawk home is already paired to ticlawk user:',
|
|
1189
|
-
...formatIdentityLines(currentIdentity),
|
|
1190
|
-
'',
|
|
1191
|
-
'The pairing code belongs to a different ticlawk user:',
|
|
1192
|
-
...formatIdentityLines(previewIdentity),
|
|
1193
|
-
'',
|
|
1194
|
-
'Refusing to switch users because these agents are currently bound to this host:',
|
|
1195
|
-
...formatAffectedAgents(affectedAgents),
|
|
1196
|
-
'',
|
|
1197
|
-
'Switching would stop this daemon from processing messages for those agents.',
|
|
1198
|
-
'',
|
|
1199
|
-
'If you want to switch user, please rerun this command with --switch-user.',
|
|
1200
|
-
].join('\n');
|
|
1201
|
-
return {
|
|
1202
|
-
statusCode: 409,
|
|
1203
|
-
body: {
|
|
1204
|
-
ok: false,
|
|
1205
|
-
error: message,
|
|
1206
|
-
code: 'ticlawk_user_mismatch',
|
|
1207
|
-
currentUser: currentIdentity,
|
|
1208
|
-
pairingUser: previewIdentity,
|
|
1209
|
-
affectedAgents,
|
|
1210
|
-
},
|
|
1211
|
-
};
|
|
1212
|
-
}
|
|
1213
|
-
|
|
1214
|
-
const pairData = await api.pair({
|
|
1215
|
-
code: connectCode,
|
|
1216
|
-
name: resolved.displayName,
|
|
1217
|
-
serviceType: resolved.runtime,
|
|
1218
|
-
runtimeMeta,
|
|
1219
|
-
runtime_host_id: hostId,
|
|
1220
|
-
runtime_host_label: hostLabel,
|
|
1221
|
-
});
|
|
1222
|
-
if (!pairData?.ok) {
|
|
1223
|
-
return { statusCode: pairData?.statusCode || 401, body: pairData };
|
|
1224
|
-
}
|
|
1225
|
-
return finishPairing.call(this, resolved, {
|
|
1226
|
-
...pairData,
|
|
1227
|
-
agent_id: pairData.agentId,
|
|
1228
|
-
connector_api_key: pairData.apiKey,
|
|
1229
|
-
});
|
|
995
|
+
void payload;
|
|
996
|
+
return await connectWithQrPairing();
|
|
1230
997
|
} catch (err) {
|
|
1231
998
|
return connectError(err?.status || 500, err.message);
|
|
1232
999
|
}
|
|
@@ -1290,52 +1057,3 @@ export function createTiclawkAdapter(ctx) {
|
|
|
1290
1057
|
},
|
|
1291
1058
|
};
|
|
1292
1059
|
}
|
|
1293
|
-
|
|
1294
|
-
export function getTiclawkAuthHelp() {
|
|
1295
|
-
return `ticlawk auth ticlawk --code <6-digit-code> [--api-url <url>]
|
|
1296
|
-
|
|
1297
|
-
Options:
|
|
1298
|
-
--code <code> 6-digit setup code from the ticlawk app
|
|
1299
|
-
--api-url <url> optional ticlawk API base URL override
|
|
1300
|
-
`;
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
export async function runTiclawkAuth(rawArgs) {
|
|
1304
|
-
const args = parseOptionArgs(rawArgs);
|
|
1305
|
-
if (args.help || args.h) {
|
|
1306
|
-
return {
|
|
1307
|
-
statusCode: 200,
|
|
1308
|
-
body: {
|
|
1309
|
-
ok: true,
|
|
1310
|
-
help: getTiclawkAuthHelp(),
|
|
1311
|
-
},
|
|
1312
|
-
};
|
|
1313
|
-
}
|
|
1314
|
-
const code = String(args.code || '').trim();
|
|
1315
|
-
if (!code) {
|
|
1316
|
-
return {
|
|
1317
|
-
statusCode: 400,
|
|
1318
|
-
body: {
|
|
1319
|
-
ok: false,
|
|
1320
|
-
error: 'ticlawk auth requires --code',
|
|
1321
|
-
},
|
|
1322
|
-
};
|
|
1323
|
-
}
|
|
1324
|
-
const updates = {
|
|
1325
|
-
TICLAWK_SETUP_CODE: code,
|
|
1326
|
-
};
|
|
1327
|
-
const apiUrl = String(args['api-url'] || '').trim();
|
|
1328
|
-
if (apiUrl) {
|
|
1329
|
-
updates.TICLAWK_API_URL = apiUrl;
|
|
1330
|
-
}
|
|
1331
|
-
persistConfig(updates);
|
|
1332
|
-
return {
|
|
1333
|
-
statusCode: 200,
|
|
1334
|
-
body: {
|
|
1335
|
-
ok: true,
|
|
1336
|
-
adapter: 'ticlawk',
|
|
1337
|
-
setupCode: 'set',
|
|
1338
|
-
apiUrl: apiUrl || undefined,
|
|
1339
|
-
},
|
|
1340
|
-
};
|
|
1341
|
-
}
|
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
createTiclawkAdapter,
|
|
3
|
-
getTiclawkAuthHelp,
|
|
4
|
-
runTiclawkAuth,
|
|
5
|
-
} from '../adapters/ticlawk/index.mjs';
|
|
1
|
+
import { createTiclawkAdapter } from '../adapters/ticlawk/index.mjs';
|
|
6
2
|
|
|
7
3
|
/**
|
|
8
4
|
* Ticlawk has a single adapter: ticlawk itself. The registry shape is kept
|
|
@@ -18,17 +14,3 @@ export function createAdapter(adapterId, ctx) {
|
|
|
18
14
|
}
|
|
19
15
|
return createTiclawkAdapter(ctx);
|
|
20
16
|
}
|
|
21
|
-
|
|
22
|
-
export function getAdapterAuthHelp(adapterId) {
|
|
23
|
-
if (adapterId !== ADAPTER_ID) {
|
|
24
|
-
throw new Error(`unsupported adapter: ${adapterId}`);
|
|
25
|
-
}
|
|
26
|
-
return getTiclawkAuthHelp();
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export async function runAdapterAuth(adapterId, rawArgs) {
|
|
30
|
-
if (adapterId !== ADAPTER_ID) {
|
|
31
|
-
throw new Error(`unsupported adapter: ${adapterId}`);
|
|
32
|
-
}
|
|
33
|
-
return runTiclawkAuth(rawArgs);
|
|
34
|
-
}
|
package/src/core/profiles.mjs
CHANGED
|
@@ -92,7 +92,6 @@ export function saveProfile({ adapter, userId, config = {}, meta = {} }) {
|
|
|
92
92
|
mkdirSync(dir, { recursive: true });
|
|
93
93
|
const currentConfig = readProfileConfig(adapter, userId);
|
|
94
94
|
const nextConfig = { ...currentConfig, ...config };
|
|
95
|
-
delete nextConfig.TICLAWK_SETUP_CODE;
|
|
96
95
|
writeDotenvAtomic(getProfileConfigPath(adapter, userId), nextConfig);
|
|
97
96
|
|
|
98
97
|
const currentMeta = readProfileMeta(adapter, userId) || {};
|
package/src/core/runtime-env.mjs
CHANGED
package/ticlawk.mjs
CHANGED
|
@@ -266,20 +266,17 @@ export async function startTiclawk() {
|
|
|
266
266
|
baseRuntimeCtx = createBaseRuntimeCtx(runtimes, persistBinding, (binding) => syncBinding(binding));
|
|
267
267
|
|
|
268
268
|
printBanner(adapter);
|
|
269
|
-
if (typeof adapter.refreshBindings === 'function') {
|
|
270
|
-
await adapter.refreshBindings();
|
|
271
|
-
}
|
|
272
269
|
registerRuntimeHandlers(runtimeList, baseRuntimeCtx, adapter);
|
|
273
|
-
await replayBindings(runtimes, adapter);
|
|
274
270
|
startLocalHttpServer({
|
|
275
271
|
port: HTTP_PORT,
|
|
276
272
|
adapter,
|
|
277
273
|
ctx: { listBindings, getBinding },
|
|
278
274
|
});
|
|
279
275
|
startReminderTicker();
|
|
276
|
+
await adapter.start();
|
|
277
|
+
await replayBindings(runtimes, adapter);
|
|
280
278
|
await recoverAllRuntimes(runtimeList, adapter);
|
|
281
279
|
await reconcileBindingsAfterRestart(runtimes, adapter);
|
|
282
|
-
await adapter.start();
|
|
283
280
|
}
|
|
284
281
|
|
|
285
282
|
if (import.meta.url === `file://${process.argv[1]}`) {
|