@flow-engineer/flow 0.3.1 → 0.3.2

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.
@@ -352,6 +352,8 @@ run again. `--channel telegram|whatsapp|imessage` picks the channel; a channel f
352
352
  open yet exits 3 with its `next`. When only observe's part of setup fails (traces and the test cloud),
353
353
  `link` still prints the link and exits 0, with one warning saying what is missing and the server's
354
354
  fix (`warnings` in `--json`): pass it on to the owner. `login`, `setup` and `link` also run on their own.
355
+ When the owner names a team's observe workspace, pass `--workspace <name|acc_id>` to `setup` or `link`
356
+ once: its `acc_` id is kept in `.env` (`FLOW_FRAMEWORK_WORKSPACE`) for later runs, and `status` shows it.
355
357
 
356
358
  In `src/main.ts`, put `fm.telegram()` from `@flow-engineer/flow/flow-messaging` in the agent's
357
359
  channels and call `fm.start(agent, { cursorFile: ".flow/flow-messaging-cursor" })`: with no webhook
@@ -1,4 +1,4 @@
1
1
  export declare const ACCOUNT_COMMANDS: Set<string>;
2
- export declare const ACCOUNT_LINES = " flow login [--wait | --no-wait] [--timeout <s>] [--force] [--no-browser] [--json]\n Sign in to flow.engineer: opens the browser, prints the link and code,\n and waits up to --timeout (default 120) for approval; --wait goes on\n waiting for the same code, --no-wait returns at once\n flow logout [--json] End this machine's sign-in and delete its token\n flow whoami [--json] Show the account you are signed in as\n flow setup [--replace] [--rotate <keys> [--now]] [--display-name <name>] [--ci] [--json]\n Write this agent's keys (its Flow key FLOW_KEY, which also pays for the\n model on the account's trial credit when there is no provider key, test\n cloud, traces, Flow Messaging) and each open channel's sender into\n ./.env (in an agent's folder: package.json); --rotate key,cloud,traces,\n messaging or all makes those keys again (the old FLOW_KEY works 24 h\n more; --now ends it at once)\n flow link [--channel telegram|whatsapp|imessage] [--display-name <name>] [--timeout <s>] [--no-browser] [--json]\n Print the link that opens a chat with this agent from a phone; signs in\n and runs setup first when this folder is not set up yet. --display-name\n (here and on setup) renames the agent: package.json's displayName and\n its prompt, agent/AGENTS.md; the phone link then says \"Hi <name>\"\n flow status [--json] What this folder has and lacks: FLOW_KEY and the model key (never their\n values) with the trial credit left, the\n sign-in, each channel's sender in .env, and, signed in, which channels\n are open now and their free-trial counts";
2
+ export declare const ACCOUNT_LINES = " flow login [--wait | --no-wait] [--timeout <s>] [--force] [--no-browser] [--json]\n Sign in to flow.engineer: opens the browser, prints the link and code,\n and waits up to --timeout (default 120) for approval; --wait goes on\n waiting for the same code, --no-wait returns at once\n flow logout [--json] End this machine's sign-in and delete its token\n flow whoami [--json] Show the account you are signed in as\n flow setup [--replace] [--rotate <keys> [--now]] [--workspace <name|acc_id>] [--display-name <name>] [--ci] [--json]\n Write this agent's keys (its Flow key FLOW_KEY, which also pays for the\n model on the account's trial credit when there is no provider key, test\n cloud, traces, Flow Messaging) and each open channel's sender into\n ./.env (in an agent's folder: package.json); --rotate key,cloud,traces,\n messaging or all makes those keys again (the old FLOW_KEY works 24 h\n more; --now ends it at once). --workspace (here and on link) puts the\n agent in that observe workspace of yours, kept in .env as\n FLOW_FRAMEWORK_WORKSPACE; a new one makes the cloud and traces keys again\n flow link [--channel telegram|whatsapp|imessage] [--workspace <name|acc_id>] [--display-name <name>] [--timeout <s>] [--no-browser] [--json]\n Print the link that opens a chat with this agent from a phone; signs in\n and runs setup first when this folder is not set up yet. --display-name\n (here and on setup) renames the agent: package.json's displayName and\n its prompt, agent/AGENTS.md; the phone link then says \"Hi <name>\"\n flow status [--json] What this folder has and lacks: FLOW_KEY and the model key (never their\n values) with the trial credit left, the workspace, the\n sign-in, each channel's sender in .env, and, signed in, which channels\n are open now and their free-trial counts";
3
3
  /** Runs one account command; resolves to the exit status. */
