@wspc/cli 0.1.3 → 0.1.5
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/dist/cli.js +246 -180
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/spec/openapi.json +21 -3
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command74 } from "commander";
|
|
5
5
|
import { realpathSync } from "fs";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
7
|
|
|
@@ -1178,6 +1178,11 @@ var todoCommentUpdate = (options) => (options.client ?? client).patch({
|
|
|
1178
1178
|
...options.headers
|
|
1179
1179
|
}
|
|
1180
1180
|
});
|
|
1181
|
+
var projectDelete = (options) => (options.client ?? client).delete({
|
|
1182
|
+
security: [{ scheme: "bearer", type: "http" }],
|
|
1183
|
+
url: "/todo/projects/{id}",
|
|
1184
|
+
...options
|
|
1185
|
+
});
|
|
1181
1186
|
var recurrenceRuleDelete = (options) => (options.client ?? client).delete({
|
|
1182
1187
|
security: [{ scheme: "bearer", type: "http" }],
|
|
1183
1188
|
url: "/todo/recurrence-rules/{id}",
|
|
@@ -1464,9 +1469,9 @@ function createConsistencyFetch(opts) {
|
|
|
1464
1469
|
}
|
|
1465
1470
|
|
|
1466
1471
|
// src/version.ts
|
|
1467
|
-
var VERSION = "0.1.
|
|
1468
|
-
var SPEC_SHA = "
|
|
1469
|
-
var SPEC_FETCHED_AT = "2026-06-
|
|
1472
|
+
var VERSION = "0.1.5";
|
|
1473
|
+
var SPEC_SHA = "706a2577";
|
|
1474
|
+
var SPEC_FETCHED_AT = "2026-06-22T07:51:02.058Z";
|
|
1470
1475
|
var API_BASE = "https://api.wspc.ai";
|
|
1471
1476
|
|
|
1472
1477
|
// src/index.ts
|
|
@@ -3294,7 +3299,7 @@ var pushTestCommand = new Command45("test").description("Send a test push notifi
|
|
|
3294
3299
|
}
|
|
3295
3300
|
render({ kind: "push_test", display: { "shape": "object", "fields": ["ok", "status", "detail", "durationMs"], "format": { "ok": "bool-badge" } } }, result.data);
|
|
3296
3301
|
if (result.data?.ok === false) {
|
|
3297
|
-
process.
|
|
3302
|
+
process.exitCode = 1;
|
|
3298
3303
|
}
|
|
3299
3304
|
});
|
|
3300
3305
|
|
|
@@ -3586,9 +3591,30 @@ var todoCommentUpdateCommand = new Command56("edit").description("Edit a comment
|
|
|
3586
3591
|
render({ kind: "todo_comment_update", display: { "shape": "object", "format": { "id": "id-short", "todo_id": "id-short", "user_id": "id-short", "created_at": "relative-time", "updated_at": "relative-time", "deleted_at": "relative-time" } } }, result.data);
|
|
3587
3592
|
});
|
|
3588
3593
|
|
|
3589
|
-
// src/generated/cli/todo/
|
|
3594
|
+
// src/generated/cli/todo/project/rm.ts
|
|
3590
3595
|
import { Command as Command57 } from "commander";
|
|
3591
|
-
var
|
|
3596
|
+
var projectDeleteCommand = new Command57("rm").description("Soft-delete a project").argument("<id>", "id").action(async (id, opts) => {
|
|
3597
|
+
const client2 = await loadSdkClient();
|
|
3598
|
+
const result = await projectDelete({
|
|
3599
|
+
client: client2._rawClient,
|
|
3600
|
+
path: {
|
|
3601
|
+
id
|
|
3602
|
+
}
|
|
3603
|
+
});
|
|
3604
|
+
if (result.error || !result.response?.ok) {
|
|
3605
|
+
process.stderr.write(
|
|
3606
|
+
`HTTP ${result.response?.status ?? "?"}: ${JSON.stringify(result.error ?? "unknown error", null, 2)}
|
|
3607
|
+
`
|
|
3608
|
+
);
|
|
3609
|
+
process.exitCode = 1;
|
|
3610
|
+
return;
|
|
3611
|
+
}
|
|
3612
|
+
render({ kind: "project_delete", display: { "shape": "object", "format": { "id": "id-short", "org_id": "id-short", "creator_user_id": "id-short", "default_todo_type_id": "id-short", "name": "truncate", "created_at": "relative-time", "updated_at": "relative-time", "deleted_at": "relative-time" } } }, result.data);
|
|
3613
|
+
});
|
|
3614
|
+
|
|
3615
|
+
// src/generated/cli/todo/rule/rm.ts
|
|
3616
|
+
import { Command as Command58 } from "commander";
|
|
3617
|
+
var recurrenceRuleDeleteCommand = new Command58("rm").description("Delete a recurring todo rule").argument("<id>", "id").option("--expected-version <value>", "expected_version").action(async (id, opts) => {
|
|
3592
3618
|
const client2 = await loadSdkClient();
|
|
3593
3619
|
const result = await recurrenceRuleDelete({
|
|
3594
3620
|
client: client2._rawClient,
|
|
@@ -3611,8 +3637,8 @@ var recurrenceRuleDeleteCommand = new Command57("rm").description("Delete a recu
|
|
|
3611
3637
|
});
|
|
3612
3638
|
|
|
3613
3639
|
// src/generated/cli/todo/rule/show.ts
|
|
3614
|
-
import { Command as
|
|
3615
|
-
var recurrenceRuleGetCommand = new
|
|
3640
|
+
import { Command as Command59 } from "commander";
|
|
3641
|
+
var recurrenceRuleGetCommand = new Command59("show").description("Get a recurring todo rule").argument("<id>", "id").action(async (id, opts) => {
|
|
3616
3642
|
const client2 = await loadSdkClient();
|
|
3617
3643
|
const result = await recurrenceRuleGet({
|
|
3618
3644
|
client: client2._rawClient,
|
|
@@ -3632,8 +3658,8 @@ var recurrenceRuleGetCommand = new Command58("show").description("Get a recurrin
|
|
|
3632
3658
|
});
|
|
3633
3659
|
|
|
3634
3660
|
// src/generated/cli/todo/rm.ts
|
|
3635
|
-
import { Command as
|
|
3636
|
-
var todoDeleteCommand = new
|
|
3661
|
+
import { Command as Command60 } from "commander";
|
|
3662
|
+
var todoDeleteCommand = new Command60("rm").description("Soft-delete a todo").argument("<id>", "id").option("--expected-version <value>", "expected_version").option("--cascade <value>", "cascade").action(async (id, opts) => {
|
|
3637
3663
|
const client2 = await loadSdkClient();
|
|
3638
3664
|
const result = await todoDelete({
|
|
3639
3665
|
client: client2._rawClient,
|
|
@@ -3657,8 +3683,8 @@ var todoDeleteCommand = new Command59("rm").description("Soft-delete a todo").ar
|
|
|
3657
3683
|
});
|
|
3658
3684
|
|
|
3659
3685
|
// src/generated/cli/todo/show.ts
|
|
3660
|
-
import { Command as
|
|
3661
|
-
var todoGetCommand = new
|
|
3686
|
+
import { Command as Command61 } from "commander";
|
|
3687
|
+
var todoGetCommand = new Command61("show").description("Get a todo by id").argument("<id>", "id").option("--include-deleted <value>", "include_deleted").option("--include-orphan-fields <value>", "include_orphan_fields").action(async (id, opts) => {
|
|
3662
3688
|
const client2 = await loadSdkClient();
|
|
3663
3689
|
const result = await todoGet({
|
|
3664
3690
|
client: client2._rawClient,
|
|
@@ -3683,8 +3709,8 @@ var todoGetCommand = new Command60("show").description("Get a todo by id").argum
|
|
|
3683
3709
|
});
|
|
3684
3710
|
|
|
3685
3711
|
// src/generated/cli/todo/update.ts
|
|
3686
|
-
import { Command as
|
|
3687
|
-
var todoUpdateCommand = new
|
|
3712
|
+
import { Command as Command62 } from "commander";
|
|
3713
|
+
var todoUpdateCommand = new Command62("update").description("Update a todo").argument("<id>", "id").option("--expected-version <value>", "expected_version").option("--title <value>", "New title. Omit to leave the existing title unchanged. Must be non-empty when supplied.").option("--description <value>", 'New description. Markdown formatted (CommonMark + GFM tables, strikethrough, task lists). Pass empty string `""` explicitly to clear an existing description, or omit to leave unchanged. Passing `null` is strictly rejected.').option("--parent-id <value>", "Re-parent the todo. Pass a valid parent ID to attach under another todo, pass `null` to move it back to the root level, or omit to leave unchanged. Nesting is limited to one level; attempting to set a child todo as a parent will trigger `PARENT_IS_CHILD`.").option("--status <value>", "New status of the todo. Allowed transitions: `open` \u2794 `in_progress` \u2794 `done`. `cancelled` represents a terminal state. Transitioning to `done` automatically emits a `captureTodoCompleted` analytics event. Omit to leave the existing status unchanged.").option("--due-at <value>", 'Update calendar due date in ISO date-only format (`YYYY-MM-DD`). Pass `""` explicitly to clear an existing due date, or omit to leave it unchanged. Passing `null` is strictly rejected.').option("--type-id <value>", "Re-assign this todo to a different active type. The new type must belong to the todo's same project; otherwise the request fails with TYPE_PROJECT_MISMATCH. New server-generated type ids use typ_<ULID>; legacy ids remain accepted.").option("--custom-fields <value>", "PATCH semantics: only the keys present in this map change. Pass `null` for a key (e.g. `custom_fields: { priority: null }`) to explicitly delete that custom field value. Array values are replaced wholesale with no element-level diff. Providing a key that is not declared on the effective todo type is rejected with `UNDECLARED_FIELD`.").option("--user-id <value>", "Reassign the owner (assignee) user ID of this todo. Target user must belong to the same organization.").action(async (id, opts) => {
|
|
3688
3714
|
const client2 = await loadSdkClient();
|
|
3689
3715
|
const result = await todoUpdate({
|
|
3690
3716
|
client: client2._rawClient,
|
|
@@ -3785,6 +3811,7 @@ function registerGeneratedCommands(root) {
|
|
|
3785
3811
|
const root_todo_project = root_todo.command("project").description("project commands");
|
|
3786
3812
|
root_todo_project.addCommand(projectCreateCommand);
|
|
3787
3813
|
root_todo_project.addCommand(projectListCommand);
|
|
3814
|
+
root_todo_project.addCommand(projectDeleteCommand);
|
|
3788
3815
|
const root_todo_rule = root_todo.command("rule").description("rule commands");
|
|
3789
3816
|
root_todo_rule.addCommand(recurrenceRuleCreateCommand);
|
|
3790
3817
|
root_todo_rule.addCommand(recurrenceRuleListCommand);
|
|
@@ -3800,7 +3827,7 @@ function registerGeneratedCommands(root) {
|
|
|
3800
3827
|
}
|
|
3801
3828
|
|
|
3802
3829
|
// src/handwritten/commands/login.ts
|
|
3803
|
-
import { Command as
|
|
3830
|
+
import { Command as Command63 } from "commander";
|
|
3804
3831
|
|
|
3805
3832
|
// src/handwritten/auth/device-flow.ts
|
|
3806
3833
|
var DEFAULT_SLEEP = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
@@ -3872,13 +3899,15 @@ async function ensureClientId(opts) {
|
|
|
3872
3899
|
apiBase: opts.baseUrl,
|
|
3873
3900
|
fetchImpl: opts.fetchImpl
|
|
3874
3901
|
});
|
|
3875
|
-
|
|
3876
|
-
|
|
3902
|
+
let existing;
|
|
3903
|
+
await opts.store.update((c) => {
|
|
3904
|
+
existing = c.envs[opts.envName]?.client_id;
|
|
3905
|
+
if (existing) return;
|
|
3906
|
+
const targetEnv = c.envs[opts.envName] ??= { api_base: opts.baseUrl, accounts: {} };
|
|
3907
|
+
targetEnv.api_base = opts.baseUrl;
|
|
3908
|
+
targetEnv.accounts ??= {};
|
|
3909
|
+
});
|
|
3877
3910
|
if (existing) return existing;
|
|
3878
|
-
const targetEnv = c.envs[opts.envName] ??= { api_base: opts.baseUrl, accounts: {} };
|
|
3879
|
-
targetEnv.api_base = opts.baseUrl;
|
|
3880
|
-
targetEnv.accounts ??= {};
|
|
3881
|
-
await opts.store.write(c);
|
|
3882
3911
|
const res = await fetchImpl(`${opts.baseUrl}/auth/oauth/register`, {
|
|
3883
3912
|
method: "POST",
|
|
3884
3913
|
headers: { "content-type": "application/json" },
|
|
@@ -3894,12 +3923,18 @@ async function ensureClientId(opts) {
|
|
|
3894
3923
|
}
|
|
3895
3924
|
const body = await res.json();
|
|
3896
3925
|
if (!body.client_id) throw new Error("client_registration_failed: missing client_id in response");
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3926
|
+
let clientId = body.client_id;
|
|
3927
|
+
await opts.store.update((c) => {
|
|
3928
|
+
const env = c.envs[opts.envName] ??= { api_base: opts.baseUrl, accounts: {} };
|
|
3929
|
+
env.api_base = opts.baseUrl;
|
|
3930
|
+
env.accounts ??= {};
|
|
3931
|
+
if (env.client_id) {
|
|
3932
|
+
clientId = env.client_id;
|
|
3933
|
+
return;
|
|
3934
|
+
}
|
|
3935
|
+
env.client_id = body.client_id;
|
|
3936
|
+
});
|
|
3937
|
+
return clientId;
|
|
3903
3938
|
}
|
|
3904
3939
|
|
|
3905
3940
|
// src/handwritten/auth/fetch-me.ts
|
|
@@ -3956,36 +3991,36 @@ async function runLogin(opts) {
|
|
|
3956
3991
|
const now = opts.now ?? Date.now;
|
|
3957
3992
|
const me = opts.fetchMe ?? ((o) => fetchMe(o));
|
|
3958
3993
|
if (opts.apiKey) {
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3994
|
+
await opts.store.update((initial) => {
|
|
3995
|
+
getOrCreateEnv(initial, envName, opts.baseUrl);
|
|
3996
|
+
initial.current_env = envName;
|
|
3997
|
+
});
|
|
3963
3998
|
const who2 = await me({
|
|
3964
3999
|
baseUrl: opts.baseUrl,
|
|
3965
4000
|
token: opts.apiKey,
|
|
3966
4001
|
store: opts.store,
|
|
3967
4002
|
envName
|
|
3968
4003
|
});
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
4004
|
+
await opts.store.update((c) => {
|
|
4005
|
+
const env = getOrCreateEnv(c, envName, opts.baseUrl);
|
|
4006
|
+
const prev = env.accounts[who2.email] ?? { email: who2.email };
|
|
4007
|
+
const acct = env.accounts[who2.email] = {
|
|
4008
|
+
...prev,
|
|
4009
|
+
email: who2.email,
|
|
4010
|
+
user_id: who2.user_id,
|
|
4011
|
+
api_key: opts.apiKey
|
|
4012
|
+
};
|
|
4013
|
+
delete acct.refresh_token;
|
|
4014
|
+
delete acct.access_token;
|
|
4015
|
+
delete acct.access_token_expires_at;
|
|
4016
|
+
env.current_account = who2.email;
|
|
4017
|
+
if (who2.email !== LEGACY_ACCOUNT_KEY) delete env.accounts[LEGACY_ACCOUNT_KEY];
|
|
4018
|
+
c.current_env = envName;
|
|
4019
|
+
});
|
|
3985
4020
|
opts.output.write(`${green("\u2713")} logged in (api key) as ${bold(who2.email)} ${dim(`\u2192 env "${envName}"`)}`);
|
|
3986
4021
|
return;
|
|
3987
4022
|
}
|
|
3988
|
-
const ensureClient = opts.ensureClient ?? ((
|
|
4023
|
+
const ensureClient = opts.ensureClient ?? ((env) => ensureClientId({ store: opts.store, envName: env, baseUrl: opts.baseUrl }));
|
|
3989
4024
|
const clientId = opts.clientId ?? await ensureClient(envName);
|
|
3990
4025
|
const flow = opts.deviceFlow ?? runDeviceFlow;
|
|
3991
4026
|
const result = await flow({
|
|
@@ -4005,22 +4040,22 @@ async function runLogin(opts) {
|
|
|
4005
4040
|
store: opts.store,
|
|
4006
4041
|
envName
|
|
4007
4042
|
});
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4043
|
+
await opts.store.update((c) => {
|
|
4044
|
+
const env = getOrCreateEnv(c, envName, opts.baseUrl);
|
|
4045
|
+
const prev = env.accounts[who.email] ?? { email: who.email };
|
|
4046
|
+
const acct = env.accounts[who.email] = {
|
|
4047
|
+
...prev,
|
|
4048
|
+
email: who.email,
|
|
4049
|
+
user_id: who.user_id,
|
|
4050
|
+
refresh_token: result.refresh_token,
|
|
4051
|
+
access_token: result.access_token,
|
|
4052
|
+
access_token_expires_at: now() + result.expires_in * 1e3
|
|
4053
|
+
};
|
|
4054
|
+
delete acct.api_key;
|
|
4055
|
+
env.current_account = who.email;
|
|
4056
|
+
if (who.email !== LEGACY_ACCOUNT_KEY) delete env.accounts[LEGACY_ACCOUNT_KEY];
|
|
4057
|
+
c.current_env = envName;
|
|
4058
|
+
});
|
|
4024
4059
|
opts.output.writeJson({ event: "login_success", email: who.email });
|
|
4025
4060
|
opts.output.write(`${green("\u2713")} logged in as ${bold(who.email)} ${dim(`\u2192 env "${envName}"`)}`);
|
|
4026
4061
|
}
|
|
@@ -4034,7 +4069,7 @@ function resolveLoginTarget(opts, env) {
|
|
|
4034
4069
|
function wantsJson(opts, env) {
|
|
4035
4070
|
return opts.json === true || env.WSPC_OUTPUT === "json";
|
|
4036
4071
|
}
|
|
4037
|
-
var loginCommand = new
|
|
4072
|
+
var loginCommand = new Command63("login").description("Log in via OAuth device flow (default) or API key").option("--api-key <key>", "Log in with a wspc API key (escape hatch)").option("--api-base <url>", "Target API base URL (default: production)").option("--env <name>", "Config env name to store credentials under").option("--json", "Emit machine-readable events to stdout").action(async (opts) => {
|
|
4038
4073
|
const store = new ConfigStore();
|
|
4039
4074
|
const { baseUrl, envName } = resolveLoginTarget(opts, process.env);
|
|
4040
4075
|
const output = wantsJson(opts, process.env) ? { write: () => {
|
|
@@ -4053,35 +4088,40 @@ var loginCommand = new Command62("login").description("Log in via OAuth device f
|
|
|
4053
4088
|
});
|
|
4054
4089
|
|
|
4055
4090
|
// src/handwritten/commands/logout.ts
|
|
4056
|
-
import { Command as
|
|
4091
|
+
import { Command as Command64 } from "commander";
|
|
4057
4092
|
|
|
4058
4093
|
// src/handwritten/auth/logout.ts
|
|
4059
4094
|
async function runLogout(opts) {
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
const
|
|
4067
|
-
env.accounts
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4095
|
+
let removed = [];
|
|
4096
|
+
let newActive;
|
|
4097
|
+
let removedSingleAccount = false;
|
|
4098
|
+
await opts.store.update((c) => {
|
|
4099
|
+
const envName = opts.envName ?? c.current_env;
|
|
4100
|
+
if (!envName || !c.envs[envName]) return;
|
|
4101
|
+
const env = c.envs[envName];
|
|
4102
|
+
env.accounts ??= {};
|
|
4103
|
+
if (opts.all) {
|
|
4104
|
+
removed = Object.keys(env.accounts);
|
|
4105
|
+
env.accounts = {};
|
|
4106
|
+
env.current_account = void 0;
|
|
4107
|
+
return;
|
|
4108
|
+
}
|
|
4109
|
+
const target = opts.email ?? env.current_account ?? (Object.keys(env.accounts).length === 1 ? Object.keys(env.accounts)[0] : void 0);
|
|
4110
|
+
if (!target || !env.accounts[target]) return;
|
|
4111
|
+
delete env.accounts[target];
|
|
4112
|
+
removed = [target];
|
|
4113
|
+
removedSingleAccount = true;
|
|
4114
|
+
if (env.current_account === target) {
|
|
4115
|
+
const remaining = Object.keys(env.accounts);
|
|
4116
|
+
env.current_account = remaining.length === 1 ? remaining[0] : void 0;
|
|
4117
|
+
}
|
|
4118
|
+
newActive = env.current_account;
|
|
4119
|
+
});
|
|
4120
|
+
return removedSingleAccount ? { removed, newActive } : { removed };
|
|
4081
4121
|
}
|
|
4082
4122
|
|
|
4083
4123
|
// src/handwritten/commands/logout.ts
|
|
4084
|
-
var logoutCommand = new
|
|
4124
|
+
var logoutCommand = new Command64("logout").description("Log out an account (default: the active account in the current env)").argument("[email]", "Email of the account to log out").option("--all", "Log out every account in the current env").action(async (email, opts) => {
|
|
4085
4125
|
const res = await runLogout({ store: new ConfigStore(), email, all: opts.all });
|
|
4086
4126
|
if (res.removed.length === 0) {
|
|
4087
4127
|
process.stdout.write("nothing to log out\n");
|
|
@@ -4094,7 +4134,7 @@ var logoutCommand = new Command63("logout").description("Log out an account (def
|
|
|
4094
4134
|
});
|
|
4095
4135
|
|
|
4096
4136
|
// src/handwritten/commands/whoami.ts
|
|
4097
|
-
import { Command as
|
|
4137
|
+
import { Command as Command65 } from "commander";
|
|
4098
4138
|
var ENV_DISPLAY = {
|
|
4099
4139
|
shape: "object",
|
|
4100
4140
|
fields: ["name", "api_base", "account", "actor", "agent_label"]
|
|
@@ -4121,12 +4161,11 @@ registerRenderer("whoami", (data) => {
|
|
|
4121
4161
|
}
|
|
4122
4162
|
});
|
|
4123
4163
|
async function backfillActiveEmail(store, envName, email, userId) {
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
}
|
|
4164
|
+
await store.update((cfg) => {
|
|
4165
|
+
rekeyLegacyAccount(cfg, envName, email, userId);
|
|
4166
|
+
});
|
|
4128
4167
|
}
|
|
4129
|
-
var whoamiCommand = new
|
|
4168
|
+
var whoamiCommand = new Command65("whoami").description("Show the active env, signed-in account, and organization").action(async () => {
|
|
4130
4169
|
const store = new ConfigStore();
|
|
4131
4170
|
const config = await store.read();
|
|
4132
4171
|
let resolved;
|
|
@@ -4179,8 +4218,8 @@ function printLoggedOut() {
|
|
|
4179
4218
|
}
|
|
4180
4219
|
|
|
4181
4220
|
// src/handwritten/commands/config.ts
|
|
4182
|
-
import { Command as
|
|
4183
|
-
var configCommand = new
|
|
4221
|
+
import { Command as Command66 } from "commander";
|
|
4222
|
+
var configCommand = new Command66("config").description("Manage wspc local config");
|
|
4184
4223
|
registerRenderer("config_show", (data) => {
|
|
4185
4224
|
const d = data;
|
|
4186
4225
|
if (d.envs.length === 0) {
|
|
@@ -4199,24 +4238,24 @@ registerRenderer("config_show", (data) => {
|
|
|
4199
4238
|
process.stdout.write(table(headers, rows));
|
|
4200
4239
|
});
|
|
4201
4240
|
async function setConfigKey(store, key, value) {
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4241
|
+
await store.update((c) => {
|
|
4242
|
+
const resolved = resolveAccount(c, { accountOverride: process.env.WSPC_ACCOUNT });
|
|
4243
|
+
const env = c.envs[resolved.envName];
|
|
4244
|
+
if (!env) throw new Error(`env "${resolved.envName}" not found`);
|
|
4245
|
+
const acct = env.accounts[resolved.email];
|
|
4246
|
+
if (!acct) throw new Error(`account "${resolved.email}" not found`);
|
|
4247
|
+
switch (key) {
|
|
4248
|
+
case "actor":
|
|
4249
|
+
if (value !== "user" && value !== "agent") throw new Error("actor must be 'user' or 'agent'");
|
|
4250
|
+
acct.actor = value;
|
|
4251
|
+
break;
|
|
4252
|
+
case "agent-label":
|
|
4253
|
+
acct.agent_label = value;
|
|
4254
|
+
break;
|
|
4255
|
+
default:
|
|
4256
|
+
throw new Error(`unknown config key: ${key}`);
|
|
4257
|
+
}
|
|
4258
|
+
});
|
|
4220
4259
|
}
|
|
4221
4260
|
configCommand.command("show").description("List configured envs (tokens redacted, current marked with \u2713)").action(async () => {
|
|
4222
4261
|
const c = await new ConfigStore().read();
|
|
@@ -4242,16 +4281,16 @@ configCommand.command("set <key> <value>").description("Set a field on the activ
|
|
|
4242
4281
|
});
|
|
4243
4282
|
configCommand.command("use <env>").description("Switch current_env").action(async (env) => {
|
|
4244
4283
|
const store = new ConfigStore();
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4284
|
+
await store.update((c) => {
|
|
4285
|
+
if (!c.envs[env]) throw new Error(`env "${env}" not found`);
|
|
4286
|
+
c.current_env = env;
|
|
4287
|
+
});
|
|
4249
4288
|
process.stdout.write(`\u2713 current_env=${env}
|
|
4250
4289
|
`);
|
|
4251
4290
|
});
|
|
4252
4291
|
|
|
4253
4292
|
// src/handwritten/commands/account.ts
|
|
4254
|
-
import { Command as
|
|
4293
|
+
import { Command as Command67 } from "commander";
|
|
4255
4294
|
async function listAccounts(store) {
|
|
4256
4295
|
const c = await store.read();
|
|
4257
4296
|
const envName = c.current_env;
|
|
@@ -4266,15 +4305,15 @@ async function listAccounts(store) {
|
|
|
4266
4305
|
}));
|
|
4267
4306
|
}
|
|
4268
4307
|
async function switchAccount(store, email) {
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4308
|
+
await store.update((c) => {
|
|
4309
|
+
const envName = c.current_env;
|
|
4310
|
+
if (!envName || !c.envs[envName]) throw new Error("no current env; run `wspc login` first");
|
|
4311
|
+
const env = c.envs[envName];
|
|
4312
|
+
if (!env.accounts?.[email]) {
|
|
4313
|
+
throw new Error(`no account '${email}' in env '${envName}'. Run \`wspc account ls\` or \`wspc login\`.`);
|
|
4314
|
+
}
|
|
4315
|
+
env.current_account = email;
|
|
4316
|
+
});
|
|
4278
4317
|
}
|
|
4279
4318
|
registerRenderer("account_ls", (data) => {
|
|
4280
4319
|
const rows = data.accounts;
|
|
@@ -4292,7 +4331,7 @@ registerRenderer("account_ls", (data) => {
|
|
|
4292
4331
|
]);
|
|
4293
4332
|
process.stdout.write(table(headers, body));
|
|
4294
4333
|
});
|
|
4295
|
-
var accountCommand = new
|
|
4334
|
+
var accountCommand = new Command67("account").description("Manage logged-in accounts");
|
|
4296
4335
|
accountCommand.command("ls").description("List accounts in the current env (active marked with \u2713)").action(async () => {
|
|
4297
4336
|
const accounts = await listAccounts(new ConfigStore());
|
|
4298
4337
|
render({ kind: "account_ls" }, { accounts });
|
|
@@ -4304,7 +4343,7 @@ accountCommand.command("switch <email>").description("Set the active account for
|
|
|
4304
4343
|
});
|
|
4305
4344
|
|
|
4306
4345
|
// src/handwritten/commands/todo-done.ts
|
|
4307
|
-
import { Command as
|
|
4346
|
+
import { Command as Command68 } from "commander";
|
|
4308
4347
|
var TODO_UPDATE_DISPLAY = {
|
|
4309
4348
|
shape: "object",
|
|
4310
4349
|
format: {
|
|
@@ -4322,7 +4361,7 @@ var TODO_UPDATE_DISPLAY = {
|
|
|
4322
4361
|
deleted_at: "relative-time"
|
|
4323
4362
|
}
|
|
4324
4363
|
};
|
|
4325
|
-
var todoDoneCommand = new
|
|
4364
|
+
var todoDoneCommand = new Command68("done").description("Mark a todo done (sugar for `update <id> --status done`)").argument("<id>", "Todo id").action(async (id) => {
|
|
4326
4365
|
const client2 = await loadSdkClient();
|
|
4327
4366
|
const result = await todoUpdate({
|
|
4328
4367
|
client: client2._rawClient,
|
|
@@ -4341,7 +4380,8 @@ var todoDoneCommand = new Command67("done").description("Mark a todo done (sugar
|
|
|
4341
4380
|
});
|
|
4342
4381
|
|
|
4343
4382
|
// src/handwritten/commands/email/send.ts
|
|
4344
|
-
import { Command as
|
|
4383
|
+
import { Command as Command69 } from "commander";
|
|
4384
|
+
import { randomUUID } from "crypto";
|
|
4345
4385
|
import { readFile, stat } from "fs/promises";
|
|
4346
4386
|
import { basename } from "path";
|
|
4347
4387
|
|
|
@@ -4399,7 +4439,7 @@ async function resolveAttachment(input) {
|
|
|
4399
4439
|
`--attach ${input}: neither a readable file nor a valid <prefix>_<ulid>:<idx> reference.`
|
|
4400
4440
|
);
|
|
4401
4441
|
}
|
|
4402
|
-
var sendCommand = new
|
|
4442
|
+
var sendCommand = new Command69("send").description("Send an outbound email").requiredOption("--from <alias-email>", "alias email to send from").option("--to <addr...>", "recipient address (repeatable)", []).option("--subject <text>", "subject").option("--text <body>", "plain-text body").option("--text-file <path>", "read text body from file").option("--reply <id>", "inbound email id to reply to").option("--attach <path-or-ref...>", "attachment (file path or eml_xxx:idx)", []).option("--idempotency-key <key>", "idempotency key (auto-generated if omitted)").action(async (opts) => {
|
|
4403
4443
|
const isReply = Boolean(opts.reply);
|
|
4404
4444
|
const to = opts.to;
|
|
4405
4445
|
const attachInputs = opts.attach;
|
|
@@ -4457,7 +4497,9 @@ var sendCommand = new Command68("send").description("Send an outbound email").re
|
|
|
4457
4497
|
const body = {
|
|
4458
4498
|
from_alias_email: opts.from,
|
|
4459
4499
|
text,
|
|
4460
|
-
idempotency_key
|
|
4500
|
+
// Server requires an idempotency_key; a fresh random one makes each send a
|
|
4501
|
+
// distinct request, which is the right default for one-shot CLI sends.
|
|
4502
|
+
idempotency_key: opts.idempotencyKey ?? randomUUID()
|
|
4461
4503
|
};
|
|
4462
4504
|
if (isReply) {
|
|
4463
4505
|
body.in_reply_to_email_id = opts.reply;
|
|
@@ -4486,7 +4528,7 @@ var sendCommand = new Command68("send").description("Send an outbound email").re
|
|
|
4486
4528
|
});
|
|
4487
4529
|
|
|
4488
4530
|
// src/handwritten/commands/email/attachment.ts
|
|
4489
|
-
import { Command as
|
|
4531
|
+
import { Command as Command70 } from "commander";
|
|
4490
4532
|
import { createWriteStream } from "fs";
|
|
4491
4533
|
import { Readable } from "stream";
|
|
4492
4534
|
import { pipeline } from "stream/promises";
|
|
@@ -4495,15 +4537,16 @@ import { pipeline } from "stream/promises";
|
|
|
4495
4537
|
function parseContentDispositionFilename(header) {
|
|
4496
4538
|
if (!header) return void 0;
|
|
4497
4539
|
if (!header.toLowerCase().startsWith("attachment")) return void 0;
|
|
4498
|
-
const quoted = header.match(/filename="([^"]
|
|
4499
|
-
if (quoted) return quoted[1];
|
|
4540
|
+
const quoted = header.match(/filename="([^"]*)"/i);
|
|
4500
4541
|
const unquoted = header.match(/filename=([^;\s]+)/i);
|
|
4501
|
-
|
|
4502
|
-
return void 0;
|
|
4542
|
+
const filename = quoted?.[1] ?? unquoted?.[1];
|
|
4543
|
+
if (!filename || filename === "." || filename === "..") return void 0;
|
|
4544
|
+
if (filename.includes("/") || filename.includes("\\")) return void 0;
|
|
4545
|
+
return filename;
|
|
4503
4546
|
}
|
|
4504
4547
|
|
|
4505
4548
|
// src/handwritten/commands/email/attachment.ts
|
|
4506
|
-
var attachmentCommand = new
|
|
4549
|
+
var attachmentCommand = new Command70("attachment").description("Download an inbound email attachment by index").argument("<email-id>").argument("<idx>").option("--output <path>", "output file path").option("--include-deleted", "allow downloads from soft-deleted parent emails").action(async (emailId, idxArg, opts) => {
|
|
4507
4550
|
const idx = Number(idxArg);
|
|
4508
4551
|
if (!Number.isInteger(idx) || idx < 0) {
|
|
4509
4552
|
process.stderr.write(`<idx> must be a non-negative integer (got "${idxArg}")
|
|
@@ -4535,7 +4578,7 @@ var attachmentCommand = new Command69("attachment").description("Download an inb
|
|
|
4535
4578
|
});
|
|
4536
4579
|
|
|
4537
4580
|
// src/handwritten/commands/drive/bind.ts
|
|
4538
|
-
import { Command as
|
|
4581
|
+
import { Command as Command71 } from "commander";
|
|
4539
4582
|
import { stat as stat2 } from "fs/promises";
|
|
4540
4583
|
import { resolve } from "path";
|
|
4541
4584
|
|
|
@@ -4625,7 +4668,7 @@ async function createDriveApi(opts = {}) {
|
|
|
4625
4668
|
|
|
4626
4669
|
// src/handwritten/commands/drive/state.ts
|
|
4627
4670
|
import { mkdir, open, readFile as readFile2, rename, rm, writeFile } from "fs/promises";
|
|
4628
|
-
import { randomUUID } from "crypto";
|
|
4671
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
4629
4672
|
import { join as join2 } from "path";
|
|
4630
4673
|
import { DateTime as DateTime4 } from "luxon";
|
|
4631
4674
|
|
|
@@ -4665,7 +4708,7 @@ async function readDriveState(root) {
|
|
|
4665
4708
|
}
|
|
4666
4709
|
async function writeDriveState(root, state, clock) {
|
|
4667
4710
|
await mkdir(join2(root, DRIVE_DIR), { recursive: true });
|
|
4668
|
-
const tmp = join2(root, DRIVE_DIR, `state.json.tmp-${process.pid}-${
|
|
4711
|
+
const tmp = join2(root, DRIVE_DIR, `state.json.tmp-${process.pid}-${randomUUID2()}`);
|
|
4669
4712
|
const snapshot = JSON.stringify(
|
|
4670
4713
|
{
|
|
4671
4714
|
...state,
|
|
@@ -4721,7 +4764,7 @@ async function ensureDriveRealtimeState(root) {
|
|
|
4721
4764
|
...state,
|
|
4722
4765
|
realtime: {
|
|
4723
4766
|
...state.realtime,
|
|
4724
|
-
client_id: `drvcli_${
|
|
4767
|
+
client_id: `drvcli_${randomUUID2().replace(/-/g, "")}`
|
|
4725
4768
|
}
|
|
4726
4769
|
};
|
|
4727
4770
|
await writeDriveState(root, next);
|
|
@@ -4798,7 +4841,7 @@ async function assertExistingDirectory(path) {
|
|
|
4798
4841
|
}
|
|
4799
4842
|
}
|
|
4800
4843
|
function driveBindCommand() {
|
|
4801
|
-
return new
|
|
4844
|
+
return new Command71("bind").description("Bind a local folder to an existing Drive library").requiredOption("--library <id>", "existing Drive library id").argument("[path]", "local folder path", ".").action(async (path, opts) => {
|
|
4802
4845
|
const root = resolve(path);
|
|
4803
4846
|
await assertExistingDirectory(root);
|
|
4804
4847
|
const api = await createDriveApi();
|
|
@@ -4816,10 +4859,10 @@ function driveBindCommand() {
|
|
|
4816
4859
|
}
|
|
4817
4860
|
|
|
4818
4861
|
// src/handwritten/commands/drive/sync.ts
|
|
4819
|
-
import { Command as
|
|
4862
|
+
import { Command as Command72 } from "commander";
|
|
4820
4863
|
import { mkdir as mkdir3, readFile as readFile4, rm as rm3, writeFile as writeFile3 } from "fs/promises";
|
|
4821
4864
|
import { basename as basename3, dirname as dirname2, join as join5, posix as pathPosix2, resolve as resolve3 } from "path";
|
|
4822
|
-
import { createHash as createHash3, randomUUID as
|
|
4865
|
+
import { createHash as createHash3, randomUUID as randomUUID4 } from "crypto";
|
|
4823
4866
|
|
|
4824
4867
|
// src/handwritten/commands/drive/decision.ts
|
|
4825
4868
|
function decideDriveAction(entry, local, remote) {
|
|
@@ -5200,7 +5243,7 @@ async function hashDriveFile(path) {
|
|
|
5200
5243
|
import { createWriteStream as createWriteStream2 } from "fs";
|
|
5201
5244
|
import { link, mkdir as mkdir2, readFile as readFile3, rename as rename2, rm as rm2, unlink, writeFile as writeFile2 } from "fs/promises";
|
|
5202
5245
|
import { basename as basename2, dirname, join as join4 } from "path";
|
|
5203
|
-
import { createHash as createHash2, randomUUID as
|
|
5246
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
5204
5247
|
import { Readable as Readable2, Transform } from "stream";
|
|
5205
5248
|
import { pipeline as pipeline2 } from "stream/promises";
|
|
5206
5249
|
async function assertLocalStillScanned(localPath, scanned) {
|
|
@@ -5215,7 +5258,7 @@ async function assertLocalStillScanned(localPath, scanned) {
|
|
|
5215
5258
|
async function writeMergedLocalFile(root, path, bytes, digest, scanned, onLocalMutation) {
|
|
5216
5259
|
const target = resolveInsideRoot(root, path);
|
|
5217
5260
|
await mkdir2(dirname(target), { recursive: true });
|
|
5218
|
-
const tmp = join4(dirname(target), `.${basename2(target)}.wspc-merge-${
|
|
5261
|
+
const tmp = join4(dirname(target), `.${basename2(target)}.wspc-merge-${randomUUID3()}.tmp`);
|
|
5219
5262
|
try {
|
|
5220
5263
|
await writeFile2(tmp, bytes, { flag: "wx" });
|
|
5221
5264
|
return await installMergedLocalFile(root, path, tmp, scanned, digest, bytes.byteLength, onLocalMutation);
|
|
@@ -5267,7 +5310,7 @@ async function installMergedLocalFile(root, path, tmp, scanned, mergedSha256, me
|
|
|
5267
5310
|
}
|
|
5268
5311
|
}
|
|
5269
5312
|
async function restoreMergedLocalFile(target, backup, backupSha256, backupSizeBytes, mergedSha256, mergedSizeBytes) {
|
|
5270
|
-
const quarantine = join4(dirname(target), `.${basename2(target)}.wspc-merge-restore-${
|
|
5313
|
+
const quarantine = join4(dirname(target), `.${basename2(target)}.wspc-merge-restore-${randomUUID3()}.tmp`);
|
|
5271
5314
|
try {
|
|
5272
5315
|
await rename2(target, quarantine);
|
|
5273
5316
|
} catch (error) {
|
|
@@ -5297,7 +5340,7 @@ async function restoreMergedLocalFile(target, backup, backupSha256, backupSizeBy
|
|
|
5297
5340
|
async function downloadRemote(root, libraryId, path, api, expectedSha256, entry, onLocalMutation) {
|
|
5298
5341
|
const target = resolveInsideRoot(root, path);
|
|
5299
5342
|
await mkdir2(dirname(target), { recursive: true });
|
|
5300
|
-
const tmp = join4(dirname(target), `.${basename2(target)}.wspc-download-${
|
|
5343
|
+
const tmp = join4(dirname(target), `.${basename2(target)}.wspc-download-${randomUUID3()}.tmp`);
|
|
5301
5344
|
try {
|
|
5302
5345
|
const response = await api.downloadFile(libraryId, path);
|
|
5303
5346
|
if (!response.body) {
|
|
@@ -5431,7 +5474,7 @@ async function localFileExists(path) {
|
|
|
5431
5474
|
return digest !== void 0;
|
|
5432
5475
|
}
|
|
5433
5476
|
function localMutationBackupPath(target) {
|
|
5434
|
-
return join4(dirname(target), `.${basename2(target)}.wspc-backup-${
|
|
5477
|
+
return join4(dirname(target), `.${basename2(target)}.wspc-backup-${randomUUID3()}.tmp`);
|
|
5435
5478
|
}
|
|
5436
5479
|
function expectedLocalBaseSha256(entry) {
|
|
5437
5480
|
return entry?.last_local_sha256 ?? entry?.content_sha256;
|
|
@@ -5533,7 +5576,7 @@ async function runDriveSyncOnce(root, api, clock = systemDriveClock) {
|
|
|
5533
5576
|
});
|
|
5534
5577
|
}
|
|
5535
5578
|
function driveSyncCommand(api) {
|
|
5536
|
-
const sync = new
|
|
5579
|
+
const sync = new Command72("sync").description("Drive sync commands");
|
|
5537
5580
|
sync.command("once").description("Run one Drive sync pass").argument("[path]", "local folder path", ".").action(async (path) => {
|
|
5538
5581
|
const summary = await runDriveSyncOnce(resolve3(path), api);
|
|
5539
5582
|
render({ kind: "drive_sync_once", display: { shape: "object" } }, summary);
|
|
@@ -5845,7 +5888,7 @@ async function writeConflictCopy(root, path, side, versionId, bytes, clock) {
|
|
|
5845
5888
|
const target = resolveInsideRoot(root, candidate);
|
|
5846
5889
|
await mkdir3(dirname2(target), { recursive: true });
|
|
5847
5890
|
for (; ; ) {
|
|
5848
|
-
const tmp = join5(dirname2(target), `.${basename3(target)}.wspc-conflict-${
|
|
5891
|
+
const tmp = join5(dirname2(target), `.${basename3(target)}.wspc-conflict-${randomUUID4()}.tmp`);
|
|
5849
5892
|
let tmpWritten = false;
|
|
5850
5893
|
try {
|
|
5851
5894
|
await writeFile3(tmp, bytes, { flag: "wx" });
|
|
@@ -5986,7 +6029,7 @@ function containsVersionConflict(value) {
|
|
|
5986
6029
|
}
|
|
5987
6030
|
|
|
5988
6031
|
// src/handwritten/commands/drive/watch.ts
|
|
5989
|
-
import { Command as
|
|
6032
|
+
import { Command as Command73 } from "commander";
|
|
5990
6033
|
import chokidar from "chokidar";
|
|
5991
6034
|
import { relative as relative2, resolve as resolve4 } from "path";
|
|
5992
6035
|
|
|
@@ -6064,7 +6107,11 @@ function createDriveRealtimeSource(args) {
|
|
|
6064
6107
|
const message = parseDriveRealtimeMessage(data);
|
|
6065
6108
|
if (message.type === "ready") {
|
|
6066
6109
|
if (message.replayed > 0) {
|
|
6067
|
-
handlers?.onEvent(
|
|
6110
|
+
handlers?.onEvent({
|
|
6111
|
+
debounce_ms: 2e3,
|
|
6112
|
+
reason: "ready_replay",
|
|
6113
|
+
...message.cursor === void 0 ? {} : { cursor: message.cursor }
|
|
6114
|
+
});
|
|
6068
6115
|
}
|
|
6069
6116
|
if (message.cursor !== void 0) {
|
|
6070
6117
|
await persistBestEffort({ ...currentRealtime, last_cursor: message.cursor });
|
|
@@ -6072,10 +6119,12 @@ function createDriveRealtimeSource(args) {
|
|
|
6072
6119
|
return;
|
|
6073
6120
|
}
|
|
6074
6121
|
if (message.type === "library_changed") {
|
|
6075
|
-
handlers?.onEvent(
|
|
6122
|
+
handlers?.onEvent({
|
|
6076
6123
|
debounce_ms: 2e3,
|
|
6077
|
-
reason: "library_changed"
|
|
6078
|
-
|
|
6124
|
+
reason: "library_changed",
|
|
6125
|
+
...message.cursor === void 0 ? {} : { cursor: message.cursor },
|
|
6126
|
+
...message.path === void 0 ? {} : { path: message.path }
|
|
6127
|
+
});
|
|
6079
6128
|
if (message.cursor !== void 0) {
|
|
6080
6129
|
await persistBestEffort({ ...currentRealtime, last_cursor: message.cursor, last_event_at: driveIsoTimestamp(clock) });
|
|
6081
6130
|
}
|
|
@@ -6083,7 +6132,11 @@ function createDriveRealtimeSource(args) {
|
|
|
6083
6132
|
}
|
|
6084
6133
|
if (message.type === "resync_required") {
|
|
6085
6134
|
const reason = message.reason ?? "resync_required";
|
|
6086
|
-
handlers?.onEvent(
|
|
6135
|
+
handlers?.onEvent({
|
|
6136
|
+
immediate: true,
|
|
6137
|
+
reason,
|
|
6138
|
+
...message.cursor === void 0 ? {} : { cursor: message.cursor }
|
|
6139
|
+
});
|
|
6087
6140
|
if (message.cursor !== void 0 || isInvalidCursorReason(reason)) {
|
|
6088
6141
|
await persistBestEffort(resyncRealtimeState(currentRealtime, message.cursor, reason, driveIsoTimestamp(clock)));
|
|
6089
6142
|
}
|
|
@@ -6148,22 +6201,37 @@ function parseDriveRealtimeMessage(raw) {
|
|
|
6148
6201
|
const messageType = typeof value.type === "string" ? value.type : void 0;
|
|
6149
6202
|
const cursor = typeof value.cursor === "string" ? value.cursor : void 0;
|
|
6150
6203
|
if (messageType === "ready") {
|
|
6151
|
-
return
|
|
6204
|
+
return {
|
|
6205
|
+
type: "ready",
|
|
6206
|
+
replayed: typeof value.replayed === "number" ? value.replayed : 0,
|
|
6207
|
+
...cursor === void 0 ? {} : { cursor }
|
|
6208
|
+
};
|
|
6152
6209
|
}
|
|
6153
6210
|
if (messageType === "library_changed") {
|
|
6154
|
-
return
|
|
6211
|
+
return {
|
|
6212
|
+
type: "library_changed",
|
|
6213
|
+
...cursor === void 0 ? {} : { cursor },
|
|
6214
|
+
...typeof value.path === "string" ? { path: value.path } : {}
|
|
6215
|
+
};
|
|
6155
6216
|
}
|
|
6156
6217
|
if (messageType === "resync_required") {
|
|
6157
|
-
return
|
|
6218
|
+
return {
|
|
6219
|
+
type: "resync_required",
|
|
6220
|
+
...cursor === void 0 ? {} : { cursor },
|
|
6221
|
+
...typeof value.reason === "string" ? { reason: value.reason } : {}
|
|
6222
|
+
};
|
|
6158
6223
|
}
|
|
6159
6224
|
if (messageType === "error") {
|
|
6160
|
-
return
|
|
6161
|
-
|
|
6162
|
-
"
|
|
6163
|
-
typeof value.message === "string" ? redactedRealtimeError(value.message) :
|
|
6164
|
-
|
|
6225
|
+
return {
|
|
6226
|
+
type: "error",
|
|
6227
|
+
...typeof value.code === "string" ? { code: value.code } : {},
|
|
6228
|
+
...typeof value.message === "string" ? { message: redactedRealtimeError(value.message) } : {}
|
|
6229
|
+
};
|
|
6165
6230
|
}
|
|
6166
|
-
return
|
|
6231
|
+
return {
|
|
6232
|
+
type: "unknown",
|
|
6233
|
+
...messageType === void 0 ? {} : { message_type: messageType }
|
|
6234
|
+
};
|
|
6167
6235
|
}
|
|
6168
6236
|
function redactedRealtimeError(error) {
|
|
6169
6237
|
const text = error instanceof Error ? error.message : String(error);
|
|
@@ -6187,7 +6255,11 @@ function resyncRealtimeState(realtime, cursor, reason, lastEventAt) {
|
|
|
6187
6255
|
const { last_cursor: _lastCursor, ...next } = realtime;
|
|
6188
6256
|
return { ...next, last_event_at: lastEventAt };
|
|
6189
6257
|
}
|
|
6190
|
-
return
|
|
6258
|
+
return {
|
|
6259
|
+
...realtime,
|
|
6260
|
+
last_event_at: lastEventAt,
|
|
6261
|
+
...cursor === void 0 ? {} : { last_cursor: cursor }
|
|
6262
|
+
};
|
|
6191
6263
|
}
|
|
6192
6264
|
function isInvalidCursorReason(reason) {
|
|
6193
6265
|
return /\bcursor[_ -]?(invalid|expired|gone|missing|not[_ -]?found)\b|\binvalid[_ -]?cursor\b/i.test(reason);
|
|
@@ -6195,12 +6267,6 @@ function isInvalidCursorReason(reason) {
|
|
|
6195
6267
|
function isRealtimeAuthError(error) {
|
|
6196
6268
|
return /\b(401|403|auth|authorization|unauthorized|forbidden)\b/i.test(String(error));
|
|
6197
6269
|
}
|
|
6198
|
-
function optionalString(target, key, value) {
|
|
6199
|
-
if (typeof value !== "string") {
|
|
6200
|
-
return target;
|
|
6201
|
-
}
|
|
6202
|
-
return { ...target, [key]: value };
|
|
6203
|
-
}
|
|
6204
6270
|
function nativeWebSocketConnector(url, handlers, init) {
|
|
6205
6271
|
const WebSocketWithInit = WebSocket;
|
|
6206
6272
|
const ws = new WebSocketWithInit(url.toString(), init);
|
|
@@ -6402,7 +6468,7 @@ async function runDriveWatch(root, options = {}) {
|
|
|
6402
6468
|
}
|
|
6403
6469
|
}
|
|
6404
6470
|
function driveWatchCommand(options = {}) {
|
|
6405
|
-
return new
|
|
6471
|
+
return new Command73("watch").description("Watch a bound Drive folder and sync local changes").argument("[path]", "local folder path", ".").action(async (path) => {
|
|
6406
6472
|
await runDriveWatch(resolve4(path), options);
|
|
6407
6473
|
});
|
|
6408
6474
|
}
|
|
@@ -6466,7 +6532,7 @@ function waitForStopSignal(onRegistered) {
|
|
|
6466
6532
|
function mountDriveCommands(program) {
|
|
6467
6533
|
let drive = program.commands.find((c) => c.name() === "drive");
|
|
6468
6534
|
if (!drive) {
|
|
6469
|
-
drive = new
|
|
6535
|
+
drive = new Command74("drive").description("Drive commands");
|
|
6470
6536
|
program.addCommand(drive);
|
|
6471
6537
|
}
|
|
6472
6538
|
if (!drive.commands.some((c) => c.name() === "bind")) {
|
|
@@ -6492,7 +6558,7 @@ function isCliEntrypoint(argv = process.argv, metaUrl = import.meta.url) {
|
|
|
6492
6558
|
}
|
|
6493
6559
|
}
|
|
6494
6560
|
function buildProgram() {
|
|
6495
|
-
const program = new
|
|
6561
|
+
const program = new Command74().name("wspc").description("Official CLI for wspc.ai").version(`wspc ${VERSION} (spec ${SPEC_SHA}, fetched ${SPEC_FETCHED_AT})`).option("--json", "Output raw JSON (machine-readable)").option("--account <email>", "Run as a specific account (overrides the active account)").hook("preAction", (_thisCommand, actionCommand) => {
|
|
6496
6562
|
const globals = actionCommand.optsWithGlobals();
|
|
6497
6563
|
if (globals.json) process.env.WSPC_OUTPUT = "json";
|
|
6498
6564
|
if (globals.account) process.env.WSPC_ACCOUNT = String(globals.account);
|