4
4
  export declare function accountCli(command: string, args: string[]): Promise<number>;
@@ -30,20 +30,22 @@ export const ACCOUNT_LINES = ` flow login [--wait | --no-wait] [--timeout <s>]
30
30
  waiting for the same code, --no-wait returns at once
31
31
  flow logout [--json] End this machine's sign-in and delete its token
32
32
  flow whoami [--json] Show the account you are signed in as
33
- flow setup [--replace] [--rotate <keys> [--now]] [--display-name <name>] [--ci] [--json]
33
+ flow setup [--replace] [--rotate <keys> [--now]] [--workspace <name|acc_id>] [--display-name <name>] [--ci] [--json]
34
34
  Write this agent's keys (its Flow key FLOW_KEY, which also pays for the
35
35
  model on the account's trial credit when there is no provider key, test
36
36
  cloud, traces, Flow Messaging) and each open channel's sender into
37
37
  ./.env (in an agent's folder: package.json); --rotate key,cloud,traces,
38
38
  messaging or all makes those keys again (the old FLOW_KEY works 24 h
39
- more; --now ends it at once)
40
- flow link [--channel telegram|whatsapp|imessage] [--display-name <name>] [--timeout <s>] [--no-browser] [--json]
39
+ more; --now ends it at once). --workspace (here and on link) puts the
40
+ agent in that observe workspace of yours, kept in .env as
41
+ FLOW_FRAMEWORK_WORKSPACE; a new one makes the cloud and traces keys again
42
+ flow link [--channel telegram|whatsapp|imessage] [--workspace <name|acc_id>] [--display-name <name>] [--timeout <s>] [--no-browser] [--json]
41
43
  Print the link that opens a chat with this agent from a phone; signs in
42
44
  and runs setup first when this folder is not set up yet. --display-name
43
45
  (here and on setup) renames the agent: package.json's displayName and
44
46
  its prompt, agent/AGENTS.md; the phone link then says "Hi <name>"
45
47
  flow status [--json] What this folder has and lacks: FLOW_KEY and the model key (never their
46
- values) with the trial credit left, the
48
+ values) with the trial credit left, the workspace, the
47
49
  sign-in, each channel's sender in .env, and, signed in, which channels
48
50
  are open now and their free-trial counts`;
49
51
  const USAGE = `Usage:\n${ACCOUNT_LINES}
@@ -100,7 +102,7 @@ export async function accountCli(command, args) {
100
102
  : {}),
101
103
  ...(command === "setup" ? { replace: { type: "boolean", default: false }, rotate: { type: "string" }, now: { type: "boolean", default: false }, ci: { type: "boolean", default: false } } : {}),
102
104
  ...(command === "link" ? { channel: { type: "string", default: "telegram" } } : {}),
103
- ...(command === "link" || command === "setup" ? { "display-name": { type: "string" } } : {}),
105
+ ...(command === "link" || command === "setup" ? { "display-name": { type: "string" }, workspace: { type: "string" } } : {}),
104
106
  },
105
107
  }));
106
108
  }
@@ -131,6 +133,12 @@ export async function accountCli(command, args) {
131
133
  console.error(`flow setup: --now goes with --rotate key (or all): it ends the old FLOW_KEY at once.\n\n${USAGE}`);
132
134
  return EXIT.usage;
133
135
  }
136
+ const workspace = typeof values.workspace === "string" ? values.workspace.trim() : undefined;
137
+ // flow.engineer takes a workspace's acc_ id or its name: 1 to 100 characters, no control characters.
138
+ if (workspace !== undefined && (!workspace || workspace.length > 100 || /[\u0000-\u001f\u007f]/.test(workspace))) {
139
+ console.error(`flow ${command}: --workspace takes a workspace's name or its acc_ id, 1 to 100 characters.\n\n${USAGE}`);
140
+ return EXIT.usage;
141
+ }
134
142
  const json = values.json === true;
135
143
  // Straight to the file descriptors, never buffered: a coding agent's cut-off command keeps every line.
136
144
  const out = (report) => writeAll(1, `${JSON.stringify(report)}\n`);
@@ -162,9 +170,9 @@ export async function accountCli(command, args) {
162
170
  else if (command === "status")
163
171
  await status(json, out, say);
164
172
  else if (command === "setup")
165
- await setupEnv({ replace: values.replace === true, rotate, now: values.now === true, ci: values.ci === true }, json, out, say);
173
+ await setupEnv({ replace: values.replace === true, rotate, now: values.now === true, ci: values.ci === true, workspace }, json, out, say);
166
174
  else
167
- await link({ channel, noBrowser: values["no-browser"] === true, timeoutSeconds, renamed }, json, out, say);
175
+ await link({ channel, noBrowser: values["no-browser"] === true, timeoutSeconds, renamed, workspace }, json, out, say);
168
176
  return EXIT.ok;
169
177
  }
170
178
  catch (error) {
@@ -499,6 +507,19 @@ const OPEN_HINT = {
499
507
  whatsapp: "Open it and send the message it fills in.",
500
508
  imessage: "Open it on an iPhone or a Mac and send the message it fills in.",
501
509
  };
510
+ /** The observe workspace this folder's agent was put in by --workspace (its acc_ id); unset: the personal one. */
511
+ const WORKSPACE_ENV = "FLOW_FRAMEWORK_WORKSPACE";
512
+ /** The answer's workspace_id, under the name setupEnv gives it only when a workspace was asked for. */
513
+ const WORKSPACE_FIELD = "workspace_kept";
514
+ /** --workspace names another workspace than the one .env's cloud and traces keys are in (none named yet: the personal one). */
515
+ function movesWorkspace(workspace, value) {
516
+ if (workspace === undefined)
517
+ return false;
518
+ const stored = value(WORKSPACE_ENV);
519
+ if (stored)
520
+ return workspace.toLowerCase() !== stored.toLowerCase();
521
+ return Boolean(value(SETUP_ENV.cloud_key) || value(SETUP_ENV.traces_key));
522
+ }
502
523
  async function setupEnv(options, json, out, say, forLink) {
503
524
  if (options.ci) {
504
525
  const steps = [
@@ -527,6 +548,14 @@ async function setupEnv(options, json, out, say, forLink) {
527
548
  const name = named.replace(/^@[^/]*\//, "").replace(/[^A-Za-z0-9 ._-]/g, "-").slice(0, 64) || "my-agent";
528
549
  const all = options.rotate.includes("all");
529
550
  const rotated = new Set(all ? Object.values(ROTATE).filter(Boolean) : options.rotate.map((k) => ROTATE[k]));
551
+ // The workspace asked for: --workspace, else the one an earlier --workspace kept in .env.
552
+ const workspace = options.workspace ?? (before.get(WORKSPACE_ENV) || undefined);
553
+ // Another workspace: the cloud and traces keys in .env are the old one's, so they are made again
554
+ // there (Messaging's key and FLOW_KEY are not bound to a workspace).
555
+ const moving = movesWorkspace(options.workspace, (n) => before.get(n));
556
+ if (moving)
557
+ for (const field of ["cloud_key", "traces_key"])
558
+ rotated.add(field);
530
559
  const rotatedEnv = new Set([...rotated].map((field) => (field === "flow_key" ? FLOW_KEY : SETUP_ENV[field])));
531
560
  // An empty value (`cp .env.example .env`) is missing: the server mints it. A model key counts in
532
561
  // the shell too (npm start reads both): FLOW_KEY is made only when it is not named as had, and
@@ -542,19 +571,22 @@ async function setupEnv(options, json, out, say, forLink) {
542
571
  // FLOW_FRAMEWORK_TRY_URL is one channel's page: link's channel when it is open, else the server's (Telegram's).
543
572
  const withTryUrl = (fields) => {
544
573
  const code = fields.join_code;
574
+ // The workspace the answer put the agent in, kept only when one was asked for.
575
+ if (workspace !== undefined && typeof fields.workspace_id === "string" && fields.workspace_id)
576
+ fields = { ...fields, [WORKSPACE_FIELD]: fields.workspace_id };
545
577
  if (!forLink || typeof fields[`${forLink.channel}_sender`] !== "string" || typeof code !== "string" || !code)
546
578
  return fields;
547
579
  const pages = fields.try_urls;
548
580
  const page = typeof pages?.[forLink.channel] === "string" ? pages[forLink.channel] : tryLink(url, code, forLink.channel);
549
581
  return { ...fields, try_url: page };
550
582
  };
551
- const chosenPage = (n) => forLink !== undefined && n === SETUP_ENV.try_url;
583
+ const chosenPage = (n) => (forLink !== undefined && n === SETUP_ENV.try_url) || n === WORKSPACE_ENV;
552
584
  let answer;
553
585
  // For link: the server's refusal when Messaging's part came (a join code) and only observe's failed.
554
586
  let observeFailed;
555
587
  try {
556
588
  // Every channel: each comes back with its shared sender only while it is open.
557
- answer = await setup(url, token, { name, hostname: hostname(), channels: [...CHANNELS], have, ...(rotate ? { rotate } : {}), ...(rotateNow ? { rotate_now: true } : {}) });
589
+ answer = await setup(url, token, { name, hostname: hostname(), channels: [...CHANNELS], have, ...(workspace !== undefined ? { workspace } : {}), ...(rotate ? { rotate } : {}), ...(rotateNow ? { rotate_now: true } : {}) });
558
590
  }
559
591
  catch (error) {
560
592
  // Texting needs only Messaging's part, so link goes on without observe's (traces and the test
@@ -579,6 +611,12 @@ async function setupEnv(options, json, out, say, forLink) {
579
611
  const had = new Map(before);
580
612
  // --replace takes every new value but a model key: only --rotate key replaces FLOW_KEY.
581
613
  const written = writeEnv(file, lines, before, envEntries(withTryUrl(fields)), (n) => rotatedEnv.has(n) || (options.replace && !MODEL_KEY_NAMES.includes(n)) || chosenPage(n));
614
+ // An older flow.engineer ignores the field and sets the agent up in the personal workspace.
615
+ if (workspace !== undefined && !observeFailed && (typeof answer.workspace_id !== "string" || !answer.workspace_id)) {
616
+ const stop = new Stop("workspace_unsupported", "flow.engineer does not support --workspace yet; the agent was set up in your personal workspace.", undefined, EXIT.refused);
617
+ stop.warnings = warnings;
618
+ throw stop;
619
+ }
582
620
  if (typeof answer.join_code !== "string" || !answer.join_code) {
583
621
  const kept = written.filter((w) => w.action === "added" || w.action === "replaced").map((w) => w.name);
584
622
  const stop = new Stop("no_join_code", `flow.engineer gave no join code${kept.length ? ` (the keys it made are in .env: ${kept.join(", ")})` : ""}, so there is no phone link yet.`, answer.next?.[0] ?? NEXT.setup, EXIT.refused);
@@ -586,7 +624,8 @@ async function setupEnv(options, json, out, say, forLink) {
586
624
  throw stop;
587
625
  }
588
626
  if (observeFailed) {
589
- const missing = [SETUP_ENV.cloud_key, SETUP_ENV.traces_key].filter((n) => !before.get(n));
627
+ // Moving workspaces, the old workspace's keys still in .env do not count.
628
+ const missing = [SETUP_ENV.cloud_key, SETUP_ENV.traces_key].filter((n) => !before.get(n) || (moving && before.get(n) === had.get(n)));
590
629
  const why = observeFailed.why.replace(/\.$/, "");
591
630
  warnings.push(`Traces and tests won't work${missing.length ? ` until .env has ${missing.join(" and ")}` : ""}: ${why}.${observeFailed.fix ? ` Then: ${observeFailed.fix}` : ""}`);
592
631
  }
@@ -621,6 +660,7 @@ async function setupEnv(options, json, out, say, forLink) {
621
660
  whatsapp_link: answer.whatsapp_link ?? null, imessage_link: answer.imessage_link ?? null, try_urls: links,
622
661
  direct_links: Object.fromEntries(Object.entries(direct).map(([c, d]) => [c, d.link])), join_message: Object.values(direct)[0]?.message ?? null,
623
662
  trial, model_key: model.report,
663
+ workspace: typeof answer.workspace_id === "string" && answer.workspace_id ? { id: answer.workspace_id, name: answer.workspace_name ?? null } : null,
624
664
  // FLOW_KEY's state, never its value: made in this call, with its id, mode and scopes.
625
665
  flow_key: { set: Boolean(before.get(FLOW_KEY) || process.env[FLOW_KEY]?.trim()), minted: flowKey?.minted === true,
626
666
  ...(flowKey?.minted ? { kid: flowKey.kid ?? null, mode: flowKey.mode ?? null, scopes: flowKey.scopes ?? [], previous_expires_at: flowKey.previous_expires_at ?? null } : {}) },
@@ -633,6 +673,8 @@ async function setupEnv(options, json, out, say, forLink) {
633
673
  say(` ${w.name}=${w.value} ${w.action}${w.action === "kept" ? (w.name === FLOW_KEY ? " (--rotate key makes a new one)" : " (yours differs; --replace takes the new one)") : ""}`);
634
674
  if (previous)
635
675
  say(previous);
676
+ if (answer.workspace_id)
677
+ say(`Workspace: ${answer.workspace_name ? `${answer.workspace_name} (${answer.workspace_id})` : answer.workspace_id}`);
636
678
  if (answer.dashboard_url)
637
679
  say(`Its dashboard: ${answer.dashboard_url}`);
638
680
  // Said by link instead, once, with its link.
@@ -669,6 +711,7 @@ function objectField(fields, name) {
669
711
  */
670
712
  function envEntries(fields) {
671
713
  const entries = Object.entries(SETUP_ENV).map(([field, envName]) => [envName, fields[field]]);
714
+ entries.push([WORKSPACE_ENV, fields[WORKSPACE_FIELD]]);
672
715
  const key = objectField(fields, "flow_key");
673
716
  if ((key?.env ?? FLOW_KEY) === FLOW_KEY && typeof key?.value === "string")
674
717
  entries.push([FLOW_KEY, key.value]);
@@ -745,7 +788,8 @@ async function status(json, out, say) {
745
788
  const named = JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf8")).name ?? basename(process.cwd());
746
789
  const name = named.replace(/^@[^/]*\//, "").replace(/[^A-Za-z0-9 ._-]/g, "-").slice(0, 64) || "my-agent";
747
790
  const have = [...Object.values(SETUP_ENV).filter((n) => n !== SETUP_ENV.try_url), ...MODEL_KEY_NAMES];
748
- server = await setup(url, current.token, { name, hostname: hostname(), channels: [...CHANNELS], have });
791
+ const workspace = value(WORKSPACE_ENV);
792
+ server = await setup(url, current.token, { name, hostname: hostname(), channels: [...CHANNELS], have, ...(workspace ? { workspace } : {}) });
749
793
  }
750
794
  }
751
795
  catch {
@@ -764,11 +808,13 @@ async function status(json, out, say) {
764
808
  ...(model.report.set && !model.next ? ["npm start"] : []),
765
809
  ...(!joinCode || !value(SETUP_ENV.messaging_key) ? [NEXT.link] : CHANNELS.filter((c) => channels[c].open && !channels[c].sender).map((c) => `${NEXT.link} --channel ${c}`)),
766
810
  ];
811
+ const workspace = value(WORKSPACE_ENV) || "personal";
767
812
  if (json)
768
- out({ model_key: model.report, flow_key: { set: Boolean(value(FLOW_KEY)) }, signed_in: account ? { email: account.email, account: account.account } : false, join_code: joinCode, messaging_key: Boolean(value(SETUP_ENV.messaging_key)), channels, next });
813
+ out({ model_key: model.report, flow_key: { set: Boolean(value(FLOW_KEY)) }, workspace, signed_in: account ? { email: account.email, account: account.account } : false, join_code: joinCode, messaging_key: Boolean(value(SETUP_ENV.messaging_key)), channels, next });
769
814
  if (model.report.env && model.report.env !== FLOW_KEY)
770
815
  say(value(FLOW_KEY) ? `${FLOW_KEY} is set.` : `${FLOW_KEY}: not set yet (${RUN} setup writes it).`);
771
816
  say(model.line);
817
+ say(`Workspace: ${workspace}`);
772
818
  say(account ? `Signed in as ${account.email}.` : current ? "Not signed in here (or flow.engineer could not be reached)." : "Not signed in.");
773
819
  for (const c of CHANNELS) {
774
820
  const { sender, open, trial } = channels[c];
@@ -828,7 +874,8 @@ async function link(options, json, out, say) {
828
874
  let trial;
829
875
  let had;
830
876
  let warnings = [];
831
- if (!value(SETUP_ENV.join_code) || !value(SETUP_ENV.messaging_key) || (needsSender && !value(SENDER_ENV[options.channel]))) {
877
+ const newWorkspace = options.workspace !== undefined && (movesWorkspace(options.workspace, value) || !value(WORKSPACE_ENV));
878
+ if (newWorkspace || !value(SETUP_ENV.join_code) || !value(SETUP_ENV.messaging_key) || (needsSender && !value(SENDER_ENV[options.channel]))) {
832
879
  agentFolder(process.cwd());
833
880
  if (!(await signedInAccount(loginUrl()))) {
834
881
  say("Not signed in to flow.engineer yet: signing in first.");
@@ -836,7 +883,7 @@ async function link(options, json, out, say) {
836
883
  }
837
884
  say("Setting up this agent's keys in .env.");
838
885
  had = value;
839
- ({ answer: { model_credit: credit, trial }, warnings } = await setupEnv({ replace: false, rotate: [], ci: false }, json, out, say, { channel: options.channel }));
886
+ ({ answer: { model_credit: credit, trial }, warnings } = await setupEnv({ replace: false, rotate: [], ci: false, workspace: options.workspace }, json, out, say, { channel: options.channel }));
840
887
  value = dotenvValue();
841
888
  }
842
889
  const joinCode = value(SETUP_ENV.join_code);
@@ -71,6 +71,9 @@ export interface FlowKey {
71
71
  }
72
72
  export interface SetupAnswer {
73
73
  agent_id: string;
74
+ /** The observe workspace (acc_...) the agent and its cloud and traces keys are in; absent from an older server. */
75
+ workspace_id?: string;
76
+ workspace_name?: string;
74
77
  /** Only the keys minted in this call come back (those the request did not say it has). */
75
78
  cloud_key?: string;
76
79
  traces_key?: string;
@@ -148,6 +151,7 @@ export declare function setup(url: string, token: string, request: {
148
151
  hostname: string;
149
152
  channels: string[];
150
153
  have: string[];
154
+ workspace?: string;
151
155
  rotate?: true | string[];
152
156
  rotate_now?: true;
153
157
  }): Promise<SetupAnswer>;
@@ -6,8 +6,8 @@
6
6
  // authorization_pending, slow_down, expired_token, access_denied, invalid_grant
7
7
  // POST /cli/revoke (bearer) {} ends this token
8
8
  // GET /cli/whoami (bearer) -> {account, email, token}
9
- // POST /cli/setup (bearer) {name, hostname, channels, have, rotate?, rotate_now?}
10
- // -> {agent_id, cloud_key?, traces_key?, messaging_key?, flow_key?, model_credit?, join_code, account, app_id,
9
+ // POST /cli/setup (bearer) {name, hostname, channels, have, workspace?, rotate?, rotate_now?}
10
+ // -> {agent_id, workspace_id?, workspace_name?, cloud_key?, traces_key?, messaging_key?, flow_key?, model_credit?, join_code, account, app_id,
11
11
  // dashboard_url, try_url, <channel>_sender?, <channel>_link? (telegram, whatsapp, imessage: each only while
12
12
  // that shared sender is open), try_urls?, trial?, minted, next}; a 502 carries every field that worked too;
13
13
  // flow_key is the agent's one Flow key, {env: "FLOW_KEY", value: "flow_test_...", kid, mode, scopes, agent_id,
@@ -18,6 +18,9 @@
18
18
  // the reason in warnings; every folder's setup makes its own key, the credit is the account's;
19
19
  // rotate is true (every key) or the answer fields to make again, such as ["flow_key"]; rotate_now: true
20
20
  // ends the old FLOW_KEY at once instead of 24 h later (previous_expires_at);
21
+ // workspace is an observe workspace's acc_ id or name (case-insensitive), 1 to 100 characters; absent: the
22
+ // personal one; workspace_id and workspace_name are the workspace the agent and its cloud and traces keys are in
23
+ // (whenever observe answered; an older server sends neither); 403 not_a_member, 409 workspace_ambiguous (pass the id);
21
24
  // 429 slow_down with Retry-After; 403 for a trial limit (sandbox_allowance_used, ...), its next prose
22
25
  // Any answer may carry warnings: up to 20 strings to show the owner as they are.
23
26
  // GET <model URL>/credit (bearer FLOW_KEY) -> {limit_usd, spent_usd, left_usd, default_model, models};
package/docs/agents.md CHANGED
@@ -63,9 +63,12 @@ before you hand it back; run them yourself, and do not offer them to the owner a
63
63
  and `init`, `setup`, `link` and `status` say so too, with the way to get one first in `next`.
64
64
  Each folder's `setup` makes its own `FLOW_KEY` (the credit is the account's);
65
65
  `setup --rotate key` replaces a leaked one (the old one works 24 h more; `--now` ends it at once).
66
+ - `setup --workspace <name|acc_id>` (and `link --workspace`) puts the agent, its cloud and traces keys in
67
+ that observe workspace of yours and keeps its `acc_` id in `.env` as `FLOW_FRAMEWORK_WORKSPACE`, which later
68
+ runs send; another workspace makes those two keys again (403 `not_a_member`, 409 `workspace_ambiguous`: pass the id).
66
69
  - `status` reports `FLOW_KEY` and the model key (never their values; `flow_key.set` in `--json`; with
67
70
  `FLOW_KEY` the model's key, the credit left from Flow, `model_key.flow_credit`; a key Flow does not
68
- know gets `setup --rotate key`), the sign-in, each channel's sender in `.env`
71
+ know gets `setup --rotate key`), the workspace (`FLOW_FRAMEWORK_WORKSPACE`, else `personal`), the sign-in, each channel's sender in `.env`
69
72
  and, signed in, which channels are open now with their trial counts. It writes nothing.
70
73
  - `chat` builds the agent from `src/agent.ts` and writes as its first test person. Each reply is
71
74
  printed (`agent: ...`) once the turn is over; `--json` gives one JSON line per thing it sends.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flow-engineer/flow",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "An open-source runtime for chat agents that feel like a person texting back.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {